unrealircd

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

commit 4e71d6feade725e626f1a0e2edd4a8eeafa47f26
parent aa3cc227c571062484313c221f7251f0974ad717
Author: acidvegas <acid.vegas@acid.vegas>
Date: Sat, 15 Jan 2022 00:16:34 -0500

Updated to 6.0.1.1

Diffstat:
ABSDmakefile | 4++++
ACONTRIBUTING.md | 5+++++
MConfig | 223++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
MMakefile.in | 26++++++++++++++++++--------
MMakefile.windows | 1303++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
MREADME.md | 5+++++
MSECURITY.md | 3++-
Mautoconf/config.guess | 1682+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mautoconf/config.sub | 2640+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mautoconf/m4/unreal.m4 | 105++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mconfigure | 660++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mconfigure.ac | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mdoc/Config.header | 8++++----
Mdoc/RELEASE-NOTES.md | 1206+++++++++++++++----------------------------------------------------------------
Mdoc/conf/badwords.conf | 68++++++++++++++++++++++++++++++++++++++++++++------------------------
Mdoc/conf/except.conf | 50++++++++++++++++++++++++++++----------------------
Mdoc/conf/modules.conf | 98++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Adoc/conf/snomasks.conf | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdoc/conf/tls/curl-ca-bundle.crt | 496++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mdoc/conf/unrealircd.hub.conf | 7++++---
Mdoc/conf/unrealircd.remote.conf | 114++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mdoc/translations.txt | 2+-
Mextras/build-tests/nix/build | 18++++++++++++++----
Mextras/build-tests/nix/configs/default | 14++++++++++----
Mextras/build-tests/nix/run-tests | 7++++++-
Mextras/build-tests/windows/build.bat | 54+++++++++++++++++++++++++++++-------------------------
Mextras/build-tests/windows/compilecmd/vs2019.bat | 39++++++++++++++++++++++++---------------
Mextras/c-ares.tar.gz | 0
Mextras/curlinstall | 2+-
Mextras/doxygen/Developers.md | 2+-
Mextras/doxygen/Doxyfile | 2+-
Aextras/geoip-classic.tar.gz | 0
Aextras/jansson.tar.gz | 0
Mextras/security/apparmor/unrealircd | 4++--
Aextras/tests/tls/cipherscan_profiles/openssl-300.txt | 27+++++++++++++++++++++++++++
Mextras/tls.cnf | 2+-
Mextras/unrealircd-upgrade-script.in | 44+++++++++++++++++++++++++++++++-------------
Minclude/channel.h | 2+-
Minclude/common.h | 14+-------------
Minclude/config.h | 47+++++++++++++++++++++++++++++++++--------------
Minclude/dbuf.h | 2+-
Minclude/dynconf.h | 80+++++++++++++++++++++++++++++++++----------------------------------------------
Minclude/fdlist.h | 8+++++---
Minclude/h.h | 850++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Minclude/ircsprintf.h | 2+-
Minclude/modules.h | 724++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Minclude/msg.h | 1-
Minclude/numeric.h | 298+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Dinclude/proto.h | 68--------------------------------------------------------------------
Minclude/setup.h.in | 9+++------
Minclude/struct.h | 694+++++++++++++++++++++++++++++++++++--------------------------------------------
Minclude/sys.h | 3---
Minclude/unrealircd.h | 2--
Dinclude/url.h | 13-------------
Minclude/version.h | 5+++--
Minclude/whowas.h | 2+-
Minclude/windows/setup.h | 8+++-----
Msrc/Makefile.in | 173+++++--------------------------------------------------------------------------
Msrc/aliases.c | 44++++++++++++++++++++++++--------------------
Msrc/api-channelmode.c | 957+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/api-clicap.c | 36+++++++++++++++++++++++-------------
Msrc/api-command.c | 20++++++++++----------
Msrc/api-efunctions.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/api-event.c | 36+++++++++---------------------------
Msrc/api-extban.c | 427+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/api-history-backend.c | 39+++++++++++++++++++--------------------
Msrc/api-isupport.c | 11++---------
Msrc/api-messagetag.c | 27+++++++++++++++++----------
Msrc/api-moddata.c | 33+++++++++++++++++++++++++--------
Msrc/api-usermode.c | 438++++++++++++++++++++++++++++---------------------------------------------------
Msrc/auth.c | 83++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/buildmod | 3++-
Msrc/channel.c | 692+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/conf.c | 6090+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/conf_preprocessor.c | 28++++++++++++++--------------
Msrc/crashreport.c | 6+++---
Msrc/crule.c | 7+------
Msrc/dbuf.c | 2+-
Msrc/debug.c | 14++++++++------
Msrc/dispatch.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/dns.c | 105+++++++++++++++++++++++++++-----------------------------------------------------
Msrc/fdlist.c | 93+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/hash.c | 358+++++--------------------------------------------------------------------------
Msrc/ircd.c | 382+++++++++++++++++++++++++------------------------------------------------------
Msrc/list.c | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Asrc/log.c | 1871+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/macosx/UnrealIRCd/AppModel.swift | 2+-
Msrc/match.c | 41++++++++++++++++++++++-------------------
Msrc/misc.c | 1079+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/modulemanager.c | 178++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/modules.c | 187+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/modules/Makefile.in | 617++++---------------------------------------------------------------------------
Msrc/modules/account-notify.c | 4++--
Msrc/modules/account-tag.c | 14+++++++-------
Msrc/modules/addmotd.c | 4++--
Msrc/modules/addomotd.c | 4++--
Msrc/modules/admin.c | 4++--
Msrc/modules/antimixedutf8.c | 69+++++++++++++++++++++++++++++++++------------------------------------
Msrc/modules/antirandom.c | 171++++++++++++++++++++++---------------------------------------------------------
Msrc/modules/authprompt.c | 87++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/modules/away.c | 71++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/modules/batch.c | 6+++---
Msrc/modules/blacklist.c | 192++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/modules/bot-tag.c | 10+++++-----
Msrc/modules/botmotd.c | 4++--
Msrc/modules/cap.c | 48+++++++++---------------------------------------
Msrc/modules/certfp.c | 37++++++++++++++++++++-----------------
Msrc/modules/chanmodes/Makefile.in | 92++++++++++---------------------------------------------------------------------
Msrc/modules/chanmodes/censor.c | 112++++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/modules/chanmodes/chanadmin.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/chanmodes/chanop.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/chanmodes/chanowner.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/delayjoin.c | 88++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/modules/chanmodes/floodprot.c | 212+++++++++++++++++++++++++++++++++++++------------------------------------------
Asrc/modules/chanmodes/halfop.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/history.c | 212++++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/modules/chanmodes/inviteonly.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/chanmodes/isregistered.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/issecure.c | 60++++++++++++++++++++++++++++++------------------------------
Asrc/modules/chanmodes/key.c | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/chanmodes/limit.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/link.c | 178+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Asrc/modules/chanmodes/moderated.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/nocolor.c | 26+++++++++++++-------------
Msrc/modules/chanmodes/noctcp.c | 12++++++------
Asrc/modules/chanmodes/noexternalmsgs.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/noinvite.c | 16++++++++--------
Msrc/modules/chanmodes/nokick.c | 10+++++-----
Msrc/modules/chanmodes/noknock.c | 22+++++++++++-----------
Msrc/modules/chanmodes/nonickchange.c | 8++++----
Msrc/modules/chanmodes/nonotice.c | 12++++++------
Msrc/modules/chanmodes/operonly.c | 35+++++++++++++++++++----------------
Msrc/modules/chanmodes/permanent.c | 15+++++++++------
Asrc/modules/chanmodes/private.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/regonly.c | 13++++++++-----
Msrc/modules/chanmodes/regonlyspeak.c | 24++++++++++++------------
Asrc/modules/chanmodes/secret.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/chanmodes/secureonly.c | 39++++++++++++++++++++-------------------
Msrc/modules/chanmodes/stripcolor.c | 24++++++++++++------------
Asrc/modules/chanmodes/topiclimit.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/chanmodes/voice.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/channeldb.c | 100++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/modules/charsys.c | 58+++++++++++++++++++++++++++++++++++++---------------------
Msrc/modules/chathistory.c | 169++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/modules/chghost.c | 209++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/modules/chgident.c | 20++++++++------------
Msrc/modules/chgname.c | 18+++++++-----------
Msrc/modules/clienttagdeny.c | 4++--
Dsrc/modules/cloak.c | 425-------------------------------------------------------------------------------
Asrc/modules/cloak_md5.c | 422+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/cloak_none.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/cloak_sha256.c | 411+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/close.c | 7++++---
Msrc/modules/connect.c | 25++++---------------------
Msrc/modules/connthrottle.c | 173+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/modules/cycle.c | 10++++++----
Msrc/modules/dccallow.c | 17+++++++++++------
Msrc/modules/dccdeny.c | 163++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/modules/echo-message.c | 10+++++-----
Msrc/modules/eos.c | 8++------
Msrc/modules/extbans/Makefile.in | 70+++++++++-------------------------------------------------------------
Msrc/modules/extbans/account.c | 34++++++++++++++++++++++------------
Msrc/modules/extbans/certfp.c | 42+++++++++++++++++++++---------------------
Asrc/modules/extbans/country.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/extbans/inchannel.c | 57+++++++++++++++++++++++++++++++--------------------------
Msrc/modules/extbans/join.c | 21++++++++-------------
Msrc/modules/extbans/msgbypass.c | 110++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/modules/extbans/nickchange.c | 23+++++++++--------------
Msrc/modules/extbans/operclass.c | 31++++++++++++++-----------------
Msrc/modules/extbans/partmsg.c | 15+++++++++------
Msrc/modules/extbans/quiet.c | 20++++++++------------
Msrc/modules/extbans/realname.c | 30++++++++++++++++--------------
Msrc/modules/extbans/securitygroup.c | 54++++++++++++++++++++++++------------------------------
Msrc/modules/extbans/textban.c | 111++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/modules/extbans/timedban.c | 178++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Asrc/modules/extended-monitor.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/extjwt.c | 1151+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/geoip_base.c | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/geoip_classic.c | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/geoip_csv.c | 838+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/geoip_maxmind.c | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/globops.c | 6++----
Msrc/modules/help.c | 12++++++------
Msrc/modules/hideserver.c | 70+++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/modules/history.c | 13++++++++-----
Msrc/modules/history_backend_mem.c | 126+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/modules/history_backend_null.c | 18+++++++++---------
Msrc/modules/ident_lookup.c | 10+++++-----
Msrc/modules/invite.c | 505+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/modules/ircops.c | 2+-
Msrc/modules/ison.c | 8+++++---
Msrc/modules/join.c | 453+++++++++++++++++++++++--------------------------------------------------------
Msrc/modules/jointhrottle.c | 28+++++++++++++---------------
Asrc/modules/json-log-tag.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/jumpserver.c | 103+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/modules/kick.c | 386+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/modules/kill.c | 36+++++++++++++++---------------------
Msrc/modules/knock.c | 26+++++++++++++-------------
Msrc/modules/labeled-response.c | 14+++++++-------
Msrc/modules/lag.c | 4++--
Msrc/modules/link-security.c | 62+++++++++++++++++++++++++++++++-------------------------------
Msrc/modules/links.c | 4++--
Msrc/modules/list.c | 42++++++++++++++++--------------------------
Msrc/modules/locops.c | 6++----
Msrc/modules/lusers.c | 16+++++++++-------
Msrc/modules/map.c | 12++++++------
Msrc/modules/md.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Dsrc/modules/mdex.c | 317-------------------------------------------------------------------------------
Msrc/modules/message-ids.c | 14++++++--------
Msrc/modules/message-tags.c | 20+++++++++++++-------
Msrc/modules/message.c | 267+++++++++++++++++++++++++++----------------------------------------------------
Msrc/modules/mkpasswd.c | 12++++++------
Msrc/modules/mode.c | 1760++++++++++++++++++++++++++++++-------------------------------------------------
Asrc/modules/monitor.c | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/motd.c | 8++++----
Msrc/modules/names.c | 63++++++++++++++++++++++++++-------------------------------------
Msrc/modules/netinfo.c | 66+++++++++++++++++++++++++++++++++---------------------------------
Msrc/modules/nick.c | 1260+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/modules/nocodes.c | 8++++----
Msrc/modules/oper.c | 149++++++++++++++++++++++++++++++++++++++-----------------------------------------
Asrc/modules/operinfo.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/opermotd.c | 2+-
Msrc/modules/part.c | 48++++++++++++++++++++++--------------------------
Msrc/modules/pass.c | 6+++---
Msrc/modules/pingpong.c | 8++++----
Msrc/modules/plaintext-policy.c | 4++--
Msrc/modules/protoctl.c | 174++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/modules/quit.c | 46++++++++++++++++++++++++++++++----------------
Msrc/modules/reply-tag.c | 12++++++------
Msrc/modules/reputation.c | 166+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/modules/require-module.c | 120+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/modules/restrict-commands.c | 100++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/modules/rmtkl.c | 26++++++++++++++------------
Msrc/modules/rules.c | 6+++---
Msrc/modules/sajoin.c | 142+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/modules/samode.c | 4++--
Msrc/modules/sapart.c | 74++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/modules/sasl.c | 53+++++++++++++++++++++++++----------------------------
Msrc/modules/sdesc.c | 10+++++-----
Msrc/modules/sendsno.c | 28+++++++++++++---------------
Msrc/modules/sendumode.c | 20++++----------------
Msrc/modules/server-time.c | 10+++++-----
Msrc/modules/server.c | 1522+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/modules/sethost.c | 6+++---
Msrc/modules/setident.c | 5++---
Msrc/modules/setname.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/modules/silence.c | 7++++---
Msrc/modules/sinfo.c | 50+++++++++++++++++++++++++-------------------------
Msrc/modules/sjoin.c | 575+++++++++++++++++++++++++++++++++----------------------------------------------
Asrc/modules/slog.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/modules/snomasks/Makefile.in | 55-------------------------------------------------------
Dsrc/modules/snomasks/dccreject.c | 70----------------------------------------------------------------------
Msrc/modules/sqline.c | 6+++---
Msrc/modules/squit.c | 18+++++++++++-------
Msrc/modules/staff.c | 233++++---------------------------------------------------------------------------
Msrc/modules/starttls.c | 9++++-----
Msrc/modules/stats.c | 349++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/modules/sts.c | 6+++---
Msrc/modules/svsjoin.c | 4++--
Msrc/modules/svskill.c | 6+++---
Msrc/modules/svslusers.c | 4++--
Msrc/modules/svsmode.c | 218++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/modules/svsmotd.c | 96+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/modules/svsnick.c | 40++++++++++++++++++++++------------------
Msrc/modules/svsnline.c | 2+-
Msrc/modules/svsnolag.c | 6+++---
Msrc/modules/svsnoop.c | 12+++++++-----
Msrc/modules/svspart.c | 6+++---
Msrc/modules/svssilence.c | 8+++++---
Msrc/modules/svssno.c | 46+++++++++-------------------------------------
Msrc/modules/svswatch.c | 4++--
Msrc/modules/swhois.c | 4++--
Msrc/modules/targetfloodprot.c | 72++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/modules/third/Makefile.in | 9++++++---
Msrc/modules/time.c | 4++--
Msrc/modules/tkl.c | 1321++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/modules/tkldb.c | 62+++++++++++++++++++++++++++++++-------------------------------
Msrc/modules/tls_antidos.c | 11+++++------
Asrc/modules/tls_cipher.c | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/topic.c | 126+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/modules/trace.c | 22+++++++++++-----------
Msrc/modules/tsctl.c | 8+++++---
Msrc/modules/typing-indicator.c | 10+++++-----
Msrc/modules/umode2.c | 4++--
Asrc/modules/unreal_server_compat.c | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/unsqline.c | 6+++---
Msrc/modules/user.c | 22++++++++++------------
Msrc/modules/userhost-tag.c | 16++++++++--------
Msrc/modules/userhost.c | 8+++++---
Msrc/modules/userip-tag.c | 16++++++++--------
Msrc/modules/userip.c | 11++++++-----
Msrc/modules/usermodes/Makefile.in | 53++++++++---------------------------------------------
Msrc/modules/usermodes/bot.c | 25++++++++++++++++---------
Msrc/modules/usermodes/censor.c | 90++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/modules/usermodes/noctcp.c | 8++++----
Msrc/modules/usermodes/nokick.c | 10+++++-----
Msrc/modules/usermodes/privacy.c | 2+-
Msrc/modules/usermodes/privdeaf.c | 6+++---
Msrc/modules/usermodes/regonlymsg.c | 8++++----
Msrc/modules/usermodes/secureonlymsg.c | 18+++++++++---------
Msrc/modules/usermodes/servicebot.c | 31++++++++++++++++---------------
Msrc/modules/usermodes/showwhois.c | 6+++---
Asrc/modules/usermodes/wallops.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/vhost.c | 67+++++++++++++++++++++++++++++++++++++++----------------------------
Dsrc/modules/wallops.c | 77-----------------------------------------------------------------------------
Asrc/modules/watch-backend.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/watch.c | 256++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/modules/webirc.c | 110++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/modules/webredir.c | 36++++++++++++++++++------------------
Msrc/modules/websocket.c | 383+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/modules/who_old.c | 121++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/modules/whois.c | 566++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/modules/whowas.c | 18++++++++++--------
Msrc/modules/whox.c | 122++++++++++++++++++++++++++++++++++---------------------------------------------
Dsrc/numeric.c | 1056-------------------------------------------------------------------------------
Msrc/openssl_hostname_validation.c | 24++++++++++++------------
Msrc/operclass.c | 6+++---
Msrc/parse.c | 161++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/random.c | 14+++++++-------
Msrc/send.c | 488+++++++++++++++++++++++++------------------------------------------------------
Msrc/serv.c | 578++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/socket.c | 512+++++++++++++++----------------------------------------------------------------
Msrc/support.c | 160++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/tls.c | 453++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/unrealdb.c | 43+++++++++++++++++++++++++++----------------
Dsrc/updconf.c | 1730-------------------------------------------------------------------------------
Dsrc/url.c | 458-------------------------------------------------------------------------------
Asrc/url_curl.c | 315+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/url_unreal.c | 1068+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/user.c | 418+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/utf8.c | 60++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/version.c.SH | 12+++++++++---
Msrc/whowas.c | 34+++++++++++++++++-----------------
Msrc/windows/UnrealIRCd.exe.manifest | 6+++---
Msrc/windows/gui.c | 119+++++++++++++++++++++++--------------------------------------------------------
Msrc/windows/service.c | 5++---
Msrc/windows/unrealinst.iss | 77++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/windows/unrealsvc.c | 4++--
Msrc/windows/win.c | 32++++++++++++++++----------------
Msrc/windows/wingui.rc | 6+++---
Munrealircd.in | 17++++++++++++-----

341 files changed, 33388 insertions(+), 26435 deletions(-)

diff --git a/BSDmakefile b/BSDmakefile
@@ -0,0 +1,4 @@
+.DONE:
+	@echo "Please use GNU Make (gmake) to build UnrealIRCd"
+.DEFAULT:
+	@echo "Please use GNU Make (gmake) to build UnrealIRCd"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+Help out and make UnrealIRCd a better product!
+
+You can do so by reporting issues, testing, programming, documenting,
+translating, helping others, and more.
+See https://www.unrealircd.org/docs/Contributing
diff --git a/Config b/Config
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # Config script for UnrealIRCd
-# (C) 2001-2019 The UnrealIRCd Team
+# (C) 2001-2021 The UnrealIRCd Team
 #
 # This configure script is free software; the UnrealIRCd Team gives 
 # unlimited permission to copy, distribute and modify as long as the
@@ -37,9 +37,6 @@ mkdir -p $TMPDIR
 mkdir -p $PRIVATELIBDIR
 
 # Do this even if we're not in advanced mode
-if [ "$SHOWLISTMODES" = "1" ] ; then
-	ARG="$ARG--with-showlistmodes "
-fi
 if [ "$ADVANCED" = "1" ] ; then
 if [ "$NOOPEROVERRIDE" = "1" ] ; then
 	ARG="$ARG--with-no-operoverride "
@@ -53,12 +50,21 @@ ARG="$ARG--enable-ssl "
 else
 ARG="$ARG--enable-ssl=$SSLDIR "
 fi
+# Ensure we install curl even if someone does ./Config -quick...
+if [ "x$CURLDIR" = "x$UNREALCWD/extras/curl" ]; then
+	INSTALLCURL=1
+else
+	# And that the path does not refer to eg an old unrealircd-1.2.3 either ;)
+	if echo "$CURLDIR"|egrep -qi extras.*curl; then
+		echo "WARNING: Potentially old cURL directory encountered ($CURLDIR)."
+		echo "I am changing the cURL directory to $UNREALCWD/extras/curl and forcing a rebuild of cURL."
+		CURLDIR="$UNREALCWD/extras/curl"
+		INSTALLCURL=1
+	fi
+fi
 if [ "$REMOTEINC" = "1" ] ; then
 ARG="$ARG--enable-libcurl=$CURLDIR "
 fi
-if [ "$PREFIXAQ" != "1" ]; then
-ARG="$ARG--disable-prefixaq "
-fi
 if [ "$MAXCONNECTIONS_REQUEST" != "auto" ]; then
 	ARG="$ARG--with-maxconnections=$MAXCONNECTIONS_REQUEST "
 fi
@@ -77,14 +83,19 @@ ARG="$ARG--with-scriptdir=$BASEPATH "
 ARG="$ARG--with-nick-history=$NICKNAMEHISTORYLENGTH "
 ARG="$ARG--with-permissions=$DEFPERM "
 ARG="$ARG--enable-dynamic-linking "
+if [ "$GEOIP" = "classic" ]; then
+	ARG="$ARG--enable-geoip-classic "
+fi
+if [ "$GEOIP" = "libmaxminddb" ]; then
+	ARG="$ARG--enable-libmaxminddb "
+fi
+if [ "$SANITIZER" = "asan" ]; then
+	ARG="$ARG--enable-asan "
+fi
 ARG="$ARG $EXTRAPARA "
 CONF="./configure $ARG"
 # remove possibly old instances of curl in ~/unrealircd/lib/
 rm -f $PRIVATELIBDIR/*curl* 1>/dev/null 2>&1
-# Ensure we install curl even if someone does ./Config -quick...
-if [ "x$CURLDIR" = "x$UNREALCWD/extras/curl" ]; then
-	INSTALLCURL=1
-fi
 if [ "x$INSTALLCURL" = "x1" ]; then
 	extras/curlinstall "$PRIVATELIBDIR" || exit 1
 fi
@@ -127,8 +138,8 @@ if [ "$QUICK" != "1" ] ; then
 				TEST="No"
 			fi
 			echo ""
-			echo "UnrealIRCd requires an SSL certificate in order to work."
-			echo "Do you want to generate an SSL certificate for the IRCd?"
+			echo "UnrealIRCd requires a TLS certificate in order to work."
+			echo "Do you want to generate a TLS certificate for the IRCd?"
 			echo "Only answer No if you already have one."
 			echo $n "[$TEST] -> $c"
 			read cc
@@ -152,62 +163,33 @@ if [ "$QUICK" != "1" ] ; then
 		if [ "$GENCERTIFICATE" = 1 ]; then
 			echo
 			echo "*******************************************************************************"
-			echo "Next you will be asked some questions in order to generate the SSL certificate."
+			echo "Next you will be asked some questions in order to generate the TLS certificate."
 			echo "IMPORTANT: If you don't own a domain or don't know what to answer, then you can"
 			echo "           simply press ENTER or use fictional names for each question!"
 			echo "*******************************************************************************"
 			echo "Press ENTER to continue"
 			read cc
-			make pem
+			$MAKE pem
 			echo "Certificate created successfully."
 			sleep 1
 		else
-			echo "Ok, not generating SSL certificate. Make sure that the certificate and key"
+			echo "Ok, not generating TLS certificate. Make sure that the certificate and key"
 			echo "are installed in conf/tls/server.cert.pem and conf/tls/server.key.pem prior to starting the IRCd."
 		fi
 	else
-		echo "SSL certificate already exists in configuration directory, no need to regenerate."
+		echo "TLS certificate already exists in configuration directory, no need to regenerate."
 	fi
 fi
 
 # Silently force a 'make clean' as otherwise part (or whole) of the
 # compiled source could be using different settings than the user
 # just requested when re-running ./Config.
-make clean 1>/dev/null 2>&1
+$MAKE clean 1>/dev/null 2>&1
 }
 
 RUN_ADVANCED () {
 TEST=""
 while [ -z "$TEST" ] ; do
-	if [ "$SHOWLISTMODES" = "1" ] ; then
-		TEST="Yes"
-	else
-		TEST="No"
-	fi
-	echo ""
-	echo "Do you want to show the modes a channel has set in the /list output?"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		cc=$TEST
-	fi
-	case "$cc" in
-	[Yy]*)
-		SHOWLISTMODES="1"
-		;;
-	[Nn]*)
-		SHOWLISTMODES=""
-		;;
-	*)
-		echo ""
-		echo "You must enter either Yes or No"
-		TEST=""
-		;;
-	esac
-done
-
-TEST=""
-while [ -z "$TEST" ] ; do
 	if [ "$NOOPEROVERRIDE" = "1" ] ; then
 		TEST="Yes"
 	else
@@ -271,16 +253,16 @@ UNREALCWD="`pwd`"
 BASEPATH="$HOME/unrealircd"
 DEFPERM="0600"
 SSLDIR=""
-NICKNAMEHISTORYLENGTH="100"
+NICKNAMEHISTORYLENGTH="2000"
 MAXCONNECTIONS_REQUEST="auto"
 REMOTEINC="1"
 CURLDIR=""
-PREFIXAQ="0"
-SHOWLISTMODES="0"
 NOOPEROVERRIDE=""
 OPEROVERRIDEVERIFY=""
 GENCERTIFICATE="1"
 EXTRAPARA=""
+SANITIZER=""
+GEOIP="classic"
 if [ "`eval echo -n 'a'`" = "-n a" ] ; then
 	c="\c"
 else
@@ -333,6 +315,33 @@ if [ "`id -u`" = "0" ]; then
 	exit
 fi
 
+# Check for gmake early...
+if [ "$MAKE" = "" ]; then
+	MAKE="make"
+fi
+
+if ! $MAKE --version 2>&1|grep -q "GNU Make"; then
+	# So $MAKE is not GNU make, but do we have gmake?
+	if gmake --version 2>&1|grep -q "GNU Make"; then
+		# Great, use that one!
+		MAKE="gmake"
+	else
+		# Both $MAKE and gmake are not GNU make, do we have a working $MAKE at all?
+		if $MAKE --version 1>/dev/null 2>&1; then
+			echo "Your system has 'make' but UnrealIRCd requires GNU Make ('gmake')"
+			echo "Please install 'gmake' (eg 'pkg install gmake' on BSD)."
+			exit 1
+		else
+			echo "Your system does not have the required tools installed to build UnrealIRCd."
+			echo "Please check https://www.unrealircd.org/docs/Installing_from_source"
+			echo "and install the required tools listed under 'Prerequisites'."
+			echo "After that, you can run ./Config again"
+			exit 1
+		fi
+	fi
+fi
+
+
 clear
 
 if [ -f "doc/Config.header" -a -z "$NOINTRO" ] ; then
@@ -347,7 +356,7 @@ echo "We will now ask you a number of questions. You can just press ENTER to acc
 echo ""
 
 # This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-UNREALRELEASES="unrealircd-5.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 unrealircd-5.0.5.1 unrealircd-5.0.5 unrealircd-5.0.4 unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0"
+UNREALRELEASES="unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6"
 if [ -f "config.settings" ]; then
 	. ./config.settings
 else
@@ -421,7 +430,7 @@ fi
 TEST="$BASEPATH"
 echo ""
 echo "In what directory do you want to install UnrealIRCd?"
-echo "(Note: UnrealIRCd 5 will need to be installed somewhere."
+echo "(Note: UnrealIRCd 6 will need to be installed somewhere."
 echo " If this directory does not exist it will be created.)"
 echo $n "[$TEST] -> $c"
 read cc
@@ -507,9 +516,12 @@ while [ -z "$TEST" ] ; do
 		TEST="No"
 	fi
 	echo ""
-	echo "Do you want to enable remote includes?"
-	echo "This allows stuff like this in your configuration file:"
-	echo "include \"https://www.somesite.org/files/opers.conf\";"
+	echo "UnrealIRCd comes with support for 'remote includes', this allows things like:"
+	echo "include \"https://www.example.org/files/opers.conf\";"
+	echo "Do you want to compile with the libcurl library to enable additional protocols?"
+	echo "If you answer 'No' then only https:// links will work for remote includes."
+	echo "Answer 'Yes' if you need other protocols, such as plaintext http, ftp, tftp or smb."
+	echo "Most people answer 'No' here because they don't use remote includes or only need https."
 	echo $n "[$TEST] -> $c"
 	read cc
 	if [ -z "$cc" ] ; then
@@ -663,32 +675,22 @@ fi
 
 TEST=""
 while [ -z "$TEST" ] ; do
-	if [ "$PREFIXAQ" = "1" ] ; then
-		TEST="Yes"
-	else
-		TEST="No"
-	fi
+	TEST="$NICKNAMEHISTORYLENGTH"
 	echo ""
-	echo "Do you want to enable prefixes for chanadmin and chanowner?"
-	echo "This will give +a the & prefix and ~ for +q (just like +o is @)"
-	echo "Supported by the major clients (mIRC, xchat, epic, eggdrop, Klient,"
-	echo "PJIRC, irssi, CGI:IRC, etc.)"
-	echo "This feature should be enabled/disabled network-wide."
+	echo "How far back do you want to keep the nickname history?"
 	echo $n "[$TEST] -> $c"
 	read cc
 	if [ -z "$cc" ] ; then
-		cc=$TEST
+		NICKNAMEHISTORYLENGTH=$TEST
+		break
 	fi
 	case "$cc" in
-	[Yy]*)
-		PREFIXAQ="1"
-		;;
-	[Nn]*)
-		PREFIXAQ=""
+	[1-9]*)
+		NICKNAMEHISTORYLENGTH="$cc"
 		;;
 	*)
 		echo ""
-		echo "You must enter either Yes or No"
+		echo "You must enter a number"
 		TEST=""
 		;;
 	esac
@@ -696,22 +698,34 @@ done
 
 TEST=""
 while [ -z "$TEST" ] ; do
-	TEST="$NICKNAMEHISTORYLENGTH"
+	TEST="$GEOIP"
 	echo ""
-	echo "How far back do you want to keep the nickname history?"
+	echo "GeoIP is a feature that allows converting an IP address to a location (country)"
+	echo "You have three options in UnrealIRCd:"
+	echo "     classic: This is the DEFAULT geoip engine that should work on all systems"
+	echo "libmaxminddb: This uses the libmaxminddb library. If you want to use it then"
+	echo "              you need to install the libmaxminddb library on your system first"
+	echo "        none: Don't built with any geoip feature"
+	echo "Choose one of: classic, libmaxminddb, none"
 	echo $n "[$TEST] -> $c"
 	read cc
 	if [ -z "$cc" ] ; then
-		NICKNAMEHISTORYLENGTH=$TEST
+		GEOIP=$TEST
 		break
 	fi
 	case "$cc" in
-	[1-9]*)
-		NICKNAMEHISTORYLENGTH="$cc"
+	classic)
+		GEOIP="$cc"
+		;;
+	libmaxminddb)
+		GEOIP="$cc"
+		;;
+	none)
+		GEOIP="$cc"
 		;;
 	*)
 		echo ""
-		echo "You must enter a number"
+		echo "Invalid choice: $cc"
 		TEST=""
 		;;
 	esac
@@ -753,6 +767,42 @@ if [ -n "$ADVANCED" ] ; then
 	RUN_ADVANCED
 fi
 
+TEST=""
+while [ -z "$TEST" ] ; do
+	if [ "$SANITIZER" = "asan" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Are you running UnrealIRCd as a test, debugging a problem or developing a module?"
+	echo "Then it is possible to run with AddressSanitizer enabled. This will make it"
+	echo "catch bugs such as out-of-bounds and other memory corruption issues, which can"
+	echo "be really helpful in some cases. The downside is that it will consume a lot"
+	echo "more memory and run slower too. So, only answer 'Yes' if you are OK with this."
+	echo "Also, on some systems (notably FreeBSD), when you enable AddressSanitizer,"
+	echo "UnrealIRCd may fail to start. So when in doubt, answer 'No'."
+	echo "Do you want to enable AddressSanitizer?"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
+	[Yy]*)
+		SANITIZER="asan"
+		;;
+	[Nn]*)
+		SANITIZER=""
+		;;
+	*)
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
+done
+
 TEST="$EXTRAPARA"
 echo ""
 echo "Would you like to pass any custom parameters to configure?"
@@ -784,17 +834,17 @@ CACHEDIR="$CACHEDIR"
 DOCDIR="$DOCDIR"
 TMPDIR="$TMPDIR"
 PRIVATELIBDIR="$PRIVATELIBDIR"
-PREFIXAQ="$PREFIXAQ"
 MAXCONNECTIONS_REQUEST="$MAXCONNECTIONS_REQUEST"
 NICKNAMEHISTORYLENGTH="$NICKNAMEHISTORYLENGTH"
+GEOIP="$GEOIP"
 DEFPERM="$DEFPERM"
 SSLDIR="$SSLDIR"
 REMOTEINC="$REMOTEINC"
 CURLDIR="$CURLDIR"
-SHOWLISTMODES="$SHOWLISTMODES"
 NOOPEROVERRIDE="$NOOPEROVERRIDE"
 OPEROVERRIDEVERIFY="$OPEROVERRIDEVERIFY"
 GENCERTIFICATE="$GENCERTIFICATE"
+SANITIZER="$SANITIZER"
 EXTRAPARA="$EXTRAPARA"
 ADVANCED="$ADVANCED"
 __EOF__
@@ -808,16 +858,17 @@ cat << __EOF__
 |_______________________________________________________________________|
 |_______________________________________________________________________|
 |                                                                       |
-| Now all you have to do is type 'make' and let it compile. When that's |
-| done, you will receive other instructions on what to do next.         |
+|                        - The UnrealIRCd Team -                        |
 |                                                                       |
+|              Bram Matthys (Syzop) - syzop@unrealircd.org              |
+|       Krzysztof Beresztant (k4be) - k4be@unrealircd.org               |
+|                            Gottem - gottem@unrealircd.org             |
+|                                 i - i@unrealircd.org                  |
 |_______________________________________________________________________|
 |_______________________________________________________________________|
-|                        - The UnrealIRCd Team -                        |
 |                                                                       |
-| * Bram Matthys (Syzop)     syzop@unrealircd.org                       |
-| * Gottem                   gottem@unrealircd.org                      |
-| * i                        i@unrealircd.org                           |
+| Now all you have to do is type '$MAKE' and let it compile. When that's |
+| done, you will receive other instructions on what to do next.         |
 |_______________________________________________________________________|
 __EOF__
 
diff --git a/Makefile.in b/Makefile.in
@@ -34,11 +34,11 @@ FROMDOS=/home/cmunk/bin/4dos
 #
 
 #XCFLAGS=-O -g -export-dynamic
-IRCDLIBS=@IRCDLIBS@ @PCRE2_LIBS@ @ARGON2_LIBS@ @CARES_LIBS@ @SODIUM_LIBS@ @PTHREAD_LIBS@
+IRCDLIBS=@IRCDLIBS@ @PCRE2_LIBS@ @ARGON2_LIBS@ @CARES_LIBS@ @SODIUM_LIBS@ @JANSSON_LIBS@ @PTHREAD_LIBS@
 CRYPTOLIB=@CRYPTOLIB@
 OPENSSLINCLUDES=
 
-XCFLAGS=@PTHREAD_CFLAGS@ @PCRE2_CFLAGS@ @ARGON2_CFLAGS@ @CARES_CFLAGS@ @SODIUM_CFLAGS@ @CFLAGS@ @HARDEN_CFLAGS@ @CPPFLAGS@
+XCFLAGS=@PTHREAD_CFLAGS@ @PCRE2_CFLAGS@ @ARGON2_CFLAGS@ @CARES_CFLAGS@ @SODIUM_CFLAGS@ @JANSSON_CFLAGS@ @CFLAGS@ @HARDEN_CFLAGS@ @CPPFLAGS@
 #
 # use the following on MIPS:
 #CFLAGS= -systype bsd43 -DSYSTYPE_BSD43 -I$(INCLUDEDIR)
@@ -89,7 +89,14 @@ XCFLAGS=@PTHREAD_CFLAGS@ @PCRE2_CFLAGS@ @ARGON2_CFLAGS@ @CARES_CFLAGS@ @SODIUM_C
 #          you are not defining CMDLINE_CONFIG 
 IRCDMODE = 711
 
+# Objects that are optional due to optional libraries:
 URL=@URL@
+GEOIP_CLASSIC_OBJECTS=@GEOIP_CLASSIC_OBJECTS@
+GEOIP_CLASSIC_LIBS=@GEOIP_CLASSIC_LIBS@
+GEOIP_CLASSIC_CFLAGS=@GEOIP_CLASSIC_CFLAGS@
+GEOIP_MAXMIND_OBJECTS=@GEOIP_MAXMIND_OBJECTS@
+LIBMAXMINDDB_CFLAGS=@LIBMAXMINDDB_CFLAGS@
+LIBMAXMINDDB_LIBS=@LIBMAXMINDDB_LIBS@
 
 # Where is your openssl binary
 OPENSSLPATH=@OPENSSLPATH@
@@ -116,7 +123,13 @@ MAKEARGS =	'CFLAGS=${CFLAGS}' 'CC=${CC}' 'IRCDLIBS=${IRCDLIBS}' \
 		'SHELL=${SHELL}' \
 		'CRYPTOLIB=${CRYPTOLIB}' \
 		'CRYPTOINCLUDES=${CRYPTOINCLUDES}' \
-		'URL=${URL}'
+		'URL=${URL}' \
+		'GEOIP_CLASSIC_OBJECTS=${GEOIP_CLASSIC_OBJECTS}' \
+		'GEOIP_CLASSIC_LIBS=${GEOIP_CLASSIC_LIBS}' \
+		'GEOIP_CLASSIC_CFLAGS=${GEOIP_CLASSIC_CFLAGS}' \
+		'GEOIP_MAXMIND_OBJECTS=${GEOIP_MAXMIND_OBJECTS}' \
+		'LIBMAXMINDDB_CFLAGS=${LIBMAXMINDDB_CFLAGS}' \
+		'LIBMAXMINDDB_LIBS=${LIBMAXMINDDB_LIBS}'
 
 custommodule:
 	@if test -z "${MODULEFILE}"; then echo "Please set MODULEFILE when calling \`\`make custommodule''. For example, \`\`make custommodule MODULEFILE=callerid''." >&2; exit 1; fi
@@ -135,7 +148,7 @@ build: Makefile
 	done
 	@echo ''
 	@echo '* UnrealIRCd compiled successfully'
-	@echo '* YOU ARE NOT DONE YET! Run "make install" to install UnrealIRCd !'
+	@echo '* YOU ARE NOT DONE YET! Run "${MAKE} install" to install UnrealIRCd !'
 	@echo ''
 
 clean:
@@ -183,9 +196,6 @@ install: all
 	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/chanmodes
 	@rm -f $(DESTDIR)@MODULESDIR@/chanmodes/*.so 1>/dev/null 2>&1
 	$(INSTALL) -m 0700 src/modules/chanmodes/*.so $(DESTDIR)@MODULESDIR@/chanmodes
-	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/snomasks
-	@rm -f $(DESTDIR)@MODULESDIR@/snomasks/*.so 1>/dev/null 2>&1
-	$(INSTALL) -m 0700 src/modules/snomasks/*.so $(DESTDIR)@MODULESDIR@/snomasks
 	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/extbans
 	@rm -f $(DESTDIR)@MODULESDIR@/extbans/*.so 1>/dev/null 2>&1
 	$(INSTALL) -m 0700 src/modules/extbans/*.so $(DESTDIR)@MODULESDIR@/extbans
@@ -248,7 +258,7 @@ pem:	extras/tls.cnf
               -config extras/tls.cnf -sha256 -out server.req.pem \
               -key server.key.pem -nodes
 	@echo "Generating self-signed certificate..."
-	$(OPENSSLPATH) req -x509 -days 3650 -sha256 -in server.req.pem \
+	$(OPENSSLPATH) req -x509 -days 3650 -sha256 -nodes -in server.req.pem \
                -key server.key.pem -out server.cert.pem
 	@echo "Setting permissions on server.*.pem files..."
 	chmod o-rwx server.req.pem server.key.pem server.cert.pem
diff --git a/Makefile.windows b/Makefile.windows
@@ -25,15 +25,25 @@ MT=mt
 #ARGON2LIB="Argon2RefDll.lib"
 
 ### SODIUM ###
-#SODIUM_LIB_DIR="C:\dev\unrealircd-5-libs\libsodium\......."
-#SODIUM_INC_DIR="C:\dev\unrealircd-5-libs\libsodium\......."
+#SODIUM_LIB_DIR="C:\dev\unrealircd-6-libs\libsodium\......."
+#SODIUM_INC_DIR="C:\dev\unrealircd-6-libs\libsodium\......."
 #SODIUMLIB="libsodium.lib"
 
+### JANSSON ###
+#JANSSON_LIB_DIR="C:\dev\unrealircd-6-libs\jansson\lib"
+#JANSSON_INC_DIR="C:\dev\unrealircd-6-libs\jansson\include"
+#JANSSONLIB="jansson.lib"
+
 ### C-ARES ####
 #CARES_LIB_DIR="C:\dev\c-ares\vc\cares\dll-release"
 #CARES_INC_DIR="C:\dev\c-ares"
 #CARESLIB="cares.lib"
 
+### GEOIP CLASSIC ###
+#GEOIPCLASSIC_LIB_DIR="c:\dev\unrealircd-6-libs\GeoIP\libGeoIP" ^
+#GEOIPCLASSIC_INC_DIR="c:\dev\unrealircd-6-libs\GeoIP\libGeoIP" ^
+#GEOIPCLASSICLIB="GeoIP.lib"
+
 ##### REMOTE INCLUDES ####
 #To enable remote include support you must have libcurl installed on your
 #system and it must have ares support enabled.
@@ -106,9 +116,16 @@ SODIUM_INC=/I "$(SODIUM_INC_DIR)"
 SODIUM_LIB=/LIBPATH:"$(SODIUM_LIB_DIR)"
 !ENDIF
 
+!IFDEF JANSSON_INC_DIR
+JANSSON_INC=/I "$(JANSSON_INC_DIR)"
+!ENDIF
+!IFDEF JANSSON_LIB_DIR
+JANSSON_LIB=/LIBPATH:"$(JANSSON_LIB_DIR)"
+!ENDIF
+
 !IFDEF USE_REMOTEINC
 CURLCFLAGS=/D USE_LIBCURL
-CURLOBJ=SRC/URL.OBJ
+CURLOBJ=SRC/URL_CURL.OBJ
 CURLLIB=libcurl.lib
 !IFDEF LIBCURL_INC_DIR
 LIBCURL_INC=/I "$(LIBCURL_INC_DIR)"
@@ -137,15 +154,15 @@ DBGLFLAG=/debug
 MODDBGCFLAG=/LDd /MD /Zi
 !ENDIF 
 
-STDOPTIONS=$(PCRE2_INC) $(ARGON2_INC) $(SODIUM_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /nologo \
+STDOPTIONS=$(PCRE2_INC) $(ARGON2_INC) $(SODIUM_INC) $(JANSSON_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) \
+ /J /I ./INCLUDE /nologo \
  $(CURLCFLAGS) /D FD_SETSIZE=16384 $(SSLCFLAGS) /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE \
  /D FAKELAG_CONFIGURABLE=1 \
- /W3 /wd4267 /wd4101 /wd4018 /wd4244 /wd4996 /WX \
- /analyze:ruleset extras\VStudioAnalyze.ruleset
+ /W3 /wd4267 /wd4101 /wd4018 /wd4244 /wd4996 /WX /analyze:ruleset extras\VStudioAnalyze.ruleset
 STDLIBS=$(CARES_LIB) $(CARESLIB) $(PCRE2_LIB) $(PCRE2LIB) $(ARGON2_LIB) $(ARGON2LIB) \
- $(SODIUM_LIB) $(SODIUMLIB) $(LIBRESSL_LIB) $(SSLLIB) $(LIBCURL_LIB) $(CURLLIB)
-CFLAGS=$(DBGCFLAG) $(STDOPTIONS) /c /Fosrc/
-CFLAGSST=$(DBGCFLAGST) $(STDOPTIONS) /c /Fosrc/
+ $(SODIUM_LIB) $(SODIUMLIB) $(JANSSON_LIB) $(JANSSONLIB) $(LIBRESSL_LIB) $(SSLLIB) $(LIBCURL_LIB) $(CURLLIB)
+CFLAGS=$(DBGCFLAG) $(STDOPTIONS) /FS /MP1 /c /Fosrc/
+CFLAGSST=$(DBGCFLAGST) $(STDOPTIONS) /FS /MP1 /c /Fosrc/
 LFLAGS=kernel32.lib user32.lib gdi32.lib shell32.lib ws2_32.lib advapi32.lib \
  dbghelp.lib oldnames.lib comctl32.lib comdlg32.lib $(STDLIBS) \
  /def:UnrealIRCd.def /implib:UnrealIRCd.lib \
@@ -157,203 +174,249 @@ INCLUDES=./include/struct.h ./include/config.h ./include/sys.h \
  ./include/common.h ./include/version.h ./include/h.h ./include/numeric.h \
  ./include/msg.h ./include/setup.h ./include/dynconf.h
 
-EXP_OBJ_FILES=SRC/CHANNEL.OBJ SRC/SEND.OBJ SRC/SOCKET.OBJ \
- SRC/CONF.OBJ SRC/CONF_PREPROCESSOR.OBJ \
- SRC/FDLIST.OBJ SRC/DBUF.OBJ  \
- SRC/HASH.OBJ SRC/PARSE.OBJ SRC/IRCD.OBJ \
- SRC/WHOWAS.OBJ \
- SRC/MISC.OBJ SRC/MATCH.OBJ SRC/CRULE.OBJ \
- SRC/DEBUG.OBJ  SRC/SUPPORT.OBJ SRC/LIST.OBJ \
- SRC/NUMERIC.OBJ \
- SRC/SERV.OBJ SRC/USER.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/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/UPDCONF.OBJ SRC/CRASHREPORT.OBJ SRC/UNREALDB.OBJ \
- SRC/OPENSSL_HOSTNAME_VALIDATION.OBJ \
- SRC/UTF8.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 
-
-DLL_FILES=SRC/MODULES/CLOAK.DLL \
- SRC/MODULES/CHGHOST.DLL SRC/MODULES/SDESC.DLL SRC/MODULES/SETIDENT.DLL \
- SRC/MODULES/SETNAME.DLL SRC/MODULES/SETHOST.DLL SRC/MODULES/CHGIDENT.DLL \
- SRC/MODULES/SVSMOTD.DLL SRC/MODULES/SVSNLINE.DLL SRC/MODULES/WHO_OLD.DLL \
- SRC/MODULES/WHOX.DLL \
- SRC/MODULES/SWHOIS.DLL SRC/MODULES/SVSMODE.DLL SRC/MODULES/AWAY.DLL \
- SRC/MODULES/SVSNOOP.DLL SRC/MODULES/MKPASSWD.DLL \
- SRC/MODULES/SVSNICK.DLL \
- SRC/MODULES/CHGNAME.DLL \
- SRC/MODULES/LAG.DLL SRC/MODULES/MESSAGE.DLL \
- SRC/MODULES/OPER.DLL SRC/MODULES/PINGPONG.DLL SRC/MODULES/QUIT.DLL \
- SRC/MODULES/SENDUMODE.DLL \
- SRC/MODULES/SQLINE.DLL SRC/MODULES/KILL.DLL SRC/MODULES/TSCTL.DLL \
- SRC/MODULES/UNSQLINE.DLL \
- SRC/MODULES/WHOIS.DLL SRC/MODULES/TKL.DLL SRC/MODULES/VHOST.DLL \
- SRC/MODULES/CYCLE.DLL SRC/MODULES/SVSJOIN.DLL SRC/MODULES/SVSPART.DLL \
- SRC/MODULES/SVSLUSERS.DLL SRC/MODULES/SVSWATCH.DLL SRC/MODULES/SVSSILENCE.DLL \
- SRC/MODULES/SENDSNO.DLL SRC/MODULES/SVSSNO.DLL SRC/MODULES/SAJOIN.DLL \
- SRC/MODULES/SAPART.DLL SRC/MODULES/SAMODE.DLL SRC/MODULES/KICK.DLL \
- SRC/MODULES/TOPIC.DLL SRC/MODULES/INVITE.DLL SRC/MODULES/LIST.DLL \
- SRC/MODULES/TIME.DLL SRC/MODULES/SVSKILL.DLL SRC/MODULES/KNOCK.DLL \
- SRC/MODULES/UMODE2.DLL SRC/MODULES/SQUIT.DLL SRC/MODULES/PROTOCTL.DLL \
- SRC/MODULES/SJOIN.DLL SRC/MODULES/PASS.DLL SRC/MODULES/USERHOST.DLL \
- SRC/MODULES/ISON.DLL SRC/MODULES/SILENCE.DLL \
- SRC/MODULES/ADDMOTD.DLL SRC/MODULES/ADDOMOTD.DLL SRC/MODULES/WALLOPS.DLL \
- SRC/MODULES/GLOBOPS.DLL SRC/MODULES/LOCOPS.DLL \
- SRC/MODULES/ADMIN.DLL SRC/MODULES/TRACE.DLL SRC/MODULES/NETINFO.DLL \
- SRC/MODULES/LINKS.DLL SRC/MODULES/HELP.DLL SRC/MODULES/RULES.DLL \
- SRC/MODULES/CLOSE.DLL SRC/MODULES/MAP.DLL SRC/MODULES/EOS.DLL \
- SRC/MODULES/SERVER.DLL SRC/MODULES/STATS.DLL \
- SRC/MODULES/DCCDENY.DLL SRC/MODULES/WHOWAS.DLL \
- SRC/MODULES/CONNECT.DLL SRC/MODULES/DCCALLOW.DLL SRC/MODULES/USERIP.DLL \
- SRC/MODULES/NICK.DLL SRC/MODULES/USER.DLL SRC/MODULES/MODE.DLL \
- SRC/MODULES/WATCH.DLL SRC/MODULES/PART.DLL SRC/MODULES/JOIN.DLL \
- SRC/MODULES/MOTD.DLL SRC/MODULES/OPERMOTD.DLL SRC/MODULES/BOTMOTD.DLL \
- SRC/MODULES/LUSERS.DLL SRC/MODULES/NAMES.DLL SRC/MODULES/SVSNOLAG.DLL \
- SRC/MODULES/STARTTLS.DLL \
- SRC/MODULES/WEBREDIR.DLL \
- SRC/MODULES/CAP.DLL \
- SRC/MODULES/SASL.DLL \
- SRC/MODULES/TLS_ANTIDOS.DLL \
- SRC/MODULES/MD.DLL \
- SRC/MODULES/CERTFP.DLL \
- SRC/MODULES/WEBIRC.DLL \
- SRC/MODULES/WEBSOCKET.DLL \
- SRC/MODULES/BLACKLIST.DLL \
- SRC/MODULES/JOINTHROTTLE.DLL \
- SRC/MODULES/ANTIRANDOM.DLL \
- SRC/MODULES/HIDESERVER.DLL \
- SRC/MODULES/JUMPSERVER.DLL \
- SRC/MODULES/IRCOPS.DLL \
- SRC/MODULES/STAFF.DLL \
- SRC/MODULES/NOCODES.DLL \
- SRC/MODULES/CHARSYS.DLL \
- SRC/MODULES/ANTIMIXEDUTF8.DLL \
- SRC/MODULES/AUTHPROMPT.DLL \
- SRC/MODULES/SINFO.DLL \
- SRC/MODULES/REPUTATION.DLL \
- SRC/MODULES/CONNTHROTTLE.DLL \
- SRC/MODULES/CHANMODES/CENSOR.DLL \
- SRC/MODULES/CHANMODES/DELAYJOIN.DLL \
- SRC/MODULES/CHANMODES/FLOODPROT.DLL \
- SRC/MODULES/CHANMODES/ISSECURE.DLL \
- SRC/MODULES/CHANMODES/LINK.DLL \
- SRC/MODULES/CHANMODES/NOCOLOR.DLL \
- SRC/MODULES/CHANMODES/NOCTCP.DLL \
- SRC/MODULES/CHANMODES/NOINVITE.DLL \
- SRC/MODULES/CHANMODES/NOKICK.DLL \
- SRC/MODULES/CHANMODES/NOKNOCK.DLL \
- SRC/MODULES/CHANMODES/NONICKCHANGE.DLL \
- SRC/MODULES/CHANMODES/NONOTICE.DLL \
- SRC/MODULES/CHANMODES/OPERONLY.DLL \
- SRC/MODULES/CHANMODES/PERMANENT.DLL \
- SRC/MODULES/CHANMODES/REGONLY.DLL \
- SRC/MODULES/CHANMODES/REGONLYSPEAK.DLL \
- SRC/MODULES/CHANMODES/SECUREONLY.DLL \
- SRC/MODULES/CHANMODES/STRIPCOLOR.DLL \
- SRC/MODULES/CHANMODES/HISTORY.DLL \
- SRC/MODULES/USERMODES/CENSOR.DLL \
- SRC/MODULES/USERMODES/NOCTCP.DLL \
- SRC/MODULES/USERMODES/BOT.DLL \
- SRC/MODULES/USERMODES/SERVICEBOT.DLL \
- SRC/MODULES/USERMODES/SHOWWHOIS.DLL \
- SRC/MODULES/USERMODES/PRIVACY.DLL \
- SRC/MODULES/USERMODES/NOKICK.DLL \
- SRC/MODULES/USERMODES/REGONLYMSG.DLL \
- SRC/MODULES/USERMODES/PRIVDEAF.DLL \
- SRC/MODULES/USERMODES/SECUREONLYMSG.DLL \
- SRC/MODULES/SNOMASKS/DCCREJECT.DLL \
- SRC/MODULES/EXTBANS/ACCOUNT.DLL \
- SRC/MODULES/EXTBANS/INCHANNEL.DLL \
- SRC/MODULES/EXTBANS/JOIN.DLL \
- SRC/MODULES/EXTBANS/NICKCHANGE.DLL \
- SRC/MODULES/EXTBANS/QUIET.DLL \
- SRC/MODULES/EXTBANS/REALNAME.DLL \
- SRC/MODULES/EXTBANS/OPERCLASS.DLL \
- SRC/MODULES/EXTBANS/CERTFP.DLL \
- SRC/MODULES/EXTBANS/TEXTBAN.DLL \
- SRC/MODULES/EXTBANS/MSGBYPASS.DLL \
- SRC/MODULES/EXTBANS/TIMEDBAN.DLL \
- SRC/MODULES/EXTBANS/PARTMSG.DLL \
- SRC/MODULES/EXTBANS/SECURITYGROUP.DLL \
- SRC/MODULES/ACCOUNT-NOTIFY.DLL \
- SRC/MODULES/MESSAGE-TAGS.DLL \
- SRC/MODULES/BATCH.DLL \
- SRC/MODULES/ACCOUNT-TAG.DLL \
- SRC/MODULES/LABELED-RESPONSE.DLL \
- SRC/MODULES/LINK-SECURITY.DLL \
- SRC/MODULES/MESSAGE-IDS.DLL \
- SRC/MODULES/PLAINTEXT-POLICY.DLL \
- SRC/MODULES/SERVER-TIME.DLL \
- SRC/MODULES/STS.DLL \
- SRC/MODULES/TKLDB.DLL \
- SRC/MODULES/CHANNELDB.DLL \
- SRC/MODULES/HISTORY_BACKEND_MEM.DLL \
- SRC/MODULES/HISTORY_BACKEND_NULL.DLL \
- SRC/MODULES/RESTRICT-COMMANDS.DLL \
- SRC/MODULES/RMTKL.DLL \
- SRC/MODULES/ECHO-MESSAGE.DLL \
- SRC/MODULES/USERIP-TAG.DLL \
- SRC/MODULES/USERHOST-TAG.DLL \
- SRC/MODULES/BOT-TAG.DLL \
- SRC/MODULES/REPLY-TAG.DLL \
- SRC/MODULES/REQUIRE-MODULE.DLL \
- SRC/MODULES/IDENT_LOOKUP.DLL \
- SRC/MODULES/HISTORY.DLL \
- SRC/MODULES/CHATHISTORY.DLL \
- SRC/MODULES/TARGETFLOODPROT.DLL \
- SRC/MODULES/TYPING-INDICATOR.DLL \
- SRC/MODULES/CLIENTTAGDENY.DLL
+EXP_OBJ_FILES=src/channel.obj src/send.obj src/socket.obj \
+ src/conf.obj src/conf_preprocessor.obj \
+ src/fdlist.obj src/dbuf.obj  \
+ src/hash.obj src/parse.obj src/ircd.obj \
+ src/whowas.obj \
+ src/misc.obj src/match.obj src/crule.obj \
+ src/debug.obj  src/support.obj src/list.obj \
+ src/serv.obj src/user.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/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)
+
+OBJ_FILES=$(EXP_OBJ_FILES) src/gui.obj src/service.obj src/windebug.obj src/rtf.obj \
+ src/editor.obj src/win.obj 
+
+DLL_FILES=\
+ src/modules/account-notify.dll \
+ src/modules/account-tag.dll \
+ src/modules/addmotd.dll \
+ src/modules/addomotd.dll \
+ src/modules/admin.dll \
+ src/modules/antimixedutf8.dll \
+ src/modules/antirandom.dll \
+ src/modules/authprompt.dll \
+ src/modules/away.dll \
+ src/modules/batch.dll \
+ src/modules/blacklist.dll \
+ src/modules/botmotd.dll \
+ src/modules/bot-tag.dll \
+ src/modules/cap.dll \
+ src/modules/certfp.dll \
+ src/modules/chanmodes/chanowner.dll \
+ src/modules/chanmodes/chanadmin.dll \
+ src/modules/chanmodes/chanop.dll \
+ src/modules/chanmodes/halfop.dll \
+ src/modules/chanmodes/voice.dll \
+ src/modules/chanmodes/censor.dll \
+ src/modules/chanmodes/delayjoin.dll \
+ src/modules/chanmodes/floodprot.dll \
+ src/modules/chanmodes/history.dll \
+ src/modules/chanmodes/inviteonly.dll \
+ src/modules/chanmodes/isregistered.dll \
+ src/modules/chanmodes/issecure.dll \
+ src/modules/chanmodes/key.dll \
+ src/modules/chanmodes/limit.dll \
+ src/modules/chanmodes/link.dll \
+ src/modules/chanmodes/moderated.dll \
+ src/modules/chanmodes/nocolor.dll \
+ src/modules/chanmodes/noctcp.dll \
+ src/modules/chanmodes/noexternalmsgs.dll \
+ src/modules/chanmodes/noinvite.dll \
+ src/modules/chanmodes/nokick.dll \
+ src/modules/chanmodes/noknock.dll \
+ src/modules/chanmodes/nonickchange.dll \
+ src/modules/chanmodes/nonotice.dll \
+ src/modules/chanmodes/operonly.dll \
+ src/modules/chanmodes/permanent.dll \
+ src/modules/chanmodes/private.dll \
+ src/modules/chanmodes/regonly.dll \
+ src/modules/chanmodes/regonlyspeak.dll \
+ src/modules/chanmodes/secret.dll \
+ src/modules/chanmodes/secureonly.dll \
+ src/modules/chanmodes/stripcolor.dll \
+ src/modules/chanmodes/topiclimit.dll \
+ src/modules/channeldb.dll \
+ src/modules/charsys.dll \
+ src/modules/chathistory.dll \
+ src/modules/chghost.dll \
+ src/modules/chgident.dll \
+ src/modules/chgname.dll \
+ src/modules/clienttagdeny.dll \
+ src/modules/close.dll \
+ src/modules/connect.dll \
+ src/modules/connthrottle.dll \
+ src/modules/cycle.dll \
+ src/modules/dccallow.dll \
+ src/modules/dccdeny.dll \
+ src/modules/echo-message.dll \
+ src/modules/eos.dll \
+ src/modules/extbans/account.dll \
+ src/modules/extbans/certfp.dll \
+ src/modules/extbans/country.dll \
+ src/modules/extbans/inchannel.dll \
+ src/modules/extbans/join.dll \
+ src/modules/extbans/msgbypass.dll \
+ src/modules/extbans/nickchange.dll \
+ src/modules/extbans/operclass.dll \
+ src/modules/extbans/partmsg.dll \
+ src/modules/extbans/quiet.dll \
+ src/modules/extbans/realname.dll \
+ src/modules/extbans/securitygroup.dll \
+ src/modules/extbans/textban.dll \
+ src/modules/extbans/timedban.dll \
+ src/modules/extended-monitor.dll \
+ src/modules/extjwt.dll \
+ src/modules/geoip_base.dll \
+ src/modules/geoip_classic.dll \
+ src/modules/geoip_csv.dll \
+ src/modules/globops.dll \
+ src/modules/help.dll \
+ src/modules/hideserver.dll \
+ src/modules/history_backend_mem.dll \
+ src/modules/history_backend_null.dll \
+ src/modules/history.dll \
+ src/modules/ident_lookup.dll \
+ src/modules/invite.dll \
+ src/modules/ircops.dll \
+ src/modules/ison.dll \
+ src/modules/join.dll \
+ src/modules/jointhrottle.dll \
+ src/modules/json-log-tag.dll \
+ src/modules/jumpserver.dll \
+ src/modules/kick.dll \
+ src/modules/kill.dll \
+ src/modules/knock.dll \
+ src/modules/labeled-response.dll \
+ src/modules/lag.dll \
+ src/modules/links.dll \
+ src/modules/link-security.dll \
+ src/modules/list.dll \
+ src/modules/locops.dll \
+ src/modules/lusers.dll \
+ src/modules/map.dll \
+ src/modules/md.dll \
+ src/modules/message.dll \
+ src/modules/message-ids.dll \
+ src/modules/message-tags.dll \
+ src/modules/mkpasswd.dll \
+ src/modules/mode.dll \
+ src/modules/monitor.dll \
+ src/modules/motd.dll \
+ src/modules/names.dll \
+ src/modules/netinfo.dll \
+ src/modules/nick.dll \
+ src/modules/nocodes.dll \
+ src/modules/cloak_md5.dll \
+ src/modules/cloak_none.dll \
+ src/modules/cloak_sha256.dll \
+ src/modules/oper.dll \
+ src/modules/operinfo.dll \
+ src/modules/opermotd.dll \
+ src/modules/part.dll \
+ src/modules/pass.dll \
+ src/modules/pingpong.dll \
+ src/modules/plaintext-policy.dll \
+ src/modules/protoctl.dll \
+ src/modules/quit.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/rules.dll \
+ src/modules/sajoin.dll \
+ src/modules/samode.dll \
+ src/modules/sapart.dll \
+ src/modules/sasl.dll \
+ src/modules/sdesc.dll \
+ src/modules/sendsno.dll \
+ src/modules/sendumode.dll \
+ src/modules/server.dll \
+ src/modules/server-time.dll \
+ src/modules/sethost.dll \
+ src/modules/setident.dll \
+ src/modules/setname.dll \
+ src/modules/silence.dll \
+ src/modules/sinfo.dll \
+ src/modules/sjoin.dll \
+ src/modules/slog.dll \
+ src/modules/sqline.dll \
+ src/modules/squit.dll \
+ src/modules/staff.dll \
+ src/modules/starttls.dll \
+ src/modules/stats.dll \
+ src/modules/sts.dll \
+ src/modules/svsjoin.dll \
+ src/modules/svskill.dll \
+ src/modules/svslusers.dll \
+ src/modules/svsmode.dll \
+ src/modules/svsmotd.dll \
+ src/modules/svsnick.dll \
+ src/modules/svsnline.dll \
+ src/modules/svsnolag.dll \
+ src/modules/svsnoop.dll \
+ src/modules/svspart.dll \
+ src/modules/svssilence.dll \
+ src/modules/svssno.dll \
+ src/modules/svswatch.dll \
+ src/modules/swhois.dll \
+ src/modules/targetfloodprot.dll \
+ src/modules/time.dll \
+ src/modules/tkl.dll \
+ src/modules/tkldb.dll \
+ src/modules/tls_antidos.dll \
+ src/modules/tls_cipher.dll \
+ src/modules/topic.dll \
+ src/modules/trace.dll \
+ src/modules/tsctl.dll \
+ src/modules/typing-indicator.dll \
+ src/modules/umode2.dll \
+ src/modules/unreal_server_compat.dll \
+ src/modules/unsqline.dll \
+ src/modules/user.dll \
+ src/modules/userhost.dll \
+ src/modules/userhost-tag.dll \
+ src/modules/userip.dll \
+ src/modules/userip-tag.dll \
+ src/modules/usermodes/bot.dll \
+ src/modules/usermodes/censor.dll \
+ src/modules/usermodes/noctcp.dll \
+ src/modules/usermodes/nokick.dll \
+ src/modules/usermodes/privacy.dll \
+ src/modules/usermodes/privdeaf.dll \
+ src/modules/usermodes/regonlymsg.dll \
+ src/modules/usermodes/secureonlymsg.dll \
+ src/modules/usermodes/servicebot.dll \
+ src/modules/usermodes/showwhois.dll \
+ src/modules/usermodes/wallops.dll \
+ src/modules/vhost.dll \
+ src/modules/watch-backend.dll \
+ src/modules/watch.dll \
+ src/modules/webirc.dll \
+ src/modules/webredir.dll \
+ src/modules/websocket.dll \
+ src/modules/whois.dll \
+ src/modules/who_old.dll \
+ src/modules/whowas.dll \
+ src/modules/whox.dll
 
 
 ALL: CONF UNREALSVC.EXE UnrealIRCd.exe MODULES 
 
 CLEAN:
-        -@erase *.obj >NUL
-        -@erase src\*.obj >NUL
-        -@erase src\win.res >NUL
-        -@erase src\version.c >NUL
-        -@erase src\windows\*.obj >NUL
-	-@erase src\modules\*.obj >NUL
-	-@erase src\modules\*.lib >NUL
-	-@erase src\modules\*.pdb >NUL
-	-@erase src\modules\*.dll >NUL
-	-@erase src\modules\*.exp >NUL
-	-@erase src\modules\*.ilk >NUL
-	-@erase src\modules\chanmodes\*.obj >NUL
-	-@erase src\modules\chanmodes\*.lib >NUL
-	-@erase src\modules\chanmodes\*.pdb >NUL
-	-@erase src\modules\chanmodes\*.dll >NUL
-	-@erase src\modules\chanmodes\*.exp >NUL
-	-@erase src\modules\chanmodes\*.ilk >NUL
-	-@erase src\modules\usermodes\*.obj >NUL
-	-@erase src\modules\usermodes\*.lib >NUL
-	-@erase src\modules\usermodes\*.pdb >NUL
-	-@erase src\modules\usermodes\*.dll >NUL
-	-@erase src\modules\usermodes\*.exp >NUL
-	-@erase src\modules\usermodes\*.ilk >NUL
-	-@erase src\modules\snomasks\*.obj >NUL
-	-@erase src\modules\snomasks\*.lib >NUL
-	-@erase src\modules\snomasks\*.pdb >NUL
-	-@erase src\modules\snomasks\*.dll >NUL
-	-@erase src\modules\snomasks\*.exp >NUL
-	-@erase src\modules\snomasks\*.ilk >NUL
-	-@erase src\modules\extbans\*.obj >NUL
-	-@erase src\modules\extbans\*.lib >NUL
-	-@erase src\modules\extbans\*.pdb >NUL
-	-@erase src\modules\extbans\*.dll >NUL
-	-@erase src\modules\extbans\*.exp >NUL
-	-@erase src\modules\extbans\*.ilk >NUL
-        -@erase .\*.exe >NUL
-	-@erase UnrealIRCd.lib >NUL
-
-./UNREALSVC.EXE: SRC/UNREALSVC.OBJ SRC/WINDOWS/UNREALSVC.RES
+	-@del /Q /S *.dll *.exe *.obj *.pdb *.res *.lib  *.exp *.ilk src\version.c >NUL
+
+UNREALSVC.EXE: SRC/UNREALSVC.OBJ SRC/WINDOWS/UNREALSVC.RES
 	$(LINK) $(DBGLFLAGST) advapi32.lib src/unrealsvc.obj src/windows/unrealsvc.res
 
 CONF:
@@ -361,9 +424,7 @@ CONF:
 	$(CC) src/windows/config.c
 	-@config.exe
 
-
-
-./UnrealIRCd.exe: $(OBJ_FILES) src/windows/win.res
+UnrealIRCd.exe: $(OBJ_FILES) src/windows/win.res
         $(LINK) $(LFLAGS) $(OBJ_FILES) src/windows/win.res /MAP
 	-@erase src\windows\win.res
 	$(MT) -manifest src\windows\UnrealIRCd.exe.manifest -outputresource:UnrealIRCd.exe;1
@@ -430,9 +491,6 @@ src/conf_preprocessor.obj: src/conf_preprocessor.c $(INCLUDES)
 src/debug.obj: src/debug.c $(INCLUDES)
         $(CC) $(CFLAGS) src/debug.c
 
-src/numeric.obj: src/numeric.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/numeric.c
-
 src/misc.obj: src/misc.c $(INCLUDES) ./include/dbuf.h
         $(CC) $(CFLAGS) src/misc.c
 
@@ -509,8 +567,8 @@ src/mempool.obj: src/mempool.c $(INCLUDES)
 src/dispatch.obj: src/dispatch.c $(INCLUDES)
 	$(CC) $(CFLAGS) src/dispatch.c
 
-src/url.obj: src/url.c $(INCLUDES) ./include/url.h
-	$(CC) $(CFLAGS) src/url.c
+src/url_curl.obj: src/url_curl.c $(INCLUDES)
+	$(CC) $(CFLAGS) src/url_curl.c
 
 src/api-extban.obj: src/api-extban.c $(INCLUDES)
 	$(CC) $(CFLAGS) src/api-extban.c
@@ -542,9 +600,6 @@ src/crypt_blowfish.obj: src/crypt_blowfish.c $(INCLUDES)
 src/operclass.obj: src/operclass.c $(INCLUDES) ./include/dbuf.h
         $(CC) $(CFLAGS) src/operclass.c
 
-src/updconf.obj: src/updconf.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/updconf.c
-
 src/crashreport.obj: src/crashreport.c $(INCLUDES) ./include/dbuf.h
         $(CC) $(CFLAGS) src/crashreport.c
 
@@ -554,6 +609,12 @@ src/unrealdb.obj: src/unrealdb.c $(INCLUDES) ./include/dbuf.h
 src/utf8.obj: src/utf8.c $(INCLUDES) ./include/dbuf.h
         $(CC) $(CFLAGS) src/utf8.c
 
+src/openssl_hostname_validation.obj: src/openssl_hostname_validation.c $(INCLUDES) ./include/dbuf.h
+        $(CC) $(CFLAGS) src/openssl_hostname_validation.c
+
+src/log.obj: src/log.c $(INCLUDES) ./include/dbuf.h
+        $(CC) $(CFLAGS) src/log.c
+
 src/windows/win.res: src/windows/wingui.rc
         $(RC) /l 0x409 /fosrc/windows/win.res /i ./include /i ./src \
               /d NDEBUG src/windows/wingui.rc
@@ -575,556 +636,642 @@ SYMBOLFILE:
 
 MODULES: $(DLL_FILES)
 
-src/modules/cloak.dll: src/modules/cloak.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/cloak.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/account-notify.dll: src/modules/account-notify.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/account-notify.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/account-notify.pdb $(MODLFLAGS)
 
-src/modules/chghost.dll: src/modules/chghost.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/chghost.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/account-tag.dll: src/modules/account-tag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/account-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/account-tag.pdb $(MODLFLAGS)
 
-src/modules/chgident.dll: src/modules/chgident.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/chgident.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/addmotd.dll: src/modules/addmotd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/addmotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/addmotd.pdb $(MODLFLAGS)
 
-src/modules/sdesc.dll: src/modules/sdesc.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/sdesc.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/addomotd.dll: src/modules/addomotd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/addomotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/addomotd.pdb $(MODLFLAGS)
 
-src/modules/sethost.dll: src/modules/sethost.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/sethost.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/admin.dll: src/modules/admin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/admin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/admin.pdb $(MODLFLAGS)
 
-src/modules/setident.dll: src/modules/setident.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/setident.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/antimixedutf8.dll: src/modules/antimixedutf8.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/antimixedutf8.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/antimixedutf8.pdb $(MODLFLAGS)
 
-src/modules/setname.dll: src/modules/setname.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/setname.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/antirandom.dll: src/modules/antirandom.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/antirandom.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/antirandom.pdb $(MODLFLAGS)
 
-src/modules/svsmotd.dll: src/modules/svsmotd.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsmotd.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/authprompt.dll: src/modules/authprompt.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/authprompt.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/authprompt.pdb $(MODLFLAGS)
 
-src/modules/svsmode.dll: src/modules/svsmode.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsmode.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/away.dll: src/modules/away.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/away.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/away.pdb $(MODLFLAGS)
 
-src/modules/tkl.dll: src/modules/tkl.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/tkl.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/batch.dll: src/modules/batch.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/batch.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/batch.pdb $(MODLFLAGS)
 
-src/modules/swhois.dll: src/modules/swhois.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/swhois.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/blacklist.dll: src/modules/blacklist.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/blacklist.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/blacklist.pdb $(MODLFLAGS)
 
-src/modules/svsnline.dll: src/modules/svsnline.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsnline.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/botmotd.dll: src/modules/botmotd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/botmotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/botmotd.pdb $(MODLFLAGS)
 
-src/modules/who_old.dll: src/modules/who_old.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/who_old.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/bot-tag.dll: src/modules/bot-tag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/bot-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/bot-tag.pdb $(MODLFLAGS)
 
-src/modules/whox.dll: src/modules/whox.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/whox.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/cap.dll: src/modules/cap.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/cap.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cap.pdb $(MODLFLAGS)
 
-src/modules/away.dll: src/modules/away.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/away.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/certfp.dll: src/modules/certfp.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/certfp.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/certfp.pdb $(MODLFLAGS)
 
-src/modules/mkpasswd.dll: src/modules/mkpasswd.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/mkpasswd.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/chanowner.dll: src/modules/chanmodes/chanowner.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/chanowner.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/chanowner.pdb $(MODLFLAGS)
 
-src/modules/svsnoop.dll: src/modules/svsnoop.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsnoop.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/chanadmin.dll: src/modules/chanmodes/chanadmin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/chanadmin.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/chanadmin.pdb $(MODLFLAGS)
 
-src/modules/svsnick.dll: src/modules/svsnick.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsnick.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/chanop.dll: src/modules/chanmodes/chanop.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/chanop.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/chanop.pdb $(MODLFLAGS)
 
-src/modules/chgname.dll: src/modules/chgname.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/chgname.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/halfop.dll: src/modules/chanmodes/halfop.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/halfop.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/halfop.pdb $(MODLFLAGS)
 
-src/modules/kill.dll: src/modules/kill.c $(INCLUDES) 
-	$(CC) $(MODCFLAGS) src/modules/kill.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/voice.dll: src/modules/chanmodes/voice.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/voice.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/voice.pdb $(MODLFLAGS)
 
-src/modules/lag.dll: src/modules/lag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/lag.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/censor.dll: src/modules/chanmodes/censor.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/censor.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/censor.pdb $(MODLFLAGS)
 
-src/modules/message.dll: src/modules/message.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/message.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/delayjoin.dll: src/modules/chanmodes/delayjoin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/delayjoin.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/delayjoin.pdb $(MODLFLAGS)
 
-src/modules/oper.dll: src/modules/oper.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/oper.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/floodprot.dll: src/modules/chanmodes/floodprot.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/floodprot.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/floodprot.pdb $(MODLFLAGS)
 
-src/modules/pingpong.dll: src/modules/pingpong.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/pingpong.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/history.dll: src/modules/chanmodes/history.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/history.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/history.pdb $(MODLFLAGS)
 
-src/modules/quit.dll: src/modules/quit.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/quit.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/inviteonly.dll: src/modules/chanmodes/inviteonly.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/inviteonly.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/inviteonly.pdb $(MODLFLAGS)
 
-src/modules/sendumode.dll: src/modules/sendumode.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sendumode.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/isregistered.dll: src/modules/chanmodes/isregistered.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/isregistered.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/isregistered.pdb $(MODLFLAGS)
 
-src/modules/sqline.dll: src/modules/sqline.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sqline.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/issecure.dll: src/modules/chanmodes/issecure.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/issecure.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/issecure.pdb $(MODLFLAGS)
 
-src/modules/tsctl.dll: src/modules/tsctl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/tsctl.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/key.dll: src/modules/chanmodes/key.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/key.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/key.pdb $(MODLFLAGS)
 
-src/modules/unsqline.dll: src/modules/unsqline.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/unsqline.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/limit.dll: src/modules/chanmodes/limit.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/limit.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/limit.pdb $(MODLFLAGS)
 
-src/modules/whois.dll: src/modules/whois.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/whois.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/link.dll: src/modules/chanmodes/link.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/link.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/link.pdb $(MODLFLAGS)
 
-src/modules/vhost.dll: src/modules/vhost.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/vhost.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/moderated.dll: src/modules/chanmodes/moderated.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/moderated.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/moderated.pdb $(MODLFLAGS)
 
-src/modules/cycle.dll: src/modules/cycle.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/cycle.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/nocolor.dll: src/modules/chanmodes/nocolor.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/nocolor.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/nocolor.pdb $(MODLFLAGS)
 
-src/modules/svsjoin.dll: src/modules/svsjoin.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsjoin.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/noctcp.dll: src/modules/chanmodes/noctcp.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/noctcp.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/noctcp.pdb $(MODLFLAGS)
 
-src/modules/svspart.dll: src/modules/svspart.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svspart.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/noexternalmsgs.dll: src/modules/chanmodes/noexternalmsgs.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/noexternalmsgs.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/noexternalmsgs.pdb $(MODLFLAGS)
 
-src/modules/svslusers.dll: src/modules/svslusers.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svslusers.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/noinvite.dll: src/modules/chanmodes/noinvite.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/noinvite.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/noinvite.pdb $(MODLFLAGS)
 
-src/modules/svswatch.dll: src/modules/svswatch.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svswatch.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/nokick.dll: src/modules/chanmodes/nokick.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/nokick.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/nokick.pdb $(MODLFLAGS)
 
-src/modules/svssilence.dll: src/modules/svssilence.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svssilence.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/noknock.dll: src/modules/chanmodes/noknock.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/noknock.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/noknock.pdb $(MODLFLAGS)
 
-src/modules/sendsno.dll: src/modules/sendsno.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sendsno.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/nonickchange.dll: src/modules/chanmodes/nonickchange.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/nonickchange.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/nonickchange.pdb $(MODLFLAGS)
 
-src/modules/svssno.dll: src/modules/svssno.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svssno.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/nonotice.dll: src/modules/chanmodes/nonotice.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/nonotice.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/nonotice.pdb $(MODLFLAGS)
 
-src/modules/sajoin.dll: src/modules/sajoin.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sajoin.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/operonly.dll: src/modules/chanmodes/operonly.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/operonly.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/operonly.pdb $(MODLFLAGS)
 
-src/modules/sapart.dll: src/modules/sapart.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sapart.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/permanent.dll: src/modules/chanmodes/permanent.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/permanent.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/permanent.pdb $(MODLFLAGS)
 
-src/modules/samode.dll: src/modules/samode.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/samode.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/private.dll: src/modules/chanmodes/private.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/private.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/private.pdb $(MODLFLAGS)
 
-src/modules/kick.dll: src/modules/kick.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/kick.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/regonly.dll: src/modules/chanmodes/regonly.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/regonly.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/regonly.pdb $(MODLFLAGS)
 
-src/modules/topic.dll: src/modules/topic.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/topic.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/regonlyspeak.dll: src/modules/chanmodes/regonlyspeak.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/regonlyspeak.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/regonlyspeak.pdb $(MODLFLAGS)
 
-src/modules/invite.dll: src/modules/invite.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/invite.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/secret.dll: src/modules/chanmodes/secret.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/secret.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/secret.pdb $(MODLFLAGS)
 
-src/modules/list.dll: src/modules/list.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/list.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/secureonly.dll: src/modules/chanmodes/secureonly.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/secureonly.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/secureonly.pdb $(MODLFLAGS)
 
-src/modules/time.dll: src/modules/time.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/time.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/stripcolor.dll: src/modules/chanmodes/stripcolor.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/stripcolor.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/stripcolor.pdb $(MODLFLAGS)
 
-src/modules/svskill.dll: src/modules/svskill.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svskill.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chanmodes/topiclimit.dll: src/modules/chanmodes/topiclimit.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chanmodes/topiclimit.c /Fesrc/modules/chanmodes/ /Fosrc/modules/chanmodes/ /Fdsrc/modules/chanmodes/topiclimit.pdb $(MODLFLAGS)
 
-src/modules/sjoin.dll: src/modules/sjoin.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sjoin.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/channeldb.dll: src/modules/channeldb.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/channeldb.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/channeldb.pdb $(MODLFLAGS)
 
-src/modules/pass.dll: src/modules/pass.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/pass.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/charsys.dll: src/modules/charsys.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/charsys.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/charsys.pdb $(MODLFLAGS)
 
-src/modules/userhost.dll: src/modules/userhost.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/userhost.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chathistory.dll: src/modules/chathistory.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chathistory.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/chathistory.pdb $(MODLFLAGS)
 
-src/modules/ison.dll: src/modules/ison.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/ison.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chghost.dll: src/modules/chghost.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chghost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/chghost.pdb $(MODLFLAGS)
 
-src/modules/silence.dll: src/modules/silence.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/silence.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chgident.dll: src/modules/chgident.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chgident.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/chgident.pdb $(MODLFLAGS)
 
-src/modules/knock.dll: src/modules/knock.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/knock.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/chgname.dll: src/modules/chgname.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/chgname.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/chgname.pdb $(MODLFLAGS)
 
-src/modules/umode2.dll: src/modules/umode2.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/umode2.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/clienttagdeny.dll: src/modules/clienttagdeny.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/clienttagdeny.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/clienttagdeny.pdb $(MODLFLAGS)
 
-src/modules/squit.dll: src/modules/squit.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/squit.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/close.dll: src/modules/close.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/close.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/close.pdb $(MODLFLAGS)
 
-src/modules/protoctl.dll: src/modules/protoctl.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/protoctl.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+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/addmotd.dll: src/modules/addmotd.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/addmotd.c /Fesrc/modules/ /Fosrc/modules/ $(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)
 
-src/modules/addomotd.dll: src/modules/addomotd.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/addomotd.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/cycle.dll: src/modules/cycle.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/cycle.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cycle.pdb $(MODLFLAGS)
 
-src/modules/wallops.dll: src/modules/wallops.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/wallops.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/dccallow.dll: src/modules/dccallow.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/dccallow.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/dccallow.pdb $(MODLFLAGS)
 
-src/modules/admin.dll: src/modules/admin.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/admin.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/dccdeny.dll: src/modules/dccdeny.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/dccdeny.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/dccdeny.pdb $(MODLFLAGS)
 
-src/modules/globops.dll: src/modules/globops.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/globops.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/echo-message.dll: src/modules/echo-message.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/echo-message.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/echo-message.pdb $(MODLFLAGS)
 
-src/modules/locops.dll: src/modules/locops.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/locops.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/eos.dll: src/modules/eos.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/eos.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/eos.pdb $(MODLFLAGS)
 
-src/modules/trace.dll: src/modules/trace.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/trace.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/account.dll: src/modules/extbans/account.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/account.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/account.pdb $(MODLFLAGS)
 
-src/modules/netinfo.dll: src/modules/netinfo.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/netinfo.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/certfp.dll: src/modules/extbans/certfp.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/certfp.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/certfp.pdb $(MODLFLAGS)
 
-src/modules/links.dll: src/modules/links.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/links.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/country.dll: src/modules/extbans/country.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/country.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/country.pdb $(MODLFLAGS)
 
-src/modules/help.dll: src/modules/help.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/help.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/inchannel.dll: src/modules/extbans/inchannel.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/inchannel.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/inchannel.pdb $(MODLFLAGS)
 
-src/modules/rules.dll: src/modules/rules.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/rules.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
- 
-src/modules/close.dll: src/modules/close.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/close.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+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/map.dll: src/modules/map.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/map.c /Fesrc/modules/ /Fosrc/modules/ $(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)
 
-src/modules/eos.dll: src/modules/eos.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/eos.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/nickchange.dll: src/modules/extbans/nickchange.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/nickchange.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/nickchange.pdb $(MODLFLAGS)
 
-src/modules/server.dll: src/modules/server.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/server.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/operclass.dll: src/modules/extbans/operclass.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/operclass.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/operclass.pdb $(MODLFLAGS)
 
-src/modules/stats.dll: src/modules/stats.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/stats.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/partmsg.dll: src/modules/extbans/partmsg.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/partmsg.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/partmsg.pdb $(MODLFLAGS)
 
-src/modules/dccdeny.dll: src/modules/dccdeny.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/dccdeny.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/quiet.dll: src/modules/extbans/quiet.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/quiet.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/quiet.pdb $(MODLFLAGS)
 
-src/modules/whowas.dll: src/modules/whowas.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/whowas.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/realname.dll: src/modules/extbans/realname.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/realname.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/realname.pdb $(MODLFLAGS)
 
-src/modules/connect.dll: src/modules/connect.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/connect.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/securitygroup.dll: src/modules/extbans/securitygroup.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/securitygroup.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/securitygroup.pdb $(MODLFLAGS)
 
-src/modules/dccallow.dll: src/modules/dccallow.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/dccallow.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/textban.dll: src/modules/extbans/textban.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/textban.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/textban.pdb $(MODLFLAGS)
 
-src/modules/userip.dll: src/modules/userip.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/userip.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extbans/timedban.dll: src/modules/extbans/timedban.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/timedban.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/timedban.pdb $(MODLFLAGS)
 
-src/modules/nick.dll: src/modules/nick.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/nick.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extended-monitor.dll: src/modules/extended-monitor.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extended-monitor.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/extended-monitor.pdb $(MODLFLAGS)
 
-src/modules/user.dll: src/modules/user.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/user.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/geoip_base.dll: src/modules/geoip_base.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/geoip_base.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip_base.pdb $(MODLFLAGS)
 
-src/modules/mode.dll: src/modules/mode.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/mode.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/extjwt.dll: src/modules/extjwt.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extjwt.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/extjwt.pdb $(MODLFLAGS)
 
-src/modules/watch.dll: src/modules/watch.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/watch.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/geoip_classic.dll: src/modules/geoip_classic.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) /I "$(GEOIPCLASSIC_INC_DIR)" src/modules/geoip_classic.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip_classic.pdb $(MODLFLAGS) /LIBPATH:"$(GEOIPCLASSIC_LIB_DIR)" $(GEOIPCLASSICLIB)
 
-src/modules/part.dll: src/modules/part.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/part.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/geoip_csv.dll: src/modules/geoip_csv.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/geoip_csv.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip_csv.pdb $(MODLFLAGS)
+
+src/modules/geoip_maxmind.dll: src/modules/geoip_maxmind.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/geoip_maxmind.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip_maxmind.pdb $(MODLFLAGS)
+
+src/modules/globops.dll: src/modules/globops.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/globops.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/globops.pdb $(MODLFLAGS)
+
+src/modules/help.dll: src/modules/help.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/help.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/help.pdb $(MODLFLAGS)
+
+src/modules/hideserver.dll: src/modules/hideserver.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/hideserver.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/hideserver.pdb $(MODLFLAGS)
+
+src/modules/history_backend_mem.dll: src/modules/history_backend_mem.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/history_backend_mem.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/history_backend_mem.pdb $(MODLFLAGS)
+
+src/modules/history_backend_null.dll: src/modules/history_backend_null.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/history_backend_null.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/history_backend_null.pdb $(MODLFLAGS)
+
+src/modules/history.dll: src/modules/history.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/history.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/history.pdb $(MODLFLAGS)
+
+src/modules/ident_lookup.dll: src/modules/ident_lookup.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/ident_lookup.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/ident_lookup.pdb $(MODLFLAGS)
+
+src/modules/invite.dll: src/modules/invite.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/invite.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/invite.pdb $(MODLFLAGS)
+
+src/modules/ircops.dll: src/modules/ircops.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/ircops.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/ircops.pdb $(MODLFLAGS)
+
+src/modules/ison.dll: src/modules/ison.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/ison.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/ison.pdb $(MODLFLAGS)
 
 src/modules/join.dll: src/modules/join.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/join.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+	$(CC) $(MODCFLAGS) src/modules/join.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/join.pdb $(MODLFLAGS)
 
-src/modules/motd.dll: src/modules/motd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/motd.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/jointhrottle.dll: src/modules/jointhrottle.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/jointhrottle.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/jointhrottle.pdb $(MODLFLAGS)
 
-src/modules/opermotd.dll: src/modules/opermotd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/opermotd.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/json-log-tag.dll: src/modules/json-log-tag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/json-log-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/json-log-tag.pdb $(MODLFLAGS)
 
-src/modules/botmotd.dll: src/modules/botmotd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/botmotd.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/jumpserver.dll: src/modules/jumpserver.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/jumpserver.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/jumpserver.pdb $(MODLFLAGS)
+
+src/modules/kick.dll: src/modules/kick.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/kick.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/kick.pdb $(MODLFLAGS)
+
+src/modules/kill.dll: src/modules/kill.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/kill.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/kill.pdb $(MODLFLAGS)
+
+src/modules/knock.dll: src/modules/knock.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/knock.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/knock.pdb $(MODLFLAGS)
+
+src/modules/labeled-response.dll: src/modules/labeled-response.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/labeled-response.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/labeled-response.pdb $(MODLFLAGS)
+
+src/modules/lag.dll: src/modules/lag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/lag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/lag.pdb $(MODLFLAGS)
+
+src/modules/links.dll: src/modules/links.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/links.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/links.pdb $(MODLFLAGS)
+
+src/modules/link-security.dll: src/modules/link-security.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/link-security.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/link-security.pdb $(MODLFLAGS)
+
+src/modules/list.dll: src/modules/list.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/list.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/list.pdb $(MODLFLAGS)
+
+src/modules/locops.dll: src/modules/locops.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/locops.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/locops.pdb $(MODLFLAGS)
 
 src/modules/lusers.dll: src/modules/lusers.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/lusers.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+	$(CC) $(MODCFLAGS) src/modules/lusers.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/lusers.pdb $(MODLFLAGS)
 
-src/modules/names.dll: src/modules/names.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/names.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+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/md.dll: src/modules/md.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/md.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+	$(CC) $(MODCFLAGS) src/modules/md.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/md.pdb $(MODLFLAGS)
 
-src/modules/certfp.dll: src/modules/certfp.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/certfp.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/message.dll: src/modules/message.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/message.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/message.pdb $(MODLFLAGS)
 
-src/modules/webirc.dll: src/modules/webirc.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/webirc.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/message-ids.dll: src/modules/message-ids.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/message-ids.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/message-ids.pdb $(MODLFLAGS)
 
-src/modules/websocket.dll: src/modules/websocket.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/websocket.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/message-tags.dll: src/modules/message-tags.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/message-tags.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/message-tags.pdb $(MODLFLAGS)
 
-src/modules/blacklist.dll: src/modules/blacklist.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/blacklist.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/mkpasswd.dll: src/modules/mkpasswd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/mkpasswd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/mkpasswd.pdb $(MODLFLAGS)
 
-src/modules/jointhrottle.dll: src/modules/jointhrottle.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/jointhrottle.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/mode.dll: src/modules/mode.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/mode.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/mode.pdb $(MODLFLAGS)
 
-src/modules/svsnolag.dll: src/modules/svsnolag.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/svsnolag.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/monitor.dll: src/modules/monitor.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/monitor.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/monitor.pdb $(MODLFLAGS)
 
-src/modules/starttls.dll: src/modules/starttls.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/starttls.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/motd.dll: src/modules/motd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/motd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/motd.pdb $(MODLFLAGS)
 
-src/modules/webredir.dll: src/modules/webredir.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/webredir.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/names.dll: src/modules/names.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/names.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/names.pdb $(MODLFLAGS)
 
-src/modules/cap.dll: src/modules/cap.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/cap.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/netinfo.dll: src/modules/netinfo.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/netinfo.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/netinfo.pdb $(MODLFLAGS)
 
-src/modules/sasl.dll: src/modules/sasl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sasl.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/nick.dll: src/modules/nick.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/nick.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/nick.pdb $(MODLFLAGS)
 
-src/modules/tls_antidos.dll: src/modules/tls_antidos.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/tls_antidos.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/nocodes.dll: src/modules/nocodes.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/nocodes.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/nocodes.pdb $(MODLFLAGS)
 
-src/modules/antirandom.dll: src/modules/antirandom.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/antirandom.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/cloak_md5.dll: src/modules/cloak_md5.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/cloak_md5.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cloak_md5.pdb $(MODLFLAGS)
 
-src/modules/hideserver.dll: src/modules/hideserver.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/hideserver.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/cloak_none.dll: src/modules/cloak_none.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/cloak_none.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cloak_none.pdb $(MODLFLAGS)
 
-src/modules/jumpserver.dll: src/modules/jumpserver.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/jumpserver.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/cloak_sha256.dll: src/modules/cloak_sha256.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/cloak_sha256.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cloak_sha256.pdb $(MODLFLAGS)
 
-src/modules/ircops.dll: src/modules/ircops.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/ircops.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/oper.dll: src/modules/oper.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/oper.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/oper.pdb $(MODLFLAGS)
 
-src/modules/staff.dll: src/modules/staff.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/staff.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/operinfo.dll: src/modules/operinfo.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/operinfo.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/operinfo.pdb $(MODLFLAGS)
 
-src/modules/nocodes.dll: src/modules/nocodes.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/nocodes.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/opermotd.dll: src/modules/opermotd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/opermotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/opermotd.pdb $(MODLFLAGS)
 
-src/modules/charsys.dll: src/modules/charsys.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/charsys.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/part.dll: src/modules/part.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/part.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/part.pdb $(MODLFLAGS)
 
-src/modules/antimixedutf8.dll: src/modules/antimixedutf8.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/antimixedutf8.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/pass.dll: src/modules/pass.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/pass.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/pass.pdb $(MODLFLAGS)
 
-src/modules/authprompt.dll: src/modules/authprompt.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/authprompt.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/pingpong.dll: src/modules/pingpong.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/pingpong.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/pingpong.pdb $(MODLFLAGS)
 
-src/modules/sinfo.dll: src/modules/sinfo.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/sinfo.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/plaintext-policy.dll: src/modules/plaintext-policy.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/plaintext-policy.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/plaintext-policy.pdb $(MODLFLAGS)
+
+src/modules/protoctl.dll: src/modules/protoctl.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/protoctl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/protoctl.pdb $(MODLFLAGS)
+
+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/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)
 
 src/modules/reputation.dll: src/modules/reputation.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/reputation.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+	$(CC) $(MODCFLAGS) src/modules/reputation.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/reputation.pdb $(MODLFLAGS)
 
-src/modules/connthrottle.dll: src/modules/connthrottle.c $(INCLUDES)
-        $(CC) $(MODCFLAGS) src/modules/connthrottle.c /Fesrc/modules/ /Fosrc/modules/ $(MODLFLAGS)
+src/modules/require-module.dll: src/modules/require-module.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/require-module.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/require-module.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/censor.dll: src/modules/chanmodes/censor.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/censor.c $(MODLFLAGS)
+src/modules/restrict-commands.dll: src/modules/restrict-commands.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/restrict-commands.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/restrict-commands.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/delayjoin.dll: src/modules/chanmodes/delayjoin.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/delayjoin.c $(MODLFLAGS)
+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/chanmodes/floodprot.dll: src/modules/chanmodes/floodprot.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/floodprot.c $(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)
 
-src/modules/chanmodes/issecure.dll: src/modules/chanmodes/issecure.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/issecure.c $(MODLFLAGS)
+src/modules/sajoin.dll: src/modules/sajoin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sajoin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sajoin.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/link.dll: src/modules/chanmodes/link.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/link.c $(MODLFLAGS)
+src/modules/samode.dll: src/modules/samode.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/samode.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/samode.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/nocolor.dll: src/modules/chanmodes/nocolor.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/nocolor.c $(MODLFLAGS)
+src/modules/sapart.dll: src/modules/sapart.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sapart.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sapart.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/noctcp.dll: src/modules/chanmodes/noctcp.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/noctcp.c $(MODLFLAGS)
+src/modules/sasl.dll: src/modules/sasl.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sasl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sasl.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/noinvite.dll: src/modules/chanmodes/noinvite.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/noinvite.c $(MODLFLAGS)
+src/modules/sdesc.dll: src/modules/sdesc.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sdesc.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sdesc.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/nokick.dll: src/modules/chanmodes/nokick.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/nokick.c $(MODLFLAGS)
+src/modules/sendsno.dll: src/modules/sendsno.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sendsno.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sendsno.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/noknock.dll: src/modules/chanmodes/noknock.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/noknock.c $(MODLFLAGS)
+src/modules/sendumode.dll: src/modules/sendumode.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sendumode.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sendumode.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/nonickchange.dll: src/modules/chanmodes/nonickchange.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/nonickchange.c $(MODLFLAGS)
+src/modules/server.dll: src/modules/server.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/server.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/server.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/nonotice.dll: src/modules/chanmodes/nonotice.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/nonotice.c $(MODLFLAGS)
+src/modules/server-time.dll: src/modules/server-time.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/server-time.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/server-time.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/operonly.dll: src/modules/chanmodes/operonly.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/operonly.c $(MODLFLAGS)
+src/modules/sethost.dll: src/modules/sethost.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sethost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sethost.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/permanent.dll: src/modules/chanmodes/permanent.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/permanent.c $(MODLFLAGS)
+src/modules/setident.dll: src/modules/setident.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/setident.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/setident.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/regonly.dll: src/modules/chanmodes/regonly.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/regonly.c $(MODLFLAGS)
+src/modules/setname.dll: src/modules/setname.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/setname.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/setname.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/regonlyspeak.dll: src/modules/chanmodes/regonlyspeak.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/regonlyspeak.c $(MODLFLAGS)
+src/modules/silence.dll: src/modules/silence.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/silence.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/silence.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/secureonly.dll: src/modules/chanmodes/secureonly.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/secureonly.c $(MODLFLAGS)
+src/modules/sinfo.dll: src/modules/sinfo.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sinfo.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sinfo.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/stripcolor.dll: src/modules/chanmodes/stripcolor.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/stripcolor.c $(MODLFLAGS)
+src/modules/sjoin.dll: src/modules/sjoin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sjoin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sjoin.pdb $(MODLFLAGS)
 
-src/modules/chanmodes/history.dll: src/modules/chanmodes/history.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/chanmodes/ /Fesrc/modules/chanmodes/ src/modules/chanmodes/history.c $(MODLFLAGS)
+src/modules/slog.dll: src/modules/slog.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/slog.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/slog.pdb $(MODLFLAGS)
 
-src/modules/usermodes/censor.dll: src/modules/usermodes/censor.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/censor.c $(MODLFLAGS)
+src/modules/sqline.dll: src/modules/sqline.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sqline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sqline.pdb $(MODLFLAGS)
 
-src/modules/usermodes/noctcp.dll: src/modules/usermodes/noctcp.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/noctcp.c $(MODLFLAGS)
+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/usermodes/bot.dll: src/modules/usermodes/bot.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/bot.c $(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/usermodes/servicebot.dll: src/modules/usermodes/servicebot.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/servicebot.c $(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)
 
-src/modules/usermodes/showwhois.dll: src/modules/usermodes/showwhois.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/showwhois.c $(MODLFLAGS)
+src/modules/stats.dll: src/modules/stats.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/stats.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/stats.pdb $(MODLFLAGS)
 
-src/modules/usermodes/privacy.dll: src/modules/usermodes/privacy.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/privacy.c $(MODLFLAGS)
+src/modules/sts.dll: src/modules/sts.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sts.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sts.pdb $(MODLFLAGS)
 
-src/modules/usermodes/nokick.dll: src/modules/usermodes/nokick.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/nokick.c $(MODLFLAGS)
+src/modules/svsjoin.dll: src/modules/svsjoin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsjoin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsjoin.pdb $(MODLFLAGS)
 
-src/modules/usermodes/regonlymsg.dll: src/modules/usermodes/regonlymsg.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/regonlymsg.c $(MODLFLAGS)
+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/usermodes/privdeaf.dll: src/modules/usermodes/privdeaf.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/privdeaf.c $(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)
 
-src/modules/usermodes/secureonlymsg.dll: src/modules/usermodes/secureonlymsg.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/usermodes/ /Fesrc/modules/usermodes/ src/modules/usermodes/secureonlymsg.c $(MODLFLAGS)
+src/modules/svsmode.dll: src/modules/svsmode.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsmode.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsmode.pdb $(MODLFLAGS)
 
-src/modules/snomasks/dccreject.dll: src/modules/snomasks/dccreject.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/snomasks/ /Fesrc/modules/snomasks/ src/modules/snomasks/dccreject.c $(MODLFLAGS)
+src/modules/svsmotd.dll: src/modules/svsmotd.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsmotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsmotd.pdb $(MODLFLAGS)
 
-src/modules/extbans/account.dll: src/modules/extbans/account.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/account.c $(MODLFLAGS)
+src/modules/svsnick.dll: src/modules/svsnick.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsnick.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnick.pdb $(MODLFLAGS)
 
-src/modules/extbans/inchannel.dll: src/modules/extbans/inchannel.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/inchannel.c $(MODLFLAGS)
+src/modules/svsnline.dll: src/modules/svsnline.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsnline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnline.pdb $(MODLFLAGS)
 
-src/modules/extbans/join.dll: src/modules/extbans/join.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/join.c $(MODLFLAGS)
+src/modules/svsnolag.dll: src/modules/svsnolag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsnolag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnolag.pdb $(MODLFLAGS)
 
-src/modules/extbans/nickchange.dll: src/modules/extbans/nickchange.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/nickchange.c $(MODLFLAGS)
+src/modules/svsnoop.dll: src/modules/svsnoop.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svsnoop.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnoop.pdb $(MODLFLAGS)
 
-src/modules/extbans/quiet.dll: src/modules/extbans/quiet.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/quiet.c $(MODLFLAGS)
+src/modules/svspart.dll: src/modules/svspart.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svspart.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svspart.pdb $(MODLFLAGS)
 
-src/modules/extbans/realname.dll: src/modules/extbans/realname.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/realname.c $(MODLFLAGS)
+src/modules/svssilence.dll: src/modules/svssilence.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svssilence.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svssilence.pdb $(MODLFLAGS)
 
-src/modules/extbans/operclass.dll: src/modules/extbans/operclass.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/operclass.c $(MODLFLAGS)
+src/modules/svssno.dll: src/modules/svssno.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svssno.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svssno.pdb $(MODLFLAGS)
 
-src/modules/extbans/certfp.dll: src/modules/extbans/certfp.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/certfp.c $(MODLFLAGS)
+src/modules/svswatch.dll: src/modules/svswatch.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svswatch.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svswatch.pdb $(MODLFLAGS)
 
-src/modules/extbans/textban.dll: src/modules/extbans/textban.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/textban.c $(MODLFLAGS)
+src/modules/swhois.dll: src/modules/swhois.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/swhois.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/swhois.pdb $(MODLFLAGS)
 
-src/modules/extbans/msgbypass.dll: src/modules/extbans/msgbypass.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/msgbypass.c $(MODLFLAGS)
+src/modules/targetfloodprot.dll: src/modules/targetfloodprot.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/targetfloodprot.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/targetfloodprot.pdb $(MODLFLAGS)
 
-src/modules/extbans/timedban.dll: src/modules/extbans/timedban.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/timedban.c $(MODLFLAGS)
+src/modules/time.dll: src/modules/time.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/time.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/time.pdb $(MODLFLAGS)
 
-src/modules/extbans/partmsg.dll: src/modules/extbans/partmsg.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/partmsg.c $(MODLFLAGS)
+src/modules/tkl.dll: src/modules/tkl.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/tkl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tkl.pdb $(MODLFLAGS)
 
-src/modules/extbans/securitygroup.dll: src/modules/extbans/securitygroup.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/securitygroup.c $(MODLFLAGS)
+src/modules/tkldb.dll: src/modules/tkldb.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/tkldb.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tkldb.pdb $(MODLFLAGS)
 
-src/modules/account-notify.dll: src/modules/account-notify.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/account-notify.c $(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)
 
-src/modules/message-tags.dll: src/modules/message-tags.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/message-tags.c $(MODLFLAGS)
+src/modules/tls_cipher.dll: src/modules/tls_cipher.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/tls_cipher.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tls_cipher.pdb $(MODLFLAGS)
 
-src/modules/batch.dll: src/modules/batch.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/batch.c $(MODLFLAGS)
+src/modules/topic.dll: src/modules/topic.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/topic.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/topic.pdb $(MODLFLAGS)
 
-src/modules/account-tag.dll: src/modules/account-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/account-tag.c $(MODLFLAGS)
+src/modules/trace.dll: src/modules/trace.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/trace.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/trace.pdb $(MODLFLAGS)
 
-src/modules/labeled-response.dll: src/modules/labeled-response.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/labeled-response.c $(MODLFLAGS)
+src/modules/tsctl.dll: src/modules/tsctl.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/tsctl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tsctl.pdb $(MODLFLAGS)
 
-src/modules/link-security.dll: src/modules/link-security.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/link-security.c $(MODLFLAGS)
+src/modules/typing-indicator.dll: src/modules/typing-indicator.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/typing-indicator.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/typing-indicator.pdb $(MODLFLAGS)
 
-src/modules/message-ids.dll: src/modules/message-ids.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/message-ids.c $(MODLFLAGS)
+src/modules/umode2.dll: src/modules/umode2.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/umode2.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/umode2.pdb $(MODLFLAGS)
 
-src/modules/plaintext-policy.dll: src/modules/plaintext-policy.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/plaintext-policy.c $(MODLFLAGS)
+src/modules/unreal_server_compat.dll: src/modules/unreal_server_compat.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/unreal_server_compat.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/unreal_server_compat.pdb $(MODLFLAGS)
 
-src/modules/server-time.dll: src/modules/server-time.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/server-time.c $(MODLFLAGS)
+src/modules/unsqline.dll: src/modules/unsqline.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/unsqline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/unsqline.pdb $(MODLFLAGS)
 
-src/modules/sts.dll: src/modules/sts.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/sts.c $(MODLFLAGS)
+src/modules/user.dll: src/modules/user.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/user.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/user.pdb $(MODLFLAGS)
 
-src/modules/tkldb.dll: src/modules/tkldb.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/tkldb.c $(MODLFLAGS)
+src/modules/userhost.dll: src/modules/userhost.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/userhost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/userhost.pdb $(MODLFLAGS)
 
-src/modules/channeldb.dll: src/modules/channeldb.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/channeldb.c $(MODLFLAGS)
+src/modules/userhost-tag.dll: src/modules/userhost-tag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/userhost-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/userhost-tag.pdb $(MODLFLAGS)
 
-src/modules/history_backend_mem.dll: src/modules/history_backend_mem.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/history_backend_mem.c $(MODLFLAGS)
+src/modules/userip.dll: src/modules/userip.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/userip.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/userip.pdb $(MODLFLAGS)
 
-src/modules/history_backend_null.dll: src/modules/history_backend_null.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/history_backend_null.c $(MODLFLAGS)
+src/modules/userip-tag.dll: src/modules/userip-tag.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/userip-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/userip-tag.pdb $(MODLFLAGS)
 
-src/modules/restrict-commands.dll: src/modules/restrict-commands.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/restrict-commands.c $(MODLFLAGS)
+src/modules/usermodes/bot.dll: src/modules/usermodes/bot.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/bot.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/bot.pdb $(MODLFLAGS)
 
-src/modules/rmtkl.dll: src/modules/rmtkl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/rmtkl.c $(MODLFLAGS)
+src/modules/usermodes/censor.dll: src/modules/usermodes/censor.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/censor.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/censor.pdb $(MODLFLAGS)
 
-src/modules/echo-message.dll: src/modules/echo-message.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/echo-message.c $(MODLFLAGS)
+src/modules/usermodes/noctcp.dll: src/modules/usermodes/noctcp.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/noctcp.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/noctcp.pdb $(MODLFLAGS)
 
-src/modules/userip-tag.dll: src/modules/userip-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/userip-tag.c $(MODLFLAGS)
+src/modules/usermodes/nokick.dll: src/modules/usermodes/nokick.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/nokick.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/nokick.pdb $(MODLFLAGS)
 
-src/modules/userhost-tag.dll: src/modules/userhost-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/userhost-tag.c $(MODLFLAGS)
+src/modules/usermodes/privacy.dll: src/modules/usermodes/privacy.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/privacy.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/privacy.pdb $(MODLFLAGS)
 
-src/modules/bot-tag.dll: src/modules/bot-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/bot-tag.c $(MODLFLAGS)
+src/modules/usermodes/privdeaf.dll: src/modules/usermodes/privdeaf.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/privdeaf.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/privdeaf.pdb $(MODLFLAGS)
 
-src/modules/reply-tag.dll: src/modules/reply-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/reply-tag.c $(MODLFLAGS)
+src/modules/usermodes/regonlymsg.dll: src/modules/usermodes/regonlymsg.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/regonlymsg.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/regonlymsg.pdb $(MODLFLAGS)
 
-src/modules/require-module.dll: src/modules/require-module.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/require-module.c $(MODLFLAGS)
+src/modules/usermodes/secureonlymsg.dll: src/modules/usermodes/secureonlymsg.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/secureonlymsg.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/secureonlymsg.pdb $(MODLFLAGS)
 
-src/modules/ident_lookup.dll: src/modules/ident_lookup.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/ident_lookup.c $(MODLFLAGS)
+src/modules/usermodes/servicebot.dll: src/modules/usermodes/servicebot.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/servicebot.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/servicebot.pdb $(MODLFLAGS)
 
-src/modules/history.dll: src/modules/history.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/history.c $(MODLFLAGS)
+src/modules/usermodes/showwhois.dll: src/modules/usermodes/showwhois.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/showwhois.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/showwhois.pdb $(MODLFLAGS)
 
-src/modules/chathistory.dll: src/modules/chathistory.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/chathistory.c $(MODLFLAGS)
+src/modules/usermodes/wallops.dll: src/modules/usermodes/wallops.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/usermodes/wallops.c /Fesrc/modules/usermodes/ /Fosrc/modules/usermodes/ /Fdsrc/modules/usermodes/wallops.pdb $(MODLFLAGS)
 
-src/modules/targetfloodprot.dll: src/modules/targetfloodprot.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/targetfloodprot.c $(MODLFLAGS)
+src/modules/vhost.dll: src/modules/vhost.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/vhost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/vhost.pdb $(MODLFLAGS)
 
-src/modules/typing-indicator.dll: src/modules/typing-indicator.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/typing-indicator.c $(MODLFLAGS)
+src/modules/watch-backend.dll: src/modules/watch-backend.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/watch-backend.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/watch-backend.pdb $(MODLFLAGS)
 
-src/modules/clienttagdeny.dll: src/modules/clienttagdeny.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/clienttagdeny.c $(MODLFLAGS)
+src/modules/watch.dll: src/modules/watch.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/watch.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/watch.pdb $(MODLFLAGS)
+
+src/modules/webirc.dll: src/modules/webirc.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/webirc.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/webirc.pdb $(MODLFLAGS)
+
+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/websocket.dll: src/modules/websocket.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/websocket.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/websocket.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)
+
+src/modules/who_old.dll: src/modules/who_old.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/who_old.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/who_old.pdb $(MODLFLAGS)
+
+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/whox.dll: src/modules/whox.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/whox.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whox.pdb $(MODLFLAGS)
 
-dummy:
diff --git a/README.md b/README.md
@@ -9,6 +9,11 @@ Key features include SSL/TLS, cloaking, its advanced anti-flood and anti-spam sy
 swear filtering and module support. We are also particularly proud on our extensive
 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.
+
 ## How to get started
 Please consult our excellent online documentation at https://www.unrealircd.org/docs/
 when setting up the IRCd!
diff --git a/SECURITY.md b/SECURITY.md
@@ -1,7 +1,8 @@
 # Security Policy
 
 ## Supported Versions
-* The latest *stable* release of the 5.x branch
+* The latest *stable* release of UnrealIRCd 5 (until 2023-07-01)
+* The latest *stable* release of UnrealIRCd 6
 
 See [UnrealIRCd releases](https://www.unrealircd.org/docs/UnrealIRCd_releases) for information on older versions and End Of Life dates.
 
diff --git a/autoconf/config.guess b/autoconf/config.guess
@@ -1,8 +1,10 @@
 #! /bin/sh
 # Attempt to guess a canonical system name.
-#   Copyright 1992-2015 Free Software Foundation, Inc.
+#   Copyright 1992-2021 Free Software Foundation, Inc.
 
-timestamp='2015-03-04'
+# shellcheck disable=SC2006,SC2268 # see below for rationale
+
+timestamp='2021-06-03'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -15,7 +17,7 @@ timestamp='2015-03-04'
 # General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
 #
 # As a special exception to the GNU General Public License, if you
 # distribute this file as part of a program that contains a
@@ -27,11 +29,19 @@ timestamp='2015-03-04'
 # Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
 #
 # You can get the latest version of this script from:
-# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
 #
 # Please send patches to <config-patches@gnu.org>.
 
 
+# The "shellcheck disable" line above the timestamp inhibits complaints
+# about features and limitations of the classic Bourne shell that were
+# superseded or lifted in POSIX.  However, this script identifies a wide
+# variety of pre-POSIX systems that do not have POSIX shells at all, and
+# even some reasonably current systems (Solaris 10 as case-in-point) still
+# have a pre-POSIX /bin/sh.
+
+
 me=`echo "$0" | sed -e 's,.*/,,'`
 
 usage="\
@@ -39,7 +49,7 @@ Usage: $0 [OPTION]
 
 Output the configuration name of the system \`$me' is run on.
 
-Operation modes:
+Options:
   -h, --help         print this help, then exit
   -t, --time-stamp   print date of last modification, then exit
   -v, --version      print version number, then exit
@@ -50,7 +60,7 @@ version="\
 GNU config.guess ($timestamp)
 
 Originally written by Per Bothner.
-Copyright 1992-2015 Free Software Foundation, Inc.
+Copyright 1992-2021 Free Software Foundation, Inc.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -84,7 +94,8 @@ if test $# != 0; then
   exit 1
 fi
 
-trap 'exit 1' 1 2 15
+# Just in case it came from the environment.
+GUESS=
 
 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a
 # compiler to aid in system detection is discouraged as it requires
@@ -96,66 +107,90 @@ trap 'exit 1' 1 2 15
 
 # Portable tmp directory creation inspired by the Autoconf team.
 
-set_cc_for_build='
-trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
-trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
-: ${TMPDIR=/tmp} ;
- { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
- { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
- { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
- { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
-dummy=$tmp/dummy ;
-tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
-case $CC_FOR_BUILD,$HOST_CC,$CC in
- ,,)    echo "int x;" > $dummy.c ;
-	for c in cc gcc c89 c99 ; do
-	  if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
-	     CC_FOR_BUILD="$c"; break ;
-	  fi ;
-	done ;
-	if test x"$CC_FOR_BUILD" = x ; then
-	  CC_FOR_BUILD=no_compiler_found ;
-	fi
-	;;
- ,,*)   CC_FOR_BUILD=$CC ;;
- ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
-esac ; set_cc_for_build= ;'
+tmp=
+# shellcheck disable=SC2172
+trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15
+
+set_cc_for_build() {
+    # prevent multiple calls if $tmp is already set
+    test "$tmp" && return 0
+    : "${TMPDIR=/tmp}"
+    # shellcheck disable=SC2039,SC3028
+    { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+	{ test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } ||
+	{ tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+	{ echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; }
+    dummy=$tmp/dummy
+    case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in
+	,,)    echo "int x;" > "$dummy.c"
+	       for driver in cc gcc c89 c99 ; do
+		   if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then
+		       CC_FOR_BUILD=$driver
+		       break
+		   fi
+	       done
+	       if test x"$CC_FOR_BUILD" = x ; then
+		   CC_FOR_BUILD=no_compiler_found
+	       fi
+	       ;;
+	,,*)   CC_FOR_BUILD=$CC ;;
+	,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+    esac
+}
 
 # This is needed to find uname on a Pyramid OSx when run in the BSD universe.
 # (ghazi@noc.rutgers.edu 1994-08-24)
-if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+if test -f /.attbin/uname ; then
 	PATH=$PATH:/.attbin ; export PATH
 fi
 
 UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
 UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
-UNAME_SYSTEM=`(uname -s) 2>/dev/null`  || UNAME_SYSTEM=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
 UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
 
-case "${UNAME_SYSTEM}" in
+case $UNAME_SYSTEM in
 Linux|GNU|GNU/*)
-	# If the system lacks a compiler, then just pick glibc.
-	# We could probably try harder.
-	LIBC=gnu
+	LIBC=unknown
 
-	eval $set_cc_for_build
-	cat <<-EOF > $dummy.c
+	set_cc_for_build
+	cat <<-EOF > "$dummy.c"
 	#include <features.h>
 	#if defined(__UCLIBC__)
 	LIBC=uclibc
 	#elif defined(__dietlibc__)
 	LIBC=dietlibc
-	#else
+	#elif defined(__GLIBC__)
 	LIBC=gnu
+	#else
+	#include <stdarg.h>
+	/* First heuristic to detect musl libc.  */
+	#ifdef __DEFINED_va_list
+	LIBC=musl
+	#endif
 	#endif
 	EOF
-	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+	cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+	eval "$cc_set_libc"
+
+	# Second heuristic to detect musl libc.
+	if [ "$LIBC" = unknown ] &&
+	   command -v ldd >/dev/null &&
+	   ldd --version 2>&1 | grep -q ^musl; then
+		LIBC=musl
+	fi
+
+	# If the system lacks a compiler, then just pick glibc.
+	# We could probably try harder.
+	if [ "$LIBC" = unknown ]; then
+		LIBC=gnu
+	fi
 	;;
 esac
 
 # Note: order is significant - the case branches are not exclusive.
 
-case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in
     *:NetBSD:*:*)
 	# NetBSD (nbsd) targets should (where applicable) match one or
 	# more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
@@ -167,29 +202,32 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
 	#
 	# Note: NetBSD doesn't particularly care about the vendor
 	# portion of the name.  We always set it to "unknown".
-	sysctl="sysctl -n hw.machine_arch"
 	UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \
-	    /sbin/$sysctl 2>/dev/null || \
-	    /usr/sbin/$sysctl 2>/dev/null || \
+	    /sbin/sysctl -n hw.machine_arch 2>/dev/null || \
+	    /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \
 	    echo unknown)`
-	case "${UNAME_MACHINE_ARCH}" in
+	case $UNAME_MACHINE_ARCH in
+	    aarch64eb) machine=aarch64_be-unknown ;;
 	    armeb) machine=armeb-unknown ;;
 	    arm*) machine=arm-unknown ;;
 	    sh3el) machine=shl-unknown ;;
 	    sh3eb) machine=sh-unknown ;;
 	    sh5el) machine=sh5le-unknown ;;
 	    earmv*)
-		arch=`echo ${UNAME_MACHINE_ARCH} | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
-		endian=`echo ${UNAME_MACHINE_ARCH} | sed -ne 's,^.*\(eb\)$,\1,p'`
+		arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
+		endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'`
 		machine=${arch}${endian}-unknown
 		;;
-	    *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+	    *) machine=$UNAME_MACHINE_ARCH-unknown ;;
 	esac
 	# The Operating System including object format, if it has switched
-	# to ELF recently, or will in the future.
-	case "${UNAME_MACHINE_ARCH}" in
-	    arm*|earm*|i386|m68k|ns32k|sh3*|sparc|vax)
-		eval $set_cc_for_build
+	# to ELF recently (or will in the future) and ABI.
+	case $UNAME_MACHINE_ARCH in
+	    earm*)
+		os=netbsdelf
+		;;
+	    arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+		set_cc_for_build
 		if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
 			| grep -q __ELF__
 		then
@@ -205,10 +243,10 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
 		;;
 	esac
 	# Determine ABI tags.
-	case "${UNAME_MACHINE_ARCH}" in
+	case $UNAME_MACHINE_ARCH in
 	    earm*)
 		expr='s/^earmv[0-9]/-eabi/;s/eb$//'
-		abi=`echo ${UNAME_MACHINE_ARCH} | sed -e "$expr"`
+		abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"`
 		;;
 	esac
 	# The OS release
@@ -216,40 +254,68 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
 	# thus, need a distinct triplet. However, they do not need
 	# kernel version information, so it can be replaced with a
 	# suitable tag, in the style of linux-gnu.
-	case "${UNAME_VERSION}" in
+	case $UNAME_VERSION in
 	    Debian*)
 		release='-gnu'
 		;;
 	    *)
-		release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+		release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2`
 		;;
 	esac
 	# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
 	# contains redundant information, the shorter form:
 	# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
-	echo "${machine}-${os}${release}${abi}"
-	exit ;;
+	GUESS=$machine-${os}${release}${abi-}
+	;;
     *:Bitrig:*:*)
 	UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
-	echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE
+	;;
     *:OpenBSD:*:*)
 	UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
-	echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE
+	;;
+    *:SecBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'`
+	GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE
+	;;
+    *:LibertyBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'`
+	GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE
+	;;
+    *:MidnightBSD:*:*)
+	GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE
+	;;
     *:ekkoBSD:*:*)
-	echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE
+	;;
     *:SolidBSD:*:*)
-	echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE
+	;;
+    *:OS108:*:*)
+	GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE
+	;;
     macppc:MirBSD:*:*)
-	echo powerpc-unknown-mirbsd${UNAME_RELEASE}
-	exit ;;
+	GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE
+	;;
     *:MirBSD:*:*)
-	echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE
+	;;
+    *:Sortix:*:*)
+	GUESS=$UNAME_MACHINE-unknown-sortix
+	;;
+    *:Twizzler:*:*)
+	GUESS=$UNAME_MACHINE-unknown-twizzler
+	;;
+    *:Redox:*:*)
+	GUESS=$UNAME_MACHINE-unknown-redox
+	;;
+    mips:OSF1:*.*)
+	GUESS=mips-dec-osf1
+	;;
     alpha:OSF1:*:*)
+	# Reset EXIT trap before exiting to avoid spurious non-zero exit code.
+	trap '' 0
 	case $UNAME_RELEASE in
 	*4.0)
 		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
@@ -263,163 +329,158 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
 	# covers most systems running today.  This code pipes the CPU
 	# types through head -n 1, so we only detect the type of CPU 0.
 	ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
-	case "$ALPHA_CPU_TYPE" in
+	case $ALPHA_CPU_TYPE in
 	    "EV4 (21064)")
-		UNAME_MACHINE="alpha" ;;
+		UNAME_MACHINE=alpha ;;
 	    "EV4.5 (21064)")
-		UNAME_MACHINE="alpha" ;;
+		UNAME_MACHINE=alpha ;;
 	    "LCA4 (21066/21068)")
-		UNAME_MACHINE="alpha" ;;
+		UNAME_MACHINE=alpha ;;
 	    "EV5 (21164)")
-		UNAME_MACHINE="alphaev5" ;;
+		UNAME_MACHINE=alphaev5 ;;
 	    "EV5.6 (21164A)")
-		UNAME_MACHINE="alphaev56" ;;
+		UNAME_MACHINE=alphaev56 ;;
 	    "EV5.6 (21164PC)")
-		UNAME_MACHINE="alphapca56" ;;
+		UNAME_MACHINE=alphapca56 ;;
 	    "EV5.7 (21164PC)")
-		UNAME_MACHINE="alphapca57" ;;
+		UNAME_MACHINE=alphapca57 ;;
 	    "EV6 (21264)")
-		UNAME_MACHINE="alphaev6" ;;
+		UNAME_MACHINE=alphaev6 ;;
 	    "EV6.7 (21264A)")
-		UNAME_MACHINE="alphaev67" ;;
+		UNAME_MACHINE=alphaev67 ;;
 	    "EV6.8CB (21264C)")
-		UNAME_MACHINE="alphaev68" ;;
+		UNAME_MACHINE=alphaev68 ;;
 	    "EV6.8AL (21264B)")
-		UNAME_MACHINE="alphaev68" ;;
+		UNAME_MACHINE=alphaev68 ;;
 	    "EV6.8CX (21264D)")
-		UNAME_MACHINE="alphaev68" ;;
+		UNAME_MACHINE=alphaev68 ;;
 	    "EV6.9A (21264/EV69A)")
-		UNAME_MACHINE="alphaev69" ;;
+		UNAME_MACHINE=alphaev69 ;;
 	    "EV7 (21364)")
-		UNAME_MACHINE="alphaev7" ;;
+		UNAME_MACHINE=alphaev7 ;;
 	    "EV7.9 (21364A)")
-		UNAME_MACHINE="alphaev79" ;;
+		UNAME_MACHINE=alphaev79 ;;
 	esac
 	# A Pn.n version is a patched version.
 	# A Vn.n version is a released version.
 	# A Tn.n version is a released field test version.
 	# A Xn.n version is an unreleased experimental baselevel.
 	# 1.2 uses "1.2" for uname -r.
-	echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
-	# Reset EXIT trap before exiting to avoid spurious non-zero exit code.
-	exitcode=$?
-	trap '' 0
-	exit $exitcode ;;
-    Alpha\ *:Windows_NT*:*)
-	# How do we know it's Interix rather than the generic POSIX subsystem?
-	# Should we change UNAME_MACHINE based on the output of uname instead
-	# of the specific Alpha model?
-	echo alpha-pc-interix
-	exit ;;
-    21064:Windows_NT:50:3)
-	echo alpha-dec-winnt3.5
-	exit ;;
+	OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+	GUESS=$UNAME_MACHINE-dec-osf$OSF_REL
+	;;
     Amiga*:UNIX_System_V:4.0:*)
-	echo m68k-unknown-sysv4
-	exit ;;
+	GUESS=m68k-unknown-sysv4
+	;;
     *:[Aa]miga[Oo][Ss]:*:*)
-	echo ${UNAME_MACHINE}-unknown-amigaos
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-amigaos
+	;;
     *:[Mm]orph[Oo][Ss]:*:*)
-	echo ${UNAME_MACHINE}-unknown-morphos
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-morphos
+	;;
     *:OS/390:*:*)
-	echo i370-ibm-openedition
-	exit ;;
+	GUESS=i370-ibm-openedition
+	;;
     *:z/VM:*:*)
-	echo s390-ibm-zvmoe
-	exit ;;
+	GUESS=s390-ibm-zvmoe
+	;;
     *:OS400:*:*)
-	echo powerpc-ibm-os400
-	exit ;;
+	GUESS=powerpc-ibm-os400
+	;;
     arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
-	echo arm-acorn-riscix${UNAME_RELEASE}
-	exit ;;
+	GUESS=arm-acorn-riscix$UNAME_RELEASE
+	;;
     arm*:riscos:*:*|arm*:RISCOS:*:*)
-	echo arm-unknown-riscos
-	exit ;;
+	GUESS=arm-unknown-riscos
+	;;
     SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
-	echo hppa1.1-hitachi-hiuxmpp
-	exit ;;
+	GUESS=hppa1.1-hitachi-hiuxmpp
+	;;
     Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
 	# akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
-	if test "`(/bin/universe) 2>/dev/null`" = att ; then
-		echo pyramid-pyramid-sysv3
-	else
-		echo pyramid-pyramid-bsd
-	fi
-	exit ;;
+	case `(/bin/universe) 2>/dev/null` in
+	    att) GUESS=pyramid-pyramid-sysv3 ;;
+	    *)   GUESS=pyramid-pyramid-bsd   ;;
+	esac
+	;;
     NILE*:*:*:dcosx)
-	echo pyramid-pyramid-svr4
-	exit ;;
+	GUESS=pyramid-pyramid-svr4
+	;;
     DRS?6000:unix:4.0:6*)
-	echo sparc-icl-nx6
-	exit ;;
+	GUESS=sparc-icl-nx6
+	;;
     DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
 	case `/usr/bin/uname -p` in
-	    sparc) echo sparc-icl-nx7; exit ;;
-	esac ;;
+	    sparc) GUESS=sparc-icl-nx7 ;;
+	esac
+	;;
     s390x:SunOS:*:*)
-	echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL
+	;;
     sun4H:SunOS:5.*:*)
-	echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=sparc-hal-solaris2$SUN_REL
+	;;
     sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
-	echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=sparc-sun-solaris2$SUN_REL
+	;;
     i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
-	echo i386-pc-auroraux${UNAME_RELEASE}
-	exit ;;
+	GUESS=i386-pc-auroraux$UNAME_RELEASE
+	;;
     i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
-	eval $set_cc_for_build
-	SUN_ARCH="i386"
+	set_cc_for_build
+	SUN_ARCH=i386
 	# If there is a compiler, see if it is configured for 64-bit objects.
 	# Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
 	# This test works for both compilers.
-	if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+	if test "$CC_FOR_BUILD" != no_compiler_found; then
 	    if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
-		(CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+		(CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
 		grep IS_64BIT_ARCH >/dev/null
 	    then
-		SUN_ARCH="x86_64"
+		SUN_ARCH=x86_64
 	    fi
 	fi
-	echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=$SUN_ARCH-pc-solaris2$SUN_REL
+	;;
     sun4*:SunOS:6*:*)
 	# According to config.sub, this is the proper way to canonicalize
 	# SunOS6.  Hard to guess exactly what SunOS6 will be like, but
 	# it's likely to be more like Solaris than SunOS4.
-	echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=sparc-sun-solaris3$SUN_REL
+	;;
     sun4*:SunOS:*:*)
-	case "`/usr/bin/arch -k`" in
+	case `/usr/bin/arch -k` in
 	    Series*|S4*)
 		UNAME_RELEASE=`uname -v`
 		;;
 	esac
 	# Japanese Language versions have a version number like `4.1.3-JL'.
-	echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
+	GUESS=sparc-sun-sunos$SUN_REL
+	;;
     sun3*:SunOS:*:*)
-	echo m68k-sun-sunos${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-sun-sunos$UNAME_RELEASE
+	;;
     sun*:*:4.2BSD:*)
 	UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
-	test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
-	case "`/bin/arch`" in
+	test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3
+	case `/bin/arch` in
 	    sun3)
-		echo m68k-sun-sunos${UNAME_RELEASE}
+		GUESS=m68k-sun-sunos$UNAME_RELEASE
 		;;
 	    sun4)
-		echo sparc-sun-sunos${UNAME_RELEASE}
+		GUESS=sparc-sun-sunos$UNAME_RELEASE
 		;;
 	esac
-	exit ;;
+	;;
     aushp:SunOS:*:*)
-	echo sparc-auspex-sunos${UNAME_RELEASE}
-	exit ;;
+	GUESS=sparc-auspex-sunos$UNAME_RELEASE
+	;;
     # The situation for MiNT is a little confusing.  The machine name
     # can be virtually everything (everything which is not
     # "atarist" or "atariste" at least should have a processor
@@ -429,44 +490,44 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
     # MiNT.  But MiNT is downward compatible to TOS, so this should
     # be no problem.
     atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
-	echo m68k-atari-mint${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-atari-mint$UNAME_RELEASE
+	;;
     atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
-	echo m68k-atari-mint${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-atari-mint$UNAME_RELEASE
+	;;
     *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
-	echo m68k-atari-mint${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-atari-mint$UNAME_RELEASE
+	;;
     milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
-	echo m68k-milan-mint${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-milan-mint$UNAME_RELEASE
+	;;
     hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
-	echo m68k-hades-mint${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-hades-mint$UNAME_RELEASE
+	;;
     *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
-	echo m68k-unknown-mint${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-unknown-mint$UNAME_RELEASE
+	;;
     m68k:machten:*:*)
-	echo m68k-apple-machten${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-apple-machten$UNAME_RELEASE
+	;;
     powerpc:machten:*:*)
-	echo powerpc-apple-machten${UNAME_RELEASE}
-	exit ;;
+	GUESS=powerpc-apple-machten$UNAME_RELEASE
+	;;
     RISC*:Mach:*:*)
-	echo mips-dec-mach_bsd4.3
-	exit ;;
+	GUESS=mips-dec-mach_bsd4.3
+	;;
     RISC*:ULTRIX:*:*)
-	echo mips-dec-ultrix${UNAME_RELEASE}
-	exit ;;
+	GUESS=mips-dec-ultrix$UNAME_RELEASE
+	;;
     VAX*:ULTRIX*:*:*)
-	echo vax-dec-ultrix${UNAME_RELEASE}
-	exit ;;
+	GUESS=vax-dec-ultrix$UNAME_RELEASE
+	;;
     2020:CLIX:*:* | 2430:CLIX:*:*)
-	echo clipper-intergraph-clix${UNAME_RELEASE}
-	exit ;;
+	GUESS=clipper-intergraph-clix$UNAME_RELEASE
+	;;
     mips:*:*:UMIPS | mips:*:*:RISCos)
-	eval $set_cc_for_build
-	sed 's/^	//' << EOF >$dummy.c
+	set_cc_for_build
+	sed 's/^	//' << EOF > "$dummy.c"
 #ifdef __cplusplus
 #include <stdio.h>  /* for printf() prototype */
 	int main (int argc, char *argv[]) {
@@ -475,95 +536,96 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
 #endif
 	#if defined (host_mips) && defined (MIPSEB)
 	#if defined (SYSTYPE_SYSV)
-	  printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+	  printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0);
 	#endif
 	#if defined (SYSTYPE_SVR4)
-	  printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+	  printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0);
 	#endif
 	#if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
-	  printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+	  printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0);
 	#endif
 	#endif
 	  exit (-1);
 	}
 EOF
-	$CC_FOR_BUILD -o $dummy $dummy.c &&
-	  dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` &&
-	  SYSTEM_NAME=`$dummy $dummyarg` &&
+	$CC_FOR_BUILD -o "$dummy" "$dummy.c" &&
+	  dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+	  SYSTEM_NAME=`"$dummy" "$dummyarg"` &&
 	    { echo "$SYSTEM_NAME"; exit; }
-	echo mips-mips-riscos${UNAME_RELEASE}
-	exit ;;
+	GUESS=mips-mips-riscos$UNAME_RELEASE
+	;;
     Motorola:PowerMAX_OS:*:*)
-	echo powerpc-motorola-powermax
-	exit ;;
+	GUESS=powerpc-motorola-powermax
+	;;
     Motorola:*:4.3:PL8-*)
-	echo powerpc-harris-powermax
-	exit ;;
+	GUESS=powerpc-harris-powermax
+	;;
     Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
-	echo powerpc-harris-powermax
-	exit ;;
+	GUESS=powerpc-harris-powermax
+	;;
     Night_Hawk:Power_UNIX:*:*)
-	echo powerpc-harris-powerunix
-	exit ;;
+	GUESS=powerpc-harris-powerunix
+	;;
     m88k:CX/UX:7*:*)
-	echo m88k-harris-cxux7
-	exit ;;
+	GUESS=m88k-harris-cxux7
+	;;
     m88k:*:4*:R4*)
-	echo m88k-motorola-sysv4
-	exit ;;
+	GUESS=m88k-motorola-sysv4
+	;;
     m88k:*:3*:R3*)
-	echo m88k-motorola-sysv3
-	exit ;;
+	GUESS=m88k-motorola-sysv3
+	;;
     AViiON:dgux:*:*)
 	# DG/UX returns AViiON for all architectures
 	UNAME_PROCESSOR=`/usr/bin/uname -p`
-	if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+	if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110
 	then
-	    if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
-	       [ ${TARGET_BINARY_INTERFACE}x = x ]
+	    if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \
+	       test "$TARGET_BINARY_INTERFACE"x = x
 	    then
-		echo m88k-dg-dgux${UNAME_RELEASE}
+		GUESS=m88k-dg-dgux$UNAME_RELEASE
 	    else
-		echo m88k-dg-dguxbcs${UNAME_RELEASE}
+		GUESS=m88k-dg-dguxbcs$UNAME_RELEASE
 	    fi
 	else
-	    echo i586-dg-dgux${UNAME_RELEASE}
+	    GUESS=i586-dg-dgux$UNAME_RELEASE
 	fi
-	exit ;;
+	;;
     M88*:DolphinOS:*:*)	# DolphinOS (SVR3)
-	echo m88k-dolphin-sysv3
-	exit ;;
+	GUESS=m88k-dolphin-sysv3
+	;;
     M88*:*:R3*:*)
 	# Delta 88k system running SVR3
-	echo m88k-motorola-sysv3
-	exit ;;
+	GUESS=m88k-motorola-sysv3
+	;;
     XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
-	echo m88k-tektronix-sysv3
-	exit ;;
+	GUESS=m88k-tektronix-sysv3
+	;;
     Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
-	echo m68k-tektronix-bsd
-	exit ;;
+	GUESS=m68k-tektronix-bsd
+	;;
     *:IRIX*:*:*)
-	echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
-	exit ;;
+	IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'`
+	GUESS=mips-sgi-irix$IRIX_REL
+	;;
     ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
-	echo romp-ibm-aix     # uname -m gives an 8 hex-code CPU id
-	exit ;;               # Note that: echo "'`uname -s`'" gives 'AIX '
+	GUESS=romp-ibm-aix    # uname -m gives an 8 hex-code CPU id
+	;;                    # Note that: echo "'`uname -s`'" gives 'AIX '
     i*86:AIX:*:*)
-	echo i386-ibm-aix
-	exit ;;
+	GUESS=i386-ibm-aix
+	;;
     ia64:AIX:*:*)
-	if [ -x /usr/bin/oslevel ] ; then
+	if test -x /usr/bin/oslevel ; then
 		IBM_REV=`/usr/bin/oslevel`
 	else
-		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+		IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
 	fi
-	echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
-	exit ;;
+	GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV
+	;;
     *:AIX:2:3)
 	if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
-		eval $set_cc_for_build
-		sed 's/^		//' << EOF >$dummy.c
+		set_cc_for_build
+		sed 's/^		//' << EOF > "$dummy.c"
 		#include <sys/systemcfg.h>
 
 		main()
@@ -574,77 +636,77 @@ EOF
 			exit(0);
 			}
 EOF
-		if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy`
+		if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"`
 		then
-			echo "$SYSTEM_NAME"
+			GUESS=$SYSTEM_NAME
 		else
-			echo rs6000-ibm-aix3.2.5
+			GUESS=rs6000-ibm-aix3.2.5
 		fi
 	elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
-		echo rs6000-ibm-aix3.2.4
+		GUESS=rs6000-ibm-aix3.2.4
 	else
-		echo rs6000-ibm-aix3.2
+		GUESS=rs6000-ibm-aix3.2
 	fi
-	exit ;;
+	;;
     *:AIX:*:[4567])
 	IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
-	if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+	if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then
 		IBM_ARCH=rs6000
 	else
 		IBM_ARCH=powerpc
 	fi
-	if [ -x /usr/bin/lslpp ] ; then
-		IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc |
+	if test -x /usr/bin/lslpp ; then
+		IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \
 			   awk -F: '{ print $3 }' | sed s/[0-9]*$/0/`
 	else
-		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+		IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
 	fi
-	echo ${IBM_ARCH}-ibm-aix${IBM_REV}
-	exit ;;
+	GUESS=$IBM_ARCH-ibm-aix$IBM_REV
+	;;
     *:AIX:*:*)
-	echo rs6000-ibm-aix
-	exit ;;
-    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
-	echo romp-ibm-bsd4.4
-	exit ;;
+	GUESS=rs6000-ibm-aix
+	;;
+    ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*)
+	GUESS=romp-ibm-bsd4.4
+	;;
     ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
-	echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
-	exit ;;                             # report: romp-ibm BSD 4.3
+	GUESS=romp-ibm-bsd$UNAME_RELEASE    # 4.3 with uname added to
+	;;                                  # report: romp-ibm BSD 4.3
     *:BOSX:*:*)
-	echo rs6000-bull-bosx
-	exit ;;
+	GUESS=rs6000-bull-bosx
+	;;
     DPX/2?00:B.O.S.:*:*)
-	echo m68k-bull-sysv3
-	exit ;;
+	GUESS=m68k-bull-sysv3
+	;;
     9000/[34]??:4.3bsd:1.*:*)
-	echo m68k-hp-bsd
-	exit ;;
+	GUESS=m68k-hp-bsd
+	;;
     hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
-	echo m68k-hp-bsd4.4
-	exit ;;
+	GUESS=m68k-hp-bsd4.4
+	;;
     9000/[34678]??:HP-UX:*:*)
-	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
-	case "${UNAME_MACHINE}" in
-	    9000/31? )            HP_ARCH=m68000 ;;
-	    9000/[34]?? )         HP_ARCH=m68k ;;
+	HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
+	case $UNAME_MACHINE in
+	    9000/31?)            HP_ARCH=m68000 ;;
+	    9000/[34]??)         HP_ARCH=m68k ;;
 	    9000/[678][0-9][0-9])
-		if [ -x /usr/bin/getconf ]; then
+		if test -x /usr/bin/getconf; then
 		    sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
 		    sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
-		    case "${sc_cpu_version}" in
-		      523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
-		      528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+		    case $sc_cpu_version in
+		      523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
+		      528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
 		      532)                      # CPU_PA_RISC2_0
-			case "${sc_kernel_bits}" in
-			  32) HP_ARCH="hppa2.0n" ;;
-			  64) HP_ARCH="hppa2.0w" ;;
-			  '') HP_ARCH="hppa2.0" ;;   # HP-UX 10.20
+			case $sc_kernel_bits in
+			  32) HP_ARCH=hppa2.0n ;;
+			  64) HP_ARCH=hppa2.0w ;;
+			  '') HP_ARCH=hppa2.0 ;;   # HP-UX 10.20
 			esac ;;
 		    esac
 		fi
-		if [ "${HP_ARCH}" = "" ]; then
-		    eval $set_cc_for_build
-		    sed 's/^		//' << EOF >$dummy.c
+		if test "$HP_ARCH" = ""; then
+		    set_cc_for_build
+		    sed 's/^		//' << EOF > "$dummy.c"
 
 		#define _HPUX_SOURCE
 		#include <stdlib.h>
@@ -677,13 +739,13 @@ EOF
 		    exit (0);
 		}
 EOF
-		    (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+		    (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"`
 		    test -z "$HP_ARCH" && HP_ARCH=hppa
 		fi ;;
 	esac
-	if [ ${HP_ARCH} = "hppa2.0w" ]
+	if test "$HP_ARCH" = hppa2.0w
 	then
-	    eval $set_cc_for_build
+	    set_cc_for_build
 
 	    # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
 	    # 32-bit code.  hppa64-hp-hpux* has the same kernel and a compiler
@@ -694,23 +756,23 @@ EOF
 	    # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
 	    # => hppa64-hp-hpux11.23
 
-	    if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) |
+	    if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
 		grep -q __LP64__
 	    then
-		HP_ARCH="hppa2.0w"
+		HP_ARCH=hppa2.0w
 	    else
-		HP_ARCH="hppa64"
+		HP_ARCH=hppa64
 	    fi
 	fi
-	echo ${HP_ARCH}-hp-hpux${HPUX_REV}
-	exit ;;
+	GUESS=$HP_ARCH-hp-hpux$HPUX_REV
+	;;
     ia64:HP-UX:*:*)
-	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
-	echo ia64-hp-hpux${HPUX_REV}
-	exit ;;
+	HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
+	GUESS=ia64-hp-hpux$HPUX_REV
+	;;
     3050*:HI-UX:*:*)
-	eval $set_cc_for_build
-	sed 's/^	//' << EOF >$dummy.c
+	set_cc_for_build
+	sed 's/^	//' << EOF > "$dummy.c"
 	#include <unistd.h>
 	int
 	main ()
@@ -735,38 +797,38 @@ EOF
 	  exit (0);
 	}
 EOF
-	$CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` &&
+	$CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` &&
 		{ echo "$SYSTEM_NAME"; exit; }
-	echo unknown-hitachi-hiuxwe2
-	exit ;;
-    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
-	echo hppa1.1-hp-bsd
-	exit ;;
+	GUESS=unknown-hitachi-hiuxwe2
+	;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*)
+	GUESS=hppa1.1-hp-bsd
+	;;
     9000/8??:4.3bsd:*:*)
-	echo hppa1.0-hp-bsd
-	exit ;;
+	GUESS=hppa1.0-hp-bsd
+	;;
     *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
-	echo hppa1.0-hp-mpeix
-	exit ;;
-    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
-	echo hppa1.1-hp-osf
-	exit ;;
+	GUESS=hppa1.0-hp-mpeix
+	;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*)
+	GUESS=hppa1.1-hp-osf
+	;;
     hp8??:OSF1:*:*)
-	echo hppa1.0-hp-osf
-	exit ;;
+	GUESS=hppa1.0-hp-osf
+	;;
     i*86:OSF1:*:*)
-	if [ -x /usr/sbin/sysversion ] ; then
-	    echo ${UNAME_MACHINE}-unknown-osf1mk
+	if test -x /usr/sbin/sysversion ; then
+	    GUESS=$UNAME_MACHINE-unknown-osf1mk
 	else
-	    echo ${UNAME_MACHINE}-unknown-osf1
+	    GUESS=$UNAME_MACHINE-unknown-osf1
 	fi
-	exit ;;
+	;;
     parisc*:Lites*:*:*)
-	echo hppa1.1-hp-lites
-	exit ;;
+	GUESS=hppa1.1-hp-lites
+	;;
     C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
-	echo c1-convex-bsd
-	exit ;;
+	GUESS=c1-convex-bsd
+	;;
     C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
 	if getsysinfo -f scalar_acc
 	then echo c32-convex-bsd
@@ -774,139 +836,145 @@ EOF
 	fi
 	exit ;;
     C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
-	echo c34-convex-bsd
-	exit ;;
+	GUESS=c34-convex-bsd
+	;;
     C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
-	echo c38-convex-bsd
-	exit ;;
+	GUESS=c38-convex-bsd
+	;;
     C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
-	echo c4-convex-bsd
-	exit ;;
+	GUESS=c4-convex-bsd
+	;;
     CRAY*Y-MP:*:*:*)
-	echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
-	exit ;;
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=ymp-cray-unicos$CRAY_REL
+	;;
     CRAY*[A-Z]90:*:*:*)
-	echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+	echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \
 	| sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
 	      -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
 	      -e 's/\.[^.]*$/.X/'
 	exit ;;
     CRAY*TS:*:*:*)
-	echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
-	exit ;;
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=t90-cray-unicos$CRAY_REL
+	;;
     CRAY*T3E:*:*:*)
-	echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
-	exit ;;
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=alphaev5-cray-unicosmk$CRAY_REL
+	;;
     CRAY*SV1:*:*:*)
-	echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
-	exit ;;
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=sv1-cray-unicos$CRAY_REL
+	;;
     *:UNICOS/mp:*:*)
-	echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
-	exit ;;
+	CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+	GUESS=craynv-cray-unicosmp$CRAY_REL
+	;;
     F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
-	FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
-	FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
-	FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
-	echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
-	exit ;;
+	FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+	FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+	FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'`
+	GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
+	;;
     5000:UNIX_System_V:4.*:*)
-	FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
-	FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
-	echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
-	exit ;;
+	FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+	FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'`
+	GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
+	;;
     i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
-	echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE
+	;;
     sparc*:BSD/OS:*:*)
-	echo sparc-unknown-bsdi${UNAME_RELEASE}
-	exit ;;
+	GUESS=sparc-unknown-bsdi$UNAME_RELEASE
+	;;
     *:BSD/OS:*:*)
-	echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE
+	;;
+    arm:FreeBSD:*:*)
+	UNAME_PROCESSOR=`uname -p`
+	set_cc_for_build
+	if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+	    | grep -q __ARM_PCS_VFP
+	then
+	    FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	    GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi
+	else
+	    FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	    GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf
+	fi
+	;;
     *:FreeBSD:*:*)
 	UNAME_PROCESSOR=`/usr/bin/uname -p`
-	case ${UNAME_PROCESSOR} in
+	case $UNAME_PROCESSOR in
 	    amd64)
-		echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
-	    *)
-		echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+		UNAME_PROCESSOR=x86_64 ;;
+	    i386)
+		UNAME_PROCESSOR=i586 ;;
 	esac
-	exit ;;
+	FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL
+	;;
     i*:CYGWIN*:*)
-	echo ${UNAME_MACHINE}-pc-cygwin
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-cygwin
+	;;
     *:MINGW64*:*)
-	echo ${UNAME_MACHINE}-pc-mingw64
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-mingw64
+	;;
     *:MINGW*:*)
-	echo ${UNAME_MACHINE}-pc-mingw32
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-mingw32
+	;;
     *:MSYS*:*)
-	echo ${UNAME_MACHINE}-pc-msys
-	exit ;;
-    i*:windows32*:*)
-	# uname -m includes "-pc" on this system.
-	echo ${UNAME_MACHINE}-mingw32
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-msys
+	;;
     i*:PW*:*)
-	echo ${UNAME_MACHINE}-pc-pw32
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-pw32
+	;;
     *:Interix*:*)
-	case ${UNAME_MACHINE} in
+	case $UNAME_MACHINE in
 	    x86)
-		echo i586-pc-interix${UNAME_RELEASE}
-		exit ;;
+		GUESS=i586-pc-interix$UNAME_RELEASE
+		;;
 	    authenticamd | genuineintel | EM64T)
-		echo x86_64-unknown-interix${UNAME_RELEASE}
-		exit ;;
+		GUESS=x86_64-unknown-interix$UNAME_RELEASE
+		;;
 	    IA64)
-		echo ia64-unknown-interix${UNAME_RELEASE}
-		exit ;;
+		GUESS=ia64-unknown-interix$UNAME_RELEASE
+		;;
 	esac ;;
-    [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
-	echo i${UNAME_MACHINE}-pc-mks
-	exit ;;
-    8664:Windows_NT:*)
-	echo x86_64-pc-mks
-	exit ;;
-    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
-	# How do we know it's Interix rather than the generic POSIX subsystem?
-	# It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
-	# UNAME_MACHINE based on the output of uname instead of i386?
-	echo i586-pc-interix
-	exit ;;
     i*:UWIN*:*)
-	echo ${UNAME_MACHINE}-pc-uwin
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-uwin
+	;;
     amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
-	echo x86_64-unknown-cygwin
-	exit ;;
-    p*:CYGWIN*:*)
-	echo powerpcle-unknown-cygwin
-	exit ;;
+	GUESS=x86_64-pc-cygwin
+	;;
     prep*:SunOS:5.*:*)
-	echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
-	exit ;;
+	SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+	GUESS=powerpcle-unknown-solaris2$SUN_REL
+	;;
     *:GNU:*:*)
 	# the GNU system
-	echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
-	exit ;;
+	GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'`
+	GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'`
+	GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL
+	;;
     *:GNU/*:*:*)
 	# other systems with GNU libc and userland
-	echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC}
-	exit ;;
-    i*86:Minix:*:*)
-	echo ${UNAME_MACHINE}-pc-minix
-	exit ;;
-    aarch64:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"`
+	GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC
+	;;
+    *:Minix:*:*)
+	GUESS=$UNAME_MACHINE-unknown-minix
+	;;
+    aarch64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     aarch64_be:Linux:*:*)
 	UNAME_MACHINE=aarch64_be
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     alpha:Linux:*:*)
-	case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+	case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in
 	  EV5)   UNAME_MACHINE=alphaev5 ;;
 	  EV56)  UNAME_MACHINE=alphaev56 ;;
 	  PCA56) UNAME_MACHINE=alphapca56 ;;
@@ -916,175 +984,226 @@ EOF
 	  EV68*) UNAME_MACHINE=alphaev68 ;;
 	esac
 	objdump --private-headers /bin/sh | grep -q ld.so.1
-	if test "$?" = 0 ; then LIBC="gnulibc1" ; fi
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
-    arc:Linux:*:* | arceb:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     arm*:Linux:*:*)
-	eval $set_cc_for_build
+	set_cc_for_build
 	if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
 	    | grep -q __ARM_EABI__
 	then
-	    echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	    GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
 	else
 	    if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
 		| grep -q __ARM_PCS_VFP
 	    then
-		echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi
+		GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi
 	    else
-		echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf
+		GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf
 	    fi
 	fi
-	exit ;;
+	;;
     avr32*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     cris:Linux:*:*)
-	echo ${UNAME_MACHINE}-axis-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-axis-linux-$LIBC
+	;;
     crisv32:Linux:*:*)
-	echo ${UNAME_MACHINE}-axis-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-axis-linux-$LIBC
+	;;
     e2k:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     frv:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     hexagon:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     i*86:Linux:*:*)
-	echo ${UNAME_MACHINE}-pc-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-linux-$LIBC
+	;;
     ia64:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    k1om:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
+    loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     m32r*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     m68*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     mips:Linux:*:* | mips64:Linux:*:*)
-	eval $set_cc_for_build
-	sed 's/^	//' << EOF >$dummy.c
+	set_cc_for_build
+	IS_GLIBC=0
+	test x"${LIBC}" = xgnu && IS_GLIBC=1
+	sed 's/^	//' << EOF > "$dummy.c"
 	#undef CPU
-	#undef ${UNAME_MACHINE}
-	#undef ${UNAME_MACHINE}el
+	#undef mips
+	#undef mipsel
+	#undef mips64
+	#undef mips64el
+	#if ${IS_GLIBC} && defined(_ABI64)
+	LIBCABI=gnuabi64
+	#else
+	#if ${IS_GLIBC} && defined(_ABIN32)
+	LIBCABI=gnuabin32
+	#else
+	LIBCABI=${LIBC}
+	#endif
+	#endif
+
+	#if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
+	CPU=mipsisa64r6
+	#else
+	#if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
+	CPU=mipsisa32r6
+	#else
+	#if defined(__mips64)
+	CPU=mips64
+	#else
+	CPU=mips
+	#endif
+	#endif
+	#endif
+
 	#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
-	CPU=${UNAME_MACHINE}el
+	MIPS_ENDIAN=el
 	#else
 	#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
-	CPU=${UNAME_MACHINE}
+	MIPS_ENDIAN=
 	#else
-	CPU=
+	MIPS_ENDIAN=
 	#endif
 	#endif
 EOF
-	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'`
-	test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; }
+	cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'`
+	eval "$cc_set_vars"
+	test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; }
+	;;
+    mips64el:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
 	;;
     openrisc*:Linux:*:*)
-	echo or1k-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=or1k-unknown-linux-$LIBC
+	;;
     or32:Linux:*:* | or1k*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     padre:Linux:*:*)
-	echo sparc-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=sparc-unknown-linux-$LIBC
+	;;
     parisc64:Linux:*:* | hppa64:Linux:*:*)
-	echo hppa64-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=hppa64-unknown-linux-$LIBC
+	;;
     parisc:Linux:*:* | hppa:Linux:*:*)
 	# Look for CPU level
 	case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
-	  PA7*) echo hppa1.1-unknown-linux-${LIBC} ;;
-	  PA8*) echo hppa2.0-unknown-linux-${LIBC} ;;
-	  *)    echo hppa-unknown-linux-${LIBC} ;;
+	  PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;;
+	  PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;;
+	  *)    GUESS=hppa-unknown-linux-$LIBC ;;
 	esac
-	exit ;;
+	;;
     ppc64:Linux:*:*)
-	echo powerpc64-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=powerpc64-unknown-linux-$LIBC
+	;;
     ppc:Linux:*:*)
-	echo powerpc-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=powerpc-unknown-linux-$LIBC
+	;;
     ppc64le:Linux:*:*)
-	echo powerpc64le-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=powerpc64le-unknown-linux-$LIBC
+	;;
     ppcle:Linux:*:*)
-	echo powerpcle-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=powerpcle-unknown-linux-$LIBC
+	;;
+    riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     s390:Linux:*:* | s390x:Linux:*:*)
-	echo ${UNAME_MACHINE}-ibm-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-ibm-linux-$LIBC
+	;;
     sh64*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     sh*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     sparc:Linux:*:* | sparc64:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     tile*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     vax:Linux:*:*)
-	echo ${UNAME_MACHINE}-dec-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-dec-linux-$LIBC
+	;;
     x86_64:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	set_cc_for_build
+	LIBCABI=$LIBC
+	if test "$CC_FOR_BUILD" != no_compiler_found; then
+	    if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \
+		(CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+		grep IS_X32 >/dev/null
+	    then
+		LIBCABI=${LIBC}x32
+	    fi
+	fi
+	GUESS=$UNAME_MACHINE-pc-linux-$LIBCABI
+	;;
     xtensa*:Linux:*:*)
-	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+	;;
     i*86:DYNIX/ptx:4*:*)
 	# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
 	# earlier versions are messed up and put the nodename in both
 	# sysname and nodename.
-	echo i386-sequent-sysv4
-	exit ;;
+	GUESS=i386-sequent-sysv4
+	;;
     i*86:UNIX_SV:4.2MP:2.*)
 	# Unixware is an offshoot of SVR4, but it has its own version
 	# number series starting with 2...
 	# I am not positive that other SVR4 systems won't match this,
 	# I just have to hope.  -- rms.
 	# Use sysv4.2uw... so that sysv4* matches it.
-	echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
+	;;
     i*86:OS/2:*:*)
 	# If we were able to find `uname', then EMX Unix compatibility
 	# is probably installed.
-	echo ${UNAME_MACHINE}-pc-os2-emx
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-os2-emx
+	;;
     i*86:XTS-300:*:STOP)
-	echo ${UNAME_MACHINE}-unknown-stop
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-stop
+	;;
     i*86:atheos:*:*)
-	echo ${UNAME_MACHINE}-unknown-atheos
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-atheos
+	;;
     i*86:syllable:*:*)
-	echo ${UNAME_MACHINE}-pc-syllable
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-syllable
+	;;
     i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
-	echo i386-unknown-lynxos${UNAME_RELEASE}
-	exit ;;
+	GUESS=i386-unknown-lynxos$UNAME_RELEASE
+	;;
     i*86:*DOS:*:*)
-	echo ${UNAME_MACHINE}-pc-msdosdjgpp
-	exit ;;
-    i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
-	UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+	GUESS=$UNAME_MACHINE-pc-msdosdjgpp
+	;;
+    i*86:*:4.*:*)
+	UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'`
 	if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
-		echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+		GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL
 	else
-		echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+		GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL
 	fi
-	exit ;;
+	;;
     i*86:*:5:[678]*)
 	# UnixWare 7.x, OpenUNIX and OpenServer 6.
 	case `/bin/uname -X | grep "^Machine"` in
@@ -1092,12 +1211,12 @@ EOF
 	    *Pentium)	     UNAME_MACHINE=i586 ;;
 	    *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
 	esac
-	echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+	;;
     i*86:*:3.2:*)
 	if test -f /usr/options/cb.name; then
 		UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
-		echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+		GUESS=$UNAME_MACHINE-pc-isc$UNAME_REL
 	elif /bin/uname -X 2>/dev/null >/dev/null ; then
 		UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
 		(/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
@@ -1107,43 +1226,43 @@ EOF
 			&& UNAME_MACHINE=i686
 		(/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
 			&& UNAME_MACHINE=i686
-		echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+		GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL
 	else
-		echo ${UNAME_MACHINE}-pc-sysv32
+		GUESS=$UNAME_MACHINE-pc-sysv32
 	fi
-	exit ;;
+	;;
     pc:*:*:*)
 	# Left here for compatibility:
 	# uname -m prints for DJGPP always 'pc', but it prints nothing about
 	# the processor, so we play safe by assuming i586.
 	# Note: whatever this is, it MUST be the same as what config.sub
-	# prints for the "djgpp" host, or else GDB configury will decide that
+	# prints for the "djgpp" host, or else GDB configure will decide that
 	# this is a cross-build.
-	echo i586-pc-msdosdjgpp
-	exit ;;
+	GUESS=i586-pc-msdosdjgpp
+	;;
     Intel:Mach:3*:*)
-	echo i386-pc-mach3
-	exit ;;
+	GUESS=i386-pc-mach3
+	;;
     paragon:*:*:*)
-	echo i860-intel-osf1
-	exit ;;
+	GUESS=i860-intel-osf1
+	;;
     i860:*:4.*:*) # i860-SVR4
 	if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
-	  echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+	  GUESS=i860-stardent-sysv$UNAME_RELEASE    # Stardent Vistra i860-SVR4
 	else # Add other i860-SVR4 vendors below as they are discovered.
-	  echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+	  GUESS=i860-unknown-sysv$UNAME_RELEASE     # Unknown i860-SVR4
 	fi
-	exit ;;
+	;;
     mini*:CTIX:SYS*5:*)
 	# "miniframe"
-	echo m68010-convergent-sysv
-	exit ;;
+	GUESS=m68010-convergent-sysv
+	;;
     mc68k:UNIX:SYSTEM5:3.51m)
-	echo m68k-convergent-sysv
-	exit ;;
+	GUESS=m68k-convergent-sysv
+	;;
     M680?0:D-NIX:5.3:*)
-	echo m68k-diab-dnix
-	exit ;;
+	GUESS=m68k-diab-dnix
+	;;
     M68*:*:R3V[5678]*:*)
 	test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
     3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
@@ -1151,9 +1270,9 @@ EOF
 	test -r /etc/.relid \
 	&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
 	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
-	  && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+	  && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
 	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
-	  && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+	  && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
     3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
 	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
 	  && { echo i486-ncr-sysv4; exit; } ;;
@@ -1162,248 +1281,438 @@ EOF
 	test -r /etc/.relid \
 	    && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
 	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
-	    && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+	    && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
 	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
-	    && { echo i586-ncr-sysv4.3${OS_REL}; exit; }
+	    && { echo i586-ncr-sysv4.3"$OS_REL"; exit; }
 	/bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
-	    && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+	    && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
     m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
-	echo m68k-unknown-lynxos${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-unknown-lynxos$UNAME_RELEASE
+	;;
     mc68030:UNIX_System_V:4.*:*)
-	echo m68k-atari-sysv4
-	exit ;;
+	GUESS=m68k-atari-sysv4
+	;;
     TSUNAMI:LynxOS:2.*:*)
-	echo sparc-unknown-lynxos${UNAME_RELEASE}
-	exit ;;
+	GUESS=sparc-unknown-lynxos$UNAME_RELEASE
+	;;
     rs6000:LynxOS:2.*:*)
-	echo rs6000-unknown-lynxos${UNAME_RELEASE}
-	exit ;;
+	GUESS=rs6000-unknown-lynxos$UNAME_RELEASE
+	;;
     PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
-	echo powerpc-unknown-lynxos${UNAME_RELEASE}
-	exit ;;
+	GUESS=powerpc-unknown-lynxos$UNAME_RELEASE
+	;;
     SM[BE]S:UNIX_SV:*:*)
-	echo mips-dde-sysv${UNAME_RELEASE}
-	exit ;;
+	GUESS=mips-dde-sysv$UNAME_RELEASE
+	;;
     RM*:ReliantUNIX-*:*:*)
-	echo mips-sni-sysv4
-	exit ;;
+	GUESS=mips-sni-sysv4
+	;;
     RM*:SINIX-*:*:*)
-	echo mips-sni-sysv4
-	exit ;;
+	GUESS=mips-sni-sysv4
+	;;
     *:SINIX-*:*:*)
 	if uname -p 2>/dev/null >/dev/null ; then
 		UNAME_MACHINE=`(uname -p) 2>/dev/null`
-		echo ${UNAME_MACHINE}-sni-sysv4
+		GUESS=$UNAME_MACHINE-sni-sysv4
 	else
-		echo ns32k-sni-sysv
+		GUESS=ns32k-sni-sysv
 	fi
-	exit ;;
+	;;
     PENTIUM:*:4.0*:*)	# Unisys `ClearPath HMP IX 4000' SVR4/MP effort
 			# says <Richard.M.Bartel@ccMail.Census.GOV>
-	echo i586-unisys-sysv4
-	exit ;;
+	GUESS=i586-unisys-sysv4
+	;;
     *:UNIX_System_V:4*:FTX*)
 	# From Gerald Hewes <hewes@openmarket.com>.
 	# How about differentiating between stratus architectures? -djm
-	echo hppa1.1-stratus-sysv4
-	exit ;;
+	GUESS=hppa1.1-stratus-sysv4
+	;;
     *:*:*:FTX*)
 	# From seanf@swdc.stratus.com.
-	echo i860-stratus-sysv4
-	exit ;;
+	GUESS=i860-stratus-sysv4
+	;;
     i*86:VOS:*:*)
 	# From Paul.Green@stratus.com.
-	echo ${UNAME_MACHINE}-stratus-vos
-	exit ;;
+	GUESS=$UNAME_MACHINE-stratus-vos
+	;;
     *:VOS:*:*)
 	# From Paul.Green@stratus.com.
-	echo hppa1.1-stratus-vos
-	exit ;;
+	GUESS=hppa1.1-stratus-vos
+	;;
     mc68*:A/UX:*:*)
-	echo m68k-apple-aux${UNAME_RELEASE}
-	exit ;;
+	GUESS=m68k-apple-aux$UNAME_RELEASE
+	;;
     news*:NEWS-OS:6*:*)
-	echo mips-sony-newsos6
-	exit ;;
+	GUESS=mips-sony-newsos6
+	;;
     R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
-	if [ -d /usr/nec ]; then
-		echo mips-nec-sysv${UNAME_RELEASE}
+	if test -d /usr/nec; then
+		GUESS=mips-nec-sysv$UNAME_RELEASE
 	else
-		echo mips-unknown-sysv${UNAME_RELEASE}
+		GUESS=mips-unknown-sysv$UNAME_RELEASE
 	fi
-	exit ;;
+	;;
     BeBox:BeOS:*:*)	# BeOS running on hardware made by Be, PPC only.
-	echo powerpc-be-beos
-	exit ;;
+	GUESS=powerpc-be-beos
+	;;
     BeMac:BeOS:*:*)	# BeOS running on Mac or Mac clone, PPC only.
-	echo powerpc-apple-beos
-	exit ;;
+	GUESS=powerpc-apple-beos
+	;;
     BePC:BeOS:*:*)	# BeOS running on Intel PC compatible.
-	echo i586-pc-beos
-	exit ;;
+	GUESS=i586-pc-beos
+	;;
     BePC:Haiku:*:*)	# Haiku running on Intel PC compatible.
-	echo i586-pc-haiku
-	exit ;;
+	GUESS=i586-pc-haiku
+	;;
     x86_64:Haiku:*:*)
-	echo x86_64-unknown-haiku
-	exit ;;
+	GUESS=x86_64-unknown-haiku
+	;;
     SX-4:SUPER-UX:*:*)
-	echo sx4-nec-superux${UNAME_RELEASE}
-	exit ;;
+	GUESS=sx4-nec-superux$UNAME_RELEASE
+	;;
     SX-5:SUPER-UX:*:*)
-	echo sx5-nec-superux${UNAME_RELEASE}
-	exit ;;
+	GUESS=sx5-nec-superux$UNAME_RELEASE
+	;;
     SX-6:SUPER-UX:*:*)
-	echo sx6-nec-superux${UNAME_RELEASE}
-	exit ;;
+	GUESS=sx6-nec-superux$UNAME_RELEASE
+	;;
     SX-7:SUPER-UX:*:*)
-	echo sx7-nec-superux${UNAME_RELEASE}
-	exit ;;
+	GUESS=sx7-nec-superux$UNAME_RELEASE
+	;;
     SX-8:SUPER-UX:*:*)
-	echo sx8-nec-superux${UNAME_RELEASE}
-	exit ;;
+	GUESS=sx8-nec-superux$UNAME_RELEASE
+	;;
     SX-8R:SUPER-UX:*:*)
-	echo sx8r-nec-superux${UNAME_RELEASE}
-	exit ;;
+	GUESS=sx8r-nec-superux$UNAME_RELEASE
+	;;
+    SX-ACE:SUPER-UX:*:*)
+	GUESS=sxace-nec-superux$UNAME_RELEASE
+	;;
     Power*:Rhapsody:*:*)
-	echo powerpc-apple-rhapsody${UNAME_RELEASE}
-	exit ;;
+	GUESS=powerpc-apple-rhapsody$UNAME_RELEASE
+	;;
     *:Rhapsody:*:*)
-	echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE
+	;;
+    arm64:Darwin:*:*)
+	GUESS=aarch64-apple-darwin$UNAME_RELEASE
+	;;
     *:Darwin:*:*)
-	UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
-	eval $set_cc_for_build
-	if test "$UNAME_PROCESSOR" = unknown ; then
-	    UNAME_PROCESSOR=powerpc
+	UNAME_PROCESSOR=`uname -p`
+	case $UNAME_PROCESSOR in
+	    unknown) UNAME_PROCESSOR=powerpc ;;
+	esac
+	if command -v xcode-select > /dev/null 2> /dev/null && \
+		! xcode-select --print-path > /dev/null 2> /dev/null ; then
+	    # Avoid executing cc if there is no toolchain installed as
+	    # cc will be a stub that puts up a graphical alert
+	    # prompting the user to install developer tools.
+	    CC_FOR_BUILD=no_compiler_found
+	else
+	    set_cc_for_build
 	fi
-	if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then
-	    if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
-		if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
-		    (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
-		    grep IS_64BIT_ARCH >/dev/null
-		then
-		    case $UNAME_PROCESSOR in
-			i386) UNAME_PROCESSOR=x86_64 ;;
-			powerpc) UNAME_PROCESSOR=powerpc64 ;;
-		    esac
-		fi
+	if test "$CC_FOR_BUILD" != no_compiler_found; then
+	    if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+		   (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+		   grep IS_64BIT_ARCH >/dev/null
+	    then
+		case $UNAME_PROCESSOR in
+		    i386) UNAME_PROCESSOR=x86_64 ;;
+		    powerpc) UNAME_PROCESSOR=powerpc64 ;;
+		esac
+	    fi
+	    # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
+	    if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
+		   (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+		   grep IS_PPC >/dev/null
+	    then
+		UNAME_PROCESSOR=powerpc
 	    fi
 	elif test "$UNAME_PROCESSOR" = i386 ; then
-	    # Avoid executing cc on OS X 10.9, as it ships with a stub
-	    # that puts up a graphical alert prompting to install
-	    # developer tools.  Any system running Mac OS X 10.7 or
-	    # later (Darwin 11 and later) is required to have a 64-bit
-	    # processor. This is not true of the ARM version of Darwin
-	    # that Apple uses in portable devices.
-	    UNAME_PROCESSOR=x86_64
+	    # uname -m returns i386 or x86_64
+	    UNAME_PROCESSOR=$UNAME_MACHINE
 	fi
-	echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE
+	;;
     *:procnto*:*:* | *:QNX:[0123456789]*:*)
 	UNAME_PROCESSOR=`uname -p`
-	if test "$UNAME_PROCESSOR" = "x86"; then
+	if test "$UNAME_PROCESSOR" = x86; then
 		UNAME_PROCESSOR=i386
 		UNAME_MACHINE=pc
 	fi
-	echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE
+	;;
     *:QNX:*:4*)
-	echo i386-pc-qnx
-	exit ;;
-    NEO-?:NONSTOP_KERNEL:*:*)
-	echo neo-tandem-nsk${UNAME_RELEASE}
-	exit ;;
+	GUESS=i386-pc-qnx
+	;;
+    NEO-*:NONSTOP_KERNEL:*:*)
+	GUESS=neo-tandem-nsk$UNAME_RELEASE
+	;;
     NSE-*:NONSTOP_KERNEL:*:*)
-	echo nse-tandem-nsk${UNAME_RELEASE}
-	exit ;;
-    NSR-?:NONSTOP_KERNEL:*:*)
-	echo nsr-tandem-nsk${UNAME_RELEASE}
-	exit ;;
+	GUESS=nse-tandem-nsk$UNAME_RELEASE
+	;;
+    NSR-*:NONSTOP_KERNEL:*:*)
+	GUESS=nsr-tandem-nsk$UNAME_RELEASE
+	;;
+    NSV-*:NONSTOP_KERNEL:*:*)
+	GUESS=nsv-tandem-nsk$UNAME_RELEASE
+	;;
+    NSX-*:NONSTOP_KERNEL:*:*)
+	GUESS=nsx-tandem-nsk$UNAME_RELEASE
+	;;
     *:NonStop-UX:*:*)
-	echo mips-compaq-nonstopux
-	exit ;;
+	GUESS=mips-compaq-nonstopux
+	;;
     BS2000:POSIX*:*:*)
-	echo bs2000-siemens-sysv
-	exit ;;
+	GUESS=bs2000-siemens-sysv
+	;;
     DS/*:UNIX_System_V:*:*)
-	echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
-	exit ;;
+	GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE
+	;;
     *:Plan9:*:*)
 	# "uname -m" is not consistent, so use $cputype instead. 386
 	# is converted to i386 for consistency with other x86
 	# operating systems.
-	if test "$cputype" = "386"; then
+	if test "${cputype-}" = 386; then
 	    UNAME_MACHINE=i386
-	else
-	    UNAME_MACHINE="$cputype"
+	elif test "x${cputype-}" != x; then
+	    UNAME_MACHINE=$cputype
 	fi
-	echo ${UNAME_MACHINE}-unknown-plan9
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-plan9
+	;;
     *:TOPS-10:*:*)
-	echo pdp10-unknown-tops10
-	exit ;;
+	GUESS=pdp10-unknown-tops10
+	;;
     *:TENEX:*:*)
-	echo pdp10-unknown-tenex
-	exit ;;
+	GUESS=pdp10-unknown-tenex
+	;;
     KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
-	echo pdp10-dec-tops20
-	exit ;;
+	GUESS=pdp10-dec-tops20
+	;;
     XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
-	echo pdp10-xkl-tops20
-	exit ;;
+	GUESS=pdp10-xkl-tops20
+	;;
     *:TOPS-20:*:*)
-	echo pdp10-unknown-tops20
-	exit ;;
+	GUESS=pdp10-unknown-tops20
+	;;
     *:ITS:*:*)
-	echo pdp10-unknown-its
-	exit ;;
+	GUESS=pdp10-unknown-its
+	;;
     SEI:*:*:SEIUX)
-	echo mips-sei-seiux${UNAME_RELEASE}
-	exit ;;
+	GUESS=mips-sei-seiux$UNAME_RELEASE
+	;;
     *:DragonFly:*:*)
-	echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
-	exit ;;
+	DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+	GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL
+	;;
     *:*VMS:*:*)
 	UNAME_MACHINE=`(uname -p) 2>/dev/null`
-	case "${UNAME_MACHINE}" in
-	    A*) echo alpha-dec-vms ; exit ;;
-	    I*) echo ia64-dec-vms ; exit ;;
-	    V*) echo vax-dec-vms ; exit ;;
+	case $UNAME_MACHINE in
+	    A*) GUESS=alpha-dec-vms ;;
+	    I*) GUESS=ia64-dec-vms ;;
+	    V*) GUESS=vax-dec-vms ;;
 	esac ;;
     *:XENIX:*:SysV)
-	echo i386-pc-xenix
-	exit ;;
+	GUESS=i386-pc-xenix
+	;;
     i*86:skyos:*:*)
-	echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//'
-	exit ;;
+	SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`
+	GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL
+	;;
     i*86:rdos:*:*)
-	echo ${UNAME_MACHINE}-pc-rdos
-	exit ;;
-    i*86:AROS:*:*)
-	echo ${UNAME_MACHINE}-pc-aros
-	exit ;;
+	GUESS=$UNAME_MACHINE-pc-rdos
+	;;
+    *:AROS:*:*)
+	GUESS=$UNAME_MACHINE-unknown-aros
+	;;
     x86_64:VMkernel:*:*)
-	echo ${UNAME_MACHINE}-unknown-esx
-	exit ;;
+	GUESS=$UNAME_MACHINE-unknown-esx
+	;;
+    amd64:Isilon\ OneFS:*:*)
+	GUESS=x86_64-unknown-onefs
+	;;
+    *:Unleashed:*:*)
+	GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
+	;;
+esac
+
+# Do we have a guess based on uname results?
+if test "x$GUESS" != x; then
+    echo "$GUESS"
+    exit
+fi
+
+# No uname command or uname output not recognized.
+set_cc_for_build
+cat > "$dummy.c" <<EOF
+#ifdef _SEQUENT_
+#include <sys/types.h>
+#include <sys/utsname.h>
+#endif
+#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
+#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
+#include <signal.h>
+#if defined(_SIZE_T_) || defined(SIGLOST)
+#include <sys/utsname.h>
+#endif
+#endif
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+  "4"
+#else
+  ""
+#endif
+  ); exit (0);
+#endif
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+  struct utsname un;
+
+  uname(&un);
+  if (strncmp(un.version, "V2", 2) == 0) {
+    printf ("i386-sequent-ptx2\n"); exit (0);
+  }
+  if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+    printf ("i386-sequent-ptx1\n"); exit (0);
+  }
+  printf ("i386-sequent-ptx\n"); exit (0);
+#endif
+
+#if defined (vax)
+#if !defined (ultrix)
+#include <sys/param.h>
+#if defined (BSD)
+#if BSD == 43
+  printf ("vax-dec-bsd4.3\n"); exit (0);
+#else
+#if BSD == 199006
+  printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#else
+  printf ("vax-dec-bsd\n"); exit (0);
+#endif
+#endif
+#else
+  printf ("vax-dec-bsd\n"); exit (0);
+#endif
+#else
+#if defined(_SIZE_T_) || defined(SIGLOST)
+  struct utsname un;
+  uname (&un);
+  printf ("vax-dec-ultrix%s\n", un.release); exit (0);
+#else
+  printf ("vax-dec-ultrix\n"); exit (0);
+#endif
+#endif
+#endif
+#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
+#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
+#if defined(_SIZE_T_) || defined(SIGLOST)
+  struct utsname *un;
+  uname (&un);
+  printf ("mips-dec-ultrix%s\n", un.release); exit (0);
+#else
+  printf ("mips-dec-ultrix\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` &&
+	{ echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; }
+
+echo "$0: unable to guess system type" >&2
+
+case $UNAME_MACHINE:$UNAME_SYSTEM in
+    mips:Linux | mips64:Linux)
+	# If we got here on MIPS GNU/Linux, output extra information.
+	cat >&2 <<EOF
+
+NOTE: MIPS GNU/Linux systems require a C compiler to fully recognize
+the system type. Please install a C compiler and try again.
+EOF
+	;;
 esac
 
 cat >&2 <<EOF
-$0: unable to guess system type
 
-This script, last modified $timestamp, has failed to recognize
-the operating system you are using. It is advised that you
-download the most up to date version of the config scripts from
+This script (version $timestamp), has failed to recognize the
+operating system you are using. If your script is old, overwrite *all*
+copies of config.guess and config.sub with the latest versions from:
 
-  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+  https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
 and
-  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+  https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
+EOF
 
-If the version you run ($0) is already up to date, please
-send the following data and any information you think might be
-pertinent to <config-patches@gnu.org> in order to provide the needed
-information to handle your system.
+our_year=`echo $timestamp | sed 's,-.*,,'`
+thisyear=`date +%Y`
+# shellcheck disable=SC2003
+script_age=`expr "$thisyear" - "$our_year"`
+if test "$script_age" -lt 3 ; then
+   cat >&2 <<EOF
+
+If $0 has already been updated, send the following data and any
+information you think might be pertinent to config-patches@gnu.org to
+provide the necessary information to handle your system.
 
 config.guess timestamp = $timestamp
 
@@ -1422,16 +1731,17 @@ hostinfo               = `(hostinfo) 2>/dev/null`
 /usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
 /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
 
-UNAME_MACHINE = ${UNAME_MACHINE}
-UNAME_RELEASE = ${UNAME_RELEASE}
-UNAME_SYSTEM  = ${UNAME_SYSTEM}
-UNAME_VERSION = ${UNAME_VERSION}
+UNAME_MACHINE = "$UNAME_MACHINE"
+UNAME_RELEASE = "$UNAME_RELEASE"
+UNAME_SYSTEM  = "$UNAME_SYSTEM"
+UNAME_VERSION = "$UNAME_VERSION"
 EOF
+fi
 
 exit 1
 
 # Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp)
 # time-stamp-start: "timestamp='"
 # time-stamp-format: "%:y-%02m-%02d"
 # time-stamp-end: "'"
diff --git a/autoconf/config.sub b/autoconf/config.sub
@@ -1,8 +1,10 @@
 #! /bin/sh
 # Configuration validation subroutine script.
-#   Copyright 1992-2015 Free Software Foundation, Inc.
+#   Copyright 1992-2021 Free Software Foundation, Inc.
 
-timestamp='2015-03-08'
+# shellcheck disable=SC2006,SC2268 # see below for rationale
+
+timestamp='2021-08-14'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -15,7 +17,7 @@ timestamp='2015-03-08'
 # General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
 #
 # As a special exception to the GNU General Public License, if you
 # distribute this file as part of a program that contains a
@@ -33,7 +35,7 @@ timestamp='2015-03-08'
 # Otherwise, we print the canonical config type on stdout and succeed.
 
 # You can get the latest version of this script from:
-# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
 
 # This file is supposed to be the same for all GNU packages
 # and recognize all the CPU types, system types and aliases
@@ -50,15 +52,21 @@ timestamp='2015-03-08'
 #	CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
 # It is wrong to echo any other type of specification.
 
+# The "shellcheck disable" line above the timestamp inhibits complaints
+# about features and limitations of the classic Bourne shell that were
+# superseded or lifted in POSIX.  However, this script identifies a wide
+# variety of pre-POSIX systems that do not have POSIX shells at all, and
+# even some reasonably current systems (Solaris 10 as case-in-point) still
+# have a pre-POSIX /bin/sh.
+
 me=`echo "$0" | sed -e 's,.*/,,'`
 
 usage="\
-Usage: $0 [OPTION] CPU-MFR-OPSYS
-       $0 [OPTION] ALIAS
+Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
 
 Canonicalize a configuration name.
 
-Operation modes:
+Options:
   -h, --help         print this help, then exit
   -t, --time-stamp   print date of last modification, then exit
   -v, --version      print version number, then exit
@@ -68,7 +76,7 @@ Report bugs and patches to <config-patches@gnu.org>."
 version="\
 GNU config.sub ($timestamp)
 
-Copyright 1992-2015 Free Software Foundation, Inc.
+Copyright 1992-2021 Free Software Foundation, Inc.
 
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -90,12 +98,12 @@ while test $# -gt 0 ; do
     - )	# Use stdin as input.
        break ;;
     -* )
-       echo "$me: invalid option $1$help"
+       echo "$me: invalid option $1$help" >&2
        exit 1 ;;
 
     *local*)
        # First pass through any local machine types.
-       echo $1
+       echo "$1"
        exit ;;
 
     * )
@@ -111,1231 +119,1181 @@ case $# in
     exit 1;;
 esac
 
-# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
-# Here we must recognize all the valid KERNEL-OS combinations.
-maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
-case $maybe_os in
-  nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \
-  linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \
-  knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \
-  kopensolaris*-gnu* | \
-  storm-chaos* | os2-emx* | rtmk-nova*)
-    os=-$maybe_os
-    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
-    ;;
-  android-linux)
-    os=-linux-android
-    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown
-    ;;
-  *)
-    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
-    if [ $basic_machine != $1 ]
-    then os=`echo $1 | sed 's/.*-/-/'`
-    else os=; fi
-    ;;
-esac
+# Split fields of configuration type
+# shellcheck disable=SC2162
+saved_IFS=$IFS
+IFS="-" read field1 field2 field3 field4 <<EOF
+$1
+EOF
+IFS=$saved_IFS
 
-### Let's recognize common machines as not being operating systems so
-### that things like config.sub decstation-3100 work.  We also
-### recognize some manufacturers as not being operating systems, so we
-### can provide default operating systems below.
-case $os in
-	-sun*os*)
-		# Prevent following clause from handling this invalid input.
-		;;
-	-dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
-	-att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
-	-unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
-	-convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
-	-c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
-	-harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
-	-apple | -axis | -knuth | -cray | -microblaze*)
-		os=
-		basic_machine=$1
-		;;
-	-bluegene*)
-		os=-cnk
-		;;
-	-sim | -cisco | -oki | -wec | -winbond)
-		os=
-		basic_machine=$1
-		;;
-	-scout)
-		;;
-	-wrs)
-		os=-vxworks
-		basic_machine=$1
-		;;
-	-chorusos*)
-		os=-chorusos
-		basic_machine=$1
-		;;
-	-chorusrdb)
-		os=-chorusrdb
-		basic_machine=$1
-		;;
-	-hiux*)
-		os=-hiuxwe2
-		;;
-	-sco6)
-		os=-sco5v6
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-sco5)
-		os=-sco3.2v5
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-sco4)
-		os=-sco3.2v4
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-sco3.2.[4-9]*)
-		os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-sco3.2v[4-9]*)
-		# Don't forget version if it is 3.2v4 or newer.
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-sco5v6*)
-		# Don't forget version if it is 3.2v4 or newer.
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-sco*)
-		os=-sco3.2v2
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-udk*)
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-isc)
-		os=-isc2.2
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-clix*)
-		basic_machine=clipper-intergraph
-		;;
-	-isc*)
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
-		;;
-	-lynx*178)
-		os=-lynxos178
-		;;
-	-lynx*5)
-		os=-lynxos5
-		;;
-	-lynx*)
-		os=-lynxos
+# Separate into logical components for further validation
+case $1 in
+	*-*-*-*-*)
+		echo Invalid configuration \`"$1"\': more than four components >&2
+		exit 1
 		;;
-	-ptx*)
-		basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+	*-*-*-*)
+		basic_machine=$field1-$field2
+		basic_os=$field3-$field4
 		;;
-	-windowsnt*)
-		os=`echo $os | sed -e 's/windowsnt/winnt/'`
+	*-*-*)
+		# Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
+		# parts
+		maybe_os=$field2-$field3
+		case $maybe_os in
+			nto-qnx* | linux-* | uclinux-uclibc* \
+			| uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
+			| netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
+			| storm-chaos* | os2-emx* | rtmk-nova*)
+				basic_machine=$field1
+				basic_os=$maybe_os
+				;;
+			android-linux)
+				basic_machine=$field1-unknown
+				basic_os=linux-android
+				;;
+			*)
+				basic_machine=$field1-$field2
+				basic_os=$field3
+				;;
+		esac
 		;;
-	-psos*)
-		os=-psos
+	*-*)
+		# A lone config we happen to match not fitting any pattern
+		case $field1-$field2 in
+			decstation-3100)
+				basic_machine=mips-dec
+				basic_os=
+				;;
+			*-*)
+				# Second component is usually, but not always the OS
+				case $field2 in
+					# Prevent following clause from handling this valid os
+					sun*os*)
+						basic_machine=$field1
+						basic_os=$field2
+						;;
+					zephyr*)
+						basic_machine=$field1-unknown
+						basic_os=$field2
+						;;
+					# Manufacturers
+					dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
+					| att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
+					| unicom* | ibm* | next | hp | isi* | apollo | altos* \
+					| convergent* | ncr* | news | 32* | 3600* | 3100* \
+					| hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
+					| ultra | tti* | harris | dolphin | highlevel | gould \
+					| cbm | ns | masscomp | apple | axis | knuth | cray \
+					| microblaze* | sim | cisco \
+					| oki | wec | wrs | winbond)
+						basic_machine=$field1-$field2
+						basic_os=
+						;;
+					*)
+						basic_machine=$field1
+						basic_os=$field2
+						;;
+				esac
+			;;
+		esac
 		;;
-	-mint | -mint[0-9]*)
-		basic_machine=m68k-atari
-		os=-mint
+	*)
+		# Convert single-component short-hands not valid as part of
+		# multi-component configurations.
+		case $field1 in
+			386bsd)
+				basic_machine=i386-pc
+				basic_os=bsd
+				;;
+			a29khif)
+				basic_machine=a29k-amd
+				basic_os=udi
+				;;
+			adobe68k)
+				basic_machine=m68010-adobe
+				basic_os=scout
+				;;
+			alliant)
+				basic_machine=fx80-alliant
+				basic_os=
+				;;
+			altos | altos3068)
+				basic_machine=m68k-altos
+				basic_os=
+				;;
+			am29k)
+				basic_machine=a29k-none
+				basic_os=bsd
+				;;
+			amdahl)
+				basic_machine=580-amdahl
+				basic_os=sysv
+				;;
+			amiga)
+				basic_machine=m68k-unknown
+				basic_os=
+				;;
+			amigaos | amigados)
+				basic_machine=m68k-unknown
+				basic_os=amigaos
+				;;
+			amigaunix | amix)
+				basic_machine=m68k-unknown
+				basic_os=sysv4
+				;;
+			apollo68)
+				basic_machine=m68k-apollo
+				basic_os=sysv
+				;;
+			apollo68bsd)
+				basic_machine=m68k-apollo
+				basic_os=bsd
+				;;
+			aros)
+				basic_machine=i386-pc
+				basic_os=aros
+				;;
+			aux)
+				basic_machine=m68k-apple
+				basic_os=aux
+				;;
+			balance)
+				basic_machine=ns32k-sequent
+				basic_os=dynix
+				;;
+			blackfin)
+				basic_machine=bfin-unknown
+				basic_os=linux
+				;;
+			cegcc)
+				basic_machine=arm-unknown
+				basic_os=cegcc
+				;;
+			convex-c1)
+				basic_machine=c1-convex
+				basic_os=bsd
+				;;
+			convex-c2)
+				basic_machine=c2-convex
+				basic_os=bsd
+				;;
+			convex-c32)
+				basic_machine=c32-convex
+				basic_os=bsd
+				;;
+			convex-c34)
+				basic_machine=c34-convex
+				basic_os=bsd
+				;;
+			convex-c38)
+				basic_machine=c38-convex
+				basic_os=bsd
+				;;
+			cray)
+				basic_machine=j90-cray
+				basic_os=unicos
+				;;
+			crds | unos)
+				basic_machine=m68k-crds
+				basic_os=
+				;;
+			da30)
+				basic_machine=m68k-da30
+				basic_os=
+				;;
+			decstation | pmax | pmin | dec3100 | decstatn)
+				basic_machine=mips-dec
+				basic_os=
+				;;
+			delta88)
+				basic_machine=m88k-motorola
+				basic_os=sysv3
+				;;
+			dicos)
+				basic_machine=i686-pc
+				basic_os=dicos
+				;;
+			djgpp)
+				basic_machine=i586-pc
+				basic_os=msdosdjgpp
+				;;
+			ebmon29k)
+				basic_machine=a29k-amd
+				basic_os=ebmon
+				;;
+			es1800 | OSE68k | ose68k | ose | OSE)
+				basic_machine=m68k-ericsson
+				basic_os=ose
+				;;
+			gmicro)
+				basic_machine=tron-gmicro
+				basic_os=sysv
+				;;
+			go32)
+				basic_machine=i386-pc
+				basic_os=go32
+				;;
+			h8300hms)
+				basic_machine=h8300-hitachi
+				basic_os=hms
+				;;
+			h8300xray)
+				basic_machine=h8300-hitachi
+				basic_os=xray
+				;;
+			h8500hms)
+				basic_machine=h8500-hitachi
+				basic_os=hms
+				;;
+			harris)
+				basic_machine=m88k-harris
+				basic_os=sysv3
+				;;
+			hp300 | hp300hpux)
+				basic_machine=m68k-hp
+				basic_os=hpux
+				;;
+			hp300bsd)
+				basic_machine=m68k-hp
+				basic_os=bsd
+				;;
+			hppaosf)
+				basic_machine=hppa1.1-hp
+				basic_os=osf
+				;;
+			hppro)
+				basic_machine=hppa1.1-hp
+				basic_os=proelf
+				;;
+			i386mach)
+				basic_machine=i386-mach
+				basic_os=mach
+				;;
+			isi68 | isi)
+				basic_machine=m68k-isi
+				basic_os=sysv
+				;;
+			m68knommu)
+				basic_machine=m68k-unknown
+				basic_os=linux
+				;;
+			magnum | m3230)
+				basic_machine=mips-mips
+				basic_os=sysv
+				;;
+			merlin)
+				basic_machine=ns32k-utek
+				basic_os=sysv
+				;;
+			mingw64)
+				basic_machine=x86_64-pc
+				basic_os=mingw64
+				;;
+			mingw32)
+				basic_machine=i686-pc
+				basic_os=mingw32
+				;;
+			mingw32ce)
+				basic_machine=arm-unknown
+				basic_os=mingw32ce
+				;;
+			monitor)
+				basic_machine=m68k-rom68k
+				basic_os=coff
+				;;
+			morphos)
+				basic_machine=powerpc-unknown
+				basic_os=morphos
+				;;
+			moxiebox)
+				basic_machine=moxie-unknown
+				basic_os=moxiebox
+				;;
+			msdos)
+				basic_machine=i386-pc
+				basic_os=msdos
+				;;
+			msys)
+				basic_machine=i686-pc
+				basic_os=msys
+				;;
+			mvs)
+				basic_machine=i370-ibm
+				basic_os=mvs
+				;;
+			nacl)
+				basic_machine=le32-unknown
+				basic_os=nacl
+				;;
+			ncr3000)
+				basic_machine=i486-ncr
+				basic_os=sysv4
+				;;
+			netbsd386)
+				basic_machine=i386-pc
+				basic_os=netbsd
+				;;
+			netwinder)
+				basic_machine=armv4l-rebel
+				basic_os=linux
+				;;
+			news | news700 | news800 | news900)
+				basic_machine=m68k-sony
+				basic_os=newsos
+				;;
+			news1000)
+				basic_machine=m68030-sony
+				basic_os=newsos
+				;;
+			necv70)
+				basic_machine=v70-nec
+				basic_os=sysv
+				;;
+			nh3000)
+				basic_machine=m68k-harris
+				basic_os=cxux
+				;;
+			nh[45]000)
+				basic_machine=m88k-harris
+				basic_os=cxux
+				;;
+			nindy960)
+				basic_machine=i960-intel
+				basic_os=nindy
+				;;
+			mon960)
+				basic_machine=i960-intel
+				basic_os=mon960
+				;;
+			nonstopux)
+				basic_machine=mips-compaq
+				basic_os=nonstopux
+				;;
+			os400)
+				basic_machine=powerpc-ibm
+				basic_os=os400
+				;;
+			OSE68000 | ose68000)
+				basic_machine=m68000-ericsson
+				basic_os=ose
+				;;
+			os68k)
+				basic_machine=m68k-none
+				basic_os=os68k
+				;;
+			paragon)
+				basic_machine=i860-intel
+				basic_os=osf
+				;;
+			parisc)
+				basic_machine=hppa-unknown
+				basic_os=linux
+				;;
+			psp)
+				basic_machine=mipsallegrexel-sony
+				basic_os=psp
+				;;
+			pw32)
+				basic_machine=i586-unknown
+				basic_os=pw32
+				;;
+			rdos | rdos64)
+				basic_machine=x86_64-pc
+				basic_os=rdos
+				;;
+			rdos32)
+				basic_machine=i386-pc
+				basic_os=rdos
+				;;
+			rom68k)
+				basic_machine=m68k-rom68k
+				basic_os=coff
+				;;
+			sa29200)
+				basic_machine=a29k-amd
+				basic_os=udi
+				;;
+			sei)
+				basic_machine=mips-sei
+				basic_os=seiux
+				;;
+			sequent)
+				basic_machine=i386-sequent
+				basic_os=
+				;;
+			sps7)
+				basic_machine=m68k-bull
+				basic_os=sysv2
+				;;
+			st2000)
+				basic_machine=m68k-tandem
+				basic_os=
+				;;
+			stratus)
+				basic_machine=i860-stratus
+				basic_os=sysv4
+				;;
+			sun2)
+				basic_machine=m68000-sun
+				basic_os=
+				;;
+			sun2os3)
+				basic_machine=m68000-sun
+				basic_os=sunos3
+				;;
+			sun2os4)
+				basic_machine=m68000-sun
+				basic_os=sunos4
+				;;
+			sun3)
+				basic_machine=m68k-sun
+				basic_os=
+				;;
+			sun3os3)
+				basic_machine=m68k-sun
+				basic_os=sunos3
+				;;
+			sun3os4)
+				basic_machine=m68k-sun
+				basic_os=sunos4
+				;;
+			sun4)
+				basic_machine=sparc-sun
+				basic_os=
+				;;
+			sun4os3)
+				basic_machine=sparc-sun
+				basic_os=sunos3
+				;;
+			sun4os4)
+				basic_machine=sparc-sun
+				basic_os=sunos4
+				;;
+			sun4sol2)
+				basic_machine=sparc-sun
+				basic_os=solaris2
+				;;
+			sun386 | sun386i | roadrunner)
+				basic_machine=i386-sun
+				basic_os=
+				;;
+			sv1)
+				basic_machine=sv1-cray
+				basic_os=unicos
+				;;
+			symmetry)
+				basic_machine=i386-sequent
+				basic_os=dynix
+				;;
+			t3e)
+				basic_machine=alphaev5-cray
+				basic_os=unicos
+				;;
+			t90)
+				basic_machine=t90-cray
+				basic_os=unicos
+				;;
+			toad1)
+				basic_machine=pdp10-xkl
+				basic_os=tops20
+				;;
+			tpf)
+				basic_machine=s390x-ibm
+				basic_os=tpf
+				;;
+			udi29k)
+				basic_machine=a29k-amd
+				basic_os=udi
+				;;
+			ultra3)
+				basic_machine=a29k-nyu
+				basic_os=sym1
+				;;
+			v810 | necv810)
+				basic_machine=v810-nec
+				basic_os=none
+				;;
+			vaxv)
+				basic_machine=vax-dec
+				basic_os=sysv
+				;;
+			vms)
+				basic_machine=vax-dec
+				basic_os=vms
+				;;
+			vsta)
+				basic_machine=i386-pc
+				basic_os=vsta
+				;;
+			vxworks960)
+				basic_machine=i960-wrs
+				basic_os=vxworks
+				;;
+			vxworks68)
+				basic_machine=m68k-wrs
+				basic_os=vxworks
+				;;
+			vxworks29k)
+				basic_machine=a29k-wrs
+				basic_os=vxworks
+				;;
+			xbox)
+				basic_machine=i686-pc
+				basic_os=mingw32
+				;;
+			ymp)
+				basic_machine=ymp-cray
+				basic_os=unicos
+				;;
+			*)
+				basic_machine=$1
+				basic_os=
+				;;
+		esac
 		;;
 esac
 
-# Decode aliases for certain CPU-COMPANY combinations.
+# Decode 1-component or ad-hoc basic machines
 case $basic_machine in
-	# Recognize the basic CPU types without company name.
-	# Some are omitted here because they have special meanings below.
-	1750a | 580 \
-	| a29k \
-	| aarch64 | aarch64_be \
-	| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
-	| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
-	| am33_2.0 \
-	| arc | arceb \
-	| arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \
-	| avr | avr32 \
-	| be32 | be64 \
-	| bfin \
-	| c4x | c8051 | clipper \
-	| d10v | d30v | dlx | dsp16xx \
-	| e2k | epiphany \
-	| fido | fr30 | frv | ft32 \
-	| h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
-	| hexagon \
-	| i370 | i860 | i960 | ia64 \
-	| ip2k | iq2000 \
-	| k1om \
-	| le32 | le64 \
-	| lm32 \
-	| m32c | m32r | m32rle | m68000 | m68k | m88k \
-	| maxq | mb | microblaze | microblazeel | mcore | mep | metag \
-	| mips | mipsbe | mipseb | mipsel | mipsle \
-	| mips16 \
-	| mips64 | mips64el \
-	| mips64octeon | mips64octeonel \
-	| mips64orion | mips64orionel \
-	| mips64r5900 | mips64r5900el \
-	| mips64vr | mips64vrel \
-	| mips64vr4100 | mips64vr4100el \
-	| mips64vr4300 | mips64vr4300el \
-	| mips64vr5000 | mips64vr5000el \
-	| mips64vr5900 | mips64vr5900el \
-	| mipsisa32 | mipsisa32el \
-	| mipsisa32r2 | mipsisa32r2el \
-	| mipsisa32r6 | mipsisa32r6el \
-	| mipsisa64 | mipsisa64el \
-	| mipsisa64r2 | mipsisa64r2el \
-	| mipsisa64r6 | mipsisa64r6el \
-	| mipsisa64sb1 | mipsisa64sb1el \
-	| mipsisa64sr71k | mipsisa64sr71kel \
-	| mipsr5900 | mipsr5900el \
-	| mipstx39 | mipstx39el \
-	| mn10200 | mn10300 \
-	| moxie \
-	| mt \
-	| msp430 \
-	| nds32 | nds32le | nds32be \
-	| nios | nios2 | nios2eb | nios2el \
-	| ns16k | ns32k \
-	| open8 | or1k | or1knd | or32 \
-	| pdp10 | pdp11 | pj | pjl \
-	| powerpc | powerpc64 | powerpc64le | powerpcle \
-	| pyramid \
-	| riscv32 | riscv64 \
-	| rl78 | rx \
-	| score \
-	| sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
-	| sh64 | sh64le \
-	| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
-	| sparcv8 | sparcv9 | sparcv9b | sparcv9v \
-	| spu \
-	| tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \
-	| ubicom32 \
-	| v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \
-	| visium \
-	| we32k \
-	| x86 | xc16x | xstormy16 | xtensa \
-	| z8k | z80)
-		basic_machine=$basic_machine-unknown
-		;;
-	c54x)
-		basic_machine=tic54x-unknown
-		;;
-	c55x)
-		basic_machine=tic55x-unknown
-		;;
-	c6x)
-		basic_machine=tic6x-unknown
-		;;
-	leon|leon[3-9])
-		basic_machine=sparc-$basic_machine
+	# Here we handle the default manufacturer of certain CPU types.  It is in
+	# some cases the only manufacturer, in others, it is the most popular.
+	w89k)
+		cpu=hppa1.1
+		vendor=winbond
 		;;
-	m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip)
-		basic_machine=$basic_machine-unknown
-		os=-none
+	op50n)
+		cpu=hppa1.1
+		vendor=oki
 		;;
-	m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+	op60c)
+		cpu=hppa1.1
+		vendor=oki
 		;;
-	ms1)
-		basic_machine=mt-unknown
+	ibm*)
+		cpu=i370
+		vendor=ibm
 		;;
-
-	strongarm | thumb | xscale)
-		basic_machine=arm-unknown
-		;;
-	xgate)
-		basic_machine=$basic_machine-unknown
-		os=-none
+	orion105)
+		cpu=clipper
+		vendor=highlevel
 		;;
-	xscaleeb)
-		basic_machine=armeb-unknown
+	mac | mpw | mac-mpw)
+		cpu=m68k
+		vendor=apple
 		;;
-
-	xscaleel)
-		basic_machine=armel-unknown
+	pmac | pmac-mpw)
+		cpu=powerpc
+		vendor=apple
 		;;
 
-	# We use `pc' rather than `unknown'
-	# because (1) that's what they normally are, and
-	# (2) the word "unknown" tends to confuse beginning users.
-	i*86 | x86_64)
-	  basic_machine=$basic_machine-pc
-	  ;;
-	# Object if more than one company name word.
-	*-*-*)
-		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
-		exit 1
-		;;
-	# Recognize the basic CPU types with company name.
-	580-* \
-	| a29k-* \
-	| aarch64-* | aarch64_be-* \
-	| alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
-	| alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
-	| alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \
-	| arm-*  | armbe-* | armle-* | armeb-* | armv*-* \
-	| avr-* | avr32-* \
-	| be32-* | be64-* \
-	| bfin-* | bs2000-* \
-	| c[123]* | c30-* | [cjt]90-* | c4x-* \
-	| c8051-* | clipper-* | craynv-* | cydra-* \
-	| d10v-* | d30v-* | dlx-* \
-	| e2k-* | elxsi-* \
-	| f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
-	| h8300-* | h8500-* \
-	| hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
-	| hexagon-* \
-	| i*86-* | i860-* | i960-* | ia64-* \
-	| ip2k-* | iq2000-* \
-	| k1om-* \
-	| le32-* | le64-* \
-	| lm32-* \
-	| m32c-* | m32r-* | m32rle-* \
-	| m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
-	| m88110-* | m88k-* | maxq-* | mcore-* | metag-* \
-	| microblaze-* | microblazeel-* \
-	| mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
-	| mips16-* \
-	| mips64-* | mips64el-* \
-	| mips64octeon-* | mips64octeonel-* \
-	| mips64orion-* | mips64orionel-* \
-	| mips64r5900-* | mips64r5900el-* \
-	| mips64vr-* | mips64vrel-* \
-	| mips64vr4100-* | mips64vr4100el-* \
-	| mips64vr4300-* | mips64vr4300el-* \
-	| mips64vr5000-* | mips64vr5000el-* \
-	| mips64vr5900-* | mips64vr5900el-* \
-	| mipsisa32-* | mipsisa32el-* \
-	| mipsisa32r2-* | mipsisa32r2el-* \
-	| mipsisa32r6-* | mipsisa32r6el-* \
-	| mipsisa64-* | mipsisa64el-* \
-	| mipsisa64r2-* | mipsisa64r2el-* \
-	| mipsisa64r6-* | mipsisa64r6el-* \
-	| mipsisa64sb1-* | mipsisa64sb1el-* \
-	| mipsisa64sr71k-* | mipsisa64sr71kel-* \
-	| mipsr5900-* | mipsr5900el-* \
-	| mipstx39-* | mipstx39el-* \
-	| mmix-* \
-	| mt-* \
-	| msp430-* \
-	| nds32-* | nds32le-* | nds32be-* \
-	| nios-* | nios2-* | nios2eb-* | nios2el-* \
-	| none-* | np1-* | ns16k-* | ns32k-* \
-	| open8-* \
-	| or1k*-* \
-	| orion-* \
-	| pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
-	| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \
-	| pyramid-* \
-	| rl78-* | romp-* | rs6000-* | rx-* \
-	| sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
-	| shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
-	| sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
-	| sparclite-* \
-	| sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \
-	| tahoe-* \
-	| tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
-	| tile*-* \
-	| tron-* \
-	| ubicom32-* \
-	| v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \
-	| vax-* \
-	| visium-* \
-	| we32k-* \
-	| x86-* | x86_64-* | xc16x-* | xps100-* \
-	| xstormy16-* | xtensa*-* \
-	| ymp-* \
-	| z8k-* | z80-*)
-		;;
-	# Recognize the basic CPU types without company name, with glob match.
-	xtensa*)
-		basic_machine=$basic_machine-unknown
-		;;
 	# Recognize the various machine names and aliases which stand
 	# for a CPU type and a company and sometimes even an OS.
-	386bsd)
-		basic_machine=i386-unknown
-		os=-bsd
-		;;
 	3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
-		basic_machine=m68000-att
+		cpu=m68000
+		vendor=att
 		;;
 	3b*)
-		basic_machine=we32k-att
-		;;
-	a29khif)
-		basic_machine=a29k-amd
-		os=-udi
-		;;
-	abacus)
-		basic_machine=abacus-unknown
-		;;
-	adobe68k)
-		basic_machine=m68010-adobe
-		os=-scout
-		;;
-	alliant | fx80)
-		basic_machine=fx80-alliant
-		;;
-	altos | altos3068)
-		basic_machine=m68k-altos
-		;;
-	am29k)
-		basic_machine=a29k-none
-		os=-bsd
-		;;
-	amd64)
-		basic_machine=x86_64-pc
-		;;
-	amd64-*)
-		basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	amdahl)
-		basic_machine=580-amdahl
-		os=-sysv
-		;;
-	amiga | amiga-*)
-		basic_machine=m68k-unknown
-		;;
-	amigaos | amigados)
-		basic_machine=m68k-unknown
-		os=-amigaos
-		;;
-	amigaunix | amix)
-		basic_machine=m68k-unknown
-		os=-sysv4
-		;;
-	apollo68)
-		basic_machine=m68k-apollo
-		os=-sysv
-		;;
-	apollo68bsd)
-		basic_machine=m68k-apollo
-		os=-bsd
-		;;
-	aros)
-		basic_machine=i386-pc
-		os=-aros
-		;;
-        asmjs)
-		basic_machine=asmjs-unknown
-		;;
-	aux)
-		basic_machine=m68k-apple
-		os=-aux
-		;;
-	balance)
-		basic_machine=ns32k-sequent
-		os=-dynix
-		;;
-	blackfin)
-		basic_machine=bfin-unknown
-		os=-linux
-		;;
-	blackfin-*)
-		basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
-		os=-linux
+		cpu=we32k
+		vendor=att
 		;;
 	bluegene*)
-		basic_machine=powerpc-ibm
-		os=-cnk
-		;;
-	c54x-*)
-		basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	c55x-*)
-		basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	c6x-*)
-		basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	c90)
-		basic_machine=c90-cray
-		os=-unicos
-		;;
-	cegcc)
-		basic_machine=arm-unknown
-		os=-cegcc
-		;;
-	convex-c1)
-		basic_machine=c1-convex
-		os=-bsd
-		;;
-	convex-c2)
-		basic_machine=c2-convex
-		os=-bsd
-		;;
-	convex-c32)
-		basic_machine=c32-convex
-		os=-bsd
-		;;
-	convex-c34)
-		basic_machine=c34-convex
-		os=-bsd
-		;;
-	convex-c38)
-		basic_machine=c38-convex
-		os=-bsd
-		;;
-	cray | j90)
-		basic_machine=j90-cray
-		os=-unicos
-		;;
-	craynv)
-		basic_machine=craynv-cray
-		os=-unicosmp
-		;;
-	cr16 | cr16-*)
-		basic_machine=cr16-unknown
-		os=-elf
-		;;
-	crds | unos)
-		basic_machine=m68k-crds
-		;;
-	crisv32 | crisv32-* | etraxfs*)
-		basic_machine=crisv32-axis
-		;;
-	cris | cris-* | etrax*)
-		basic_machine=cris-axis
-		;;
-	crx)
-		basic_machine=crx-unknown
-		os=-elf
-		;;
-	da30 | da30-*)
-		basic_machine=m68k-da30
-		;;
-	decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
-		basic_machine=mips-dec
+		cpu=powerpc
+		vendor=ibm
+		basic_os=cnk
 		;;
 	decsystem10* | dec10*)
-		basic_machine=pdp10-dec
-		os=-tops10
+		cpu=pdp10
+		vendor=dec
+		basic_os=tops10
 		;;
 	decsystem20* | dec20*)
-		basic_machine=pdp10-dec
-		os=-tops20
+		cpu=pdp10
+		vendor=dec
+		basic_os=tops20
 		;;
 	delta | 3300 | motorola-3300 | motorola-delta \
 	      | 3300-motorola | delta-motorola)
-		basic_machine=m68k-motorola
+		cpu=m68k
+		vendor=motorola
 		;;
-	delta88)
-		basic_machine=m88k-motorola
-		os=-sysv3
-		;;
-	dicos)
-		basic_machine=i686-pc
-		os=-dicos
-		;;
-	djgpp)
-		basic_machine=i586-pc
-		os=-msdosdjgpp
-		;;
-	dpx20 | dpx20-*)
-		basic_machine=rs6000-bull
-		os=-bosx
-		;;
-	dpx2* | dpx2*-bull)
-		basic_machine=m68k-bull
-		os=-sysv3
-		;;
-	ebmon29k)
-		basic_machine=a29k-amd
-		os=-ebmon
-		;;
-	elxsi)
-		basic_machine=elxsi-elxsi
-		os=-bsd
+	dpx2*)
+		cpu=m68k
+		vendor=bull
+		basic_os=sysv3
 		;;
 	encore | umax | mmax)
-		basic_machine=ns32k-encore
+		cpu=ns32k
+		vendor=encore
 		;;
-	es1800 | OSE68k | ose68k | ose | OSE)
-		basic_machine=m68k-ericsson
-		os=-ose
+	elxsi)
+		cpu=elxsi
+		vendor=elxsi
+		basic_os=${basic_os:-bsd}
 		;;
 	fx2800)
-		basic_machine=i860-alliant
+		cpu=i860
+		vendor=alliant
 		;;
 	genix)
-		basic_machine=ns32k-ns
-		;;
-	gmicro)
-		basic_machine=tron-gmicro
-		os=-sysv
-		;;
-	go32)
-		basic_machine=i386-pc
-		os=-go32
+		cpu=ns32k
+		vendor=ns
 		;;
 	h3050r* | hiux*)
-		basic_machine=hppa1.1-hitachi
-		os=-hiuxwe2
-		;;
-	h8300hms)
-		basic_machine=h8300-hitachi
-		os=-hms
-		;;
-	h8300xray)
-		basic_machine=h8300-hitachi
-		os=-xray
-		;;
-	h8500hms)
-		basic_machine=h8500-hitachi
-		os=-hms
-		;;
-	harris)
-		basic_machine=m88k-harris
-		os=-sysv3
-		;;
-	hp300-*)
-		basic_machine=m68k-hp
-		;;
-	hp300bsd)
-		basic_machine=m68k-hp
-		os=-bsd
-		;;
-	hp300hpux)
-		basic_machine=m68k-hp
-		os=-hpux
+		cpu=hppa1.1
+		vendor=hitachi
+		basic_os=hiuxwe2
 		;;
 	hp3k9[0-9][0-9] | hp9[0-9][0-9])
-		basic_machine=hppa1.0-hp
+		cpu=hppa1.0
+		vendor=hp
 		;;
 	hp9k2[0-9][0-9] | hp9k31[0-9])
-		basic_machine=m68000-hp
+		cpu=m68000
+		vendor=hp
 		;;
 	hp9k3[2-9][0-9])
-		basic_machine=m68k-hp
+		cpu=m68k
+		vendor=hp
 		;;
 	hp9k6[0-9][0-9] | hp6[0-9][0-9])
-		basic_machine=hppa1.0-hp
+		cpu=hppa1.0
+		vendor=hp
 		;;
 	hp9k7[0-79][0-9] | hp7[0-79][0-9])
-		basic_machine=hppa1.1-hp
+		cpu=hppa1.1
+		vendor=hp
 		;;
 	hp9k78[0-9] | hp78[0-9])
 		# FIXME: really hppa2.0-hp
-		basic_machine=hppa1.1-hp
+		cpu=hppa1.1
+		vendor=hp
 		;;
 	hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
 		# FIXME: really hppa2.0-hp
-		basic_machine=hppa1.1-hp
+		cpu=hppa1.1
+		vendor=hp
 		;;
 	hp9k8[0-9][13679] | hp8[0-9][13679])
-		basic_machine=hppa1.1-hp
+		cpu=hppa1.1
+		vendor=hp
 		;;
 	hp9k8[0-9][0-9] | hp8[0-9][0-9])
-		basic_machine=hppa1.0-hp
-		;;
-	hppa-next)
-		os=-nextstep3
-		;;
-	hppaosf)
-		basic_machine=hppa1.1-hp
-		os=-osf
-		;;
-	hppro)
-		basic_machine=hppa1.1-hp
-		os=-proelf
-		;;
-	i370-ibm* | ibm*)
-		basic_machine=i370-ibm
+		cpu=hppa1.0
+		vendor=hp
 		;;
 	i*86v32)
-		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
-		os=-sysv32
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=sysv32
 		;;
 	i*86v4*)
-		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
-		os=-sysv4
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=sysv4
 		;;
 	i*86v)
-		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
-		os=-sysv
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=sysv
 		;;
 	i*86sol2)
-		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
-		os=-solaris2
+		cpu=`echo "$1" | sed -e 's/86.*/86/'`
+		vendor=pc
+		basic_os=solaris2
 		;;
-	i386mach)
-		basic_machine=i386-mach
-		os=-mach
-		;;
-	i386-vsta | vsta)
-		basic_machine=i386-unknown
-		os=-vsta
+	j90 | j90-cray)
+		cpu=j90
+		vendor=cray
+		basic_os=${basic_os:-unicos}
 		;;
 	iris | iris4d)
-		basic_machine=mips-sgi
-		case $os in
-		    -irix*)
+		cpu=mips
+		vendor=sgi
+		case $basic_os in
+		    irix*)
 			;;
 		    *)
-			os=-irix4
+			basic_os=irix4
 			;;
 		esac
 		;;
-	isi68 | isi)
-		basic_machine=m68k-isi
-		os=-sysv
-		;;
-	leon-*|leon[3-9]-*)
-		basic_machine=sparc-`echo $basic_machine | sed 's/-.*//'`
-		;;
-	m68knommu)
-		basic_machine=m68k-unknown
-		os=-linux
-		;;
-	m68knommu-*)
-		basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
-		os=-linux
-		;;
-	m88k-omron*)
-		basic_machine=m88k-omron
-		;;
-	magnum | m3230)
-		basic_machine=mips-mips
-		os=-sysv
-		;;
-	merlin)
-		basic_machine=ns32k-utek
-		os=-sysv
-		;;
-	microblaze*)
-		basic_machine=microblaze-xilinx
-		;;
-	mingw64)
-		basic_machine=x86_64-pc
-		os=-mingw64
-		;;
-	mingw32)
-		basic_machine=i686-pc
-		os=-mingw32
-		;;
-	mingw32ce)
-		basic_machine=arm-unknown
-		os=-mingw32ce
-		;;
 	miniframe)
-		basic_machine=m68000-convergent
-		;;
-	*mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
-		basic_machine=m68k-atari
-		os=-mint
-		;;
-	mips3*-*)
-		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
-		;;
-	mips3*)
-		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
-		;;
-	monitor)
-		basic_machine=m68k-rom68k
-		os=-coff
-		;;
-	morphos)
-		basic_machine=powerpc-unknown
-		os=-morphos
-		;;
-	moxiebox)
-		basic_machine=moxie-unknown
-		os=-moxiebox
-		;;
-	msdos)
-		basic_machine=i386-pc
-		os=-msdos
-		;;
-	ms1-*)
-		basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
-		;;
-	msys)
-		basic_machine=i686-pc
-		os=-msys
-		;;
-	mvs)
-		basic_machine=i370-ibm
-		os=-mvs
-		;;
-	nacl)
-		basic_machine=le32-unknown
-		os=-nacl
+		cpu=m68000
+		vendor=convergent
 		;;
-	ncr3000)
-		basic_machine=i486-ncr
-		os=-sysv4
-		;;
-	netbsd386)
-		basic_machine=i386-unknown
-		os=-netbsd
-		;;
-	netwinder)
-		basic_machine=armv4l-rebel
-		os=-linux
-		;;
-	news | news700 | news800 | news900)
-		basic_machine=m68k-sony
-		os=-newsos
-		;;
-	news1000)
-		basic_machine=m68030-sony
-		os=-newsos
+	*mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
+		cpu=m68k
+		vendor=atari
+		basic_os=mint
 		;;
 	news-3600 | risc-news)
-		basic_machine=mips-sony
-		os=-newsos
-		;;
-	necv70)
-		basic_machine=v70-nec
-		os=-sysv
-		;;
-	next | m*-next )
-		basic_machine=m68k-next
-		case $os in
-		    -nextstep* )
+		cpu=mips
+		vendor=sony
+		basic_os=newsos
+		;;
+	next | m*-next)
+		cpu=m68k
+		vendor=next
+		case $basic_os in
+		    openstep*)
+		        ;;
+		    nextstep*)
 			;;
-		    -ns2*)
-		      os=-nextstep2
+		    ns2*)
+		      basic_os=nextstep2
 			;;
 		    *)
-		      os=-nextstep3
+		      basic_os=nextstep3
 			;;
 		esac
 		;;
-	nh3000)
-		basic_machine=m68k-harris
-		os=-cxux
-		;;
-	nh[45]000)
-		basic_machine=m88k-harris
-		os=-cxux
-		;;
-	nindy960)
-		basic_machine=i960-intel
-		os=-nindy
-		;;
-	mon960)
-		basic_machine=i960-intel
-		os=-mon960
-		;;
-	nonstopux)
-		basic_machine=mips-compaq
-		os=-nonstopux
-		;;
 	np1)
-		basic_machine=np1-gould
-		;;
-	neo-tandem)
-		basic_machine=neo-tandem
-		;;
-	nse-tandem)
-		basic_machine=nse-tandem
-		;;
-	nsr-tandem)
-		basic_machine=nsr-tandem
+		cpu=np1
+		vendor=gould
 		;;
 	op50n-* | op60c-*)
-		basic_machine=hppa1.1-oki
-		os=-proelf
-		;;
-	openrisc | openrisc-*)
-		basic_machine=or32-unknown
-		;;
-	os400)
-		basic_machine=powerpc-ibm
-		os=-os400
-		;;
-	OSE68000 | ose68000)
-		basic_machine=m68000-ericsson
-		os=-ose
-		;;
-	os68k)
-		basic_machine=m68k-none
-		os=-os68k
+		cpu=hppa1.1
+		vendor=oki
+		basic_os=proelf
 		;;
 	pa-hitachi)
-		basic_machine=hppa1.1-hitachi
-		os=-hiuxwe2
-		;;
-	paragon)
-		basic_machine=i860-intel
-		os=-osf
-		;;
-	parisc)
-		basic_machine=hppa-unknown
-		os=-linux
-		;;
-	parisc-*)
-		basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
-		os=-linux
+		cpu=hppa1.1
+		vendor=hitachi
+		basic_os=hiuxwe2
 		;;
 	pbd)
-		basic_machine=sparc-tti
+		cpu=sparc
+		vendor=tti
 		;;
 	pbb)
-		basic_machine=m68k-tti
-		;;
-	pc532 | pc532-*)
-		basic_machine=ns32k-pc532
-		;;
-	pc98)
-		basic_machine=i386-pc
+		cpu=m68k
+		vendor=tti
 		;;
-	pc98-*)
-		basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	pentium | p5 | k5 | k6 | nexgen | viac3)
-		basic_machine=i586-pc
-		;;
-	pentiumpro | p6 | 6x86 | athlon | athlon_*)
-		basic_machine=i686-pc
-		;;
-	pentiumii | pentium2 | pentiumiii | pentium3)
-		basic_machine=i686-pc
-		;;
-	pentium4)
-		basic_machine=i786-pc
-		;;
-	pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
-		basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	pentiumpro-* | p6-* | 6x86-* | athlon-*)
-		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
-		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	pentium4-*)
-		basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+	pc532)
+		cpu=ns32k
+		vendor=pc532
 		;;
 	pn)
-		basic_machine=pn-gould
-		;;
-	power)	basic_machine=power-ibm
-		;;
-	ppc | ppcbe)	basic_machine=powerpc-unknown
+		cpu=pn
+		vendor=gould
 		;;
-	ppc-* | ppcbe-*)
-		basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	ppcle | powerpclittle | ppc-le | powerpc-little)
-		basic_machine=powerpcle-unknown
-		;;
-	ppcle-* | powerpclittle-*)
-		basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	ppc64)	basic_machine=powerpc64-unknown
-		;;
-	ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
-		;;
-	ppc64le | powerpc64little | ppc64-le | powerpc64-little)
-		basic_machine=powerpc64le-unknown
-		;;
-	ppc64le-* | powerpc64little-*)
-		basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+	power)
+		cpu=power
+		vendor=ibm
 		;;
 	ps2)
-		basic_machine=i386-ibm
-		;;
-	pw32)
-		basic_machine=i586-unknown
-		os=-pw32
-		;;
-	rdos | rdos64)
-		basic_machine=x86_64-pc
-		os=-rdos
-		;;
-	rdos32)
-		basic_machine=i386-pc
-		os=-rdos
-		;;
-	rom68k)
-		basic_machine=m68k-rom68k
-		os=-coff
+		cpu=i386
+		vendor=ibm
 		;;
 	rm[46]00)
-		basic_machine=mips-siemens
+		cpu=mips
+		vendor=siemens
 		;;
 	rtpc | rtpc-*)
-		basic_machine=romp-ibm
-		;;
-	s390 | s390-*)
-		basic_machine=s390-ibm
+		cpu=romp
+		vendor=ibm
 		;;
-	s390x | s390x-*)
-		basic_machine=s390x-ibm
-		;;
-	sa29200)
-		basic_machine=a29k-amd
-		os=-udi
-		;;
-	sb1)
-		basic_machine=mipsisa64sb1-unknown
+	sde)
+		cpu=mipsisa32
+		vendor=sde
+		basic_os=${basic_os:-elf}
 		;;
-	sb1el)
-		basic_machine=mipsisa64sb1el-unknown
+	simso-wrs)
+		cpu=sparclite
+		vendor=wrs
+		basic_os=vxworks
 		;;
-	sde)
-		basic_machine=mipsisa32-sde
-		os=-elf
+	tower | tower-32)
+		cpu=m68k
+		vendor=ncr
 		;;
-	sei)
-		basic_machine=mips-sei
-		os=-seiux
+	vpp*|vx|vx-*)
+		cpu=f301
+		vendor=fujitsu
 		;;
-	sequent)
-		basic_machine=i386-sequent
+	w65)
+		cpu=w65
+		vendor=wdc
 		;;
-	sh)
-		basic_machine=sh-hitachi
-		os=-hms
+	w89k-*)
+		cpu=hppa1.1
+		vendor=winbond
+		basic_os=proelf
 		;;
-	sh5el)
-		basic_machine=sh5le-unknown
+	none)
+		cpu=none
+		vendor=none
 		;;
-	sh64)
-		basic_machine=sh64-unknown
+	leon|leon[3-9])
+		cpu=sparc
+		vendor=$basic_machine
 		;;
-	sparclite-wrs | simso-wrs)
-		basic_machine=sparclite-wrs
-		os=-vxworks
+	leon-*|leon[3-9]-*)
+		cpu=sparc
+		vendor=`echo "$basic_machine" | sed 's/-.*//'`
 		;;
-	sps7)
-		basic_machine=m68k-bull
-		os=-sysv2
+
+	*-*)
+		# shellcheck disable=SC2162
+		saved_IFS=$IFS
+		IFS="-" read cpu vendor <<EOF
+$basic_machine
+EOF
+		IFS=$saved_IFS
 		;;
-	spur)
-		basic_machine=spur-unknown
+	# We use `pc' rather than `unknown'
+	# because (1) that's what they normally are, and
+	# (2) the word "unknown" tends to confuse beginning users.
+	i*86 | x86_64)
+		cpu=$basic_machine
+		vendor=pc
 		;;
-	st2000)
-		basic_machine=m68k-tandem
+	# These rules are duplicated from below for sake of the special case above;
+	# i.e. things that normalized to x86 arches should also default to "pc"
+	pc98)
+		cpu=i386
+		vendor=pc
 		;;
-	stratus)
-		basic_machine=i860-stratus
-		os=-sysv4
+	x64 | amd64)
+		cpu=x86_64
+		vendor=pc
 		;;
-	strongarm-* | thumb-*)
-		basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'`
+	# Recognize the basic CPU types without company name.
+	*)
+		cpu=$basic_machine
+		vendor=unknown
 		;;
-	sun2)
-		basic_machine=m68000-sun
+esac
+
+unset -v basic_machine
+
+# Decode basic machines in the full and proper CPU-Company form.
+case $cpu-$vendor in
+	# Here we handle the default manufacturer of certain CPU types in canonical form. It is in
+	# some cases the only manufacturer, in others, it is the most popular.
+	craynv-unknown)
+		vendor=cray
+		basic_os=${basic_os:-unicosmp}
 		;;
-	sun2os3)
-		basic_machine=m68000-sun
-		os=-sunos3
+	c90-unknown | c90-cray)
+		vendor=cray
+		basic_os=${Basic_os:-unicos}
 		;;
-	sun2os4)
-		basic_machine=m68000-sun
-		os=-sunos4
+	fx80-unknown)
+		vendor=alliant
 		;;
-	sun3os3)
-		basic_machine=m68k-sun
-		os=-sunos3
+	romp-unknown)
+		vendor=ibm
 		;;
-	sun3os4)
-		basic_machine=m68k-sun
-		os=-sunos4
+	mmix-unknown)
+		vendor=knuth
 		;;
-	sun4os3)
-		basic_machine=sparc-sun
-		os=-sunos3
+	microblaze-unknown | microblazeel-unknown)
+		vendor=xilinx
 		;;
-	sun4os4)
-		basic_machine=sparc-sun
-		os=-sunos4
+	rs6000-unknown)
+		vendor=ibm
 		;;
-	sun4sol2)
-		basic_machine=sparc-sun
-		os=-solaris2
+	vax-unknown)
+		vendor=dec
 		;;
-	sun3 | sun3-*)
-		basic_machine=m68k-sun
+	pdp11-unknown)
+		vendor=dec
 		;;
-	sun4)
-		basic_machine=sparc-sun
+	we32k-unknown)
+		vendor=att
 		;;
-	sun386 | sun386i | roadrunner)
-		basic_machine=i386-sun
+	cydra-unknown)
+		vendor=cydrome
 		;;
-	sv1)
-		basic_machine=sv1-cray
-		os=-unicos
+	i370-ibm*)
+		vendor=ibm
 		;;
-	symmetry)
-		basic_machine=i386-sequent
-		os=-dynix
+	orion-unknown)
+		vendor=highlevel
 		;;
-	t3e)
-		basic_machine=alphaev5-cray
-		os=-unicos
+	xps-unknown | xps100-unknown)
+		cpu=xps100
+		vendor=honeywell
 		;;
-	t90)
-		basic_machine=t90-cray
-		os=-unicos
+
+	# Here we normalize CPU types with a missing or matching vendor
+	dpx20-unknown | dpx20-bull)
+		cpu=rs6000
+		vendor=bull
+		basic_os=${basic_os:-bosx}
 		;;
-	tile*)
-		basic_machine=$basic_machine-unknown
-		os=-linux-gnu
+
+	# Here we normalize CPU types irrespective of the vendor
+	amd64-*)
+		cpu=x86_64
 		;;
-	tx39)
-		basic_machine=mipstx39-unknown
+	blackfin-*)
+		cpu=bfin
+		basic_os=linux
 		;;
-	tx39el)
-		basic_machine=mipstx39el-unknown
+	c54x-*)
+		cpu=tic54x
 		;;
-	toad1)
-		basic_machine=pdp10-xkl
-		os=-tops20
+	c55x-*)
+		cpu=tic55x
 		;;
-	tower | tower-32)
-		basic_machine=m68k-ncr
+	c6x-*)
+		cpu=tic6x
 		;;
-	tpf)
-		basic_machine=s390x-ibm
-		os=-tpf
+	e500v[12]-*)
+		cpu=powerpc
+		basic_os=${basic_os}"spe"
 		;;
-	udi29k)
-		basic_machine=a29k-amd
-		os=-udi
+	mips3*-*)
+		cpu=mips64
 		;;
-	ultra3)
-		basic_machine=a29k-nyu
-		os=-sym1
+	ms1-*)
+		cpu=mt
 		;;
-	v810 | necv810)
-		basic_machine=v810-nec
-		os=-none
+	m68knommu-*)
+		cpu=m68k
+		basic_os=linux
 		;;
-	vaxv)
-		basic_machine=vax-dec
-		os=-sysv
+	m9s12z-* | m68hcs12z-* | hcs12z-* | s12z-*)
+		cpu=s12z
 		;;
-	vms)
-		basic_machine=vax-dec
-		os=-vms
+	openrisc-*)
+		cpu=or32
 		;;
-	vpp*|vx|vx-*)
-		basic_machine=f301-fujitsu
+	parisc-*)
+		cpu=hppa
+		basic_os=linux
 		;;
-	vxworks960)
-		basic_machine=i960-wrs
-		os=-vxworks
+	pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+		cpu=i586
 		;;
-	vxworks68)
-		basic_machine=m68k-wrs
-		os=-vxworks
+	pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
+		cpu=i686
 		;;
-	vxworks29k)
-		basic_machine=a29k-wrs
-		os=-vxworks
+	pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+		cpu=i686
 		;;
-	w65*)
-		basic_machine=w65-wdc
-		os=-none
+	pentium4-*)
+		cpu=i786
 		;;
-	w89k-*)
-		basic_machine=hppa1.1-winbond
-		os=-proelf
+	pc98-*)
+		cpu=i386
 		;;
-	xbox)
-		basic_machine=i686-pc
-		os=-mingw32
+	ppc-* | ppcbe-*)
+		cpu=powerpc
 		;;
-	xps | xps100)
-		basic_machine=xps100-honeywell
+	ppcle-* | powerpclittle-*)
+		cpu=powerpcle
 		;;
-	xscale-* | xscalee[bl]-*)
-		basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'`
+	ppc64-*)
+		cpu=powerpc64
 		;;
-	ymp)
-		basic_machine=ymp-cray
-		os=-unicos
+	ppc64le-* | powerpc64little-*)
+		cpu=powerpc64le
 		;;
-	z8k-*-coff)
-		basic_machine=z8k-unknown
-		os=-sim
+	sb1-*)
+		cpu=mipsisa64sb1
 		;;
-	z80-*-coff)
-		basic_machine=z80-unknown
-		os=-sim
+	sb1el-*)
+		cpu=mipsisa64sb1el
 		;;
-	none)
-		basic_machine=none-none
-		os=-none
+	sh5e[lb]-*)
+		cpu=`echo "$cpu" | sed 's/^\(sh.\)e\(.\)$/\1\2e/'`
 		;;
-
-# Here we handle the default manufacturer of certain CPU types.  It is in
-# some cases the only manufacturer, in others, it is the most popular.
-	w89k)
-		basic_machine=hppa1.1-winbond
+	spur-*)
+		cpu=spur
 		;;
-	op50n)
-		basic_machine=hppa1.1-oki
+	strongarm-* | thumb-*)
+		cpu=arm
 		;;
-	op60c)
-		basic_machine=hppa1.1-oki
+	tx39-*)
+		cpu=mipstx39
 		;;
-	romp)
-		basic_machine=romp-ibm
+	tx39el-*)
+		cpu=mipstx39el
 		;;
-	mmix)
-		basic_machine=mmix-knuth
+	x64-*)
+		cpu=x86_64
 		;;
-	rs6000)
-		basic_machine=rs6000-ibm
+	xscale-* | xscalee[bl]-*)
+		cpu=`echo "$cpu" | sed 's/^xscale/arm/'`
 		;;
-	vax)
-		basic_machine=vax-dec
+	arm64-*)
+		cpu=aarch64
 		;;
-	pdp10)
-		# there are many clones, so DEC is not a safe bet
-		basic_machine=pdp10-unknown
+
+	# Recognize the canonical CPU Types that limit and/or modify the
+	# company names they are paired with.
+	cr16-*)
+		basic_os=${basic_os:-elf}
 		;;
-	pdp11)
-		basic_machine=pdp11-dec
+	crisv32-* | etraxfs*-*)
+		cpu=crisv32
+		vendor=axis
 		;;
-	we32k)
-		basic_machine=we32k-att
+	cris-* | etrax*-*)
+		cpu=cris
+		vendor=axis
 		;;
-	sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele)
-		basic_machine=sh-unknown
+	crx-*)
+		basic_os=${basic_os:-elf}
 		;;
-	sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
-		basic_machine=sparc-sun
+	neo-tandem)
+		cpu=neo
+		vendor=tandem
 		;;
-	cydra)
-		basic_machine=cydra-cydrome
+	nse-tandem)
+		cpu=nse
+		vendor=tandem
 		;;
-	orion)
-		basic_machine=orion-highlevel
+	nsr-tandem)
+		cpu=nsr
+		vendor=tandem
 		;;
-	orion105)
-		basic_machine=clipper-highlevel
+	nsv-tandem)
+		cpu=nsv
+		vendor=tandem
 		;;
-	mac | mpw | mac-mpw)
-		basic_machine=m68k-apple
+	nsx-tandem)
+		cpu=nsx
+		vendor=tandem
 		;;
-	pmac | pmac-mpw)
-		basic_machine=powerpc-apple
+	mipsallegrexel-sony)
+		cpu=mipsallegrexel
+		vendor=sony
 		;;
-	*-unknown)
-		# Make sure to match an already-canonicalized machine name.
+	tile*-*)
+		basic_os=${basic_os:-linux-gnu}
 		;;
+
 	*)
-		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
-		exit 1
+		# Recognize the canonical CPU types that are allowed with any
+		# company name.
+		case $cpu in
+			1750a | 580 \
+			| a29k \
+			| aarch64 | aarch64_be \
+			| abacus \
+			| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
+			| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
+			| alphapca5[67] | alpha64pca5[67] \
+			| am33_2.0 \
+			| amdgcn \
+			| arc | arceb | arc32 | arc64 \
+			| arm | arm[lb]e | arme[lb] | armv* \
+			| avr | avr32 \
+			| asmjs \
+			| ba \
+			| be32 | be64 \
+			| bfin | bpf | bs2000 \
+			| c[123]* | c30 | [cjt]90 | c4x \
+			| c8051 | clipper | craynv | csky | cydra \
+			| d10v | d30v | dlx | dsp16xx \
+			| e2k | elxsi | epiphany \
+			| f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
+			| h8300 | h8500 \
+			| hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+			| hexagon \
+			| i370 | i*86 | i860 | i960 | ia16 | ia64 \
+			| ip2k | iq2000 \
+			| k1om \
+			| le32 | le64 \
+			| lm32 \
+			| loongarch32 | loongarch64 | loongarchx32 \
+			| m32c | m32r | m32rle \
+			| m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
+			| m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
+			| m88110 | m88k | maxq | mb | mcore | mep | metag \
+			| microblaze | microblazeel \
+			| mips | mipsbe | mipseb | mipsel | mipsle \
+			| mips16 \
+			| mips64 | mips64eb | mips64el \
+			| mips64octeon | mips64octeonel \
+			| mips64orion | mips64orionel \
+			| mips64r5900 | mips64r5900el \
+			| mips64vr | mips64vrel \
+			| mips64vr4100 | mips64vr4100el \
+			| mips64vr4300 | mips64vr4300el \
+			| mips64vr5000 | mips64vr5000el \
+			| mips64vr5900 | mips64vr5900el \
+			| mipsisa32 | mipsisa32el \
+			| mipsisa32r2 | mipsisa32r2el \
+			| mipsisa32r3 | mipsisa32r3el \
+			| mipsisa32r5 | mipsisa32r5el \
+			| mipsisa32r6 | mipsisa32r6el \
+			| mipsisa64 | mipsisa64el \
+			| mipsisa64r2 | mipsisa64r2el \
+			| mipsisa64r3 | mipsisa64r3el \
+			| mipsisa64r5 | mipsisa64r5el \
+			| mipsisa64r6 | mipsisa64r6el \
+			| mipsisa64sb1 | mipsisa64sb1el \
+			| mipsisa64sr71k | mipsisa64sr71kel \
+			| mipsr5900 | mipsr5900el \
+			| mipstx39 | mipstx39el \
+			| mmix \
+			| mn10200 | mn10300 \
+			| moxie \
+			| mt \
+			| msp430 \
+			| nds32 | nds32le | nds32be \
+			| nfp \
+			| nios | nios2 | nios2eb | nios2el \
+			| none | np1 | ns16k | ns32k | nvptx \
+			| open8 \
+			| or1k* \
+			| or32 \
+			| orion \
+			| picochip \
+			| pdp10 | pdp11 | pj | pjl | pn | power \
+			| powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \
+			| pru \
+			| pyramid \
+			| riscv | riscv32 | riscv32be | riscv64 | riscv64be \
+			| rl78 | romp | rs6000 | rx \
+			| s390 | s390x \
+			| score \
+			| sh | shl \
+			| sh[1234] | sh[24]a | sh[24]ae[lb] | sh[23]e | she[lb] | sh[lb]e \
+			| sh[1234]e[lb] |  sh[12345][lb]e | sh[23]ele | sh64 | sh64le \
+			| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet \
+			| sparclite \
+			| sparcv8 | sparcv9 | sparcv9b | sparcv9v | sv1 | sx* \
+			| spu \
+			| tahoe \
+			| thumbv7* \
+			| tic30 | tic4x | tic54x | tic55x | tic6x | tic80 \
+			| tron \
+			| ubicom32 \
+			| v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
+			| vax \
+			| visium \
+			| w65 \
+			| wasm32 | wasm64 \
+			| we32k \
+			| x86 | x86_64 | xc16x | xgate | xps100 \
+			| xstormy16 | xtensa* \
+			| ymp \
+			| z8k | z80)
+				;;
+
+			*)
+				echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
+				exit 1
+				;;
+		esac
 		;;
 esac
 
 # Here we canonicalize certain aliases for manufacturers.
-case $basic_machine in
-	*-digital*)
-		basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+case $vendor in
+	digital*)
+		vendor=dec
 		;;
-	*-commodore*)
-		basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+	commodore*)
+		vendor=cbm
 		;;
 	*)
 		;;
@@ -1343,200 +1301,215 @@ esac
 
 # Decode manufacturer-specific aliases for certain operating systems.
 
-if [ x"$os" != x"" ]
+if test x$basic_os != x
 then
+
+# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just
+# set os.
+case $basic_os in
+	gnu/linux*)
+		kernel=linux
+		os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'`
+		;;
+	os2-emx)
+		kernel=os2
+		os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'`
+		;;
+	nto-qnx*)
+		kernel=nto
+		os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'`
+		;;
+	*-*)
+		# shellcheck disable=SC2162
+		saved_IFS=$IFS
+		IFS="-" read kernel os <<EOF
+$basic_os
+EOF
+		IFS=$saved_IFS
+		;;
+	# Default OS when just kernel was specified
+	nto*)
+		kernel=nto
+		os=`echo "$basic_os" | sed -e 's|nto|qnx|'`
+		;;
+	linux*)
+		kernel=linux
+		os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
+		;;
+	*)
+		kernel=
+		os=$basic_os
+		;;
+esac
+
+# Now, normalize the OS (knowing we just have one component, it's not a kernel,
+# etc.)
 case $os in
-	# First match some system type aliases
-	# that might get confused with valid system types.
-	# -solaris* is a basic system type, with this one exception.
-	-auroraux)
-		os=-auroraux
+	# First match some system type aliases that might get confused
+	# with valid system types.
+	# solaris* is a basic system type, with this one exception.
+	auroraux)
+		os=auroraux
 		;;
-	-solaris1 | -solaris1.*)
-		os=`echo $os | sed -e 's|solaris1|sunos4|'`
+	bluegene*)
+		os=cnk
 		;;
-	-solaris)
-		os=-solaris2
+	solaris1 | solaris1.*)
+		os=`echo "$os" | sed -e 's|solaris1|sunos4|'`
 		;;
-	-svr4*)
-		os=-sysv4
+	solaris)
+		os=solaris2
 		;;
-	-unixware*)
-		os=-sysv4.2uw
+	unixware*)
+		os=sysv4.2uw
 		;;
-	-gnu/linux*)
-		os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+	# es1800 is here to avoid being matched by es* (a different OS)
+	es1800*)
+		os=ose
 		;;
-	# First accept the basic system types.
-	# The portable systems comes first.
-	# Each alternative MUST END IN A *, to match a version number.
-	# -sysv* is not here because it comes later, after sysvr4.
-	-gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
-	      | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\
-	      | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
-	      | -sym* | -kopensolaris* | -plan9* \
-	      | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
-	      | -aos* | -aros* | -cloudabi* \
-	      | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
-	      | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
-	      | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
-	      | -bitrig* | -openbsd* | -solidbsd* \
-	      | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
-	      | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
-	      | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
-	      | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
-	      | -chorusos* | -chorusrdb* | -cegcc* \
-	      | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
-	      | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \
-	      | -linux-newlib* | -linux-musl* | -linux-uclibc* \
-	      | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \
-	      | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
-	      | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
-	      | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
-	      | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
-	      | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
-	      | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
-	      | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* | -tirtos*)
-	# Remember, each alternative MUST END IN *, to match a version number.
-		;;
-	-qnx*)
-		case $basic_machine in
-		    x86-* | i*86-*)
-			;;
-		    *)
-			os=-nto$os
-			;;
-		esac
+	# Some version numbers need modification
+	chorusos*)
+		os=chorusos
 		;;
-	-nto-qnx*)
+	isc)
+		os=isc2.2
 		;;
-	-nto*)
-		os=`echo $os | sed -e 's|nto|nto-qnx|'`
+	sco6)
+		os=sco5v6
 		;;
-	-sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
-	      | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \
-	      | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+	sco5)
+		os=sco3.2v5
 		;;
-	-mac*)
-		os=`echo $os | sed -e 's|mac|macos|'`
+	sco4)
+		os=sco3.2v4
 		;;
-	-linux-dietlibc)
-		os=-linux-dietlibc
+	sco3.2.[4-9]*)
+		os=`echo "$os" | sed -e 's/sco3.2./sco3.2v/'`
 		;;
-	-linux*)
-		os=`echo $os | sed -e 's|linux|linux-gnu|'`
+	sco*v* | scout)
+		# Don't match below
 		;;
-	-sunos5*)
-		os=`echo $os | sed -e 's|sunos5|solaris2|'`
+	sco*)
+		os=sco3.2v2
 		;;
-	-sunos6*)
-		os=`echo $os | sed -e 's|sunos6|solaris3|'`
+	psos*)
+		os=psos
 		;;
-	-opened*)
-		os=-openedition
+	qnx*)
+		os=qnx
 		;;
-	-os400*)
-		os=-os400
+	hiux*)
+		os=hiuxwe2
 		;;
-	-wince*)
-		os=-wince
+	lynx*178)
+		os=lynxos178
 		;;
-	-osfrose*)
-		os=-osfrose
+	lynx*5)
+		os=lynxos5
 		;;
-	-osf*)
-		os=-osf
+	lynxos*)
+		# don't get caught up in next wildcard
 		;;
-	-utek*)
-		os=-bsd
+	lynx*)
+		os=lynxos
 		;;
-	-dynix*)
-		os=-bsd
+	mac[0-9]*)
+		os=`echo "$os" | sed -e 's|mac|macos|'`
 		;;
-	-acis*)
-		os=-aos
+	opened*)
+		os=openedition
 		;;
-	-atheos*)
-		os=-atheos
+	os400*)
+		os=os400
 		;;
-	-syllable*)
-		os=-syllable
+	sunos5*)
+		os=`echo "$os" | sed -e 's|sunos5|solaris2|'`
 		;;
-	-386bsd)
-		os=-bsd
+	sunos6*)
+		os=`echo "$os" | sed -e 's|sunos6|solaris3|'`
 		;;
-	-ctix* | -uts*)
-		os=-sysv
+	wince*)
+		os=wince
 		;;
-	-nova*)
-		os=-rtmk-nova
+	utek*)
+		os=bsd
 		;;
-	-ns2 )
-		os=-nextstep2
+	dynix*)
+		os=bsd
 		;;
-	-nsk*)
-		os=-nsk
+	acis*)
+		os=aos
 		;;
-	# Preserve the version number of sinix5.
-	-sinix5.*)
-		os=`echo $os | sed -e 's|sinix|sysv|'`
+	atheos*)
+		os=atheos
 		;;
-	-sinix*)
-		os=-sysv4
+	syllable*)
+		os=syllable
 		;;
-	-tpf*)
-		os=-tpf
+	386bsd)
+		os=bsd
 		;;
-	-triton*)
-		os=-sysv3
+	ctix* | uts*)
+		os=sysv
 		;;
-	-oss*)
-		os=-sysv3
+	nova*)
+		os=rtmk-nova
 		;;
-	-svr4)
-		os=-sysv4
+	ns2)
+		os=nextstep2
 		;;
-	-svr3)
-		os=-sysv3
+	# Preserve the version number of sinix5.
+	sinix5.*)
+		os=`echo "$os" | sed -e 's|sinix|sysv|'`
 		;;
-	-sysvr4)
-		os=-sysv4
+	sinix*)
+		os=sysv4
 		;;
-	# This must come after -sysvr4.
-	-sysv*)
+	tpf*)
+		os=tpf
 		;;
-	-ose*)
-		os=-ose
+	triton*)
+		os=sysv3
 		;;
-	-es1800*)
-		os=-ose
+	oss*)
+		os=sysv3
 		;;
-	-xenix)
-		os=-xenix
+	svr4*)
+		os=sysv4
 		;;
-	-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
-		os=-mint
+	svr3)
+		os=sysv3
 		;;
-	-aros*)
-		os=-aros
+	sysvr4)
+		os=sysv4
 		;;
-	-zvmoe)
-		os=-zvmoe
+	ose*)
+		os=ose
 		;;
-	-dicos*)
-		os=-dicos
+	*mint | mint[0-9]* | *MiNT | MiNT[0-9]*)
+		os=mint
 		;;
-	-nacl*)
+	dicos*)
+		os=dicos
 		;;
-	-none)
+	pikeos*)
+		# Until real need of OS specific support for
+		# particular features comes up, bare metal
+		# configurations are quite functional.
+		case $cpu in
+		    arm*)
+			os=eabi
+			;;
+		    *)
+			os=elf
+			;;
+		esac
 		;;
 	*)
-		# Get rid of the `-' at the beginning of $os.
-		os=`echo $os | sed 's/[^-]*-//'`
-		echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
-		exit 1
+		# No normalization, but not necessarily accepted, that comes below.
 		;;
 esac
+
 else
 
 # Here we handle the default operating systems that come with various machines.
@@ -1549,261 +1522,362 @@ else
 # will signal an error saying that MANUFACTURER isn't an operating
 # system, and we'll never get to this point.
 
-case $basic_machine in
+kernel=
+case $cpu-$vendor in
 	score-*)
-		os=-elf
+		os=elf
 		;;
 	spu-*)
-		os=-elf
+		os=elf
 		;;
 	*-acorn)
-		os=-riscix1.2
+		os=riscix1.2
 		;;
 	arm*-rebel)
-		os=-linux
+		kernel=linux
+		os=gnu
 		;;
 	arm*-semi)
-		os=-aout
+		os=aout
 		;;
 	c4x-* | tic4x-*)
-		os=-coff
+		os=coff
 		;;
 	c8051-*)
-		os=-elf
+		os=elf
+		;;
+	clipper-intergraph)
+		os=clix
 		;;
 	hexagon-*)
-		os=-elf
+		os=elf
 		;;
 	tic54x-*)
-		os=-coff
+		os=coff
 		;;
 	tic55x-*)
-		os=-coff
+		os=coff
 		;;
 	tic6x-*)
-		os=-coff
+		os=coff
 		;;
 	# This must come before the *-dec entry.
 	pdp10-*)
-		os=-tops20
+		os=tops20
 		;;
 	pdp11-*)
-		os=-none
+		os=none
 		;;
 	*-dec | vax-*)
-		os=-ultrix4.2
+		os=ultrix4.2
 		;;
 	m68*-apollo)
-		os=-domain
+		os=domain
 		;;
 	i386-sun)
-		os=-sunos4.0.2
+		os=sunos4.0.2
 		;;
 	m68000-sun)
-		os=-sunos3
+		os=sunos3
 		;;
 	m68*-cisco)
-		os=-aout
+		os=aout
 		;;
 	mep-*)
-		os=-elf
+		os=elf
 		;;
 	mips*-cisco)
-		os=-elf
+		os=elf
 		;;
 	mips*-*)
-		os=-elf
+		os=elf
 		;;
 	or32-*)
-		os=-coff
+		os=coff
 		;;
 	*-tti)	# must be before sparc entry or we get the wrong os.
-		os=-sysv3
+		os=sysv3
 		;;
 	sparc-* | *-sun)
-		os=-sunos4.1.1
+		os=sunos4.1.1
 		;;
-	*-be)
-		os=-beos
+	pru-*)
+		os=elf
 		;;
-	*-haiku)
-		os=-haiku
+	*-be)
+		os=beos
 		;;
 	*-ibm)
-		os=-aix
+		os=aix
 		;;
 	*-knuth)
-		os=-mmixware
+		os=mmixware
 		;;
 	*-wec)
-		os=-proelf
+		os=proelf
 		;;
 	*-winbond)
-		os=-proelf
+		os=proelf
 		;;
 	*-oki)
-		os=-proelf
+		os=proelf
 		;;
 	*-hp)
-		os=-hpux
+		os=hpux
 		;;
 	*-hitachi)
-		os=-hiux
+		os=hiux
 		;;
 	i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
-		os=-sysv
+		os=sysv
 		;;
 	*-cbm)
-		os=-amigaos
+		os=amigaos
 		;;
 	*-dg)
-		os=-dgux
+		os=dgux
 		;;
 	*-dolphin)
-		os=-sysv3
+		os=sysv3
 		;;
 	m68k-ccur)
-		os=-rtu
+		os=rtu
 		;;
 	m88k-omron*)
-		os=-luna
+		os=luna
 		;;
-	*-next )
-		os=-nextstep
+	*-next)
+		os=nextstep
 		;;
 	*-sequent)
-		os=-ptx
+		os=ptx
 		;;
 	*-crds)
-		os=-unos
+		os=unos
 		;;
 	*-ns)
-		os=-genix
+		os=genix
 		;;
 	i370-*)
-		os=-mvs
-		;;
-	*-next)
-		os=-nextstep3
+		os=mvs
 		;;
 	*-gould)
-		os=-sysv
+		os=sysv
 		;;
 	*-highlevel)
-		os=-bsd
+		os=bsd
 		;;
 	*-encore)
-		os=-bsd
+		os=bsd
 		;;
 	*-sgi)
-		os=-irix
+		os=irix
 		;;
 	*-siemens)
-		os=-sysv4
+		os=sysv4
 		;;
 	*-masscomp)
-		os=-rtu
+		os=rtu
 		;;
 	f30[01]-fujitsu | f700-fujitsu)
-		os=-uxpv
+		os=uxpv
 		;;
 	*-rom68k)
-		os=-coff
+		os=coff
 		;;
 	*-*bug)
-		os=-coff
+		os=coff
 		;;
 	*-apple)
-		os=-macos
+		os=macos
 		;;
 	*-atari*)
-		os=-mint
+		os=mint
+		;;
+	*-wrs)
+		os=vxworks
 		;;
 	*)
-		os=-none
+		os=none
 		;;
 esac
+
 fi
 
+# Now, validate our (potentially fixed-up) OS.
+case $os in
+	# Sometimes we do "kernel-libc", so those need to count as OSes.
+	musl* | newlib* | relibc* | uclibc*)
+		;;
+	# Likewise for "kernel-abi"
+	eabi* | gnueabi*)
+		;;
+	# VxWorks passes extra cpu info in the 4th filed.
+	simlinux | simwindows | spe)
+		;;
+	# Now accept the basic system types.
+	# The portable systems comes first.
+	# Each alternative MUST end in a * to match a version number.
+	gnu* | android* | bsd* | mach* | minix* | genix* | ultrix* | irix* \
+	     | *vms* | esix* | aix* | cnk* | sunos | sunos[34]* \
+	     | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
+	     | sym* |  plan9* | psp* | sim* | xray* | os68k* | v88r* \
+	     | hiux* | abug | nacl* | netware* | windows* \
+	     | os9* | macos* | osx* | ios* \
+	     | mpw* | magic* | mmixware* | mon960* | lnews* \
+	     | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
+	     | aos* | aros* | cloudabi* | sortix* | twizzler* \
+	     | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \
+	     | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \
+	     | mirbsd* | netbsd* | dicos* | openedition* | ose* \
+	     | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
+	     | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
+	     | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
+	     | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
+	     | udi* | lites* | ieee* | go32* | aux* | hcos* \
+	     | chorusrdb* | cegcc* | glidix* | serenity* \
+	     | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
+	     | midipix* | mingw32* | mingw64* | mint* \
+	     | uxpv* | beos* | mpeix* | udk* | moxiebox* \
+	     | interix* | uwin* | mks* | rhapsody* | darwin* \
+	     | openstep* | oskit* | conix* | pw32* | nonstopux* \
+	     | storm-chaos* | tops10* | tenex* | tops20* | its* \
+	     | os2* | vos* | palmos* | uclinux* | nucleus* | morphos* \
+	     | scout* | superux* | sysv* | rtmk* | tpf* | windiss* \
+	     | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \
+	     | skyos* | haiku* | rdos* | toppers* | drops* | es* \
+	     | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
+	     | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
+	     | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr*)
+		;;
+	# This one is extra strict with allowed versions
+	sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
+		# Don't forget version if it is 3.2v4 or newer.
+		;;
+	none)
+		;;
+	*)
+		echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
+		exit 1
+		;;
+esac
+
+# As a final step for OS-related things, validate the OS-kernel combination
+# (given a valid OS), if there is a kernel.
+case $kernel-$os in
+	linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
+		   | linux-musl* | linux-relibc* | linux-uclibc* )
+		;;
+	uclinux-uclibc* )
+		;;
+	-dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
+		# These are just libc implementations, not actual OSes, and thus
+		# require a kernel.
+		echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
+		exit 1
+		;;
+	kfreebsd*-gnu* | kopensolaris*-gnu*)
+		;;
+	vxworks-simlinux | vxworks-simwindows | vxworks-spe)
+		;;
+	nto-qnx*)
+		;;
+	os2-emx)
+		;;
+	*-eabi* | *-gnueabi*)
+		;;
+	-*)
+		# Blank kernel with real OS is always fine.
+		;;
+	*-*)
+		echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
+		exit 1
+		;;
+esac
+
 # Here we handle the case where we know the os, and the CPU type, but not the
 # manufacturer.  We pick the logical manufacturer.
-vendor=unknown
-case $basic_machine in
-	*-unknown)
-		case $os in
-			-riscix*)
+case $vendor in
+	unknown)
+		case $cpu-$os in
+			*-riscix*)
 				vendor=acorn
 				;;
-			-sunos*)
+			*-sunos*)
 				vendor=sun
 				;;
-			-cnk*|-aix*)
+			*-cnk* | *-aix*)
 				vendor=ibm
 				;;
-			-beos*)
+			*-beos*)
 				vendor=be
 				;;
-			-hpux*)
+			*-hpux*)
 				vendor=hp
 				;;
-			-mpeix*)
+			*-mpeix*)
 				vendor=hp
 				;;
-			-hiux*)
+			*-hiux*)
 				vendor=hitachi
 				;;
-			-unos*)
+			*-unos*)
 				vendor=crds
 				;;
-			-dgux*)
+			*-dgux*)
 				vendor=dg
 				;;
-			-luna*)
+			*-luna*)
 				vendor=omron
 				;;
-			-genix*)
+			*-genix*)
 				vendor=ns
 				;;
-			-mvs* | -opened*)
+			*-clix*)
+				vendor=intergraph
+				;;
+			*-mvs* | *-opened*)
+				vendor=ibm
+				;;
+			*-os400*)
 				vendor=ibm
 				;;
-			-os400*)
+			s390-* | s390x-*)
 				vendor=ibm
 				;;
-			-ptx*)
+			*-ptx*)
 				vendor=sequent
 				;;
-			-tpf*)
+			*-tpf*)
 				vendor=ibm
 				;;
-			-vxsim* | -vxworks* | -windiss*)
+			*-vxsim* | *-vxworks* | *-windiss*)
 				vendor=wrs
 				;;
-			-aux*)
+			*-aux*)
 				vendor=apple
 				;;
-			-hms*)
+			*-hms*)
 				vendor=hitachi
 				;;
-			-mpw* | -macos*)
+			*-mpw* | *-macos*)
 				vendor=apple
 				;;
-			-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+			*-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
 				vendor=atari
 				;;
-			-vos*)
+			*-vos*)
 				vendor=stratus
 				;;
 		esac
-		basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
 		;;
 esac
 
-echo $basic_machine$os
+echo "$cpu-$vendor-${kernel:+$kernel-}$os"
 exit
 
 # Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
+# eval: (add-hook 'before-save-hook 'time-stamp)
 # time-stamp-start: "timestamp='"
 # time-stamp-format: "%:y-%02m-%02d"
 # time-stamp-end: "'"
diff --git a/autoconf/m4/unreal.m4 b/autoconf/m4/unreal.m4
@@ -130,9 +130,13 @@ AC_DEFUN([CHECK_LIBCURL],
 		LIBS="$LIBS_SAVEDA"
 		CFLAGS="$CFLAGS_SAVEDA"
 
-		URL="url.o"
-		AC_SUBST(URL)
+		dnl Finally, choose the cURL implementation of url.c
+		URL="url_curl.o"
+	],[
+		dnl Choose UnrealIRCds internal implementation of url.c
+		URL="url_unreal.o"
 	]) dnl AS_IF(enable_curl) 
+	AC_SUBST(URL)
 ])
 
 dnl the following 2 macros are based on CHECK_SSL by Mark Ethan Trostler <trostler@juniper.net> 
@@ -178,7 +182,11 @@ AS_IF([test $enable_ssl != "no"],
 	else
 		CRYPTOLIB="-lssl -lcrypto";
 		if test ! "$ssldir" = "/usr" ; then
-			LDFLAGS="$LDFLAGS -L$ssldir/lib";
+			if test -d "$ssldir/lib64" ; then
+				LDFLAGS="$LDFLAGS -L$ssldir/lib64";
+			else
+				LDFLAGS="$LDFLAGS -L$ssldir/lib";
+			fi
 			dnl check if binary path exists
 			if test -f "$ssldir/bin/openssl"; then
 			    OPENSSLPATH="$ssldir/bin/openssl";
@@ -312,3 +320,94 @@ else
 	AC_MSG_RESULT([no])
 fi
 ])
+
+dnl For geoip-api-c
+AC_DEFUN([CHECK_GEOIP_CLASSIC],
+[
+	AC_ARG_ENABLE(geoip_classic,
+	[AC_HELP_STRING([--enable-geoip-classic=no/yes],[enable GeoIP Classic support])],
+	[enable_geoip_classic=$enableval],
+	[enable_geoip_classic=no])
+
+	AS_IF([test "x$enable_geoip_classic" = "xyes"],
+	[
+		dnl First see if the system provides it
+		has_system_geoip_classic="no"
+		PKG_CHECK_MODULES([GEOIP_CLASSIC], [geoip >= 1.6.0],
+		                  [has_system_geoip_classic=yes
+		                   AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libGeoIP.*])],
+		                  [has_system_geoip_classic=no])
+
+		dnl Otherwise fallback to our own..
+		AS_IF([test "$has_system_geoip_classic" = "no"],[
+			dnl REMEMBER TO CHANGE WITH A NEW GEOIP LIBRARY RELEASE!
+			geoip_classic_version="1.6.12"
+			AC_MSG_RESULT(extracting GeoIP Classic library)
+			cur_dir=`pwd`
+			cd extras
+			dnl remove old directory to force a recompile...
+			dnl and remove its installation prefix just to clean things up.
+			rm -rf GeoIP-$geoip_classic_version geoip-classic
+			if test "x$ac_cv_path_GUNZIP" = "x" ; then
+				tar xfz geoip-classic.tar.gz
+			else
+				cp geoip-classic.tar.gz geoip-classic.tar.gz.bak
+				gunzip -f geoip-classic.tar.gz
+				cp geoip-classic.tar.gz.bak geoip-classic.tar.gz
+				tar xf geoip-classic.tar
+			fi
+			AC_MSG_RESULT(configuring GeoIP Classic library)
+			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"
+			AC_MSG_RESULT(compiling GeoIP Classic library)
+			$ac_cv_prog_MAKER || exit 1
+			AC_MSG_RESULT(installing GeoIP Classic library)
+			$ac_cv_prog_MAKER install || exit 1
+			dnl Try pkg-config first...
+			AS_IF([test -n "$ac_cv_path_PKGCONFIG"],
+			       [GEOIP_CLASSIC_LIBS="`$ac_cv_path_PKGCONFIG --libs geoip.pc`"
+			        GEOIP_CLASSIC_CFLAGS="`$ac_cv_path_PKGCONFIG --cflags geoip.pc`"])
+			dnl In case the system does not have pkg-config, fallback to hardcoded settings...
+			AS_IF([test -z "$GEOIP_CLASSIC_LIBS"],
+			       [GEOIP_CLASSIC_LIBS="-L$PRIVATELIBDIR -lGeoIP"
+			        GEOIP_CLASSIC_CFLAGS="-I$cur_dir/extras/geoip-classic/include"])
+			cd $cur_dir
+		])
+		
+		AC_SUBST(GEOIP_CLASSIC_LIBS)
+		AC_SUBST(GEOIP_CLASSIC_CFLAGS)
+
+		GEOIP_CLASSIC_OBJECTS="geoip_classic.so"
+		AC_SUBST(GEOIP_CLASSIC_OBJECTS)
+	]) dnl AS_IF(enable_geoip_classic) 
+])
+
+AC_DEFUN([CHECK_LIBMAXMINDDB],
+[
+	AC_ARG_ENABLE(libmaxminddb,
+	[AC_HELP_STRING([--enable-libmaxminddb=no/yes],[enable GeoIP libmaxminddb support])],
+	[enable_libmaxminddb=$enableval],
+	[enable_libmaxminddb=no])
+
+	AS_IF([test "x$enable_libmaxminddb" = "xyes"],
+	[
+		dnl see if the system provides it
+		has_system_libmaxminddb="no"
+		PKG_CHECK_MODULES([LIBMAXMINDDB], [libmaxminddb >= 1.4.3],
+		                  [has_system_libmaxminddb=yes])
+		AS_IF([test "x$has_system_libmaxminddb" = "xyes"],
+		[
+
+			AC_SUBST(LIBMAXMINDDB_LIBS)
+			AC_SUBST(LIBMAXMINDDB_CFLAGS)
+
+			GEOIP_MAXMIND_OBJECTS="geoip_maxmind.so"
+			AC_SUBST(GEOIP_MAXMIND_OBJECTS)
+		])
+	])
+])
+
diff --git a/configure b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for unrealircd 5.2.0.1.
+# Generated by GNU Autoconf 2.69 for unrealircd 6.0.1.1.
 #
 # Report bugs to <https://bugs.unrealircd.org/>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unrealircd'
 PACKAGE_TARNAME='unrealircd'
-PACKAGE_VERSION='5.2.0.1'
-PACKAGE_STRING='unrealircd 5.2.0.1'
+PACKAGE_VERSION='6.0.1.1'
+PACKAGE_STRING='unrealircd 6.0.1.1'
 PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
 PACKAGE_URL='https://unrealircd.org/'
 
@@ -626,6 +626,12 @@ ac_subst_vars='LTLIBOBJS
 LIBOBJS
 UNRLINCDIR
 IRCDLIBS
+GEOIP_MAXMIND_OBJECTS
+LIBMAXMINDDB_LIBS
+LIBMAXMINDDB_CFLAGS
+GEOIP_CLASSIC_OBJECTS
+GEOIP_CLASSIC_LIBS
+GEOIP_CLASSIC_CFLAGS
 URL
 PTHREAD_CFLAGS
 PTHREAD_LIBS
@@ -639,6 +645,8 @@ build_os
 build_vendor
 build_cpu
 build
+JANSSON_LIBS
+JANSSON_CFLAGS
 CARES_LIBS
 CARES_CFLAGS
 SODIUM_LIBS
@@ -746,19 +754,20 @@ with_docdir
 with_pidfile
 with_privatelibdir
 with_maxconnections
-enable_prefixaq
-with_showlistmodes
 with_no_operoverride
 with_operoverride_verify
 with_system_pcre2
 with_system_argon2
 with_system_sodium
 with_system_cares
+with_system_jansson
 enable_ssl
 enable_dynamic_linking
 enable_werror
 enable_asan
 enable_libcurl
+enable_geoip_classic
+enable_libmaxminddb
 '
       ac_precious_vars='build_alias
 host_alias
@@ -779,7 +788,13 @@ ARGON2_LIBS
 SODIUM_CFLAGS
 SODIUM_LIBS
 CARES_CFLAGS
-CARES_LIBS'
+CARES_LIBS
+JANSSON_CFLAGS
+JANSSON_LIBS
+GEOIP_CLASSIC_CFLAGS
+GEOIP_CLASSIC_LIBS
+LIBMAXMINDDB_CFLAGS
+LIBMAXMINDDB_LIBS'
 
 
 # Initialize some variables set by options.
@@ -1330,7 +1345,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures unrealircd 5.2.0.1 to adapt to many kinds of systems.
+\`configure' configures unrealircd 6.0.1.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1396,7 +1411,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unrealircd 5.2.0.1:";;
+     short | recursive ) echo "Configuration of unrealircd 6.0.1.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1406,7 +1421,6 @@ Optional Features:
   --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
   --enable-hardening      Enable compiler and linker options to frustrate
                           memory corruption exploits [yes]
-  --disable-prefixaq      Disable chanadmin (+a) and chanowner (+q) prefixes
   --enable-ssl=           enable ssl will check /usr/local/opt/openssl
                           /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg
                           /usr/sfw /usr/local /usr
@@ -1418,6 +1432,10 @@ Optional Features:
   --enable-asan           Enable address sanitizer and other debugging
                           options, not recommended for production servers!
   --enable-libcurl=DIR    enable libcurl (remote include) support
+  --enable-geoip-classic=no/yes
+                          enable GeoIP Classic support
+  --enable-libmaxminddb=no/yes
+                          enable GeoIP libmaxminddb support
 
 Optional Packages:
   --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
@@ -1447,7 +1465,6 @@ Optional Packages:
                           stored. Disable when building a package for a distro
   --with-maxconnections=size
                           Specify the max file descriptors to use
-  --with-showlistmodes    Specify whether modes are shown in /list
   --with-no-operoverride  Disable OperOverride
   --with-operoverride-verify
                           Require opers to invite themselves to +s/+p channels
@@ -1459,6 +1476,9 @@ Optional Packages:
                           library. Normally autodetected via pkg-config
   --without-system-cares  Use bundled version instead of system c-ares.
                           Normally autodetected via pkg-config.
+  --without-system-jansson
+                          Use bundled version instead of system jansson.
+                          Normally autodetected via pkg-config.
 
 Some influential environment variables:
   CC          C compiler command
@@ -1486,6 +1506,18 @@ Some influential environment variables:
   CARES_CFLAGS
               C compiler flags for CARES, overriding pkg-config
   CARES_LIBS  linker flags for CARES, overriding pkg-config
+  JANSSON_CFLAGS
+              C compiler flags for JANSSON, overriding pkg-config
+  JANSSON_LIBS
+              linker flags for JANSSON, overriding pkg-config
+  GEOIP_CLASSIC_CFLAGS
+              C compiler flags for GEOIP_CLASSIC, overriding pkg-config
+  GEOIP_CLASSIC_LIBS
+              linker flags for GEOIP_CLASSIC, overriding pkg-config
+  LIBMAXMINDDB_CFLAGS
+              C compiler flags for LIBMAXMINDDB, overriding pkg-config
+  LIBMAXMINDDB_LIBS
+              linker flags for LIBMAXMINDDB, overriding pkg-config
 
 Use these variables to override the choices made by `configure' or to help
 it to find libraries and programs with nonstandard names/locations.
@@ -1554,7 +1586,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unrealircd configure 5.2.0.1
+unrealircd configure 6.0.1.1
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1923,7 +1955,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by unrealircd $as_me 5.2.0.1, which was
+It was created by unrealircd $as_me 6.0.1.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2315,7 +2347,7 @@ orig_cflags="$CFLAGS"
 BUILDDIR_NOW="`pwd`"
 
 # Generation version number (e.g.: X in X.Y.Z)
-UNREAL_VERSION_GENERATION="5"
+UNREAL_VERSION_GENERATION="6"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_GENERATION $UNREAL_VERSION_GENERATION
@@ -2323,7 +2355,7 @@ _ACEOF
 
 
 # Major version number (e.g.: Y in X.Y.Z)
-UNREAL_VERSION_MAJOR="2"
+UNREAL_VERSION_MAJOR="0"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MAJOR $UNREAL_VERSION_MAJOR
@@ -2331,7 +2363,7 @@ _ACEOF
 
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR="0"
+UNREAL_VERSION_MINOR="1"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -5121,6 +5153,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 CFLAGS="$CFLAGS -funsigned-char"
 
 
+
 CFLAGS="$CFLAGS -Wall"
 
 ac_ext=c
@@ -5223,6 +5256,54 @@ 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
+
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror  -Wformat-nonliteral"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ax_cv_check_cflags__Werror___Wformat_nonliteral=yes
+else
+  ax_cv_check_cflags__Werror___Wformat_nonliteral=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___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 :
+  CFLAGS="$CFLAGS -Wformat-nonliteral"
+else
+  :
+fi
+
+  ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wduplicated-cond" >&5
 $as_echo_n "checking whether C compiler accepts -Wduplicated-cond... " >&6; }
 if ${ax_cv_check_cflags__Werror___Wduplicated_cond+:} false; then :
@@ -5312,6 +5393,55 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 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
+
+  { $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
+
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror  -Wparentheses"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ax_cv_check_cflags__Werror___Wparentheses=yes
+else
+  ax_cv_check_cflags__Werror___Wparentheses=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wparentheses" >&5
+$as_echo "$ax_cv_check_cflags__Werror___Wparentheses" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wparentheses" = xyes; then :
+  CFLAGS="$CFLAGS -Wparentheses"
+else
+  :
+fi
+
+  ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
 
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
@@ -5791,20 +5921,24 @@ 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 :
+  CFLAGS="$CFLAGS -Wno-tautological-compare -Wno-pragmas"
+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 whether C compiler accepts -Waddress" >&5
-$as_echo_n "checking whether C compiler accepts -Waddress... " >&6; }
-if ${ax_cv_check_cflags__Werror___Waddress+:} false; then :
+  { $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
 
   ax_check_save_flags=$CFLAGS
-  CFLAGS="$CFLAGS -Werror  -Waddress"
+  CFLAGS="$CFLAGS -Werror  -Wpragmas"
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -5817,19 +5951,19 @@ main ()
 }
 _ACEOF
 if ac_fn_c_try_compile "$LINENO"; then :
-  ax_cv_check_cflags__Werror___Waddress=yes
+  ax_cv_check_cflags__Werror___Wpragmas=yes
 else
-  ax_cv_check_cflags__Werror___Waddress=no
+  ax_cv_check_cflags__Werror___Wpragmas=no
 fi
 rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Waddress" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Waddress" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Waddress" = xyes; then :
-  CFLAGS="$CFLAGS -Wno-address"
+{ $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 :
+  no_pragmas=1
 else
-  :
+  no_pragmas=0
 fi
 
   ac_ext=c
@@ -5838,21 +5972,20 @@ 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
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wcast-function-type" >&5
-$as_echo_n "checking whether C compiler accepts -Wcast-function-type... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wcast_function_type+:} false; then :
+  { $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
 
   ax_check_save_flags=$CFLAGS
-  CFLAGS="$CFLAGS -Werror  -Wcast-function-type"
+  CFLAGS="$CFLAGS -Werror  -Wunknown-warning-option"
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -5865,19 +5998,19 @@ main ()
 }
 _ACEOF
 if ac_fn_c_try_compile "$LINENO"; then :
-  ax_cv_check_cflags__Werror___Wcast_function_type=yes
+  ax_cv_check_cflags__Werror___Wunknown_warning_option=yes
 else
-  ax_cv_check_cflags__Werror___Wcast_function_type=no
+  ax_cv_check_cflags__Werror___Wunknown_warning_option=no
 fi
 rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wcast_function_type" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wcast_function_type" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wcast_function_type" = xyes; then :
-  CFLAGS="$CFLAGS -Wno-cast-function-type"
+{ $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 :
+  unknown_warning_option=1
 else
-  :
+  unknown_warning_option=0
 fi
 
   ac_ext=c
@@ -5887,6 +6020,14 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
+if test "$unknown_warning_option" = "1"; then
+		CFLAGS="$CFLAGS -Wno-unknown-warning-option"
+else
+	if test "$no_pragmas" = "1"; then
+								CFLAGS="$CFLAGS -Wno-pragmas"
+	fi
+fi
+
 
 
 
@@ -6008,6 +6149,19 @@ $as_echo "#define HAVE_STRLNCAT /**/" >>confdefs.h
 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
+done
+
 
 for ac_func in getrusage
 do :
@@ -6448,29 +6602,6 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
-# Check whether --enable-prefixaq was given.
-if test "${enable_prefixaq+set}" = set; then :
-  enableval=$enable_prefixaq;
-else
-  enable_prefixaq=yes
-fi
-
-if test $enable_prefixaq = "yes"; then :
-
-$as_echo "#define PREFIX_AQ /**/" >>confdefs.h
-
-fi
-
-
-# Check whether --with-showlistmodes was given.
-if test "${with_showlistmodes+set}" = set; then :
-  withval=$with_showlistmodes; if test $withval = "yes"; then :
-
-$as_echo "#define LIST_SHOW_MODES /**/" >>confdefs.h
-
-fi
-fi
-
 
 # Check whether --with-no-operoverride was given.
 if test "${with_no_operoverride+set}" = set; then :
@@ -6524,6 +6655,14 @@ else
 fi
 
 
+# Check whether --with-system-jansson was given.
+if test "${with_system_jansson+set}" = set; then :
+  withval=$with_system_jansson;
+else
+  with_system_jansson=yes
+fi
+
+
 # Check whether --enable-ssl was given.
 if test "${enable_ssl+set}" = set; then :
   enableval=$enable_ssl;
@@ -6570,7 +6709,11 @@ $as_echo "not found" >&6; }
 	else
 		CRYPTOLIB="-lssl -lcrypto";
 		if test ! "$ssldir" = "/usr" ; then
-			LDFLAGS="$LDFLAGS -L$ssldir/lib";
+			if test -d "$ssldir/lib64" ; then
+				LDFLAGS="$LDFLAGS -L$ssldir/lib64";
+			else
+				LDFLAGS="$LDFLAGS -L$ssldir/lib";
+			fi
 						if test -f "$ssldir/bin/openssl"; then
 			    OPENSSLPATH="$ssldir/bin/openssl";
 			fi
@@ -7655,7 +7798,7 @@ fi
 
 if test "$has_system_cares" = "no"; then :
 
-cares_version="1.17.1"
+cares_version="1.17.2"
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting c-ares resolver library" >&5
 $as_echo "extracting c-ares resolver library" >&6; }
 cur_dir=`pwd`
@@ -7716,6 +7859,130 @@ cd $cur_dir
 
 fi
 
+has_system_jansson="no"
+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; }
+
+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
+  ($PKG_CONFIG --exists --print-errors "jansson >= 2.0.0") 2>&5
+  ac_status=$?
+  $as_echo "$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
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+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
+  ($PKG_CONFIG --exists --print-errors "jansson >= 2.0.0") 2>&5
+  ac_status=$?
+  $as_echo "$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
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        JANSSON_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "jansson >= 2.0.0" 2>&1`
+        else
+	        JANSSON_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "jansson >= 2.0.0" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$JANSSON_PKG_ERRORS" >&5
+
+	has_system_jansson=no
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "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; }
+	has_system_jansson=yes
+if test "x$PRIVATELIBDIR" != "x"; then :
+  rm -f "$PRIVATELIBDIR/"libjansson*
+fi
+fi
+fi
+
+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; }
+cur_dir=`pwd`
+cd extras
+rm -rf jansson-$jansson_version jansson
+if test "x$ac_cv_path_GUNZIP" = "x" ; then
+	tar xfz jansson.tar.gz
+else
+	cp jansson.tar.gz jansson.tar.gz.bak
+	gunzip -f jansson.tar.gz
+	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; }
+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
+CFLAGS="$save_cflags"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling jansson resolver library" >&5
+$as_echo "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; }
+$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 :
+  JANSSON_LIBS="`$ac_cv_path_PKGCONFIG --libs jansson.pc`"
+fi
+if test -z "$JANSSON_LIBS"; then :
+  JANSSON_LIBS="-L$PRIVATELIBDIR -ljansson"
+fi
+
+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
@@ -8338,10 +8605,268 @@ rm -f core conftest.err conftest.$ac_objext \
 		LIBS="$LIBS_SAVEDA"
 		CFLAGS="$CFLAGS_SAVEDA"
 
-		URL="url.o"
+				URL="url_curl.o"
+
+else
+
+				URL="url_unreal.o"
+
+fi
+
+
+
+	# Check whether --enable-geoip_classic was given.
+if test "${enable_geoip_classic+set}" = set; then :
+  enableval=$enable_geoip_classic; enable_geoip_classic=$enableval
+else
+  enable_geoip_classic=no
+fi
+
+
+	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; }
+
+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
+  ($PKG_CONFIG --exists --print-errors "geoip >= 1.6.0") 2>&5
+  ac_status=$?
+  $as_echo "$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
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+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
+  ($PKG_CONFIG --exists --print-errors "geoip >= 1.6.0") 2>&5
+  ac_status=$?
+  $as_echo "$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
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
 
 
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
 fi
+        if test $_pkg_short_errors_supported = yes; then
+	        GEOIP_CLASSIC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "geoip >= 1.6.0" 2>&1`
+        else
+	        GEOIP_CLASSIC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "geoip >= 1.6.0" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$GEOIP_CLASSIC_PKG_ERRORS" >&5
+
+	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; }
+	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; }
+	has_system_geoip_classic=yes
+		                   if test "x$PRIVATELIBDIR" != "x"; then :
+  rm -f "$PRIVATELIBDIR/"libGeoIP.*
+fi
+fi
+
+				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; }
+			cur_dir=`pwd`
+			cd extras
+									rm -rf GeoIP-$geoip_classic_version geoip-classic
+			if test "x$ac_cv_path_GUNZIP" = "x" ; then
+				tar xfz geoip-classic.tar.gz
+			else
+				cp geoip-classic.tar.gz geoip-classic.tar.gz.bak
+				gunzip -f geoip-classic.tar.gz
+				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; }
+			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; }
+			$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; }
+			$ac_cv_prog_MAKER install || exit 1
+						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 :
+  GEOIP_CLASSIC_LIBS="-L$PRIVATELIBDIR -lGeoIP"
+			        GEOIP_CLASSIC_CFLAGS="-I$cur_dir/extras/geoip-classic/include"
+fi
+			cd $cur_dir
+
+fi
+
+
+
+
+		GEOIP_CLASSIC_OBJECTS="geoip_classic.so"
+
+
+fi
+
+
+	# Check whether --enable-libmaxminddb was given.
+if test "${enable_libmaxminddb+set}" = set; then :
+  enableval=$enable_libmaxminddb; enable_libmaxminddb=$enableval
+else
+  enable_libmaxminddb=no
+fi
+
+
+	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; }
+
+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
+  ($PKG_CONFIG --exists --print-errors "libmaxminddb >= 1.4.3") 2>&5
+  ac_status=$?
+  $as_echo "$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
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+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
+  ($PKG_CONFIG --exists --print-errors "libmaxminddb >= 1.4.3") 2>&5
+  ac_status=$?
+  $as_echo "$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
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBMAXMINDDB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libmaxminddb >= 1.4.3" 2>&1`
+        else
+	        LIBMAXMINDDB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libmaxminddb >= 1.4.3" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBMAXMINDDB_PKG_ERRORS" >&5
+
+	as_fn_error $? "Package requirements (libmaxminddb >= 1.4.3) were not met:
+
+$LIBMAXMINDDB_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+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;}
+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.
+
+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.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+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; }
+	has_system_libmaxminddb=yes
+fi
+		if test "x$has_system_libmaxminddb" = "xyes"; then :
+
+
+
+
+
+			GEOIP_MAXMIND_OBJECTS="geoip_maxmind.so"
+
+
+fi
+
+fi
+
 
 UNRLINCDIR="`pwd`/include"
 
@@ -8358,7 +8883,7 @@ fi
 
 
 
-ac_config_files="$ac_config_files Makefile src/Makefile src/modules/Makefile src/modules/chanmodes/Makefile src/modules/usermodes/Makefile src/modules/snomasks/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/third/Makefile extras/unrealircd-upgrade-script unrealircd"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -8866,7 +9391,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by unrealircd $as_me 5.2.0.1, which was
+This file was extended by unrealircd $as_me 6.0.1.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -8929,7 +9454,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-unrealircd config.status 5.2.0.1
+unrealircd config.status 6.0.1.1
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -9056,7 +9581,6 @@ do
     "src/modules/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/Makefile" ;;
     "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/snomasks/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/snomasks/Makefile" ;;
     "src/modules/extbans/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/extbans/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" ;;
diff --git a/configure.ac b/configure.ac
@@ -7,7 +7,7 @@ dnl src/windows/unrealinst.iss
 dnl doc/Config.header
 dnl src/version.c.SH
 
-AC_INIT([unrealircd], [5.2.0.1], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [6.0.1.1], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
 AC_CONFIG_SRCDIR([src/ircd.c])
 AC_CONFIG_HEADER([include/setup.h])
 AC_CONFIG_AUX_DIR([autoconf])
@@ -26,15 +26,15 @@ BUILDDIR_NOW="`pwd`"
 
 dnl Calculate the versions. Perhaps the use of expr is a little too extravagant
 # Generation version number (e.g.: X in X.Y.Z)
-UNREAL_VERSION_GENERATION=["5"]
+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=["2"]
+UNREAL_VERSION_MAJOR=["0"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MAJOR], [$UNREAL_VERSION_MAJOR], [Major version number (e.g.: Y for X.Y.Z)])
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR=["0"]
+UNREAL_VERSION_MINOR=["1"]
 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
@@ -189,18 +189,26 @@ CFLAGS="$CFLAGS -funsigned-char"
 
 dnl Compiler -W checks...
 
+dnl == ADD THESE WARNINGS ==
+
 dnl We should be able to turn this on unconditionally:
 CFLAGS="$CFLAGS -Wall"
 
 dnl More warnings (if the compiler supports it):
 check_cc_flag([-Wextra], [CFLAGS="$CFLAGS -Wextra"])
 check_cc_flag([-Waggregate-return], [CFLAGS="$CFLAGS -Waggregate-return"])
+check_cc_flag([-Wformat-nonliteral], [CFLAGS="$CFLAGS -Wformat-nonliteral"])
+
 dnl The following few are more experimental, if they have false positives we'll have
 dnl to disable them:
 dnl Can't use this, too bad: check_cc_flag([-Wlogical-op], [CFLAGS="$CFLAGS -Wlogical-op"])
 check_cc_flag([-Wduplicated-cond], [CFLAGS="$CFLAGS -Wduplicated-cond"])
 check_cc_flag([-Wduplicated-branches], [CFLAGS="$CFLAGS -Wduplicated-branches"])
 
+check_cc_flag([-Wparentheses], [CFLAGS="$CFLAGS -Wparentheses"])
+
+dnl == REMOVE THESE WARNINGS ==
+
 dnl And now to filter out certain warnings:
 dnl [!] NOTE REGARDING THE check_cc_flag used by these:
 dnl We check for the -Woption even though we are going to use -Wno-option.
@@ -247,12 +255,31 @@ 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 one fails with ircstrdup(var, staticstring)
-dnl Shame we have to turn it off completely...
-check_cc_flag([-Waddress], [CFLAGS="$CFLAGS -Wno-address"])
-
-dnl This one breaks our TO_INTFUNC() that is used in m_tkl for tkl_typetochar
-check_cc_flag([-Wcast-function-type], [CFLAGS="$CFLAGS -Wno-cast-function-type"])
+dnl Yeah this old clang version is a bit problematic
+dnl (ships in Ubuntu 16.04 for example)
+dnl -Wtautological-compare has false positives
+dnl -Wno-pragmas is needed, despite -Wno-unknown-warning-option
+AS_IF([$CC --version | grep -q "clang version 3."],
+        [CFLAGS="$CFLAGS -Wno-tautological-compare -Wno-pragmas"])
+
+dnl This one MUST be LAST!!
+dnl It disables -Wsomeunknownoption being an error. Which is needed for
+dnl the pragma's in individual files to selectively disable some warnings
+dnl on clang/gcc (that may exist in eg gcc but not in clang or vice versa).
+check_cc_flag([-Wpragmas], [no_pragmas=1],[no_pragmas=0])
+check_cc_flag([-Wunknown-warning-option], [unknown_warning_option=1], [unknown_warning_option=0])
+
+if test "$unknown_warning_option" = "1"; then
+	dnl This is the best option
+	CFLAGS="$CFLAGS -Wno-unknown-warning-option"
+else
+	if test "$no_pragmas" = "1"; then
+		dnl This is a fallback needed for older gcc/clang, it also
+		dnl disables several other useful warnings/errors related
+		dnl to pragma's unfortunately.
+		CFLAGS="$CFLAGS -Wno-pragmas"
+	fi
+fi
 
 dnl End of -W... compiler checks.
 
@@ -352,6 +379,8 @@ AC_CHECK_FUNCS(strlcat,
 	AC_DEFINE([HAVE_STRLCAT], [], [Define if you have strlcat]))
 AC_CHECK_FUNCS(strlncat,
 	AC_DEFINE([HAVE_STRLNCAT], [], [Define if you have strlncat]))
+AC_CHECK_FUNCS(strlncpy,
+	AC_DEFINE([HAVE_STRLNCPY], [], [Define if you have strlncpy]))
 
 AC_CHECK_FUNCS([getrusage],
 	[AC_DEFINE([GETRUSAGE_2], [], [Define if you have getrusage])],
@@ -492,17 +521,6 @@ AC_ARG_WITH(maxconnections, [AS_HELP_STRING([--with-maxconnections=size], [Speci
 	[ac_fd=0])
 AC_DEFINE_UNQUOTED([MAXCONNECTIONS_REQUEST], [$ac_fd], [Set to the maximum number of connections you want])
 
-AC_ARG_ENABLE([prefixaq],
-	[AS_HELP_STRING([--disable-prefixaq],[Disable chanadmin (+a) and chanowner (+q) prefixes])],
-	[],
-	[enable_prefixaq=yes])
-AS_IF([test $enable_prefixaq = "yes"],
-	[AC_DEFINE([PREFIX_AQ], [], [Define if you want +a/+q prefixes])])
-
-AC_ARG_WITH(showlistmodes,
-	[AS_HELP_STRING([--with-showlistmodes], [Specify whether modes are shown in /list])],
-	[AS_IF([test $withval = "yes"],
-		[AC_DEFINE([LIST_SHOW_MODES], [], [Define if you want modes shown in /list])])])
 AC_ARG_WITH(no-operoverride, [AS_HELP_STRING([--with-no-operoverride], [Disable OperOverride])],
 	[AS_IF([test $withval = "yes"],
 		[AC_DEFINE([NO_OPEROVERRIDE], [], [Define if you want OperOverride disabled])])])
@@ -513,6 +531,7 @@ AC_ARG_WITH(system-pcre2, [AS_HELP_STRING([--without-system-pcre2], [Use the sys
 AC_ARG_WITH(system-argon2, [AS_HELP_STRING([--without-system-argon2], [Use bundled version instead of system argon2 library. Normally autodetected via pkg-config])], [], [with_system_argon2=yes])
 AC_ARG_WITH(system-sodium, [AS_HELP_STRING([--without-system-sodium], [Use bundled version instead of system sodium library. Normally autodetected via pkg-config])], [], [with_system_sodium=yes])
 AC_ARG_WITH(system-cares, [AS_HELP_STRING([--without-system-cares], [Use bundled version instead of system c-ares. Normally autodetected via pkg-config.])], [], [with_system_cares=yes])
+AC_ARG_WITH(system-jansson, [AS_HELP_STRING([--without-system-jansson], [Use bundled version instead of system jansson. Normally autodetected via pkg-config.])], [], [with_system_jansson=yes])
 CHECK_SSL
 CHECK_SSL_CTX_SET1_CURVES_LIST
 CHECK_SSL_CTX_SET_MIN_PROTO_VERSION
@@ -697,7 +716,7 @@ AS_IF([test "$has_system_cares" = "no"], [
 dnl REMEMBER TO CHANGE WITH A NEW C-ARES RELEASE!
 dnl NOTE: when changing this here, ALSO change it in extras/curlinstall
 dnl       and in the comment in this file around line 400!
-cares_version="1.17.1"
+cares_version="1.17.2"
 AC_MSG_RESULT(extracting c-ares resolver library)
 cur_dir=`pwd`
 cd extras
@@ -763,10 +782,65 @@ AC_SUBST(CARES_LIBS)
 cd $cur_dir
 ])
 
+dnl Use system jansson when available, unless --without-system-jansson
+has_system_jansson="no"
+AS_IF([test "x$with_system_jansson" = "xyes"],[
+PKG_CHECK_MODULES([JANSSON], [jansson >= 2.0.0],[has_system_jansson=yes
+AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libjansson*])],[has_system_jansson=no])])
+
+AS_IF([test "$has_system_jansson" = "no"],[
+dnl REMEMBER TO CHANGE WITH A NEW JANSSON RELEASE!
+jansson_version="2.13.1"
+AC_MSG_RESULT(extracting jansson library)
+cur_dir=`pwd`
+cd extras
+dnl remove old jansson directory to force a recompile...
+dnl and remove its installation prefix just to clean things up.
+rm -rf jansson-$jansson_version jansson
+if test "x$ac_cv_path_GUNZIP" = "x" ; then
+	tar xfz jansson.tar.gz
+else
+	cp jansson.tar.gz jansson.tar.gz.bak
+	gunzip -f jansson.tar.gz
+	cp jansson.tar.gz.bak jansson.tar.gz
+	tar xf jansson.tar
+fi
+AC_MSG_RESULT(compiling jansson library)
+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
+CFLAGS="$save_cflags"
+AC_MSG_RESULT(compiling jansson resolver library)
+$ac_cv_prog_MAKER || exit 1
+AC_MSG_RESULT(installing jansson resolver library)
+$ac_cv_prog_MAKER install || exit 1
+JANSSON_CFLAGS="-I$cur_dir/extras/jansson/include"
+AC_SUBST(JANSSON_CFLAGS)
+JANSSON_LIBS=
+dnl See c-ares's compilation section for more info on this hack.
+dnl ensure that we're linking against the bundled version
+dnl (we only reach this code if linking against the bundled version is desired).
+AS_IF([test -n "$ac_cv_path_PKGCONFIG"],
+       [JANSSON_LIBS="`$ac_cv_path_PKGCONFIG --libs jansson.pc`"])
+dnl ^^^ FIXME FIXME this is likely incorrect the .pc etc
+dnl For when pkg-config isn't available
+AS_IF([test -z "$JANSSON_LIBS"],
+       [JANSSON_LIBS="-L$PRIVATELIBDIR -ljansson"])
+AC_SUBST(JANSSON_LIBS)
+cd $cur_dir
+])
+
+
 AX_PTHREAD()
 
 CHECK_LIBCURL
 
+CHECK_GEOIP_CLASSIC
+
+CHECK_LIBMAXMINDDB
+
 UNRLINCDIR="`pwd`/include"
 
 dnl Moved to the very end to ensure it doesn't affect any libs or tests.
@@ -789,7 +863,6 @@ AC_CONFIG_FILES([Makefile
 	src/modules/Makefile
 	src/modules/chanmodes/Makefile
 	src/modules/usermodes/Makefile
-	src/modules/snomasks/Makefile
 	src/modules/extbans/Makefile
 	src/modules/third/Makefile
 	extras/unrealircd-upgrade-script
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,7 +7,7 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 5.2.0.1
+                                for UnrealIRCd 6.0.1.1
                                     
 This program will help you to compile your IRC server, and ask you
 questions regarding the compile-time settings of it during the process. 
@@ -16,15 +16,15 @@ A short installation guide is available online at:
 https://www.unrealircd.org/docs/Installing_from_source
 
 Full documentation is available at:
-https://www.unrealircd.org/docs/UnrealIRCd_5_documentation
+https://www.unrealircd.org/docs/UnrealIRCd_6_documentation
 
 --------------------------------------------------------------------------------------
 
 The full release notes are available in doc/RELEASE-NOTES.md
 For easier viewing, check out the latest online release notes at:
-https://github.com/unrealircd/unrealircd/blob/unreal52/doc/RELEASE-NOTES.md
+https://github.com/unrealircd/unrealircd/blob/unreal60_dev/doc/RELEASE-NOTES.md
 
-UnrealIRCd 5 is compatible with the following services:
+UnrealIRCd 6 is compatible with the following services:
 * anope with the "unreal4" protocol module - version 2.0.7 or higher required!
 * atheme with the "unreal4" protocol module - tested with version 7.2.9
 
diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md
@@ -1,1000 +1,254 @@
-UnrealIRCd 5.2.0.1 Release Notes
-=================================
-
-About 5.2.0.1
---------------
-5.2.0.1 fixes an issue with spamfilter that was present in 5.2.0.
-In channels spamfilters were processed for type ```p``` instead of ```c```.
-Existing 5.2.0 users on *NIX can upgrade without restart by running
-```./unrealircd hot-patch wrongspamfilter520```
-
-UnrealIRCd 5.2.0 is out!
--------------------------
-
-This is UnrealIRCd 5.2.0, a release with lots of new features.
-The two main new features are: an improved and more flexible anti-flood block
-and channel history which can now be stored encrypted on disk and allows
-clients to fetch hundreds/thousands of lines.
-
-Upgrading and the 5.0.x series
--------------------------------
-UnrealIRCd 5.2.0 is the direct successor to 5.0.9/5.0.9.1.
-There will be [no further 5.0.x releases](https://www.unrealircd.org/docs/FAQ#About_the_new_5.2.x_series),
-in particular there will be no 5.0.10.
-
-Only four bugs that affect a limited number of people/networks were fixed.
-UnrealIRCd 5.2.0 is mostly a feature release.
-Admins wishing to take a conservative approach don't need to rush an
-upgrade from 5.0.x to 5.2.0, they can wait for a 5.2.1 or 5.2.2 release.
-
-If you are upgrading from 5.0.9(.1) to 5.2.0 then feel free to try the new
-```./unrealircd upgrade``` command.
-
-The only configuration change is in the set::anti-flood block (as explained
-further down under *Enhancements*). When starting UnrealIRCd will give you
-clear instructions if anything needs to be changed (and what).
-This process is really minor, the server will usually tell you to just
-delete a few old lines from the configuration file.
-
-Enhancements
--------------
-* The set::anti-flood block has been redone so you can have different limits
-  for *unknown-users* and *known-users*.
-  * As a reminder, by default, *known-users* are users who are identified
-    to services OR are on an IP that has been connected for over 2 hours
-    in the past X days. The exact definition of "known-users" is in the
-    [security-group block](https://www.unrealircd.org/docs/Security-group_block).
-  * See [here](https://www.unrealircd.org/docs/Anti-flood_settings)
-    for more information on the layout of the new set::anti-flood block.
-  * All violations of target-flood, nick-flood, join-flood, away-flood,
-    invite-flood, knock-flood, max-concurrent-conversations are now
-    reported to opers with the snomask ```f``` (flood).
-* Add support for database encryption. The way this works
-  is that you define an encryption password in a
-  [secret { } block](https://www.unrealircd.org/docs/Secret_block).
-  Then from the various modules you can refer to this secret
-  block, from
-  [set::reputation::db-secret](https://www.unrealircd.org/docs/Set_block#set::reputation),
-  [set::tkldb::db-secret](https://www.unrealircd.org/docs/Set_block#set::tkldb)
-  and [set::channeldb::db-secret](https://www.unrealircd.org/docs/Set_block#set::channeldb).
-  This way you can encrypt the reputation, TKL and channel
-  database for increased privacy.
-* Add optional support for
-  [persistent channel history](https://www.unrealircd.org/docs/Set_block#Persistent_channel_history):
-  * This stores channel history on disk for channels that have
-    both ```+H``` and ```+P``` set.
-  * If you enable this then we ALWAYS require you to set an
-    encryption password, as we do not allow storing of
-    channel history in plain text.
-  * If you enable the option, then the history is stored in
-    ```data/history/``` in individual .db files. No channel
-    names are visible in the filenames for optimal privacy.
-  * See [Persistent channel history](https://www.unrealircd.org/docs/Set_block#Persistent_channel_history)
-    on how to enable this. By default it is off.
-* Add support for IRCv3
-  [draft/chathistory](https://ircv3.net/specs/extensions/chathistory).
-* The maximums for channel mode ```+H``` have been raised and are now
-  different for ```+r``` (registered) and ```-r``` channels. For unregistered
-  channels the limit is now 200 lines / 31 days. For registered channels
-  the limit is 5000 lines / 31 days. The old limit for both was 200 lines / 7 days.
-  These maximums can be changed in the now slightly different
-  [set::history::channel::max-storage-per-channel](https://www.unrealircd.org/docs/Set_block#set::history)
-  block.
-* Add c-ares and libsodium version output to boot screen and /VERSION.
-* WHOX now supports displaying the
-  [reputation score](https://www.unrealircd.org/docs/Reputation_score).
-  If you are an IRCOp then you can use e.g. ```WHO * %cuhsnfmdaRr```.
-* Add ability to [spamfilter](https://www.unrealircd.org/docs/Spamfilter)
-  message tags via the new ```T``` target. Right now it would be unusual
-  to use this, but some day when we have more
-  [message tags](https://www.unrealircd.org/docs/Message_tags) it
-  may come in handy.
-* Support [```+draft/reply```](https://ircv3.net/specs/client-tags/reply) IRCv3
-  client tag. Can be used by bots (and others) to indicate to what message
-  people are replying to. This module, reply-tag, is loaded by default.
-* Send [```draft/bot```](https://ircv3.net/specs/extensions/bot-mode) IRCv3
-  message tag if the user has mode ```+B``` set.
-* [Websockets](https://www.unrealircd.org/docs/WebSocket_support):
-  add support for clients to negotiate an explicit type via
-  ```Sec-WebSocket-Protocol```, instead of only the default type from
-  [listen::websocket::type](https://www.unrealircd.org/docs/WebSocket_support#2._Enable_websocket_on_the_port).
-  This is based on an IRCv3 websocket draft specification.
-  Note that UnrealIRCd refuses type text if your configuration allows
-  non-UTF8 characters in channel or nick names because it would lead
-  to security and compatibility issues.
-* [set::restrict-commands](https://www.unrealircd.org/docs/Set_block#set::restrict-commands):
-  new option *exempt-tls* which allows SSL/TLS users to bypass a restriction.
-
-Fixes
-------
-* Server squiting the wrong side. Often harmless, but when (re)connecting
-  rapidly to multiple servers with autoconnect this could cause the
-  network to fall apart.
-* Forbid using [extended server bans](https://www.unrealircd.org/docs/Extended_server_bans)
-  in ZLINE/GZLINE since they won't work there.
-* Extended server ban ```~a:accname``` was not working for shun, and only
-  partially working for kline/gline.
-* More accurate /ELINE error message.
-
-Changed
---------
-* Channel mode ```+H``` always showed time in minutes (```m```) until now.
-  From now on it will show it in minutes (```m```), hours (```h```) or
-  days (```d```) depending on the actual value. Eg ```+H 50:7d```.
-* If you ran ```./unrealircd stop``` we used to wait only 1 second.
-  From now on we will wait up to 10 seconds max. This gives UnrealIRCd
-  plenty of time to write database files.
-* If you have zero [log blocks](https://www.unrealircd.org/docs/Log_block)
-  then we already automatically logged errors to ```ircd.log```.
-  From now on we will log everything (not only errors) to that file.
-
-Removed
---------
-* Version check for curl and openssl as nowadays they have ABI guarantees.
-
-Module coders / Developers
----------------------------
-* New UnrealDB API and disk format, see
-  https://www.unrealircd.org/docs/Dev:UnrealDB
-* We now use libsodium for file encryption routines as well
-  as some helpers to lock/clear passwords in memory.
-* Updated ```HOOKTYPE_LOCAL_NICKCHANGE``` and
-  ```HOOKTYPE_REMOTE_NICKCHANGE``` to include an
-  ```MessageTag *mtags``` argument in the middle.
-  You can use ```#if UNREAL_VERSION_TIME>=202115``` to detect this.
-* Updated channel mode ```conv_param``` function to
-  include a ```Channel *channel``` argument at the end.
-  You can use ```#if UNREAL_VERSION_TIME>=202120``` to detect this.
-* New: ```ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, priority);```.
-  This can be used for modules to indicate they wish to be unloaded
-  before or after others. It is used by for example the channel
-  and history modules so they can save their databases before
-  channel mode modules or other modules get unloaded.
-* New CAP [```draft/chathistory```](https://ircv3.net/specs/extensions/chathistory).
-  If a client REQ's this CAP then UnrealIRCd won't send history on-join as
-  it assumes the client will fetch it when they feel the need for it.
-* New informative CAP:
-  [```unrealircd.org/history-backend```](https://www.unrealircd.org/history-backend)
-
-Reminder: UnrealIRCd 4 is no longer supported
-----------------------------------------------
-
-UnrealIRCd 4.x is [no longer supported](https://www.unrealircd.org/docs/UnrealIRCd_4_EOL).
-Admins must [upgrade to UnrealIRCd 5](https://www.unrealircd.org/docs/Upgrading_from_4.x).
-
-UnrealIRCd 5.0.9.1
--------------------
-The only change between 5.0.9 and 5.0.9.1 is:
-* Build improvements on *NIX (faster compiling and lower memory requirements)
-* Windows version is unchanged and still 5.0.9
-
-UnrealIRCd 5.0.9
------------------
-The 5.0.9 release comes with several nice feature enhancements. There are no major bug fixes.
-
-Enhancements:
-* Changes to the "Client connecting" notice on IRC (for IRCOps):
-  * The format changed slightly, instead of ```{clients}``` it
-    now shows ```[class: clients]```
-  * SSL/TLS information is still shown via ```[secure]```
-  * New: ```[reputation: NNN]``` to show the current
-    [reputation score](https://www.unrealircd.org/docs/Reputation_score)
-  * New: ```[account: abcdef]``` to show the services account,
-    but only if [SASL](https://www.unrealircd.org/docs/SASL) was used.
-* In the log file the format also changed slightly:
-  * IP information is now added as ```[127.0.0.1]``` in both the
-    connect and disconnect log messages.
-  * The vhost is logged as ```[vhost: xyz]``` instead of ```[VHOST xyz]```
-  * All the other values are now logged as well on-connect,
-    similar to the "Client connecting" notice, so: secure, reputation,
-    account (if applicable).
-* New option [allow::global-maxperip](https://www.unrealircd.org/docs/Allow_block):
-  this imposes a global (network-wide) restriction on the number of
-  connections per IP address.
-  If you don't have a global-maxperip setting in the allow block then it
-  will default to maxperip plus one. So, if you currently have an
-  allow::maxperip of 3 then global-maxperip will be 4.
-* [Handshake delay](https://www.unrealircd.org/docs/Set_block#set::handshake-delay)
-  is automatically disabled for users that are exempt from blacklist checking.
-* Always exempt 127.* from gline, kline, etc.
-* You can now have dated logfiles thanks to strftime formatting.
-  For example ```log "ircd.%Y-%m-%d.log" { }``` will create a log
-  file like called ircd.2020-01-31.log, a new one every day.
-* The Windows build now supports TLSv1.3 too.
+UnrealIRCd 6.0.1.1
+===================
+If you are already running UnrealIRCd 6 then read below on the 
+changes between 6.0.0 and 6.0.1(.1). Otherwise, jump straight to the
+[summary about UnrealIRCd 6](#Summary) to learn more about UnrealIRCd 6.
 
 Fixes:
-* Windows: some warnings and error messages on boot were previously
-  missing.
-
-Changes:
-* Add ```doc/KEYS``` which contains the public key(s) used to sign UnrealIRCd releases
-* The options set::anti-flood::unknown-flood-* have been renamed and
-integrated in a new block called
-[set::anti-flood::handshake-data-flood](https://www.unrealircd.org/docs/Set_block#set::anti-flood::handshake-data-flood).
-The ban-action can now also be changed. Note that almost nobody will have to
-change this setting since it has a good default.
-* On *NIX bump the default maximum connections from 8192 to 16384.
-That is, when in "auto" mode, which is like for 99% of the users.
-Note that the system may still limit the actual number of connections
-to a lower value, epending on the value of ```ulimit -n -H```.
-
-UnrealIRCd 5.0.8
------------------
-
-The main purpose of this release is to enhance the
-[reputation](https://www.unrealircd.org/docs/Reputation_score)
-functionality. There have also been some other changes and minor
-bug fixes. For more information, see below.
-
-Enhancements:
-* Support for [security groups](https://www.unrealircd.org/docs/Security-group_block),
-  of which four groups always exist by default: known-users, unknown-users,
-  tls-users and tls-and-known-users.
-* New extended ban ```~G:securitygroupname```. Typical usage would be
-  ```MODE #chan +b ~G:unknown-users``` which will ban all users from the
-  channel that are not identified to services and have a reputation
-  score below 25 (by default). The exact settings can be tweaked in the
-  [security group block](https://www.unrealircd.org/docs/Security-group_block).
-* The reputation command (IRCOp-only) has been extended to make it
-  easier to look for potential troublemakers:
-  * ```REPUTATION Nick``` shows reputation about the nick name
-  * ```REPUTATION IP``` shows reputation about the IP address
-  * ```REPUTATION #channel``` lists users in channel with their reputation score
-  * ```REPUTATION <NN``` lists users with reputation scores below value NN
-* Only send the first 1000 matches on ```STATS gline``` or a
-  similar command. This to prevent the IRCOp from being flooded off.
-  This value can be changed via
-  [set::max-stats-matches](https://www.unrealircd.org/docs/Set_block#set::max-stats-matches)
-* Warn when the SSL/TLS server certificate is expired or expires soon
-  (within 7 days).
-* New option allow::options::reject-on-auth-failure if you want to
-  stop matching on a passworded allow block, see the
-  [allow password documentation](https://www.unrealircd.org/docs/Allow_block#password)
-  for more information. Note that most people won't use this.
-
-Fixes:
-* The ```WHO``` command searched on nick name even if it was told
-  to search on a specific account name via WHOX options.
-* Some typos in the Config script and a warning
-* Counting clients twice in some circumstances
-
-Changes:
-* Support for $(DESTDIR) in 'make install' if packaging for a distro
-* Mention the ban reason in Q-line server notices
-* Add self-test to module manager and improve the error message in case
-  the IRCd source directory does not exist.
-* Print out a more helpful error if you run the unrealircd binary
-  rather than the unrealircd script with an argument like 'mkpasswd' etc.
-* On *NIX create a symlink 'source' to the UnrealIRCd source
-
-Module coders / Developers:
-* The [Doxygen module API docs](https://www.unrealircd.org/api/5/index.html)
-  have been improved, in particular the 
-  [Hook API](https://www.unrealircd.org/api/5/group__HookAPI.html)
-  is now 100% documented.
-
-UnrealIRCd 5.0.7
------------------
-
-UnrealIRCd 5.0.7 consists mainly of fixes for the 5.x stable series,
-with some minor enhancements.
-
-Enhancements:
-* Add support for ```estonian-utf8```, ```latvian-utf8``` and
-  ```lithuanian-utf8``` in
-  [set::allowed-nickchars](https://www.unrealircd.org/docs/Nick_Character_Sets)
-* Add [message tags](https://www.unrealircd.org/docs/Message_tags)
-  to ```PONG``` to help fix timestamp issues in KiwiIRC.
-* Dutch helpop file (conf/help/help.nl.conf)
-
-Fixes:
-* When having multiple text bans (```+b ~T:censor```), these caused an empty
-  message.
-* Text bans are now no longer bypassed by voiced users (```+v```).
-* [Websockets](https://www.unrealircd.org/docs/WebSocket_support) that used
-```labeled-response``` sometimes received multiple IRC messages in one
-websocket packet.
-* The reputation score of [WEBIRC users](https://www.unrealircd.org/docs/WebIRC_block)
-  was previously the score of the WEBIRC IP rather than the end-user IP.
-* ```STATS badword``` was not working.
-* When setting a very high channel limit, it showed a weird MODE ```+l``` value.
-* The ```LINKS``` command worked, even when disabled via
-  ```hideserver::disable-links``` in the optional hideserver module.
-* In some cases ```WHO``` did not show your own entry, such as when
-  searching on account name, which was confusing.
-* Memory leak when repeatedly using ```./unrealircd reloadtls``` or
-  ```/REHASH -tls```.
-
-Module coders / Developers:
-* No changes, only some small additions to the
-[Doxygen module API docs](https://www.unrealircd.org/api/5/index.html)
-
-UnrealIRCd 5.0.6
------------------
-
-UnrealIRCd 5.0.6 is a small maintenance release for the stable 5.x series.
-For existing 5.x users there is probably little reason to upgrade.
+* In 6.0.1.1: extended bans were not properly synced between U5 and U6.
+  This caused missing extended bans on the U5 side (MODE was working OK,
+  this only happened when linking servers)
+* Text extbans did not have any effect (`+b ~text:censor:*badword*`)
+* Timed bans were not expiring if all servers on the network were on U6
+* Channel mode `+f` could place a timed extban with `~t` instead of `~time`
+* Crash when unloading any of the vhoaq modules at runtime
+* `./unrealircd upgrade` not working on FreeBSD and not with self-compiled cURL
+* Some log messages being wrong (`CHGIDENT`, `CHGNAME`)
+* Remove confusing high cpu load warning
 
 Enhancements:
-* Spanish help conf was added (conf/help/help.es.conf)
-
-Fixes:
-* History playback on join was not obeying the limits from
-  [set::history::channel::playback-on-join](https://www.unrealircd.org/docs/Set_block#set::history).
-  Note that if you want to see more lines, there is the ```HISTORY```
-  command. For more information on the different ways to retrieve history, see
-  [Channel History](https://www.unrealircd.org/docs/Channel_history)
-* [Spamfilter](https://www.unrealircd.org/docs/Spamfilter) with the
-  ['tempshun' action](https://www.unrealircd.org/docs/Actions) was letting
-  the message through.
-* In very specific circumstances a ```REHASH -tls``` would cause outgoing
-  linking to fail with the error "called a function you should not call".
-* Crash if empty [set::cloak-method](https://www.unrealircd.org/docs/Set_block#set::cloak-method)
-* Issues with labeled-response on websockets (partial fix)
+* Error on unknown snomask in set::snomask-on-oper and oper::snomask.
+* TKL add/remove/expire messages now show `[duration: 60m]` instead of
+  the `[expires: ZZZ GMT]` string since that is what people are more
+  interested in and is not affected by time zones. The format in all the
+  3 notices is also consistent now.
 
-Module coders / Developers:
-* In ```RPL_ISUPPORT``` we now announce ```BOT=B``` to indicate the user mode and
-  ```WHO``` status flag for bots.
-* ```HOOKTYPE_ACCOUNT_LOGIN``` is called for remote users too now (also on server syncs)
-* Send ```RPL_LOGGEDOUT``` when logging out of services account
-* Fix double batch in message tags when using both labeled-response
-  and the ```HISTORY``` command
-
-UnrealIRCd 5.0.5.1
--------------------
-
-5.0.5.1 reverts the previously introduced UTF8 Spamfilter support.
-Unfortunately we had to do this, due to a bug in the PCRE2 regex library
-that caused a freeze / infinite loop with certain regexes and text.
-
-UnrealIRCd 5.0.5
+UnrealIRCd 6.0.0
 -----------------
 
-This 5.0.5 release mainly focuses on new features, while also fixing a few bugs.
-
-Fixes:
-* [except ban { }](https://www.unrealircd.org/docs/Except_ban_block)
-  without 'type' was not exempting from gline.
-* Channel mode ```+L #forward``` and ```+k key```: should forward
-  on wrong key, but was also redirecting on correct key.
-* Crash on 32-bit machines in tkldb (on start or rehash)
-* Crash when saving channeldb when a parameter channel mode is combined
-  with ```+P``` and that module was loaded after channeldb. This may
-  happen if you use 3rd party modules that add parameter channel modes.
-
-Enhancements:
-* [antimixedutf8](https://www.unrealircd.org/docs/Set_block#set::antimixedutf8)
-  has been improved to detect CJK and other scripts and this will now
-  catch more mixed UTF8 spam. Note that, if you previously manually
-  set the score very tight (much lower than the default of 10) then you
-  may have to increase it a bit, or not, depending on your network.
-* Support for IRCv3 [+typing clienttag](https://ircv3.net/specs/client-tags/typing.html),
-  which adds "user is typing" support to channels and PM (if the client
-  supports it).
-* New flood countermeasure,
-  [set::anti-flood::target-flood](https://www.unrealircd.org/docs/Set_block#set%3A%3Aanti-flood%3A%3Atarget-flood),
-  which limits flooding to channels and users. This is only meant as a
-  filter for high rate floods. You are still encouraged to use
-  [channel mode +f](https://www.unrealircd.org/docs/Anti-flood_features#Channel_mode_f)
-  in channels which give you more customized and fine-grained options
-  to deal with low- and medium-rate floods.
-* If a chanop /INVITEs someone, it will now override ban forwards
-  such as ```+b ~f:#forward:*!*@*```.
-
-Changes:
-* We now do parallel builds by default (```make -j4```) within ./Config,
-  unless the ```$MAKE``` or ```$MAKEFLAGS``` environment variable is set.
-* [set::restrict-commands](https://www.unrealircd.org/docs/Set_block#set%3A%3Arestrict-commands):
-  * The ```disable``` option is now removed as it is implied. In other words: if
-    you want to disable a command, then simply don't use ```connect-delay```.
-  * You can now have a block without ```connect-delay``` but still make
-    users bypass the restriction with ```exempt-identified``` and/or
-    ```exempt-reputation-score```. Previously this was not possible.
-* We now give an error when an IRCOp tries to place an *LINE that already
-  exists. (Previously we sometimes replaced the existing *LINE and other
-  times we did not)
-* Add Polish HELPOP (help.pl.conf)
-
-Module coders / Developers:
-* Breaking API change in ```HOOKTYPE_CAN_SEND_TO_USER``` and
-  ```HOOKTYPE_CAN_SEND_TO_CHANNEL```: the final argument has changed
-  from ```int notice``` to ```SendType sendtype```, which is an
-  enum, since we now have 3 message options (PRIVMSG, NOTICE, TAGMSG).
-
-UnrealIRCd 5.0.4
-------------------
-
-This new 5.0.4 version fixes quite a number of bugs. It contains only two small feature improvements.
-
-Fixes:
-* When placing a SHUN on an online user it was not always effective.
-* Channeldb was not properly restoring all channel modes, such as +P.
-* When upgrading UnrealIRCd it could sometimes crash the currently
-  running IRC server (rare), or trigger a crash report on
-  ```./unrealircd restart``` (quite common).
-* UnrealIRCd was giving up too easily on ident lookups.
-* Crash when unloading a module with moddata.
-* Crash if an authenticated server sends wrong information (rare).
-* Removing a TEMPSHUN did not work if the user was on another server.
-* SAJOIN to 0 (part all channels) resulted in a desync when used on remote users.
-* Forced nick change from services was not showing up if the user
-  was not in any channels.
-
-Enhancements:
-* New option [set::hide-idle-time::policy](https://www.unrealircd.org/docs/Set_block#set%3A%3Ahide-idle-time)
-  by which you can change usermode +I (hide idle time in WHOIS) from
-  oper-only to settable by users. More options will follow in a future
-  release.
-* In WHOIS you can now see if a user is currently (temp)shunned.
-  This only works for locally connected users for technical reasons,
-  so use ```/WHOIS Nick Nick``` to see it for remote users.
-
-Changes:
-* The oper notices and logging with regards to server linking have changed
-  a little. They are more consistent and log more now.
-* When an IRCOp tries to oper up from an insecure connection we will now
-  mention the https://www.unrealircd.org/docs/FAQ#oper-requires-tls page.
-  This message is customizable through
-  [set::plaintext-policy::oper-message](https://www.unrealircd.org/docs/Set_block#set::plaintext-policy).
-* The French HELPOP text was updated.
-
-UnrealIRCd 5.0.3.1
--------------------
-This fixes a crash issue after REHASH in 5.0.3.
-
-UnrealIRCd 5.0.3
------------------
-Fixes:
-* Fix serious flood issue in labeled-response implementation.
-* An IRCOp SQUIT'ing a far remote server may cause a broken link topology
-* In channels that are +D (delayed join), PARTs were not shown correctly to
-  channel operators.
-
-Enhancements:
-* A new HISTORY command for history playback (```HISTORY #channel number-of-lines```)
-  which allows you to fetch more lines than the on-join history playback.
-  Of course, taking into account the set limits in the +H channel mode.
-  This command is one of the [two interfaces](https://www.unrealircd.org/docs/Channel_history#Ways_to_retrieve_history)
-  to [Channel history](https://www.unrealircd.org/docs/Channel_history).
-* Two new [message tags](https://www.unrealircd.org/docs/Message_tags),
-  ```unrealircd.org/userip``` and ```unrealircd.org/userhost```
-  which communicate the user@ip and real user@host to IRCOps.
-
-Changes:
-* Drop the draft/ prefix now that the IRCv3
-  [labeled-response](https://ircv3.net/specs/extensions/labeled-response.html)
-  specification is out of draft.
-* The operclass permission ```immune:target-limit``` is now called
-  ```immune:max-concurrent-conversations```, since it bypasses
-  [set::anti-flood::max-concurrent-conversations](https://www.unrealircd.org/docs/Set_block#set::anti-flood::max-concurrent-conversations).
-  For 99% of the users this change is not important, but it may be
-  if you use highly customized [operclass blocks](https://www.unrealircd.org/docs/Operclass_block)
-
-Are you upgrading from UnrealIRCd 4.x to UnrealIRCd 5? If so,
-then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5). At the
-very least, check out [Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
-
-UnrealIRCd 5.0.2
------------------
-
-Fixes:
-* Halfop users are not synced correctly, resulting in missing users across links.
-* [Channel history](https://www.unrealircd.org/docs/Channel_history) used
-incorrect time internally, resulting in messages expiring too soon.
-The syntax is now really ```/MODE #chan +H lines:time-in-minutes```.
-To make clear that the time is in minutes, an 'm' will be added
-automatically by the server (eg ```+H 15:1440m```).
-* Documentation: to exempt someone from gline via /ELINE you have to use type 'G', not 'g'.
-  Similarly, to exempt from spamfilter, use type 'F' and not 'f'.
-* Exempting IPs from throttling via [except throttle](https://www.unrealircd.org/docs/Except_throttle_block) was not working.
-* Unable to customize [set::tls::outdated-protocols](https://www.unrealircd.org/docs/Set_block#set::ssl::outdated-protocols)
-  and [set::tls::outdated-ciphers](https://www.unrealircd.org/docs/Set_block#set::ssl::outdated-ciphers).
-* Specifying multiple channels did not work in [set::auto-join](https://www.unrealircd.org/docs/Set_block#set::auto-join),
-  [set::oper-auto-join](https://www.unrealircd.org/docs/Set_block#set::oper-auto-join) and
-  [tld::channel](https://www.unrealircd.org/docs/Tld_block).
-
-Enhancements:
-* [Extended server bans](https://www.unrealircd.org/docs/Extended_server_bans) in *LINE and /ELINE allow
-  you to ban or exempt users on criteria other than host/IP. These use a
-  similar syntax to extended bans. Currently supported are ~a, ~S and ~r. Examples:
-  * ```/ELINE ~a:TrustedAccount kG 0 This user can bypass kline/gline when using SASL```
-  * ```/ELINE ~S:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef kGF 0 Trusted user with this certificate fingerprint```
-  * ```/GLINE ~r:*some*stupid*real*name*```
-  * These can also be used in the configuration file, eg: ```except ban { mask ~S:11223344etc; type all; };```
-* New options that may not be used much, but can be useful on specific networks:
-  * The IRCd may add automatic bans, for example due to a blacklist hit,
-    a spamfilter hit, or because of antirandom or antimixedutf8. The new
-    option [set::automatic-ban-target](https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target) specifies on *what* the ban should
-    be placed. The default is *ip*. Other options are: userip, host, userhost, account, certfp.
-  * Similarly, an oper may type ```/GLINE nickname```. The new option
-    [set::manual-ban-target](https://www.unrealircd.org/docs/Set_block#set::manual-ban-target) specifies on what the ban should be placed.
-    By default this is *host* (fallback to *ip*).
-* New options to exempt webirc users: [set::connthrottle::webirc-bypass](https://www.unrealircd.org/docs/Connthrottle),
-  [set::restrict-commands::name-of-command::exempt-webirc](https://www.unrealircd.org/docs/Set_block#set::restrict-commands).
-
-UnrealIRCd 5.0.1
------------------
-
-Fixes:
-* IRCd may hang in rare circumstances
-* Windows: fix repeated "ERROR renaming 'data/reputation.db.tmp'" warnings
-* Antirandom and blacklist did not deal properly with 'warn' actions
-* [Authprompt](https://www.unrealircd.org/docs/Authentication#How_it_looks_like)
-  did not always work properly
-* Line numbers were incorrect in config file warnings/errors when using @if or @define
-
-Enhancements:
-* New /ELINE exception type 'm' to bypass allow::maxperip.
-  Or in the configuration file: ```except ban { mask 203.0.113.0/24; type maxperip; };```
-* IRCOps can override MLOCK restrictions when services are down,
-  if they have the channel:override:mlock operclass permission,
-  such as opers which use the operclass 'netadmin-with-override'.
-
-Other:
-* Gottem and k4be have [uploaded their 3rd party modules](https://modules.unrealircd.org/)
-  to unrealircd-contrib so *NIX users can now easily install them using the new
-  [Module manager](https://www.unrealircd.org/docs/Module_manager)
-
-UnrealIRCd 5
--------------
-After more than 6 months of hard work, UnrealIRCd 5 is now our new "stable" branch.
-In particular I would like to thank Gottem and 'i' for their source code
-contributions and PeGaSuS and westor for testing releases.
-
-When we transitioned from 3.2.x to 4.0.0 there were 175,000 lines of source code
-added/removed during 3 years of development. This time it was 120,000 lines in
-only 6 months, a major effort!
-
-**If you are upgrading from 4.x to 5.x, then it would be wise to read
-[Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
-In any case, be sure to upgrade your services package first! (if you use any)**
-
-UnrealIRCd 5 is compatible with the following services:
-* [anope](https://www.anope.org/) (version 2.0.7 or higher) -
-  with the "unreal4" protocol module
-* [atheme](https://atheme.github.io/atheme.html) (version 7.2.9 or higher) -
-  with the "unreal4" protocol module
+Many thanks to k4be for his help during development, other contributors for
+their feedback and patches, the people who tested the beta's and release
+candidates, translators and everyone else who made this release happen!
 
 Summary
 --------
-The most visible change to end-users is channel history. A lot of IRCv3 features were added.
-Various modules from Gottem have been integrated and enhanced.
-We now have a 3rd party module manager so you can install modules with 1 simple command.
-Channel settings of ```+P``` channels and *LINES are saved in a database and
-restored on startup (via 'channeldb' and 'tkldb' respectively).
-Channel mode ```+L``` has a slight change of meaning, the existing floodprot
-mode (```+f```) has a new type to prevent repeated messages and a new drop action.
-A few extended bans have been added as well (```~f``` and ```~p```).
-IRCOps now have the ability to add ban exceptions via the ```/ELINE``` command.
-Advanced admins can use more dynamic configuration options where you can
-define variables and use them later in the configuration file.
-Finally, there have been speed improvements, we use better defaults and
-have added more countermeasures and options against spambots.
-Under the hood *a significant amount* of the source code was changed and cleaned up.
-
-Read below for the full list of enhancements, changes and removals (and information for developers too).
+UnrealIRCd 6 comes with a completely redone logging system (with optional
+JSON support), named extended bans, four new IRCv3 features,
+geoip support and remote includes support built-in.
+
+Additionally, things are more customizable such as what gets sent to
+which snomask. All the +vhoaq channel modes are now modular as well,
+handy for admins who don't want or need halfops or +q/+a.
+For WHOIS it is now customizable in detail who gets to see what.
+
+A summary of the features is available at
+[What's new in UnrealIRCd 6](https://www.unrealircd.org/docs/What's_new_in_UnrealIRCd_6).
+For complete information, continue reading the release notes below.
+The sections below contain all the details.
+
+Upgrading from UnrealIRCd 5
+----------------------------
+The previous stable series, UnrealIRCd 5, will no longer get any new features.
+We still do bug fixes until July 1, 2022. In the 12 months after that, only
+security issues will be fixed. Finally, after July 1, 2023,
+[all support will stop](https://www.unrealircd.org/docs/UnrealIRCd_5_EOL).
+
+If you want to hold off for a while because you are cautious or if you
+depend on 3rd party modules (which may not have been upgraded yet by their
+authors) then feel free to wait a while.
+
+If you are upgrading from UnrealIRCd 5 to 6 then you can use your existing
+configuration and files. There's no need to start from scratch.
+However, you will need to make a few updates, see
+[Upgrading from 5.x to 6.x](https://www.unrealircd.org/docs/Upgrading_from_5.x).
 
 Enhancements
 -------------
-* Support for IRCv3 server generated [message tags](https://ircv3.net/specs/extensions/message-tags), which allows us to communicate
-  additional information in protocol messages such as in JOIN and PRIVMSG.
-  Currently implemented and permitted message tags are:
-  * [account](https://ircv3.net/specs/extensions/account-tag-3.2): communicate the services account that a user uses
-  * [msgid](https://ircv3.net/specs/extensions/message-ids): assign an unique message id to each message
-  * [time](https://ircv3.net/specs/extensions/server-time-3.2): assign a time label to each message
-  The last two are mainly for history playback.
-* Support for IRCv3 [echo-message](https://ircv3.net/specs/extensions/echo-message-3.2), which helps clients, among other things,
-  to see if the message you sent was altered in any way, eg: censored,
-  stripped from color, etc.
-* Support for IRCv3 [draft/labeled-response-0.2](https://ircv3.net/specs/extensions/labeled-response), which helps clients to
-  correlate commands and responses.
-* Support for IRCv3 [BATCH](https://ircv3.net/specs/extensions/batch-3.2), needed for some other features.
-* Recording and playback of [channel history](https://www.unrealircd.org/docs/Channel_history) when channel mode +H is set.
-  The syntax is: ```MODE #chan +H max-lines-to-record:max-time-to-record-in-minutes```.
-
-  For example: ```MODE #chan +H 50:1440``` means the last 50 messages will be stored and no
-  message will be stored longer than 1440 minutes (1 day).
-
-  The channel history is then played back when joining such a channel,
-  but with two things to keep in mind:
-  1) The client must support the 'server-time' CAP ('time' message tag),
-     otherwise history is not shown. Any modern IRC client supports this.
-  2) Only a maximum of 15 lines are played back on-join by default
-
-  The reason for the maximum 15 lines on-join playback is that this can
-  be quite annoying if you rejoin repeatedly and as to not flood the users
-  screen too much (unwanted). In the future we will support a mechanism
-  for clients to "fetch" history - rather than sending it on-join - so
-  they can fetch more than the 15 lines, up to the number of lines and
-  time configured in the +H channel mode.
-
-  You can configure the exact number of lines that are played back and
-  all the limits that apply to +H via [set::history::channel](https://www.unrealircd.org/docs/Set_block#set::history).
-* For saving and retrieving history we currently have the following options:
-  * *history_backend_mem*: channel history is stored in memory.
-    This is very fast but also means history is lost on restart.
-  * *history_backend_null*: don't store channel history at all.
-    This can be useful to load on servers with no users on it, such as a
-    hub server, where storing history is unnecessary.
-
-  As you can see there is currently no 'disk' backend. However, in the
-  future more options may be added. Also note that 3rd party modules
-  can add history backends as well.
-* Support for ban exceptions via the new ```/ELINE``` command. This allows you
-  to add exceptions for regular bans (KLINE/GLINE/ZLINE/etc), but also
-  for connection throttling and blacklist checking.
-  For more information, just type ```/ELINE ``` in your IRC client as an IRCOp.
-* [Websocket](https://www.unrealircd.org/docs/WebSocket_support) support now includes type 'text'
-  in addition to 'binary', which should work with [KiwiIRC](https://kiwiirc.com/)'s nextclient.
-
-  Also, websockets are no longer active on all ports by default. You have to explicitly
-  enable the websocket option in the listen block and also specify type *text* or *binary*,
-  eg: ```listen { ip *; port 6667; options { websocket { type text; } } }```
-
-  Also note that websockets require nick names and channels to consist of UTF8
-  characters only, due to
-  [WebSocket being incompatible with non-UTF8](https://www.unrealircd.org/docs/WebSocket_support#Problems_with_websockets_and_non-UTF8)
-* There's now a [Module manager](https://www.unrealircd.org/docs/Module_manager)
-  which allows you to install and upgrade 3rd party modules in an easy way:
-  * ```./unrealircd module list``` - to list all available 3rd party modules
-  * ```./unrealircd module install third/something``` - to install the specified module.
-* You can now test for configuration errors without actually starting the
-  IRC server. This is ideal if you are upgrading UnrealIRCd to a newer
-  version: simply run ```./unrealircd configtest``` to make sure it passes
-  the configuration test, and then you can safely restart the server for
-  the upgrade (in this example case).
-* Channel mode +L now kicks in for any rejected join, so not just for +l but
-  also for +b, +i, +O, +z, +R and +k. If, for example, the channel is
-  +L #insecure and also +z then, when an insecure user ties to join, they
-  will be redirected to #insecure.
-* New extended ban ~f to forward users to the specified channel if the ban
-  matches. Example: ```MODE #chan +b ~f:#badisp:*!*@*.isp.org```
-* Channel mode +f now has a 'd' action: drop message. This will send an
-  error message to the user and not show the message in the channel but
-  otherwise do nothing (no kick or ban).
-  For example: ```MODE #chan +f [5t#d]:15``` will limit sending a maximum of
-  5 messages per 15 seconds per-user and drop any messages sent above that limit.
-* Channel mode +f now has 'r' floodtype to prevent repeated lines. This will
-  compare the current message to the last message and the one before that
-  the user sent to the channel. If it's a repeat then the user can be
-  kicked (the default action), the message can be dropped ('d') or the
-  user can be banned ('b'). Example: ```MODE #chan +f [1r#d]:15```
-  If you want to permit 1 repeated line but not 2 then use: ```+f [2r#d]:15```
-* New module **tkldb** (loaded by default): all *LINES and spamfilters are now
-  saved across reboots. No need for services for that anymore.
-* New module **channeldb** (loaded by default): saves and restores all channel
-  settings including topic, modes, bans etc. of +P (persistent) channels.
-* New module [restrict-commands](https://www.unrealircd.org/docs/Set_block#set::restrict-commands), which allows you to restrict any IRC
-  command based on criteria such as "how long is this user connected",
-  "is this user registered (has a services account)" etc.
-  The example.conf now ships with configuration to disable LIST the
-  first 60 seconds and disable INVITE the first 120 seconds.
-  If you are having spambot problems then tweaking this configuration
-  may be helpful to you.
-* New option [set::require-module](https://www.unrealircd.org/docs/Set_block#set::require-module), which allows you to require certain
-  modules on other UnrealIRCd 5 servers, otherwise the link is rejected.
-* New option [set::min-nick-length](https://www.unrealircd.org/docs/Set_block#set::min-nick-length) to set a minimum nick length.
-* New module rmtkl (loaded by default): this allows you to remove TKL's
-  such as GLINEs easily via the /RMTKL command.
-* The [reputation and connthrottle](https://www.unrealircd.org/docs/Connthrottle) modules are now loaded by default.
-  Just as a reminder, what these do is classifying your users in "known
-  users (known IP's)" and "unknown IP's" for IP's that have not been
-  seen before (or only for a short amount of time). Then, when there
-  is a connection flood, unknown/new IP addresses are throttled at
-  20 connections per minute, while known users are always allowed in.
-* Add support for [defines and conditional configuration](https://www.unrealircd.org/docs/Defines_and_conditional_config) via @define and @if.
-  This is mostly for power users, in particular users who share the same
-  configuration file across several servers.
-* New extban ~p to hide the part/quit message in PART and QUIT.
-  For example: ```MODE #chan +b ~p:*!*@*.nl```
-* You will now see a warning when a server is not responding even
-  before they time out. How long to wait for a PONG reply upon PING
-  can be changed via [set::ping-warning](https://www.unrealircd.org/docs/Set_block#set::ping-warning) and defaults to 15 seconds.
-  If you see the warning frequently then your connection is flakey.
-* Add new setting [set::broadcast-channel-messages](https://www.unrealircd.org/docs/Set_block#set::broadcast-channel-messages) which defines when
-  channel messages are sent across server links. The default setting
-  is *auto* which is the correct setting for pretty much everyone.
-* Add new option [set::part-instead-of-quit-on-comment-change](https://www.unrealircd.org/docs/Set_block#set::part-instead-of-quit-on-comment-change):
-  when a QUIT message is changed due to channel restrictions, such as
-  stripping color or censoring a word, we normally change the QUIT
-  message. This has an effect on ALL channels, not just the one that
-  imposed the restrictions. While we feel that is the best tradeoff,
-  there is now also this new option (off by default) that will change
-  the QUIT into a PART in such a case, so the other channels that
-  do not have the restrictions (eg: are -S and -G) can still see the
-  original QUIT message.
-* New module [webredir](https://www.unrealircd.org/docs/Set_block#set::webredir::url). Quite some people run their IRCd on port 443 or 80
-  so their users can avoid firewall restrictions in place. In such a case,
-  with this module, you can now send a HTTP redirect in case some user
-  enters your IRC server name in their browser. Eg https://irc.example.org/
-  can be made to redirect to https://www.example.org/
-* We now protect against misbehaving SASL servers and will time out
-  SASL sessions after
-  [set::sasl-timeout](https://www.unrealircd.org/docs/Set_block#set::sasl-timeout),
-  which is 15 seconds by default.
-
-Changed
+* Completely new log system and snomasks overhaul
+  * Both logging and snomask sending is done by a single logging function
+  * Support for [JSON logging](https://www.unrealircd.org/docs/JSON_logging)
+    to disk, instead of the default text format.
+    JSON logging adds lot of detail to log messages and consistently
+    expands things like *client* with properties like *hostname*,
+    *connected_since*, *reputation*, *modes*, etc.
+  * The JSON data is also sent to all IRCOps who request the
+    `unrealircd.org/json-log` capability. The data is then sent in
+    a message-tag called `unrealircd.org/json-log`. This makes it ideal
+    for client scripts and bots to do automated things.
+  * A new style log { } block is used to map what log messages should be
+    logged to disk, and which ones should be sent to snomasks.
+  * The default logging to snomask configuration is in `snomasks.default.conf`
+    which everyone should include from unrealircd.conf. That is, unless you
+    wish to completely reconfigure which logging goes to which snomasks
+    yourself, which is also an option now.
+  * See [Snomasks](https://www.unrealircd.org/docs/Snomasks#UnrealIRCd_6)
+    on the new snomasks - lots of letters changed!
+  * See [FAQ: Converting log { } block](https://www.unrealircd.org/docs/FAQ#old-log-block)
+    on how to change your existing log { } blocks for disk logging.
+  * We now have a consistent log format and log messages can be multiline.
+  * Colors are enabled by default in snomask server notices, these can be disabled via
+    [set::server-notice-colors](https://www.unrealircd.org/docs/Set_block#set::server-notice-colors)
+    and also in [oper::server-notice-colors](https://www.unrealircd.org/docs/Oper_block)
+* Almost all channel modes are modularized
+  * Only the three list modes (+b/+e/+I) are still in the core
+  * The five [level modes](https://www.unrealircd.org/docs/Channel_Modes#Access_levels)
+    (+vhoaq) are now also modular. They are all loaded by default but you can
+    blacklist one or more if you don't want them. For example to disable halfop:
+    `blacklist-module chanmodes/halfop;`
+  * Support for compiling without PREFIX_AQ has been removed because
+    people often confused it with disabling +a/+q which is something
+    different.
+* Named extended bans
+  * Extbans now no longer show up with single letters but with names.
+    For example `+b ~c:#channel` is now `+b ~channel:#channel`.
+  * Extbans are automatically converted from the old to the new style,
+    both from clients and from/to older UnrealIRCd 5 servers.
+    The auto-conversion also works fine with complex extbans such as
+    `+b ~t:5:~q:nick!user@host` to `+b ~time:5:~quiet:nick!user@host`.
+* New IRCv3 features:
+  * [MONITOR](https://ircv3.net/specs/extensions/monitor.html): an
+    alternative for `WATCH` to monitor other users ("notify list").
+  * draft/extended-monitor: extensions for MONITOR, still in draft.
+  * [invite-notify](https://ircv3.net/specs/extensions/invite-notify):
+    report channel invites to other chanops (or users) in a machine
+    readable way.
+  * [setname](https://ircv3.net/specs/extensions/setname.html):
+    notify clients about realname (gecos) changes.
+* GeoIP lookups are now done by default
+  * This shows the country of the user to IRCOps in `WHOIS` and in the
+    "user connecting" line.
+  * By default the `geoip_classic` module is loaded, for which we
+    provide a mirror of database updates at unrealircd.org. This uses
+    the classic geolite library that is now shipped with UnrealIRCd
+  * Other options are the `geoip_maxmind` and `geoip_csv` modules.
+* Configure `WHOIS` output in a very precise way
+  * You can now decide which fields (eg modes, geo, certfp, etc) you want
+    to expose to who (everyone, self, oper).
+  * See [set::whois-details](https://www.unrealircd.org/docs/Set_block#set::whois-details)
+    for more details.
+* We now ship with 3 cloaking modules and you need to load 1 explicitly
+  via `loadmodule`:
+  * `cloak_sha256`: the recommended module for anyone starting a *new*
+    network. It uses the SHA256 algorithm under the hood.
+  * `cloak_md5`: for anyone who is upgrading their network from older
+    UnrealIRCd versions. Use this so your cloaked host bans remain the same.
+  * `cloak_none`: if you don't want any cloaking, not even as an option
+    to your users (rare)
+* Remote includes are now supported everywhere in the config file.
+  * Support for `https://` fetching is now always available, even
+    if you don't compile with libcurl support.
+  * Anywhere an URL is encountered on its own, it will be fetched
+    automatically. This makes it work not only for includes and motd
+    (which was already supported) but also for any other file.
+  * To prevent something from being interpreted as a remote include
+    URL you can use 'value' instead of "value".
+* Invite notification: set `set::normal-user-invite-notification yes;` to make
+  chanops receive information about normal users inviting someone to their channel.
+  The name of this setting may change in a later version.
+* Websocket: you can add a `listen::options::websocket::forward 1.2.3.4` option
+  to make unrealircd accept a `Forwarded` (RFC 7239) header from a reverse proxy
+  connecting from `1.2.3.4` (plans to accept legacy `X-Forwarded-For` and a proxy
+  password too). This feature is currently experimental.
+
+Changes
 --------
-* Channel mode +L can now be set by chanops (+o and higher) instead of only
-  by +q (channel owner)
-* Channel names must now be valid UTF8 by default.
-  We actually have 3 possible settings of [set::allowed-channelchars](https://www.unrealircd.org/docs/Set_block#set::allowed-channelchars):
-  * **utf8**:  Channel must be valid UTF8, this is the new default
-  * **ascii**: A very strict setting, for example in use at freenode,
-     the channel name may not contain high ascii or UTF8
-  * **any**:   A very loose setting, which allows almost all characters
-     in the channel name. This was the OLD default, up to and
-     including UnrealIRCd 4. It is no longer recommended.
-
-  For most networks this new default setting of utf8 will be fine, since
-  by far most IRC clients use UTF8 for many years already.
-  If you have a network that has a significant portion of chatters
-  that are on old non-UTF8 clients that use a specific character set
-  then you may want to use ```set { allowed-nickchars any; }```
-  Some Russian and Ukrainian networks are known to need this.
-* The "except tkl" block is now called [except ban](https://www.unrealircd.org/docs/Except_ban_block#UnrealIRCd_5). If no type
-  is specified in an except ban { } block then we exempt the entry
-  from kline, gline, zline, gzline and shun.
-* We no longer use a blacklist for stats (set::oper-only-stats).
-  We use a whitelist now instead: [set::allow-user-starts](https://www.unrealircd.org/docs/Set_block#set::allow-user-stats).
-  Most users can just remove their old set::oper-only-stats line,
-  since the new default set::allow-user-starts setting is fine.
-* Windows: we now require a 64-bit version, Windows 7 or later.
-  The new program path is: C:\Program Files\UnrealIRCd 5
-  and the binaries have been moved to a new subdirectory: bin\
-* Modules lost their m_ prefix, so for example m_map is now just map.
-  Also the modules in cap/ are now directly in modules.
-* More modules that were previously PERM (permanent) can now be unloaded
-  and reloaded on the fly. This allows more "hotfixing" without restart
-  in case of a bug and also more control for admins at runtime.
-  Only <5 modules out of 173 are permanent now.
-* User mode +T now blocks channel CTCPs as well.
-* User mode +q (unkickable) could previously be set by any IRCOp.
-  This has been changed to require the self:unkickablemode operclass
-  permission. This is included in the *-with-override operclasses
-  (eg: netadmin-with-operoverride).
-* [set::modes-on-join](https://www.unrealircd.org/docs/Set_block#set::modes-on-join) is now ```+nt``` by default.
-* The [authprompt](https://www.unrealircd.org/docs/Authentication#How_it_looks_like) module is now loaded by default. This means that if
-  you do a soft kline on someone (eg: ```KLINE %*@*.badisp```) then the user
-  has a chance to [authenticate](https://www.unrealircd.org/docs/Authentication#How_it_looks_like) to services, even without SASL, and
-  bypass the ban if (s)he is authenticated.
-* The WHOX module is now used by default. Previously it was optional.
-  WHOX enhances the "WHO" output, providing additional information to
-  IRC clients such as the services account that someone is using.
-  It is also more universal than standard WHO. Unfortunately this also
-  means the WHO syntax changed to something less logical.
-* At many places the term *SSL* has been changed to *SSL/TLS* or *TLS*.
-  Configuration items (eg: set::ssl to set::tls) have been renamed
-  as well and so have directories (eg: conf/ssl to conf/tls).
-  The old configuration names still work and currently does NOT raise
-  any warning. Also, when upgrading an existing installation on *NIX,
-  the conf/tls directory will be symlinked to conf/ssl as to not break
-  any Let's Encrypt certificate scripts.
-* It is now mandatory to have at least one open SSL/TLS port, otherwise
-  UnrealIRCd will refuse to boot. Previously this was a warning.
-* IRCOps now need to use SSL/TLS in order to oper up, as the
-  [set::plaintext-policy::oper](https://www.unrealircd.org/docs/Set_block#set::plaintext-policy) default setting is now 'deny'.
-  Similarly, [set::outdated-tls-policy::oper](https://www.unrealircd.org/docs/Set_block#set::outdated-tls-policy) is now also 'deny'.
-  You can change this, if you want, but it is not recommended.
-* [set::outdated-tls-policy::server](https://www.unrealircd.org/docs/Set_block#set::outdated-tls-policy) is now 'deny' as well, since all
-  servers should use reasonable SSL/TLS protocols and ciphers.
-* The default generated certificated has been changed from RSA 4096 bits
-  to Elliptic Curve Cryptography "384r1". This provides the same amount
-  of security but at higher speed. This only affects the default self-
-  signed certificate. You can still use RSA certificates just fine.
-* If you do use an RSA certificate, we now require it to be at least
-  2048 bits otherwise UnrealIRCd will refuse to boot.
-* When matching [allow { } blocks](https://www.unrealircd.org/docs/Allow_block), we now always continue with the next
-  block (if any) if the password did not match or no password was
-  specified. In other words, allow::options::nopasscont is now the
-  default and we behave as if there was a ::wrongpasscont too.
-* All snomasks are now oper-only. Previously some were not, which
-  was confusing and could lead to information leaks.
-  Also removed weird set::snomask-on-connect accordingly.
-* The IRCd now uses hash tables that are resilient against hash table
-  attacks. Also, the hash tables have increased in size to speed things
-  up when looking up nick names etc.
-* Server options in VERSION (eg: Fhin6OoEMR3) are no longer shown to
-  normal users. They don't mean much nowadays anyway.
-* ```./Config``` now asks fewer questions and configure runs faster since
-  many unnecessary checks have been removed (compatibility with very
-  old compilers / systems).
-* We now default to system libs (eg: ```--with-system-pcre2``` is assumed)
-* Spamfilter should catch some more spam evasion techniques.
-* All /DCCDENY and deny dcc { } parsing and checking is now moved to
-  the 'dccdeny' module.
-* Windows: If you choose to run UnrealIRCd as a service then it now
-  runs under the low-privilege NetworkService account rather than
-  the high-privilege LocalSystem account.
-
-Minor issues fixed
--------------------
-* Specifying a custom OpenSSL/LibreSSL path should work now
+* TLS cipher and some other information is now visible for remote
+  clients as well, also in `[secure: xyz]` connect line.
+* Error messages in remote includes use the url instead of a temporary file
+* Downgrading from UnrealIRCd 6 is only supported down to 5.2.0 (so not
+  lower like 5.0.x). If this is a problem then make a copy of your db files
+  (eg: reputation.db).
 
 Removed
 --------
-* Support for old server protocols has been removed.
-  This means UnrealIRCd 5.x cannot link to 3.2.x. It also means you need
-  to use reasonably new services. Generally, if your services can link to
-  4.x then they should be able to link to 5.x as well. More information
-  about this change and why it was done
-  [can be found here](https://www.unrealircd.org/docs/FAQ#old-server-protocol).
-* Connecting with a server password will no longer send that password
-  to NickServ. Use [SASL](https://www.unrealircd.org/docs/SASL) instead!
-* Extended ban ~R (registered nick): this was the old method to match
-  registered users. Everyone should use ~a (services account) instead.
-* The old TRE **posix** regex method has been removed because the TRE
-  library is no longer maintained for over a decade and contains many
-  bugs. (It was already deprecated in UnrealIRCd 4.2.3).
-  Use type **regex** instead, which uses the modern PCRE2 regex engine.
-* Timesync support has been removed. Use your OS time synchronization
-  instead. (Note that Timesync was already disabled by default in 2018)
-* Changing time offsets via ```TSCTL OFFSET``` and ```TSCTL SVSTIME``` are no longer
-  supported. Use your OS time synchronization (NTP!). Adjustments via
-  TSCTL are simply not accurate enough.
-* The *nopost* module was removed since it no longer serves any useful
-  purpose. UnrealIRCd already protects against these kind of attacks
-  via ping cookies ([set::ping-cookie](https://www.unrealircd.org/docs/Set_block#set::ping-cookie), enabled by default).
-
-Deprecated
------------
-* The set::official-channels block is now deprecated. This provided a
-  mechanism to pre-configure channels that would have 0 members and
-  would appear in /LIST with those settings, but once you joined all
-  those settings would be gone. Rather confusing.
-
-  Since UnrealIRCd 4.x we have permanent channels (+P) and since 5.x
-  we store these permanent channels in a database so all settings are
-  saved every few minutes and across restarts.
+* /REHASH -motd and -opermotd are gone, just use /REHASH
 
-  Since permanent channels (+P) are much better, the official-channels
-  support will be removed in a later version. There's no reason to
-  use official-channels anymore.
-
-Developers
------------
-* The module header is now as follows:
-
-      ModuleHeader MOD_HEADER
-        = {
-              "nameofmodule",
-              "5.0",
-              "Some description", 
-              "Name of Author",
-              "unrealircd-5",
-          };
-  There's a new author field, the version must start with a digit,
-  and also the name of the module must match the loadmodule name.
-  So for example third/funmod must also be named third/funmod.
-* The ```MOD_TEST```, ```MOD_INIT```, ```MOD_LOAD``` and ```MOD_UNLOAD``` functions no longer
-  take a name argument. So: ```MOD_INIT(mymod)``` is now ```MOD_INIT()```
-* We now use our own BuildBot infrastructure, so Travis-CI and AppVeyor
-  have been removed.
-* We now use a new test framework.
-* ```Auth_Check()``` now returns ```1``` for allow and ```0``` on deny (!!)
-* New function ```new_message()``` which should be called when a new message
-  is sent, or at least for all channel events. It adds (or inherits)
-  message tags like 'account', 'msgid', 'time', etc.
-* Many send functions now take an extra MessageTag *mtags parameter,
-  including but not limited to: sendto_one() and sendto_server().
-* Command functions (CMD_FUNC) have an extra ```MessageTag *mtags```,
-  on the other hand the ```cptr``` parameter has been removed.
-* Command functions no longer return ```int``` but are ```void```,
-  the same is true for  ```exit_client()```. ```FLUSH_BUFFER``` has been removed too.
-  All this is a consequence of removing this (limited) signaling
-  of client exits. From now on, if you call ```exit_client()``` it will free
-  a lot of the client data and exit the user (close socket, send [s]quit),
-  but it will **not free 'sptr' itself**, so you can simply check if some
-  upstream function killed the client by checking ```IsDead(sptr)```.
-  This is highly recommended after running ```do_cmd()``` or calling other
-  functions that could kill a client. In which case you should return
-  rather than continue doing anything with ```sptr```.
-  Ultimately, in the main loop, the client will be freed (normally in less than 1 second).
-* New single unified ```sendto_channel()``` and ```sendto_local_common_channels()```
-  functions that are used by all the channel commands.
-* Numerics should now be sent using ```sendnumeric()```. There's also
-  a format string version ```sendnumericfmt()``` in case you need it,
-  in which case you need to pass the numeric format string yourself.
-  In such a case, don't forget the colon character, like ":%s", where needed.
-* The parameters in several hooks have changed. Many now have an
-  extra ```MessageTag *mtags``` parameter. Sometimes there are other changes
-  as well, for example ```HOOKTYPE_CHANMSG``` now has 4 extra parameters.
-* You can call do_cmd() with NULL mtags. Usually this is the correct way.
-* If you used ```HOOKTYPE_PRE_USERMSG``` to block a message then you
-  should now use ```HOOKTYPE_CAN_SEND_TO_USER```. Similarly, the hook
-  ```HOOKTYPE_CAN_SEND``` which deals with channels is now called
-  ```HOOKTYPE_CAN_SEND_TO_CHANNEL```. Some other remarks:
-  * You CANNOT use HOOKTYPE_PRE_USERMSG anymore.
-  * The hooks require you to set an error message if you return HOOK_DENY.
-  * You should not send an error message yourself from these hooks.
-    In other words: do not use sendnumeric(). This is done by the
-    hook caller, based on the error message you return.
-  * Thanks to this, all rejecting of user messages now use generic
-    numeric 531 and all rejecting of channel messages use numeric 404.
-    See also under *Client protocol* later in this document.
-* If you use CommandOverrideAddEx() to specify a priority value (rare)
-  then be aware that in 5.0.1 we now use the 4.0.x behavior again to
-  match the same style of priorities in hooks: overrides with the
-  lowest priority are run first.
-* If you ever send a timestamp in a printf-like function, such as
-  in ```sendto_server()```, then be sure to use ```%lld``` and cast the timestamp
-  to *long long* so that it is compatible with both *NIX and Windows.
-  Example: ```sendnotice(sptr, "Timestamp is %lld", (long long)ts);```
-* ```EventAdd()``` changed the order of parameters and expects every_msec now
-  which specifies the time in milliseconds rather than seconds. This
-  allows for additional precision, or at least multiple calls per second.
-  The minimum allowed every_msec value is 100 at this time.
-  The prototype is now: ```EventAdd(Module *module, char *name,
-  vFP event, void *data, long every_msec, int count);```
-* New ```HOOKTYPE_IS_HANDSHAKE_FINISHED```. If a module returns ```0``` there, then
-  the ```register_user()``` function will not be called and the user will
-  not come online (yet). This is used by CAP and some other stuff.
-  Can be useful if your module needs to "hold" a user in the registration
-  phase.
-* The function ```is_module_loaded()``` now takes a relative path like
-  "usermodes/noctcp" because with just "ctcp" one could not see the
-  difference between usermodes/noctcp and chanmodes/noctcp.
-* ```CHFL_CHANPROT``` is now ```CHFL_CHANADMIN```, ```is_chanprot()``` is now ```is_chanadmin()```
-* All hash tables now use [SipHash](https://en.wikipedia.org/wiki/SipHash), which is a hash function that is
-  resilient against hash table attacks. If you, as a module dev, too
-  use any hash tables anywhere (note: this is quite rare), then you
-  are recommended to use our functions, see the functions siphash()
-  and siphash_nocase() in src/hash.c.
-* The random generator has been updated to use [ChaCha](https://en.wikipedia.org/wiki/Salsa20#ChaCha20_adoption) (more modern).
-* You can now save pointers and integers etc. across rehashes by using
-  ```LoadPersistentPointer()``` and ```SavePersistentPointer()```. For an example,
-  see ```src/modules/chanmodes/floodprot.c``` how this can be used.
-  Note that there can be no struct or type changes between rehashes.
-* New ModData types: ```MODDATA_LOCALVAR``` and ```MODDA_GLOBALVAR```. These are
-  settings or things that are locally or globally identified by the
-  variable name only and not attached to any user/channel.
-* Various files have been renamed. As previously mentioned, the m_
-  prefix was dropped in ```src/modules/m_*.c```. Similarly the s_ prefix
-  was dropped in ```src/s_*.c``` since it no longer had meaning. Also some
-  files have been deleted and integrated elsewhere or renamed to
-  have a name that better reflects their true meaning.
-  Related to this change is that all command functions are now called
-  ```cmd_name``` rather than ```m_name```.
-* ```HOOKTYPE_CHECK_INIT``` and ```HOOKTYPE_PRE_LOCAL_CONNECT```
-  have their return value changed. You should now return ```HOOK_*```, such
-  as ```HOOK_DENY``` or ```HOOK_CONTINUE```.
+Breaking changes
+-----------------
+See https://www.unrealircd.org/docs/Upgrading_from_5.x, but in short:
+
+You can use the unrealircd.conf from UnrealIRCd 5, but you need to make
+a few changes:
+* You need to add `include "snomasks.default.conf";`
+* You need to load a cloaking module explicitly. Assuming you already
+  have a network then add: `loadmodule "cloak_md5";`
+* The log block(s) need to be updated, use something like:
+  ```
+  log {
+          source {
+              !debug;
+              all;
+          }
+          destination {
+              file "ircd.log" { maxsize 100M; }
+          }
+  }
+  ```
+
+Module coders (API changes)
+----------------------------
+* Be sure to bump the version in the module header from `unrealircd-5` to `unrealircd-6`
+* We use a lot more `const char *` now (instead of `char *`). In particular `parv`
+  is const now and so are a lot of arguments to hooks. This will mean that in your
+  module you have to use more const too. The reason for this change is to indicate
+  that certain strings should not be touched, as doing so is dangerous or could
+  have had side-effects that were unpredictable.
+* Logging has been completely redone. Don't use `ircd_log()`, `sendto_snomask()`,
+  `sendto_ops()` and `sendto_realops()` anymore. Instead use `unreal_log()` which
+  handles both logging to disk and notifying IRCOps.
+* Various struct member names changed, in particular in `ConfigEntry` and `ConfigFile`,
+  but also `channel->chname` is `channel->name` now.
+* get_channel() is now make_channel() and creates if needed, otherwise use find_channel()
+* The Extended Ban API has been changed a lot. We use a `BanContext` struct now
+  that we pass around a lot. You also don't need to do `+3` magic anymore on the
+  string as it is handled in another layer. When registering the extended ban,
+  `.flag` is now `.letter`, and you also need to set a `.name` to a string due
+  to named extended bans. Have a look at the built-in extban modules to see
+  how to handle the changes.
+* ModData now has an option `MODDATA_SYNC_EARLY`. See under *Server protocol*.
+* If you want to lag someone up, don't touch `client->since`, but instead use:
+  `add_fake_lag(client, msec)`
+* Some client/user struct changes, with `client->user->account` (instead of svid)
+  and `client->uplink->name` being the most important ones.
+* Possibly more, but above is like 90%+ of the changes that you will encounter.
 
 Server protocol
 ----------------
-* UnrealIRCd 5 now assumes you support the following PROTOCTL options:
-  ```NOQUIT EAUTH SID NICKv2 SJOIN SJ3 NICKIP TKLEXT2```.
-  If you fail to use ```SID``` or ```EAUTH``` then you will receive an
-  error. For the other options, support is *assumed*, no warning or
-  error is shown when you lack support. These are options that most,
-  if not all, services support since UnrealIRCd 4.x so it shouldn't be
-  a problem. More information [here](https://www.unrealircd.org/docs/FAQ#old-server-protocol)
-* ```PROTOCTL MTAGS``` indicates that the server is capable of handling
-  message tags and that the server can cope with 4K lines. (Note that
-  the ordinary non-message-tag part is still limited to 512 bytes).
-* Pseudo ID support in SASL was removed. We now use real UID's.
-  This breaks services who rely on the old pseudo ID format.
+* When multiple related `SJOIN` messages are generated for the same channel
+  then we now only send the current channel modes (eg `+sntk key`) in the
+  first SJOIN and not in the other ones as they are unneeded for the
+  immediate followup SJOINs, they waste unnecessary bytes and CPU.
+  Such messages may be generated when syncing a channel that has dozens
+  of users and/or bans/exempts/invexes. Ideally this should not need any
+  changes in other software, since we already supported such messages in the
+  past and code for handling it exists way back to 3.2.x, but you better
+  check to be sure!
+* If you send `PROTOCTL NEXTBANS` then you will receive extended bans
+  with Named EXTended BANs instead of letters (eg: `+b ~account:xyz`),
+  otherwise you receive them with letters (eg: `+b ~a:xyz`).
+* Some ModData of users is (also) communicated in the `UID` message while
+  syncing using a message tag that only appears in server-to-server traffic,
+  `s2s-md/moddataname=value`. Thus, data such as operinfo, tls cipher,
+  geoip, certfp, sasl and webirc is communicated at the same time as when
+  a remote connection is added.
+  This makes it that a "connecting from" server notice can include all this
+  information and also so code can make an immediate decission on what to do
+  with the user in hooks. ModData modules need to set
+  `mreq.sync = MODDATA_SYNC_EARLY;` if they want this.
+  Servers of course need to enable `MTAGS` in PROTOCTL to see this.
+* The `SLOG` command is used to broadcast logging messages. This is done
+  for log::destination remote, as used in doc/conf/snomasks.default.conf,
+  for example for link errors, oper ups, flood messages, etc.
+  It also includes all JSON data in a message tag when `PROTOCTL MTAGS` is used.
+* Bounced modes are gone: these were MODEs that started with a `&` which
+  servers were to act on with reversed logic (add becoming remove and
+  vice versa) and never to send something back to that server.
+  In practice this was almost never used and complicated the code (way)
+  too much.
 
 Client protocol
 ----------------
-* Support for message tags and other IRCv3 features. See the IRCv3
-  specifications for more details.
-* When a message is blocked, for whatever reason, we now use a generic
-  numeric response: ```:server 531 yourname targetname :reason``` for the block
-  This replaces all the various NOTICEs, ```ERR_NOCTCP```, ```ERR_NONONREG```, etc.
-  with just one single numeric.
-  The only other numerics that you may still encounter when PM'ing are
-  ```ERR_NOSUCHNICK```, ```ERR_TOOMANYTARGETS``` and ```ERR_TARGETTOOFAST```, which are
-  generic errors to any command involving targets. And ```ERR_SERVICESDOWN```.
-  Note that channel messages already had a generic numeric for signaling
-  blocked messages for a very long time, ```ERR_CANNOTSENDTOCHAN```.
-* The 271 response to the SILENCE command is now:
-  ```:server 271 yournick listentry!*@*```
-  Previously the nick name appeared twice, which was a mistake.
-* The 470 numeric, which is sent on /JOIN #channel redirect to #redirect
-  now uses the following format:
-  ```:server 470 yournick #channel #redirect :[Link] Cannot join channel...etc..```
-* Clients are recommended to implement and enable the
-  [server-time](https://ircv3.net/specs/extensions/server-time-3.2)
-  extension by default. When enabled, channel history is played back
-  on-join (if any) when the channel has channel mode +H.
-  Otherwise your users will not see channel history.
+* Extended bans now have names instead of letters. If a client sends the
+  old format with letters (eg `+b ~a:XYZ`) then the server will
+  convert it to the new format with names (eg: `+b ~account:XYZ`)
+* Support for `MONITOR` and the other IRCv3 features (see *Enhancements*)
diff --git a/doc/conf/badwords.conf b/doc/conf/badwords.conf
@@ -1,27 +1,44 @@
-badword channel { word "bitch";     replace "wombat";         }
-badword channel { word "bro";       replace "bo";             }
-badword channel { word "(brother)"; replace "bredda";         }
-badword channel { word "car";       replace "taxi";           }
-badword channel { word "discord";   replace "dicsord";        }
-badword channel { word "efnet";     replace "efrael";         }
-badword channel { word "hate";      replace "04 hate "; }
-badword channel { word "hello";     replace "smello";         }
-badword channel { word "house";     replace "flat";           }
-badword channel { word "gaming";    replace "gaymen";         }
-badword channel { word "im";        replace "m";              }
-badword channel { word "i'm";       replace "m";              }
-badword channel { word "i am";      replace "m";              }
-badword channel { word "ima";       replace "m";              }
-badword channel { word "my";        replace "me";             }
-badword channel { word "(nigger)";  replace "angel";          }
-badword channel { word "np";        replace "mp";             }
-badword channel { word "on";        replace "pon";            }
-badword channel { word "same";      replace "salami";         }
-badword channel { word "(skyp)";    replace "skik";           }
-badword channel { word "(ss)";      replace "ϟϟ";             }
-badword channel { word "(troll)";   replace "papillion";      }
-badword channel { word "uber";      replace "HELLS ANGELS";   }
-badword channel { word "(year)";    replace "yonk";           }
+badword channel { word "apartment";  replace "flat";           }
+badword channel { word "banana";     replace "bogoya";         }
+badword channel { word "bitch";      replace "wombat";         }
+badword channel { word "bro";        replace "bo";             }
+badword channel { word "(brother)";  replace "bredda";         }
+badword channel { word "bruh";       replace "bredda";         }
+badword channel { word "car";        replace "taxi";           }
+badword channel { word "cool";       replace "safe";           }
+badword channel { word "drunk";      replace "buck";           }
+badword channel { word "dude";       replace "blud";           }
+badword channel { word "discord";    replace "dicksword";      }
+badword channel { word "elaborate";  replace "ebloggerate";    }
+badword channel { word "efnet";      replace "efrael";         }
+badword channel { word "fuckin";     replace "blood clot";     }
+badword channel { word "fucking";    replace "blood clot";     }
+badword channel { word "fuckn";      replace "blood clot";     }
+badword channel { word "hate";       replace "04 hate "; }
+badword channel { word "hello";      replace "smello";         }
+badword channel { word "high";       replace "HIE";            }
+badword channel { word "home";       replace "flat";           }
+badword channel { word "house";      replace "flat";           }
+badword channel { word "gaming";     replace "gaymen";         }
+badword channel { word "im";         replace "m";              }
+badword channel { word "i'm";        replace "m";              }
+badword channel { word "i am";       replace "m";              }
+badword channel { word "ima";        replace "m";              }
+badword channel { word "jfc";        replace "bloody hell";    }
+badword channel { word "my";         replace "me";             }
+badword channel { word "nice";       replace "safe";           }
+badword channel { word "(nigger)";   replace "angel";          }
+badword channel { word "np";         replace "mp";             }
+badword channel { word "on";         replace "pon";            }
+badword channel { word "same";       replace "salami";         }
+badword channel { word "shitfaced";  replace "buck";           }
+badword channel { word "shit faced"; replace "buck";           }
+badword channel { word "shoes";      replace "kicks";          }
+badword channel { word "(skyp)";     replace "skik";           }
+badword channel { word "(ss)";       replace "ϟϟ";             }
+badword channel { word "(troll)";    replace "papillion";      }
+badword channel { word "uber";       replace "HELLS ANGELS";   }
+badword channel { word "(year)";     replace "yonk";           }
 
 badword channel { word "sup";       replace "wah gwaan"; }
 badword channel { word "wussup";    replace "wah gwaan"; }
@@ -33,15 +50,18 @@ badword channel { word "what's up"; replace "wah gwaan"; }
 badword channel { word "wuddup";    replace "wah gwaan"; }
 
 badword channel { word "gal";     replace "bint";  }
+badword channel { word "gf";      replace "bint";  }
 badword channel { word "(girl)";  replace "bint";  }
 badword channel { word "lady";    replace "bint";  }
 badword channel { word "ladies";  replace "bints"; }
 badword channel { word "(woman)"; replace "bint";  }
+badword channel { word "wife";    replace "bint";  }
 badword channel { word "women";   replace "bints"; }
 
 badword channel { word "ganja";     replace "bobby brown"; }
 badword channel { word "marijuana"; replace "bobby brown"; }
 badword channel { word "pot";       replace "bobby brown"; }
+badword channel { word "reefer";    replace "bobby brown"; }
 badword channel { word "weed";      replace "bobby brown"; }
 
 badword channel { word "kek";   replace "%%"; }
diff --git a/doc/conf/except.conf b/doc/conf/except.conf
@@ -1,28 +1,34 @@
 # IRCCloud
-except ban { mask *@5.254.36.56/29;     }
-except ban { mask *@192.184.9.108/32;   }
-except ban { mask *@192.184.9.112/32;   }
-except ban { mask *@192.184.10.118/32;  }
-except ban { mask *@192.184.10.9/32;    }
-except ban { mask *@192.184.8.103/32;   }
-except ban { mask *@2001:67c:2f08::/48; }
-except ban { mask *@2a03:5180:f::/62;   }
-except ban { mask *@2a03:5180:f:4::/63; }
-except ban { mask *@2a03:5180:f:6::/64; }
+except ban { 
+	mask *@5.254.36.56/29;
+	mask *@192.184.9.108/32;
+	mask *@192.184.9.112/32;
+	mask *@192.184.10.118/32;
+	mask *@192.184.10.9/32;
+	mask *@192.184.8.103/32;
+	mask *@2001:67c:2f08::/48;
+	mask *@2a03:5180:f::/62;
+	mask *@2a03:5180:f:4::/63;
+	mask *@2a03:5180:f:6::/64;
+}
 
 # KiwiIRC
-except ban { mask *@107.161.19.53;  }
-except ban { mask *@107.161.19.109; }
-except ban { mask *@109.169.31.4;   }
-except ban { mask *@109.169.31.13;  } # KiwiIRC Verify Bot (out.kiwiirc.com)
+except ban { 
+	mask *@107.161.19.53;
+	mask *@107.161.19.109;
+	mask *@109.169.31.4;
+	mask *@109.169.31.13; # KiwiIRC Verify Bot (out.kiwiirc.com)
+}
 
 # Mibbit
-except ban { mask *@207.192.75.252;                 } # ircip1.mibbit.com
-except ban { mask *@64.62.228.82;                   } # ircip2.mibbit.com
-except ban { mask *@78.129.202.38;                  } # ircip3.mibbit.com
-except ban { mask *@109.169.29.95;                  } # ircip4.mibbit.com
-except ban { mask *@97.107.138.109;                 } # bot.search.mibbit.com
-except ban { mask *@2600:3c03::f03c:91ff:fe96:c1fa; } # bot.search.mibbit.com
+except ban {
+	mask *@207.192.75.252;                 # ircip1.mibbit.com
+	mask *@64.62.228.82;                   # ircip2.mibbit.com
+	mask *@78.129.202.38;                  # ircip3.mibbit.com
+	mask *@109.169.29.95;                  # ircip4.mibbit.com
+	mask *@97.107.138.109;                 # bot.search.mibbit.com
+	mask *@2600:3c03::f03c:91ff:fe96:c1fa; # bot.search.mibbit.com
+}
 
 # Netsplit
-except ban { mask *@85.25.10.40;  } # anaconda.netsplit.de
-\ No newline at end of file
+except ban { mask *@85.25.10.40; } # anaconda.netsplit.de
+\ No newline at end of file
diff --git a/doc/conf/modules.conf b/doc/conf/modules.conf
@@ -1,5 +1,5 @@
 // Cloaking (+x)
-loadmodule "cloak";
+loadmodule "cloak_sha256";
 
 // User Commands (Minimal)
 #loadmodule "admin";
@@ -63,6 +63,7 @@ loadmodule "kill";
 #loadmodule "locops";
 loadmodule "mkpasswd";
 loadmodule "oper";
+loadmodule "operinfo";
 #loadmodule "opermotd";
 loadmodule "sajoin";
 loadmodule "samode";
@@ -76,7 +77,6 @@ loadmodule "tkl";
 loadmodule "trace";
 loadmodule "tsctl";
 loadmodule "unsqline";
-#loadmodule "wallops";
 
 // Server-2-Server Commands
 loadmodule "eos";
@@ -86,9 +86,11 @@ loadmodule "netinfo";
 loadmodule "server";
 loadmodule "sinfo";
 loadmodule "sjoin";
+loadmodule "slog";
 loadmodule "sqline";
 loadmodule "swhois";
 loadmodule "umode2";
+loadmodule "unreal_server_compat";
 
 // Services Commands
 loadmodule "sendsno";
@@ -108,25 +110,39 @@ loadmodule "svssno";
 loadmodule "svswatch";
 
 // Channel Modes
-loadmodule "chanmodes/censor";       /* +G */
-loadmodule "chanmodes/delayjoin";    /* +D */
-loadmodule "chanmodes/floodprot";    /* +f */
-loadmodule "chanmodes/history";      /* +H */
-loadmodule "chanmodes/issecure";     /* +Z */
-loadmodule "chanmodes/link";         /* +L */
-loadmodule "chanmodes/nocolor";      /* +c */
-loadmodule "chanmodes/noctcp";       /* +C */
-loadmodule "chanmodes/noinvite";     /* +V */
-loadmodule "chanmodes/nokick";       /* +Q */
-loadmodule "chanmodes/noknock";      /* +K */
-loadmodule "chanmodes/nonickchange"; /* +N */
-loadmodule "chanmodes/nonotice";     /* +T */
-loadmodule "chanmodes/operonly";     /* +O */
-loadmodule "chanmodes/permanent";    /* +P */
-loadmodule "chanmodes/regonly";      /* +R */
-loadmodule "chanmodes/regonlyspeak"; /* +M */
-loadmodule "chanmodes/secureonly";   /* +z */
-loadmodule "chanmodes/stripcolor";   /* +S */
+loadmodule "chanmodes/chanowner";      /* +q */
+loadmodule "chanmodes/chanadmin";      /* +a */
+loadmodule "chanmodes/chanop";         /* +o */
+loadmodule "chanmodes/halfop";         /* +h */
+loadmodule "chanmodes/voice";          /* +v */
+loadmodule "chanmodes/censor";         /* +G */
+loadmodule "chanmodes/delayjoin";      /* +D */
+loadmodule "chanmodes/floodprot";      /* +f */
+loadmodule "chanmodes/history";        /* +H */
+loadmodule "chanmodes/inviteonly";     /* +i */
+loadmodule "chanmodes/isregistered";   /* +r */
+loadmodule "chanmodes/issecure";       /* +Z */
+loadmodule "chanmodes/key";            /* +k */
+loadmodule "chanmodes/limit";          /* +l */
+loadmodule "chanmodes/link";           /* +L */
+loadmodule "chanmodes/moderated";      /* +m */
+loadmodule "chanmodes/nocolor";        /* +c */
+loadmodule "chanmodes/noctcp";         /* +C */
+loadmodule "chanmodes/noexternalmsgs"; /* +n */
+loadmodule "chanmodes/noinvite";       /* +V */
+loadmodule "chanmodes/nokick";         /* +Q */
+loadmodule "chanmodes/noknock";        /* +K */
+loadmodule "chanmodes/nonickchange";   /* +N */
+loadmodule "chanmodes/nonotice";       /* +T */
+loadmodule "chanmodes/operonly";       /* +O */
+loadmodule "chanmodes/permanent";      /* +P */
+loadmodule "chanmodes/private";        /* +p */
+loadmodule "chanmodes/regonly";        /* +R */
+loadmodule "chanmodes/regonlyspeak";   /* +M */
+loadmodule "chanmodes/secret";         /* +s */
+loadmodule "chanmodes/secureonly";     /* +z */
+loadmodule "chanmodes/stripcolor";     /* +S */
+loadmodule "chanmodes/topiclimit";     /* +t */
 
 // User Modes
 loadmodule "usermodes/bot";           /* +B */
@@ -139,24 +155,23 @@ loadmodule "usermodes/regonlymsg";    /* +R */
 loadmodule "usermodes/secureonlymsg"; /* +Z */
 loadmodule "usermodes/servicebot";    /* +S */
 #loadmodule "usermodes/showwhois";    /* +W */
-
-// Snomasks
-#loadmodule "snomasks/dccreject"; /* +D */
+#loadmodule "usermodes/wallops";      /* +w */
 
 // Extended Bans
-loadmodule "extbans/account";       /* +b ~a */
-#loadmodule "extbans/certfp";       /* +b ~S */
-#loadmodule "extbans/inchannel";    /* +b ~c */
-loadmodule "extbans/join";          /* +b ~j */
-loadmodule "extbans/msgbypass";     /* +e ~m */
-#loadmodule "extbans/nickchange";   /* +b ~n */
-#loadmodule "extbans/operclass";    /* +b ~O */
-#loadmodule "extbans/partmsg";      /* +b ~p */
-loadmodule "extbans/quiet";         /* +b ~q */
-#loadmodule "extbans/realname";     /* +b ~r */
-#loadmodule "extbans/textban";      /* +b ~T */
-loadmodule "extbans/timedban";      /* +b ~t */
-loadmodule "extbans/securitygroup"; /* +b ~G */
+loadmodule "extbans/account";       /* +b ~account        */
+loadmodule "extbans/certfp";        /* +b ~certfp         */
+#loadmodule "extbans/country";      /* +b ~country        */
+loadmodule "extbans/inchannel";     /* +b ~channel        */
+loadmodule "extbans/join";          /* +b ~join           */
+loadmodule "extbans/msgbypass";     /* +e ~msgbypass      */
+#loadmodule "extbans/nickchange";   /* +b ~nickchange     */
+#loadmodule "extbans/operclass";    /* +b ~operclass      */
+#loadmodule "extbans/partmsg";      /* +b ~partmsg        */
+loadmodule "extbans/quiet";         /* +b ~quiet          */
+#loadmodule "extbans/realname";     /* +b ~realname       */
+loadmodule "extbans/textban";       /* +b ~text           */
+loadmodule "extbans/timedban";      /* +b ~time           */
+loadmodule "extbans/securitygroup"; /* +b ~security-group */
 
 // IRCv3 Extensions
 loadmodule "account-notify";
@@ -166,10 +181,12 @@ loadmodule "bot-tag";
 loadmodule "chathistory";
 loadmodule "clienttagdeny";
 loadmodule "echo-message";
+loadmodule "extended-monitor";
 loadmodule "labeled-response";
 loadmodule "link-security";
 loadmodule "message-ids";
 loadmodule "message-tags";
+loadmodule "monitor";
 loadmodule "plaintext-policy";
 loadmodule "reply-tag";
 loadmodule "server-time";
@@ -178,24 +195,29 @@ loadmodule "typing-indicator";
 
 // Other
 loadmodule "antimixedutf8";
-loadmodule "authprompt";
+#loadmodule "authprompt";
 loadmodule "blacklist";
 loadmodule "certfp";
 loadmodule "channeldb";
 loadmodule "charsys";
 loadmodule "connthrottle";
+#loadmodule "geoip_base";
+#loadmodule "geoip_classic";
 loadmodule "hideserver";
 loadmodule "history_backend_mem";
 #loadmodule "history_backend_null";
 loadmodule "ident_lookup";
 loadmodule "jointhrottle";
+loadmodule "json-log-tag";
 loadmodule "targetfloodprot";
 loadmodule "tkldb";
 loadmodule "tls_antidos";
+loadmodule "tls_cipher";
 loadmodule "userhost-tag";
 loadmodule "userip-tag";
 loadmodule "reputation";
 loadmodule "restrict-commands";
 loadmodule "rmtkl";
+loadmodule "watch-backend";
 #loadmodule "webirc";
 #loadmodule "websocket";
 \ No newline at end of file
diff --git a/doc/conf/snomasks.conf b/doc/conf/snomasks.conf
@@ -0,0 +1,228 @@
+/* Server bans snomask - 'b' */
+log {
+	source {
+		tkl.BAN_REALNAME;
+		tkl.TKL_ADD;
+		tkl.TKL_DEL;
+		tkl.TKL_ADD_TEMPSHUN;
+		tkl.TKL_DEL_TEMPSHUN;
+		tkl.TKL_EXPIRE;
+		tkl.RMTKL_COMMAND;
+	}
+	destination {
+		snomask b;
+	}
+}
+
+/* Blacklist snomask: 'B' */
+log {
+	source {
+		blacklist;
+	}
+	destination {
+		snomask B;
+	}
+}
+
+/* Local client connects snomask - 'c' */
+log {
+	source {
+		connect.LOCAL_CLIENT_CONNECT;
+		connect.LOCAL_CLIENT_DISCONNECT;
+	}
+	destination {
+		snomask c;
+	}
+}
+
+/* Remote client connects snomask - 'C' */
+log {
+	source {
+		connect.REMOTE_CLIENT_CONNECT;
+		connect.REMOTE_CLIENT_DISCONNECT;
+	}
+	destination {
+		snomask C;
+	}
+}
+
+/* DCC rejections snomask - 'd' */
+log {
+	source {
+		dcc;
+	}
+	destination {
+		snomask d;
+	}
+}
+
+/* Debug snomask (not recommended) - 'D' */
+log {
+	source {
+		debug;
+	}
+	destination {
+		snomask D;
+	}
+}
+
+/* Floods snomask - 'f' */
+log {
+	source {
+		flood;
+	}
+	destination {
+		snomask f;
+	}
+}
+
+/* Join, parts, kicks - 'j' */
+log {
+	source {
+		// TODO: these don't exist yet..
+		join.LOCAL_CLIENT_JOIN;
+		join.REMOTE_CLIENT_JOIN;
+		part.LOCAL_CLIENT_PART;
+		part.REMOTE_CLIENT_PART;
+		kick.LOCAL_CLIENT_KICK;
+		kick.REMOTE_CLIENT_KICK;
+	}
+	destination {
+		snomask j;
+	}
+}
+
+/* Kill snomask */
+log {
+	source {
+		kill;
+	}
+	destination {
+		snomask k;
+	}
+}
+
+/* Local nick changes snomask - 'n' */
+log {
+	source {
+		nick.LOCAL_NICK_CHANGE;
+	}
+	destination {
+		snomask n;
+	}
+}
+
+/* Remote nick changes snomask - 'N' */
+log {
+	source {
+		nick.REMOTE_NICK_CHANGE;
+	}
+	destination {
+		snomask N;
+	}
+}
+
+/* Deny nick (QLINE) rejections snomask - 'q' */
+log {
+	source {
+		nick.QLINE_NICK_LOCAL_ATTEMPT;
+		nick.QLINE_NICK_REMOTE;
+	}
+	destination {
+		snomask q;
+	}
+}
+
+/* Spamfilter hits snomask - 'S' */
+log {
+	source {
+		tkl.SPAMFILTER_MATCH;
+	}
+	destination {
+		snomask S;
+	}
+}
+
+/* IRCOp overriding in channels (OperOverride) - 'o' */
+log {
+	source {
+		operoverride;
+	}
+	destination {
+		snomask o;
+	}
+}
+
+/* IRCOp changing user properties or forcing users to do things - 'O' */
+log {
+	source {
+		chgcmds;
+		sacmds;
+	}
+	destination {
+		snomask O;
+	}
+}
+
+/* VHOST usage - 'v' */
+log {
+	source {
+		vhost;
+	}
+	destination {
+		snomask v;
+	}
+}
+
+/* Snomask s (server notices) - the "catch all" snomask for all other things */
+log {
+	source {
+		link;
+		oper;
+		!debug;
+		nomatch;
+	}
+	destination {
+		snomask s;
+	}
+}
+
+/* These log sources are sent to all servers (globally).
+ * These are generally two categories:
+ * 1) Things that affect the network as a whole, eg linking
+ * 2) Things that otherwise cannot be logged by a remote server
+ *    that may interest ircops. Eg: a spamfilter match,
+ *    since that would otherwise not be propagated.
+ */
+log {
+	source {
+		/* All link messages affect the network so
+		 * these should be global. Except for the
+		 * link connecting... and timeout while
+		 * connecting.. messages, which can be noisy.
+		 */
+		link;
+		!link.LINK_CONNECTING;
+		!link.LINK_CONNECT_TIMEOUT;
+		!link.SERVER_LINKED_REMOTE;
+		!link.SERVER_LINKED;
+		/* All oper up/downs */
+		oper;
+		/* Flood messages, important to keep an eye on, network-wide */
+		flood;
+		/* TEMPSHUN: these are otherwise missing for snomask 'b' */
+		tkl.TKL_ADD_TEMPSHUN;
+		tkl.TKL_DEL_TEMPSHUN;
+		/* Spamfilter matches: needed for snomask 'S' */
+		tkl.SPAMFILTER_MATCH;
+		/* Critical issue: */
+		tls.TLS_CERT_EXPIRING;
+		/* SAMODE: needed for snomask 'o' */
+		samode.SAMODE_COMMAND;
+		/* Never any debug messages */
+		!debug;
+	}
+	destination {
+		remote;
+	}
+}
diff --git a/doc/conf/tls/curl-ca-bundle.crt b/doc/conf/tls/curl-ca-bundle.crt
@@ -1,7 +1,7 @@
 ##
 ## Bundle of CA Root Certificates
 ##
-## Certificate data from Mozilla as of: Tue Jan 19 04:12:04 2021 GMT
+## Certificate data from Mozilla as of: Tue Oct 26 03:12:05 2021 GMT
 ##
 ## This is a bundle of X.509 certificates of public Certificate Authorities
 ## (CA). These were automatically extracted from Mozilla's root certificates
@@ -14,7 +14,7 @@
 ## Just configure this file as the SSLCACertificateFile.
 ##
 ## Conversion done with mk-ca-bundle.pl version 1.28.
-## SHA256: 3bdc63d1de27058fec943a999a2a8a01fcc6806a611b19221a7727d3d9bbbdfd
+## SHA256: bb36818a81feaa4cca61101e6d6276cd09e972efcb08112dfed846918ca41d7f
 ##
 
 
@@ -156,38 +156,6 @@ Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
 12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
 -----END CERTIFICATE-----
 
-QuoVadis Root CA
-================
------BEGIN CERTIFICATE-----
-MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE
-ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
-eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz
-MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp
-cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD
-EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
-AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk
-J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL
-F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL
-YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen
-AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w
-PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y
-ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7
-MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj
-YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
-ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
-Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW
-Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu
-BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw
-FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0
-aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6
-tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo
-fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul
-LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x
-gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi
-5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi
-5nrQNiOKSnQ2+Q==
------END CERTIFICATE-----
-
 QuoVadis Root CA 2
 ==================
 -----BEGIN CERTIFICATE-----
@@ -275,26 +243,6 @@ s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
 FL39vmwLAw==
 -----END CERTIFICATE-----
 
-Sonera Class 2 Root CA
-======================
------BEGIN CERTIFICATE-----
-MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
-U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw
-NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
-IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3
-/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT
-dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG
-f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P
-tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH
-nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT
-XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt
-0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI
-cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph
-Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx
-EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH
-llpwrN9M
------END CERTIFICATE-----
-
 XRamp Global CA Root
 ====================
 -----BEGIN CERTIFICATE-----
@@ -433,26 +381,6 @@ mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
 vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
 -----END CERTIFICATE-----
 
-DST Root CA X3
-==============
------BEGIN CERTIFICATE-----
-MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK
-ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
-DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1
-cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD
-ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT
-rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9
-UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy
-xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d
-utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T
-AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ
-MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug
-dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE
-GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw
-RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS
-fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
------END CERTIFICATE-----
-
 SwissSign Gold CA - G2
 ======================
 -----BEGIN CERTIFICATE-----
@@ -718,51 +646,6 @@ vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
 TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
 -----END CERTIFICATE-----
 
-GeoTrust Primary Certification Authority - G2
-=============================================
------BEGIN CERTIFICATE-----
-MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC
-VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu
-Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD
-ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1
-OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
-MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl
-b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG
-BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc
-KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD
-VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+
-EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m
-ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2
-npaqBA+K
------END CERTIFICATE-----
-
-VeriSign Universal Root Certification Authority
-===============================================
------BEGIN CERTIFICATE-----
-MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
-BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
-ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
-IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u
-IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV
-UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
-cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
-IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0
-aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj
-1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP
-MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72
-9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I
-AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR
-tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G
-CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O
-a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
-DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3
-Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx
-Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx
-P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P
-wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4
-mJO37M2CYfE45k+XmCpajQ==
------END CERTIFICATE-----
-
 NetLock Arany (Class Gold) Főtanúsítvány
 ========================================
 -----BEGIN CERTIFICATE-----
@@ -938,82 +821,6 @@ Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
 WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
 -----END CERTIFICATE-----
 
-Chambers of Commerce Root - 2008
-================================
------BEGIN CERTIFICATE-----
-MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
-MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
-bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
-QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
-Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
-ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
-EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
-cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
-AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
-XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
-h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
-ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
-NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
-D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
-lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
-0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
-ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
-EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
-G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
-BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
-bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
-bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
-CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
-AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
-wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
-3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
-RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6
-M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
-YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
-9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
-zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
-nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
-OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
------END CERTIFICATE-----
-
-Global Chambersign Root - 2008
-==============================
------BEGIN CERTIFICATE-----
-MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
-MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
-bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
-QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
-NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
-Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
-QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
-aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
-VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
-XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
-ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
-/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
-TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
-H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
-Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
-HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
-wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
-AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
-BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
-BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
-aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
-aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
-1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
-dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
-/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
-ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
-dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
-9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
-foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
-qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
-P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
-c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
-09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
------END CERTIFICATE-----
-
 Go Daddy Root Certificate Authority - G2
 ========================================
 -----BEGIN CERTIFICATE-----
@@ -1315,27 +1122,6 @@ OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
 vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
 -----END CERTIFICATE-----
 
-Trustis FPS Root CA
-===================
------BEGIN CERTIFICATE-----
-MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG
-EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290
-IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV
-BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ
-KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ
-RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk
-H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa
-cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt
-o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA
-AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd
-BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c
-GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC
-yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P
-8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV
-l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl
-iB6XzCGcKQENZetX2fNXlrtIzYE=
------END CERTIFICATE-----
-
 Buypass Class 2 Root CA
 =======================
 -----BEGIN CERTIFICATE-----
@@ -1980,36 +1766,6 @@ uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
 yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
 -----END CERTIFICATE-----
 
-Staat der Nederlanden Root CA - G3
-==================================
------BEGIN CERTIFICATE-----
-MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
-CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
-Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC
-TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
-ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y
-olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t
-x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy
-EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K
-Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur
-mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5
-1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp
-07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo
-FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE
-41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB
-AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu
-yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD
-U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq
-KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1
-v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA
-8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b
-8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r
-mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq
-1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI
-JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV
-tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk=
------END CERTIFICATE-----
-
 Staat der Nederlanden EV Root CA
 ================================
 -----BEGIN CERTIFICATE-----
@@ -3226,3 +2982,251 @@ qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG
 I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg
 kpzNNIaRkPpkUZ3+/uul9XXeifdy
 -----END CERTIFICATE-----
+
+AC RAIZ FNMT-RCM SERVIDORES SEGUROS
+===================================
+-----BEGIN CERTIFICATE-----
+MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF
+UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy
+NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4
+MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt
+UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB
+QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
+BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2
+LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG
+SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD
+zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c=
+-----END CERTIFICATE-----
+
+GlobalSign Root R46
+===================
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV
+BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv
+b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX
+BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es
+CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/
+r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje
+2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt
+bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj
+K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4
+12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on
+ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls
+eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9
+vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM
+BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
+JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy
+gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92
+CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm
+OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq
+JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye
+qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz
+nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7
+DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3
+QEUxeCp6
+-----END CERTIFICATE-----
+
+GlobalSign Root E46
+===================
+-----BEGIN CERTIFICATE-----
+MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT
+AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg
+RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV
+BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB
+jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj
+QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL
+gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk
+vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
+CAezNIm8BZ/3Hobui3A=
+-----END CERTIFICATE-----
+
+GLOBALTRUST 2020
+================
+-----BEGIN CERTIFICATE-----
+MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
+IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
+VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
+BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
+MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
+D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
+VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
+CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
+fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
+A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
+JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
+DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
+clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
+mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
+IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
+VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
+4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
+iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
+8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
+HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
+vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
+oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
+YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
+gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
+-----END CERTIFICATE-----
+
+ANF Secure Server Root CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4
+NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv
+bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg
+Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw
+MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw
+EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC
+AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz
+BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv
+T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv
+B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse
+zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM
+VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j
+7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z
+JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe
+8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO
+Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
+o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E
+BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ
+UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx
+j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt
+dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM
+5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb
+5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54
+EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H
+hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy
+g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3
+r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
+-----END CERTIFICATE-----
+
+Certum EC-384 CA
+================
+-----BEGIN CERTIFICATE-----
+MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ
+TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2
+MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh
+dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
+GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq
+vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn
+iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo
+ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0
+QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
+-----END CERTIFICATE-----
+
+Certum Trusted Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG
+EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew
+HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY
+QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p
+fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52
+HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2
+fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt
+g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4
+NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk
+fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ
+P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY
+njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK
+HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
+vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL
+LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s
+ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K
+h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8
+CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA
+4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo
+WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj
+6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT
+OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck
+bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
+-----END CERTIFICATE-----
+
+TunTrust Root CA
+================
+-----BEGIN CERTIFICATE-----
+MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG
+A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj
+dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw
+NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD
+ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz
+2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b
+bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7
+NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd
+gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW
+VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f
+Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ
+juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas
+DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS
+VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI
+04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
+90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl
+0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd
+Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY
+YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp
+adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x
+xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP
+jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM
+MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z
+ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r
+AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
+-----END CERTIFICATE-----
+
+HARICA TLS RSA Root CA 2021
+===========================
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG
+EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
+cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz
+OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
+bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB
+IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN
+JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu
+a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y
+Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K
+5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv
+dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR
+0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH
+GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm
+haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ
+CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G
+A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU
+EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq
+QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD
+QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR
+j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5
+vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0
+qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6
+Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/
+PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn
+kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=
+-----END CERTIFICATE-----
+
+HARICA TLS ECC Root CA 2021
+===========================
+-----BEGIN CERTIFICATE-----
+MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH
+UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
+QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX
+DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj
+IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv
+b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l
+AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b
+ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
+0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi
+rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
+CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
+-----END CERTIFICATE-----
diff --git a/doc/conf/unrealircd.hub.conf b/doc/conf/unrealircd.hub.conf
@@ -16,9 +16,10 @@ link services.supernets.org {
 	class servers;
 }
 
-log "ircd.log" { flags { errors; } maxsize 1K; }
-
-except ban { mask *@127.0.0.1; }
+log {
+	source { error; fatal; warn; }
+	destination { file "ircd.log" { maxsize 10M; } }
+}
 
 ulines { services.supernets.org; }
 
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
@@ -14,8 +14,8 @@ alias os { target operserv; type services; }
 class clients { pingfreq 120; maxclients 100; sendq 1M; options { nofakelag; } }
 class servers { pingfreq 120; maxclients  10; sendq 1M; connfreq 30;           }
 
-allow { ip *;         class clients; maxperip 2;  global-maxperip 2;  }
-allow { ip 127.0.0.1; class clients; maxperip 10; global-maxperip 10; }
+allow { mask *;         class clients; maxperip 2;  global-maxperip 2;  }
+allow { mask 127.0.0.1; class clients; maxperip 10; global-maxperip 10; }
 
 #require authentication {
 #	mask *@*;
@@ -26,9 +26,10 @@ listen { ip *; port 6667;      options { clientsonly;      } }
 listen { ip *; port 6697;      options { clientsonly; tls; } }
 listen { ip *; port REDACTED;  options { serversonly; tls; } }
 
-deny channel { channel "#pumpcoin"; reason "This channel has moved to #exchange";  redirect "#exchange";  }
 deny channel { channel "#dev";      reason "This channel has moved to #superbowl"; redirect "#superbowl"; }
 deny channel { channel "#help";     reason "This channel has moved to #superbowl"; redirect "#superbowl"; }
+deny channel { channel "#mensa";    reason "This channel has been closed";         redirect "#superbowl"; }
+deny channel { channel "#pumpcoin"; reason "This channel has moved to #exchange";  redirect "#exchange";  }
 
 link irc.supernets.org {
 	incoming { mask REDACTED; }
@@ -42,7 +43,15 @@ link irc.supernets.org {
 	class servers;
 }
 
-log "errors.log" { flags { errors; } maxsize 10K; }
+log {
+	source { error; fatal; warn; }
+	destination { file "ircd.log" { maxsize 10M; } }
+}
+
+log {
+	source { all; }
+	destination { channel "#REDACTED" }
+}
 
 tld { mask *@*; motd remote.motd; rules remote.motd; options { remote; } }
 
@@ -81,39 +90,28 @@ blacklist torbl {
 	reason "8,4   E N T E R   T H E   V O I D   ";
 }
 
-webirc { mask 107.161.19.53;  password "REDACTED"; } # KiwiIRC
-webirc { mask 107.161.19.109; password "REDACTED"; }
-webirc { mask 107.161.31.4;   password "REDACTED"; }
-
-webirc { mask 207.192.75.252; password "REDACTED"; } # Mibbit
-webirc { mask 64.62.228.82;   password "REDACTED"; }
-webirc { mask 78.129.202.38;  password "REDACTED"; }
-webirc { mask 109.169.29.95 ; password "REDACTED"; }
-
 set {
 	kline-address "enterthevoid@supernets.org";
 	gline-address "enterthevoid@supernets.org";
 	modes-on-connect "+iIpTx";
 	modes-on-oper "+Hq";
-	snomask-on-oper "+bcFfkGsSo";
+	snomask-on-oper "+bBcCfksSoO";
 	modes-on-join "+ns";
 	level-on-join "op";
 	restrict-usermodes "ips";
 	restrict-channelmodes "nLpPs";
 	restrict-commands {
-		channel-message { connect-delay 60;   exempt-identified yes; exempt-reputation-score 100; }
-		channel-notice  { connect-delay 60;   exempt-identified yes; exempt-reputation-score 100; }
-		invite          { connect-delay 3600; exempt-identified yes; exempt-reputation-score 100; }
-		join            { connect-delay 15;   exempt-identified yes; exempt-reputation-score 100; }
-		list            { connect-delay 30;   exempt-identified yes; exempt-reputation-score 100; }
-		private-message { connect-delay 300;  exempt-identified yes; exempt-reputation-score 100; }
-		private-notice  { connect-delay 3600; exempt-identified yes; exempt-reputation-score 100; }
+		channel-message { connect-delay 60;  exempt-identified yes; exempt-reputation-score 100; }
+		channel-notice  { connect-delay 60;  exempt-identified yes; exempt-reputation-score 100; }
+		invite          { connect-delay 300; exempt-identified yes; exempt-reputation-score 100; }
+		join            { connect-delay 15;  exempt-identified yes; exempt-reputation-score 100; }
+		list            { connect-delay 30;  exempt-identified yes; exempt-reputation-score 100; }
+		private-message { connect-delay 300; exempt-identified yes; exempt-reputation-score 100; }
+		private-notice  { connect-delay 300; exempt-identified yes; exempt-reputation-score 100; }
 	}
-	#auto-join "#superbowl";
-	oper-auto-join "#superbowl";
+	auto-join "#superbowl";
 	static-quit "EMO-QUIT";
 	static-part "EMO-PART";
-	who-limit 100;
 	nick-length 20;
 	maxchannelsperuser 10;
 	channel-command-prefix "`!@$.";
@@ -134,15 +132,15 @@ set {
 		user warn;
 		oper deny;
 		server deny;
-		user-message "4WARNING: You are not using a secure (SSL/TLS) connection";
-		oper-message "Network operators must connect using SSL/TLS";
+		user-message "4WARNING: You are not on a secure TLS connection";
+		oper-message "Network operators must be on a secure TLS connection";
 	}
 	outdated-tls-policy {
 		user warn;
 		oper deny;
 		server deny;
 		user-message "4WARNING: You are using an outdated SSL/TLS protocol or cipher";
-		oper-message "Network operators must connect using an up-to-date SSL/TLS protocol or cipher";
+		oper-message "Network operators must be using an up-to-date SSL/TLS protocol & cipher";
 	}
 	anti-flood {
 		everyone {
@@ -152,14 +150,14 @@ set {
 				ban-action gzline;
 				ban-time 1h;
 			}
-			target-flood {
-				channel-notice  15:5;
-				channel-privmsg 45:5;
-				channel-tagmsg  15:5;
-				private-notice  10:5;
-				private-privmsg 30:5;
-				private-tagmsg  10:5;
-			}
+			#target-flood {
+			#	channel-notice  15:5;
+			#	channel-privmsg 45:5;
+			#	channel-tagmsg  15:5;
+			#	private-notice  10:5;
+			#	private-privmsg 30:5;
+			#	private-tagmsg  10:5;
+			#}
 		}
 		known-users {
 			away-flood   3:300;
@@ -171,6 +169,8 @@ set {
 				users 5;
 				new-user-every 60s;
 			}
+			lag-penalty 10; # update?
+			lag-penalty-bytes 0;
 		}
 		unknown-users {
 			away-flood   3:300;
@@ -182,6 +182,8 @@ set {
 				users 3;
 				new-user-every 60s;
 			}
+			lag-penalty 1000;
+			lag-penalty-bytes 90;
 		}
 	}
 	default-bantime 30d;
@@ -207,32 +209,50 @@ set {
 		ban-reason "8,4   E N T E R   T H E   V O I D   ";
 	}
 	connthrottle {
-		known-users   { minimum-reputation-score 100; sasl-bypass yes;       }
-		new-users     { local-throttle 20:60;         global-throttle 30:60; }
-		disabled-when { reputation-gathering 1w;      start-delay 3m;        }
+		known-users   { minimum-reputation-score 25; sasl-bypass yes;       }
+		new-users     { local-throttle 20:60;        global-throttle 30:60; }
+		disabled-when { reputation-gathering 1w;     start-delay 3m;        }
 	}
 	history {
 		channel {
-			playback-on-join { lines 100; time 1d; }
+			playback-on-join { lines 1000; time 1d; }
 			max-storage-per-channel {
-				registered   { lines 100; time 1d; } 
-				unregistered { lines 50;  time 1h; } 
+				registered   { lines 1000; time 1d; } 
+				unregistered { lines 100;  time 1h; } 
 			}
 		}
 	}
-	hide-idle-time { policy usermode; }
+	hide-idle-time { policy always; }
+	whois-details {
+		basic           { everyone full; }
+		modes           { everyone none;    self full; oper full; }
+		realhost        { everyone none;    self full; oper full; }
+		registered-nick { everyone full; }
+		channels        { everyone limited; self full; oper full; }
+		server          { everyone full; }
+		away            { everyone full; }
+		oper            { everyone limited; self full; oper full; }
+		secure          { everyone limited; self full; oper full; }
+		bot             { everyone full; }
+		services        { everyone full; }
+		reputation      { everyone none;    self none; oper full; }
+		geo             { everyone none;    self none; oper full; }
+		certfp          { everyone full; }
+		shunned         { everyone none;    self none; oper full; }
+		account         { everyone full; }
+		swhois          { everyone full; }
+		idle            { everyone limited; self full; oper full; }
+	}
 }
 
 hideserver {
 	disable-map yes;
 	disable-links yes;
-	map-deny-message "Denied";
-	links-deny-message "Denied";
+	map-deny-message "8,4   E N T E R   T H E   V O I D   ";
+	links-deny-message "8,4   E N T E R   T H E   V O I D   ";
 }
 
 security-group known-users {
 	identified yes;
-	webirc no;
-	tls no;
-	reputation-score 100;
+	reputation-score 25;
 }
 \ No newline at end of file
diff --git a/doc/translations.txt b/doc/translations.txt
@@ -1,6 +1,6 @@
 ==[ Translations ]===========================================================
 
-In UnrealIRCd 5 we support the following translations:
+In UnrealIRCd we support the following translations:
 * on-line documentation at https://www.unrealircd.org/docs/ (wiki!)
 * help.conf
 * example.conf
diff --git a/extras/build-tests/nix/build b/extras/build-tests/nix/build
@@ -16,7 +16,7 @@ else
 	export MAKE="make -j4"
 fi
 
-export CPPFLAGS="-DFAKELAG_CONFIGURABLE -DNOREMOVETMP"
+export CPPFLAGS="-DFAKELAG_CONFIGURABLE -DNOREMOVETMP -DRAWCMDLOGGING"
 
 # !! skipped for now: extras/build-tests/nix/select-config $BUILDCONFIG !!
 # !! temporary use this:
@@ -27,6 +27,16 @@ if lsb_release -av 2>&1|egrep 'Debian.*jessie'; then
 	echo "Disabling ASan due to false positives on deb8"
 	echo 'EXTRAPARA="--enable-werror --disable-asan"' >>config.settings
 fi
+if uname -s|grep -i freebsd; then
+	echo "Disabling ASan on FreeBSD due to 100% CPU loop in OpenSSL initialization routine"
+	echo 'EXTRAPARA="--enable-werror --disable-asan"' >>config.settings
+fi
+
+# If SSLDIR is set the environment, this overrides config.settings
+# Used for example in the openssl3 build tests.
+if [ "$SSLDIR" != "" ]; then
+	echo 'SSLDIR="'"$SSLDIR"'"' >>config.settings
+fi
 
 # Read config.settings, this makes a couple of variables available to us.
 . ./config.settings
@@ -36,10 +46,10 @@ if [ "$SSLDIR" != "" ]; then
 fi
 ./Config -quick || (tail -n 5000 config.log; exit 1)
 $MAKE
-yes ''|make pem
-make
+yes ''|$MAKE pem
+$MAKE || exit 1
 ./unrealircd module install third/dumpcmds
-make install
+$MAKE install || exit 1
 
 set +x
 echo ""
diff --git a/extras/build-tests/nix/configs/default b/extras/build-tests/nix/configs/default
@@ -1,3 +1,9 @@
+# These are the settings saved from running './Config'.
+# Note that it is not recommended to edit config.settings by hand!
+# Chances are you misunderstand what a variable does or what the
+# supported values are. You better just re-run the ./Config script
+# and answer appropriately there, to get a correct config.settings
+# file.
 #
 BASEPATH=$HOME/unrealircd
 BINDIR=$HOME/unrealircd/bin
@@ -9,16 +15,16 @@ CACHEDIR=$HOME/unrealircd/cache
 DOCDIR=$HOME/unrealircd/doc
 TMPDIR=$HOME/unrealircd/tmp
 PRIVATELIBDIR=$HOME/unrealircd/lib
-PREFIXAQ="1"
-MAXCONNECTIONS="1024"
+MAXCONNECTIONS_REQUEST="auto"
 NICKNAMEHISTORYLENGTH="2000"
+GEOIP="classic"
 DEFPERM="0600"
 SSLDIR=""
 REMOTEINC=""
 CURLDIR=""
-SHOWLISTMODES="1"
 NOOPEROVERRIDE=""
 OPEROVERRIDEVERIFY=""
 GENCERTIFICATE="0"
-EXTRAPARA="--enable-werror --enable-asan"
+SANITIZER="asan"
+EXTRAPARA="--enable-werror"
 ADVANCED=""
diff --git a/extras/build-tests/nix/run-tests b/extras/build-tests/nix/run-tests
@@ -19,8 +19,13 @@ if [ ! -d ~/cipherscan ]; then
 	git clone -q https://github.com/mozilla/cipherscan
 fi
 
+if [ "$HOSTNAME" = "deb8" ]; then
+	echo "Not running tests on Debian 8. It's LTS is EOL and trouble with running tests."
+	exit 0
+fi
+
 # Install 'unrealircd-tests'
-git clone -q https://github.com/unrealircd/unrealircd-tests.git
+git clone -q --branch unreal60 https://github.com/unrealircd/unrealircd-tests.git unrealircd-tests
 cd unrealircd-tests
 
 # FreeBSD has various issues with the tests from us and others,
diff --git a/extras/build-tests/windows/build.bat b/extras/build-tests/windows/build.bat
@@ -16,35 +16,44 @@ rem cinst innosetup -y
 
 rem Installing UnrealIRCd dependencies
 cd \projects
-mkdir unrealircd-5-libs
-cd unrealircd-5-libs
-curl -fsS -o unrealircd-libraries-5-devel.zip https://www.unrealircd.org/files/dev/win/libs/unrealircd-libraries-5-devel.zip
-unzip unrealircd-libraries-5-devel.zip
-copy dlltool.exe \users\user\worker\unreal5-w10\build /y
+mkdir unrealircd-6-libs
+cd unrealircd-6-libs
+curl -fsS -o unrealircd-libraries-6-devel.zip https://www.unrealircd.org/files/dev/win/libs/unrealircd-libraries-6-devel.zip
+unzip unrealircd-libraries-6-devel.zip
+copy dlltool.exe \users\user\worker\unreal6-w10\build /y
 
-rem for appveyor: cd \projects\unrealircd
-cd \users\user\worker\unreal5-w10\build
+rem for appveyor, use: cd \projects\unrealircd
+cd \users\user\worker\unreal6-w10\build
 
-rem Now the actual build
-call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat
+rem Install 'unrealircd-tests'
+cd ..
+rd /q/s unrealircd-tests
+git clone -q --branch unreal60 https://github.com/unrealircd/unrealircd-tests.git unrealircd-tests
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+cd build
 
-rem The above command will fail, due to missing symbol file
-rem However the symbol file can only be generated after the above command
-rem So... we create the symbolfile...
+rem Now the actual build
+rem - First this, otherwise JOM will fail
+IF NOT EXIST src\version.c nmake -f Makefile.windows CONF
+rem - Then build most of UnrealIRCd.exe etc
+call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat UNREALSVC.EXE UnrealIRCd.exe
+rem - It will fail due to missing symbolfile, which we create here..
 nmake -f makefile.windows SYMBOLFILE
-
-rem And we re-run the exact same command:
-call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat
+rem - Then we finalize building UnrealIRCd.exe: should be no error
+call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat UNREALSVC.EXE UnrealIRCd.exe
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+rem - Build all the modules (DLL files): should be no error
+call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat MODULES
 if %ERRORLEVEL% NEQ 0 EXIT /B 1
 
 rem Compile dependencies for unrealircd-tests -- this doesn't belong here though..
-curl -fsS -o src\modules\third\fakereputation.c https://raw.githubusercontent.com/unrealircd/unrealircd-tests/master/serverconfig/unrealircd/modules/fakereputation.c
+copy ..\unrealircd-tests\serverconfig\unrealircd\modules\fakereputation.c src\modules\third /Y
 call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat CUSTOMMODULE MODULEFILE=fakereputation
 if %ERRORLEVEL% NEQ 0 EXIT /B 1
 
-rem Convert c:\dev to c:\projects\unrealircd-5-libs
+rem Convert c:\dev to c:\projects\unrealircd-6-libs
 rem TODO: should use environment variable in innosetup script?
-sed -i "s/c:\\dev\\unrealircd-5-libs/c:\\projects\\unrealircd-5-libs/gi" src\windows\unrealinst.iss
+sed -i "s/c:\\dev\\unrealircd-6-libs/c:\\projects\\unrealircd-6-libs/gi" src\windows\unrealinst.iss
 
 rem Build installer file
 "c:\Program Files (x86)\Inno Setup 5\iscc.exe" /Q- src\windows\unrealinst.iss
@@ -60,7 +69,7 @@ taskkill -im unrealircd.exe -f
 sleep 2
 rem Just a safety measure so we don't end up testing
 rem some old version...
-del "C:\Program Files\UnrealIRCd 5\bin\unrealircd.exe"
+del "C:\Program Files\UnrealIRCd 6\bin\unrealircd.exe"
 
 echo Running installer...
 start /WAIT unrealircd-dev-build.exe /VERYSILENT /LOG=setup.log
@@ -70,12 +79,7 @@ rem Upload artifact
 rem appveyor PushArtifact unrealircd-dev-build.exe
 rem if %ERRORLEVEL% NEQ 0 EXIT /B 1
 
-rem Install 'unrealircd-tests'
-cd ..
-rd /q/s unrealircd-tests
-git clone https://github.com/unrealircd/unrealircd-tests.git
-if %ERRORLEVEL% NEQ 0 EXIT /B 1
-cd unrealircd-tests
+cd ..\unrealircd-tests
 dir
 
 rem All tests except db:
diff --git a/extras/build-tests/windows/compilecmd/vs2019.bat b/extras/build-tests/windows/compilecmd/vs2019.bat
@@ -1,21 +1,30 @@
 rem Build command for Visual Studio 2019
 
-nmake -f makefile.windows ^
-LIBRESSL_INC_DIR="c:\projects\unrealircd-5-libs\libressl\include" ^
-LIBRESSL_LIB_DIR="c:\projects\unrealircd-5-libs\libressl\lib" ^
-SSLLIB="crypto-46.lib ssl-48.lib" ^
+rem This used to start with:
+rem nmake -f makefile.windows ^
+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" ^
 USE_REMOTEINC=1 ^
-LIBCURL_INC_DIR="c:\projects\unrealircd-5-libs\curl\include" ^
-LIBCURL_LIB_DIR="c:\projects\unrealircd-5-libs\curl\builds\libcurl-vc-x64-release-dll-ssl-dll-cares-dll-ipv6-obj-lib" ^
-CARES_LIB_DIR="c:\projects\unrealircd-5-libs\c-ares\msvc\cares\dll-release" ^
-CARES_INC_DIR="c:\projects\unrealircd-5-libs\c-ares\include" ^
+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" ^
+CARES_LIB_DIR="c:\projects\unrealircd-6-libs\c-ares\msvc\cares\dll-release" ^
+CARES_INC_DIR="c:\projects\unrealircd-6-libs\c-ares\include" ^
 CARESLIB="cares.lib" ^
-PCRE2_INC_DIR="c:\projects\unrealircd-5-libs\pcre2\include" ^
-PCRE2_LIB_DIR="c:\projects\unrealircd-5-libs\pcre2\lib" ^
+PCRE2_INC_DIR="c:\projects\unrealircd-6-libs\pcre2\include" ^
+PCRE2_LIB_DIR="c:\projects\unrealircd-6-libs\pcre2\lib" ^
 PCRE2LIB="pcre2-8.lib" ^
-ARGON2_LIB_DIR="c:\projects\unrealircd-5-libs\argon2\vs2015\build" ^
-ARGON2_INC_DIR="c:\projects\unrealircd-5-libs\argon2\include" ^
+ARGON2_LIB_DIR="c:\projects\unrealircd-6-libs\argon2\vs2015\build" ^
+ARGON2_INC_DIR="c:\projects\unrealircd-6-libs\argon2\include" ^
 ARGON2LIB="Argon2RefDll.lib" ^
-SODIUM_LIB_DIR="c:\projects\unrealircd-5-libs\libsodium\bin\x64\Release\v142\dynamic" ^
-SODIUM_INC_DIR="c:\projects\unrealircd-5-libs\libsodium\src\libsodium\include" ^
-SODIUMLIB="libsodium.lib" %*
+SODIUM_LIB_DIR="c:\projects\unrealircd-6-libs\libsodium\bin\x64\Release\v142\dynamic" ^
+SODIUM_INC_DIR="c:\projects\unrealircd-6-libs\libsodium\src\libsodium\include" ^
+SODIUMLIB="libsodium.lib" ^
+JANSSON_LIB_DIR="c:\projects\unrealircd-6-libs\jansson\lib" ^
+JANSSON_INC_DIR="c:\projects\unrealircd-6-libs\jansson\include" ^
+JANSSONLIB="jansson.lib" ^
+GEOIPCLASSIC_LIB_DIR="c:\projects\unrealircd-6-libs\GeoIP\libGeoIP" ^
+GEOIPCLASSIC_INC_DIR="c:\projects\unrealircd-6-libs\GeoIP\libGeoIP" ^
+GEOIPCLASSICLIB="GeoIP.lib" %*
diff --git a/extras/c-ares.tar.gz b/extras/c-ares.tar.gz
Binary files differ.
diff --git a/extras/curlinstall b/extras/curlinstall
@@ -4,7 +4,7 @@ OUTF="curl-latest.tar.gz"
 OUTD="curl-latest"
 ARESPATH="`pwd`/extras/c-ares"
 UNREALDIR="`pwd`"
-CARESVERSION="1.17.1"
+CARESVERSION="1.17.2"
 LIBDIR="$1"
 
 if [ "x$1" = "x" ]; then
diff --git a/extras/doxygen/Developers.md b/extras/doxygen/Developers.md
@@ -1,4 +1,4 @@
-Welcome to the doxygen-generated documentation for the UnrealIRCd 5.x API.
+Welcome to the doxygen-generated documentation for the UnrealIRCd 6.x API.
 This is intended **for developers only!**
 
 If you are creating a 3rd party module for UnrealIRCd or are interested
diff --git a/extras/doxygen/Doxyfile b/extras/doxygen/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME           = "UnrealIRCd"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 5.2.0.1
+PROJECT_NUMBER         = 6.0.1.1
 
 # 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/geoip-classic.tar.gz b/extras/geoip-classic.tar.gz
Binary files differ.
diff --git a/extras/jansson.tar.gz b/extras/jansson.tar.gz
Binary files differ.
diff --git a/extras/security/apparmor/unrealircd b/extras/security/apparmor/unrealircd
@@ -1,9 +1,9 @@
-# AppArmor profile for UnrealIRCd 5
+# AppArmor profile for UnrealIRCd 6
 #
 # Note that you may still see some DENIED warnings in logs with
 # operation="chmod". These are harmless and can be safely ignored.
 #
-# Tested on Ubuntu 16.04 LTS and Ubuntu 18.04 LTS
+# Tested on Ubuntu 16.04 LTS, Ubuntu 18.04 LTS, Ubuntu 20.04 LTS
 #
 # IMPORTANT: you will have to modify the path to executable below
 #            if it's not /home/ircd/unrealircd/bin/unrealircd !
diff --git a/extras/tests/tls/cipherscan_profiles/openssl-300.txt b/extras/tests/tls/cipherscan_profiles/openssl-300.txt
@@ -0,0 +1,27 @@
+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/extras/tls.cnf b/extras/tls.cnf
@@ -18,7 +18,7 @@ stateOrProvinceName_default     = New York
 localityName                    = Locality Name (eg, city)
 
 0.organizationName              = Organization Name (eg, company)
-0.organizationName_default      = SuperNETs
+0.organizationName_default      = IRC geeks
 
 organizationalUnitName          = Organizational Unit Name (eg, section)
 organizationalUnitName_default  = IRCd  
diff --git a/extras/unrealircd-upgrade-script.in b/extras/unrealircd-upgrade-script.in
@@ -1,11 +1,11 @@
-#!/bin/bash
+#!/usr/bin/env bash
 #
 # This is stage 1 of the UnrealIRCd upgrade script
 # It downloads stage 2 online, verifies the integrity, and then
 # passes control to it to proceed with the rest of the upgrade.
 #
-# This is a bash script, so it is less cross-platform than
-# the rest of UnrealIRCd. We also mostly assume Linux here.
+# This is a bash script, so it is less cross-platform than the
+# rest of UnrealIRCd. We also mostly assume Linux/FreeBSD here.
 #
 
 BUILDDIR="@BUILDDIR@"
@@ -55,13 +55,19 @@ if [ ! -d "$BUILDDIR" ]; then
 	exit 1
 fi
 
+FETCHER="wget"
 if ! wget --help 1>/dev/null 2>&1; then
-	echo "The tool 'wget' is missing, which is used by this script."
-	echo "On Linux consider running 'sudo apt install wget' or 'sudo yum install wget'"
-	echo "and run this script again."
-	echo "Or, don't use this script and follow the manual upgrade procedure from"
-	echo "https://www.unrealircd.org/docs/Upgrading"
-	exit 1
+	# fetch is a pain: it always returns 1 (false) even for usage info and has no --version
+	fetch 1>/dev/null 2>&1
+	if [ "$?" -ne 1 ]; then
+		echo "The tool 'wget' is missing, which is used by this script."
+		echo "On Linux consider running 'sudo apt install wget' or 'sudo yum install wget'"
+		echo "and run this script again."
+		echo "Or, don't use this script and follow the manual upgrade procedure from"
+		echo "https://www.unrealircd.org/docs/Upgrading"
+		exit 1
+	fi
+	FETCHER="fetch"
 fi
 
 # Weird way to get version, but ok.
@@ -70,11 +76,16 @@ UNREALVER="`./configure --version|head -n1|awk '{ print $3 }'`"
 cd .. || fail "Could not cd back"
 
 # Set and export all variables with settings
-export UNREALVER BUILDDIR SCRIPTDIR DOCDIR TMPDIR
+export UNREALVER BUILDDIR SCRIPTDIR DOCDIR TMPDIR FETCHER
 
 # Download the install script
-wget -O unrealircd-upgrade-script.stage2 "https://www.unrealircd.org/downloads/unrealircd-upgrade-script.stage2?from=$UNREALVER" || fail "Could not download online installer"
-wget -O unrealircd-upgrade-script.stage2.asc "https://www.unrealircd.org/downloads/unrealircd-upgrade-script.stage2.asc" || fail "Could not download online installer signature"
+if [ "$FETCHER" = "wget" ]; then
+	wget -O unrealircd-upgrade-script.stage2 "https://www.unrealircd.org/downloads/unrealircd-upgrade-script.stage2?from=$UNREALVER" || fail "Could not download online installer"
+	wget -O unrealircd-upgrade-script.stage2.asc "https://www.unrealircd.org/downloads/unrealircd-upgrade-script.stage2.asc" || fail "Could not download online installer signature"
+else
+	fetch -o unrealircd-upgrade-script.stage2 "https://www.unrealircd.org/downloads/unrealircd-upgrade-script.stage2?from=$UNREALVER" || fail "Could not download online installer"
+	fetch -o unrealircd-upgrade-script.stage2.asc "https://www.unrealircd.org/downloads/unrealircd-upgrade-script.stage2.asc" || fail "Could not download online installer signature"
+fi
 
 # GPG verification - if available
 if gpg --version 1>/dev/null 2>&1; then
@@ -94,7 +105,11 @@ if gpg --version 1>/dev/null 2>&1; then
 	fi
 else
 	echo "WARNING: The GnuPG (GPG/PGP) verification tool 'gpg' is not installed."
-	echo "Consider running 'sudo apt install gpg' or 'yum install gnupg2'"
+	if [[ "$OSTYPE" == "freebsd"* ]] ; then
+		echo "Consider running 'sudo pkg install gnupg'"
+	else
+		echo "Consider running 'sudo apt install gpg' or 'yum install gnupg2'"
+	fi
 	echo "When 'gpg' is installed then the UnrealIRCd upgrade script can"
 	echo "verify the digital signature of the download file."
 	warn "Unable to check download integrity"
@@ -103,3 +118,6 @@ fi
 
 chmod +x unrealircd-upgrade-script.stage2
 ./unrealircd-upgrade-script.stage2 $*
+SAVERET="$?"
+rm -f unrealircd-upgrade-script.stage2 unrealircd-upgrade-script.stage2
+exit $SAVERET
diff --git a/include/channel.h b/include/channel.h
@@ -26,7 +26,7 @@
 
 #define	MODEBUFLEN	200
 
-#define ChannelExists(n)	(find_channel(n, NULL))
+#define ChannelExists(n)	(find_channel(n))
 
 /* NOTE: Timestamps will be added to MODE-commands, so never make
  * RESYNCMODES and MODEPARAMS higher than MAXPARA-3. DALnet servers
diff --git a/include/common.h b/include/common.h
@@ -98,7 +98,6 @@ extern int myncmp(const char *, const char *, int);
 extern char *strtoken(char **, char *, char *);
 
 extern MODVAR int  global_count, max_global_count;
-extern char *myctime(time_t);
 #ifdef _WIN32
 extern int gettimeofday(struct timeval *tp, void *tzp);
 #endif
@@ -174,18 +173,7 @@ extern MODVAR unsigned char char_atribs[];
 #define EXPAR2	extchmstr[1]
 #define EXPAR3	extchmstr[2]
 #define EXPAR4	extchmstr[3]
-
-#ifdef PREFIX_AQ
-#define CHPFIX        "(qaohv)~&@%+"
-#define CHPAR1        "beI"
-#else
-#define CHPFIX        "(ohv)@%+"
-#define CHPAR1        "beIqa"
-#endif /* PREFIX_AQ */
-
-#define CHPAR2        "k"
-#define CHPAR3        "l"
-#define CHPAR4        "psmntir"
+#define CHPAR1  "beI"
 
 #ifdef _WIN32
 /*
diff --git a/include/config.h b/include/config.h
@@ -98,9 +98,6 @@
  */
 /* #undef	DEBUGMODE */
 
-/* Similarly, DEBUG_IOENGINE can be used to debug the I/O engine. */
-/* #undef	DEBUG_IOENGINE */
-
 /*
  * Full pathnames and defaults of irc system's support files.
  */
@@ -126,7 +123,7 @@
  * Common usage for this are: a trusted bot ran by an IRCOp, that you only
  * want to give "flood access" and nothing else, and other such things.
  */
-#define FAKELAG_CONFIGURABLE
+//#undef FAKELAG_CONFIGURABLE
 
 /* The default value for class::sendq */
 #define DEFAULT_SENDQ	3000000
@@ -212,10 +209,25 @@
  * when there is no socket data waiting for us (no clients sending anything).
  * Was 2000ms in 3.2.x, 1000ms for versions below 3.4-alpha4.
  * 500ms in UnrealIRCd 4 (?)
- * 250ms in UnrealIRCd 5.
+ * 250ms in UnrealIRCd 5 and UnrealIRCd 6.
  */
 #define SOCKETLOOP_MAX_DELAY 250
 
+/* After how much time should we timeout downloads:
+ * DOWNLOAD_CONNECT_TIMEOUT: for the DNS and connect() / TLS_connect() call
+ * DOWNLOAD_TRANSFER_TIMEOUT: for the complete transfer (including connect)
+ * This can't be in the configuration file, as we need it while
+ * fetching the configuration file.. ;)
+ */
+#define DOWNLOAD_CONNECT_TIMEOUT 15
+#define DOWNLOAD_TRANSFER_TIMEOUT 45
+
+/* Maximum number of HTTP redirects to follow.
+ * Keep this reasonably low, as this may delay booting up to
+ * DOWNLOAD_TRANSFER_TIMEOUT * DOWNLOAD_MAX_REDIRECTS
+ */
+#define DOWNLOAD_MAX_REDIRECTS 2
+
 /*
  * Max time from the nickname change that still causes KILL
  * automaticly to switch for the current nick of that user. (seconds)
@@ -233,25 +245,34 @@
 #endif
 
 /* Maximum number of ModData objects that may be attached to an object */
-/* UnrealIRCd 4.0.0 - 4.0.13:  8,    8, 4, 4
- * UnrealIRCd 4.0.14+       : 12,    8, 4, 4
- * UnrealIRCd 5.0.0         : 12, 8, 8, 4, 4, 500, 500
+/* UnrealIRCd 4.0.0 - 4.0.13:  8,     8, 4, 4
+ * UnrealIRCd 4.0.14+       : 12,     8, 4, 4
+ * UnrealIRCd 5.0.0         : 12,  8, 8, 4, 4, 500, 500
+ * UnrealIRCd 6.0.0         : 24, 12, 8, 4, 4, 500, 500
  */
-#define MODDATA_MAX_CLIENT		 12
-#define MODDATA_MAX_LOCAL_CLIENT	  8
+#define MODDATA_MAX_CLIENT		 24
+#define MODDATA_MAX_LOCAL_CLIENT	 12
 #define MODDATA_MAX_CHANNEL		  8
 #define MODDATA_MAX_MEMBER		  4
 #define MODDATA_MAX_MEMBERSHIP		  4
 #define MODDATA_MAX_LOCAL_VARIABLE	500
 #define MODDATA_MAX_GLOBAL_VARIABLE	500
 
+/** Size of the member modes buffer, so can be max this-1 modes
+ * assigned to an individual user (and thus max prefixes as well).
+ * The default is 8, so 7 max modes, and is a bit tight.
+ * It allows for vhoaq (5) and then 2 additional ones from 3rd
+ * party modules.
+ */
+#define MEMBERMODESLEN	8
+
 /* If EXPERIMENTAL is #define'd then all users will receive a notice about
  * this when they connect, along with a pointer to bugs.unrealircd.org where
  * they can report any problems. This is mainly to help UnrealIRCd development.
  */
 #undef EXPERIMENTAL
 
-/* Default SSL/TLS cipherlist (except for TLS1.3, see further down).
+/* 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"
@@ -261,7 +282,7 @@
  */
 #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"
 
-/* Default SSL/TLS curves for ECDH(E)
+/* Default TLS curves for ECDH(E)
  * This can be changed via set::ssl::options::ecdh-curve in the config file.
  * NOTE: This requires openssl 1.0.2 or newer, otherwise these defaults
  *       are not applied, due to the missing openssl API call.
@@ -280,10 +301,8 @@
 #define	IRCD_PIDFILE PIDFILE
 
 #ifdef DEBUGMODE
- #define Debug(x) debug x
  #define LOGFILE LPATH
 #else
- #define Debug(x) ;
  #define LOGFILE "/dev/null"
 #endif
 
diff --git a/include/dbuf.h b/include/dbuf.h
@@ -70,7 +70,7 @@ typedef struct dbufbuf {
 **	memory as needed. Bytes are copied into internal buffers
 **	from users buffer.
 */
-void dbuf_put(dbuf *, char *, size_t);
+void dbuf_put(dbuf *, const char *, size_t);
 					/* Dynamic buffer header */
 					/* Pointer to data to be stored */
 					/* Number of bytes to store */
diff --git a/include/dynconf.h b/include/dynconf.h
@@ -32,26 +32,12 @@ struct FloodSettings {
 	long period[MAXFLOODOPTIONS];
 };
 
-typedef struct NetworkConfiguration NetworkConfiguration;
-struct NetworkConfiguration {
-	unsigned x_inah:1;
-	char *x_ircnetwork;
-	char *x_ircnet005;
-	char *x_defserv;
-	char *x_services_name;
-	char *x_hidden_host;
-	char *x_prefix_quit;
-	char *x_helpchan;
-	char *x_stats_server;
-	char *x_sasl_server;
-};
-
 enum UHAllowed { UHALLOW_ALWAYS, UHALLOW_NOCHANS, UHALLOW_REJOIN, UHALLOW_NEVER };
 
 struct ChMode {
-        long mode;
+	long mode;
 	long extmodes;
-	char *extparams[EXTCMODETABLESZ];
+	char *extparams[256];
 };
 
 typedef struct OperStat {
@@ -70,10 +56,9 @@ typedef enum HideIdleTimePolicy { HIDE_IDLE_TIME_NEVER=1, HIDE_IDLE_TIME_ALWAYS=
 /** The set { } block configuration */
 typedef struct Configuration Configuration;
 struct Configuration {
-	unsigned som:1;
+	unsigned show_opermotd:1;
 	unsigned hide_ulines:1;
 	unsigned flat_map:1;
-	unsigned allow_chatops:1;
 	unsigned ident_check:1;
 	unsigned fail_oper_warn:1;
 	unsigned show_connect_info:1;
@@ -86,8 +71,6 @@ struct Configuration {
 	unsigned allow_part_if_shunned:1;
 	unsigned disable_cap:1;
 	unsigned check_target_nick_bans:1;
-	unsigned use_egd : 1;
-	char *dns_bindip;
 	char *link_bindip;
 	long throttle_period;
 	char throttle_count;
@@ -100,11 +83,10 @@ struct Configuration {
 	char *oper_auto_join_chans;
 	char *allow_user_stats;
 	OperStat *allow_user_stats_ext;
-	int  ping_warning;
-	int  maxchannelsperuser;
-	int  maxdccallow;
-	int  anti_spam_quit_message_time;
-	char *egd_path;
+	int ping_warning;
+	int maxchannelsperuser;
+	int maxdccallow;
+	int anti_spam_quit_message_time;
 	char *static_quit;
 	char *static_part;
 	TLSOptions *tls_options;
@@ -122,12 +104,14 @@ struct Configuration {
 	char *restrict_usermodes;
 	char *restrict_channelmodes;
 	char *restrict_extendedbans;
+	int named_extended_bans;
 	char *channel_command_prefix;
 	long handshake_data_flood_amount;
 	long handshake_data_flood_ban_time;
 	int handshake_data_flood_ban_action;
 	struct ChMode modes_on_join;
-	int level_on_join;
+	int modes_on_join_set;
+	char *level_on_join;
 	FloodSettings *floodsettings;
 	int ident_connect_timeout;
 	int ident_read_timeout;
@@ -148,7 +132,6 @@ struct Configuration {
 	int maxbanlength;
 	int watch_away_notification;
 	int uhnames;
-	NetworkConfiguration network;
 	unsigned short default_ipv6_clone_mask;
 	int ping_cookie;
 	int min_nick_length;
@@ -176,6 +159,17 @@ struct Configuration {
 	BroadcastChannelMessagesOption broadcast_channel_messages;
 	AllowedChannelChars allowed_channelchars;
 	HideIdleTimePolicy hide_idle_time;
+	unsigned inah:1;
+	char *network_name;
+	char *network_name_005;
+	char *default_server;
+	char *services_name;
+	char *cloak_prefix;
+	char *prefix_quit;
+	char *helpchan;
+	char *stats_server;
+	char *sasl_server;
+	int server_notice_colors;
 };
 
 extern MODVAR Configuration iConf;
@@ -187,7 +181,7 @@ extern MODVAR int ipv6_disabled;
 #define CONN_MODES			iConf.conn_modes
 #define OPER_MODES			iConf.oper_modes
 #define OPER_SNOMASK			iConf.oper_snomask
-#define SHOWOPERMOTD			iConf.som
+#define SHOWOPERMOTD			iConf.show_opermotd
 #define HIDE_ULINES			iConf.hide_ulines
 #define FLAT_MAP			iConf.flat_map
 #define ALLOW_CHATOPS			iConf.allow_chatops
@@ -197,7 +191,6 @@ extern MODVAR int ipv6_disabled;
 #define DONT_RESOLVE			iConf.dont_resolve
 #define AUTO_JOIN_CHANS			iConf.auto_join_chans
 #define OPER_AUTO_JOIN_CHANS		iConf.oper_auto_join_chans
-#define DNS_BINDIP			iConf.dns_bindip
 #define LINK_BINDIP			iConf.link_bindip
 #define IDENT_CHECK			iConf.ident_check
 #define FAILOPER_WARN			iConf.fail_oper_warn
@@ -205,23 +198,17 @@ extern MODVAR int ipv6_disabled;
 #define NOCONNECTTLSLINFO		iConf.no_connect_tls_info
 #define ALLOW_USER_STATS			iConf.allow_user_stats
 #define ANTI_SPAM_QUIT_MSG_TIME		iConf.anti_spam_quit_message_time
-#ifdef HAVE_RAND_EGD
-#define USE_EGD				iConf.use_egd
-#else
-#define USE_EGD				0
-#endif
-#define EGD_PATH			iConf.egd_path
 
-#define ircnetwork			iConf.network.x_ircnetwork
-#define ircnet005			iConf.network.x_ircnet005
-#define defserv				iConf.network.x_defserv
-#define SERVICES_NAME		iConf.network.x_services_name
-#define hidden_host			iConf.network.x_hidden_host
-#define helpchan			iConf.network.x_helpchan
-#define STATS_SERVER			iConf.network.x_stats_server
-#define SASL_SERVER			iConf.network.x_sasl_server
-#define iNAH				iConf.network.x_inah
-#define PREFIX_QUIT			iConf.network.x_prefix_quit
+#define NETWORK_NAME			iConf.network_name
+#define NETWORK_NAME_005		iConf.network_name_005
+#define DEFAULT_SERVER			iConf.default_server
+#define SERVICES_NAME			iConf.services_name
+#define CLOAK_PREFIX			iConf.cloak_prefix
+#define HELP_CHANNEL			iConf.helpchan
+#define STATS_SERVER			iConf.stats_server
+#define SASL_SERVER			iConf.sasl_server
+#define iNAH				iConf.inah
+#define PREFIX_QUIT			iConf.prefix_quit
 
 #define STATIC_QUIT			iConf.static_quit
 #define STATIC_PART			iConf.static_part
@@ -232,7 +219,7 @@ extern MODVAR int ipv6_disabled;
 #define THROTTLING_PERIOD		iConf.throttle_period
 #define THROTTLING_COUNT		iConf.throttle_count
 #define USE_BAN_VERSION			iConf.use_ban_version
-#define MODES_ON_JOIN			iConf.modes_on_join.mode
+#define MODES_ON_JOIN			iConf.modes_on_join.extmodes
 #define LEVEL_ON_JOIN			iConf.level_on_join
 
 #define IDENT_CONNECT_TIMEOUT	iConf.ident_connect_timeout
@@ -309,7 +296,6 @@ struct SetCheck {
 	unsigned has_maxchannelsperuser:1;
 	unsigned has_maxdccallow:1;
 	unsigned has_anti_spam_quit_message_time:1;
-	unsigned has_egd_path:1;
 	unsigned has_static_quit:1;
 	unsigned has_static_part:1;
 	unsigned has_allow_userhost_change:1;
diff --git a/include/fdlist.h b/include/fdlist.h
@@ -7,6 +7,8 @@
 
 typedef void (*IOCallbackFunc)(int fd, int revents, void *data);
 
+typedef enum FDCloseMethod { FDCLOSE_SOCKET=0, FDCLOSE_FILE=1, FDCLOSE_NONE=3 } FDCloseMethod;
+
 typedef struct fd_entry {
 	int fd;
 	char desc[FD_DESC_SZ];
@@ -15,14 +17,14 @@ typedef struct fd_entry {
 	void *data;
 	time_t deadline;
 	unsigned char is_open;
+	FDCloseMethod close_method;
 	unsigned int backend_flags;
 } FDEntry;
 
 extern MODVAR FDEntry fd_table[MAXCONNECTIONS + 1];
 
-extern int fd_open(int fd, const char *desc);
-extern void fd_close(int fd);
-extern int fd_unmap(int fd);
+extern int fd_open(int fd, const char *desc, FDCloseMethod close_method);
+extern int fd_close(int fd);
 extern void fd_unnotify(int fd);
 extern int fd_socket(int family, int type, int protocol, const char *desc);
 extern int fd_accept(int sockfd);
diff --git a/include/h.h b/include/h.h
@@ -30,10 +30,6 @@
 
 extern MODVAR char *extraflags;
 extern MODVAR int tainted;
-/* for the new s_err.c */
-extern char *getreply(int);
-#define rpl_str(x) getreply(x)
-#define err_str(x) getreply(x)
 extern MODVAR Member *freemember;
 extern MODVAR Membership *freemembership;
 extern MODVAR Client me;
@@ -52,7 +48,7 @@ extern MODVAR char umodestring[UMODETABLESZ+1];
 #define get_recvq(x) ((x)->local->class->recvq ? (x)->local->class->recvq : DEFAULT_RECVQ)
 
 /* Configuration preprocessor */
-extern PreprocessorItem parse_preprocessor_item(char *start, char *end, char *filename, int linenumber, ConditionalConfig **cc);
+extern PreprocessorItem parse_preprocessor_item(char *start, char *end, const char *filename, int linenumber, ConditionalConfig **cc);
 extern void preprocessor_cc_duplicate_list(ConditionalConfig *r, ConditionalConfig **out);
 extern void preprocessor_cc_free_level(ConditionalConfig **cc_list, int level);
 extern void preprocessor_cc_free_list(ConditionalConfig *cc);
@@ -76,7 +72,6 @@ extern MODVAR ConfigItem_tld		*conf_tld;
 extern MODVAR ConfigItem_oper		*conf_oper;
 extern MODVAR ConfigItem_listen	*conf_listen;
 extern MODVAR ConfigItem_allow		*conf_allow;
-extern MODVAR ConfigItem_except	*conf_except;
 extern MODVAR ConfigItem_vhost		*conf_vhost;
 extern MODVAR ConfigItem_link		*conf_link;
 extern MODVAR ConfigItem_sni		*conf_sni;
@@ -85,9 +80,7 @@ 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_log		*conf_log;
 extern MODVAR ConfigItem_alias		*conf_alias;
-extern MODVAR ConfigItem_include	*conf_include;
 extern MODVAR ConfigItem_help		*conf_help;
 extern MODVAR ConfigItem_offchans	*conf_offchans;
 extern MODVAR SecurityGroup		*securitygroups;
@@ -97,10 +90,13 @@ extern EVENT(e_unload_module_delayed);
 extern EVENT(throttling_check_expire);
 
 extern void  module_loadall(void);
-extern long set_usermode(char *umode);
-extern char *get_usermode_string_raw(long umodes);
-extern ConfigFile *config_parse(char *filename, char *confdata);
-extern ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned int line_offset);
+extern long set_usermode(const char *umode);
+extern const char *get_usermode_string(Client *acptr);
+extern const char *get_usermode_string_r(Client *client, char *buf, size_t buflen);
+extern const char *get_usermode_string_raw(long umodes);
+extern const char *get_usermode_string_raw_r(long umodes, char *buf, size_t buflen);
+extern ConfigFile *config_parse(const char *filename, char *confdata);
+extern ConfigFile *config_parse_with_offset(const char *filename, char *confdata, unsigned int line_offset);
 extern void config_error(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2)));
 extern void config_warn(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2)));
 extern void config_error_missing(const char *filename, int line, const char *entry);
@@ -115,27 +111,25 @@ extern int config_is_blankorempty(ConfigEntry *cep, const char *block);
 extern MODVAR int config_verbose;
 extern void config_entry_free(ConfigEntry *ce);
 extern void config_entry_free_all(ConfigEntry *ce);
-extern ConfigFile *config_load(char *filename, char *displayname);
+extern ConfigFile *config_load(const char *filename, const char *displayname);
 extern void config_free(ConfigFile *cfptr);
-extern void ipport_seperate(char *string, char **ip, char **port);
-extern ConfigItem_class	*find_class(char *name);
-extern ConfigItem_deny_dcc	*find_deny_dcc(char *name);
-extern ConfigItem_oper		*find_oper(char *name);
-extern ConfigItem_operclass	*find_operclass(char *name);
-extern ConfigItem_listen *find_listen(char *ipmask, int port, int ipv6);
-extern ConfigItem_sni *find_sni(char *name);
-extern ConfigItem_ulines	*find_uline(char *host);
-extern ConfigItem_except	*find_except(Client *, short type);
+extern void ipport_seperate(const char *string, char **ip, char **port);
+extern ConfigItem_class	*find_class(const char *name);
+extern ConfigItem_oper		*find_oper(const char *name);
+extern ConfigItem_operclass	*find_operclass(const char *name);
+extern ConfigItem_listen *find_listen(const char *ipmask, int port, int ipv6);
+extern ConfigItem_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(char *servername, Client *acptr);
-extern ConfigItem_ban 		*find_ban(Client *, char *host, short type);
-extern ConfigItem_ban 		*find_banEx(Client *,char *host, short type, short type2);
-extern ConfigItem_vhost	*find_vhost(char *name);
-extern ConfigItem_deny_channel *find_channel_allowed(Client *cptr, char *name);
-extern ConfigItem_alias	*find_alias(char *name);
-extern ConfigItem_help 	*find_Help(char *command);
+extern ConfigItem_link		*find_link(const char *servername, Client *acptr);
+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);
+extern ConfigItem_deny_channel *find_channel_allowed(Client *cptr, const char *name);
+extern ConfigItem_alias	*find_alias(const char *name);
+extern ConfigItem_help 	*find_Help(const char *command);
 
-extern OperPermission ValidatePermissionsForPath(char *path, Client *client, Client *victim, Channel *channel, void *extra);
+extern OperPermission ValidatePermissionsForPath(const char *path, Client *client, Client *victim, Channel *channel, const void *extra);
 extern void OperClassValidatorDel(OperClassValidator *validator);
 
 extern ConfigItem_ban  *find_ban_ip(Client *client);
@@ -144,8 +138,8 @@ extern void append_ListItem(ListStruct *item, ListStruct **list);
 extern void add_ListItemPrio(ListStructPrio *, ListStructPrio **, int);
 extern void del_ListItem(ListStruct *, ListStruct **);
 extern MODVAR LoopStruct loop;
-extern int del_banid(Channel *channel, char *banid);
-extern int del_exbanid(Channel *channel, char *banid);
+extern int del_banid(Channel *channel, const char *banid);
+extern int del_exbanid(Channel *channel, const char *banid);
 #define REPORT_DO_DNS	"NOTICE * :*** Looking up your hostname...\r\n"
 #define REPORT_FIN_DNS	"NOTICE * :*** Found your hostname\r\n"
 #define REPORT_FIN_DNSC "NOTICE * :*** Found your hostname (cached)\r\n"
@@ -161,48 +155,66 @@ extern MODVAR struct list_head oper_list;
 extern MODVAR struct list_head unknown_list;
 extern MODVAR struct list_head global_server_list;
 extern MODVAR struct list_head dead_list;
-extern RealCommand *find_command(char *cmd, int flags);
-extern RealCommand *find_command_simple(char *cmd);
+extern 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);
 extern Member *find_member_link(Member *, Client *);
-extern int remove_user_from_channel(Client *, Channel *);
+extern int remove_user_from_channel(Client *client, Channel *channel, int dont_log);
 extern void add_server_to_table(Client *);
 extern void remove_server_from_table(Client *);
-extern void iNAH_host(Client *client, char *host);
-extern void set_snomask(Client *client, char *snomask);
-extern char *get_snomask_string(Client *client);
+extern void iNAH_host(Client *client, const char *host);
+extern void set_snomask(Client *client, const char *snomask);
 extern int check_tkls(Client *cptr);
 /* for services */
 extern void send_user_joins(Client *, Client *);
 extern int valid_channelname(const char *);
-extern int valid_server_name(char *name);
-extern long get_access(Client *, Channel *);
-extern int ban_check_mask(Client *, Channel *, char *, int, char **, char **, int);
-extern int extban_is_ok_nuh_extban(Client *, Channel *, char *, int, int, int);
-extern char *extban_conv_param_nuh_or_extban(char *);
-extern char *extban_conv_param_nuh(char *);
-extern Ban *is_banned(Client *, Channel *, int, char **, char **);
-extern Ban *is_banned_with_nick(Client *, Channel *, int, char *, char **, char **);
+extern int valid_server_name(const char *name);
+extern Cmode *find_channel_mode_handler(char letter);
+extern int valid_channel_access_mode_letter(char letter);
+extern int check_channel_access(Client *client, Channel *channel, const char *modes);
+extern int check_channel_access_membership(Membership *mb, const char *modes);
+extern int check_channel_access_member(Member *mb, const char *modes);
+extern int check_channel_access_string(const char *current_modes, const char *modes);
+extern int check_channel_access_letter(const char *current_modes, const char letter);
+extern const char *get_channel_access(Client *client, Channel *channel);
+extern void add_member_mode_fast(Member *mb, Membership *mbs, char letter);
+extern void del_member_mode_fast(Member *mb, Membership *mbs, char letter);
+extern void add_member_mode(Client *client, Channel *channel, char letter);
+extern void del_member_mode(Client *client, Channel *channel, char letter);
+extern char sjoin_prefix_to_mode(char s);
+extern char mode_to_sjoin_prefix(char s);
+extern char mode_to_prefix(char s);
+extern char prefix_to_mode(char s);
+extern const char *modes_to_prefix(const char *modes);
+extern const char *modes_to_sjoin_prefix(const char *modes);
+extern char rank_to_mode(int rank);
+extern int mode_to_rank(char mode);
+extern char lowest_ranking_mode(const char *modes);
+extern char lowest_ranking_prefix(const char *prefix);
+extern void channel_member_modes_generate_equal_or_greater(const char *modes, char *buf, size_t buflen);
+extern int ban_check_mask(BanContext *b);
+extern int extban_is_ok_nuh_extban(BanContext *b);
+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 void ircd_log(int, FORMAT_STRING(const char *), ...) __attribute__((format(printf,2,3)));
-extern Client *find_client(char *, Client *);
-extern Client *find_name(char *, Client *);
-extern Client *find_nickserv(char *, Client *);
-extern Client *find_person(char *, Client *);
-extern Client *find_server(char *, Client *);
-extern Client *find_service(char *, Client *);
+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_service(const char *, Client *);
 #define find_server_quick(x) find_server(x, NULL)
 extern char *find_or_add(char *);
 extern void inittoken();
 extern void reset_help();
 
 extern MODVAR char *debugmode, *configfile, *sbrk0;
-extern char *getfield(char *);
-extern void set_sockhost(Client *, char *);
+extern void set_sockhost(Client *, const char *);
 #ifdef _WIN32
-extern MODFUNC char *sock_strerror(int);
+extern const char *sock_strerror(int);
 #endif
-extern int dgets(int, char *, int);
 
 #ifdef _WIN32
 extern MODVAR int debuglevel;
@@ -213,86 +225,111 @@ extern MODVAR int OpenFiles;  /* number of files currently open */
 extern MODVAR int debuglevel, portnum, debugtty, maxusersperchannel;
 extern MODVAR int readcalls, udpfd, resfd;
 extern Client *add_connection(ConfigItem_listen *, int);
-extern void add_local_domain(char *, int);
 extern int check_server_init(Client *);
 extern void close_connection(Client *);
 extern void close_unbound_listeners();
-extern int connect_server(ConfigItem_link *, Client *, struct hostent *);
-extern void get_my_name(Client *, char *, int);
 extern int get_sockerr(Client *);
 extern int inetport(ConfigItem_listen *, char *, int, int);
 extern void init_sys();
 extern void check_user_limit(void);
 extern void init_modef();
-extern int verify_hostname(char *name);
+extern int verify_hostname(const char *name);
 
-extern void report_error(char *, Client *);
 extern int setup_ping();
 
 extern void set_channel_mlock(Client *, Channel *, const char *, int);
 
-extern void restart(char *);
-extern void server_reboot(char *);
+extern void restart(const char *);
+extern void server_reboot(const char *);
 extern void terminate(), write_pidfile();
 extern void *safe_alloc(size_t size);
 extern void set_socket_buffers(int fd, int rcvbuf, int sndbuf);
 extern int send_queued(Client *);
 extern void send_queued_cb(int fd, int revents, void *data);
-extern void sendto_connectnotice(Client *client, int disconnect, char *comment);
-extern void sendto_serv_butone_nickcmd(Client *one, Client *client, char *umodes);
-extern void    sendto_message_one(Client *to, Client *from, char *sender,
-    char *cmd, char *nick, char *msg);
-#define PREFIX_ALL		0
-#define PREFIX_HALFOP	0x1
-#define PREFIX_VOICE	0x2
-#define PREFIX_OP	0x4
-#define PREFIX_ADMIN	0x08
-#define PREFIX_OWNER	0x10
+extern void sendto_serv_butone_nickcmd(Client *one, MessageTag *mtags, Client *client, const char *umodes);
+extern void    sendto_message_one(Client *to, Client *from, const char *sender, const char *cmd, const char *nick, const char *msg);
 extern void sendto_channel(Channel *channel, Client *from, Client *skip,
-                           int prefix, long clicap, int sendflags,
+                           char *member_modes, long clicap, int sendflags,
                            MessageTag *mtags,
                            FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,8,9)));
 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 sendto_match_servs(Channel *, Client *, FORMAT_STRING(const char *), ...) __attribute__((format(printf,3,4)));
-extern void sendto_match_butone(Client *, Client *, char *, int, MessageTag *,
+extern void sendto_match_butone(Client *, Client *, const char *, int, MessageTag *,
     FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,6,7)));
 extern void sendto_all_butone(Client *, Client *, FORMAT_STRING(const char *), ...) __attribute__((format(printf,3,4)));
 extern void sendto_ops(FORMAT_STRING(const char *), ...) __attribute__((format(printf,1,2)));
-extern void sendto_ops_butone(Client *, Client *, FORMAT_STRING(const char *), ...) __attribute__((format(printf,3,4)));
 extern void sendto_prefix_one(Client *, Client *, MessageTag *, FORMAT_STRING(const char *), ...) __attribute__((format(printf,4,5)));
+extern void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl);
 extern void sendto_opers(FORMAT_STRING(const char *), ...) __attribute__((format(printf,1,2)));
 extern void sendto_umode(int, FORMAT_STRING(const char *), ...) __attribute__((format(printf,2,3)));
 extern void sendto_umode_global(int, FORMAT_STRING(const char *), ...) __attribute__((format(printf,2,3)));
-extern void sendto_snomask(int snomask, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
-extern void sendto_snomask_global(int snomask, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
 extern void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
-extern void sendnumeric(Client *to, int numeric, ...);
+/** Send numeric message to a client.
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see include/numeric.h
+ * @param ...		The parameters for the numeric
+ * @note Be sure to provide the correct number and type of parameters that belong to the numeric. Check include/numeric.h when in doubt!
+ * @section sendnumeric_examples Examples
+ * @subsection sendnumeric_permission_denied Send "Permission Denied" numeric
+ * This numeric has no parameter, so is simple:
+ * @code
+ * sendnumeric(client, ERR_NOPRIVILEGES);
+ * @endcode
+ * @subsection sendnumeric_notenoughparameters Send "Not enough parameters" numeric
+ * This numeric requires 1 parameter: the name of the command.
+ * @code
+ * sendnumeric(client, ERR_NEEDMOREPARAMS, "SOMECOMMAND");
+ * @endcode
+ * @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)));
+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
+ * you need to set an 'errbuf' with a full IRC protocol line to reject the request
+ * (which then may or may not be sent depending on operoverride privileges).
+ * @param buf		The buffer where the message should be stored to (full IRC protocol line)
+ * @param buflen	The size of the buffer
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see include/numeric.h
+ * @param ...		The parameters for the numeric
+ * @note Be sure to provide the correct number and type of parameters that belong to the numeric. Check include/numeric.h when in doubt!
+ * @ingroup SendFunctions
+ */
+#define buildnumeric(buf, buflen, to, numeric, ...) buildnumericfmt(buf, buflen, to, numeric, STR_ ## numeric, ##__VA_ARGS__)
+extern void buildnumericfmt(char *buf, size_t buflen, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,5,6)));
 extern void sendto_server(Client *one, unsigned long caps, unsigned long nocaps, MessageTag *mtags, FORMAT_STRING(const char *format), ...) __attribute__((format(printf, 5, 6)));
-extern void sendto_ops_and_log(FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,1,2)));
-
+extern void send_raw_direct(Client *user, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf, 2, 3)));
 extern MODVAR int writecalls, writeb[];
 extern int deliver_it(Client *cptr, char *str, int len, int *want_read);
 extern int target_limit_exceeded(Client *client, void *target, const char *name);
-extern char *canonize(char *buffer);
+extern char *canonize(const char *buffer);
 extern int check_registered(Client *);
 extern int check_registered_user(Client *);
-extern char *get_client_name(Client *, int);
-extern char *get_client_host(Client *);
-extern char *myctime(time_t);
-extern char *short_date(time_t, char *buf);
-extern char *long_date(time_t);
-extern void exit_client(Client *client, MessageTag *recv_mtags, char *comment);
-extern void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char *comment);
-extern void initstats(), tstats(Client *, char *);
-extern char *check_string(char *);
-extern char *make_nick_user_host(char *, char *, char *);
-extern char *make_nick_user_host_r(char *namebuf, char *nick, char *name, char *host);
-extern char *make_user_host(char *, char *);
+extern const char *get_client_name(Client *, int);
+extern const char *get_client_host(Client *);
+extern const char *myctime(time_t);
+extern const char *short_date(time_t, char *buf);
+extern const char *long_date(time_t);
+extern const char *pretty_time_val(long);
+extern const char *pretty_time_val_r(char *buf, size_t buflen, long timeval);
+extern const char *pretty_date(time_t t);
+extern time_t server_time_to_unix_time(const char *tbuf);
+extern time_t rfc2616_time_to_unix_time(const char *tbuf);
+extern const char *rfc2616_time(time_t clock);
+extern void exit_client(Client *client, MessageTag *recv_mtags, const char *comment);
+extern void exit_client_fmt(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf, 3, 4)));
+extern void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment);
+extern void initstats();
+extern const char *check_string(const char *);
+extern char *make_nick_user_host(const char *, const char *, const char *);
+extern char *make_nick_user_host_r(char *namebuf, size_t namebuflen, const char *nick, const char *name, const char *host);
+extern char *make_user_host(const char *, const char *);
 extern void parse(Client *cptr, char *buffer, int length);
-extern int hunt_server(Client *, MessageTag *, char *, int, int, char **);
+extern int hunt_server(Client *, MessageTag *, const char *, int, int, const char **);
 extern int cmd_server_estab(Client *);
 extern void umode_init(void);
 #define UMODE_GLOBAL 1
@@ -309,79 +346,68 @@ extern void free_client(Client *);
 extern void free_link(Link *);
 extern void free_ban(Ban *);
 extern void free_user(Client *);
-extern int find_str_match_link(Link *, char *);
+extern int link_list_length(Link *lp);
+extern int find_str_match_link(Link *, const char *);
 extern void free_str_list(Link *);
 extern Link *make_link();
 extern Ban *make_ban();
 extern User *make_user(Client *);
 extern Server *make_server();
 extern Client *make_client(Client *, Client *);
+extern Channel *make_channel(const char *name);
 extern Member *find_channel_link(Member *, Channel *);
-extern char *pretty_mask(char *);
+extern char *pretty_mask(const char *);
 extern void add_client_to_list(Client *);
 extern void remove_client_from_list(Client *);
-extern void initlists();
-extern struct hostent *get_res(char *);
-extern struct hostent *gethost_byaddr(char *, Link *);
-extern struct hostent *gethost_byname(char *, Link *);
+extern void initlists(void);
+extern void initlist_channels(void);
+extern struct hostent *get_res(const char *);
+extern struct hostent *gethost_byaddr(const char *, Link *);
+extern struct hostent *gethost_byname(const char *, Link *);
 extern void flush_cache();
 extern void init_resolver(int firsttime);
 extern time_t timeout_query_list(time_t);
 extern time_t expire_cache(time_t);
-extern void del_queries(char *);
+extern void del_queries(const char *);
 
 /* Hash stuff */
 #define NICK_HASH_TABLE_SIZE 32768
 #define CHAN_HASH_TABLE_SIZE 32768
-#define WATCH_HASH_TABLE_SIZE 32768
 #define WHOWAS_HASH_TABLE_SIZE 32768
 #define THROTTLING_HASH_TABLE_SIZE 8192
-#define hash_find_channel find_channel
 extern uint64_t siphash(const char *in, const char *k);
 extern uint64_t siphash_raw(const char *in, size_t len, const char *k);
 extern uint64_t siphash_nocase(const char *in, const char *k);
 extern void siphash_generate_key(char *k);
 extern void init_hash(void);
 uint64_t hash_whowas_name(const char *name);
-extern int add_to_client_hash_table(char *, Client *);
-extern int del_from_client_hash_table(char *, Client *);
-extern int add_to_id_hash_table(char *, Client *);
-extern int del_from_id_hash_table(char *, Client *);
-extern int add_to_channel_hash_table(char *, Channel *);
-extern void del_from_channel_hash_table(char *, Channel *);
-extern int add_to_watch_hash_table(char *, Client *, int);
-extern int del_from_watch_hash_table(char *, Client *);
-extern int hash_check_watch(Client *, int);
-extern int hash_del_watch_list(Client *);
-extern void count_watch_memory(int *, u_long *);
-extern Watch *hash_get_watch(char *);
+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 *);
+extern int del_from_id_hash_table(const char *, Client *);
+extern int add_to_channel_hash_table(const char *, Channel *);
+extern void del_from_channel_hash_table(const char *, Channel *);
 extern Channel *hash_get_chan_bucket(uint64_t);
 extern Client *hash_find_client(const char *, Client *);
 extern Client *hash_find_id(const char *, Client *);
 extern Client *hash_find_nickatserver(const char *, Client *);
-extern Channel *find_channel(char *name, Channel *channel);
+extern Channel *find_channel(const char *name);
 extern Client *hash_find_server(const char *, Client *);
 extern struct MODVAR ThrottlingBucket *ThrottlingHash[THROTTLING_HASH_TABLE_SIZE];
 
-extern char *find_by_aln(char *);
-extern char *convert2aln(int);
-extern int convertfromaln(char *);
-extern char *find_server_aln(char *);
-extern time_t atime(char *xtime);
 
 
 /* Mode externs
 */
-extern MODVAR long UMODE_INVISIBLE; /*  0x0001	 makes user invisible */
-extern MODVAR long UMODE_OPER;      /*  0x0002	 Operator */
-extern MODVAR long UMODE_WALLOP;    /*  0x0004	 send wallops to them */
-extern MODVAR long UMODE_REGNICK;   /*  0x0020	 Nick set by services as registered */
-extern MODVAR long UMODE_SERVNOTICE;/* 0x0100	 server notices such as kill */
-extern MODVAR long UMODE_HIDE;	     /* 0x8000	 Hide from Nukes */
-extern MODVAR long UMODE_SECURE;    /*	0x800000	 User is a secure connect */
-extern MODVAR long UMODE_DEAF;      /* 0x10000000       Deaf */
-extern MODVAR long UMODE_HIDEOPER;  /* 0x20000000	 Hide oper mode */
-extern MODVAR long UMODE_SETHOST;   /* 0x40000000	 used sethost */
+extern MODVAR long UMODE_INVISIBLE; /*  makes user invisible */
+extern MODVAR long UMODE_OPER;      /*  Operator */
+extern MODVAR long UMODE_REGNICK;   /*  Nick set by services as registered */
+extern MODVAR long UMODE_SERVNOTICE;/* server notices such as kill */
+extern MODVAR long UMODE_HIDE;	     /* Hide from Nukes */
+extern MODVAR long UMODE_SECURE;    /*	User is a secure connect */
+extern MODVAR long UMODE_DEAF;      /* Deaf */
+extern MODVAR long UMODE_HIDEOPER;  /* Hide oper mode */
+extern MODVAR long UMODE_SETHOST;   /* used sethost */
 extern MODVAR long UMODE_HIDLE;     /* hides oper idle times */
 extern MODVAR long AllUmodes, SendUmodes;
 
@@ -403,24 +429,32 @@ extern MODVAR long SNO_OPER;
 #ifndef HAVE_STRLCPY
 extern size_t strlcpy(char *dst, const char *src, size_t size);
 #endif
+#ifndef HAVE_STRLNCPY
+extern size_t strlncpy(char *dst, const char *src, size_t size, size_t n);
+#endif
 #ifndef HAVE_STRLCAT
 extern size_t strlcat(char *dst, const char *src, size_t size);
 #endif
 #ifndef HAVE_STRLNCAT
 extern size_t strlncat(char *dst, const char *src, size_t size, size_t n);
 #endif
+extern void strlcat_letter(char *buf, char c, size_t buflen);
 extern char *strldup(const char *src, size_t n);
 
 extern void dopacket(Client *, char *, int);
 
 extern void debug(int, FORMAT_STRING(const char *), ...) __attribute__((format(printf,2,3)));
 #if defined(DEBUGMODE)
-extern void send_usage(Client *, char *);
-extern void count_memory(Client *, char *);
-extern int checkprotoflags(Client *, int, char *, int);
+extern void send_usage(Client *, const char *);
+extern void count_memory(Client *, const char *);
+extern int checkprotoflags(Client *, int, const char *, int);
 #endif
 
-extern char *inetntop(int af, const void *in, char *local_dummy, size_t the_size);
+extern const char *inetntop(int af, const void *in, char *local_dummy, size_t the_size);
+
+extern void delletterfromstring(char *s, char letter);
+extern void addlettertodynamicstringsorted(char **str, char letter);
+extern int sort_character_lowercase_before_uppercase(char x, char y);
 
 /* Internal command stuff - not for modules */
 extern MODVAR RealCommand *CommandHash[256];
@@ -442,25 +476,19 @@ extern void close_connections(void);
 extern int b64_encode(unsigned char const *src, size_t srclength, char *target, size_t targsize);
 extern int b64_decode(char const *src, unsigned char *target, size_t targsize);
 
-extern AuthenticationType Auth_FindType(char *hash, char *type);
+extern AuthenticationType Auth_FindType(const char *hash, const char *type);
 extern AuthConfig	*AuthBlockToAuthConfig(ConfigEntry *ce);
 extern void		Auth_FreeAuthConfig(AuthConfig *as);
-extern int		Auth_Check(Client *cptr, AuthConfig *as, char *para);
-extern char   		*Auth_Hash(int type, char *para);
+extern int		Auth_Check(Client *cptr, AuthConfig *as, const char *para);
+extern const char	*Auth_Hash(int type, const char *para);
 extern int   		Auth_CheckError(ConfigEntry *ce);
-extern int              Auth_AutoDetectHashType(char *hash);
+extern int              Auth_AutoDetectHashType(const char *hash);
 
-extern void make_cloakedhost(Client *client, char *curr, char *buf, size_t buflen);
-extern int  channel_canjoin(Client *client, char *name);
+extern void make_cloakedhost(Client *client, const char *curr, char *buf, size_t buflen);
+extern int  channel_canjoin(Client *client, const char *name);
 extern char *collapse(char *pattern);
 extern void dcc_sync(Client *client);
-extern void report_flines(Client *client);
-extern void report_network(Client *client);
-extern void report_dynconf(Client *client);
-extern void count_memory(Client *cptr, char *nick);
-extern void list_scache(Client *client);
-extern char *oflagstr(long oflag);
-extern int rehash(Client *client, int sig);
+extern void request_rehash(Client *client);
 extern void s_die();
 extern int match_simple(const char *mask, const char *name);
 extern int match_esc(const char *mask, const char *name);
@@ -468,23 +496,19 @@ extern int add_listener(ConfigItem_listen *conf);
 extern void link_cleanup(ConfigItem_link *link_ptr);
 extern void       listen_cleanup();
 extern int  numeric_collides(long numeric);
-extern u_long cres_mem(Client *client, char *nick);
 extern void      flag_add(char ch);
 extern void      flag_del(char ch);
 extern void init_dynconf(void);
-extern char *pretty_time_val(long);
-extern char *pretty_date(time_t t);
-extern int        init_conf(char *filename, int rehash);
-extern void       validate_configuration(void);
-extern void       run_configuration(void);
+extern int config_read_start(void);
+extern int is_config_read_finished(void);
+extern int config_test(void);
+extern void config_run(void);
 extern void rehash_motdrules();
 extern void read_motd(const char *filename, MOTDFile *motd); /* s_serv.c */
 extern void send_proto(Client *, ConfigItem_link *);
 extern void unload_all_modules(void);
 extern void set_sock_opts(int fd, Client *cptr, int ipv6);
 extern void stripcrlf(char *line);
-extern time_t rfc2time(char *s);
-extern char *rfctime(time_t t, char *buf);
 extern int strnatcmp(char const *a, char const *b);
 extern int strnatcasecmp(char const *a, char const *b);
 extern void outofmemory(size_t bytes);
@@ -509,7 +533,7 @@ extern void *safe_alloc(size_t size);
  * @param dst   The current pointer and the pointer where a new copy of the string will be stored.
  * @param str   The string you want to copy
  */
-#define safe_strdup(dst,str) do { if (dst) free(dst); if (!(str)) dst = NULL; else dst = our_strdup(str); } while(0)
+#define safe_strdup(dst,str) do { if (dst) free(dst); if ((str) == NULL) dst = NULL; else dst = our_strdup(str); } while(0)
 
 /** Return a copy of the string. Do not free any existing memory.
  * @param str   The string to duplicate
@@ -573,7 +597,7 @@ extern char *our_strdup(const char *str);
 extern char *our_strldup(const char *str, size_t max);
 extern char *our_strdup_sensitive(const char *str);
 
-extern long config_checkval(char *value, unsigned short flags);
+extern long config_checkval(const char *value, unsigned short flags);
 extern void config_status(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2)));
 extern void init_random();
 extern u_char getrandom8();
@@ -581,10 +605,15 @@ extern uint16_t getrandom16();
 extern uint32_t getrandom32();
 extern void gen_random_alnum(char *buf, int numbytes);
 
+/* Check config entry for empty/missing parameter */
+#define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); errors++; continue; }
+/* as above, but accepting empty string */
+#define CheckNullAllowEmpty(x) if ((!(x)->value)) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); errors++; continue; }
+
 extern MODVAR char extchmstr[4][64];
 
-extern int extcmode_default_requirechop(Client *, Channel *, char, char *, int, int);
-extern int extcmode_default_requirehalfop(Client *, Channel *, char, char *, int, int);
+extern int extcmode_default_requirechop(Client *, Channel *, char, const char *, int, int);
+extern int extcmode_default_requirehalfop(Client *, Channel *, char, const char *, int, int);
 extern Cmode_t extcmode_get(Cmode *);
 extern void extcmode_init(void);
 extern void make_extcmodestr();
@@ -592,8 +621,7 @@ extern void extcmode_duplicate_paramlist(void **xi, void **xo);
 extern void extcmode_free_paramlist(void **ar);
 
 extern void chmode_str(struct ChMode *, char *, char *, size_t, size_t);
-extern char *get_client_status(Client *);
-extern char *get_snomask_string_raw(long);
+extern const char *get_client_status(Client *);
 extern void SocketLoop(void *);
 #ifdef _WIN32
 extern void InitDebug(void);
@@ -605,54 +633,46 @@ extern void CleanUp(void);
 extern int CountRTFSize(unsigned char *buffer);
 extern void IRCToRTF(unsigned char *buffer, unsigned char *string);
 #endif
-extern void sendto_chmodemucrap(Client *, Channel *, char *);
-extern void verify_opercount(Client *, char *);
-extern int valid_host(char *host);
-extern int count_oper_sessions(char *);
+extern void verify_opercount(Client *, const char *);
+extern int valid_host(const char *host, int strict);
+extern int count_oper_sessions(const char *);
 extern char *unreal_mktemp(const char *dir, const char *suffix);
-extern char *unreal_getpathname(char *filepath, char *path);
-extern char *unreal_getfilename(char *path);
-extern char *unreal_getmodfilename(char *path);
-extern char *unreal_mkcache(const char *url);
-extern int has_cached_version(const char *url);
+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_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);
 extern void unreal_setfilemodtime(const char *filename, time_t mtime);
 extern void DeleteTempModules(void);
 extern MODVAR Extban *extbaninfo;
-extern Extban *findmod_by_bantype(char c);
+extern Extban *findmod_by_bantype(const char *str, const char **remainder);
 extern Extban *ExtbanAdd(Module *reserved, ExtbanInfo req);
 extern void ExtbanDel(Extban *);
 extern void extban_init(void);
 extern char *trim_str(char *str, int len);
 extern MODVAR char *ban_realhost, *ban_virthost, *ban_ip;
-extern BanAction banact_stringtoval(char *s);
-extern char *banact_valtostring(BanAction val);
+extern BanAction banact_stringtoval(const char *s);
+extern const char *banact_valtostring(BanAction val);
 extern BanAction banact_chartoval(char c);
 extern char banact_valtochar(BanAction val);
-extern int spamfilter_gettargets(char *s, Client *client);
+extern int spamfilter_gettargets(const char *s, Client *client);
 extern char *spamfilter_target_inttostring(int v);
-extern Spamfilter *unreal_buildspamfilter(char *s);
-extern char *our_strcasestr(char *haystack, char *needle);
-extern int spamfilter_getconftargets(char *s);
-extern void remove_oper_snomasks(Client *client);
+extern char *our_strcasestr(const char *haystack, const char *needle);
+extern int spamfilter_getconftargets(const char *s);
+extern void remove_all_snomasks(Client *client);
 extern void remove_oper_modes(Client *client);
 extern char *spamfilter_inttostring_long(int v);
-extern Channel *get_channel(Client *cptr, char *chname, int flag);
 extern MODVAR char backupbuf[];
-extern void add_invite(Client *, Client *, Channel *, MessageTag *);
-extern void del_invite(Client *, Channel *);
 extern int is_invited(Client *client, Channel *channel);
-extern void channel_modes(Client *cptr, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel);
-extern MODVAR char modebuf[BUFSIZE], parabuf[BUFSIZE];
-extern int op_can_override(char *acl, Client *client,Channel *channel,void* extra);
-extern Client *find_chasing(Client *client, char *user, int *chasing);
+extern void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel, int hide_local_modes);
+extern int op_can_override(const char *acl, Client *client,Channel *channel,void* extra);
+extern Client *find_chasing(Client *client, const char *user, int *chasing);
 extern MODVAR long opermode;
 extern MODVAR long sajoinmode;
-extern void add_user_to_channel(Channel *channel, Client *who, int flags);
-extern int add_banid(Client *, Channel *, char *);
-extern int add_exbanid(Client *cptr, Channel *channel, char *banid);
+extern void add_user_to_channel(Channel *channel, Client *who, const char *modes);
+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);
@@ -664,10 +684,9 @@ extern int callbacks_check(void);
 extern void callbacks_switchover(void);
 extern int efunctions_check(void);
 extern void efunctions_switchover(void);
-extern char *encode_ip(char *);
-extern char *decode_ip(char *);
-extern void sendto_fconnectnotice(Client *client, int disconnect, char *comment);
-extern void sendto_one_nickcmd(Client *server, Client *client, char *umodes);
+extern const char *encode_ip(const char *);
+extern const char *decode_ip(const char *);
+extern void sendto_one_nickcmd(Client *server, MessageTag *mtags, Client *client, const char *umodes);
 extern int on_dccallow_list(Client *to, Client *from);
 extern int add_dccallow(Client *client, Client *optr);
 extern int del_dccallow(Client *client, Client *optr);
@@ -677,7 +696,7 @@ extern void del_async_connects(void);
 extern void isupport_init(void);
 extern void clicap_init(void);
 extern void efunctions_init(void);
-extern void do_cmd(Client *client, MessageTag *mtags, char *cmd, int parc, char *parv[]);
+extern void do_cmd(Client *client, MessageTag *mtags, const char *cmd, int parc, const char *parv[]);
 extern MODVAR char *me_hash;
 extern MODVAR int dontspread;
 extern MODVAR int labeled_response_inhibit;
@@ -685,33 +704,35 @@ extern MODVAR int labeled_response_inhibit_end;
 extern MODVAR int labeled_response_force;
 
 /* Efuncs */
-extern MODVAR void (*do_join)(Client *, int, char **);
-extern MODVAR void (*join_channel)(Channel *channel, Client *client, MessageTag *mtags, int flags);
-extern MODVAR int (*can_join)(Client *client, Channel *channel, char *key, char *parv[]);
-extern MODVAR void (*do_mode)(Channel *channel, Client *client, MessageTag *mtags, int parc, char *parv[], time_t sendts, int samode);
-extern MODVAR void (*set_mode)(Channel *channel, Client *cptr, int parc, char *parv[], u_int *pcount,
-    char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], int bounce);
-extern MODVAR void (*cmd_umode)(Client *, MessageTag *, int, char **);
-extern MODVAR int (*register_user)(Client *client, char *nick, char *username, char *umode, char *virthost, char *ip);
+extern MODVAR void (*do_join)(Client *, int, const char **);
+extern MODVAR void (*join_channel)(Channel *channel, Client *client, MessageTag *mtags, const char *flags);
+extern MODVAR int (*can_join)(Client *client, Channel *channel, const char *key, char **errmsg);
+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 (*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_type_string)(TKL *tk);
-extern MODVAR TKL *(*tkl_add_serverban)(int type, char *usermask, char *hostmask, char *reason, char *setby,
+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,
                                             time_t expire_at, time_t set_at, int soft, int flags);
-extern MODVAR TKL *(*tkl_add_banexception)(int type, char *usermask, char *hostmask, char *reason, char *set_by,
-                                               time_t expire_at, time_t set_at, int soft, char *bantypes, int flags);
-extern MODVAR TKL *(*tkl_add_nameban)(int type, char *name, int hold, char *reason, char *setby,
+extern MODVAR TKL *(*tkl_add_banexception)(int type, const char *usermask, const char *hostmask, const char *reason, const char *set_by,
+                                               time_t expire_at, time_t set_at, int soft, const char *bantypes, int flags);
+extern MODVAR TKL *(*tkl_add_nameban)(int type, const char *name, int hold, const char *reason, const char *setby,
                                           time_t expire_at, time_t set_at, int flags);
-extern MODVAR TKL *(*tkl_add_spamfilter)(int type, unsigned short target, unsigned short action, Match *match, char *setby,
+extern MODVAR TKL *(*tkl_add_spamfilter)(int type, unsigned short target, unsigned short action, Match *match, const char *setby,
                                              time_t expire_at, time_t set_at,
-                                             time_t spamf_tkl_duration, char *spamf_tkl_reason,
+                                             time_t spamf_tkl_duration, const char *spamf_tkl_reason,
                                              int flags);
-extern MODVAR TKL *(*find_tkl_serverban)(int type, char *usermask, char *hostmask, int softban);
-extern MODVAR TKL *(*find_tkl_banexception)(int type, char *usermask, char *hostmask, int softban);
-extern MODVAR TKL *(*find_tkl_nameban)(int type, char *name, int hold);
-extern MODVAR TKL *(*find_tkl_spamfilter)(int type, char *match_string, unsigned short action, unsigned short target);
-extern MODVAR void (*sendnotice_tkl_del)(char *removed_by, TKL *tkl);
+extern MODVAR TKL *(*find_tkl_serverban)(int type, const char *usermask, const char *hostmask, int softban);
+extern MODVAR TKL *(*find_tkl_banexception)(int type, const char *usermask, const char *hostmask, int softban);
+extern MODVAR TKL *(*find_tkl_nameban)(int type, const char *name, int hold);
+extern MODVAR TKL *(*find_tkl_spamfilter)(int type, const char *match_string, unsigned short action, unsigned short target);
+extern MODVAR void (*sendnotice_tkl_del)(const char *removed_by, TKL *tkl);
 extern MODVAR void (*sendnotice_tkl_add)(TKL *tkl);
 extern MODVAR void (*free_tkl)(TKL *tkl);
 extern MODVAR TKL *(*tkl_del_line)(TKL *tkl);
@@ -719,50 +740,53 @@ extern MODVAR void (*tkl_check_local_remove_shun)(TKL *tmp);
 extern MODVAR int (*find_tkline_match)(Client *cptr, int skip_soft);
 extern MODVAR int (*find_shun)(Client *cptr);
 extern MODVAR int (*find_spamfilter_user)(Client *client, int flags);
-extern MODVAR TKL *(*find_qline)(Client *cptr, char *nick, int *ishold);
+extern MODVAR TKL *(*find_qline)(Client *cptr, const char *nick, int *ishold);
 extern MODVAR TKL *(*find_tkline_match_zap)(Client *cptr);
-extern MODVAR void (*tkl_stats)(Client *cptr, int type, char *para, int *cnt);
+extern MODVAR void (*tkl_stats)(Client *cptr, int type, const char *para, int *cnt);
 extern MODVAR void (*tkl_sync)(Client *client);
-extern MODVAR void (*cmd_tkl)(Client *client, MessageTag *recv_mtags, int parc, char *parv[]);
-extern MODVAR int (*place_host_ban)(Client *client, BanAction action, char *reason, long duration);
-extern MODVAR int (*match_spamfilter)(Client *client, char *str_in, int type, char *cmd, char *target, int flags, TKL **rettk);
-extern MODVAR int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, char *cmd);
+extern MODVAR void (*cmd_tkl)(Client *client, MessageTag *recv_mtags, int parc, const char *parv[]);
+extern MODVAR int (*place_host_ban)(Client *client, BanAction action, const char *reason, long duration);
+extern MODVAR int (*match_spamfilter)(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
+extern MODVAR int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, const char *cmd);
 extern MODVAR int (*join_viruschan)(Client *client, TKL *tk, int type);
-extern MODVAR unsigned char *(*StripColors)(unsigned char *text);
-extern MODVAR const char *(*StripControlCodes)(unsigned char *text);
-extern MODVAR void (*spamfilter_build_user_string)(char *buf, char *nick, Client *acptr);
+extern MODVAR const char *(*StripColors)(const char *text);
+extern MODVAR const char *(*StripControlCodes)(const char *text);
+extern MODVAR void (*spamfilter_build_user_string)(char *buf, const char *nick, Client *acptr);
 extern MODVAR void (*send_protoctl_servers)(Client *client, int response);
-extern MODVAR int (*verify_link)(Client *client, char *servername, ConfigItem_link **link_out);
+extern MODVAR int (*verify_link)(Client *client, ConfigItem_link **link_out);
 extern MODVAR void (*send_server_message)(Client *client);
 extern MODVAR void (*broadcast_md_client)(ModDataInfo *mdi, Client *acptr, ModData *md);
 extern MODVAR void (*broadcast_md_channel)(ModDataInfo *mdi, Channel *channel, ModData *md);
 extern MODVAR void (*broadcast_md_member)(ModDataInfo *mdi, Channel *channel, Member *m, ModData *md);
 extern MODVAR void (*broadcast_md_membership)(ModDataInfo *mdi, Client *acptr, Membership *m, ModData *md);
-extern MODVAR void (*broadcast_md_client_cmd)(Client *except, Client *sender, Client *acptr, char *varname, char *value);
-extern MODVAR void (*broadcast_md_channel_cmd)(Client *except, Client *sender, Channel *channel, char *varname, char *value);
-extern MODVAR void (*broadcast_md_member_cmd)(Client *except, Client *sender, Channel *channel, Client *acptr, char *varname, char *value);
-extern MODVAR void (*broadcast_md_membership_cmd)(Client *except, Client *sender, Client *acptr, Channel *channel, char *varname, char *value);
+extern MODVAR void (*broadcast_md_client_cmd)(Client *except, Client *sender, Client *acptr, const char *varname, const char *value);
+extern MODVAR void (*broadcast_md_channel_cmd)(Client *except, Client *sender, Channel *channel, const char *varname, const char *value);
+extern MODVAR void (*broadcast_md_member_cmd)(Client *except, Client *sender, Channel *channel, Client *acptr, const char *varname, const char *value);
+extern MODVAR void (*broadcast_md_membership_cmd)(Client *except, Client *sender, Client *acptr, Channel *channel, const char *varname, const char *value);
+extern MODVAR void (*moddata_add_s2s_mtags)(Client *client, MessageTag **mtags);
+extern MODVAR void (*moddata_extract_s2s_mtags)(Client *client, MessageTag *mtags);
 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, char *software, int protocol, char *flags);
-extern MODVAR int (*match_user)(char *rmask, Client *acptr, int options);
+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);
 extern MODVAR void (*userhost_save_current)(Client *client);
 extern MODVAR void (*userhost_changed)(Client *client);
 extern MODVAR void (*send_join_to_local_users)(Client *client, Channel *channel, MessageTag *mtags);
 extern MODVAR int (*do_nick_name)(char *nick);
 extern MODVAR int (*do_remote_nick_name)(char *nick);
-extern MODVAR char *(*charsys_get_current_languages)(void);
+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 void (*parse_message_tags)(Client *cptr, char **str, MessageTag **mtag_list);
-extern MODVAR char *(*mtags_to_string)(MessageTag *m, Client *acptr);
-extern MODVAR int (*can_send_to_channel)(Client *cptr, Channel *channel, char **msgtext, char **errmsg, int notice);
+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);
 extern MODVAR void (*broadcast_md_globalvar)(ModDataInfo *mdi, ModData *md);
-extern MODVAR void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, char *varname, char *value);
-extern MODVAR int (*tkl_ip_hash)(char *ip);
+extern MODVAR void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, const char *varname, const char *value);
+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 (*del_silence)(Client *client, const char *mask);
@@ -771,109 +795,118 @@ extern MODVAR int (*is_silenced)(Client *client, Client *acptr);
 extern MODVAR void *(*labeled_response_save_context)(void);
 extern MODVAR void (*labeled_response_set_context)(void *ctx);
 extern MODVAR void (*labeled_response_force_end)(void);
-extern MODVAR void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment);
+extern MODVAR void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, const char *comment);
+extern MODVAR int (*watch_add)(const char *nick, Client *client, int flags);
+extern MODVAR int (*watch_del)(const char *nick, Client *client, int flags);
+extern MODVAR int (*watch_del_list)(Client *client, int flags);
+extern MODVAR Watch *(*watch_get)(const char *nick);
+extern MODVAR int (*watch_check)(Client *client, int reply, int (*watch_notify)(Client *client, Watch *watch, Link *lp, int event));
+extern MODVAR char *(*tkl_uhost)(TKL *tkl, char *buf, size_t buflen, int options);
+extern MODVAR void (*do_unreal_log_remote_deliver)(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized);
+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);
 /* /Efuncs */
 
-/* SSL/TLS functions */
-extern int early_init_ssl();
-extern int init_ssl();
+/* TLS functions */
+extern int early_init_tls();
+extern int init_tls();
 extern int ssl_handshake(Client *);   /* Handshake the accpeted con.*/
 extern int ssl_client_handshake(Client *, ConfigItem_link *); /* and the initiated con.*/
-extern int ircd_SSL_accept(Client *acptr, int fd);
-extern int ircd_SSL_connect(Client *acptr, int fd);
+extern int unreal_tls_accept(Client *acptr, int fd);
+extern int unreal_tls_connect(Client *acptr, int fd);
 extern int SSL_smart_shutdown(SSL *ssl);
-extern void ircd_SSL_client_handshake(int, int, void *);
+extern const char *ssl_error_str(int err, int my_errno);
+extern void unreal_tls_client_handshake(int, int, void *);
 extern void SSL_set_nonblocking(SSL *s);
 extern SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server);
-extern MODFUNC char  *tls_get_cipher(SSL *ssl);
+extern const char *tls_get_cipher(Client *client);
 extern TLSOptions *get_tls_options_for_client(Client *acptr);
 extern int outdated_tls_client(Client *acptr);
-extern char *outdated_tls_client_build_string(char *pattern, Client *acptr);
+extern const char *outdated_tls_client_build_string(const char *pattern, Client *acptr);
 extern int check_certificate_expiry_ctx(SSL_CTX *ctx, char **errstr);
 extern EVENT(tls_check_expiry);
-/* End of SSL/TLS functions */
+extern MODVAR EVP_MD *sha256_function;
+extern MODVAR EVP_MD *sha1_function;
+extern MODVAR EVP_MD *md5_function;
+/* End of TLS functions */
 
 extern void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list);
-extern char *mtags_to_string_default_handler(MessageTag *m, Client *client);
+extern const char *mtags_to_string_default_handler(MessageTag *m, Client *client);
 extern void *labeled_response_save_context_default_handler(void);
 extern void labeled_response_set_context_default_handler(void *ctx);
 extern void labeled_response_force_end_default_handler(void);
 extern int add_silence_default_handler(Client *client, const char *mask, int senderr);
 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 MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules;
 extern MODVAR int max_connection_count;
-extern int add_listmode(Ban **list, Client *cptr, Channel *channel, char *banid);
-extern int add_listmode_ex(Ban **list, Client *cptr, Channel *channel, char *banid, char *setby, time_t seton);
-extern int del_listmode(Ban **list, Channel *channel, char *banid);
+extern int add_listmode(Ban **list, Client *cptr, Channel *channel, const char *banid);
+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 char *clean_ban_mask(char *, int, Client *);
+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);
 extern char *md5hash(char *dst, const char *src, unsigned long n);
 extern char *sha256hash(char *dst, const char *src, unsigned long n);
+extern void sha256hash_binary(char *dst, const char *src, unsigned long n);
+extern void sha1hash_binary(char *dst, const char *src, unsigned long n);
 extern MODVAR TKL *tklines[TKLISTLEN];
 extern MODVAR TKL *tklines_ip_hash[TKLIPHASHLEN1][TKLIPHASHLEN2];
-extern char *cmdname_by_spamftarget(int target);
+extern const char *cmdname_by_spamftarget(int target);
 extern void unrealdns_delreq_bycptr(Client *cptr);
-extern void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
-extern void unrealdns_gethostbyname_link(char *name, ConfigItem_link *conf, int ipv4_only);
+extern void unrealdns_gethostbyname_link(const char *name, ConfigItem_link *conf, int ipv4_only);
 extern void unrealdns_delasyncconnects(void);
-extern int is_autojoin_chan(char *chname);
+extern int is_autojoin_chan(const char *chname);
 extern void unreal_free_hostent(struct hostent *he);
-extern struct hostent *unreal_create_hostent(char *name, char *ip);
-extern char *unreal_time_sync_error(void);
+extern struct hostent *unreal_create_hostent(const char *name, const char *ip);
+extern const char *unreal_time_sync_error(void);
 extern int unreal_time_synch(int timeout);
-extern char *getcloak(Client *client);
+extern const char *getcloak(Client *client);
 extern MODVAR unsigned char param_to_slot_mapping[256];
-extern char *cm_getparameter(Channel *channel, char mode);
-extern void cm_putparameter(Channel *channel, char mode, char *str);
+extern const char *cm_getparameter(Channel *channel, char mode);
+extern const char *cm_getparameter_ex(void **p, char mode);
+extern void cm_putparameter(Channel *channel, char mode, const char *str);
+extern void cm_putparameter_ex(void **p, char mode, const char *str);
 extern void cm_freeparameter(Channel *channel, char mode);
-extern char *cm_getparameter_ex(void **p, char mode);
-extern void cm_putparameter_ex(void **p, char mode, char *str);
 extern void cm_freeparameter_ex(void **p, char mode, char *str);
-extern int file_exists(char *file);
-extern time_t get_file_time(char *fname);
-extern long get_file_size(char *fname);
+extern int file_exists(const char *file);
+extern time_t get_file_time(const char *fname);
+extern long get_file_size(const char *fname);
 extern void free_motd(MOTDFile *motd); /* s_serv.c */
 extern void fix_timers(void);
-extern char *chfl_to_sjoin_symbol(int s);
+extern const char *chfl_to_sjoin_symbol(int s);
 extern char chfl_to_chanmode(int s);
-extern void add_pending_net(Client *client, char *str);
+extern void add_pending_net(Client *client, const char *str);
 extern void free_pending_net(Client *client);
 extern Client *find_non_pending_net_duplicates(Client *cptr);
-extern PendingNet *find_pending_net_by_sid_butone(char *sid, Client *exempt);
+extern PendingNet *find_pending_net_by_sid_butone(const char *sid, Client *exempt);
 extern Client *find_pending_net_duplicates(Client *cptr, Client **srv, char **sid);
 extern MODVAR char serveropts[];
 extern MODVAR char *ISupportStrings[];
 extern void read_packet(int fd, int revents, void *data);
 extern int process_packet(Client *cptr, char *readbuf, int length, int killsafely);
-extern void sendto_realops_and_log(FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,1,2)));
-extern int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in);
-extern void config_report_ssl_error(void);
-extern int dead_socket(Client *to, char *notice);
-extern Match *unreal_create_match(MatchType type, char *str, char **error);
+extern int parse_chanmode(ParseMode *pm, const char *modebuf_in, const char *parabuf_in);
+extern int dead_socket(Client *to, const char *notice);
+extern Match *unreal_create_match(MatchType type, const char *str, char **error);
 extern void unreal_delete_match(Match *m);
-extern int unreal_match(Match *m, char *str);
-extern int unreal_match_method_strtoval(char *str);
+extern int unreal_match(Match *m, const char *str);
+extern int unreal_match_method_strtoval(const char *str);
 extern char *unreal_match_method_valtostr(int val);
-extern int mixed_network(void);
 extern void unreal_delete_masks(ConfigItem_mask *m);
 extern void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce);
 extern int unreal_mask_match(Client *acptr, ConfigItem_mask *m);
-extern char *our_strcasestr(char *haystack, char *needle);
-extern void update_conf(void);
-extern MODVAR int need_34_upgrade;
+extern int unreal_mask_match_string(const char *name, ConfigItem_mask *m);
 #ifdef _WIN32
 extern MODVAR BOOL IsService;
 #endif
-extern int match_ip46(char *a, char *b);
 extern void extcmodes_check_for_changes(void);
 extern void umodes_check_for_changes(void);
-extern int config_parse_flood(char *orig, int *times, int *period);
-extern int swhois_add(Client *acptr, char *tag, int priority, char *swhois, Client *from, Client *skip);
-extern int swhois_delete(Client *acptr, char *tag, char *swhois, Client *from, Client *skip);
+extern int config_parse_flood(const char *orig, int *times, int *period);
+extern int swhois_add(Client *acptr, const char *tag, int priority, const char *swhois, Client *from, Client *skip);
+extern int swhois_delete(Client *acptr, const char *tag, const char *swhois, Client *from, Client *skip);
 extern void remove_oper_privileges(Client *client, int broadcast_mode_change);
 extern int client_starttls(Client *acptr);
 extern void start_server_handshake(Client *cptr);
@@ -882,21 +915,21 @@ extern void report_crash(void);
 extern void modulemanager(int argc, char *argv[]);
 extern int inet_pton4(const char *src, unsigned char *dst);
 extern int inet_pton6(const char *src, unsigned char *dst);
-extern int unreal_bind(int fd, char *ip, int port, int ipv6);
-extern int unreal_connect(int fd, char *ip, int port, int ipv6);
-extern int is_valid_ip(char *str);
+extern int unreal_bind(int fd, const char *ip, int port, int ipv6);
+extern int unreal_connect(int fd, const char *ip, int port, int ipv6);
+extern int is_valid_ip(const char *str);
 extern int ipv6_capable(void);
 extern MODVAR Client *remote_rehash_client;
 extern MODVAR int debugfd;
-extern void convert_to_absolute_path(char **path, char *reldir);
+extern void convert_to_absolute_path(char **path, const char *reldir);
 extern int has_user_mode(Client *acptr, char mode);
 extern int has_channel_mode(Channel *channel, char mode);
+extern int has_channel_mode_raw(Cmode_t m, char mode);
 extern Cmode_t get_extmode_bitbychar(char m);
-extern long get_mode_bitbychar(char m);
 extern long find_user_mode(char mode);
 extern void start_listeners(void);
 extern void buildvarstring(const char *inbuf, char *outbuf, size_t len, const char *name[], const char *value[]);
-extern void reinit_ssl(Client *);
+extern void reinit_tls(void);
 extern CMD_FUNC(cmd_error);
 extern CMD_FUNC(cmd_dns);
 extern CMD_FUNC(cmd_info);
@@ -910,53 +943,53 @@ extern CMD_FUNC(cmd_module);
 extern CMD_FUNC(cmd_rehash);
 extern CMD_FUNC(cmd_die);
 extern CMD_FUNC(cmd_restart);
-extern void cmd_alias(Client *client, MessageTag *recv_mtags, int parc, char *parv[], char *cmd); /* special! */
-extern char *pcre2_version(void);
+extern void cmd_alias(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], const char *cmd); /* special! */
+extern const char *pcre2_version(void);
 extern int get_terminal_width(void);
 extern int has_common_channels(Client *c1, Client *c2);
 extern int user_can_see_member(Client *user, Client *target, Channel *channel);
 extern int invisible_user_in_channel(Client *target, Channel *channel);
-extern MODVAR int ssl_client_index;
+extern MODVAR int tls_client_index;
 extern TLSOptions *FindTLSOptionsForUser(Client *acptr);
 extern int IsWebsocket(Client *acptr);
-extern Policy policy_strtoval(char *s);
-extern char *policy_valtostr(Policy policy);
+extern Policy policy_strtoval(const char *s);
+extern const char *policy_valtostr(Policy policy);
 extern char policy_valtochar(Policy policy);
-extern int verify_certificate(SSL *ssl, char *hostname, char **errstr);
-extern char *certificate_name(SSL *ssl);
+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 unload_all_unused_mtag_handlers(void);
-extern void send_cap_notify(int add, char *token);
+extern void send_cap_notify(int add, const char *token);
 extern void sendbufto_one(Client *to, char *msg, unsigned int quick);
 extern MODVAR int current_serial;
-extern char *spki_fingerprint(Client *acptr);
-extern char *spki_fingerprint_ex(X509 *x509_cert);
-extern int is_module_loaded(char *name);
+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 void close_std_descriptors(void);
-extern void banned_client(Client *acptr, char *bantype, char *reason, int global, int noexit);
+extern void banned_client(Client *acptr, const char *bantype, const char *reason, int global, int noexit);
 extern char *mystpcpy(char *dst, const char *src);
-extern size_t add_sjsby(char *buf, char *setby, time_t seton);
-extern MaxTarget *findmaxtarget(char *cmd);
-extern void setmaxtargets(char *cmd, int limit);
+extern size_t add_sjsby(char *buf, const char *setby, time_t seton);
+extern MaxTarget *findmaxtarget(const char *cmd);
+extern void setmaxtargets(const char *cmd, int limit);
 extern void freemaxtargets(void);
-extern int max_targets_for_command(char *cmd);
+extern int max_targets_for_command(const char *cmd);
 extern void set_targmax_defaults(void);
-extern void parse_chanmodes_protoctl(Client *client, char *str);
-extern void concat_params(char *buf, int len, int parc, char *parv[]);
+extern void parse_chanmodes_protoctl(Client *client, const char *str);
+extern void concat_params(char *buf, int len, int parc, const char *parv[]);
 extern void charsys_check_for_changes(void);
-extern int maxclients;
-extern int fast_badword_match(ConfigItem_badword *badword, char *line);
-extern int fast_badword_replace(ConfigItem_badword *badword, char *line, char *buf, int max);
-extern char *stripbadwords(char *str, ConfigItem_badword *start_bw, int *blocked);
-extern int badword_config_process(ConfigItem_badword *ca, char *str);
+extern MODVAR int maxclients;
+extern int fast_badword_match(ConfigItem_badword *badword, const char *line);
+extern int fast_badword_replace(ConfigItem_badword *badword, const char *line, char *buf, int max);
+extern const char *stripbadwords(const char *str, ConfigItem_badword *start_bw, int *blocked);
+extern int badword_config_process(ConfigItem_badword *ca, const char *str);
 extern void badword_config_free(ConfigItem_badword *ca);
-extern char *badword_config_check_regex(char *s, int fastsupport, int check_broadness);
-extern AllowedChannelChars allowed_channelchars_strtoval(char *str);
-extern char *allowed_channelchars_valtostr(AllowedChannelChars v);
-extern HideIdleTimePolicy hideidletime_strtoval(char *str);
-extern char *hideidletime_valtostr(HideIdleTimePolicy v);
+extern const char *badword_config_check_regex(const char *s, int fastsupport, int check_broadness);
+extern AllowedChannelChars allowed_channelchars_strtoval(const char *str);
+extern const char *allowed_channelchars_valtostr(AllowedChannelChars v);
+extern HideIdleTimePolicy hideidletime_strtoval(const char *str);
+extern const char *hideidletime_valtostr(HideIdleTimePolicy v);
 extern long ClientCapabilityBit(const char *token);
 extern int is_handshake_finished(Client *client);
 extern void SetCapability(Client *acptr, const char *token);
@@ -966,12 +999,12 @@ extern void new_message_special(Client *sender, MessageTag *recv_mtags, MessageT
 extern void generate_batch_id(char *str);
 extern MessageTag *find_mtag(MessageTag *mtags, const char *token);
 extern MessageTag *duplicate_mtag(MessageTag *mtag);
+#define safe_free_message_tags(x) do { if (x) free_message_tags(x); x = NULL; } while(0)
 extern void free_message_tags(MessageTag *m);
-extern time_t server_time_to_unix_time(const char *tbuf);
-extern int history_set_limit(char *object, int max_lines, long max_t);
-extern int history_add(char *object, MessageTag *mtags, char *line);
-extern HistoryResult *history_request(char *object, HistoryFilter *filter);
-extern int history_destroy(char *object);
+extern int history_set_limit(const char *object, int max_lines, long max_t);
+extern int history_add(const char *object, MessageTag *mtags, const char *line);
+extern HistoryResult *history_request(const char *object, HistoryFilter *filter);
+extern int history_destroy(const char *object);
 extern int can_receive_history(Client *client);
 extern void history_send_result(Client *client, HistoryResult *r);
 extern void free_history_result(HistoryResult *r);
@@ -983,61 +1016,88 @@ extern int read_int64(FILE *fd, uint64_t *t);
 extern int read_int32(FILE *fd, uint32_t *t);
 extern int read_data(FILE *fd, void *buf, size_t len);
 extern int write_data(FILE *fd, const void *buf, size_t len);
-extern int write_str(FILE *fd, char *x);
+extern int write_str(FILE *fd, const char *x);
 extern int read_str(FILE *fd, char **x);
-extern int char_to_channelflag(char c);
 extern void _free_entire_name_list(NameList *n);
-extern void _add_name_list(NameList **list, char *name);
-extern void _del_name_list(NameList **list, char *name);
-extern NameList *find_name_list(NameList *list, char *name);
-extern NameList *find_name_list_match(NameList *list, char *name);
+extern void _add_name_list(NameList **list, const char *name);
+extern void _del_name_list(NameList **list, const char *name);
+extern NameList *find_name_list(NameList *list, const char *name);
+extern NameList *find_name_list_match(NameList *list, const char *name);
 extern int minimum_msec_since_last_run(struct timeval *tv_old, long minimum);
 extern int unrl_utf8_validate(const char *str, const char **end);
-extern char *unrl_utf8_make_valid(const char *str);
+extern char *unrl_utf8_make_valid(const char *str, char *outputbuf, size_t outputbuflen, int strict_length_check);
 extern void utf8_test(void);
 extern MODVAR int non_utf8_nick_chars_in_use;
 extern void short_motd(Client *client);
 extern int should_show_connect_info(Client *client);
-extern void send_invalid_channelname(Client *client, char *channelname);
+extern void send_invalid_channelname(Client *client, const char *channelname);
 extern int is_extended_ban(const char *str);
-extern int valid_sid(char *name);
+extern int is_extended_server_ban(const char *str);
+extern int empty_mode(const char *m);
+extern void free_multilinemode(MultiLineMode *m);
+#define safe_free_multilinemode(m) do { if (m) free_multilinemode(m); m = NULL; } while(0)
+extern int valid_sid(const char *name);
+extern int valid_uid(const char *name);
 extern void parse_client_queued(Client *client);
-extern char *sha256sum_file(const char *fname);
+extern const char *sha256sum_file(const char *fname);
 extern char *filename_strip_suffix(const char *fname, const char *suffix);
 extern char *filename_add_suffix(const char *fname, const char *suffix);
 extern int filename_has_suffix(const char *fname, const char *suffix);
-extern void addmultiline(MultiLine **l, char *line);
+extern void addmultiline(MultiLine **l, const char *line);
 extern void freemultiline(MultiLine *l);
 #define safe_free_multiline(x) do { if (x) freemultiline(x); x = NULL; } while(0)
+extern MultiLine *line2multiline(const char *str);
 extern void sendnotice_multiline(Client *client, MultiLine *m);
 extern void unreal_del_quotes(char *i);
-extern char *unreal_add_quotes(char *str);
-extern int unreal_add_quotes_r(char *i, char *o, size_t len);
+extern const char *unreal_add_quotes(const char *str);
+extern int unreal_add_quotes_r(const char *i, char *o, size_t len);
 extern void user_account_login(MessageTag *recv_mtags, Client *client);
 extern void link_generator(void);
 extern void update_throttling_timer_settings(void);
 extern int hide_idle_time(Client *client, Client *target);
-extern void lost_server_link(Client *serv, FORMAT_STRING(const char *fmt), ...);
-extern char *sendtype_to_cmd(SendType sendtype);
+extern void lost_server_link(Client *serv, const char *tls_error_string);
+extern const char *sendtype_to_cmd(SendType sendtype);
 extern MODVAR MessageTagHandler *mtaghandlers;
-extern int security_group_valid_name(char *name);
-extern int security_group_exists(char *name);
-extern SecurityGroup *add_security_group(char *name, int order);
-extern SecurityGroup *find_security_group(char *name);
+extern int security_group_valid_name(const char *name);
+extern int security_group_exists(const char *name);
+extern SecurityGroup *add_security_group(const char *name, int order);
+extern SecurityGroup *find_security_group(const char *name);
 extern void free_security_group(SecurityGroup *s);
 extern void set_security_group_defaults(void);
 extern int user_allowed_by_security_group(Client *client, SecurityGroup *s);
-extern int user_allowed_by_security_group_name(Client *client, char *secgroupname);
-extern void add_nvplist(NameValuePrioList **lst, int priority, char *name, char *value);
-extern void add_fmt_nvplist(NameValuePrioList **lst, int priority, char *name, FORMAT_STRING(const char *format), ...) __attribute__((format(printf,4,5)));
-extern NameValuePrioList *find_nvplist(NameValuePrioList *list, char *name);
+extern int user_allowed_by_security_group_name(Client *client, const char *secgroupname);
+#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)))
+extern const char *do_nv_find_by_value(NameValue *table, long value, int numelements);
+extern void add_nvplist(NameValuePrioList **lst, int priority, const char *name, const char *value);
+extern void add_fmt_nvplist(NameValuePrioList **lst, int priority, const char *name, FORMAT_STRING(const char *format), ...) __attribute__((format(printf,4,5)));
+/** Combination of add_nvplist() and buildnumeric() for convenience - only used in WHOIS response functions.
+ * @param lst		The NameValuePrioList &head
+ * @param priority	The priority of the item being added
+ * @param name		The name of the item being added (eg: "certfp")
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see include/numeric.h
+ * @param ...		The parameters for the numeric
+ * @note Be sure to provide the correct number and type of parameters that belong to the numeric. Check include/numeric.h when in doubt!
+ */
+#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 void free_nvplist(NameValuePrioList *lst);
-extern char *get_connect_extinfo(Client *client);
-extern char *unreal_strftime(char *str);
-extern void strtolower_safe(char *dst, char *src, int size);
+extern const char *get_connect_extinfo(Client *client);
+extern char *unreal_strftime(const char *str);
+extern void strtolower(char *str);
+extern void strtolower_safe(char *dst, const char *src, int size);
+extern void strtoupper(char *str);
+extern void strtoupper_safe(char *dst, const char *src, int size);
 extern int running_interactively(void);
+extern int terminal_supports_color(void);
 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);
 /* src/unrealdb.c start */
 extern UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block);
 extern int unrealdb_close(UnrealDB *c);
@@ -1045,30 +1105,106 @@ extern char *unrealdb_test_db(const char *filename, char *secret_block);
 extern int unrealdb_write_int64(UnrealDB *c, uint64_t t);
 extern int unrealdb_write_int32(UnrealDB *c, uint32_t t);
 extern int unrealdb_write_int16(UnrealDB *c, uint16_t t);
-extern int unrealdb_write_str(UnrealDB *c, char *x);
+extern int unrealdb_write_str(UnrealDB *c, const char *x);
 extern int unrealdb_write_char(UnrealDB *c, char t);
 extern int unrealdb_read_int64(UnrealDB *c, uint64_t *t);
 extern int unrealdb_read_int32(UnrealDB *c, uint32_t *t);
 extern int unrealdb_read_int16(UnrealDB *c, uint16_t *t);
 extern int unrealdb_read_str(UnrealDB *c, char **x);
 extern int unrealdb_read_char(UnrealDB *c, char *t);
-extern char *unrealdb_test_secret(char *name);
+extern const char *unrealdb_test_secret(const char *name);
 extern UnrealDBConfig *unrealdb_copy_config(UnrealDBConfig *src);
 extern UnrealDBConfig *unrealdb_get_config(UnrealDB *db);
 extern void unrealdb_free_config(UnrealDBConfig *c);
 extern UnrealDBError unrealdb_get_error_code(void);
-extern char *unrealdb_get_error_string(void);
+extern const char *unrealdb_get_error_string(void);
 /* src/unrealdb.c end */
 /* secret { } related stuff */
-extern Secret *find_secret(char *secret_name);
+extern Secret *find_secret(const char *secret_name);
 extern void free_secret_cache(SecretCache *c);
 extern void free_secret(Secret *s);
 extern Secret *secrets;
 /* end */
-extern int check_password_strength(char *pass, int min_length, int strict, char **err);
-extern int valid_secret_password(char *pass, char **err);
+extern int check_password_strength(const char *pass, int min_length, int strict, char **err);
+extern int valid_secret_password(const char *pass, char **err);
 extern int flood_limit_exceeded(Client *client, FloodOption opt);
 extern FloodSettings *find_floodsettings_block(const char *name);
 extern FloodSettings *get_floodsettings_for_user(Client *client, FloodOption opt);
-extern MODVAR char *floodoption_names[];
-extern void flood_limit_exceeded_log(Client *client, char *floodname);
+extern MODVAR const char *floodoption_names[];
+extern void flood_limit_exceeded_log(Client *client, const char *floodname);
+/* logging */
+extern int config_test_log(ConfigFile *conf, ConfigEntry *ce);
+extern int config_run_log(ConfigFile *conf, ConfigEntry *ce);
+extern LogType log_type_stringtoval(const char *str);
+extern const char *log_type_valtostring(LogType v);
+#ifdef DEBUGMODE
+#define unreal_log(...) do_unreal_log(__VA_ARGS__, log_data_source(__FILE__, __LINE__, __FUNCTION__), NULL)
+#define unreal_log_raw(...) do_unreal_log_raw(__VA_ARGS__, log_data_source(__FILE__, __LINE__, __FUNCTION__), NULL)
+#else
+#define unreal_log(...) do_unreal_log(__VA_ARGS__, NULL)
+#define unreal_log_raw(...) do_unreal_log_raw(__VA_ARGS__, NULL)
+#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 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);
+extern LogData *log_data_timestamp(const char *key, time_t ts);
+extern LogData *log_data_client(const char *key, Client *client);
+extern LogData *log_data_channel(const char *key, Channel *channel);
+extern LogData *log_data_source(const char *file, int line, const char *function);
+extern LogData *log_data_socket_error(int fd);
+extern LogData *log_data_link_block(ConfigItem_link *link);
+extern LogData *log_data_tkl(const char *key, TKL *tkl);
+extern LogData *log_data_tls_error(void);
+extern void log_pre_rehash(void);
+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 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 const char *timestamp_iso8601_now(void);
+extern const char *timestamp_iso8601(time_t v);
+extern int is_valid_snomask(char c);
+extern int is_valid_snomask_string_testing(const char *str, char **wrong);
+/* end of logging */
+extern void add_fake_lag(Client *client, long msec);
+extern char *prefix_with_extban(const char *remainder, BanContext *b, Extban *extban, char *buf, size_t buflen);
+extern GeoIPResult *geoip_client(Client *client);
+extern GeoIPResult *geoip_lookup(const char *ip);
+extern void free_geoip_result(GeoIPResult *r);
+extern const char *get_operlogin(Client *client);
+extern const char *get_operclass(Client *client);
+/* url stuff */
+extern const char *unreal_mkcache(const char *url);
+extern int has_cached_version(const char *url);
+extern int url_is_valid(const char *);
+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 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 EVENT(garbage_collect);
+extern EVENT(loop_event);
+extern EVENT(check_pings);
+extern EVENT(handshake_timeout);
+extern EVENT(check_deadsockets);
+extern EVENT(try_connections);
+extern const char *my_itoa(int i);
+extern void load_tunefile(void);
+extern EVENT(save_tunefile);
+extern void read_motd(const char *filename, MOTDFile *motd);
+extern int target_limit_exceeded(Client *client, void *target, const char *name);
+extern void make_umodestr(void);
+extern void initwhowas(void);
+extern void uid_init(void);
+extern const char *uid_get(void);
diff --git a/include/ircsprintf.h b/include/ircsprintf.h
@@ -17,7 +17,7 @@
 # define FORMAT_STRING(p) p
 #endif
 
-extern char *ircvsnprintf(char *str, size_t size, const char *format, va_list);
+extern char *ircvsnprintf(char *str, size_t size, const char *format, va_list) __attribute__((format(printf,3,0)));
 extern char *ircsnprintf(char *str, size_t size, FORMAT_STRING(const char *format), ...) __attribute__((format(printf,3,4)));
 
 #endif
diff --git a/include/modules.h b/include/modules.h
@@ -24,7 +24,7 @@
 #define MAXCUSTOMHOOKS  30
 #define MAXHOOKTYPES	150
 #define MAXCALLBACKS	30
-#define MAXEFUNCTIONS	90
+#define MAXEFUNCTIONS	128
 #if defined(_WIN32)
  #define MOD_EXTENSION "dll"
  #define DLLFUNC	_declspec(dllexport)
@@ -95,7 +95,6 @@ typedef enum ModuleObjectType {
 	MOBJ_COMMAND = 3,
 	MOBJ_HOOKTYPE = 4,
 	MOBJ_VERSIONFLAG = 5,
-	MOBJ_SNOMASK = 6,
 	MOBJ_UMODE = 7,
 	MOBJ_COMMANDOVERRIDE = 8,
 	MOBJ_EXTBAN = 9,
@@ -110,23 +109,16 @@ typedef enum ModuleObjectType {
 	MOBJ_HISTORY_BACKEND = 18,
 } ModuleObjectType;
 
-typedef struct {
-        long mode; /**< Mode mask */
-        char flag; /**< Mode character */
-        int unset_on_deoper; /**< When set to 1 then this user mode will be unset on de-oper */
-        int (*allowed)(Client *client, int what); /**< The 'is this user allowed to set this mode?' routine */
-        char unloaded; /**< Internal flag to indicate module is being unloaded */
-        Module *owner; /**< Module that owns this user mode */
-} Umode;
-
-typedef struct {
-        long mode; /**< Snomask mask */
-        char flag; /**< Snomask character */
-        int unset_on_deoper; /**< When set to 1 then this snomask will be unset on de-oper */
-        int (*allowed)(Client *client, int what); /**< The 'is this user allowed to set this snomask?' routine */
-        char unloaded; /**< Internal flag to indicate module is being unloaded */
-        Module *owner; /**< Module that owns this snomask */
-} Snomask;
+typedef struct Umode Umode;
+struct Umode {
+	Umode *prev, *next;
+	long mode; /**< Mode mask */
+	char letter; /**< Mode character */
+	int unset_on_deoper; /**< When set to 1 then this user mode will be unset on de-oper */
+	int (*allowed)(Client *client, int what); /**< The 'is this user allowed to set this mode?' routine */
+	char unloaded; /**< Internal flag to indicate module is being unloaded */
+	Module *owner; /**< Module that owns this user mode */
+};
 
 typedef enum ModDataType {
 	MODDATATYPE_LOCAL_VARIABLE	= 1,
@@ -138,6 +130,11 @@ typedef enum ModDataType {
 	MODDATATYPE_MEMBERSHIP		= 7,
 } ModDataType;
 
+typedef enum ModDataSync {
+	MODDATA_SYNC_NORMAL		= 1, /**< Sync normally via MD command */
+	MODDATA_SYNC_EARLY		= 2, /**< Attempt to (also) sync early in the UID command */
+} ModDataSync;
+
 typedef struct ModDataInfo ModDataInfo;
 
 struct ModDataInfo {
@@ -148,9 +145,11 @@ struct ModDataInfo {
 	int slot; /**< Assigned slot */
 	char unloaded; /**< Module being unloaded? */
 	void (*free)(ModData *m); /**< Function will be called when the data needs to be freed (may be NULL if not using dynamic storage) */
-	char *(*serialize)(ModData *m); /**< Function which converts the data to a string. May return NULL if 'm' contains no data (since for example m->ptr may be NULL). */
-	void (*unserialize)(char *str, ModData *m); /**< Function which converts the string back to data */
-	int sync; /**< Send in netsynch (when servers connect) */
+	const char *(*serialize)(ModData *m); /**< Function which converts the data to a string. May return NULL if 'm' contains no data (since for example m->ptr may be NULL). */
+	void (*unserialize)(const char *str, ModData *m); /**< Function which converts the string back to data */
+	ModDataSync sync; /**< Send in netsynch (when servers connect) */
+	int remote_write; /**< Allow remote servers to set/unset this moddata, even if it they target one of our own clients */
+	int self_write; /**< Allow remote servers to set/unset moddata of their own server object (irc1.example.net writing the MD object of irc1.example.net) */
 };
 
 #define moddata_client(acptr, md)    acptr->moddata[md->slot]
@@ -191,6 +190,17 @@ typedef enum BypassChannelMessageRestrictionType {
 /** Channel mode bit/value */
 typedef unsigned long Cmode_t;
 
+typedef enum CmodeType {
+	CMODE_NORMAL=0,
+	CMODE_MEMBER=1,
+} CmodeType;
+
+#define RANK_CHANOWNER  4000
+#define RANK_CHANADMIN  3000
+#define RANK_CHANOP     2000
+#define RANK_HALFOP     1000
+#define RANK_VOICE        -1
+
 /** Channel mode handler.
  * This struct contains all extended channel mode information,
  * like the flag, mode, their handler functions, etc.
@@ -199,15 +209,36 @@ typedef unsigned long Cmode_t;
  * and set the 'is_ok' function. All the rest is for parameter modes
  * or is optional.
  */
-typedef struct {
+typedef struct Cmode Cmode;
+struct Cmode {
+	Cmode *prev, *next;
+
 	/** mode character (like 'Z') */
-	char		flag;
+	char		letter;
+
+	CmodeType	type;
 
-	/** unique flag (like 0x10) */
+	/** If type is CMODE_NORMAL, then bitmask (eg: 0x10) that
+	 * is used in channel->mode.mode
+	 */
 	Cmode_t		mode;
 
+	/** If type is CMODE_MEMBER, then the prefix used in NAMES etc (eg @) */
+	char		prefix;
+
+	/** If type is CMODE_MEMBER, then the prefix used in SJOIN (eg @) */
+	char		sjoin_prefix;
+
+	/** If type is CMODE_MEMBER, then the rank of this prefix.
+	 * Higher ranking = more rights.
+	 * This is used, for example, in NAMES without NAMESX when we can only
+	 * show one symbol but not all.
+	 * For the shipped modules vhoaq we use the RANK_* values.
+	 */
+	int		rank;
+
 	/** Number of parameters (1 or 0) */
-	int			paracount;
+	int		paracount;
 
 	/** Check access or parameter of the channel mode.
 	 * @param client	The client
@@ -217,24 +248,24 @@ typedef struct {
 	 * @param what		MODE_ADD or MODE_DEL
 	 * @returns EX_DENY, EX_ALLOW or EX_ALWAYS_DENY
 	 */
-	int (*is_ok)(Client *client, Channel *channel, char mode, char *para, int checkt, int what);
+	int (*is_ok)(Client *client, Channel *channel, char mode, const char *para, int checkt, int what);
 
 	/** Store parameter in memory for channel.
 	 * This function pointer is NULL (unused) for modes without parameters.
-	 * @param list		The list, this usually points to channel->mode.extmodeparams.
+	 * @param list		The list, this usually points to channel->mode.mode_params.
 	 * @param para		The parameter to store.
 	 * @returns the head of the list, RTFS if you wonder why.
 	 * @note IMPORTANT: only allocate a new paramstruct if you need to.
 	 *       Search for any current one first! Eg: in case of mode +y 5 and then +y 6 later without -y.
 	 */
-	void *(*put_param)(void *list, char *para);
+	void *(*put_param)(void *list, const char *para);
 
 	/** Get the stored parameter as a readable/printable string.
 	 * This function pointer is NULL (unused) for modes without parameters.
 	 * @param parastruct	The parameter struct
 	 * @returns a pointer to the string (temporary storage)
 	 */
-	char *(*get_param)(void *parastruct);
+	const char *(*get_param)(void *parastruct);
 
 	/** Convert input parameter to output.
 	 * This converts stuff like where a MODE +l "1aaa" becomes "1".
@@ -249,7 +280,7 @@ typedef struct {
 	 *       In particular you MUST NOT SEND ERRORS to the client.
 	 *       This should be done in is_ok() and not in conv_param().
 	 */
-	char *(*conv_param)(char *para, Client *client, Channel *channel);
+	const char *(*conv_param)(const char *para, Client *client, Channel *channel);
 
 	/** Free and remove parameter from list.
 	 * This function pointer is NULL (unused) for modes without parameters.
@@ -295,24 +326,28 @@ typedef struct {
 	char unloaded;
 	
 	/** Slot number - Can be used instead of GETPARAMSLOT() */
-	int slot;
+	int param_slot;
 	
 	/** Module owner */
         Module *owner;
-} Cmode;
+};
 
 /** The struct used to register a channel mode handler.
  * For documentation, see Cmode struct.
  */
 typedef struct {
-	char		flag;
+	char		letter;
+	CmodeType	type;
+	char		prefix;
+	char		sjoin_prefix;
+	int		rank;
 	int		paracount;
-	int		(*is_ok)(Client *,Channel *, char mode, char *para, int, int);
-	void *	(*put_param)(void *, char *);
-	char *		(*get_param)(void *);
-	char *		(*conv_param)(char *, Client *, Channel *);
+	int		(*is_ok)(Client *,Channel *, char mode, const char *para, int, int);
+	void *		(*put_param)(void *, const char *);
+	const char *	(*get_param)(void *);
+	const char *	(*conv_param)(const char *, Client *, Channel *);
 	void		(*free_param)(void *);
-	void *	(*dup_struct)(void *);
+	void *		(*dup_struct)(void *);
 	int		(*sjoin_check)(Channel *, void *, void *);
 	char		local;
 	char		unset_with_param;
@@ -328,7 +363,7 @@ typedef struct {
 #define GETPARAMHANDLERBYLETTER(x)	ParamTable[GETPARAMSLOT(x)]
 
 /** Get paramter data struct - for like: ((aModejEntry *)GETPARASTRUCT(channel, 'j'))->t */
-#define GETPARASTRUCT(mychannel, mychar)	channel->mode.extmodeparams[GETPARAMSLOT(mychar)]
+#define GETPARASTRUCT(mychannel, mychar)	channel->mode.mode_params[GETPARAMSLOT(mychar)]
 
 #define GETPARASTRUCTEX(v, mychar)	v[GETPARAMSLOT(mychar)]
 
@@ -341,16 +376,20 @@ typedef struct {
 
 /*** Extended bans ***/
 
-// TODO: These should be enums!
+typedef enum ExtbanCheck {
+	EXBCHK_ACCESS=0,	/**< Check access */
+	EXBCHK_ACCESS_ERR=1,	/**< Check access and send error */
+	EXBCHK_PARAM=2		/**< Check if the parameter is valid */
+} ExtbanCheck;
 
-#define EXBCHK_ACCESS		0 /* Check access */
-#define EXBCHK_ACCESS_ERR	1 /* Check access and send error */
-#define EXBCHK_PARAM		2 /* Check if the parameter is valid */
+typedef enum ExtbanType {
+	EXBTYPE_BAN=0,		/**< Ban (channel mode +b) */
+	EXBTYPE_EXCEPT=1,	/**< Ban exception (channel mode +e) */
+	EXBTYPE_INVEX=2,	/**< Invite exception (channel mode +I) */
+	EXBTYPE_TKL=3		/**< TKL or other generic matcher outside banning routines */
+} ExtbanType;
 
-#define EXBTYPE_BAN		0 /* a ban */
-#define EXBTYPE_EXCEPT		1 /* an except */
-#define EXBTYPE_INVEX		2 /* an invite exception */
-#define EXBTYPE_TKL		3 /* TKL or other generic matcher outside banning routines */
+#define BCTX_CONV_OPTION_WRITE_LETTER_BANS	1 /* Always write letter extbans in output of conv_param */
 
 #define EXTBANTABLESZ		32
 
@@ -363,54 +402,66 @@ typedef enum ExtbanOptions {
 } ExtbanOptions;
 
 typedef struct {
-	/** extbans module */
-	Module *owner;
+	Client *client;		/**< Client to check, can be a remote client */
+	Channel *channel;	/**< Channel to check */
+	const char *banstr;	/**< Mask string (ban) */
+	int ban_check_types;	/**< Ban types to check for, one or more of BANCHK_* OR'd together */
+	const char *msg;	/**< Message, only for some BANCHK_* types (for censoring text) */
+	const char *error_msg;	/**< Error message, can be NULL */
+	int no_extbans;		/**< Set to 1 to disable extended bans checking - only nick!user@host allowed */
+	int what;		/**< MODE_ADD or MODE_DEL (for is_ok) */
+	ExtbanType ban_type;	/**< EXBTYPE_BAN or EXBTYPE_EXCEPT (for is_ok) */
+	ExtbanCheck is_ok_check;/**< One of EXBCHK_* (for is_ok) */
+	int conv_options;	/**< One of BCTX_CONV_OPTION_* (for conv_param) */
+} BanContext;
+
+typedef struct Extban Extban;
+
+struct Extban {
+	Extban *prev, *next;
+
 	/** extended ban character */
-	char	flag;
+	char letter;
+
+	/** extended ban name */
+	char *name;
 
 	/** extban options */
 	ExtbanOptions options;
 
-	/** access checking [optional].
-	 * Client *: the client
-	 * Channel *: the channel
-	 * para: the ban parameter
-	 * int: check type (see EXBCHK_*)
-	 * int: what (MODE_ADD or MODE_DEL)
-	 * int: what2 (EXBTYPE_BAN or EXBTYPE_EXCEPT)
-	 * return value: 1=ok, 0=bad
-	 * NOTE: just set this of NULL if you want only +hoaq to place/remove bans as usual.
-	 * NOTE2: This has not been tested yet!!
-	 */
-	int			(*is_ok)(Client *, Channel *, char *para, int, int, int);
+	unsigned int is_banned_events;	/**< Which BANCHK_* events to listen on */
+
+	int (*is_ok)(BanContext *b);
 
 	/** Convert input parameter to output [optional].
 	 * like with normal bans '+b blah' gets '+b blah!*@*', and it allows
-	 * you to limit the length of the ban too. You can set this to NULL however
-	 * to use the value as-is.
-	 * char *: the input parameter.
+	 * you to limit the length of the ban too.
 	 * return value: pointer to output string (temp. storage)
 	 */
-	char *		(*conv_param)(char *);
-
-	/** Checks if the user is affected by this ban [required].
-	 * Called from is_banned.
-	 * Client *: the client
-	 * Channel *: the channel
-	 * para: the ban entry
-	 * int: a value of BANCHK_* (see struct.h)
-	 * char **: optionally a message, can be NULL!! (for some BANCHK_ types)
-	 * char **: optionally for setting an error message, can be NULL!!
+	const char *(*conv_param)(BanContext *b, Extban *handler);
+
+	/** Checks if the user is affected by this ban [optional].
+	 * This may be set to NULL if you have is_banned_events set to 0 (zero),
+	 * this can be useful if you don't actually ban a user, eg for text bans.
+	 * This function is called from is_banned() and two other places.
 	 */
-	int			(*is_banned)(Client *client, Channel *channel, char *para, int checktype, char **msg, char **errormsg);
-} Extban;
+	int (*is_banned)(BanContext *b);
+
+	/** extbans module */
+	Module *owner;
+
+	/* Set to 1 during rehash when module is unloading (which may be re-used, and then set to 0) */
+	char unloaded;
+};
 
 typedef struct {
-	char	flag;
+	char letter;
+	char *name;
 	ExtbanOptions options;
-	int			(*is_ok)(Client *, Channel *, char *para, int, int, int);
-	char *			(*conv_param)(char *);
-	int			(*is_banned)(Client *, Channel *, char *, int, char **, char **);
+	int (*is_ok)(BanContext *b);
+	const char *(*conv_param)(BanContext *b, Extban *handler);
+	int (*is_banned)(BanContext *b);
+	unsigned int is_banned_events;
 } ExtbanInfo;
 
 
@@ -439,8 +490,8 @@ struct ClientCapability {
 	char *name;                              /**< The name of the CAP */
 	long cap;                                /**< The acptr->user->proto we should set (if any, can be 0, like for sts) */
 	int flags;                               /**< A flag from CLICAP_FLAGS_* */
-	int (*visible)(Client *);               /**< Should the capability be visible? Note: parameter may be NULL. [optional] */
-	char *(*parameter)(Client *);           /**< CAP parameters. Note: parameter may be NULL. [optional] */
+	int (*visible)(Client *);                /**< Should the capability be visible? Note: parameter may be NULL. [optional] */
+	const char *(*parameter)(Client *);      /**< CAP parameters. Note: parameter may be NULL. [optional] */
 	MessageTagHandler *mtag_handler;         /**< For reverse dependency */
 	Module *owner;                           /**< Module introducing this CAP. */
 	char unloaded;                           /**< Internal flag to indicate module is being unloaded */
@@ -450,7 +501,7 @@ typedef struct {
 	char *name;
 	int flags;
 	int (*visible)(Client *);
-	char *(*parameter)(Client *);
+	const char *(*parameter)(Client *);
 } ClientCapabilityInfo;
 
 /** @defgroup MessagetagAPI Message tag API
@@ -465,13 +516,13 @@ typedef struct {
 /** Message Tag Handler */
 struct MessageTagHandler {
 	MessageTagHandler *prev, *next;
-	char *name;                                 /**< The name of the message-tag */
-	int flags;                                  /**< A flag of MTAG_HANDLER_FLAGS_* */
-	int (*is_ok)(Client *, char *, char *);     /**< Verify syntax and access rights */
-	int (*can_send)(Client *);                  /**< Tag may be sent to this client (normally NULL!) */
-	Module *owner;                              /**< Module introducing this CAP. */
-	ClientCapability *clicap_handler;           /**< Client capability handler associated with this */
-	char unloaded;                              /**< Internal flag to indicate module is being unloaded */
+	char *name;                                             /**< The name of the message-tag */
+	int flags;                                              /**< A flag of MTAG_HANDLER_FLAGS_* */
+	int (*is_ok)(Client *, const char *, const char *);     /**< Verify syntax and access rights */
+	int (*should_send_to_client)(Client *);                 /**< Tag may be sent to this client (normally NULL!) */
+	Module *owner;                                          /**< Module introducing this CAP. */
+	ClientCapability *clicap_handler;                       /**< Client capability handler associated with this */
+	char unloaded;                                          /**< Internal flag to indicate module is being unloaded */
 };
 
 /** The struct used to register a message tag handler.
@@ -480,8 +531,8 @@ struct MessageTagHandler {
 typedef struct {
 	char *name;
 	int flags;
-	int (*is_ok)(Client *, char *, char *);
-	int (*can_send)(Client *);
+	int (*is_ok)(Client *, const char *, const char *);
+	int (*should_send_to_client)(Client *);
 	ClientCapability *clicap_handler;
 } MessageTagHandlerInfo;
 
@@ -531,10 +582,10 @@ typedef struct HistoryBackend HistoryBackend;
 struct HistoryBackend {
 	HistoryBackend *prev, *next;
 	char *name;                                   /**< The name of the history backend (eg: "mem") */
-	int (*history_set_limit)(char *object, int max_lines, long max_time); /**< Impose a limit on a history object */
-	int (*history_add)(char *object, MessageTag *mtags, char *line); /**< Add to history */
-	HistoryResult *(*history_request)(char *object, HistoryFilter *filter);  /**< Request history */
-	int (*history_destroy)(char *object);  /**< Destroy history of this object completely */
+	int (*history_set_limit)(const char *object, int max_lines, long max_time); /**< Impose a limit on a history object */
+	int (*history_add)(const char *object, MessageTag *mtags, const char *line); /**< Add to history */
+	HistoryResult *(*history_request)(const char *object, HistoryFilter *filter);  /**< Request history */
+	int (*history_destroy)(const char *object);  /**< Destroy history of this object completely */
 	Module *owner;                                /**< Module introducing this */
 	char unloaded;                                /**< Internal flag to indicate module is being unloaded */
 };
@@ -544,10 +595,10 @@ struct HistoryBackend {
  */
 typedef struct {
 	char *name;
-	int (*history_set_limit)(char *object, int max_lines, long max_time);
-	int (*history_add)(char *object, MessageTag *mtags, char *line);
-	HistoryResult *(*history_request)(char *object, HistoryFilter *filter);
-	int (*history_destroy)(char *object);
+	int (*history_set_limit)(const char *object, int max_lines, long max_time);
+	int (*history_add)(const char *object, MessageTag *mtags, const char *line);
+	HistoryResult *(*history_request)(const char *object, HistoryFilter *filter);
+	int (*history_destroy)(const char *object);
 } HistoryBackendInfo;
 
 struct Hook {
@@ -557,7 +608,8 @@ struct Hook {
 	union {
 		int (*intfunc)();
 		void (*voidfunc)();
-		char *(*pcharfunc)();
+		char *(*stringfunc)();
+		const char *(*conststringfunc)();
 	} func;
 	Module *owner;
 };
@@ -568,7 +620,9 @@ struct Callback {
 	union {
 		int (*intfunc)();
 		void (*voidfunc)();
-		char *(*pcharfunc)();
+		void *(*pvoidfunc)();
+		char *(*stringfunc)();
+		const char *(*conststringfunc)();
 	} func;
 	Module *owner;
 	char willberemoved; /* will be removed on next rehash? (eg the 'old'/'current' one) */
@@ -589,7 +643,8 @@ struct Efunction {
 		int (*intfunc)();
 		void (*voidfunc)();
 		void *(*pvoidfunc)();
-		char *(*pcharfunc)();
+		char *(*stringfunc)();
+		const char *(*conststringfunc)();
 	} func;
 	Module *owner;
 	char willberemoved; /* will be removed on next rehash? (eg the 'old'/'current' one) */
@@ -618,7 +673,6 @@ typedef struct ModuleObject {
 		Command *command;
 		Hooktype *hooktype;
 		Versionflag *versionflag;
-		Snomask *snomask;
 		Umode *umode;
 		CommandOverride *cmdoverride;
 		Extban *extban;
@@ -717,31 +771,29 @@ extern MODVAR Hooktype		Hooktypes[MAXCUSTOMHOOKS];
 extern MODVAR Callback *Callbacks[MAXCALLBACKS], *RCallbacks[MAXCALLBACKS];
 extern MODVAR ClientCapability *clicaps;
 
-extern Event *EventAdd(Module *module, char *name, vFP event, void *data, long every_msec, int count);
+extern Event *EventAdd(Module *module, const char *name, vFP event, void *data, long every_msec, int count);
 extern void   EventDel(Event *event);
 extern Event *EventMarkDel(Event *event);
-extern Event *EventFind(char *name);
+extern Event *EventFind(const char *name);
 extern int EventMod(Event *event, EventInfo *mods);
 extern void DoEvents(void);
 extern void EventStatus(Client *client);
 extern void SetupEvents(void);
 
 
-extern void    Module_Init(void);
-extern char    *Module_Create(char *path);
-extern char    *Module_TransformPath(char *path_);
-extern void    Init_all_testing_modules(void);
-extern void    Unload_all_loaded_modules(void);
-extern void    Unload_all_testing_modules(void);
-extern int     Module_Unload(char *name);
-extern vFP     Module_Sym(char *name);
-extern vFP     Module_SymX(char *name, Module **mptr);
-extern int	Module_free(Module *mod);
-
+extern void Module_Init(void);
+extern const char *Module_Create(const char *path);
+extern const char *Module_TransformPath(const char *path_);
+extern void Init_all_testing_modules(void);
+extern void Unload_all_loaded_modules(void);
+extern void Unload_all_testing_modules(void);
+extern int Module_Unload(const char *name);
+extern vFP Module_Sym(const char *name);
+extern vFP Module_SymX(const char *name, Module **mptr);
+extern int Module_free(Module *mod);
 #ifdef __OpenBSD__
-extern void *obsd_dlsym(void *handle, char *symbol);
+extern void *obsd_dlsym(void *handle, const char *symbol);
 #endif
-
 #ifdef _WIN32
 extern const char *our_dlerror(void);
 #endif
@@ -771,179 +823,115 @@ extern HistoryBackend *HistoryBackendAdd(Module *module, HistoryBackendInfo *mre
 extern void HistoryBackendDel(HistoryBackend *m);
 
 #ifndef GCC_TYPECHECKING
-#define HookAdd(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, func, NULL, NULL)
-#define HookAddVoid(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, NULL, func, NULL)
-#define HookAddPChar(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, NULL, NULL, func)
+#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)
+#define HookAddString(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, NULL, NULL, func, NULL)
+#define HookAddConstString(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, NULL, NULL, NULL, func)
 #else
 #define HookAdd(module, hooktype, priority, func) \
 __extension__ ({ \
 	ValidateHooks(hooktype, func); \
-    HookAddMain(module, hooktype, priority, func, NULL, NULL); \
+    HookAddMain(module, hooktype, priority, func, NULL, NULL, NULL); \
 })
 
 #define HookAddVoid(module, hooktype, priority, func) \
 __extension__ ({ \
 	ValidateHooks(hooktype, func); \
-    HookAddMain(module, hooktype, priority, NULL, func, NULL); \
+    HookAddMain(module, hooktype, priority, NULL, func, NULL, NULL); \
 })
 
-#define HookAddPChar(module, hooktype, priority, func) \
+#define HookAddString(module, hooktype, priority, func) \
+__extension__ ({ \
+	ValidateHooks(hooktype, func); \
+    HookAddMain(module, hooktype, priority, NULL, NULL, func, NULL); \
+})
+#define HookAddConstString(module, hooktype, priority, func) \
 __extension__ ({ \
 	ValidateHooks(hooktype, func); \
-    HookAddMain(module, hooktype, priority, NULL, NULL, func); \
+    HookAddMain(module, hooktype, priority, NULL, NULL, NULL, func); \
 })
 #endif /* GCC_TYPCHECKING */
 
-extern Hook	*HookAddMain(Module *module, int hooktype, int priority, int (*intfunc)(), void (*voidfunc)(), char *(*pcharfunc)());
+extern Hook	*HookAddMain(Module *module, int hooktype, int priority, int (*intfunc)(), void (*voidfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)());
 extern Hook	*HookDel(Hook *hook);
 
-extern Hooktype *HooktypeAdd(Module *module, char *string, int *type);
+extern Hooktype *HooktypeAdd(Module *module, const char *string, int *type);
 extern void HooktypeDel(Hooktype *hooktype, Module *module);
 
-#define RunHook0(hooktype) do { Hook *h; for (h = Hooks[hooktype]; h; h = h->next)(*(h->func.intfunc))(); } while(0)
-#define RunHook(hooktype,x) do { Hook *h; for (h = Hooks[hooktype]; h; h = h->next) (*(h->func.intfunc))(x); } while(0)
-#define RunHookReturn(hooktype,x,retchk) \
-{ \
- int retval; \
- Hook *h; \
- for (h = Hooks[hooktype]; h; h = h->next) \
- { \
-  retval = (*(h->func.intfunc))(x); \
-  if (retval retchk) return; \
- } \
-}
-#define RunHookReturn2(hooktype,x,y,retchk) \
-{ \
- int retval; \
- Hook *h; \
- for (h = Hooks[hooktype]; h; h = h->next) \
- { \
-  retval = (*(h->func.intfunc))(x,y); \
-  if (retval retchk) return; \
- } \
-}
-#define RunHookReturn3(hooktype,x,y,z,retchk) \
-{ \
- int retval; \
- Hook *h; \
- for (h = Hooks[hooktype]; h; h = h->next) \
- { \
-  retval = (*(h->func.intfunc))(x,y,z); \
-  if (retval retchk) return; \
- } \
-}
-#define RunHookReturn4(hooktype,a,b,c,d,retchk) \
+#define RunHook(hooktype,...) do { Hook *h; for (h = Hooks[hooktype]; h; h = h->next) (*(h->func.intfunc))(__VA_ARGS__); } while(0)
+#define RunHookReturn(hooktype,retchk,...) \
 { \
  int retval; \
  Hook *h; \
  for (h = Hooks[hooktype]; h; h = h->next) \
  { \
-  retval = (*(h->func.intfunc))(a,b,c,d); \
+  retval = (*(h->func.intfunc))(__VA_ARGS__); \
   if (retval retchk) return; \
  } \
 }
-#define RunHookReturnInt(hooktype,x,retchk) \
-{ \
- int retval; \
- Hook *h; \
- for (h = Hooks[hooktype]; h; h = h->next) \
- { \
-  retval = (*(h->func.intfunc))(x); \
-  if (retval retchk) return retval; \
- } \
-}
-#define RunHookReturnInt2(hooktype,x,y,retchk) \
-{ \
- int retval; \
- Hook *h; \
- for (h = Hooks[hooktype]; h; h = h->next) \
- { \
-  retval = (*(h->func.intfunc))(x,y); \
-  if (retval retchk) return retval; \
- } \
-}
-#define RunHookReturnInt3(hooktype,x,y,z,retchk) \
+#define RunHookReturnInt(hooktype,retchk,...) \
 { \
  int retval; \
  Hook *h; \
  for (h = Hooks[hooktype]; h; h = h->next) \
  { \
-  retval = (*(h->func.intfunc))(x,y,z); \
-  if (retval retchk) return retval; \
- } \
-}
-#define RunHookReturnInt4(hooktype,a,b,c,d,retchk) \
-{ \
- int retval; \
- Hook *h; \
- for (h = Hooks[hooktype]; h; h = h->next) \
- { \
-  retval = (*(h->func.intfunc))(a,b,c,d); \
+  retval = (*(h->func.intfunc))(__VA_ARGS__); \
   if (retval retchk) return retval; \
  } \
 }
 
-#define RunHookReturnVoid(hooktype,x,ret) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) if((*(hook->func.intfunc))(x) ret) return; } while(0)
-#define RunHook2(hooktype,x,y) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(x,y); } while(0)
-#define RunHook3(hooktype,a,b,c) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(a,b,c); } while(0)
-#define RunHook4(hooktype,a,b,c,d) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(a,b,c,d); } while(0)
-#define RunHook5(hooktype,a,b,c,d,e) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(a,b,c,d,e); } while(0)
-#define RunHook6(hooktype,a,b,c,d,e,f) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(a,b,c,d,e,f); } while(0)
-#define RunHook7(hooktype,a,b,c,d,e,f,g) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(a,b,c,d,e,f,g); } while(0)
-#define RunHook8(hooktype,a,b,c,d,e,f,g,h) do { Hook *hook; for (hook = Hooks[hooktype]; hook; hook = hook->next) (*(hook->func.intfunc))(a,b,c,d,e,f,g,h); } while(0)
-
-#define CallbackAdd(cbtype, func) CallbackAddMain(NULL, cbtype, func, NULL, NULL)
-#define CallbackAddEx(module, cbtype, func) CallbackAddMain(module, cbtype, func, NULL, NULL)
-#define CallbackAddVoid(cbtype, func) CallbackAddMain(NULL, cbtype, NULL, func, NULL)
-#define CallbackAddVoidEx(module, cbtype, func) CallbackAddMain(module, cbtype, NULL, func, NULL)
-#define CallbackAddPChar(cbtype, func) CallbackAddMain(NULL, cbtype, NULL, NULL, func)
-#define CallbackAddPCharEx(module, cbtype, func) CallbackAddMain(module, cbtype, NULL, NULL, func)
-
-extern Callback	*CallbackAddMain(Module *module, int cbtype, int (*intfunc)(), void (*voidfunc)(), char *(*pcharfunc)());
+#define CallbackAdd(module, cbtype, func) CallbackAddMain(module, cbtype, func, NULL, NULL, NULL, NULL)
+#define CallbackAddVoid(module, cbtype, func) CallbackAddMain(module, cbtype, NULL, func, NULL, NULL, NULL)
+#define CallbackAddPVoid(module, cbtype, func) CallbackAddMain(module, cbtype, NULL, NULL, func, NULL, NULL)
+#define CallbackAddString(module, cbtype, func) CallbackAddMain(module, cbtype, NULL, NULL, NULL, func, NULL)
+#define CallbackAddConstString(module, cbtype, func) CallbackAddMain(module, cbtype, NULL, NULL, NULL, NULL, func)
+
+extern Callback *CallbackAddMain(Module *module, int cbtype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)());
 extern Callback	*CallbackDel(Callback *cb);
 
-#define EfunctionAdd(module, cbtype, func) EfunctionAddMain(module, cbtype, func, NULL, NULL, NULL)
-#define EfunctionAddVoid(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, func, NULL, NULL)
-#define EfunctionAddPVoid(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, NULL, func, NULL)
-#define EfunctionAddPChar(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, NULL, NULL, func)
+#define EfunctionAdd(module, cbtype, func) EfunctionAddMain(module, cbtype, func, NULL, NULL, NULL, NULL)
+#define EfunctionAddVoid(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, func, NULL, NULL, NULL)
+#define EfunctionAddPVoid(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, NULL, func, NULL, NULL)
+#define EfunctionAddString(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, NULL, NULL, func, NULL)
+#define EfunctionAddConstString(module, cbtype, func) EfunctionAddMain(module, cbtype, NULL, NULL, NULL, NULL, func)
 
-extern Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*intfunc)(), void (*voidfunc)(), void *(*pvoidfunc)(), char *(*pcharfunc)());
+extern Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*intfunc)(), void (*voidfunc)(), void *(*pvoidfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)());
 extern Efunction *EfunctionDel(Efunction *cb);
 
-extern Command *CommandAdd(Module *module, char *cmd, CmdFunc func, unsigned char params, int flags);
-extern Command *AliasAdd(Module *module, char *cmd, AliasCmdFunc aliasfunc, unsigned char params, int flags);
+extern Command *CommandAdd(Module *module, const char *cmd, CmdFunc func, unsigned char params, int flags);
+extern Command *AliasAdd(Module *module, const char *cmd, AliasCmdFunc aliasfunc, unsigned char params, int flags);
 extern void CommandDel(Command *command);
 extern void CommandDelX(Command *command, RealCommand *cmd);
-extern int CommandExists(char *name);
-extern CommandOverride *CommandOverrideAdd(Module *module, char *cmd, OverrideCmdFunc func);
-extern CommandOverride *CommandOverrideAddEx(Module *module, char *name, int priority, OverrideCmdFunc func);
+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, char *parv[]);
+extern void CallCommandOverride(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, const char *parv[]);
 
 extern void moddata_free_client(Client *acptr);
 extern void moddata_free_local_client(Client *acptr);
 extern void moddata_free_channel(Channel *channel);
 extern void moddata_free_member(Member *m);
 extern void moddata_free_membership(Membership *m);
-extern ModDataInfo *findmoddata_byname(char *name, ModDataType type);
-extern int moddata_client_set(Client *acptr, char *varname, char *value);
-extern char *moddata_client_get(Client *acptr, char *varname);
-extern int moddata_local_client_set(Client *acptr, char *varname, char *value);
-extern char *moddata_local_client_get(Client *acptr, char *varname);
-
-extern int LoadPersistentPointerX(ModuleInfo *modinfo, char *varshortname, void **var, void (*free_variable)(ModData *m));
+extern ModDataInfo *findmoddata_byname(const char *name, ModDataType type);
+extern int moddata_client_set(Client *acptr, const char *varname, const char *value);
+extern const char *moddata_client_get(Client *acptr, const char *varname);
+extern ModData *moddata_client_get_raw(Client *client, const char *varname);
+extern int moddata_local_client_set(Client *acptr, const char *varname, const char *value);
+extern const char *moddata_local_client_get(Client *acptr, const char *varname);
+
+extern int LoadPersistentPointerX(ModuleInfo *modinfo, const char *varshortname, void **var, void (*free_variable)(ModData *m));
 #define LoadPersistentPointer(modinfo, var, free_variable) LoadPersistentPointerX(modinfo, #var, (void **)&var, free_variable)
-extern void SavePersistentPointerX(ModuleInfo *modinfo, char *varshortname, void *var);
+extern void SavePersistentPointerX(ModuleInfo *modinfo, const char *varshortname, void *var);
 #define SavePersistentPointer(modinfo, var) SavePersistentPointerX(modinfo, #var, var)
 
-extern int LoadPersistentIntX(ModuleInfo *modinfo, char *varshortname, int *var);
+extern int LoadPersistentIntX(ModuleInfo *modinfo, const char *varshortname, int *var);
 #define LoadPersistentInt(modinfo, var) LoadPersistentIntX(modinfo, #var, &var)
-extern void SavePersistentIntX(ModuleInfo *modinfo, char *varshortname, int var);
+extern void SavePersistentIntX(ModuleInfo *modinfo, const char *varshortname, int var);
 #define SavePersistentInt(modinfo, var) SavePersistentIntX(modinfo, #var, var)
 
-extern int LoadPersistentLongX(ModuleInfo *modinfo, char *varshortname, long *var);
+extern int LoadPersistentLongX(ModuleInfo *modinfo, const char *varshortname, long *var);
 #define LoadPersistentLong(modinfo, var) LoadPersistentLongX(modinfo, #var, &var)
-extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long var);
+extern void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, long var);
 #define SavePersistentLong(modinfo, var) SavePersistentLongX(modinfo, #var, var)
 
 /** Hooks trigger on "events", such as a new user connecting or joining a channel,
@@ -1121,8 +1109,6 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va
 #define HOOKTYPE_CHANNEL_SYNCED	83
 /** See hooktype_can_sajoin() */
 #define HOOKTYPE_CAN_SAJOIN	84
-/** See hooktype_check_init() */
-#define HOOKTYPE_CHECK_INIT	85
 /** See hooktype_mode_deop() */
 #define HOOKTYPE_MODE_DEOP	86
 /** See hooktype_dcc_denied() */
@@ -1131,8 +1117,6 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va
 #define HOOKTYPE_SECURE_CONNECT	88
 /** See hooktype_can_bypass_channel_message_restriction() */
 #define HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION	89
-/** See hooktype_require_sasl() */
-#define HOOKTYPE_REQUIRE_SASL	90
 /** See hooktype_sasl_continuation() */
 #define HOOKTYPE_SASL_CONTINUATION	91
 /** See hooktype_sasl_result() */
@@ -1161,6 +1145,18 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va
 #define HOOKTYPE_CLOSE_CONNECTION	103
 /** See hooktype_connect_extinfo() */
 #define HOOKTYPE_CONNECT_EXTINFO	104
+/** See hooktype_is_invited() */
+#define HOOKTYPE_IS_INVITED	105
+/** See hooktype_post_local_nickchange() */
+#define HOOKTYPE_POST_LOCAL_NICKCHANGE	106
+/** See hooktype_post_remote_nickchange() */
+#define HOOKTYPE_POST_REMOTE_NICKCHANGE	107
+/** See hooktype_userhost_changed() */
+#define HOOKTYPE_USERHOST_CHANGED 108
+/** See hooktype_realname_changed() */
+#define HOOKTYPE_REALNAME_CHANGED 109
+/** See hooktype_can_set_topic() */
+#define HOOKTYPE_CAN_SET_TOPIC	110
 /* Adding a new hook here?
  * 1) Add the #define HOOKTYPE_.... with a new number
  * 2) Add a hook prototype (see below)
@@ -1193,7 +1189,7 @@ int hooktype_remote_connect(Client *client);
  * @param client		The quit/disconnect reason
  * @return The quit reason (you may also return 'comment' if it should be unchanged) or NULL for an empty reason.
  */
-char *hooktype_pre_local_quit(Client *client, char *comment);
+const char *hooktype_pre_local_quit(Client *client, const char *comment);
 
 /** Called when a local user quits or otherwise disconnects (function prototype for HOOKTYPE_PRE_LOCAL_QUIT).
  * @param client		The client
@@ -1201,7 +1197,7 @@ char *hooktype_pre_local_quit(Client *client, char *comment);
  * @param comment       	The quit/exit reason
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_quit(Client *client, MessageTag *mtags, char *comment);
+int hooktype_local_quit(Client *client, MessageTag *mtags, const char *comment);
 
 /** Called when a remote user qutis or otherwise disconnects (function prototype for HOOKTYPE_REMOTE_QUIT).
  * @param client		The client
@@ -1209,7 +1205,7 @@ int hooktype_local_quit(Client *client, MessageTag *mtags, char *comment);
  * @param comment       	The quit/exit reason
  * @return The return value is ignored (use return 0)
  */
-int hooktype_remote_quit(Client *client, MessageTag *mtags, char *comment);
+int hooktype_remote_quit(Client *client, MessageTag *mtags, const char *comment);
 
 /** Called when an unregistered user disconnects, so before the user was fully online (function prototype for HOOKTYPE_UNKUSER_QUIT).
  * @param client		The client
@@ -1217,7 +1213,7 @@ int hooktype_remote_quit(Client *client, MessageTag *mtags, char *comment);
  * @param comment       	The quit/exit reason
  * @return The return value is ignored (use return 0)
  */
-int hooktype_unkuser_quit(Client *client, MessageTag *mtags, char *comment);
+int hooktype_unkuser_quit(Client *client, MessageTag *mtags, const char *comment);
 
 /** Called when a local or remote server connects / links in (function prototype for HOOKTYPE_SERVER_CONNECT).
  * @param client		The client
@@ -1262,7 +1258,7 @@ int hooktype_server_quit(Client *client, MessageTag *mtags);
  * @param newnick		The new nick name
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_nickchange(Client *client, MessageTag *mtags, char *newnick);
+int hooktype_local_nickchange(Client *client, MessageTag *mtags, const char *newnick);
 
 /** Called when a remote user changes the nick name (function prototype for HOOKTYPE_REMOTE_NICKCHANGE).
  * @param client		The client
@@ -1270,45 +1266,42 @@ int hooktype_local_nickchange(Client *client, MessageTag *mtags, char *newnick);
  * @param newnick		The new nick name
  * @return The return value is ignored (use return 0)
  */
-int hooktype_remote_nickchange(Client *client, MessageTag *mtags, char *newnick);
+int hooktype_remote_nickchange(Client *client, MessageTag *mtags, const char *newnick);
 
 /** Called when a user wants to join a channel, may the user join? (function prototype for HOOKTYPE_CAN_JOIN).
  * @param client		The client
  * @param channel		The channel the user wants to join
  * @param key			The key supplied by the client
- * @param parv			The parameters from the JOIN. Normally you should not use this.
  * @return Return 0 to allow the user, any other value should be an IRC numeric (eg: ERR_BANNEDFROMCHAN).
  */
-int hooktype_can_join(Client *client, Channel *channel, char *key, char *parv[]);
+int hooktype_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
 
-/** Called when a user wants to join a channel, may the user join? (function prototype for HOOKTYPE_PRE_LOCAL_JOIN).
- * FIXME: It's not entirely clear why we have both hooktype_can_join() and hooktype_pre_local_join().
+/** Called when a user wants to join a channel (function prototype for HOOKTYPE_PRE_LOCAL_JOIN).
+ * IMPORTANT: Generally you want to use HOOKTYPE_CAN_JOIN / hooktype_can_join() instead!!
  * @param client		The client
  * @param channel		The channel the user wants to join
- * @param parv			The parameters from the JOIN. May contain channel key in parv[2].
+ * @param key			Channel key (can be NULL)
  * @retval HOOK_DENY		Deny the join.
  * @retval HOOK_ALLOW		Allow the join (stop processing other modules)
  * @retval HOOK_CONTINUE	Allow the join, unless another module blocks it.
  */
-int hooktype_pre_local_join(Client *client, Channel *channel, char *parv[]);
+int hooktype_pre_local_join(Client *client, Channel *channel, const char *key);
 
 /** Called when a local user joins a channel (function prototype for HOOKTYPE_LOCAL_JOIN).
  * @param client		The client
  * @param channel		The channel the user wants to join
  * @param mtags         	Message tags associated with the event
- * @param parv			The parameters from the JOIN. May contain channel key in parv[2].
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+int hooktype_local_join(Client *client, Channel *channel, MessageTag *mtags);
 
 /** Called when a remote user joins a channel (function prototype for HOOKTYPE_REMOTE_JOIN).
  * @param client		The client
  * @param channel		The channel the user wants to join
  * @param mtags         	Message tags associated with the event
- * @param parv			The parameters from the JOIN. May contain channel key in parv[2].
  * @return The return value is ignored (use return 0)
  */
-int hooktype_remote_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+int hooktype_remote_join(Client *client, Channel *channel, MessageTag *mtags);
 
 /** Called when a local user wants to part a channel (function prototype for HOOKTYPE_PRE_LOCAL_PART).
  * @param client		The client
@@ -1316,7 +1309,7 @@ int hooktype_remote_join(Client *client, Channel *channel, MessageTag *mtags, ch
  * @param comment		The PART reason, this may be NULL.
  * @return The part reason (you may also return 'comment' if it should be unchanged) or NULL for an empty reason.
  */
-char *hooktype_pre_local_part(Client *client, Channel *channel, char *comment);
+const char *hooktype_pre_local_part(Client *client, Channel *channel, const char *comment);
 
 /** Called when a local user parts a channel (function prototype for HOOKTYPE_LOCAL_PART).
  * @param client		The client
@@ -1325,7 +1318,7 @@ char *hooktype_pre_local_part(Client *client, Channel *channel, char *comment);
  * @param comment		The PART reason, this may be NULL.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_part(Client *client, Channel *channel, MessageTag *mtags, char *comment);
+int hooktype_local_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
 
 /** Called when a remote user parts a channel (function prototype for HOOKTYPE_REMOTE_PART).
  * @param client		The client
@@ -1334,25 +1327,25 @@ int hooktype_local_part(Client *client, Channel *channel, MessageTag *mtags, cha
  * @param comment		The PART reason, this may be NULL.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_remote_part(Client *client, Channel *channel, MessageTag *mtags, char *comment);
+int hooktype_remote_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
 
 /** Do not use this function, use hooktype_can_kick() instead!
  */
-char *hooktype_pre_local_kick(Client *client, Client *victim, Channel *channel, char *comment);
+const char *hooktype_pre_local_kick(Client *client, Client *victim, Channel *channel, const char *comment);
 
 /** Called when a local user wants to kick another user from a channel (function prototype for HOOKTYPE_CAN_KICK).
  * @param client		The client issuing the command
  * @param victim		The victim that should be kicked
  * @param channel		The channel the user should be kicked from
  * @param comment		The KICK reason, this may be NULL.
- * @param client_flags		The access flags of 'client', one of CHFL_*, eg CHFL_CHANOP.
- * @param victim_flags		The access flags of 'victim', one of CHFL_*, eg CHFL_VOICE.
- * @param error			The error message that should be shown to the user (full IRC protocol line).
+ * @param client_member_modes	The member modes of 'client' (eg "o"), never NULL but can be empty.
+ * @param victim_member_modes	The member modes of 'victim' (eg "v"), never NULL but can be empty.
+ * @param errmsg		The error message that should be shown to the user (full IRC protocol line).
  * @retval EX_DENY		Deny the KICK (unless IRCOp with sufficient override rights).
  * @retval EX_ALWAYS_DENY	Deny the KICK always (even if IRCOp).
  * @retval EX_ALLOW		Allow the kick, unless another module blocks it.
  */
-int hooktype_can_kick(Client *client, Client *victim, Channel *channel, char *comment, long client_flags, long victim_flags, char **error);
+int hooktype_can_kick(Client *client, Client *victim, Channel *channel, const char *comment, const char *client_member_modes, const char *victim_member_modes, const char **errmsg);
 
 /** Called when a local user is kicked (function prototype for HOOKTYPE_LOCAL_KICK).
  * @param client		The client issuing the command
@@ -1362,7 +1355,7 @@ int hooktype_can_kick(Client *client, Client *victim, Channel *channel, char *co
  * @param comment		The KICK reason, this may be NULL.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
+int hooktype_local_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, const char *comment);
 
 /** Called when a remote user is kicked (function prototype for HOOKTYPE_REMOTE_KICK).
  * @param client		The client issuing the command
@@ -1372,7 +1365,7 @@ int hooktype_local_kick(Client *client, Client *victim, Channel *channel, Messag
  * @param comment		The KICK reason, this may be NULL.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_remote_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
+int hooktype_remote_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, const char *comment);
 
 /** Called right before a message is sent to the channel (function prototype for HOOKTYPE_PRE_CHANMSG).
  * This function is only used by delayjoin. It cannot block a message. See hooktype_can_send_to_user() instead!
@@ -1382,7 +1375,7 @@ int hooktype_remote_kick(Client *client, Client *victim, Channel *channel, Messa
  * @param text			The text that will be sent
  * @return The return value is ignored (use return 0)
  */
-int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype);
+int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, const char *text, SendType sendtype);
 
 /** Called when a user wants to send a message to another user (function prototype for HOOKTYPE_CAN_SEND_TO_USER).
  * @param client		The sender
@@ -1393,7 +1386,7 @@ int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, ch
  * @retval HOOK_DENY		Deny the message. The 'errmsg' will be sent to the user.
  * @retval HOOK_CONTINUE	Allow the message, unless other modules block it.
  */
-int hooktype_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int hooktype_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
 
 /** Called when a user wants to send a message to a channel (function prototype for HOOKTYPE_CAN_SEND_TO_CHANNEL).
  * @param client		The sender
@@ -1405,7 +1398,7 @@ int hooktype_can_send_to_user(Client *client, Client *target, char **text, char 
  * @retval HOOK_DENY		Deny the message. The 'errmsg' will be sent to the user.
  * @retval HOOK_CONTINUE	Allow the message, unless other modules block it.
  */
-int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *member, char **text, char **errmsg, SendType sendtype);
+int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *member, const char **text, const char **errmsg, SendType sendtype);
 
 /** Called when a message is sent from one user to another user (function prototype for HOOKTYPE_USERMSG).
  * @param client		The sender
@@ -1415,20 +1408,31 @@ int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *m
  * @param sendtype		The message type, for example SEND_TYPE_PRIVMSG.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype);
+int hooktype_usermsg(Client *client, Client *to, MessageTag *mtags, const char *text, SendType sendtype);
 
 /** Called when a message is sent to a channel (function prototype for HOOKTYPE_CHANMSG).
  * @param client		The sender
  * @param channel		The channel
  * @param sendflags		One of SEND_* (eg SEND_ALL, SKIP_DEAF).
- * @param prefix		Either zero, one or a combination of PREFIX_*.
+ * @param member_modes		Either NULL, or a member mode like "h", "o", etc.
  * @param target		Target string, usually this is "#channel", but it can also contain prefixes like "@#channel"
  * @param mtags         	Message tags associated with the event
  * @param text			The text
  * @param sendtype		The message type, for example SEND_TYPE_PRIVMSG.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
+int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, const char *member_modes, const char *target, MessageTag *mtags, const char *text, SendType sendtype);
+
+/** Called when a user wants to set the topic (function prototype for HOOKTYPE_CAN_SET_TOPIC).
+ * @param client		The client issuing the command
+ * @param channel		The channel the topic should be set for
+ * @param topic			The topic that should be set, this may be NULL for unset.
+ * @param errmsg		The error message that should be shown to the user (full IRC protocol line).
+ * @retval EX_DENY		Deny the TOPIC (unless IRCOp with sufficient override rights).
+ * @retval EX_ALWAYS_DENY	Deny the TOPIC always (even if IRCOp).
+ * @retval EX_ALLOW		Allow the TOPIC, unless another module blocks it.
+ */
+int hooktype_can_set_topic(Client *client, Channel *channel, const char *topic, const char **errmsg);
 
 /** Called when a local user wants to change the channel topic (function prototype for HOOKTYPE_PRE_LOCAL_TOPIC).
  * @param client		The client
@@ -1436,7 +1440,7 @@ int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, int prefix
  * @param topic			The new requested topic
  * @return The new topic (you may also return 'topic'), or NULL if the topic change request should be rejected.
  */
-char *hooktype_pre_local_topic(Client *client, Channel *channel, char *topic);
+const char *hooktype_pre_local_topic(Client *client, Channel *channel, const char *topic);
 
 /** Called when the channel topic is changed (function prototype for HOOKTYPE_TOPIC).
  * @param client		The client
@@ -1445,7 +1449,7 @@ char *hooktype_pre_local_topic(Client *client, Channel *channel, char *topic);
  * @param topic			The new topic
  * @return The return value is ignored (use return 0)
  */
-int hooktype_topic(Client *client, Channel *channel, MessageTag *mtags, char *topic);
+int hooktype_topic(Client *client, Channel *channel, MessageTag *mtags, const char *topic);
 
 /** Called when a local user changes channel modes, called early (function prototype for HOOKTYPE_PRE_LOCAL_CHANMODE).
  * WARNING: This does not allow you to stop or reject the channel modes. It only allows you to do stuff -before- the
@@ -1459,7 +1463,7 @@ int hooktype_topic(Client *client, Channel *channel, MessageTag *mtags, char *to
  * @param samode		Is this an SAMODE?
  * @return The return value is ignored (use return 0)
  */
-int hooktype_pre_local_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+int hooktype_pre_local_chanmode(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode);
 
 /** Called when a remote user changes channel modes, called early (function prototype for HOOKTYPE_PRE_REMOTE_CHANMODE).
  * WARNING: This does not allow you to stop or reject the channel modes. It only allows you to do stuff -before- the
@@ -1473,7 +1477,7 @@ int hooktype_pre_local_chanmode(Client *client, Channel *channel, MessageTag *mt
  * @param samode		Is this an SAMODE?
  * @return The return value is ignored (use return 0)
  */
-int hooktype_pre_remote_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+int hooktype_pre_remote_chanmode(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode);
 
 /** Called when a local user changes channel modes (function prototype for HOOKTYPE_LOCAL_CHANMODE).
  * @param client		The client
@@ -1483,9 +1487,10 @@ int hooktype_pre_remote_chanmode(Client *client, Channel *channel, MessageTag *m
  * @param parabuf		The parameter buffer, for example "NiceOp"
  * @param sendts		Send timestamp
  * @param samode		Is this an SAMODE?
+ * @param destroy_channel	Module can set this to 1 to indicate 'channel' was destroyed
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+int hooktype_local_chanmode(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel);
 
 /** Called when a remote user changes channel modes (function prototype for HOOKTYPE_REMOTE_CHANMODE).
  * @param client		The client
@@ -1495,9 +1500,10 @@ int hooktype_local_chanmode(Client *client, Channel *channel, MessageTag *mtags,
  * @param parabuf		The parameter buffer, for example "NiceOp"
  * @param sendts		Send timestamp
  * @param samode		Is this an SAMODE?
+ * @param destroy_channel	Module can set this to 1 to indicate 'channel' was destroyed
  * @return The return value is ignored (use return 0)
  */
-int hooktype_remote_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+int hooktype_remote_chanmode(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel);
 
 /** Called when a channel mode is removed by a local or remote user (function prototype for HOOKTYPE_MODECHAR_DEL).
  * NOTE: This is currently not terribly useful for most modules. It is used by by the floodprot and noknock modules.
@@ -1519,9 +1525,10 @@ int hooktype_modechar_add(Channel *channel, int modechar);
  * @param client		The client
  * @param mtags         	Message tags associated with the event
  * @param reason		The away reason, or NULL if away is unset.
+ * @param already_as_away	Set to 1 if the user only changed their away reason.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_away(Client *client, MessageTag *mtags, char *reason);
+int hooktype_away(Client *client, MessageTag *mtags, const char *reason, int already_as_away);
 
 /** Called when a user wants to invite another user to a channel (function prototype for HOOKTYPE_PRE_INVITE).
  * @param client		The client
@@ -1544,14 +1551,14 @@ int hooktype_pre_invite(Client *client, Client *acptr, Channel *channel, int *ov
 int hooktype_invite(Client *client, Client *acptr, Channel *channel, MessageTag *mtags);
 
 /** Called when a user wants to knock on a channel (function prototype for HOOKTYPE_PRE_KNOCK).
- * FIXME: where is the knock reason ?
  * @param client		The client
  * @param channel		The channel to knock on
+ * @param reason		Knock reason (can be replaced if needed)
  * @retval HOOK_DENY		Deny the knock.
  * @retval HOOK_ALLOW		Allow the knock (stop processing other modules)
  * @retval HOOK_CONTINUE	Allow the knock, unless another module blocks it.
  */
-int hooktype_pre_knock(Client *client, Channel *channel);
+int hooktype_pre_knock(Client *client, Channel *channel, const char **reason);
 
 /** Called when a user knocks on a channel (function prototype for HOOKTYPE_KNOCK).
  * @param client		The client
@@ -1560,14 +1567,16 @@ int hooktype_pre_knock(Client *client, Channel *channel);
  * @param comment		The knock reason
  * @return The return value is ignored (use return 0)
  */
-int hooktype_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment);
+int hooktype_knock(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
 
 /** Called when a user whoises someone (function prototype for HOOKTYPE_WHOIS).
  * @param client		The client issuing the command
  * @param target		The user who is the target of the /WHOIS.
+ * @param list			The name/value/prio list that you can add information to
+ *				that will be sent to the user as the WHOIS response.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_whois(Client *client, Client *target);
+int hooktype_whois(Client *client, Client *target, NameValuePrioList **list);
 
 /** Called to add letters to the WHO status column (function prototype for HOOKTYPE_WHO_STATUS).
  * If a user does a /WHO request, then WHO will show a number of status flags
@@ -1580,7 +1589,7 @@ int hooktype_whois(Client *client, Client *target);
  * @param cansee		If 'client' can see 'target' (eg: in same channel or -i)
  * @return Return 0 if no WHO status flags need to be added, otherwise return the ascii character (eg: return 'B').
  */
-int hooktype_who_status(Client *client, Client *target, Channel *channel, Member *member, char *status, int cansee);
+int hooktype_who_status(Client *client, Client *target, Channel *channel, Member *member, const char *status, int cansee);
 
 /** Called when an IRCOp wants to kill another user (function prototype for HOOKTYPE_PRE_KILL).
  * @param client		The client
@@ -1590,7 +1599,7 @@ int hooktype_who_status(Client *client, Client *target, Channel *channel, Member
  * @retval EX_ALWAYS_DENY	Deny the KICK always (even if IRCOp).
  * @retval EX_ALLOW		Allow the kick, unless another module blocks it.
  */
-int hooktype_pre_kill(Client *client, Client *victim, char *reason);
+int hooktype_pre_kill(Client *client, Client *victim, const char *reason);
 
 /** Called when a local user kills another user (function prototype for HOOKTYPE_LOCAL_KILL).
  * Note that kills from remote IRCOps will show up as regular quits, so use hooktype_remote_quit() and hooktype_local_quit().
@@ -1599,15 +1608,14 @@ int hooktype_pre_kill(Client *client, Client *victim, char *reason);
  * @param comment		The kill reason
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_kill(Client *client, Client *victim, char *comment);
+int hooktype_local_kill(Client *client, Client *victim, const char *comment);
 
-/** Called when an IRCOp /REHASH'es, and passes the parameters (function prototype for HOOKTYPE_REHASHFLAG).
- * FIXME: shouldn't this be merged with hooktype_rehash() ?
+/** Called when an IRCOp calls /REHASH with a -parameter (function prototype for HOOKTYPE_REHASHFLAG).
  * @param client		The client issuing the command, or NULL if rehashing due to system signal.
  * @param str			The rehash flag (eg: "-all")
  * @return The return value is ignored (use return 0)
  */
-int hooktype_rehashflag(Client *client, char *str);
+int hooktype_rehashflag(Client *client, const char *str);
 
 /** Called when the server is rehashing (function prototype for HOOKTYPE_REHASH).
  * @return The return value is ignored (use return 0)
@@ -1667,29 +1675,29 @@ int hooktype_configrun_ex(ConfigFile *cfptr, ConfigEntry *ce, int section, void 
  * @param str			The parameter to the STATS command, eg 'something'.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_stats(Client *client, char *str);
+int hooktype_stats(Client *client, const char *str);
 
 /** Called when a user becomes IRCOp or is no longer an IRCOp (function prototype for HOOKTYPE_LOCAL_OPER).
  * @param client		The client
  * @param add			1 if the user becomes IRCOp, 0 if the user is no longer IRCOp
+ * @param oper_block		The name of the oper block used to oper up
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_oper(Client *client, int add);
+int hooktype_local_oper(Client *client, int add, ConfigItem_oper *oper_block);
 
 /** Called when a client sends a PASS command (function prototype for HOOKTYPE_LOCAL_PASS).
  * @param client		The client
  * @param password		The password supplied by the client
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_pass(Client *client, char *password);
+int hooktype_local_pass(Client *client, const char *password);
 
 /** Called when a channel is created (function prototype for HOOKTYPE_CHANNEL_CREATE).
- * @param client		The client
  * @param channel		The channel that just got created
  * @note This function is not used much, use hooktype_local_join() and hooktype_remote_join() instead.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_channel_create(Client *client, Channel *channel);
+int hooktype_channel_create(Channel *channel);
 
 /** Called when a channel is completely destroyed (function prototype for HOOKTYPE_CHANNEL_DESTROY).
  * @param channel		The channel that is about to be destroyed
@@ -1731,13 +1739,16 @@ int hooktype_tkl_add(Client *client, TKL *tkl);
  */
 int hooktype_tkl_del(Client *client, TKL *tkl);
 
-/** Called when something is logged via the ircd_log() function (function prototype for HOOKTYPE_LOG).
- * @param flags			One of LOG_*, such as LOG_ERROR.
- * @param timebuf		The time buffer, such as "[2030-01-01 12:00:00]"
- * @param buf			The text to be logged
+/** Called when something is logged via the unreal_log() function (function prototype for HOOKTYPE_LOG).
+ * @param loglevel		Loglevel (eg ULOG_INFO)
+ * @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 timebuf		The [xxxx] time buffer, for convenience
  * @return The return value is ignored (use return 0)
  */
-int hooktype_log(int flags, char *timebuf, char *buf);
+int hooktype_log(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, 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
@@ -1748,7 +1759,7 @@ int hooktype_log(int flags, char *timebuf, char *buf);
  * @param tkl			The spamfilter TKL entry that matched
  * @return The return value is ignored (use return 0)
  */
-int hooktype_local_spamfilter(Client *client, char *str, char *str_in, int type, char *target, TKL *tkl);
+int hooktype_local_spamfilter(Client *client, const char *str, const char *str_in, int type, const char *target, TKL *tkl);
 
 /** Called when a user sends something to a user that has the sender silenced (function prototype for HOOKTYPE_SILENCED).
  * UnrealIRCd support a SILENCE list. If the target user has added someone on the silence list, eg via SILENCE +BadUser,
@@ -1771,7 +1782,7 @@ int hooktype_silenced(Client *client, Client *target, SendType sendtype);
  * @note If you want to alter the buffer contents then replace 'readbuf' with your own buffer and set 'length' appropriately.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_rawpacket_in(Client *client, char *readbuf, int *length);
+int hooktype_rawpacket_in(Client *client, const char *readbuf, int *length);
 
 /** Called when a packet is received or sent (function prototype for HOOKTYPE_PACKET).
  * @param client		The locally connected sender, this can be &me
@@ -1812,11 +1823,10 @@ int hooktype_free_user(Client *client);
  * @param client		The client
  * @param channel		The channel
  * @param key			The channel key
- * @param parv			The join parameters
  * @note I don't think this works?
  * @return Unclear..
  */
-int hooktype_can_join_limitexceeded(Client *client, Channel *channel, char *key, char *parv[]);
+int hooktype_can_join_limitexceeded(Client *client, Channel *channel, const char *key, char **errmsg);
 
 /** Called to check if the user is visible in the channel (function prototype for HOOKTYPE_VISIBLE_IN_CHANNEL).
  * For example, the delayjoin module (+d/+D) will 'return 0' here if the user is hidden due to delayed join.
@@ -1904,16 +1914,6 @@ int hooktype_channel_synced(Channel *channel, int merge, int removetheirs, int n
  */
 int hooktype_can_sajoin(Client *target, Channel *channel, Client *client);
 
-/** Called when the hostname is initialized for a client (function prototype for HOOKTYPE_CHECK_INIT).
- * This is a very specific call, it is only meant for the WEBIRC module.
- * @param client		The client
- * @param sockname		The socket name
- * @param size			The size of the socket name? :D
- * @retval HOOK_CONTINUE	Proceed normally
- * @retval HOOK_DENY		Reject the connection(?)
- */
-int hooktype_check_init(Client *client, char *sockname, size_t size);
-
 /** May the target user be deoped? (function prototype for HOOKTYPE_MODE_DEOP).
  * This is for example used by the +S (Services bot) user mode to block deop requests to services bots.
  * @param client		The client issuing the command
@@ -1921,13 +1921,14 @@ int hooktype_check_init(Client *client, char *sockname, size_t size);
  * @param channel		The channel
  * @param what			Always MODE_DEL at the moment
  * @param modechar		The mode character: q/a/o/h/v
- * @param my_access		Cached result of get_access(), so one of CHFL_*, for example CHFL_CHANOP.
- * @param badmode		The error string that should be sent to the client
+ * @param client_access		Channel member modes of 'client', eg "o", never NULL but can be empty.
+ * @param target_access		Channel member modes of 'client', eg "h", never NULL but can be empty.
+ * @param reject_reason		The error string that should be sent to the client
  * @retval HOOK_CONTINUE	Proceed normally (allow it)
  * @retval HOOK_DENY		Reject the mode change
  * @retval HOOK_ALWAYS_DENY	Reject the mode change, even if IRCOp/Services/..
  */
-int hooktype_mode_deop(Client *client, Client *victim, Channel *channel, u_int what, int modechar, long my_access, char **badmode);
+int hooktype_mode_deop(Client *client, Client *victim, Channel *channel, u_int what, int modechar, const char *client_access, const char *target_access, const char **reject_reason);
 
 /** Called when a DCC request was denied by the IRCd (function prototype for HOOKTYPE_DCC_DENIED).
  * @param client		The client who tried to send a file
@@ -1937,7 +1938,7 @@ int hooktype_mode_deop(Client *client, Client *victim, Channel *channel, u_int w
  * @param denydcc		The deny dcc { ] rule that triggered.
  * @return The return value is ignored (use return 0)
  */
-int hooktype_dcc_denied(Client *client, char *target, char *realfile, char *displayfile, ConfigItem_deny_dcc *denydcc);
+int hooktype_dcc_denied(Client *client, const char *target, const char *realfile, const char *displayfile, ConfigItem_deny_dcc *denydcc);
 
 /** Called in the user accept procedure, when setting the +z user mode (function prototype for HOOKTYPE_SECURE_CONNECT).
  * This is only meant to be used by the WEBIRC module, so it can do -z for fake secure users.
@@ -1956,13 +1957,6 @@ int hooktype_secure_connect(Client *client);
  */
 int hooktype_can_bypass_channel_message_restriction(Client *client, Channel *channel, BypassChannelMessageRestrictionType bypass_type);
 
-/** Called when xxxx (function prototype for HOOKTYPE_REQUIRE_SASL).
- * FIXME: this hook is never called!?
- * @param client		The client
- * @return The return value is ignored (use return 0)
- */
-int hooktype_require_sasl(Client *client, char *reason);
-
 /** Called when a SASL continuation response is received (function prototype for HOOKTYPE_SASL_CONTINUATION).
  * This is only used by the authprompt module, it unlikely that you need it.
  * @param client		The client for which the SASL authentication is taking place
@@ -1970,7 +1964,7 @@ int hooktype_require_sasl(Client *client, char *reason);
  * @retval HOOK_CONTINUE	Continue as normal
  * @retval HOOK_DENY		Do not handle the SASL request, or at least don't show the response to the client.
  */
-int hooktype_sasl_continuation(Client *client, char *buf);
+int hooktype_sasl_continuation(Client *client, const char *buf);
 
 /** Called when a SASL result response is received (function prototype for HOOKTYPE_SASL_RESULT).
  * This is only used by the authprompt module.
@@ -1990,7 +1984,7 @@ int hooktype_sasl_result(Client *client, int success);
  * @param duration		The duration of the ban, 0 for permanent ban
  * @return The magic value 99 is used to exempt the user (=do not ban!), otherwise the ban is added.
  */
-int hooktype_place_host_ban(Client *client, int action, char *reason, long duration);
+int hooktype_place_host_ban(Client *client, int action, const char *reason, long duration);
 
 /** Called when a TKL ban is hit by this user (function prototype for HOOKTYPE_FIND_TKLINE_MATCH).
  * This is called when an existing TKL entry is hit by the user.
@@ -2020,7 +2014,7 @@ int hooktype_welcome(Client *client, int after_numeric);
  * @param buf			The buffer (without message tags)
  * @return The return value is ignored (use return 0)
  */
-int hooktype_pre_command(Client *from, MessageTag *mtags, char *buf);
+int hooktype_pre_command(Client *from, MessageTag *mtags, const char *buf);
 
 /** Called right after finishing a client command (function prototype for HOOKTYPE_POST_COMMAND).
  * This is only used by labeled-reponse. If you think this hook is useful then you
@@ -2030,7 +2024,7 @@ int hooktype_pre_command(Client *from, MessageTag *mtags, char *buf);
  * @param buf			The buffer (without message tags)
  * @return The return value is ignored (use return 0)
  */
-int hooktype_post_command(Client *from, MessageTag *mtags, char *buf);
+int hooktype_post_command(Client *from, MessageTag *mtags, const char *buf);
 
 /** Called when new_message() is executed (function prototype for HOOKTYPE_NEW_MESSAGE).
  * When a new message with message tags is prepared, code in UnrealIRCd
@@ -2044,7 +2038,7 @@ int hooktype_post_command(Client *from, MessageTag *mtags, char *buf);
  * @param signature		Special signature when used through new_message_special()
  * @return The return value is ignored (use return 0)
  */
-void hooktype_new_message(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+void hooktype_new_message(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 /** Is the client handshake finished? (function prototype for HOOKTYPE_IS_HANDSHAKE_FINISHED).
  * This is called by the is_handshake_finished() function to check if the user
@@ -2067,7 +2061,7 @@ int hooktype_is_handshake_finished(Client *client);
  * @param comment		The quit message
  * @return The original quit message (comment), the new quit message (pointing to your own static buffer), or NULL (no quit message)
  */
-char *hooktype_pre_local_quit_chan(Client *client, Channel *channel, char *comment);
+const char *hooktype_pre_local_quit_chan(Client *client, Channel *channel, const char *comment);
 
 /** Called when an ident lookup should be made (function prototype for HOOKTYPE_IDENT_LOOKUP).
  * This is used by the ident_lookup module.
@@ -2077,7 +2071,7 @@ char *hooktype_pre_local_quit_chan(Client *client, Channel *channel, char *comme
 int hooktype_ident_lookup(Client *client);
 
 /** Called when someone logs in/out a services account (function prototype for HOOKTYPE_ACCOUNT_LOGIN).
- * The account name can be found in client->user->svid. It will be the string "0" if the user is logged out.
+ * The account name can be found in client->user->account. It will be the string "0" if the user is logged out.
  * @param client		The client
  * @param mtags         	Message tags associated with the event
  * @return The return value is ignored (use return 0)
@@ -2102,6 +2096,45 @@ int hooktype_close_connection(Client *client);
  */
 int hooktype_connect_extinfo(Client *client, NameValuePrioList **list);
 
+/** Called when a user wants to join a channel that require invitation.
+ * Use hook priorities to enforce a specific policy, especially denying the invitation.
+ * @param client		The client
+ * @param channel		The channel client is willing to join
+ * @param invited		Set to 0 for user who should not be invited, set to 1 if the user is invited.
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_is_invited(Client *client, Channel *channel, int *invited);
+
+/** Called after a local user has changed the nick name (function prototype for HOOKTYPE_POST_LOCAL_NICKCHANGE).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the event
+ * @param oldnick		The nick name before the nick change
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_post_local_nickchange(Client *client, MessageTag *mtags, const char *oldnick);
+
+/** Called after a remote user has changed the nick name (function prototype for HOOKTYPE_POST_REMOTE_NICKCHANGE).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the event
+ * @param oldnick		The nick name before the nick change
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_post_remote_nickchange(Client *client, MessageTag *mtags, const char *oldnick);
+
+/** Called when user name or user host has changed.
+ * @param client		The client whose user@host has changed
+ * @param olduser		Old username of the client
+ * @param oldhost		Old hostname of the client
+ * @return The return value is ignored (use return 0)
+ */
+ 
+int hooktype_realname_changed(Client *client, const char *oldinfo);
+/** Called when user realname has changed.
+ * @param client		The client whose realname has changed
+ * @param oldinfo		Old realname of the client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_userhost_changed(Client *client, const char *olduser, const char *oldhost);
 /** @} */
 
 #ifdef GCC_TYPECHECKING
@@ -2138,6 +2171,7 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_REMOTE_QUIT) && !ValidateHook(hooktype_remote_quit, func)) || \
         ((hooktype == HOOKTYPE_PRE_LOCAL_JOIN) && !ValidateHook(hooktype_pre_local_join, func)) || \
         ((hooktype == HOOKTYPE_PRE_LOCAL_KICK) && !ValidateHook(hooktype_pre_local_kick, func)) || \
+        ((hooktype == HOOKTYPE_CAN_SET_TOPIC) && !ValidateHook(hooktype_can_set_topic, func)) || \
         ((hooktype == HOOKTYPE_PRE_LOCAL_TOPIC) && !ValidateHook(hooktype_pre_local_topic, func)) || \
         ((hooktype == HOOKTYPE_REMOTE_NICKCHANGE) && !ValidateHook(hooktype_remote_nickchange, func)) || \
         ((hooktype == HOOKTYPE_CHANNEL_CREATE) && !ValidateHook(hooktype_channel_create, func)) || \
@@ -2186,7 +2220,6 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_CHANNEL_SYNCED) && !ValidateHook(hooktype_channel_synced, func)) || \
         ((hooktype == HOOKTYPE_CAN_SAJOIN) && !ValidateHook(hooktype_can_sajoin, func)) || \
         ((hooktype == HOOKTYPE_WHOIS) && !ValidateHook(hooktype_whois, func)) || \
-        ((hooktype == HOOKTYPE_CHECK_INIT) && !ValidateHook(hooktype_check_init, func)) || \
         ((hooktype == HOOKTYPE_WHO_STATUS) && !ValidateHook(hooktype_who_status, func)) || \
         ((hooktype == HOOKTYPE_MODE_DEOP) && !ValidateHook(hooktype_mode_deop, func)) || \
         ((hooktype == HOOKTYPE_PRE_KILL) && !ValidateHook(hooktype_pre_kill, func)) || \
@@ -2196,7 +2229,6 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_SERVER_SYNCED) && !ValidateHook(hooktype_server_synced, func)) || \
         ((hooktype == HOOKTYPE_SECURE_CONNECT) && !ValidateHook(hooktype_secure_connect, func)) || \
         ((hooktype == HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION) && !ValidateHook(hooktype_can_bypass_channel_message_restriction, func)) || \
-        ((hooktype == HOOKTYPE_REQUIRE_SASL) && !ValidateHook(hooktype_require_sasl, func)) || \
         ((hooktype == HOOKTYPE_SASL_CONTINUATION) && !ValidateHook(hooktype_sasl_continuation, func)) || \
         ((hooktype == HOOKTYPE_SASL_RESULT) && !ValidateHook(hooktype_sasl_result, func)) || \
         ((hooktype == HOOKTYPE_PLACE_HOST_BAN) && !ValidateHook(hooktype_place_host_ban, func)) || \
@@ -2211,7 +2243,12 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_CONFIGRUN_EX) && !ValidateHook(hooktype_configrun_ex, func)) || \
         ((hooktype == HOOKTYPE_ACCOUNT_LOGIN) && !ValidateHook(hooktype_account_login, func)) || \
         ((hooktype == HOOKTYPE_CLOSE_CONNECTION) && !ValidateHook(hooktype_close_connection, func)) || \
-        ((hooktype == HOOKTYPE_CONNECT_EXTINFO) && !ValidateHook(hooktype_connect_extinfo, func)) ) \
+        ((hooktype == HOOKTYPE_CONNECT_EXTINFO) && !ValidateHook(hooktype_connect_extinfo, func)) || \
+        ((hooktype == HOOKTYPE_IS_INVITED) && !ValidateHook(hooktype_is_invited, func)) || \
+        ((hooktype == HOOKTYPE_POST_LOCAL_NICKCHANGE) && !ValidateHook(hooktype_post_local_nickchange, func)) || \
+        ((hooktype == HOOKTYPE_POST_REMOTE_NICKCHANGE) && !ValidateHook(hooktype_post_remote_nickchange, func)) || \
+        ((hooktype == HOOKTYPE_USERHOST_CHANGED) && !ValidateHook(hooktype_userhost_changed, func)) || \
+        ((hooktype == HOOKTYPE_REALNAME_CHANGED) && !ValidateHook(hooktype_realname_changed, func)) )\
         _hook_error_incompatible();
 #endif /* GCC_TYPECHECKING */
 
@@ -2222,10 +2259,11 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
 
 /* Callback types */
 #define CALLBACKTYPE_CLOAK 1
-#define CALLBACKTYPE_CLOAKKEYCSUM 2
+#define CALLBACKTYPE_CLOAK_KEY_CHECKSUM 2
 #define CALLBACKTYPE_CLOAK_EX 3
 #define CALLBACKTYPE_BLACKLIST_CHECK 4
 #define CALLBACKTYPE_REPUTATION_STARTTIME 5
+#define CALLBACKTYPE_GEOIP_LOOKUP 6
 
 /* To add a new efunction, only if you are an UnrealIRCd coder:
  * 1) Add a new entry here
@@ -2241,6 +2279,7 @@ enum EfunctionType {
 	EFUNC_CAN_JOIN,
 	EFUNC_DO_MODE,
 	EFUNC_SET_MODE,
+	EFUNC_SET_CHANNEL_MODE,
 	EFUNC_CMD_UMODE,
 	EFUNC_REGISTER_USER,
 	EFUNC_TKL_HASH,
@@ -2281,6 +2320,8 @@ enum EfunctionType {
 	EFUNC_BROADCAST_MD_CHANNEL_CMD,
 	EFUNC_BROADCAST_MD_MEMBER_CMD,
 	EFUNC_BROADCAST_MD_MEMBERSHIP_CMD,
+	EFUNC_MODDATA_ADD_S2S_MTAGS,
+	EFUNC_MODDATA_EXTRACT_S2S_MTAGS,
 	EFUNC_SEND_MODDATA_CLIENT,
 	EFUNC_SEND_MODDATA_CHANNEL,
 	EFUNC_SEND_MODDATA_MEMBERS,
@@ -2293,10 +2334,12 @@ enum EfunctionType {
 	EFUNC_DO_REMOTE_NICK_NAME,
 	EFUNC_CHARSYS_GET_CURRENT_LANGUAGES,
 	EFUNC_BROADCAST_SINFO,
+	EFUNC_CONNECT_SERVER,
 	EFUNC_PARSE_MESSAGE_TAGS,
 	EFUNC_MTAGS_TO_STRING,
 	EFUNC_TKL_CHARTOTYPE,
 	EFUNC_TKL_TYPE_STRING,
+	EFUNC_TKL_TYPE_CONFIG_STRING,
 	EFUNC_CAN_SEND_TO_CHANNEL,
 	EFUNC_CAN_SEND_TO_USER,
 	EFUNC_BROADCAST_MD_GLOBALVAR,
@@ -2321,6 +2364,15 @@ enum EfunctionType {
 	EFUNC_LABELED_RESPONSE_SET_CONTEXT,
 	EFUNC_LABELED_RESPONSE_FORCE_END,
 	EFUNC_KICK_USER,
+	EFUNC_WATCH_ADD,
+	EFUNC_WATCH_DEL,
+	EFUNC_WATCH_DEL_LIST,
+	EFUNC_WATCH_GET,
+	EFUNC_WATCH_CHECK,
+	EFUNC_TKL_UHOST,
+	EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER,
+	EFUNC_GET_CHMODES_FOR_USER,
+	EFUNC_WHOIS_GET_POLICY,
 };
 
 /* Module flags */
@@ -2354,7 +2406,7 @@ enum EfunctionType {
 #define MOD_LOAD() DLLFUNC int Mod_Load(ModuleInfo *modinfo)
 #define MOD_UNLOAD() DLLFUNC int Mod_Unload(ModuleInfo *modinfo)
 
-#define CLOAK_KEYCRC	RCallbacks[CALLBACKTYPE_CLOAKKEYCSUM] != NULL ? RCallbacks[CALLBACKTYPE_CLOAKKEYCSUM]->func.pcharfunc() : "nil"
+#define CLOAK_KEY_CHECKSUM	RCallbacks[CALLBACKTYPE_CLOAK_KEY_CHECKSUM] != NULL ? RCallbacks[CALLBACKTYPE_CLOAK_KEY_CHECKSUM]->func.stringfunc() : "nil"
 
 #ifdef DYNAMIC_LINKING
  #include "modversion.h"
diff --git a/include/msg.h b/include/msg.h
@@ -51,7 +51,6 @@
 #define MSG_PONG	"PONG"	/* PONG */
 #define MSG_OPER	"OPER"	/* OPER */
 #define MSG_PASS	"PASS"	/* PASS */
-#define MSG_WALLOPS	"WALLOPS"	/* WALL */
 #define MSG_TIME	"TIME"	/* TIME */
 #define MSG_NAMES	"NAMES"	/* NAME */
 #define MSG_ADMIN	"ADMIN"	/* ADMI */
diff --git a/include/numeric.h b/include/numeric.h
@@ -36,7 +36,6 @@
 #define RPL_ISUPPORT	     005
 
 #define RPL_REDIR	     10
-#define RPL_YOURID           42
 
 #define RPL_REMOTEISUPPORT 105
 
@@ -51,29 +50,23 @@
 #define ERR_TOOMANYCHANNELS  405
 #define ERR_WASNOSUCHNICK    406
 #define ERR_TOOMANYTARGETS   407
-#define ERR_NOSUCHSERVICE    408
 #define	ERR_NOORIGIN         409
 
 #define ERR_INVALIDCAPCMD    410
 
 #define ERR_NORECIPIENT      411
 #define ERR_NOTEXTTOSEND     412
-#define ERR_NOTOPLEVEL       413
-#define ERR_WILDTOPLEVEL     414
 #define ERR_TOOMANYMATCHES   416
 
 #define ERR_UNKNOWNCOMMAND   421
 #define	ERR_NOMOTD           422
 #define	ERR_NOADMININFO      423
-#define	ERR_FILEERROR        424
 #define ERR_NOOPERMOTD	     425
 #define ERR_TOOMANYAWAY	     429
 #define ERR_NONICKNAMEGIVEN  431
 #define ERR_ERRONEUSNICKNAME 432
 #define ERR_NICKNAMEINUSE    433
 #define ERR_NORULES          434
-#define ERR_SERVICECONFUSED  435
-#define	ERR_NICKCOLLISION    436
 #define ERR_BANNICKCHANGE    437
 #define ERR_NCHANGETOOFAST   438
 #define ERR_TARGETTOOFAST    439
@@ -82,53 +75,36 @@
 #define ERR_USERNOTINCHANNEL 441
 #define ERR_NOTONCHANNEL     442
 #define	ERR_USERONCHANNEL    443
-#define ERR_NOLOGIN          444
-#define	ERR_SUMMONDISABLED   445
-#define ERR_USERSDISABLED    446
 #define ERR_NONICKCHANGE     447
 #define ERR_FORBIDDENCHANNEL 448
 
 
 #define ERR_NOTREGISTERED    451
 
-#define ERR_HOSTILENAME      455
-
-#define ERR_NOHIDING	     459
 #define ERR_NOTFORHALFOPS	 460
 #define ERR_NEEDMOREPARAMS   461
 #define ERR_ALREADYREGISTRED 462
-#define ERR_NOPERMFORHOST    463
 #define ERR_PASSWDMISMATCH   464
 #define ERR_YOUREBANNEDCREEP 465
-#define ERR_YOUWILLBEBANNED  466
-#define	ERR_KEYSET           467
 #define ERR_ONLYSERVERSCANCHANGE 468
-#define ERR_LINKSET	     469
 #define ERR_LINKCHANNEL	     470
 #define ERR_CHANNELISFULL    471
 #define ERR_UNKNOWNMODE      472
 #define ERR_INVITEONLYCHAN   473
 #define ERR_BANNEDFROMCHAN   474
 #define	ERR_BADCHANNELKEY    475
-#define	ERR_BADCHANMASK      476
 #define ERR_NEEDREGGEDNICK   477
 #define ERR_BANLISTFULL      478
-#define ERR_LINKFAIL	     479
 #define ERR_CANNOTKNOCK		 480
 
 #define ERR_NOPRIVILEGES     481
 #define ERR_CHANOPRIVSNEEDED 482
-#define	ERR_CANTKILLSERVER   483
-#define ERR_ATTACKDENY       484
 #define ERR_KILLDENY	     485
 
-#define ERR_NONONREG	     486
 #define ERR_NOTFORUSERS	    487
 
 #define ERR_SECUREONLYCHAN   489
-#define ERR_NOSWEAR	     490
 #define ERR_NOOPERHOST       491
-#define ERR_NOCTCP	     492
 
 #define ERR_CHANOWNPRIVNEEDED 499
 
@@ -138,13 +114,11 @@
 
 #define ERR_SILELISTFULL     511
 #define ERR_TOOMANYWATCH     512
-#define ERR_NEEDPONG         513
 
 #define ERR_TOOMANYDCC       514
 
 #define ERR_DISABLED         517
 #define ERR_NOINVITE		 518
-#define ERR_ADMONLY			 519
 #define ERR_OPERONLY		 520
 #define ERR_LISTSYNTAX       521
 
@@ -163,7 +137,6 @@
 #define RPL_WHOISREGNICK     307
 #define RPL_RULESSTART       308
 #define RPL_ENDOFRULES       309
-#define RPL_WHOISHELPOP      310	/* -Donwulff */
 
 #define RPL_WHOISUSER        311
 #define RPL_WHOISSERVER      312
@@ -173,7 +146,6 @@
 /* rpl_endofwho below (315) */
 #define	RPL_ENDOFWHOWAS      369
 
-#define RPL_WHOISCHANOP      316	/* redundant and not needed but reserved */
 #define RPL_WHOISIDLE        317
 
 #define RPL_ENDOFWHOIS       318
@@ -198,7 +170,7 @@
 #define RPL_WHOISBOT	     335
 #define RPL_USERIP	     340
 #define RPL_INVITING         341
-#define	RPL_SUMMONING        342
+#define RPL_WHOISCOUNTRY     344
 
 #define RPL_VERSION          351
 
@@ -211,7 +183,6 @@
 
 #define RPL_EXLIST	     348
 #define RPL_ENDOFEXLIST      349
-#define RPL_KILLDONE         361
 #define	RPL_CLOSING          362
 #define RPL_CLOSEEND         363
 #define RPL_LINKS            364
@@ -223,7 +194,6 @@
 
 #define	RPL_INFO             371
 #define	RPL_MOTD             372
-#define	RPL_INFOSTART        373
 #define	RPL_ENDOFINFO        374
 #define	RPL_MOTDSTART        375
 #define	RPL_ENDOFMOTD        376
@@ -232,9 +202,6 @@
 #define RPL_WHOISMODES       379
 #define RPL_YOUREOPER        381
 #define RPL_REHASHING        382
-#define RPL_YOURESERVICE     383
-#define RPL_MYPORTIS         384
-#define RPL_NOTOPERANYMORE   385
 #define RPL_QLIST			 386
 #define	RPL_ENDOFQLIST		 387
 #define	RPL_ALIST			 388
@@ -255,7 +222,6 @@
 #define RPL_TRACEOPERATOR    204
 #define RPL_TRACEUSER        205
 #define RPL_TRACESERVER      206
-#define RPL_TRACESERVICE     207
 #define RPL_TRACENEWTYPE     208
 #define RPL_TRACECLASS       209
 
@@ -263,37 +229,28 @@
 #define RPL_STATSLINKINFO    211
 #define RPL_STATSCOMMANDS    212
 #define RPL_STATSCLINE       213
-#define RPL_STATSOLDNLINE    214
 
 #define RPL_STATSILINE       215
-#define RPL_STATSKLINE       216
 #define RPL_STATSQLINE       217
 #define RPL_STATSYLINE       218
 #define RPL_ENDOFSTATS       219
-#define RPL_STATSBLINE	     220
 
 
 #define RPL_UMODEIS          221
-#define RPL_SQLINE_NICK      222
 #define RPL_STATSGLINE		 223
 #define RPL_STATSTLINE		 224
-#define RPL_STATSELINE	     225
 #define RPL_STATSNLINE	     226
 #define RPL_STATSVLINE	     227
 #define RPL_STATSBANVER	     228
 #define RPL_STATSSPAMF       229
 #define RPL_STATSEXCEPTTKL   230
-#define RPL_SERVICEINFO      231
 #define RPL_RULES            232
 #define	RPL_SERVICE          233
-#define RPL_SERVLIST         234
-#define RPL_SERVLISTEND      235
 
 #define	RPL_STATSLLINE       241
 #define	RPL_STATSUPTIME      242
 #define	RPL_STATSOLINE       243
 #define	RPL_STATSHLINE       244
-#define	RPL_STATSSLINE       245
 #define RPL_STATSXLINE	     247
 #define RPL_STATSULINE       248
 #define	RPL_STATSDEBUG	     249
@@ -319,14 +276,6 @@
 #define RPL_STATSDLINE       275
 #define RPL_WHOISCERTFP      276
 
-#define RPL_HELPHDR	     290
-#define RPL_HELPOP	     291
-#define RPL_HELPTLR	     292
-#define RPL_HELPHLP	     293
-#define RPL_HELPFWD	     294
-#define RPL_HELPIGN	     295
-
-
 /*
  * New /MAP format.
  */
@@ -335,7 +284,6 @@
 #define RPL_MAPEND           007
 
 
-#define ERR_WHOSYNTAX 522
 #define ERR_WHOLIMEXCEED 523
 #define ERR_OPERSPVERIFY 524
 
@@ -357,7 +305,6 @@
 #define RPL_NOWOFF           605
 #define RPL_WATCHLIST        606
 #define RPL_ENDOFWATCHLIST   607
-#define RPL_CLEARWATCH       608
 #define RPL_NOWISAWAY        609
 
 #define RPL_DCCSTATUS        617
@@ -365,16 +312,18 @@
 #define RPL_ENDOFDCCLIST     619
 #define RPL_DCCINFO          620
 
-#define RPL_DUMPING			 640
-#define RPL_DUMPRPL			 641
-#define RPL_EODUMP           642
-
 #define RPL_SPAMCMDFWD       659
 
 #define RPL_STARTTLS         670
 
 #define RPL_WHOISSECURE      671
 
+#define RPL_MONONLINE		730
+#define RPL_MONOFFLINE		731
+#define RPL_MONLIST			732
+#define RPL_ENDOFMONLIST	733
+#define ERR_MONLISTFULL		734
+
 #define ERR_MLOCKRESTRICTED	742
 
 #define ERR_CANNOTDOCOMMAND 972
@@ -382,15 +331,242 @@
 
 #define ERR_STARTTLS            691
 
+#define ERR_INVALIDMODEPARAM	696
+
 #define RPL_LOGGEDIN            900
 #define RPL_LOGGEDOUT           901
-#define ERR_NICKLOCKED          902
 
 #define RPL_SASLSUCCESS         903
 #define ERR_SASLFAIL            904
 #define ERR_SASLTOOLONG         905
 #define ERR_SASLABORTED         906
-#define ERR_SASLALREADY         907
 #define RPL_SASLMECHS           908
 
-#define ERR_NUMERICERR       999
+/* Numeric texts */
+
+#define STR_RPL_WELCOME			/* 001 */	":Welcome to the %s IRC Network %s!%s@%s"
+#define STR_RPL_YOURHOST		/* 002 */	":Your host is %s, running version %s"
+#define STR_RPL_CREATED			/* 003 */	":This server was created %s"
+#define STR_RPL_MYINFO			/* 004 */	"%s %s %s %s"
+#define STR_RPL_ISUPPORT		/* 005 */	"%s :are supported by this server"
+#define STR_RPL_MAP			/* 006 */	":%s%-*s(%ld) %s"
+#define STR_RPL_MAPEND			/* 007 */	":End of /MAP"
+#define STR_RPL_SNOMASK			/* 008 */	"+%s :Server notice mask"
+#define STR_RPL_REDIR			/* 010 */	"%s %d :Please use this Server/Port instead"
+#define STR_RPL_REMOTEISUPPORT		/* 105 */	"%s :are supported by this server"
+#define STR_RPL_TRACELINK		/* 200 */	"Link %s%s %s %s"
+#define STR_RPL_TRACECONNECTING		/* 201 */	"Attempt %s %s"
+#define STR_RPL_TRACEHANDSHAKE		/* 202 */	"Handshaking %s %s"
+#define STR_RPL_TRACEUNKNOWN		/* 203 */	"???? %s %s"
+#define STR_RPL_TRACEOPERATOR		/* 204 */	"Operator %s %s [%s] %lld"
+#define STR_RPL_TRACEUSER		/* 205 */	"User %s %s [%s] %lld"
+#define STR_RPL_TRACESERVER		/* 206 */	"Server %s %dS %dC %s %s!%s@%s %lld"
+#define STR_RPL_TRACENEWTYPE		/* 208 */	"%s 0 %s"
+#define STR_RPL_TRACECLASS		/* 209 */	"Class %s %d"
+#define STR_RPL_STATSHELP		/* 210 */	":%s"
+#define STR_RPL_STATSCOMMANDS		/* 212 */	"%s %u %lu"
+#define STR_RPL_STATSCLINE		/* 213 */	"%c %s * %s %d %d %s"
+#define STR_RPL_STATSILINE		/* 215 */	"I %s %s %d %d %s %s %d"
+#define STR_RPL_STATSQLINE		/* 217 */	"%c %s %lld %lld %s :%s"
+#define STR_RPL_STATSYLINE		/* 218 */	"Y %s %d %d %d %d %d"
+#define STR_RPL_ENDOFSTATS		/* 219 */	"%c :End of /STATS report"
+#define STR_RPL_UMODEIS			/* 221 */	"%s"
+#define STR_RPL_STATSGLINE		/* 223 */	"%c %s %lld %lld %s :%s"
+#define STR_RPL_STATSTLINE		/* 224 */	"T %s %s %s"
+#define STR_RPL_STATSNLINE		/* 226 */	"n %s %s"
+#define STR_RPL_STATSVLINE		/* 227 */	"v %s %s %s"
+#define STR_RPL_STATSBANVER		/* 228 */	"%s %s"
+#define STR_RPL_STATSSPAMF		/* 229 */	"%c %s %s %s %lld %lld %lld %s %s :%s"
+#define STR_RPL_STATSEXCEPTTKL		/* 230 */	"%s %s %lld %lld %s :%s"
+#define STR_RPL_RULES			/* 232 */	":- %s"
+#define STR_RPL_STATSLLINE		/* 241 */	"%c %s * %s %d %d"
+#define STR_RPL_STATSUPTIME		/* 242 */	":Server Up %lld days, %lld:%02lld:%02lld"
+#define STR_RPL_STATSOLINE		/* 243 */	"%c %s * %s %s %s"
+#define STR_RPL_STATSHLINE		/* 244 */	"%c %s * %s %d %d"
+#define STR_RPL_STATSXLINE		/* 247 */	"X %s %d"
+#define STR_RPL_STATSULINE		/* 248 */	"U %s"
+#define STR_RPL_STATSDEBUG		/* 249 */	":%s"
+#define STR_RPL_STATSCONN		/* 250 */	":Highest connection count: %d (%d clients)"
+#define STR_RPL_LUSERCLIENT		/* 251 */	":There are %d users and %d invisible on %d servers"
+#define STR_RPL_LUSEROP			/* 252 */	"%d :operator(s) online"
+#define STR_RPL_LUSERUNKNOWN		/* 253 */	"%d :unknown connection(s)"
+#define STR_RPL_LUSERCHANNELS		/* 254 */	"%d :channels formed"
+#define STR_RPL_LUSERME			/* 255 */	":I have %d clients and %d servers"
+#define STR_RPL_ADMINME			/* 256 */	":Administrative info about %s"
+#define STR_RPL_ADMINLOC1		/* 257 */	":%s"
+#define STR_RPL_ADMINLOC2		/* 258 */	":%s"
+#define STR_RPL_ADMINEMAIL		/* 259 */	":%s"
+#define STR_RPL_TRACELOG		/* 261 */	"File %s %d"
+#define STR_RPL_TRYAGAIN		/* 263 */	"%s :Flooding detected. Please wait a while and try again."
+#define STR_RPL_LOCALUSERS		/* 265 */	"%d %d :Current local users %d, max %d"
+#define STR_RPL_GLOBALUSERS		/* 266 */	"%d %d :Current global users %d, max %d"
+#define STR_RPL_SILELIST		/* 271 */	"%s"
+#define STR_RPL_ENDOFSILELIST		/* 272 */	":End of Silence List"
+#define STR_RPL_STATSDLINE		/* 275 */	"%c %s %s"
+#define STR_RPL_WHOISCERTFP		/* 276 */	"%s :has client certificate fingerprint %s"
+#define STR_RPL_AWAY			/* 301 */	"%s :%s"
+#define STR_RPL_USERHOST		/* 302 */	":%s %s %s %s %s"
+#define STR_RPL_ISON			/* 303 */	":"
+#define STR_RPL_UNAWAY			/* 305 */	":You are no longer marked as being away"
+#define STR_RPL_NOWAWAY			/* 306 */	":You have been marked as being away"
+#define STR_RPL_WHOISREGNICK		/* 307 */	"%s :is identified for this nick"
+#define STR_RPL_RULESSTART		/* 308 */	":- %s Server Rules - "
+#define STR_RPL_ENDOFRULES		/* 309 */	":End of RULES command."
+#define STR_RPL_WHOISUSER		/* 311 */	"%s %s %s * :%s"
+#define STR_RPL_WHOISSERVER		/* 312 */	"%s %s :%s"
+#define STR_RPL_WHOISOPERATOR		/* 313 */	"%s :is %s"
+#define STR_RPL_WHOWASUSER		/* 314 */	"%s %s %s * :%s"
+#define STR_RPL_ENDOFWHO		/* 315 */	"%s :End of /WHO list."
+#define STR_RPL_WHOISIDLE		/* 317 */	"%s %lld %lld :seconds idle, signon time"
+#define STR_RPL_ENDOFWHOIS		/* 318 */	"%s :End of /WHOIS list."
+#define STR_RPL_WHOISCHANNELS		/* 319 */	"%s :%s"
+#define STR_RPL_WHOISSPECIAL		/* 320 */	"%s :%s"
+#define STR_RPL_LISTSTART		/* 321 */	"Channel :Users  Name"
+#define STR_RPL_LIST			/* 322 */	"%s %d :%s %s"
+#define STR_RPL_LISTEND			/* 323 */	":End of /LIST"
+#define STR_RPL_CHANNELMODEIS		/* 324 */	"%s %s %s"
+#define STR_RPL_CREATIONTIME		/* 329 */	"%s %lld"
+#define STR_RPL_WHOISLOGGEDIN		/* 330 */	"%s %s :is logged in as"
+#define STR_RPL_NOTOPIC			/* 331 */	"%s :No topic is set."
+#define STR_RPL_TOPIC			/* 332 */	"%s :%s"
+#define STR_RPL_TOPICWHOTIME		/* 333 */	"%s %s %lld"
+#define STR_RPL_LISTSYNTAX		/* 334 */	":%s"
+#define STR_RPL_WHOISBOT		/* 335 */	"%s :is a \2Bot\2 on %s"
+#define STR_RPL_INVITELIST		/* 336 */	":%s"
+#define STR_RPL_ENDOFINVITELIST		/* 337 */	":End of /INVITE list."
+#define STR_RPL_USERIP			/* 340 */	":%s %s %s %s %s"
+#define STR_RPL_INVITING		/* 341 */	"%s %s"
+#define STR_RPL_WHOISCOUNTRY		/* 344 */	"%s %s :is connecting from %s"
+#define STR_RPL_INVEXLIST		/* 346 */	"%s %s %s %lld"
+#define STR_RPL_ENDOFINVEXLIST		/* 347 */	"%s :End of Channel Invite List"
+#define STR_RPL_EXLIST			/* 348 */	"%s %s %s %lld"
+#define STR_RPL_ENDOFEXLIST		/* 349 */	"%s :End of Channel Exception List"
+#define STR_RPL_VERSION			/* 351 */	"%s.%s %s :%s%s%s [%s=%d]"
+#define STR_RPL_WHOREPLY		/* 352 */	"%s %s %s %s %s %s :%d %s"
+#define STR_RPL_NAMREPLY		/* 353 */	"%s"
+#define STR_RPL_CLOSING			/* 362 */	"%s :Closed. Status = %d"
+#define STR_RPL_CLOSEEND		/* 363 */	"%d: Connections Closed"
+#define STR_RPL_LINKS			/* 364 */	"%s %s :%d %s"
+#define STR_RPL_ENDOFLINKS		/* 365 */	"%s :End of /LINKS list."
+#define STR_RPL_ENDOFNAMES		/* 366 */	"%s :End of /NAMES list."
+#define STR_RPL_BANLIST			/* 367 */	"%s %s %s %lld"
+#define STR_RPL_ENDOFBANLIST		/* 368 */	"%s :End of Channel Ban List"
+#define STR_RPL_ENDOFWHOWAS		/* 369 */	"%s :End of WHOWAS"
+#define STR_RPL_INFO			/* 371 */	":%s"
+#define STR_RPL_MOTD			/* 372 */	":- %s"
+#define STR_RPL_ENDOFINFO		/* 374 */	":End of /INFO list."
+#define STR_RPL_MOTDSTART		/* 375 */	":- %s Message of the Day - "
+#define STR_RPL_ENDOFMOTD		/* 376 */	":End of /MOTD command."
+#define STR_RPL_WHOISHOST		/* 378 */	"%s :is connecting from %s@%s %s"
+#define STR_RPL_WHOISMODES		/* 379 */	"%s :is using modes %s %s"
+#define STR_RPL_YOUREOPER		/* 381 */	":You are now an IRC Operator"
+#define STR_RPL_REHASHING		/* 382 */	"%s :Rehashing"
+#define STR_RPL_QLIST			/* 386 */	"%s %s"
+#define STR_RPL_ENDOFQLIST		/* 387 */	"%s :End of Channel Owner List"
+#define STR_RPL_ALIST			/* 388 */	"%s %s"
+#define STR_RPL_ENDOFALIST		/* 389 */	"%s :End of Protected User List"
+#define STR_RPL_TIME			/* 391 */	"%s :%s"
+#define STR_RPL_HOSTHIDDEN		/* 396 */	"%s :is now your displayed host"
+#define STR_ERR_NOSUCHNICK		/* 401 */	"%s :No such nick/channel"
+#define STR_ERR_NOSUCHSERVER		/* 402 */	"%s :No such server"
+#define STR_ERR_NOSUCHCHANNEL		/* 403 */	"%s :No such channel"
+#define STR_ERR_CANNOTSENDTOCHAN	/* 404 */	"%s :%s (%s)"
+#define STR_ERR_TOOMANYCHANNELS		/* 405 */	"%s :You have joined too many channels"
+#define STR_ERR_WASNOSUCHNICK		/* 406 */	"%s :There was no such nickname"
+#define STR_ERR_TOOMANYTARGETS		/* 407 */	"%s :Too many targets. The maximum is %d for %s."
+#define STR_ERR_NOORIGIN		/* 409 */	":No origin specified"
+#define STR_ERR_INVALIDCAPCMD		/* 410 */	"%s :Invalid CAP subcommand"
+#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_UNKNOWNCOMMAND		/* 421 */	"%s :Unknown command"
+#define STR_ERR_NOMOTD			/* 422 */	":MOTD File is missing"
+#define STR_ERR_NOADMININFO		/* 423 */	"%s :No administrative info available"
+#define STR_ERR_NOOPERMOTD		/* 425 */	":OPERMOTD File is missing"
+#define STR_ERR_TOOMANYAWAY		/* 429 */	":Too Many aways - Flood Protection activated"
+#define STR_ERR_NONICKNAMEGIVEN		/* 431 */	":No nickname given"
+#define STR_ERR_ERRONEUSNICKNAME	/* 432 */	"%s :Nickname is unavailable: %s"
+#define STR_ERR_NICKNAMEINUSE		/* 433 */	"%s :Nickname is already in use."
+#define STR_ERR_NORULES			/* 434 */	":RULES File is missing"
+#define STR_ERR_BANNICKCHANGE		/* 437 */	"%s :Cannot change nickname while banned on channel"
+#define STR_ERR_NCHANGETOOFAST		/* 438 */	"%s :Nick change too fast. Please try again later."
+#define STR_ERR_TARGETTOOFAST		/* 439 */	"%s :Message target change too fast. Please wait %lld seconds"
+#define STR_ERR_SERVICESDOWN		/* 440 */	"%s :Services are currently down. Please try again later."
+#define STR_ERR_USERNOTINCHANNEL	/* 441 */	"%s %s :They aren't on that channel"
+#define STR_ERR_NOTONCHANNEL		/* 442 */	"%s :You're not on that channel"
+#define STR_ERR_USERONCHANNEL		/* 443 */	"%s %s :is already on channel"
+#define STR_ERR_NONICKCHANGE		/* 447 */	":Can not change nickname while on %s (+N)"
+#define STR_ERR_FORBIDDENCHANNEL	/* 448 */	"%s :Cannot join channel: %s"
+#define STR_ERR_NOTREGISTERED		/* 451 */	":You have not registered"
+#define STR_ERR_NOTFORHALFOPS		/* 460 */	":Halfops cannot set mode %c"
+#define STR_ERR_NEEDMOREPARAMS		/* 461 */	"%s :Not enough parameters"
+#define STR_ERR_ALREADYREGISTRED	/* 462 */	":You may not reregister"
+#define STR_ERR_PASSWDMISMATCH		/* 464 */	":Password Incorrect"
+#define STR_ERR_YOUREBANNEDCREEP	/* 465 */	":%s"
+#define STR_ERR_ONLYSERVERSCANCHANGE	/* 468 */	"%s :Only servers can change that mode"
+#define STR_ERR_LINKCHANNEL		/* 470 */	"%s %s :[Link] %s has become full, so you are automatically being transferred to the linked channel %s"
+#define STR_ERR_CHANNELISFULL		/* 471 */	"%s :Cannot join channel (+l)"
+#define STR_ERR_UNKNOWNMODE		/* 472 */	"%c :is unknown mode char to me"
+#define STR_ERR_INVITEONLYCHAN		/* 473 */	"%s :Cannot join channel (+i)"
+#define STR_ERR_BANNEDFROMCHAN		/* 474 */	"%s :Cannot join channel (+b)"
+#define STR_ERR_BADCHANNELKEY		/* 475 */	"%s :Cannot join channel (+k)"
+#define STR_ERR_NEEDREGGEDNICK		/* 477 */	"%s :You need a registered nick to join that channel."
+#define STR_ERR_BANLISTFULL		/* 478 */	"%s %s :Channel ban/ignore list is full"
+#define STR_ERR_CANNOTKNOCK		/* 480 */	":Cannot knock on %s (%s)"
+#define STR_ERR_NOPRIVILEGES		/* 481 */	":Permission Denied- You do not have the correct IRC operator privileges"
+#define STR_ERR_CHANOPRIVSNEEDED	/* 482 */	"%s :You're not channel operator"
+#define STR_ERR_KILLDENY		/* 485 */	":Cannot kill protected user %s."
+#define STR_ERR_NOTFORUSERS		/* 487 */	":%s is a server only command"
+#define STR_ERR_SECUREONLYCHAN		/* 489 */	"%s :Cannot join channel (Secure connection is required)"
+#define STR_ERR_NOOPERHOST		/* 491 */	":No O-lines for your host"
+#define STR_ERR_CHANOWNPRIVNEEDED	/* 499 */	"%s :You're not a channel owner"
+#define STR_ERR_TOOMANYJOINS		/* 500 */	"%s :Too many join requests. Please wait a while and try again."
+#define STR_ERR_UMODEUNKNOWNFLAG	/* 501 */	":Unknown MODE flag"
+#define STR_ERR_USERSDONTMATCH		/* 502 */	":Cant change mode for other users"
+#define STR_ERR_SILELISTFULL		/* 511 */	"%s :Your silence list is full"
+#define STR_ERR_TOOMANYWATCH		/* 512 */	"%s :Maximum size for WATCH-list is 128 entries"
+#define STR_ERR_TOOMANYDCC		/* 514 */	"%s :Your dcc allow list is full. Maximum size is %d entries"
+#define STR_ERR_DISABLED		/* 517 */	"%s :%s" /* ircu */
+#define STR_ERR_NOINVITE		/* 518 */	":Cannot invite (+V) at channel %s"
+#define STR_ERR_OPERONLY		/* 520 */	":Cannot join channel %s (IRCops only)"
+#define STR_ERR_LISTSYNTAX		/* 521 */	":Bad list syntax, type /quote list ? or /raw list ?"
+#define STR_ERR_WHOLIMEXCEED		/* 523 */	":Error, /who limit of %d exceeded. Please narrow your search down and try again"
+#define STR_ERR_OPERSPVERIFY		/* 524 */	":Trying to join +s or +p channel as an oper. Please invite yourself first."
+#define STR_ERR_CANTSENDTOUSER		/* 531 */	"%s :%s"
+#define STR_RPL_REAWAY			/* 597 */	"%s %s %s %lld :%s"
+#define STR_RPL_GONEAWAY		/* 598 */	"%s %s %s %lld :%s"
+#define STR_RPL_NOTAWAY			/* 599 */	"%s %s %s %lld :is no longer away"
+#define STR_RPL_LOGON			/* 600 */	"%s %s %s %lld :logged online"
+#define STR_RPL_LOGOFF			/* 601 */	"%s %s %s %lld :logged offline"
+#define STR_RPL_WATCHOFF		/* 602 */	"%s %s %s %lld :stopped watching"
+#define STR_RPL_WATCHSTAT		/* 603 */	":You have %d and are on %d WATCH entries"
+#define STR_RPL_NOWON			/* 604 */	"%s %s %s %lld :is online"
+#define STR_RPL_NOWOFF			/* 605 */	"%s %s %s %lld :is offline"
+#define STR_RPL_WATCHLIST		/* 606 */	":%s"
+#define STR_RPL_ENDOFWATCHLIST		/* 607 */	":End of WATCH %c"
+#define STR_RPL_NOWISAWAY		/* 609 */	"%s %s %s %lld :is away"
+#define STR_RPL_MAPMORE			/* 610 */	":%s%-*s --> *more*"
+#define STR_RPL_DCCSTATUS		/* 617 */	":%s has been %s your DCC allow list"
+#define STR_RPL_DCCLIST			/* 618 */	":%s"
+#define STR_RPL_ENDOFDCCLIST		/* 619 */	":End of DCCALLOW %s"
+#define STR_RPL_DCCINFO			/* 620 */	":%s"
+#define STR_RPL_SPAMCMDFWD		/* 659 */	"%s :Command processed, but a copy has been sent to ircops for evaluation (anti-spam) purposes. [%s]"
+#define STR_RPL_STARTTLS		/* 670 */	":STARTTLS successful, go ahead with TLS handshake" /* kineircd */
+#define STR_RPL_WHOISSECURE		/* 671 */	"%s :%s" /* our variation on the kineircd numeric */
+#define STR_ERR_STARTTLS		/* 691 */	":%s"
+#define STR_ERR_INVALIDMODEPARAM	/* 696 */	"%s %c %s :%s"
+#define STR_RPL_MONONLINE		/* 730 */	":%s!%s@%s"
+#define STR_RPL_MONOFFLINE		/* 731 */	":%s"
+#define STR_RPL_MONLIST			/* 732 */	":%s"
+#define STR_RPL_ENDOFMONLIST		/* 733 */	":End of MONITOR list"
+#define STR_ERR_MONLISTFULL		/* 734 */	"%d %s :Monitor list is full."
+#define STR_ERR_MLOCKRESTRICTED		/* 742 */	"%s %c %s :MODE cannot be set due to channel having an active MLOCK restriction policy"
+#define STR_RPL_LOGGEDIN		/* 900 */	"%s!%s@%s %s :You are now logged in as %s."
+#define STR_RPL_LOGGEDOUT		/* 901 */	"%s!%s@%s :You are now logged out."
+#define STR_RPL_SASLSUCCESS		/* 903 */	":SASL authentication successful"
+#define STR_ERR_SASLFAIL		/* 904 */	":SASL authentication failed"
+#define STR_ERR_SASLTOOLONG		/* 905 */	":SASL message too long"
+#define STR_ERR_SASLABORTED		/* 906 */	":SASL authentication aborted"
+#define STR_RPL_SASLMECHS		/* 908 */	"%s :are available SASL mechanisms"
+#define STR_ERR_CANNOTDOCOMMAND		/* 972 */	"%s :%s"
+#define STR_ERR_CANNOTCHANGECHANMODE	/* 974 */	"%c :%s"
diff --git a/include/proto.h b/include/proto.h
@@ -1,68 +0,0 @@
-/************************************************************************
- *   Unreal Internet Relay Chat Daemon, include/proto.h
- *      (C) Dominick Meglio <codemastr@unrealircd.com> 2000
- *
- *   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.
- */
-
-#ifndef proto_h
-#define proto_h
-/* lusers.c */
-extern void init_irccounts(void);
-
-/* match.c */
-extern char *collapse(char *pattern);
-
-/* scache.c */
-extern void clear_scache_hash_table(void);
-
-/* send.c */
-extern void sendto_one(Client *, MessageTag *mtags, FORMAT_STRING(const char *), ...) __attribute__((format(printf,3,4)));
-extern void sendto_realops(FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,1,2)));
-
-/* ircd.c */
-extern EVENT(garbage_collect);
-extern EVENT(loop_event);
-extern EVENT(check_pings);
-extern EVENT(handshake_timeout);
-extern EVENT(check_deadsockets);
-extern EVENT(try_connections);
-/* support.c */
-extern char *my_itoa(int i);
-
-/* s_serv.c */
-extern void load_tunefile(void);
-extern EVENT(save_tunefile);
-extern void read_motd(const char *filename, MOTDFile *motd);
-
-/* s_user.c */
-extern int target_limit_exceeded(Client *client, void *target, const char *name);
-extern void make_umodestr(void);
-extern char *get_usermode_string(Client *acptr);
-
-/* s_misc.c */
-extern char *convert_time(time_t ltime);
-
-/* whowas.c */
-extern void initwhowas(void);
-
-/* uid.c */
-extern void uid_init(void);
-extern const char *uid_get(void);
-
-#endif /* proto_h */
diff --git a/include/setup.h.in b/include/setup.h.in
@@ -115,6 +115,9 @@
 /* Define to 1 if you have the `strlncat' function. */
 #undef HAVE_STRLNCAT
 
+/* Define to 1 if you have the `strlncpy' function. */
+#undef HAVE_STRLNCPY
+
 /* Define to 1 if you have the `syslog' function. */
 #undef HAVE_SYSLOG
 
@@ -130,9 +133,6 @@
 /* Define to 1 if you have the <unistd.h> header file. */
 #undef HAVE_UNISTD_H
 
-/* Define if you want modes shown in /list */
-#undef LIST_SHOW_MODES
-
 /* Define the location of the log files */
 #undef LOGDIR
 
@@ -181,9 +181,6 @@
 /* Define the path of the pid file */
 #undef PIDFILE
 
-/* Define if you want +a/+q prefixes */
-#undef PREFIX_AQ
-
 /* Define the location of private libraries */
 #undef PRIVATELIBDIR
 
diff --git a/include/struct.h b/include/struct.h
@@ -39,6 +39,7 @@
 #include <openssl/rand.h>
 #include <openssl/md5.h>
 #include <openssl/ripemd.h>
+#include <jansson.h>
 #include "common.h"
 #include "sys.h"
 #include <stdio.h>
@@ -54,6 +55,14 @@
 # ifdef SYSSYSLOGH
 #  include <sys/syslog.h>
 # endif
+#ifndef UNREAL_LOGGER_CODE
+/* undef these as they cause confusion with our ULOG_xxx codes */
+#undef LOG_DEBUG
+#undef LOG_INFO
+#undef LOG_WARNING
+#undef LOG_ERROR
+#undef LOG_FATAL
+#endif
 #endif
 #define PCRE2_CODE_UNIT_WIDTH 8
 #include "pcre2.h"
@@ -92,19 +101,15 @@ typedef struct ConfigFlag_allow ConfigFlag_allow;
 typedef struct ConfigItem_allow_channel ConfigItem_allow_channel;
 typedef struct ConfigItem_allow_dcc ConfigItem_allow_dcc;
 typedef struct ConfigItem_vhost ConfigItem_vhost;
-typedef struct ConfigItem_except ConfigItem_except;
 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_log ConfigItem_log;
-typedef struct ConfigItem_unknown ConfigItem_unknown;
-typedef struct ConfigItem_unknown_ext ConfigItem_unknown_ext;
 typedef struct ConfigItem_alias ConfigItem_alias;
 typedef struct ConfigItem_alias_format ConfigItem_alias_format;
-typedef struct ConfigItem_include ConfigItem_include;
+typedef struct ConfigResource ConfigResource;
 typedef struct ConfigItem_blacklist_module ConfigItem_blacklist_module;
 typedef struct ConfigItem_help ConfigItem_help;
 typedef struct ConfigItem_offchans ConfigItem_offchans;
@@ -129,9 +134,6 @@ typedef struct Mode Mode;
 typedef struct MessageTag MessageTag;
 typedef struct MOTDFile MOTDFile; /* represents a whole MOTD, including remote MOTD support info */
 typedef struct MOTDLine MOTDLine; /* one line of a MOTD stored as a linked list */
-#ifdef USE_LIBCURL
-typedef struct MOTDDownload MOTDDownload; /* used to coordinate download of a remote MOTD */
-#endif
 
 typedef struct RealCommand RealCommand;
 typedef struct CommandOverride CommandOverride;
@@ -163,13 +165,11 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
 #include "dbuf.h"		/* THIS REALLY SHOULDN'T BE HERE!!! --msa */
 #endif
 
-#define	HOSTLEN		63	/* Length of hostname.  Updated to         */
-				/* comply with RFC1123                     */
-
+#define	HOSTLEN		63	/* Length of hostname */
 #define	NICKLEN		30
 #define	USERLEN		10
 #define	REALLEN	 	50
-#define SVIDLEN		30
+#define ACCOUNTLEN	30
 #define MAXTOPICLEN	360	/* absolute maximum permitted topic length (above this = potential desync) */
 #define MAXAWAYLEN	360	/* absolute maximum permitted away length (above this = potential desync) */
 #define MAXKICKLEN	360	/* absolute maximum kick length (above this = only cutoff danger) */
@@ -182,8 +182,8 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
 #define READBUFSIZE	8192	/* for the read buffer */
 #define	MAXRECIPIENTS 	20
 #define	MAXSILELENGTH	NICKLEN+USERLEN+HOSTLEN+10
-#define IDLEN		10
-#define SIDLEN           3
+#define IDLEN		12
+#define SIDLEN		3
 #define SWHOISLEN	256
 #define UMODETABLESZ (sizeof(long) * 8)
 #define MAXCCUSERS		20 /* Maximum for set::anti-flood::max-concurrent-conversations */
@@ -203,16 +203,77 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
 /* Logging types */
 #define LOG_ERROR 0x0001
 #define LOG_KILL  0x0002
-#define LOG_TKL   0x0004
-#define LOG_KLINE 0x0008
-#define LOG_CLIENT 0x0010
-#define LOG_SERVER 0x0020
-#define LOG_OPER   0x0040
 #define LOG_SACMDS 0x0080
 #define LOG_CHGCMDS 0x0100
 #define LOG_OVERRIDE 0x0200
-#define LOG_SPAMFILTER 0x0400
-#define LOG_FLOOD 0x0800
+
+typedef enum LogFieldType {
+	LOG_FIELD_INTEGER, // and unsigned?
+	LOG_FIELD_STRING,
+	LOG_FIELD_CLIENT,
+	LOG_FIELD_CHANNEL,
+	LOG_FIELD_OBJECT
+} LogFieldType;
+
+typedef struct LogData {
+	LogFieldType type;
+	char *key;
+	union {
+		int64_t integer;
+		char *string;
+		Client *client;
+		Channel *channel;
+		json_t *object;
+	} value;
+} LogData;
+
+/** New log levels for unreal_log() */
+/* Note: the reason for these high numbers is so we can easily catch
+ * if someone makes a mistake to use LOG_INFO (from syslog.h) instead
+ * of the ULOG_xxx levels.
+ */
+typedef enum LogLevel {
+	ULOG_INVALID = 0,
+	ULOG_DEBUG = 1000,
+	ULOG_INFO = 2000,
+	ULOG_WARNING = 3000,
+	ULOG_ERROR = 4000,
+	ULOG_FATAL = 5000
+} LogLevel;
+
+/** Logging types (text, json, etc) */
+typedef enum LogType {
+	LOG_TYPE_INVALID = 0,
+	LOG_TYPE_TEXT = 1,
+	LOG_TYPE_JSON = 2,
+} LogType;
+
+#define LOG_CATEGORY_LEN	32
+#define LOG_EVENT_ID_LEN	64
+typedef struct LogSource LogSource;
+struct LogSource {
+	LogSource *prev, *next;
+	LogLevel loglevel;
+	char negative; /**< 1 if negative match (eg !operoverride), 0 if normal */
+	char subsystem[LOG_CATEGORY_LEN+1];
+	char event_id[LOG_EVENT_ID_LEN+1];
+};
+
+typedef struct Log Log;
+struct Log {
+	Log *prev, *next;
+	LogSource *sources;
+	char destination[CHANNELLEN+1];
+	char *file;
+	char *filefmt;
+	long maxsize;
+	int type;
+	int logfd;
+};
+
+/** This is used for deciding the <index> in logs[<index>] and temp_logs[<index>] */
+typedef enum LogDestination { LOG_DEST_SNOMASK=0, LOG_DEST_OPER=1, LOG_DEST_REMOTE=2, LOG_DEST_CHANNEL=3, LOG_DEST_DISK=4 } LogDestination;
+#define NUM_LOG_DESTINATIONS 5
 
 /*
 ** 'offsetof' is defined in ANSI-C. The following definition
@@ -251,7 +312,7 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
 
 /** This specifies the current client status or the client type - see @link ClientStatus @endlink in particular.
  * You may think "server" or "client" are the only choices here, but there are many more
- * such as states where the user is in the middle of an SSL/TLS handshake.
+ * such as states where the user is in the middle of an TLS handshake.
  * @defgroup ClientStatuses Client statuses / types
  * @{
  */
@@ -259,8 +320,8 @@ typedef enum ClientStatus {
 	CLIENT_STATUS_LOG			= -7,	/**< Client is a log file */
 	CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE	= -8,	/**< Client is doing a STARTTLS handshake */
 	CLIENT_STATUS_CONNECTING		= -6,	/**< Client is an outgoing connect */
-	CLIENT_STATUS_TLS_CONNECT_HANDSHAKE	= -5,	/**< Client is doing an SSL/TLS handshake - outgoing connection */
-	CLIENT_STATUS_TLS_ACCEPT_HANDSHAKE	= -4,	/**< Client is doing an SSL/TLS handshake - incoming connection */
+	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 */
 	CLIENT_STATUS_HANDSHAKE			= -3,	/**< Client is doing a server handshake - outgoing connection */
 	CLIENT_STATUS_ME			= -2,	/**< Client is &me (this server) */
 	CLIENT_STATUS_UNKNOWN			= -1,	/**< Client is doing a hanshake. May become a server or user later, we don't know yet */
@@ -325,16 +386,17 @@ typedef enum ClientStatus {
 #define CLIENT_FLAG_DCCNOTICE		0x00200000	/**< Has the user seen a notice on how to use DCCALLOW already? */
 #define CLIENT_FLAG_SHUNNED		0x00400000	/**< Connection is shunned (user cannot execute any commands) */
 #define CLIENT_FLAG_VIRUS		0x00800000	/**< Tagged by spamfilter as a virus */
-#define CLIENT_FLAG_TLS			0x01000000	/**< Connection is using SSL/TLS */
+#define CLIENT_FLAG_TLS			0x01000000	/**< Connection is using TLS */
 #define CLIENT_FLAG_NOFAKELAG		0x02000000	/**< Exemption from fake lag */
 #define CLIENT_FLAG_DCCBLOCK		0x04000000	/**< Block all DCC send requests */
 #define CLIENT_FLAG_MAP			0x08000000	/**< Show this entry in /MAP (only used in map module) */
 #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 SNO_DEFOPER "+kscfvGqobS"
-#define SNO_DEFUSER "+ks"
+#define OPER_SNOMASKS "+bBcdfkqsSoO"
 
 #define SEND_UMODES (SendUmodes)
 #define ALL_UMODES (AllUmodes)
@@ -346,13 +408,14 @@ typedef enum ClientStatus {
  * Note that client protocol extensions have been moved
  * to the ClientCapability API which uses acptr->local->caps.
  */
-#define PROTO_VL	0x000040	/* Negotiated VL protocol */
-#define PROTO_VHP	0x000100	/* Send hostnames in NICKv2 even if not sethosted */
-#define PROTO_CLK	0x001000	/* Send cloaked host in the NICK command (regardless of +x/-x) */
-#define PROTO_MLOCK	0x002000	/* server supports MLOCK */
-#define PROTO_EXTSWHOIS 0x004000	/* extended SWHOIS support */
-#define PROTO_SJSBY	0x008000	/* SJOIN setby information (TS and nick) */
-#define PROTO_MTAGS	0x010000	/* Support message tags and big buffers */
+#define PROTO_VL	0x000001	/* Negotiated VL protocol */
+#define PROTO_VHP	0x000002	/* Send hostnames in NICKv2 even if not sethosted */
+#define PROTO_CLK	0x000004	/* Send cloaked host in the NICK command (regardless of +x/-x) */
+#define PROTO_MLOCK	0x000008	/* server supports MLOCK */
+#define PROTO_EXTSWHOIS 0x000010	/* extended SWHOIS support */
+#define PROTO_SJSBY	0x000020	/* SJOIN setby information (TS and nick) */
+#define PROTO_MTAGS	0x000040	/* Support message tags and big buffers */
+#define PROTO_NEXTBANS	0x000080	/* Server supports named extended bans */
 
 /* For client capabilities: */
 #define CAP_INVERT	1L
@@ -368,41 +431,21 @@ typedef enum ClientStatus {
 #define IsDeaf(x)               ((x)->umodes & UMODE_DEAF)
 #define	IsOper(x)		((x)->umodes & UMODE_OPER)
 #define	IsInvisible(x)		((x)->umodes & UMODE_INVISIBLE)
-#define IsARegNick(x)		((x)->umodes & (UMODE_REGNICK))
 #define IsRegNick(x)		((x)->umodes & UMODE_REGNICK)
-#define	SendWallops(x)		(!IsMe(x) && IsUser(x) && ((x)->umodes & UMODE_WALLOP))
 #define IsHidden(x)             ((x)->umodes & UMODE_HIDE)
 #define IsSetHost(x)		((x)->umodes & UMODE_SETHOST)
 #define IsHideOper(x)		((x)->umodes & UMODE_HIDEOPER)
 #define	SetOper(x)		((x)->umodes |= UMODE_OPER)
 #define	SetInvisible(x)		((x)->umodes |= UMODE_INVISIBLE)
-#define	SetWallops(x)  		((x)->umodes |= UMODE_WALLOP)
 #define SetRegNick(x)		((x)->umodes & UMODE_REGNICK)
 #define SetHidden(x)            ((x)->umodes |= UMODE_HIDE)
 #define SetHideOper(x)		((x)->umodes |= UMODE_HIDEOPER)
 #define IsSecureConnect(x)	((x)->umodes & UMODE_SECURE)
 #define	ClearOper(x)		((x)->umodes &= ~UMODE_OPER)
 #define	ClearInvisible(x)	((x)->umodes &= ~UMODE_INVISIBLE)
-#define	ClearWallops(x)		((x)->umodes &= ~UMODE_WALLOP)
 #define ClearHidden(x)          ((x)->umodes &= ~UMODE_HIDE)
 #define ClearHideOper(x)	((x)->umodes &= ~UMODE_HIDEOPER)
 
-/* Snomask macros: */
-#define	SendServNotice(x)	(((x)->user) && ((x)->user->snomask & SNO_SNOTICE))
-#define IsKillsF(x)		((x)->user->snomask & SNO_KILLS)
-#define IsClientF(x)		((x)->user->snomask & SNO_CLIENT)
-#define IsFloodF(x)		((x)->user->snomask & SNO_FLOOD)
-#define IsEyes(x)		((x)->user->snomask & SNO_EYES)
-#define SetKillsF(x)		((x)->user->snomask |= SNO_KILLS)
-#define SetClientF(x)		((x)->user->snomask |= SNO_CLIENT)
-#define SetFloodF(x)		((x)->user->snomask |= SNO_FLOOD)
-#define SetEyes(x)		((x)->user->snomask |= SNO_EYES)
-#define ClearKillsF(x)		((x)->user->snomask &= ~SNO_KILLS)
-#define ClearClientF(x)		((x)->user->snomask &= ~SNO_CLIENT)
-#define ClearFloodF(x)		((x)->user->snomask &= ~SNO_FLOOD)
-#define ClearEyes(x)		((x)->user->snomask &= ~SNO_EYES)
-
-
 /* Client flags macros: to check for via IsXX(),
  * to set via SetXX() and to clear the flag via ClearXX()
  */
@@ -416,6 +459,7 @@ typedef enum ClientStatus {
 #define IsDCCNotice(x)			((x)->flags & CLIENT_FLAG_DCCNOTICE)
 #define IsDead(x)			((x)->flags & CLIENT_FLAG_DEAD)
 #define IsDeadSocket(x)			((x)->flags & CLIENT_FLAG_DEADSOCKET)
+#define IsServerDisconnectLogged(x)	((x)->flags & CLIENT_FLAG_SERVER_DISCONNECT_LOGGED)
 #define IsUseIdent(x)			((x)->flags & CLIENT_FLAG_USEIDENT)
 #define IsDNSLookup(x)			((x)->flags & CLIENT_FLAG_DNSLOOKUP)
 #define IsEAuth(x)			((x)->flags & CLIENT_FLAG_EAUTH)
@@ -447,6 +491,7 @@ typedef enum ClientStatus {
 #define SetDCCNotice(x)			do { (x)->flags |= CLIENT_FLAG_DCCNOTICE; } while(0)
 #define SetDead(x)			do { (x)->flags |= CLIENT_FLAG_DEAD; } while(0)
 #define SetDeadSocket(x)		do { (x)->flags |= CLIENT_FLAG_DEADSOCKET; } while(0)
+#define SetServerDisconnectLogged(x)	do { (x)->flags |= CLIENT_FLAG_SERVER_DISCONNECT_LOGGED; } while(0)
 #define SetUseIdent(x)			do { (x)->flags |= CLIENT_FLAG_USEIDENT; } while(0)
 #define SetDNSLookup(x)			do { (x)->flags |= CLIENT_FLAG_DNSLOOKUP; } while(0)
 #define SetEAuth(x)			do { (x)->flags |= CLIENT_FLAG_EAUTH; } while(0)
@@ -508,9 +553,9 @@ typedef enum ClientStatus {
 #define	IsNotSpoof(x)	((x)->local->nospoof == 0)
 #define GetHost(x)	(IsHidden(x) ? (x)->user->virthost : (x)->user->realhost)
 #define GetIP(x)	(x->ip ? x->ip : "255.255.255.255")
-#define IsLoggedIn(x)	(IsRegNick(x) || (x->user && (*x->user->svid != '*') && !isdigit(*x->user->svid))) /* registered nick (+r) or just logged into services (may be -r) */
-#define IsSynched(x)	(x->serv->flags.synced)
-#define IsServerSent(x) (x->serv && x->serv->flags.server_sent)
+#define IsLoggedIn(x)	(x->user && (*x->user->account != '*') && !isdigit(*x->user->account)) /**< Logged into services */
+#define IsSynched(x)	(x->server->flags.synced)
+#define IsServerSent(x) (x->server && x->server->flags.server_sent)
 
 /* And more that access client stuff - but actually modularized */
 #define GetReputation(client) (moddata_client_get(client, "reputation") ? atoi(moddata_client_get(client, "reputation")) : 0) /**< Get reputation value for a client */
@@ -527,33 +572,14 @@ typedef enum ClientStatus {
 #define SupportVHP(x)		(CHECKSERVERPROTO(x, PROTO_VHP))
 #define SupportCLK(x)		(CHECKSERVERPROTO(x, PROTO_CLK))
 #define SupportMTAGS(x)		(CHECKSERVERPROTO(x, PROTO_MTAGS))
+#define SupportNEXTBANS(x)	(CHECKSERVERPROTO(x, PROTO_NEXTBANS))
 
 #define SetVL(x)		((x)->local->proto |= PROTO_VL)
 #define SetSJSBY(x)		((x)->local->proto |= PROTO_SJSBY)
 #define SetVHP(x)		((x)->local->proto |= PROTO_VHP)
 #define SetCLK(x)		((x)->local->proto |= PROTO_CLK)
 #define SetMTAGS(x)		((x)->local->proto |= PROTO_MTAGS)
-
-/*
- * defined debugging levels
- */
-#define	DEBUG_FATAL  0
-#define	DEBUG_ERROR  1		/* report_error() and other errors that are found */
-#define	DEBUG_NOTICE 3
-#define	DEBUG_DNS    4		/* used by all DNS related routines - a *lot* */
-#define	DEBUG_INFO   5		/* general usful info */
-#define	DEBUG_NUM    6		/* numerics */
-#define	DEBUG_SEND   7		/* everything that is sent out */
-#define	DEBUG_DEBUG  8		/* anything to do with debugging, ie unimportant :) */
-#define	DEBUG_MALLOC 9		/* malloc/free calls */
-#define	DEBUG_LIST  10		/* debug list use */
-
-/*
- * defines for curses in client
- */
-#define	DUMMY_TERM	0
-#define	CURSES_TERM	1
-#define	TERMCAP_TERM	2
+#define SetNEXTBANS(x)		((x)->local->proto |= PROTO_NEXTBANS)
 
 /* Dcc deny types (see src/s_extra.c) */
 #define DCCDENY_HARD	0
@@ -576,12 +602,20 @@ union ModData
 #ifndef _WIN32
  #define CHECK_LIST_ENTRY(list)		if (offsetof(typeof(*list),prev) != offsetof(ListStruct,prev)) \
 					{ \
-						ircd_log(LOG_ERROR, "[BUG] %s:%d: List operation on struct with incorrect order (->prev must be 1st struct member)", __FILE__, __LINE__); \
+						unreal_log(ULOG_FATAL, "main", "BUG_LIST_OPERATION", NULL, \
+						           "[BUG] $file:$line: List operation on struct with incorrect order ($error_details)", \
+						           log_data_string("error_details", "->prev must be 1st struct member"), \
+						           log_data_string("file", __FILE__), \
+						           log_data_integer("line", __LINE__)); \
 						abort(); \
 					} \
 					if (offsetof(typeof(*list),next) != offsetof(ListStruct,next)) \
 					{ \
-						ircd_log(LOG_ERROR, "[BUG] %s:%d: List operation on struct with incorrect order (->next must be 2nd struct member))", __FILE__, __LINE__); \
+						unreal_log(ULOG_FATAL, "main", "BUG_LIST_OPERATION", NULL, \
+						           "[BUG] $file:$line: List operation on struct with incorrect order ($error_details)", \
+						           log_data_string("error_details", "->next must be 2nd struct member"), \
+						           log_data_string("file", __FILE__), \
+						           log_data_integer("line", __LINE__)); \
 						abort(); \
 					}
 #else
@@ -591,17 +625,29 @@ union ModData
 #ifndef _WIN32
  #define CHECK_PRIO_LIST_ENTRY(list)	if (offsetof(typeof(*list),prev) != offsetof(ListStructPrio,prev)) \
 					{ \
-						ircd_log(LOG_ERROR, "[BUG] %s:%d: List operation on struct with incorrect order (->prev must be 1st struct member)", __FILE__, __LINE__); \
+						unreal_log(ULOG_FATAL, "main", "BUG_LIST_OPERATION", NULL, \
+						           "[BUG] $file:$line: List operation on struct with incorrect order ($error_details)", \
+						           log_data_string("error_details", "->prev must be 1st struct member"), \
+						           log_data_string("file", __FILE__), \
+						           log_data_integer("line", __LINE__)); \
 						abort(); \
 					} \
 					if (offsetof(typeof(*list),next) != offsetof(ListStructPrio,next)) \
 					{ \
-						ircd_log(LOG_ERROR, "[BUG] %s:%d: List operation on struct with incorrect order (->next must be 2nd struct member))", __FILE__, __LINE__); \
+						unreal_log(ULOG_FATAL, "main", "BUG_LIST_OPERATION", NULL, \
+						           "[BUG] $file:$line: List operation on struct with incorrect order ($error_details)", \
+						           log_data_string("error_details", "->next must be 2nd struct member"), \
+						           log_data_string("file", __FILE__), \
+						           log_data_integer("line", __LINE__)); \
 						abort(); \
 					} \
 					if (offsetof(typeof(*list),priority) != offsetof(ListStructPrio,priority)) \
 					{ \
-						ircd_log(LOG_ERROR, "[BUG] %s:%d: List operation on struct with incorrect order (->priority must be 3rd struct member))", __FILE__, __LINE__); \
+						unreal_log(ULOG_FATAL, "main", "BUG_LIST_OPERATION", NULL, \
+						           "[BUG] $file:$line: List operation on struct with incorrect order ($error_details)", \
+						           log_data_string("error_details", "->priority must be 3rd struct member"), \
+						           log_data_string("file", __FILE__), \
+						           log_data_integer("line", __LINE__)); \
 						abort(); \
 					}
 #else
@@ -610,7 +656,10 @@ union ModData
 
 #define CHECK_NULL_LIST_ITEM(item)	if ((item)->prev || (item)->next) \
 					{ \
-						ircd_log(LOG_ERROR, "[BUG] %s:%d: List operation on item with non-NULL 'prev' or 'next' -- are you adding to a list twice?", __FILE__, __LINE__); \
+						unreal_log(ULOG_FATAL, "main", "BUG_LIST_OPERATION_DOUBLE_ADD", NULL, \
+						           "[BUG] $file:$line: List operation on item with non-NULL 'prev' or 'next' -- are you adding to a list twice?", \
+						           log_data_string("file", __FILE__), \
+						           log_data_integer("line", __LINE__)); \
 						abort(); \
 					}
 
@@ -711,43 +760,10 @@ struct MultiLine {
 	char *line;
 };
 
-#ifdef USE_LIBCURL
-struct MOTDDownload
-{
-	MOTDFile *themotd;
-};
-#endif /* USE_LIBCURL */
-
 struct MOTDFile 
 {
 	struct MOTDLine *lines;
 	struct tm last_modified; /* store the last modification time */
-
-#ifdef USE_LIBCURL
-	/*
-	  This pointer is used to communicate with an asynchronous MOTD
-	  download. The problem is that a download may take 10 seconds or
-	  more to complete and, in that time, the IRCd could be rehashed.
-	  This would mean that TLD blocks are reallocated and thus the
-	  aMotd structs would be free()d in the meantime.
-
-	  To prevent such a situation from leading to a segfault, we
-	  introduce this remote control pointer. It works like this:
-	  1. read_motd() is called with a URL. A new MOTDDownload is
-	     allocated and the pointer is placed here. This pointer is
-	     also passed to the asynchrnous download handler.
-	  2.a. The download is completed and read_motd_async_downloaded()
-	       is called with the same pointer. From this function, this pointer
-	       if free()d. No other code may free() the pointer. Not even free_motd().
-	    OR
-	  2.b. The user rehashes the IRCd before the download is completed.
-	       free_motd() is called, which sets motd_download->themotd to NULL
-	       to signal to read_motd_async_downloaded() that it should ignore
-	       the download. read_motd_async_downloaded() is eventually called
-	       and frees motd_download.
-	 */
-	struct MOTDDownload *motd_download;
-#endif /* USE_LIBCURL */
 };
 
 struct MOTDLine {
@@ -758,16 +774,17 @@ struct MOTDLine {
 struct LoopStruct {
 	unsigned do_garbage_collect : 1;
 	unsigned config_test : 1;
-	unsigned ircd_booted : 1;
-	unsigned ircd_forked : 1;
+	unsigned booted : 1;
+	unsigned forked : 1;
 	unsigned do_bancheck : 1; /* perform *line bancheck? */
 	unsigned do_bancheck_spamf_user : 1; /* perform 'user' spamfilter bancheck */
 	unsigned do_bancheck_spamf_away : 1; /* perform 'away' spamfilter bancheck */
-	unsigned ircd_rehashing : 1;
-	unsigned ircd_terminating : 1;
+	unsigned rehashing : 1;
+	unsigned terminating : 1;
+	unsigned config_load_failed : 1;
+	unsigned rehash_download_busy : 1; /* don't return "all downloads complete", needed for race condition */
 	unsigned tainted : 1;
-	Client *rehash_save_cptr, *rehash_save_client;
-	int rehash_save_sig;
+	Client *rehash_save_client;
 	void (*boot_function)();
 };
 
@@ -801,7 +818,7 @@ typedef struct Whowas {
 	struct Whowas *prev;	/* for hash table... */
 	struct Whowas *cnext;	/* for client struct linked list */
 	struct Whowas *cprev;	/* for client struct linked list */
-} aWhowas;
+} WhoWas;
 
 typedef struct SWhois SWhois;
 struct SWhois {
@@ -848,7 +865,7 @@ struct SWhois {
  *        Note that reading parv[parc] and beyond is OUT OF BOUNDS and will cause a crash.
  *        E.g. parv[3] in the above example is out of bounds.
  */
-#define CMD_FUNC(x) void (x) (Client *client, MessageTag *recv_mtags, int parc, char *parv[])
+#define CMD_FUNC(x) void (x) (Client *client, MessageTag *recv_mtags, int parc, const char *parv[])
 /** @} */
 
 /** Command override function - used by all command override handlers.
@@ -865,13 +882,13 @@ struct SWhois {
  *        Note that reading parv[parc] and beyond is OUT OF BOUNDS and will cause a crash.
  *        E.g. parv[3] in the above example.
  */
-#define CMD_OVERRIDE_FUNC(x) void (x)(CommandOverride *ovr, Client *client, MessageTag *recv_mtags, int parc, char *parv[])
+#define CMD_OVERRIDE_FUNC(x) void (x)(CommandOverride *ovr, Client *client, MessageTag *recv_mtags, int parc, const char *parv[])
 
 
 
-typedef void (*CmdFunc)(Client *client, MessageTag *mtags, int parc, char *parv[]);
-typedef void (*AliasCmdFunc)(Client *client, MessageTag *mtags, int parc, char *parv[], char *cmd);
-typedef void (*OverrideCmdFunc)(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, char *parv[]);
+typedef void (*CmdFunc)(Client *client, MessageTag *mtags, int parc, const char *parv[]);
+typedef void (*AliasCmdFunc)(Client *client, MessageTag *mtags, int parc, const char *parv[], const char *cmd);
+typedef void (*OverrideCmdFunc)(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, const char *parv[]);
 
 #include <sodium.h>
 
@@ -1106,21 +1123,30 @@ struct SpamExcept {
 /** IRC Counts, used for /LUSERS */
 typedef struct IRCCounts IRCCounts;
 struct IRCCounts {
-	int  clients;		/* total */
-	int  invisible;		/* invisible */
-	unsigned short  servers;		/* servers */
-	int  operators;		/* operators */
-	int  unknown;		/* unknown local connections */
-	int  channels;		/* channels */
-	int  me_clients;	/* my clients */
-	unsigned short  me_servers;	/* my servers */
-	int  me_max;		/* local max */
-	int  global_max;	/* global max */
+	int clients;		/* total */
+	int invisible;		/* invisible */
+	int servers;		/* servers */
+	int operators;		/* operators */
+	int unknown;		/* unknown local connections */
+	int channels;		/* channels */
+	int me_clients;		/* my clients */
+	int me_servers;		/* my servers */
+	int me_max;		/* local max */
+	int global_max;		/* global max */
 };
 
 /** The /LUSERS stats information */
 extern MODVAR IRCCounts irccounts;
 
+typedef struct NameValue NameValue;
+/** Name and value list used in a static array, such as in conf.c */
+struct NameValue
+{
+	long value;
+	char *name;
+};
+
+/** Name and value list used in dynamic linked lists */
 typedef struct NameValueList NameValueList;
 struct NameValueList {
 	NameValueList *prev, *next;
@@ -1166,21 +1192,12 @@ struct CommandOverride {
 	OverrideCmdFunc		func;
 };
 
-extern MODVAR Umode *Usermode_Table;
-extern MODVAR short	 Usermode_highest;
-
-extern MODVAR Snomask *Snomask_Table;
-extern MODVAR short Snomask_highest;
-
-extern MODVAR Cmode *Channelmode_Table;
-extern MODVAR unsigned short Channelmode_highest;
+extern MODVAR Umode *usermodes;
+extern MODVAR Cmode *channelmodes;
 
 extern Umode *UmodeAdd(Module *module, char ch, int options, int unset_on_deoper, int (*allowed)(Client *client, int what), long *mode);
 extern void UmodeDel(Umode *umode);
 
-extern Snomask *SnomaskAdd(Module *module, char ch, int (*allowed)(Client *client, int what), long *mode);
-extern void SnomaskDel(Snomask *sno);
-
 extern Cmode *CmodeAdd(Module *reserved, CmodeInfo req, Cmode_t *mode);
 extern void CmodeDel(Cmode *cmode);
 
@@ -1199,16 +1216,13 @@ extern void unload_all_unused_moddata(void);
 #define IsServersOnlyListener(x)	((x) && ((x)->options & LISTENER_SERVERSONLY))
 
 #define CONNECT_TLS		0x000001
-//0x000002 unused (was ziplinks)
-#define CONNECT_AUTO		0x000004
-#define CONNECT_QUARANTINE	0x000008
-#define CONNECT_NODNSCACHE	0x000010
-#define CONNECT_NOHOSTCHECK	0x000020
-#define CONNECT_INSECURE	0x000040
+#define CONNECT_AUTO		0x000002
+#define CONNECT_QUARANTINE	0x000004
+#define CONNECT_INSECURE	0x000008
 
-#define TLSFLAG_FAILIFNOCERT 	0x1
-#define TLSFLAG_NOSTARTTLS	0x8
-#define TLSFLAG_DISABLECLIENTCERT 0x10
+#define TLSFLAG_FAILIFNOCERT 		0x0001
+#define TLSFLAG_NOSTARTTLS		0x0002
+#define TLSFLAG_DISABLECLIENTCERT	0x0004
 
 /** Flood counters for local clients */
 typedef struct FloodCounter {
@@ -1225,9 +1239,17 @@ typedef enum FloodOption {
 	FLD_INVITE		= 3,	/**< invite-flood */
 	FLD_KNOCK		= 4,	/**< knock-flood */
 	FLD_CONVERSATIONS	= 5,	/**< max-concurrent-conversations */
+	FLD_LAG_PENALTY		= 6,	/**< lag-penalty / lag-penalty-bytes */
 } FloodOption;
 #define MAXFLOODOPTIONS 10
 
+typedef struct TrafficStats TrafficStats;
+struct TrafficStats {
+	long long messages_sent;	/* IRC lines sent */
+	long long messages_received;	/* IRC lines received */
+	long long bytes_sent;		/* Bytes sent */
+	long long bytes_received;	/* Received bytes */
+};
 
 /** This shows the Client struct (any client), the User struct (a user), Server (a server) that are commonly accessed both in the core and by 3rd party coders.
  * @defgroup CommonStructs Common structs
@@ -1242,7 +1264,7 @@ struct Client {
 	struct list_head special_node;		/**< For special lists (server || unknown || oper) */
 	LocalClient *local;			/**< Additional information regarding locally connected clients */
 	User *user;				/**< Additional information, if this client is a user */
-	Server *serv;				/**< Additional information, if this is a server */
+	Server *server;				/**< Additional information, if this is a server */
 	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 */
@@ -1257,7 +1279,7 @@ struct Client {
 	char info[REALLEN + 1];			/**< Additional client information text. For users this is gecos/realname */
 	char id[IDLEN + 1];			/**< Unique ID: SID or UID */
 	struct list_head id_hash;		/**< For UID/SID hash table (idTable) */
-	Client *srvptr;				/**< Server on where this client is connected to (can be &me) */
+	Client *uplink;				/**< Server on where this client is connected to (can be &me) */
 	char *ip;				/**< IP address of user or server (never NULL) */
 	ModData moddata[MODDATA_MAX_CLIENT];	/**< Client attached module data, used by the ModData system */
 };
@@ -1266,10 +1288,11 @@ struct Client {
  */
 struct LocalClient {
 	int fd;				/**< File descriptor, can be <0 if socket has been closed already. */
-	SSL *ssl;			/**< OpenSSL/LibreSSL struct for SSL/TLS connection */
-	time_t since;			/**< Time when user will next be allowed to send something (actually since<currenttime+10) */
-	time_t firsttime;		/**< Time user was created (connected on IRC) */
-	time_t lasttime;		/**< Last time any message was received */
+	SSL *ssl;			/**< OpenSSL/LibreSSL struct for TLS connection */
+	time_t fake_lag;		/**< Time when user will next be allowed to send something (actually fake_lag<currenttime+10) */
+	int fake_lag_msec;		/**< Used for calculating 'fake_lag' penalty (modulo) */
+	time_t creationtime;		/**< Time user was created (connected on IRC) */
+	time_t last_msg_received;	/**< Last time any message was received */
 	dbuf sendQ;			/**< Outgoing send queue (data to be sent) */
 	dbuf recvQ;			/**< Incoming receive queue (incoming data yet to be parsed) */
 	ConfigItem_class *class;	/**< The class { } block associated to this client */
@@ -1279,27 +1302,16 @@ struct LocalClient {
 	u_char targets[MAXCCUSERS];	/**< Hash values of targets for target limiting */
 	ConfigItem_listen *listener;	/**< If this client IsListening() then this is the listener configuration attached to it */
 	long serial;			/**< Current serial number for send.c functions (to avoid sending duplicate messages) */
-	time_t nextnick;		/**< Time the next nick change will be allowed */
-	time_t last;			/**< Last time a RESETIDLE message was received (PRIVMSG) */
-	long sendM;			/**< Statistics: protocol messages send */
-	long sendK;			/**< Statistics: total k-bytes send */
-	long receiveM;			/**< Statistics: protocol messages received */
-	long receiveK;			/**< Statistics: total k-bytes received */
-	u_short sendB;			/**< Statistics: counters to count upto 1-k lots of bytes */
-	u_short receiveB;		/**< Statistics: sent and received (???) */
-	short lastsq;			/**< # of 2k blocks when sendqueued called last */
-	Link *watch;			/**< Watch notification list (WATCH) for this user */
-	u_short watches;		/**< Number of entries in the watch list */
+	time_t next_nick_allowed;		/**< Time the next nick change will be allowed */
+	time_t idle_since;		/**< Last time a RESETIDLE message was received (PRIVMSG) */
+	TrafficStats traffic;		/**< Traffic statistics */
 	ModData moddata[MODDATA_MAX_LOCAL_CLIENT];	/**< LocalClient attached module data, used by the ModData system */
-#ifdef DEBUGMODE
-	time_t cputime;			/**< Something with debugging (why is this a time_t? TODO) */
-#endif
 	char *error_str;		/**< Quit reason set by dead_socket() in case of socket/buffer error, later used by exit_client() */
 	char sasl_agent[NICKLEN + 1];	/**< SASL: SASL Agent the user is interacting with */
 	unsigned char sasl_out;		/**< SASL: Number of outgoing sasl messages */
 	unsigned char sasl_complete;	/**< SASL: >0 if SASL authentication was successful */
 	time_t sasl_sent_time;		/**< SASL: 0 or the time that the (last) AUTHENTICATE command has been sent */
-	char *sni_servername;		/**< Servername as sent by client via SNI (Server Name Indication) in SSL/TLS, otherwise NULL */
+	char *sni_servername;		/**< Servername as sent by client via SNI (Server Name Indication) in TLS, otherwise NULL */
 	int cap_protocol;		/**< CAP protocol in use. At least 300 for any CAP capable client. 302 for 3.2, etc.. */
 	uint32_t nospoof;		/**< Anti-spoofing random number (used in user handshake PING/PONG) */
 	char *passwd;			/**< Password used during connect, if any (freed once connected and set to NULL) */
@@ -1315,38 +1327,27 @@ struct LocalClient {
  */
 struct User {
 	Membership *channel;		/**< Channels that the user is in (linked list) */
-	Link *invited;			/**< Channels has the user been invited to (linked list) */
 	Link *dccallow;			/**< DCCALLOW list (linked list) */
-	char *away;			/**< AWAY message, or NULL if not away */
-	char svid[SVIDLEN + 1];		/**< Services account name or ID (SVID) */
-	unsigned short joined;		/**< Number of channels joined */
+	char account[ACCOUNTLEN + 1];	/**< Services account name or ID (SVID) - use IsLoggedIn(client) to check if logged in */
+	int joined;			/**< Number of channels joined */
 	char username[USERLEN + 1];	/**< Username, the user portion in nick!user@host. */
 	char realhost[HOSTLEN + 1];	/**< Realhost, the real host of the user (IP or hostname) - usually this is not shown to other users */
 	char cloakedhost[HOSTLEN + 1];	/**< Cloaked host - generated by cloaking algorithm */
 	char *virthost;			/**< Virtual host - when user has user mode +x this is the active host */
 	char *server;			/**< Server name the user is on (?) */
 	SWhois *swhois;			/**< Special "additional" WHOIS entries such as "a Network Administrator" */
-	aWhowas *whowas;		/**< Something for whowas :D :D */
-	int snomask;			/**< Server Notice Mask (snomask) - only for IRCOps */
-	char *operlogin;		/**< Which oper { } block was used to oper up, otherwise NULL - used by oper::maxlogins */
-	struct {
-		time_t nick_t;		/**< For set::anti-flood::nick-flood: time */
-		time_t knock_t;		/**< For set::anti-flood::knock-flood: time */
-		time_t invite_t;	/**< For set::anti-flood::invite-flood: time */
-		unsigned char nick_c;	/**< For set::anti-flood::nick-flood: counter */
-		unsigned char knock_c;	/**< For set::anti-flood::knock-flood: counter */
-		unsigned char invite_c;	/**< For set::anti-flood::invite-flood: counter */
-	} flood;			/**< Anti-flood counters */
-	time_t lastaway;		/**< Last time the user went AWAY */
+	WhoWas *whowas;			/**< Something for whowas :D :D */
+	char *snomask;			/**< Server Notice Mask (snomask) - only for IRCOps */
+	char *operlogin;		/**< Which oper { } block was used to oper up, otherwise NULL - used for auditting and by oper::maxlogins */
+	char *away;			/**< AWAY message, or NULL if not away */
+	time_t away_since;		/**< Last time the user went AWAY */
 };
 
-/** Server information (local servers and remote servers), you use client->serv to access these (see also @link Client @endlink).
+/** Server information (local servers and remote servers), you use client->server to access these (see also @link Client @endlink).
  */
 struct Server {
-	char *up;			/**< Name of uplink for this server */
 	char by[NICKLEN + 1];		/**< Uhhhh - who activated this connection - AGAIN? */
 	ConfigItem_link *conf;		/**< link { } block associated with this server, or NULL */
-	time_t timestamp;		/**< Remotely determined connect try time */
 	long users;			/**< Number of users on this server */
 	time_t boottime;		/**< Startup time of server (boot time) */
 	struct {
@@ -1424,20 +1425,29 @@ struct ConditionalConfig
 	char *opt; /**< Only for IF_VALUE */
 };
 
+/** Configuration file (config parser) */
 struct ConfigFile
 {
-        char            *cf_filename;
-        ConfigEntry     *cf_entries;
-        ConfigFile     *cf_next;
+	char *filename;		/**< Filename of configuration file */
+	ConfigEntry *items;	/**< All items in the configuration file */
+	ConfigFile *next;	/**< Next configuration file */
 };
 
+/** Configuration entry (config parser) */
 struct ConfigEntry
 {
-        ConfigFile	*ce_fileptr;
-        int 	 	ce_varlinenum, ce_fileposstart, ce_fileposend, ce_sectlinenum;
-        char 		*ce_varname, *ce_vardata;
-        ConfigEntry     *ce_entries, *ce_prevlevel, *ce_next;
-        ConditionalConfig *ce_cond;
+	char *name;			/**< Variable name */
+	char *value;			/**< Variable value, can be NULL */
+	ConfigEntry *next;		/**< Next ConfigEntry */
+	ConfigEntry *items;		/**< Items (children), can be NULL */
+	ConfigFile *file;		/**< To which configfile does this belong? */
+	int line_number;		/**< Line number of the variable name (this one is usually used for errors) */
+	int file_position_start;	/**< Position (byte) within configuration file of the start of the block, rarely used */
+	int file_position_end;		/**< Position (byte) within configuration file of the end of the block, rarely used */
+	int section_linenumber;		/**< Line number of the section (only used internally for parse errors) */
+	ConfigEntry *parent;		/**< Parent item, can be NULL */
+	ConditionalConfig *conditional_config;	/**< Used for conditional config by the main parser */
+	unsigned escaped:1;
 };
 
 struct ConfigFlag 
@@ -1524,8 +1534,7 @@ struct ConfigFlag_allow {
 struct ConfigItem_allow {
 	ConfigItem_allow *prev, *next;
 	ConfigFlag flag;
-	char *ip;
-	char *hostname;
+	ConfigItem_mask *mask;
 	char *server;
 	AuthConfig *auth;
 	int maxperip; /**< Maximum connections permitted per IP address (locally) */
@@ -1533,7 +1542,7 @@ struct ConfigItem_allow {
 	int port;
 	ConfigItem_class *class;
 	ConfigFlag_allow flags;
-	unsigned short ipv6_clone_mask;
+	int ipv6_clone_mask;
 };
 
 struct OperClassACLPath
@@ -1576,7 +1585,7 @@ struct OperClassCheckParams
         Client *client;
         Client *victim;
         Channel *channel;
-        void *extra;
+        const void *extra;
 };
 
 struct ConfigItem_operclass {
@@ -1596,9 +1605,10 @@ struct ConfigItem_oper {
 	unsigned long modes, require_modes;
 	char *vhost;
 	int maxlogins;
+	int server_notice_colors;
 };
 
-/** The SSL/TLS options that are used in set::tls and otherblocks::tls-options.
+/** The TLS options that are used in set::tls and otherblocks::tls-options.
  * NOTE: If you add something here then you must also update the
  *       conf_tlsblock() function in s_conf.c to have it inherited
  *       from set::tls to the other config blocks!
@@ -1645,7 +1655,8 @@ struct ConfigItem_ulines {
 struct ConfigItem_tld {
 	ConfigItem_tld 	*prev, *next;
 	ConfigFlag_tld 	flag;
-	char 		*mask, *channel;
+	ConfigItem_mask *mask;
+	char 		*channel;
 	char 		*motd_file, *rules_file, *smotd_file;
 	char 		*botmotd_file, *opermotd_file;
 	MOTDFile	rules, motd, smotd, botmotd, opermotd;
@@ -1663,6 +1674,7 @@ struct ConfigItem_listen {
 	SSL_CTX *ssl_ctx;
 	TLSOptions *tls_options;
 	int websocket_options; /* should be in module, but lazy */
+	char *websocket_forward;
 };
 
 struct ConfigItem_sni {
@@ -1711,13 +1723,6 @@ struct ConfigItem_link {
 	TLSOptions *tls_options; /**< SSL Options for outgoing connection (optional) */
 };
 
-struct ConfigItem_except {
-	ConfigItem_except      *prev, *next;
-	ConfigFlag_except      flag;
-	int type;
-	char		*mask;
-};
-
 struct ConfigItem_ban {
 	ConfigItem_ban	*prev, *next;
 	ConfigFlag_ban	flag;
@@ -1734,7 +1739,8 @@ struct ConfigItem_deny_dcc {
 struct ConfigItem_deny_link {
 	ConfigItem_deny_link *prev, *next;
 	ConfigFlag_except flag;
-	char *mask, *rule, *prettyrule;
+	ConfigItem_mask  *mask;
+	char *rule, *prettyrule;
 };
 
 struct ConfigItem_deny_version {
@@ -1764,32 +1770,6 @@ struct ConfigItem_allow_dcc {
 	char			*filename;
 };
 
-struct ConfigItem_log {
-	ConfigItem_log *prev, *next;
-	ConfigFlag flag;
-	char *file; /**< Filename to log to (either generated or specified) */
-	char *filefmt; /**< Filename with dynamic % stuff */
-	long maxsize;
-	int  flags;
-	int  logfd;
-};
-
-struct ConfigItem_unknown {
-	ConfigItem_unknown *prev, *next;
-	ConfigFlag flag;
-	ConfigEntry *ce;
-};
-
-struct ConfigItem_unknown_ext {
-	ConfigItem_unknown_ext *prev, *next;
-	ConfigFlag flag;
-	char *ce_varname, *ce_vardata;
-	ConfigFile      *ce_fileptr;
-	int             ce_varlinenum;
-	ConfigEntry     *ce_entries;
-};
-
-
 typedef enum { 
 	ALIAS_SERVICES=1, ALIAS_STATS, ALIAS_NORMAL, ALIAS_COMMAND, ALIAS_CHANNEL, ALIAS_REAL
 } AliasType;
@@ -1812,33 +1792,23 @@ struct ConfigItem_alias_format {
 	Match *expr;
 };
 
-/**
- * In a rehash scenario, conf_include will contain all of the included
- * configs that are actually in use. It also will contain includes
- * that are being processed so that the configuration may be updated.
- * INCLUDE_NOTLOADED is set on all of the config files that are being
- * loaded and unset on already-loaded files. See
- * unload_loaded_includes() and load_includes().
- */
-#define INCLUDE_NOTLOADED  0x1
-#define INCLUDE_REMOTE     0x2
-#define INCLUDE_DLQUEUED   0x4
-/**
- * Marks that an include was loaded without error. This seems to
- * overlap with the INCLUDE_NOTLOADED meaning(?). --binki
- */
-#define INCLUDE_USED       0x8
+#define RESOURCE_REMOTE     0x1
+#define RESOURCE_DLQUEUED   0x2
+#define RESOURCE_INCLUDE    0x4
+
+typedef struct ConfigEntryWrapper ConfigEntryWrapper;
+struct ConfigEntryWrapper {
+	ConfigEntryWrapper *prev, *next;
+	ConfigEntry *ce;
+};
 	
-struct ConfigItem_include {
-	ConfigItem_include *prev, *next;
-	ConfigFlag_ban flag;
-	char *file;
-#ifdef USE_LIBCURL
-	char *url;
-	char *errorbuf;
-#endif
-	char *included_from;
-	int included_from_line;
+struct ConfigResource {
+	ConfigResource *prev, *next;
+	int type;
+	ConfigEntryWrapper *wce; /**< The place(s) where this resource is begin used */
+	char *file; /**< File to read: can be a conf/something file or a downloaded file */
+	char *url; /**< URL, if it is an URL */
+	char *cache_file; /**< Set to filename of local cached copy, if it is available */
 };
 
 struct ConfigItem_blacklist_module {
@@ -1855,7 +1825,7 @@ struct ConfigItem_help {
 
 struct ConfigItem_offchans {
 	ConfigItem_offchans *prev, *next;
-	char chname[CHANNELLEN+1];
+	char name[CHANNELLEN+1];
 	char *topic;
 };
 
@@ -1868,6 +1838,7 @@ struct SecurityGroup {
 	int reputation_score;
 	int webirc;
 	int tls;
+	ConfigItem_mask *include_mask;
 };
 
 #define HM_HOST 1
@@ -1885,14 +1856,6 @@ struct IRCStatistics {
 	unsigned int is_cl;	/* number of client connections */
 	unsigned int is_sv;	/* number of server connections */
 	unsigned int is_ni;	/* connection but no idea who it was */
-	unsigned short is_cbs;	/* bytes sent to clients */
-	unsigned short is_cbr;	/* bytes received to clients */
-	unsigned short is_sbs;	/* bytes sent to servers */
-	unsigned short is_sbr;	/* bytes received to servers */
-	unsigned long is_cks;	/* k-bytes sent to clients */
-	unsigned long is_ckr;	/* k-bytes received to clients */
-	unsigned long is_sks;	/* k-bytes sent to servers */
-	unsigned long is_skr;	/* k-bytes received to servers */
 	time_t is_cti;		/* time spent connected by clients */
 	time_t is_sti;		/* time spent connected by servers */
 	unsigned int is_ac;	/* connections accepted */
@@ -1910,11 +1873,6 @@ struct IRCStatistics {
 	unsigned int is_loc;	/* local connections made */
 };
 
-typedef struct MemoryInfo {
-	unsigned int classes;
-	unsigned long classesmem;
-} MemoryInfo;
-
 #define EXTCMODETABLESZ 32
 
 /* Number of maximum paramter modes to allow.
@@ -1928,13 +1886,30 @@ typedef struct MemoryInfo {
  * Otherwise, see the extended channel modes API, CmodeAdd(), etc.
  */
 struct Mode {
-	long mode;				/**< Core modes set on this channel (one of MODE_*) */
-	Cmode_t extmode;			/**< Other ("extended") channel modes set on this channel */
-	void *extmodeparams[MAXPARAMMODES+1];	/**< Parameters for extended channel modes */
-	int  limit;				/**< The +l limit in effect (eg: 40), if any - otherwise 0 */
-	char key[KEYLEN + 1];			/**< The +k key in effect (eg: secret), if any - otherwise NULL */
+	Cmode_t mode;			/**< Other ("extended") channel modes set on this channel */
+	void *mode_params[MAXPARAMMODES+1];	/**< Parameters for extended channel modes */
 };
 
+/* flags for Link if used to contain Watch --k4be */
+
+/* WATCH type */
+#define WATCH_FLAG_TYPE_WATCH	(1<<0) /* added via /WATCH command */
+#define WATCH_FLAG_TYPE_MONITOR	(1<<1) /* added via /MONITOR command */
+
+/* behaviour switches */
+#define WATCH_FLAG_AWAYNOTIFY	(1<<8) /* should send AWAY notifications */
+
+/* watch triggering events */
+#define WATCH_EVENT_ONLINE		0
+#define WATCH_EVENT_OFFLINE		1
+#define WATCH_EVENT_AWAY		2
+#define WATCH_EVENT_NOTAWAY		3
+#define WATCH_EVENT_REAWAY		4
+#define WATCH_EVENT_USERHOST	5
+#define WATCH_EVENT_REALNAME	6
+#define WATCH_EVENT_LOGGEDIN	7
+#define WATCH_EVENT_LOGGEDOUT	8
+
 /* Used for notify-hash buckets... -Donwulff */
 
 struct Watch {
@@ -1962,6 +1937,8 @@ struct Link {
 	} value;
 };
 
+#define IsInvalidChannelTS(x)	((x) <= 1000000) /**< Invalid channel creation time */
+
 /**
  * @addtogroup CommonStructs
  * @{
@@ -1979,13 +1956,12 @@ struct Channel {
 	time_t topic_time;			/**< Time at which the topic was last set */
 	int users;				/**< Number of users in the channel */
 	Member *members;			/**< List of channel members (users in the channel) */
-	Link *invites;				/**< List of outstanding /INVITE's from ops */
 	Ban *banlist;				/**< List of bans (+b) */
 	Ban *exlist;				/**< List of ban exceptions (+e) */
 	Ban *invexlist;				/**< List of invite exceptions (+I) */
 	char *mode_lock;			/**< Mode lock (MLOCK) applied to channel - usually by Services */
 	ModData moddata[MODDATA_MAX_CHANNEL];	/**< Channel attached module data, used by the ModData system */
-	char chname[1];				/**< Channel name */
+	char name[CHANNELLEN+1];		/**< Channel name */
 };
 
 /** user/channel member struct (channel->members).
@@ -1997,7 +1973,7 @@ struct Member
 {
 	struct Member *next;				/**< Next entry in list */
 	Client	      *client;				/**< The client */
-	int		flags;				/**< The access of the user on this channel (one or more of CHFL_*) */
+	char member_modes[MEMBERMODESLEN];		/**< The access of the user on this channel (eg "vhoqa") */
 	ModData moddata[MODDATA_MAX_MEMBER];		/** Member attached module data, used by the ModData system */
 };
 
@@ -2010,7 +1986,7 @@ struct Membership
 {
 	struct Membership 	*next;			/**< Next entry in list */
 	struct Channel		*channel;			/**< The channel */
-	int			flags;			/**< The access of the user on this channel (one or more of CHFL_*) */
+	char member_modes[MEMBERMODESLEN];		/**< The (new) access of the user on this channel (eg "vhoqa") */
 	ModData moddata[MODDATA_MAX_MEMBERSHIP];	/**< Membership attached module data, used by the ModData system */
 };
 
@@ -2024,97 +2000,18 @@ struct Ban {
 	time_t when;		/**< When the entry was added */
 };
 
-/*
-** Channel Related macros follow
-*/
-
-/* Channel related flags */
-#ifdef PREFIX_AQ
- #define CHFL_CHANOP_OR_HIGHER (CHFL_CHANOP|CHFL_CHANADMIN|CHFL_CHANOWNER)
- #define CHFL_HALFOP_OR_HIGHER (CHFL_CHANOWNER|CHFL_CHANADMIN|CHFL_CHANOP|CHFL_HALFOP)
-#else
- #define CHFL_CHANOP_OR_HIGHER (CHFL_CHANOP)
- #define CHFL_HALFOP_OR_HIGHER (CHFL_CHANOP|CHFL_HALFOP)
-#endif
-
-/** Channel flags (privileges) of users on a channel.
- * This is used by Member and Membership (m->flags) to indicate the access rights of a user in a channel.
- * Also used by SJOIN and MODE to set some flags while a JOIN or MODE is in process.
- * @defgroup ChannelFlags Channel access flags
- * @{
- */
-/** Is channel owner (+q) */
-#define is_chanowner(cptr,channel) (get_access(cptr,channel) & CHFL_CHANOWNER)
-/** Is channel admin (+a) */
-#define is_chanadmin(cptr,channel) (get_access(cptr,channel) & CHFL_CHANADMIN)
-/** Is channel operator or higher (+o/+a/+q) */
-#define is_chan_op(cptr,channel) (get_access(cptr,channel) & CHFL_CHANOP_OR_HIGHER)
-/** Is some kind of channel op (+h/+o/+a/+q) */
-#define is_skochanop(cptr,channel) (get_access(cptr,channel) & CHFL_HALFOP_OR_HIGHER)
-/** Is half-op (+h) */
-#define is_half_op(cptr,channel) (get_access(cptr,channel) & CHFL_HALFOP)
-/** Has voice (+v) */
-#define has_voice(cptr,channel) (get_access(cptr,channel) & CHFL_VOICE)
-/* Important:
- * Do not blindly change the values of CHFL_* as they must match the
- * ones in MODE_*. I already screwed this up twice. -- Syzop
- * Obviously these should be decoupled in a code cleanup.
- */
-#define	CHFL_CHANOP     0x0001	/**< Channel operator (+o) */
-#define	CHFL_VOICE      0x0002	/**< Voice (+v, can speak through bans and +m) */
-#define	CHFL_DEOPPED	0x0004	/**< De-oped by a server (temporary state) */
-#define CHFL_CHANOWNER	0x0040	/**< Channel owner (+q) */
-#define CHFL_CHANADMIN	0x0080	/**< Channel admin (+a) */
-#define CHFL_HALFOP	0x0100	/**< Channel halfop (+h) */
-#define	CHFL_BAN     	0x0200	/**< Channel ban (+b) - not a real flag, only used in sjoin.c */
-#define CHFL_EXCEPT	0x0400	/**< Channel except (+e) - not a real flag, only used in sjoin.c */
-#define CHFL_INVEX	0x0800  /**< Channel invite exception (+I) - not a real flag, only used in sjoin.c */
-/** @} */
-
-#define CHFL_REJOINING	0x8000  /* used internally by rejoin_* */
-
-#define	CHFL_OVERLAP    (CHFL_CHANOWNER|CHFL_CHANADMIN|CHFL_CHANOP|CHFL_VOICE|CHFL_HALFOP)
-
 /* Channel macros */
-/* Don't blindly change these MODE_* values, see comment 20 lines up! */
-#define	MODE_CHANOP		CHFL_CHANOP
-#define	MODE_VOICE		CHFL_VOICE
-#define	MODE_PRIVATE		0x0004
-#define	MODE_SECRET		0x0008
-#define	MODE_MODERATED  	0x0010
-#define	MODE_TOPICLIMIT 	0x0020
-#define MODE_CHANOWNER		0x0040
-#define MODE_CHANADMIN		0x0080
-#define	MODE_HALFOP		0x0100
 #define MODE_EXCEPT		0x0200
 #define	MODE_BAN		0x0400
-#define	MODE_INVITEONLY 	0x0800
-#define	MODE_NOPRIVMSGS 	0x1000
-#define	MODE_KEY		0x2000
-#define	MODE_LIMIT		0x4000
-#define MODE_RGSTR		0x8000
 #define MODE_INVEX		0x8000000
 
-/*
- * mode flags which take another parameter (With PARAmeterS)
- */
-#define	MODE_WPARAS (MODE_HALFOP|MODE_CHANOP|MODE_VOICE|MODE_CHANOWNER|MODE_CHANADMIN|MODE_BAN|MODE_KEY|MODE_LIMIT|MODE_EXCEPT|MODE_INVEX)
-/*
- * Undefined here, these are used in conjunction with the above modes in
- * the source.
-#define	MODE_DEL       0x200000000
-#define	MODE_ADD       0x400000000
- */
-
-#define	HoldChannel(x)		(!(x))
 /* name invisible */
-#define	SecretChannel(x)	((x) && ((x)->mode.mode & MODE_SECRET))
+#define	SecretChannel(x)	((x) && has_channel_mode((x), 's'))
 /* channel not shown but names are */
-#define	HiddenChannel(x)	((x) && ((x)->mode.mode & MODE_PRIVATE))
+#define	HiddenChannel(x)	((x) && has_channel_mode((x), 'p'))
 /* channel visible */
 #define	ShowChannel(v,c)	(PubChannel(c) || IsMember((v),(c)))
-#define	PubChannel(x)		((!x) || ((x)->mode.mode &\
-				 (MODE_PRIVATE | MODE_SECRET)) == 0)
+#define	PubChannel(x)		(!SecretChannel((x)) && !HiddenChannel((x)))
 
 #define	IsChannelName(name) ((name) && (*(name) == '#'))
 
@@ -2194,11 +2091,19 @@ struct ParseMode {
 	char modechar;
 	char *param;
 	Cmode *extm;
-	char *modebuf; /* curr pos */
-	char *parabuf; /* curr pos */
+	const char *modebuf; /* curr pos */
+	const char *parabuf; /* curr pos */
 	char buf[512]; /* internal parse buffer */
 };
 
+#define MAXMULTILINEMODES       3
+typedef struct MultiLineMode MultiLineMode;
+struct MultiLineMode {
+	char *modeline[MAXMULTILINEMODES+1];
+	char *paramline[MAXMULTILINEMODES+1];
+	int numlines;
+};
+
 typedef struct PendingServer PendingServer;
 struct PendingServer {
 	PendingServer *prev, *next;
@@ -2230,15 +2135,13 @@ struct MaxTarget {
 #define MARK_AS_OFFICIAL_MODULE(modinf)	do { if (modinf && modinf->handle) ModuleSetOptions(modinfo->handle, MOD_OPT_OFFICIAL, 1);  } while(0)
 #define MARK_AS_GLOBAL_MODULE(modinf)	do { if (modinf && modinf->handle) ModuleSetOptions(modinfo->handle, MOD_OPT_GLOBAL, 1);  } while(0)
 
-/* old.. please don't use anymore */
-#define CHANOPPFX "@"
-
 /* used for is_banned type field: */
-#define BANCHK_JOIN		0	/* checking if a ban forbids the person from joining */
-#define BANCHK_MSG		1	/* checking if a ban forbids the person from sending messages */
-#define BANCHK_NICK		2	/* checking if a ban forbids the person from changing his/her nick */
-#define BANCHK_LEAVE_MSG	3	/* checking if a ban forbids the person from leaving a message in PART or QUIT */
-#define BANCHK_TKL		4	/* called from a server ban routine, or other match_user() usage */
+#define BANCHK_JOIN		0x0001	/* checking if a ban forbids the person from joining */
+#define BANCHK_MSG		0x0002	/* checking if a ban forbids the person from sending messages */
+#define BANCHK_NICK		0x0004	/* checking if a ban forbids the person from changing his/her nick */
+#define BANCHK_LEAVE_MSG	0x0008	/* checking if a ban forbids the person from leaving a message in PART or QUIT */
+#define BANCHK_TKL		0x0010	/* called from a server ban routine, or other match_user() usage */
+#define BANCHK_ALL		(BANCHK_JOIN|BANCHK_MSG|BANCHK_NICK|BANCHK_LEAVE_MSG)	/* all events except BANCHK_TKL which is special */
 
 #define TKLISTLEN		26
 #define TKLIPHASHLEN1		4
@@ -2256,8 +2159,6 @@ struct MaxTarget {
 #define MATCH_MASK_IS_UHOST         0x1000
 #define MATCH_MASK_IS_HOST          0x2000
 
-#define MATCH_USE_IDENT             0x0100
-
 typedef enum {
 	POLICY_ALLOW=1,
 	POLICY_WARN=2,
@@ -2303,6 +2204,19 @@ struct ConfigItem_badword {
 #define SKIP_DEAF	0x4
 #define SKIP_CTCP	0x8
 
+typedef struct GeoIPResult GeoIPResult;
+struct GeoIPResult {
+	char *country_code;
+	char *country_name;
+};
+
+typedef enum WhoisConfigDetails {
+	WHOIS_CONFIG_DETAILS_DEFAULT	= 0,
+	WHOIS_CONFIG_DETAILS_NONE	= 1,
+	WHOIS_CONFIG_DETAILS_LIMITED	= 2,
+	WHOIS_CONFIG_DETAILS_FULL	= 3,
+} WhoisConfigDetails;
+
 #endif /* __struct_include__ */
 
 #include "dynconf.h"
diff --git a/include/sys.h b/include/sys.h
@@ -55,9 +55,6 @@
 #endif /* HAVE_INTTYPES_H */
 #endif /* HAVE_STDINT_H */
 
-#ifdef SSL
-#include <openssl/ssl.h>
-#endif
 #ifndef _WIN32
 #include <netinet/in.h>
 #include <sys/socket.h>
diff --git a/include/unrealircd.h b/include/unrealircd.h
@@ -10,7 +10,6 @@
 #include "numeric.h"
 #include "msg.h"
 #include "mempool.h"
-#include "proto.h"
 #include "channel.h"
 #include <time.h>
 #include <sys/stat.h>
@@ -31,7 +30,6 @@
 #include <fcntl.h>
 #include <signal.h>
 #include "h.h"
-#include "url.h"
 #include "version.h"
 #ifdef USE_LIBCURL
  #include <curl/curl.h>
diff --git a/include/url.h b/include/url.h
@@ -1,13 +0,0 @@
-#ifndef URL_H
-#define URL_H
-#include "types.h"
-
-int MODFUNC url_is_valid(const char *);
-extern const char MODFUNC *displayurl(const char *url);
-char MODFUNC *url_getfilename(const char *url);
-char MODFUNC *download_file(const char *, char **);
-void MODFUNC download_file_async(const char *, time_t, vFP, void *callback_data);
-void MODFUNC url_do_transfers_async(void);
-void MODFUNC url_init(void);
-
-#endif
diff --git a/include/version.h b/include/version.h
@@ -54,9 +54,10 @@
  * 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	202120
+#define UNREAL_VERSION_TIME	202148
 
-#define UnrealProtocol 		5002
+#define UNREAL_VERSION		((UNREAL_VERSION_GENERATION << 24) + (UNREAL_VERSION_MAJOR << 16) + (UNREAL_VERSION_MINOR << 8))
+#define UnrealProtocol 		6000
 #define PATCH1  		macro_to_str(UNREAL_VERSION_GENERATION)
 #define PATCH2  		"." macro_to_str(UNREAL_VERSION_MAJOR)
 #define PATCH3  		"." macro_to_str(UNREAL_VERSION_MINOR)
diff --git a/include/whowas.h b/include/whowas.h
@@ -59,7 +59,7 @@ void off_history(Client *);
 **	nickname within the timelimit. Returns NULL, if no
 **	one found...
 */
-Client *get_history(char *, time_t);
+Client *get_history(const char *, time_t);
 					/* Nick name */
 					/* Time limit in seconds */
 
diff --git a/include/windows/setup.h b/include/windows/setup.h
@@ -35,8 +35,6 @@
 #define PIDFILE PERMDATADIR"/unrealircd.pid"
 #define NO_U_TYPES
 #define NEED_U_INT32_T
-#define PREFIX_AQ
-#define LIST_SHOW_MODES
 #define strcasecmp _stricmp
 #define strncasecmp _strnicmp
 #define HAVE_EXPLICIT_BZERO
@@ -57,13 +55,13 @@
 #define _WIN32_WINNT 0x0601
 
 /* Generation version number (e.g.: 3 for Unreal3*) */
-#define UNREAL_VERSION_GENERATION 5
+#define UNREAL_VERSION_GENERATION 6
 
 /* Major version number (e.g.: 2 for Unreal3.2*) */
-#define UNREAL_VERSION_MAJOR 2
+#define UNREAL_VERSION_MAJOR 0
 
 /* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 0
+#define UNREAL_VERSION_MINOR 1
 
 /* Version suffix such as a beta marker or release candidate marker. (e.g.:
    -rcX for unrealircd-3.2.9-rcX) */
diff --git a/src/Makefile.in b/src/Makefile.in
@@ -24,15 +24,15 @@ CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
 OBJS=dns.o auth.o channel.o crule.o dbuf.o \
 	fdlist.o hash.o ircd.o ircsprintf.o list.o \
 	match.o modules.o parse.o mempool.o operclass.o \
-	conf_preprocessor.o conf.o debug.o dispatch.o numeric.o \
+	conf_preprocessor.o conf.o debug.o dispatch.o \
 	misc.o serv.o aliases.o socket.o \
 	tls.o user.o scache.o send.o support.o \
 	version.o whowas.o random.o api-usermode.o api-channelmode.o \
 	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 \
-	crypt_blowfish.o unrealdb.o updconf.o crashreport.o modulemanager.o \
-	utf8.o \
+	crypt_blowfish.o unrealdb.o crashreport.o modulemanager.o \
+	utf8.o log.o \
 	openssl_hostname_validation.o $(URL)
 
 SRC=$(OBJS:%.o=%.c)
@@ -48,13 +48,16 @@ INCLUDES = ../include/channel.h \
 	../include/ircsprintf.h \
 	../include/license.h \
 	../include/modules.h ../include/modversion.h ../include/msg.h \
-	../include/numeric.h ../include/proto.h \
+	../include/numeric.h \
 	../include/resource.h ../include/setup.h \
 	../include/struct.h ../include/sys.h \
-	../include/types.h ../include/url.h \
+	../include/types.h \
 	../include/version.h ../include/whowas.h \
 	../include/openssl_hostname_validation.h
 
+.SUFFIXES:
+.SUFFIXES: .c .h .o
+
 all: build
 
 build:
@@ -80,170 +83,16 @@ mods:
 version.c: version.c.SH
 	$(SHELL) version.c.SH
 
-version.o: version.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c version.c
-
-parse.o: parse.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c parse.c
-
-socket.o: socket.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c socket.c
-
-dispatch.o: dispatch.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c dispatch.c
-
-dbuf.o: dbuf.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c dbuf.c
-
-auth.o: auth.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c auth.c
-
-send.o: send.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c send.c
-
-tls.o: tls.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c tls.c
-
-match.o: match.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c match.c
-
-modules.o: modules.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c modules.c
-
-mempool.o: mempool.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c mempool.c
-
-support.o: support.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c support.c
-
-userload.o: userload.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c userload.c
-
-aliases.o: aliases.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c aliases.c
+%.o:	%.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(BINCFLAGS) -c $<
 
 clean:
 	$(RM) -f *.o *.so *~ core ircd version.c; \
-	cd modules; make clean
+	cd modules; ${MAKE} clean
 
 cleandir: clean
 
 depend:
 	makedepend -I${INCLUDEDIR} ${SRC}
 
-channel.o: channel.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c channel.c
-
-ircd.o: ircd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c ircd.c
-
-list.o: list.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c list.c
-
-dns.o: dns.c $(INCLUDES) ../include/dns.h
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c dns.c
-
-fdlist.o: fdlist.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c fdlist.c
-
-conf_preprocessor.o: conf_preprocessor.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c conf_preprocessor.c
-
-conf.o: conf.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c conf.c
-
-debug.o: debug.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c debug.c
-
-numeric.o: numeric.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c numeric.c
-
-misc.o: misc.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c misc.c
-
-scache.o: scache.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c scache.c
-
-ircsprintf.o: ircsprintf.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c ircsprintf.c
-
-user.o: user.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c user.c
-
-serv.o: serv.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c serv.c
-
-whowas.o: whowas.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c whowas.c
-
-hash.o: hash.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c hash.c
-
-crule.o: crule.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c crule.c
-
-random.o: random.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c random.c
-
-operclass.o: operclass.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c operclass.c
-
-api-usermode.o: api-usermode.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-usermode.c
-
-api-event.o: api-event.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-event.c
-
-api-channelmode.o: api-channelmode.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-channelmode.c
-
-api-moddata.o: api-moddata.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-moddata.c
-
-api-extban.o: api-extban.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-extban.c
-
-api-command.o: api-command.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-command.c
-
-api-isupport.o: api-isupport.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-isupport.c
-
-api-clicap.o: api-clicap.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-clicap.c
-
-api-messagetag.o: api-messagetag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-messagetag.c
-
-api-history-backend.o: api-history-backend.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-history-backend.c
-
-api-efunctions.o: api-efunctions.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c api-efunctions.c
-
-crypt_blowfish.o: crypt_blowfish.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c crypt_blowfish.c
-
-unrealdb.o: unrealdb.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c unrealdb.c
-
-updconf.o: updconf.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c updconf.c
-
-crashreport.o: crashreport.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c crashreport.c
-
-modulemanager.o: modulemanager.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c modulemanager.c
-
-utf8.o: utf8.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c utf8.c
-
-openssl_hostname_validation.o: openssl_hostname_validation.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c openssl_hostname_validation.c
-
-url.o: url.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(BINCFLAGS) -c url.c
-
 # DO NOT DELETE THIS LINE -- make depend depends on it.
-
diff --git a/src/aliases.c b/src/aliases.c
@@ -42,11 +42,12 @@ void strrangetok(char *in, char *out, char tok, short first, short last) {
 /* cmd_alias is a special type of command, it has an extra argument 'cmd'. */
 static int recursive_alias = 0;
 
-void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *cmd)
+void cmd_alias(Client *client, MessageTag *mtags, int parc, const char *parv[], const char *cmd)
 {
 	ConfigItem_alias *alias;
 	Client *acptr;
 	int ret;
+	char request[BUFSIZE];
 
 	if (!(alias = find_alias(cmd))) 
 	{
@@ -64,7 +65,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 
 	if (alias->type == ALIAS_SERVICES) 
 	{
-		if (SERVICES_NAME && (acptr = find_person(alias->nick, NULL)))
+		if (SERVICES_NAME && (acptr = find_user(alias->nick, NULL)))
 		{
 			if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, cmd, alias->nick, 0, NULL))
 				return;
@@ -76,7 +77,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 	}
 	else if (alias->type == ALIAS_STATS) 
 	{
-		if (STATS_SERVER && (acptr = find_person(alias->nick, NULL)))
+		if (STATS_SERVER && (acptr = find_user(alias->nick, NULL)))
 		{
 			if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, cmd, alias->nick, 0, NULL))
 				return;
@@ -88,7 +89,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 	}
 	else if (alias->type == ALIAS_NORMAL) 
 	{
-		if ((acptr = find_person(alias->nick, NULL))) 
+		if ((acptr = find_user(alias->nick, NULL))) 
 		{
 			if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, cmd, alias->nick, 0, NULL))
 				return;
@@ -106,19 +107,19 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 	else if (alias->type == ALIAS_CHANNEL)
 	{
 		Channel *channel;
-		if ((channel = find_channel(alias->nick, NULL)))
+		if ((channel = find_channel(alias->nick)))
 		{
-			char *msg = parv[1];
-			char *errmsg = NULL;
+			const char *msg = parv[1];
+			const char *errmsg = NULL;
 			if (can_send_to_channel(client, channel, &msg, &errmsg, 0))
 			{
-				if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_CHANMSG, cmd, channel->chname, 0, NULL))
+				if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_CHANMSG, cmd, channel->name, 0, NULL))
 					return;
 				new_message(client, NULL, &mtags);
 				sendto_channel(channel, client, client->direction,
-				               PREFIX_ALL, 0, SEND_ALL|SKIP_DEAF, mtags,
+				               NULL, 0, SEND_ALL|SKIP_DEAF, mtags,
 				               ":%s PRIVMSG %s :%s",
-				               client->name, channel->chname, parv[1]);
+				               client->name, channel->name, parv[1]);
 				free_message_tags(mtags);
 				return;
 			}
@@ -132,7 +133,10 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 		char *ptr = "";
 
 		if (!(parc < 2 || *parv[1] == '\0'))
-			ptr = parv[1]; 
+		{
+			strlcpy(request, parv[1], sizeof(request));
+			ptr = request;
+		}
 
 		for (format = alias->format; format; format = format->next)
 		{
@@ -201,7 +205,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 				
 				if (format->type == ALIAS_SERVICES) 
 				{
-					if (SERVICES_NAME && (acptr = find_person(format->nick, NULL)))
+					if (SERVICES_NAME && (acptr = find_user(format->nick, NULL)))
 					{
 						if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, cmd, format->nick, 0, NULL))
 							return;
@@ -212,7 +216,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 				}
 				else if (format->type == ALIAS_STATS) 
 				{
-					if (STATS_SERVER && (acptr = find_person(format->nick, NULL)))
+					if (STATS_SERVER && (acptr = find_user(format->nick, NULL)))
 					{
 						if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, cmd, format->nick, 0, NULL))
 							return;
@@ -223,7 +227,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 				}
 				else if (format->type == ALIAS_NORMAL) 
 				{
-					if ((acptr = find_person(format->nick, NULL))) 
+					if ((acptr = find_user(format->nick, NULL))) 
 					{
 						if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, cmd, format->nick, 0, NULL))
 							return;
@@ -241,19 +245,19 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
 				else if (format->type == ALIAS_CHANNEL)
 				{
 					Channel *channel;
-					if ((channel = find_channel(format->nick, NULL)))
+					if ((channel = find_channel(format->nick)))
 					{
-						char *msg = output;
-						char *errmsg = NULL;
+						const char *msg = output;
+						const char *errmsg = NULL;
 						if (!can_send_to_channel(client, channel, &msg, &errmsg, 0))
 						{
-							if (alias->spamfilter && match_spamfilter(client, output, SPAMF_CHANMSG, cmd, channel->chname, 0, NULL))
+							if (alias->spamfilter && match_spamfilter(client, output, SPAMF_CHANMSG, cmd, channel->name, 0, NULL))
 								return;
 							new_message(client, NULL, &mtags);
 							sendto_channel(channel, client, client->direction,
-							               PREFIX_ALL, 0, SEND_ALL|SKIP_DEAF, mtags,
+							               NULL, 0, SEND_ALL|SKIP_DEAF, mtags,
 							               ":%s PRIVMSG %s :%s",
-							               client->name, channel->chname, parv[1]);
+							               client->name, channel->name, parv[1]);
 							free_message_tags(mtags);
 							return;
 						}
diff --git a/src/api-channelmode.c b/src/api-channelmode.c
@@ -33,10 +33,8 @@
  * @{
  */
 
-/** Table with details on each channel mode handler */
-Cmode *Channelmode_Table = NULL;
-/** Highest index in Channelmode_Table */
-unsigned short Channelmode_highest = 0;
+/** List of all channel modes, their handlers, etc */
+Cmode *channelmodes = NULL;
 
 /** @} */
 
@@ -48,6 +46,7 @@ char extchmstr[4][64];
 /* Private functions (forward declaration) and variables */
 static void make_cmodestr(void);
 static char previous_chanmodes[256];
+static char previous_prefix[256];
 static Cmode *ParamTable[MAXPARAMMODES+1];
 static void unload_extcmode_commit(Cmode *cmode);
 
@@ -55,6 +54,7 @@ static void unload_extcmode_commit(Cmode *cmode);
 void make_extcmodestr()
 {
 	char *p;
+	Cmode *cm;
 	int i;
 	
 	extchmstr[0][0] = extchmstr[1][0] = extchmstr[2][0] = extchmstr[3][0] = '\0';
@@ -64,31 +64,30 @@ void make_extcmodestr()
 
 	/* type 2: 1 par to set/unset (has .unset_with_param) */
 	p = extchmstr[1];
-	for (i=0; i <= Channelmode_highest; i++)
-		if (Channelmode_Table[i].paracount && Channelmode_Table[i].flag &&
-		    Channelmode_Table[i].unset_with_param)
-			*p++ = Channelmode_Table[i].flag;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if (cm->paracount && cm->letter && cm->unset_with_param && (cm->type != CMODE_MEMBER))
+			*p++ = cm->letter;
 	*p = '\0';
 
 	/* type 3: 1 param to set, 0 params to unset (does not have .unset_with_param) */
 	p = extchmstr[2];
-	for (i=0; i <= Channelmode_highest; i++)
-		if (Channelmode_Table[i].paracount && Channelmode_Table[i].flag &&
-		    !Channelmode_Table[i].unset_with_param)
-			*p++ = Channelmode_Table[i].flag;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if (cm->paracount && cm->letter && !cm->unset_with_param)
+			*p++ = cm->letter;
 	*p = '\0';
 	
 	/* type 4: paramless modes */
 	p = extchmstr[3];
-	for (i=0; i <= Channelmode_highest; i++)
-		if (!Channelmode_Table[i].paracount && Channelmode_Table[i].flag)
-			*p++ = Channelmode_Table[i].flag;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if (!cm->paracount && cm->letter)
+			*p++ = cm->letter;
 	*p = '\0';
 }
 
 /** Create the string that is used in numeric 004 */
 static void make_cmodestr(void)
 {
+	Cmode *cm;
 	char *p = &cmodestring[0];
 	CoreChannelModeTable *tab = &corechannelmodetable[0];
 	int i;
@@ -98,35 +97,36 @@ static void make_cmodestr(void)
 		p++;
 		tab++;
 	}
-	for (i=0; i <= Channelmode_highest; i++)
-		if (Channelmode_Table[i].flag)
-			*p++ = Channelmode_Table[i].flag;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if (cm->letter)
+			*p++ = cm->letter;
 	*p = '\0';
 }
 
 /** Check for changes - if any are detected, we broadcast the change */
-void extcmodes_check_for_changes(void)
+void extcmodes_check_for_changed_channel_modes(void)
 {
 	char chanmodes[256];
 	ISupport *isup;
 
+	//sort_cmodes();
 	make_cmodestr();
 	make_extcmodestr();
 
 	snprintf(chanmodes, sizeof(chanmodes), "%s%s", CHPAR1, EXPAR1);
-	safe_strdup(me.serv->features.chanmodes[0], chanmodes);
-	snprintf(chanmodes, sizeof(chanmodes), "%s%s", CHPAR2, EXPAR2);
-	safe_strdup(me.serv->features.chanmodes[1], chanmodes);
-	snprintf(chanmodes, sizeof(chanmodes), "%s%s", CHPAR3, EXPAR3);
-	safe_strdup(me.serv->features.chanmodes[2], chanmodes);
-	snprintf(chanmodes, sizeof(chanmodes), "%s%s", CHPAR4, EXPAR4);
-	safe_strdup(me.serv->features.chanmodes[3], chanmodes);
+	safe_strdup(me.server->features.chanmodes[0], chanmodes);
+	snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR2);
+	safe_strdup(me.server->features.chanmodes[1], chanmodes);
+	snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR3);
+	safe_strdup(me.server->features.chanmodes[2], chanmodes);
+	snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR4);
+	safe_strdup(me.server->features.chanmodes[3], chanmodes);
 
 	ircsnprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
-	            me.serv->features.chanmodes[0],
-	            me.serv->features.chanmodes[1],
-	            me.serv->features.chanmodes[2],
-	            me.serv->features.chanmodes[3]);
+	            me.server->features.chanmodes[0],
+	            me.server->features.chanmodes[1],
+	            me.server->features.chanmodes[2],
+	            me.server->features.chanmodes[3]);
 
 	isup = ISupportFind("CHANMODES");
 	if (!isup)
@@ -139,10 +139,10 @@ void extcmodes_check_for_changes(void)
 	
 	if (*previous_chanmodes && strcmp(chanmodes, previous_chanmodes))
 	{
-		ircd_log(LOG_ERROR, "Channel modes changed at runtime: %s -> %s",
-			previous_chanmodes, chanmodes);
-		sendto_realops("Channel modes changed at runtime: %s -> %s",
-			previous_chanmodes, chanmodes);
+		unreal_log(ULOG_INFO, "mode", "CHANNEL_MODES_CHANGED", NULL,
+		           "Channel modes changed at runtime: $old_channel_modes -> $new_channel_modes",
+		           log_data_string("old_channel_modes", previous_chanmodes),
+		           log_data_string("new_channel_modes", chanmodes));
 		/* Broadcast change to all (locally connected) servers */
 		sendto_server(NULL, 0, 0, NULL, "PROTOCTL CHANMODES=%s", chanmodes);
 	}
@@ -150,40 +150,152 @@ void extcmodes_check_for_changes(void)
 	strlcpy(previous_chanmodes, chanmodes, sizeof(previous_chanmodes));
 }
 
-/** Initialize the extended channel modes system */
-void extcmode_init(void)
+void make_prefix(char **isupport_prefix, char **isupport_statusmsg)
 {
-	Cmode_t val = 1;
-	int	i;
-	Channelmode_Table = safe_alloc(sizeof(Cmode) * EXTCMODETABLESZ);
-	for (i = 0; i < EXTCMODETABLESZ; i++)
+	static char prefix[256];
+	static char prefix_prefix[256];
+	char prefix_modes[256];
+	int rank[256];
+	Cmode *cm;
+	int n;
+
+	*prefix = *prefix_prefix = *prefix_modes = '\0';
+
+	for (n=0, cm=channelmodes; cm && n < ARRAY_SIZEOF(rank)-1; cm = cm->next)
+	{
+		if ((cm->type == CMODE_MEMBER) && cm->letter)
+		{
+			strlcat_letter(prefix_prefix, cm->prefix, sizeof(prefix_prefix));
+			strlcat_letter(prefix_modes, cm->letter, sizeof(prefix_modes));
+			rank[n] = cm->rank;
+			n++;
+		}
+	}
+
+	if (*prefix_prefix)
 	{
-		Channelmode_Table[i].mode = val;
-		val *= 2;
+		int i, j;
+		/* Now sort the damn thing */
+		for (i=0; i < n; i++)
+		{
+			for (j=i+1; j < n; j++)
+			{
+				if (rank[i] < rank[j])
+				{
+					/* swap */
+					char save;
+					int save_rank;
+					save = prefix_prefix[i];
+					prefix_prefix[i] = prefix_prefix[j];
+					prefix_prefix[j] = save;
+					save = prefix_modes[i];
+					prefix_modes[i] = prefix_modes[j];
+					prefix_modes[j] = save;
+					save_rank = rank[i];
+					rank[i] = rank[j];
+					rank[j] = save_rank;
+				}
+			}
+		}
+		snprintf(prefix, sizeof(prefix), "(%s)%s", prefix_modes, prefix_prefix);
 	}
-	Channelmode_highest = 0;
+
+	*isupport_prefix = prefix;
+	*isupport_statusmsg = prefix_prefix;
+}
+
+void extcmodes_check_for_changed_prefixes(void)
+{
+	ISupport *isup;
+	char *prefix, *statusmsg;
+
+	make_prefix(&prefix, &statusmsg);
+	ISupportSet(NULL, "PREFIX", prefix);
+	ISupportSet(NULL, "STATUSMSG", statusmsg);
+
+	if (*previous_prefix && strcmp(prefix, previous_prefix))
+	{
+		unreal_log(ULOG_INFO, "mode", "PREFIX_CHANGED", NULL,
+		           "Prefix changed at runtime: $old_prefix -> $new_prefix",
+		           log_data_string("old_prefix", previous_prefix),
+		           log_data_string("new_prefix", prefix));
+		/* Broadcast change to all (locally connected) servers */
+		sendto_server(NULL, 0, 0, NULL, "PROTOCTL PREFIX=%s", prefix);
+	}
+
+	strlcpy(previous_prefix, prefix, sizeof(previous_prefix));
+}
+
+/** Check for changes - if any are detected, we broadcast the change */
+void extcmodes_check_for_changes(void)
+{
+	extcmodes_check_for_changed_channel_modes();
+	extcmodes_check_for_changed_prefixes();
+}
+
+/** Initialize the extended channel modes system */
+void extcmode_init(void)
+{
 	memset(&extchmstr, 0, sizeof(extchmstr));
 	memset(&param_to_slot_mapping, 0, sizeof(param_to_slot_mapping));
 	*previous_chanmodes = '\0';
+	*previous_prefix = '\0';
 }
 
 /** Update letter->slot mapping and slot->handler mapping */
-void extcmode_para_addslot(Cmode *c, int slot)
+void extcmode_para_addslot(Cmode *cm, int slot)
 {
 	if ((slot < 0) || (slot > MAXPARAMMODES))
 		abort();
-	c->slot = slot;
-	ParamTable[slot] = c;
-	param_to_slot_mapping[c->flag] = slot;
+	cm->param_slot = slot;
+	ParamTable[slot] = cm;
+	param_to_slot_mapping[cm->letter] = slot;
 }
 
 /** Update letter->slot mapping and slot->handler mapping */
-void extcmode_para_delslot(Cmode *c, int slot)
+void extcmode_para_delslot(Cmode *cm, int slot)
 {
 	if ((slot < 0) || (slot > MAXPARAMMODES))
 		abort();
 	ParamTable[slot] = NULL;
-	param_to_slot_mapping[c->flag] = 0;
+	param_to_slot_mapping[cm->letter] = 0;
+}
+
+void channelmode_add_sorted(Cmode *n)
+{
+	Cmode *m;
+
+	if (channelmodes == NULL)
+	{
+		channelmodes = n;
+		return;
+	}
+
+	for (m = channelmodes; m; m = m->next)
+	{
+		if (m->letter == '\0')
+			abort();
+		if (sort_character_lowercase_before_uppercase(n->letter, m->letter))
+		{
+			/* Insert us before */
+			if (m->prev)
+				m->prev->next = n;
+			else
+				channelmodes = n; /* new head */
+			n->prev = m->prev;
+
+			n->next = m;
+			m->prev = n;
+			return;
+		}
+		if (!m->next)
+		{
+			/* Append us at end */
+			m->next = n;
+			n->prev = m;
+			return;
+		}
+	}
 }
 
 /** @defgroup ChannelModeAPI Channel mode API
@@ -198,19 +310,17 @@ void extcmode_para_delslot(Cmode *c, int slot)
  */
 Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
 {
-	short i = 0, j = 0;
 	int paraslot = -1;
 	int existing = 0;
+	Cmode *cm;
 
-	while (i < EXTCMODETABLESZ)
+	for (cm=channelmodes; cm; cm = cm->next)
 	{
-		if (!Channelmode_Table[i].flag)
-			break;
-		else if (Channelmode_Table[i].flag == req.flag)
+		if (cm->letter == req.letter)
 		{
-			if (Channelmode_Table[i].unloaded)
+			if (cm->unloaded)
 			{
-				Channelmode_Table[i].unloaded = 0;
+				cm->unloaded = 0;
 				existing = 1;
 				break;
 			} else {
@@ -219,22 +329,67 @@ Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
 				return NULL;
 			}
 		}
-		i++;
 	}
-	if (i == EXTCMODETABLESZ)
+
+	if (!cm)
 	{
-		Debug((DEBUG_DEBUG, "CmodeAdd failed, no space"));
-		if (module)
-			module->errorcode = MODERR_NOSPACE;
-		return NULL;
+		long l, found = 0;
+
+		if (req.type == CMODE_NORMAL)
+		{
+			for (l = 1; l < LONG_MAX/2; l *= 2)
+			{
+				found = 0;
+				for (cm=channelmodes; cm; cm = cm->next)
+				{
+					if (cm->mode == l)
+					{
+						found = 1;
+						break;
+					}
+				}
+				if (!found)
+					break;
+			}
+			/* If 'found' is still true, then we are out of space */
+			if (found)
+			{
+				unreal_log(ULOG_ERROR, "module", "CHANNEL_MODE_OUT_OF_SPACE", NULL,
+					   "CmodeAdd: out of space!!!");
+				if (module)
+					module->errorcode = MODERR_NOSPACE;
+				return NULL;
+			}
+			cm = safe_alloc(sizeof(Cmode));
+			cm->letter = req.letter;
+			cm->mode = l;
+			*mode = cm->mode;
+		} else if (req.type == CMODE_MEMBER)
+		{
+			if (!req.prefix || !req.sjoin_prefix || !req.paracount ||
+			    !req.unset_with_param || !req.rank)
+			{
+				unreal_log(ULOG_ERROR, "module", "CMODEADD_API_ERROR", NULL,
+					   "CmodeAdd(): module is missing required information. "
+					   "Module: $module_name",
+					   log_data_string("module_name", module->header->name));
+				module->errorcode = MODERR_INVALID;
+				return NULL;
+			}
+			cm = safe_alloc(sizeof(Cmode));
+			cm->letter = req.letter;
+		} else {
+			abort();
+		}
+		channelmode_add_sorted(cm);
 	}
 
-	if (req.paracount == 1)
+	if ((req.paracount == 1) && (req.type == CMODE_NORMAL))
 	{
 		if (existing)
 		{
 			/* Re-use parameter slot of the module with the same modechar that is unloading */
-			paraslot = Channelmode_Table[i].slot;
+			paraslot = cm->param_slot;
 		}
 		else
 		{
@@ -243,7 +398,8 @@ Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
 			{
 				if (paraslot == MAXPARAMMODES - 1)
 				{
-					Debug((DEBUG_DEBUG, "CmodeAdd failed, no space for parameter"));
+					unreal_log(ULOG_ERROR, "module", "CHANNEL_MODE_OUT_OF_SPACE", NULL,
+						   "CmodeAdd: out of space!!! Place 2.");
 					if (module)
 						module->errorcode = MODERR_NOSPACE;
 					return NULL;
@@ -252,39 +408,40 @@ Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
 		}
 	}
 
-	*mode = Channelmode_Table[i].mode;
-	/* Update extended channel mode table highest */
-	Channelmode_Table[i].flag = req.flag;
-	Channelmode_Table[i].paracount = req.paracount;
-	Channelmode_Table[i].is_ok = req.is_ok;
-	Channelmode_Table[i].put_param = req.put_param;
-	Channelmode_Table[i].get_param = req.get_param;
-	Channelmode_Table[i].conv_param = req.conv_param;
-	Channelmode_Table[i].free_param = req.free_param;
-	Channelmode_Table[i].dup_struct = req.dup_struct;
-	Channelmode_Table[i].sjoin_check = req.sjoin_check;
-	Channelmode_Table[i].local = req.local;
-	Channelmode_Table[i].unset_with_param = req.unset_with_param;
-	Channelmode_Table[i].owner = module;
-	Channelmode_Table[i].unloaded = 0;
-	
-	for (j = 0; j < EXTCMODETABLESZ; j++)
-		if (Channelmode_Table[j].flag)
-			if (j > Channelmode_highest)
-				Channelmode_highest = j;
-
-        if (Channelmode_Table[i].paracount == 1)
-                extcmode_para_addslot(&Channelmode_Table[i], paraslot);
-                
+	cm->letter = req.letter;
+	cm->type = req.type;
+	cm->prefix = req.prefix;
+	cm->sjoin_prefix = req.sjoin_prefix;
+	cm->rank = req.rank;
+	cm->paracount = req.paracount;
+	cm->is_ok = req.is_ok;
+	cm->put_param = req.put_param;
+	cm->get_param = req.get_param;
+	cm->conv_param = req.conv_param;
+	cm->free_param = req.free_param;
+	cm->dup_struct = req.dup_struct;
+	cm->sjoin_check = req.sjoin_check;
+	cm->local = req.local;
+	cm->unset_with_param = req.unset_with_param;
+	cm->owner = module;
+	cm->unloaded = 0;
+
+	if (cm->type == CMODE_NORMAL)
+	{
+		*mode = cm->mode;
+		if (cm->paracount == 1)
+			extcmode_para_addslot(cm, paraslot);
+	}
+
 	if (module)
 	{
 		ModuleObject *cmodeobj = safe_alloc(sizeof(ModuleObject));
-		cmodeobj->object.cmode = &Channelmode_Table[i];
+		cmodeobj->object.cmode = cm;
 		cmodeobj->type = MOBJ_CMODE;
 		AddListItem(cmodeobj, module->objects);
 		module->errorcode = MODERR_NOERROR;
 	}
-	return &(Channelmode_Table[i]);
+	return cm;
 }
 
 /** Delete a previously registered channel mode - not called by modules.
@@ -304,7 +461,7 @@ void CmodeDel(Cmode *cmode)
 		}
 		cmode->owner = NULL;
 	}
-	if (loop.ircd_rehashing)
+	if (loop.rehashing)
 		cmode->unloaded = 1;
 	else
 		unload_extcmode_commit(cmode);
@@ -322,80 +479,110 @@ static void unload_extcmode_commit(Cmode *cmode)
 	Channel *channel;
 
 	if (!cmode)
-		return;	
+		return;
 
-	/* Unset channel mode and send MODE to everyone */
-
-	if (cmode->paracount == 0)
+	if (cmode->type == CMODE_NORMAL)
 	{
-		/* Paramless mode, easy */
-		for (channel = channels; channel; channel = channel->nextch)
+		/* Unset channel mode and send MODE to everyone */
+		if (cmode->paracount == 0)
 		{
-			if (channel->mode.extmode & cmode->mode)
+			/* Paramless mode, easy */
+			for (channel = channels; channel; channel = channel->nextch)
 			{
-				MessageTag *mtags = NULL;
-
-				new_message(&me, NULL, &mtags);
-				sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
-					       ":%s MODE %s -%c",
-					       me.name, channel->chname, cmode->flag);
-				sendto_server(NULL, 0, 0, mtags,
-					":%s MODE %s -%c 0",
-					me.id, channel->chname, cmode->flag);
-				free_message_tags(mtags);
-
-				channel->mode.extmode &= ~cmode->mode;
+				if (channel->mode.mode & cmode->mode)
+				{
+					MessageTag *mtags = NULL;
+
+					new_message(&me, NULL, &mtags);
+					sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
+						       ":%s MODE %s -%c",
+						       me.name, channel->name, cmode->letter);
+					sendto_server(NULL, 0, 0, mtags,
+						":%s MODE %s -%c 0",
+						me.id, channel->name, cmode->letter);
+					free_message_tags(mtags);
+
+					channel->mode.mode &= ~cmode->mode;
+				}
 			}
+		} else
+		{
+			/* Parameter mode, more complicated */
+			for (channel = channels; channel; channel = channel->nextch)
+			{
+				if (channel->mode.mode & cmode->mode)
+				{
+					MessageTag *mtags = NULL;
+
+					new_message(&me, NULL, &mtags);
+					if (cmode->unset_with_param)
+					{
+						const char *param = cmode->get_param(GETPARASTRUCT(channel, cmode->letter));
+						sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
+							       ":%s MODE %s -%c %s",
+							       me.name, channel->name, cmode->letter, param);
+						sendto_server(NULL, 0, 0, mtags,
+							":%s MODE %s -%c %s 0",
+							me.id, channel->name, cmode->letter, param);
+					} else {
+						sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
+							       ":%s MODE %s -%c",
+							       me.name, channel->name, cmode->letter);
+						sendto_server(NULL, 0, 0, mtags,
+							":%s MODE %s -%c 0",
+							me.id, channel->name, cmode->letter);
+					}
+					free_message_tags(mtags);
+
+					cmode->free_param(GETPARASTRUCT(channel, cmode->letter));
+					channel->mode.mode &= ~cmode->mode;
+				}
+			}
+			extcmode_para_delslot(cmode, cmode->param_slot);
 		}
 	} else
+	if (cmode->type == CMODE_MEMBER)
 	{
-		/* Parameter mode, more complicated */
 		for (channel = channels; channel; channel = channel->nextch)
 		{
-			if (channel->mode.extmode & cmode->mode)
+			Member *m;
+			for (m = channel->members; m; m = m->next)
 			{
-				MessageTag *mtags = NULL;
-
-				new_message(&me, NULL, &mtags);
-				if (cmode->unset_with_param)
+				if (strchr(m->member_modes, cmode->letter))
 				{
-					char *param = cmode->get_param(GETPARASTRUCT(channel, cmode->flag));
+					MessageTag *mtags = NULL;
+
+					new_message(&me, NULL, &mtags);
 					sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
 						       ":%s MODE %s -%c %s",
-						       me.name, channel->chname, cmode->flag, param);
+						       me.name, channel->name, cmode->letter, m->client->name);
 					sendto_server(NULL, 0, 0, mtags,
 						":%s MODE %s -%c %s 0",
-						me.id, channel->chname, cmode->flag, param);
-				} else {
-					sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
-						       ":%s MODE %s -%c",
-						       me.name, channel->chname, cmode->flag);
-					sendto_server(NULL, 0, 0, mtags,
-						":%s MODE %s -%c 0",
-						me.id, channel->chname, cmode->flag);
+						me.id, channel->name, cmode->letter, m->client->id);
+					free_message_tags(mtags);
+					del_member_mode(m->client, channel, cmode->letter);
 				}
-				free_message_tags(mtags);
-
-				cmode->free_param(GETPARASTRUCT(channel, cmode->flag));
-				channel->mode.extmode &= ~cmode->mode;
 			}
 		}
-		extcmode_para_delslot(cmode, cmode->slot);
 	}
 
-	cmode->flag = '\0';
+	DelListItem(cmode, channelmodes);
+	safe_free(cmode);
 }
 
 /** Unload all unused channel modes after a REHASH */
 void unload_all_unused_extcmodes(void)
 {
-	int i;
+	Cmode *cm, *cm_next;
 
-	for (i = 0; i < EXTCMODETABLESZ; i++)
-		if (Channelmode_Table[i].flag && Channelmode_Table[i].unloaded)
+	for (cm=channelmodes; cm; cm = cm_next)
+	{
+		cm_next = cm->next;
+		if (cm->letter && cm->unloaded)
 		{
-			unload_extcmode_commit(&Channelmode_Table[i]);
+			unload_extcmode_commit(cm);
 		}
+	}
 
 }
 
@@ -407,18 +594,29 @@ void unload_all_unused_extcmodes(void)
  * @param channel	The channel
  * @param mode		The mode character (eg: 'f')
  */
-char *cm_getparameter(Channel *channel, char mode)
+const char *cm_getparameter(Channel *channel, char mode)
 {
 	return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCT(channel, mode));
 }
 
+/** Get parameter for a channel mode - special version for SJOIN.
+ * This version doesn't take a channel, but a mode.mode_params.
+ * It is only used by SJOIN and should not be used in 3rd party modules.
+ * @param p	The list, eg oldmode.mode_params
+ * @param mode	The mode letter
+ */
+const char *cm_getparameter_ex(void **p, char mode)
+{
+	return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCTEX(p, mode));
+}
+
 /** Set parameter for a channel mode.
  * @param channel	The channel
  * @param mode		The mode character (eg: 'f')
  * @param str		The parameter string
  * @note Module users should not use this function directly, it is only used by MODE and SJOIN.
  */
-void cm_putparameter(Channel *channel, char mode, char *str)
+void cm_putparameter(Channel *channel, char mode, const char *str)
 {
 	GETPARASTRUCT(channel, mode) = GETPARAMHANDLERBYLETTER(mode)->put_param(GETPARASTRUCT(channel, mode), str);
 }
@@ -433,25 +631,15 @@ void cm_freeparameter(Channel *channel, char mode)
 	GETPARASTRUCT(channel, mode) = NULL;
 }
 
-/** Get parameter for a channel mode - special version for SJOIN.
- * This version doesn't take a channel, but a mode.extmodeparams.
- * It is only used by SJOIN and should not be used in 3rd party modules.
- * @param p	The list, eg oldmode.extmodeparams
- * @param mode	The mode letter
- */
-char *cm_getparameter_ex(void **p, char mode)
-{
-	return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCTEX(p, mode));
-}
 
 /** Set parameter for a channel mode - special version for SJOIN.
- * This version doesn't take a channel, but a mode.extmodeparams.
+ * This version doesn't take a channel, but a mode.mode_params.
  * It is only used by SJOIN and should not be used in 3rd party modules.
- * @param p	The list, eg oldmode.extmodeparams
+ * @param p	The list, eg oldmode.mode_params
  * @param mode	The mode letter
  * @param str	The mode parameter string to set
  */
-void cm_putparameter_ex(void **p, char mode, char *str)
+void cm_putparameter_ex(void **p, char mode, const char *str)
 {
 	GETPARASTRUCTEX(p, mode) = GETPARAMHANDLERBYLETTER(mode)->put_param(GETPARASTRUCTEX(p, mode), str);
 }
@@ -465,9 +653,9 @@ void cm_putparameter_ex(void **p, char mode, char *str)
  * @param what		MODE_ADD / MODE_DEL (???)
  * @returns EX_ALLOW or EX_DENY
  */
-int extcmode_default_requirechop(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+int extcmode_default_requirechop(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
-	if (IsUser(client) && is_chan_op(client, channel))
+	if (IsUser(client) && check_channel_access(client, channel, "oaq"))
 		return EX_ALLOW;
 	if (checkt == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
 		sendnumeric(client, ERR_NOTFORHALFOPS, mode);
@@ -483,9 +671,9 @@ int extcmode_default_requirechop(Client *client, Channel *channel, char mode, ch
  * @param what		MODE_ADD / MODE_DEL (???)
  * @returns EX_ALLOW or EX_DENY
  */
-int extcmode_default_requirehalfop(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+int extcmode_default_requirehalfop(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
-	if (IsUser(client) && (is_chan_op(client, channel) || is_half_op(client, channel)))
+	if (IsUser(client) && (check_channel_access(client, channel, "oaq") || check_channel_access(client, channel, "h")))
 		return EX_ALLOW;
 	return EX_DENY;
 }
@@ -505,10 +693,10 @@ void extcmode_duplicate_paramlist(void **xi, void **xo)
 		handler = CMP_GETHANDLERBYSLOT(i);
 		if (!handler)
 			continue; /* nothing there.. */
-		inx = xi[handler->slot]; /* paramter data of input is here */
+		inx = xi[handler->param_slot]; /* paramter data of input is here */
 		if (!inx)
 			continue; /* not set */
-		xo[handler->slot] = handler->dup_struct(inx); /* call dup_struct with that input and set the output param to that */
+		xo[handler->param_slot] = handler->dup_struct(inx); /* call dup_struct with that input and set the output param to that */
 	}
 }
 
@@ -525,8 +713,8 @@ void extcmode_free_paramlist(void **ar)
 		handler = GETPARAMHANDLERBYSLOT(i);
 		if (!handler)
 			continue; /* nothing here... */
-		handler->free_param(ar[handler->slot]);
-		ar[handler->slot] = NULL;
+		handler->free_param(ar[handler->param_slot]);
+		ar[handler->param_slot] = NULL;
 	}
 }
 
@@ -535,17 +723,450 @@ void extcmode_free_paramlist(void **ar)
 /** Internal function: returns 1 if the specified module has 1 or more extended channel modes registered */
 int module_has_extcmode_param_mode(Module *mod)
 {
-	int i = 0;
+	Cmode *cm;
+
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->letter) && (cm->owner == mod) && (cm->paracount))
+			return 1;
+
+	return 0;
+}
+
+/** Channel member privileges - getting, setting, checking vhoaq status, etc.
+ * These functions get or set the access rights of channel members, such as +vhoaq.
+ * They can also convert between modes (vhoaq), prefixes and sjoin prefixes.
+ * @defgroup ChannelMember Channel members access privileges
+ * @{
+ */
+
+/** Retrieve channel access for a user on a channel, returns modes eg "qao".
+ * @param client	The client
+ * @param channel	The channel
+ * @returns The modes, sorted by high ranking to lower ranking, eg "qao".
+ * An empty string ("") is returned when not in the channel or no modes.
+ */
+const char *get_channel_access(Client *client, Channel *channel)
+{
+	Membership *mb;
 
-	while (i < EXTCMODETABLESZ)
+	mb = find_membership_link(client->user->channel, channel);
+	if (!mb)
+		return "";
+	return mb->member_modes;
+}
+
+/** Check channel access for user.
+ * @param client	The client to check
+ * @param channel	The channel to check
+ * @param modes		Which mode(s) to check for
+ * @returns If the client in channel has any of the modes set, 1 is returned.
+ * Otherwise 0 is returned, which is also the case if the user is
+ * not a user or is not in the channel at all.
+ */
+int check_channel_access(Client *client, Channel *channel, const char *modes)
+{
+	Membership *mb;
+	const char *p;
+
+	if (!IsUser(client))
+		return 0; /* eg server */
+
+	mb = find_membership_link(client->user->channel, channel);
+	if (!mb)
+		return 0; /* not a member */
+
+	for (p = mb->member_modes; *p; p++)
+		if (strchr(modes, *p))
+			return 1; /* match new style */
+
+	return 0; /* nomatch */
+}
+
+/** Check channel access for user.
+ * @param client	The client to check
+ * @param channel	The channel to check
+ * @param modes		Which mode(s) to check for
+ * @returns If the client in channel has any of the modes set, 1 is returned.
+ * Otherwise 0 is returned, which is also the case if the user is
+ * not a user or is not in the channel at all.
+ */
+int check_channel_access_membership(Membership *mb, const char *modes)
+{
+	const char *p;
+
+	if (!mb)
+		return 0;
+
+	for (p = mb->member_modes; *p; p++)
+		if (strchr(modes, *p))
+			return 1; /* match new style */
+
+	return 0; /* nomatch */
+}
+
+/** Check channel access for user.
+ * @param client	The client to check
+ * @param channel	The channel to check
+ * @param modes		Which mode(s) to check for
+ * @returns If the client in channel has any of the modes set, 1 is returned.
+ * Otherwise 0 is returned, which is also the case if the user is
+ * not a user or is not in the channel at all.
+ */
+int check_channel_access_member(Member *mb, const char *modes)
+{
+	const char *p;
+
+	if (!mb)
+		return 0;
+
+	for (p = mb->member_modes; *p; p++)
+		if (strchr(modes, *p))
+			return 1; /* match new style */
+
+	return 0; /* nomatch */
+}
+
+/** Check channel access for user.
+ * @param current	Flags currently set on the client (eg mb->member_modes)
+ * @param modes		Which mode(s) to check for
+ * @returns If the client in channel has any of the modes set, 1 is returned.
+ * Otherwise 0 is returned.
+ */
+int check_channel_access_string(const char *current_modes, const char *modes)
+{
+	const char *p;
+
+	for (p = current_modes; *p; p++)
+		if (strchr(modes, *p))
+			return 1;
+
+	return 0; /* nomatch */
+}
+
+/** Check channel access for user.
+ * @param current	Flags currently set on the client (eg mb->member_modes)
+ * @param letter	Which mode letter to check for
+ * @returns If the client in channel has any of the modes set, 1 is returned.
+ * Otherwise 0 is returned.
+ */
+int check_channel_access_letter(const char *current_modes, const char letter)
+{
+	return strchr(current_modes, letter) ? 1 : 0;
+}
+
+Cmode *find_channel_mode_handler(char letter)
+{
+	Cmode *cm;
+
+	for (cm=channelmodes; cm; cm = cm->next)
+		if (cm->letter == letter)
+			return cm;
+	return NULL;
+}
+
+/** Is 'letter' a valid mode used for access/levels/ranks? (vhoaq and such)
+ * @param letter	The channel mode letter to check, eg 'v'
+ * @returns 1 if valid, 0 if the channel mode does not exist or is not a level mode.
+ */
+int valid_channel_access_mode_letter(char letter)
+{
+	Cmode *cm;
+
+	if ((cm = find_channel_mode_handler(letter)) && (cm->type == CMODE_MEMBER))
+		return 1;
+
+	return 0;
+}
+
+void addlettertomstring(char *str, char letter)
+{
+	Cmode *cm;
+	int n;
+	int my_rank;
+	char *p;
+
+	if (!(cm = find_channel_mode_handler(letter)) || (cm->type != CMODE_MEMBER))
+		return; // should we BUG on this? if something makes it this far, it can never be good right?
+
+	my_rank = cm->rank;
+
+	n = strlen(str);
+	if (n >= MEMBERMODESLEN-1)
+		return; // panic!
+
+	for (p = str; *p; p++)
 	{
-		if ((Channelmode_Table[i].flag) &&
-		    (Channelmode_Table[i].owner == mod) &&
-		    (Channelmode_Table[i].paracount))
+		cm = find_channel_mode_handler(*p);
+		if (!cm)
+			continue; /* wtf */
+		if (cm->rank < my_rank)
 		{
-			return 1;
+			/* We need to insert us here */
+			n = strlen(p);
+			memmove(p+1, p, n+1); // +1 for NUL byte
+			*p = letter;
+			return;
 		}
-		i++;
 	}
-	return 0;
+	/* We should be at the end */
+	str[n] = letter;
+	str[n+1] = '\0';
+}
+
+void add_member_mode_fast(Member *mb, Membership *mbs, char letter)
+{
+	addlettertomstring(mb->member_modes, letter);
+	addlettertomstring(mbs->member_modes, letter);
+}
+
+void del_member_mode_fast(Member *mb, Membership *mbs, char letter)
+{
+	delletterfromstring(mb->member_modes, letter);
+	delletterfromstring(mbs->member_modes, letter);
+}
+
+int find_mbs(Client *client, Channel *channel, Member **mb, Membership **mbs)
+{
+	*mbs = NULL;
+
+	if (!(*mb = find_member_link(channel->members, client)))
+		return 0;
+
+	if (!(*mbs = find_membership_link(client->user->channel, channel)))
+		return 0;
+	
+	return 1;
 }
+
+void add_member_mode(Client *client, Channel *channel, char letter)
+{
+	Member *mb;
+	Membership *mbs;
+
+	if (!find_mbs(client, channel, &mb, &mbs))
+		return;
+
+	add_member_mode_fast(mb, mbs, letter);
+}
+
+void del_member_mode(Client *client, Channel *channel, char letter)
+{
+	Member *mb;
+	Membership *mbs;
+
+	if (!find_mbs(client, channel, &mb, &mbs))
+		return;
+
+	del_member_mode_fast(mb, mbs, letter);
+}
+
+char sjoin_prefix_to_mode(char s)
+{
+	Cmode *cm;
+
+	/* Filter this out early to avoid spurious results */
+	if (s == '\0')
+		return '\0';
+
+	/* First the hardcoded list modes: */
+	if (s == '&')
+		return 'b';
+	if (s == '"')
+		return 'e';
+	if (s == '\'')
+		return 'I';
+
+	/* Now the dynamic ones (+vhoaq): */
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->sjoin_prefix == s) && (cm->type == CMODE_MEMBER))
+			return cm->letter;
+
+	/* Not found */
+	return '\0';
+}
+
+char mode_to_sjoin_prefix(char s)
+{
+	Cmode *cm;
+
+	/* Filter this out early to avoid spurious results */
+	if (s == '\0')
+		return '\0';
+
+	/* First the hardcoded list modes: */
+	if (s == 'b')
+		return '&';
+	if (s == 'e')
+		return '"';
+	if (s == 'I')
+		return '\'';
+
+	/* Now the dynamic ones (+vhoaq): */
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->letter == s) && (cm->type == CMODE_MEMBER))
+			return cm->sjoin_prefix;
+
+	/* Not found */
+	return '\0';
+}
+
+const char *modes_to_sjoin_prefix(const char *modes)
+{
+	static char buf[MEMBERMODESLEN];
+	const char *m;
+	char f;
+
+	*buf = '\0';
+	for (m = modes; *m; m++)
+	{
+		f = mode_to_sjoin_prefix(*m);
+		if (f)
+			strlcat_letter(buf, f, sizeof(buf));
+	}
+
+	return buf;
+}
+
+char mode_to_prefix(char s)
+{
+	Cmode *cm;
+
+	/* Filter this out early to avoid spurious results */
+	if (s == '\0')
+		return '\0';
+
+	/* Now the dynamic ones (+vhoaq): */
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->letter == s) && (cm->type == CMODE_MEMBER))
+			return cm->prefix;
+
+	/* Not found */
+	return '\0';
+}
+
+const char *modes_to_prefix(const char *modes)
+{
+	static char buf[MEMBERMODESLEN];
+	const char *m;
+	char f;
+
+	*buf = '\0';
+	for (m = modes; *m; m++)
+	{
+		f = mode_to_prefix(*m);
+		if (f)
+			strlcat_letter(buf, f, sizeof(buf));
+	}
+
+	return buf;
+}
+
+char prefix_to_mode(char s)
+{
+	Cmode *cm;
+
+	/* Filter this out early to avoid spurious results */
+	if (s == '\0')
+		return '\0';
+
+	/* Now the dynamic ones (+vhoaq): */
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->prefix == s) && (cm->type == CMODE_MEMBER))
+			return cm->letter;
+
+	/* Not found */
+	return '\0';
+}
+
+char rank_to_mode(int rank)
+{
+	Cmode *cm;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->type == CMODE_MEMBER) && (cm->rank == rank))
+			return cm->letter;
+	return '\0';
+}
+
+int mode_to_rank(char mode)
+{
+	Cmode *cm;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->type == CMODE_MEMBER) && (cm->letter == mode))
+			return cm->rank;
+	return '\0';
+}
+
+int prefix_to_rank(char prefix)
+{
+	Cmode *cm;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->type == CMODE_MEMBER) && (cm->prefix == prefix))
+			return cm->rank;
+	return '\0';
+}
+
+char rank_to_prefix(int rank)
+{
+	Cmode *cm;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->type == CMODE_MEMBER) && (cm->rank == rank))
+			return cm->prefix;
+	return '\0';
+}
+
+char lowest_ranking_prefix(const char *prefix)
+{
+	const char *p;
+	int winning_rank = INT_MAX;
+
+	for (p = prefix; *p; p++)
+	{
+		int rank = prefix_to_rank(*p);
+		if (rank < winning_rank)
+			winning_rank = rank;
+	}
+	if (winning_rank == INT_MAX)
+		return '\0'; /* No result */
+	return rank_to_prefix(winning_rank);
+}
+
+char lowest_ranking_mode(const char *mode)
+{
+	const char *p;
+	int winning_rank = INT_MAX;
+
+	for (p = mode; *p; p++)
+	{
+		int rank = mode_to_rank(*p);
+		if (rank < winning_rank)
+			winning_rank = rank;
+	}
+	if (winning_rank == INT_MAX)
+		return '\0'; /* No result */
+	return rank_to_mode(winning_rank);
+}
+
+/** Generate all member modes that are equal or greater than 'modes'.
+ * Eg calling this with "o" would generate "oaq" with the default loaded modules.
+ * This is used in sendto_channel() to make multiple check_channel_access_member()
+ * calls more easy / faster.
+ */
+void channel_member_modes_generate_equal_or_greater(const char *modes, char *buf, size_t buflen)
+{
+	const char *p;
+	int rank;
+	Cmode *cm;
+
+	*buf = '\0';
+
+	/* First we must grab the lowest ranking mode, eg 'vhoaq' results in rank for 'v' */
+	rank = lowest_ranking_mode(modes);
+	if (!rank)
+		return; /* zero matches */
+
+	for (cm=channelmodes; cm; cm = cm->next)
+	if ((cm->type == CMODE_MEMBER) && (cm->rank >= rank))
+		strlcat_letter(buf, cm->letter, buflen);
+}
+
+/** @} */
diff --git a/src/api-clicap.c b/src/api-clicap.c
@@ -79,7 +79,9 @@ long ClientCapabilityBit(const char *token)
 #ifdef DEBUGMODE
 	if (!clicap)
 	{
-		ircd_log(LOG_ERROR, "WARNING: ClientCapabilityBit(): unknown token '%s'", token);
+		unreal_log(ULOG_WARNING, "main", "BUG_CLIENTCAPABILITYBIT_UNKNOWN_TOKEN", NULL,
+		           "[BUG] ClientCapabilityBit() check for unknown token: $token",
+		           log_data_string("token", token));
 	}
 #endif
 
@@ -102,7 +104,7 @@ long clicap_allocate_cap(void)
 	ClientCapability *clicap;
 
 	/* The first bit (v=1) is used by the "invert" marker */
-	for (v=2; v < LONG_MAX; v = v * 2)
+	for (v=2; v; v <<= 1)
 	{
 		unsigned char found = 0;
 		for (clicap = clicaps; clicap; clicap = clicap->next)
@@ -162,8 +164,8 @@ ClientCapability *ClientCapabilityAdd(Module *module, ClientCapabilityInfo *clic
 			v = clicap_allocate_cap();
 			if (v == 0)
 			{
-				sendto_realops("ClientCapabilityAdd: out of space!!!");
-				ircd_log(LOG_ERROR, "ClientCapabilityAdd: out of space!!!");
+				unreal_log(ULOG_ERROR, "module", "CLIENTCAPABILITY_OUT_OF_SPACE", NULL,
+				           "ClientCapabilityAdd: out of space!!!");
 				if (module)
 					module->errorcode = MODERR_NOSPACE;
 				return NULL;
@@ -204,8 +206,9 @@ ClientCapability *ClientCapabilityAdd(Module *module, ClientCapabilityInfo *clic
 void unload_clicap_commit(ClientCapability *clicap)
 {
 	/* This is an unusual operation, I think we should log it. */
-	ircd_log(LOG_ERROR, "Unloading client capability '%s'", clicap->name);
-	sendto_realops("Unloading client capability '%s'", clicap->name);
+	unreal_log(ULOG_INFO, "module", "UNLOAD_CLICAP", NULL,
+	           "Unloading client capability '$token'",
+	           log_data_string("token", clicap->name));
 
 	/* NOTE: Stripping the CAP from local clients is done
 	 * in clicap_post_rehash(), so not here.
@@ -245,7 +248,7 @@ void ClientCapabilityDel(ClientCapability *clicap)
 		clicap->owner = NULL;
 	}
 
-	if (loop.ircd_rehashing)
+	if (loop.rehashing)
 		clicap->unloaded = 1;
 	else
 		unload_clicap_commit(clicap);
@@ -263,7 +266,12 @@ void unload_all_unused_caps(void)
 	}
 }
 
-#define MAXCLICAPS 64
+#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 */
 
@@ -279,7 +287,9 @@ void clicap_pre_rehash(void)
 	{
 		if (i == MAXCLICAPS)
 		{
-			ircd_log(LOG_ERROR, "More than %d caps loaded - what???", MAXCLICAPS);
+			unreal_log(ULOG_ERROR, "module", "BUG_TOO_MANY_CLIENTCAPABILITIES", NULL,
+			           "[BUG] clicap_pre_rehash: More than $count caps loaded - this should never happen",
+			           log_data_integer("count", MAXCLICAPS));
 			break;
 		}
 		safe_strdup(old_caps[i], clicap->name);
@@ -318,13 +328,13 @@ void clicap_post_rehash(void)
 	int i;
 	int found;
 
-	if (!loop.ircd_rehashing)
+	if (!loop.rehashing)
 		return; /* First boot */
 
 	/* Let's deal with CAP DEL first:
 	 * Go through the old caps and see what's missing now.
 	 */
-	for (i = 0; old_caps[i]; i++)
+	for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
 	{
 		name = old_caps[i];
 		found = 0;
@@ -351,7 +361,7 @@ void clicap_post_rehash(void)
 	{
 		name = clicap->name;
 		found = 0;
-		for (i = 0; old_caps[i]; i++)
+		for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
 		{
 			if (!strcmp(old_caps[i], name))
 			{
@@ -368,6 +378,6 @@ void clicap_post_rehash(void)
 	}
 
 	/* Now free the old caps. */
-	for (i = 0; old_caps[i]; i++)
+	for (i = 0; i < MAXCLICAPS && old_caps[i]; i++)
 		safe_free(old_caps[i]);
 }
diff --git a/src/api-command.c b/src/api-command.c
@@ -23,8 +23,8 @@
 #include "unrealircd.h"
 
 /* Forward declarations */
-static Command *CommandAddInternal(Module *module, char *cmd, CmdFunc func, AliasCmdFunc aliasfunc, unsigned char params, int flags);
-static RealCommand *add_Command_backend(char *cmd);
+static Command *CommandAddInternal(Module *module, const char *cmd, CmdFunc func, AliasCmdFunc aliasfunc, unsigned char params, int flags);
+static RealCommand *add_Command_backend(const char *cmd);
 
 /** @defgroup CommandAPI Command API
  * @{
@@ -32,7 +32,7 @@ static RealCommand *add_Command_backend(char *cmd);
 
 /** Returns 1 if the specified command exists
  */
-int CommandExists(char *name)
+int CommandExists(const char *name)
 {
 	RealCommand *p;
 	
@@ -53,7 +53,7 @@ int CommandExists(char *name)
  * @param flags		Who may execute this command - one or more CMD_* flags
  * @returns The newly registered command, or NULL in case of error (eg: already exist)
  */
-Command *CommandAdd(Module *module, char *cmd, CmdFunc func, unsigned char params, int flags)
+Command *CommandAdd(Module *module, const char *cmd, CmdFunc func, unsigned char params, int flags)
 {
 	if (flags & CMD_ALIAS)
 	{
@@ -75,7 +75,7 @@ Command *CommandAdd(Module *module, char *cmd, CmdFunc func, unsigned char param
  * @param flags		Who may execute this command - one or more CMD_* flags
  * @returns The newly registered command (alias), or NULL in case of error (eg: already exist)
  */
-Command *AliasAdd(Module *module, char *cmd, AliasCmdFunc aliasfunc, unsigned char params, int flags)
+Command *AliasAdd(Module *module, const char *cmd, AliasCmdFunc aliasfunc, unsigned char params, int flags)
 {
 	if (!(flags & CMD_ALIAS))
 		flags |= CMD_ALIAS;
@@ -84,7 +84,7 @@ Command *AliasAdd(Module *module, char *cmd, AliasCmdFunc aliasfunc, unsigned ch
 
 /** @} */
 
-static Command *CommandAddInternal(Module *module, char *cmd, CmdFunc func, AliasCmdFunc aliasfunc, unsigned char params, int flags)
+static Command *CommandAddInternal(Module *module, const char *cmd, CmdFunc func, AliasCmdFunc aliasfunc, unsigned char params, int flags)
 {
 	Command *command = NULL;
 	RealCommand *c;
@@ -191,7 +191,7 @@ void CommandDel(Command *command)
  * @note If mtags is NULL then new message tags are created for the command
  *       (and destroyed before return).
  */
-void do_cmd(Client *client, MessageTag *mtags, char *cmd, int parc, char *parv[])
+void do_cmd(Client *client, MessageTag *mtags, const char *cmd, int parc, const char *parv[])
 {
 	RealCommand *cmptr;
 
@@ -233,7 +233,7 @@ void init_CommandHash(void)
 	CommandAdd(NULL, MSG_MODULE, cmd_module, MAXPARA, CMD_USER);
 }
 
-static RealCommand *add_Command_backend(char *cmd)
+static RealCommand *add_Command_backend(const char *cmd)
 {
 	RealCommand *c = safe_alloc(sizeof(RealCommand));
 
@@ -250,7 +250,7 @@ static RealCommand *add_Command_backend(char *cmd)
  */
 
 /** Find a command by name and flags */
-RealCommand *find_command(char *cmd, int flags)
+RealCommand *find_command(const char *cmd, int flags)
 {
 	RealCommand *p;
 	for (p = CommandHash[toupper(*cmd)]; p; p = p->next) {
@@ -269,7 +269,7 @@ RealCommand *find_command(char *cmd, int flags)
 }
 
 /** Find a command by name (no access rights check) */
-RealCommand *find_command_simple(char *cmd)
+RealCommand *find_command_simple(const char *cmd)
 {
 	RealCommand *c;
 
diff --git a/src/api-efunctions.c b/src/api-efunctions.c
@@ -34,47 +34,50 @@ static Efunction *Efunctions[MAXEFUNCTIONS]; /* Efunction objects (used for reha
 static EfunctionsList efunction_table[MAXEFUNCTIONS];
 
 /* Efuncs */
-void (*do_join)(Client *client, int parc, char *parv[]);
-void (*join_channel)(Channel *channel, Client *client, MessageTag *mtags, int flags);
-int (*can_join)(Client *client, Channel *channel, char *key, char *parv[]);
-void (*do_mode)(Channel *channel, Client *client, MessageTag *mtags, int parc, char *parv[], time_t sendts, int samode);
-void (*set_mode)(Channel *channel, Client *client, int parc, char *parv[], u_int *pcount,
-    char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], int bounce);
-void (*cmd_umode)(Client *client, MessageTag *mtags, int parc, char *parv[]);
-int (*register_user)(Client *client, char *nick, char *username, char *umode, char *virthost, char *ip);
+void (*do_join)(Client *client, int parc, const char *parv[]);
+void (*join_channel)(Channel *channel, Client *client, MessageTag *mtags, const char *member_modes);
+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 (*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_type_string)(TKL *tk);
-TKL *(*tkl_add_serverban)(int type, char *usermask, char *hostmask, char *reason, char *setby,
+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);
+TKL *(*tkl_add_serverban)(int type, const char *usermask, const char *hostmask, const char *reason, const char *setby,
                               time_t expire_at, time_t set_at, int soft, int flags);
-TKL *(*tkl_add_nameban)(int type, char *name, int hold, char *reason, char *setby,
+TKL *(*tkl_add_nameban)(int type, const char *name, int hold, const char *reason, const char *setby,
                             time_t expire_at, time_t set_at, int flags);
-TKL *(*tkl_add_spamfilter)(int type, unsigned short target, unsigned short action, Match *match, char *setby,
+TKL *(*tkl_add_spamfilter)(int type, unsigned short target, unsigned short action, Match *match, const char *setby,
                                time_t expire_at, time_t set_at,
-                               time_t spamf_tkl_duration, char *spamf_tkl_reason,
+                               time_t spamf_tkl_duration, const char *spamf_tkl_reason,
                                int flags);
-TKL *(*tkl_add_banexception)(int type, char *usermask, char *hostmask, char *reason, char *set_by,
-                                time_t expire_at, time_t set_at, int soft, char *bantypes, int flags);
+TKL *(*tkl_add_banexception)(int type, const char *usermask, const char *hostmask, const char *reason, const char *set_by,
+                                time_t expire_at, time_t set_at, int soft, const char *bantypes, int flags);
 TKL *(*tkl_del_line)(TKL *tkl);
 void (*tkl_check_local_remove_shun)(TKL *tmp);
 int (*find_tkline_match)(Client *client, int skip_soft);
 int (*find_shun)(Client *client);
 int(*find_spamfilter_user)(Client *client, int flags);
-TKL *(*find_qline)(Client *client, char *nick, int *ishold);
+TKL *(*find_qline)(Client *client, const char *nick, int *ishold);
 TKL *(*find_tkline_match_zap)(Client *client);
-void (*tkl_stats)(Client *client, int type, char *para, int *cnt);
+void (*tkl_stats)(Client *client, int type, const char *para, int *cnt);
 void (*tkl_sync)(Client *client);
-void (*cmd_tkl)(Client *client, MessageTag *mtags, int parc, char *parv[]);
-int (*place_host_ban)(Client *client, BanAction action, char *reason, long duration);
-int (*match_spamfilter)(Client *client, char *str_in, int type, char *cmd, char *target, int flags, TKL **rettk);
-int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, char *cmd);
+void (*cmd_tkl)(Client *client, MessageTag *mtags, int parc, const char *parv[]);
+int (*place_host_ban)(Client *client, BanAction action, const char *reason, long duration);
+int (*match_spamfilter)(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
+int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, const char *cmd);
 int (*join_viruschan)(Client *client, TKL *tk, int type);
-unsigned char *(*StripColors)(unsigned char *text);
-const char *(*StripControlCodes)(unsigned char *text);
-void (*spamfilter_build_user_string)(char *buf, char *nick, Client *client);
+const char *(*StripColors)(const char *text);
+const char *(*StripControlCodes)(const char *text);
+void (*spamfilter_build_user_string)(char *buf, const char *nick, Client *client);
 void (*send_protoctl_servers)(Client *client, int response);
-int (*verify_link)(Client *client, char *servername, ConfigItem_link **link_out);
+int (*verify_link)(Client *client, ConfigItem_link **link_out);
 void (*introduce_user)(Client *to, Client *client);
 void (*send_server_message)(Client *client);
 void (*broadcast_md_client)(ModDataInfo *mdi, Client *client, ModData *md);
@@ -82,37 +85,40 @@ 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, char *software, int protocol, char *flags);
-void (*broadcast_md_client_cmd)(Client *except, Client *sender, Client *acptr, char *varname, char *value);
-void (*broadcast_md_channel_cmd)(Client *except, Client *sender, Channel *channel, char *varname, char *value);
-void (*broadcast_md_member_cmd)(Client *except, Client *sender, Channel *channel, Client *acptr, char *varname, char *value);
-void (*broadcast_md_membership_cmd)(Client *except, Client *sender, Client *acptr, Channel *channel, char *varname, char *value);
+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);
+void (*broadcast_md_member_cmd)(Client *except, Client *sender, Channel *channel, Client *acptr, const char *varname, const char *value);
+void (*broadcast_md_membership_cmd)(Client *except, Client *sender, Client *acptr, Channel *channel, const char *varname, const char *value);
+void (*moddata_add_s2s_mtags)(Client *client, MessageTag **mtags);
+void (*moddata_extract_s2s_mtags)(Client *client, MessageTag *mtags);
 void (*send_moddata_client)(Client *srv, Client *client);
 void (*send_moddata_channel)(Client *srv, Channel *channel);
 void (*send_moddata_members)(Client *srv);
 void (*broadcast_moddata_client)(Client *client);
-int (*match_user)(char *rmask, Client *client, int options);
+int (*match_user)(const char *rmask, Client *client, int options);
 void (*userhost_changed)(Client *client);
 void (*userhost_save_current)(Client *client);
 void (*send_join_to_local_users)(Client *client, Channel *channel, MessageTag *mtags);
 int (*do_nick_name)(char *nick);
 int (*do_remote_nick_name)(char *nick);
-char *(*charsys_get_current_languages)(void);
+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);
 void (*parse_message_tags)(Client *client, char **str, MessageTag **mtag_list);
-char *(*mtags_to_string)(MessageTag *m, Client *client);
-int (*can_send_to_channel)(Client *client, Channel *channel, char **msgtext, char **errmsg, int notice);
+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);
 void (*broadcast_md_globalvar)(ModDataInfo *mdi, ModData *md);
-void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, char *varname, char *value);
-int (*tkl_ip_hash)(char *ip);
+void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, const char *varname, const char *value);
+int (*tkl_ip_hash)(const char *ip);
 int (*tkl_ip_hash_type)(int type);
-void (*sendnotice_tkl_del)(char *removed_by, TKL *tkl);
+void (*sendnotice_tkl_del)(const char *removed_by, TKL *tkl);
 void (*sendnotice_tkl_add)(TKL *tkl);
 void (*free_tkl)(TKL *tkl);
-TKL *(*find_tkl_serverban)(int type, char *usermask, char *hostmask, int softban);
-TKL *(*find_tkl_banexception)(int type, char *usermask, char *hostmask, int softban);
-TKL *(*find_tkl_nameban)(int type, char *name, int hold);
-TKL *(*find_tkl_spamfilter)(int type, char *match_string, unsigned short action, unsigned short target);
+TKL *(*find_tkl_serverban)(int type, const char *usermask, const char *hostmask, int softban);
+TKL *(*find_tkl_banexception)(int type, const char *usermask, const char *hostmask, int softban);
+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 (*is_silenced)(Client *client, Client *acptr);
 int (*del_silence)(Client *client, const char *mask);
@@ -120,9 +126,17 @@ int (*add_silence)(Client *client, const char *mask, int senderr);
 void *(*labeled_response_save_context)(void);
 void (*labeled_response_set_context)(void *ctx);
 void (*labeled_response_force_end)(void);
-void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment);
+void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, const char *comment);
+int (*watch_add)(const char *nick, Client *client, int flags);
+int (*watch_del)(const char *nick, Client *client, int flags);
+int (*watch_del_list)(Client *client, int flags);
+Watch *(*watch_get)(const char *nick);
+int (*watch_check)(Client *client, int reply, int (*watch_notify)(Client *client, Watch *watch, Link *lp, int event));
+void (*do_unreal_log_remote_deliver)(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized);
+char *(*get_chmodes_for_user)(Client *client, const char *flags);
+WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name);
 
-Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*cfunc)())
+Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
 {
 	Efunction *p;
 
@@ -140,8 +154,10 @@ Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(),
 		p->func.voidfunc = vfunc;
 	if (pvfunc)
 		p->func.pvoidfunc = pvfunc;
-	if (cfunc)
-		p->func.pcharfunc = cfunc;
+	if (stringfunc)
+		p->func.stringfunc = stringfunc;
+	if (conststringfunc)
+		p->func.conststringfunc = conststringfunc;
 	p->type = eftype;
 	p->owner = module;
 	AddListItem(p, Efunctions[eftype]);
@@ -256,7 +272,9 @@ void efunctions_switchover(void)
 				continue;
 			if (!efunction_table[i].funcptr)
 			{
-				ircd_log(LOG_ERROR, "[BUG] efunctions_switchover(): someone forgot to initialize the function table for efunc %d", i);
+				unreal_log(ULOG_FATAL, "module", "BUG_EFUNCTIONS_SWITCHOVER", NULL,
+				           "[BUG] efunctions_switchover(): someone forgot to initialize the function table for efunc $efunction_number",
+				           log_data_integer("efunction_number", i));
 				abort();
 			}
 			*efunction_table[i].funcptr = e->func.voidfunc;  /* This is the new one. */
@@ -273,10 +291,19 @@ void efunctions_switchover(void)
 	}
 }
 
-#define efunc_init_function(what, func, default_func) efunc_init_function_(what, #func, (void *)&func, default_func)
+#define efunc_init_function(what, func, default_func) efunc_init_function_(what, #func, (void *)&func, (void *)default_func)
 
 void efunc_init_function_(EfunctionType what, char *name, void *func, void *default_func)
 {
+	if (what >= MAXEFUNCTIONS)
+	{
+		/* increase MAXEFUNCTIONS if you ever encounter that --k4be */
+		unreal_log(ULOG_FATAL, "module", "BUG_EFUNC_INIT_FUNCTION_TOO_MANY", NULL,
+		           "Too many efunctions! ($efunctions_request > $efunctions_max)",
+		           log_data_integer("efunctions_request", what),
+		           log_data_integer("efunctions_max", MAXEFUNCTIONS));
+		abort();
+	}
 	safe_strdup(efunction_table[what].name, name);
 	efunction_table[what].funcptr = func;
 	efunction_table[what].deffunc = default_func;
@@ -290,6 +317,7 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_CAN_JOIN, can_join, NULL);
 	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_CMD_UMODE, cmd_umode, NULL);
 	efunc_init_function(EFUNC_REGISTER_USER, register_user, NULL);
 	efunc_init_function(EFUNC_TKL_HASH, tkl_hash, NULL);
@@ -327,6 +355,8 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_BROADCAST_MD_CHANNEL_CMD, broadcast_md_channel_cmd, NULL);
 	efunc_init_function(EFUNC_BROADCAST_MD_MEMBER_CMD, broadcast_md_member_cmd, NULL);
 	efunc_init_function(EFUNC_BROADCAST_MD_MEMBERSHIP_CMD, broadcast_md_membership_cmd, NULL);
+	efunc_init_function(EFUNC_MODDATA_ADD_S2S_MTAGS, moddata_add_s2s_mtags, NULL);
+	efunc_init_function(EFUNC_MODDATA_EXTRACT_S2S_MTAGS, moddata_extract_s2s_mtags, NULL);
 	efunc_init_function(EFUNC_SEND_MODDATA_CLIENT, send_moddata_client, NULL);
 	efunc_init_function(EFUNC_SEND_MODDATA_CHANNEL, send_moddata_channel, NULL);
 	efunc_init_function(EFUNC_SEND_MODDATA_MEMBERS, send_moddata_members, NULL);
@@ -339,10 +369,12 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_DO_REMOTE_NICK_NAME, do_remote_nick_name, NULL);
 	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_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_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);
 	efunc_init_function(EFUNC_BROADCAST_MD_GLOBALVAR, broadcast_md_globalvar, NULL);
 	efunc_init_function(EFUNC_BROADCAST_MD_GLOBALVAR_CMD, broadcast_md_globalvar_cmd, NULL);
@@ -365,4 +397,13 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_LABELED_RESPONSE_SET_CONTEXT, labeled_response_set_context, labeled_response_set_context_default_handler);
 	efunc_init_function(EFUNC_LABELED_RESPONSE_FORCE_END, labeled_response_force_end, labeled_response_force_end_default_handler);
 	efunc_init_function(EFUNC_KICK_USER, kick_user, NULL);
+	efunc_init_function(EFUNC_WATCH_ADD, watch_add, NULL);
+	efunc_init_function(EFUNC_WATCH_DEL, watch_del, NULL);
+	efunc_init_function(EFUNC_WATCH_DEL_LIST, watch_del_list, NULL);
+	efunc_init_function(EFUNC_WATCH_GET, watch_get, NULL);
+	efunc_init_function(EFUNC_WATCH_CHECK, watch_check, NULL);
+	efunc_init_function(EFUNC_TKL_UHOST, tkl_uhost, NULL);
+	efunc_init_function(EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER, do_unreal_log_remote_deliver, do_unreal_log_remote_deliver_default_handler);
+	efunc_init_function(EFUNC_GET_CHMODES_FOR_USER, get_chmodes_for_user, NULL);
+	efunc_init_function(EFUNC_WHOIS_GET_POLICY, whois_get_policy, NULL);
 }
diff --git a/src/api-event.c b/src/api-event.c
@@ -45,7 +45,7 @@ extern EVENT(unrealdb_expire_secret_cache);
  *        can be later, in case of high load, in very extreme cases even up to 1000 or 2000
  *        msec later but that would be very unusual. Just saying, it's not a guarantee..
  */
-Event *EventAdd(Module *module, char *name, vFP event, void *data, long every_msec, int count)
+Event *EventAdd(Module *module, const char *name, vFP event, void *data, long every_msec, int count)
 {
 	Event *newevent;
 
@@ -56,16 +56,6 @@ Event *EventAdd(Module *module, char *name, vFP event, void *data, long every_ms
 		return NULL;
 	}
 
-	if ((every_msec < 100) && (count == 0))
-	{
-		ircd_log(LOG_ERROR, "[BUG] EventAdd() '%s' from module '%s' with suspiciously low every_msec value (%ld). "
-		                    "Note that it is in milliseconds now (1000 = 1 second)!",
-		                    name,
-		                    module ? module->header->name : "???",
-		                    every_msec);
-		every_msec = 100;
-	}
-
 	newevent = safe_alloc(sizeof(Event));
 	safe_strdup(newevent->name, name);
 	newevent->count = count;
@@ -128,12 +118,16 @@ static void EventDelReal(Event *e)
 {
 	if (!e->deleted)
 	{
-		ircd_log(LOG_ERROR, "EventDelReal called while e->deleted is 0. This cannot happen. Event name: %s.", e->name);
+		unreal_log(ULOG_FATAL, "module", "BUG_EVENTDELREAL_ZERO", NULL,
+		           "[BUG] EventDelReal called while e->deleted is 0. This cannot happen. Event name: $event_name",
+		           log_data_string("event_name", e->name));
 		abort();
 	}
 	if (e->owner)
 	{
-		ircd_log(LOG_ERROR, "EventDelReal called while e->owner is non-NULL. This cannot happen. Event name: %s.", e->name);
+		unreal_log(ULOG_FATAL, "module", "BUG_EVENTDELREAL_NULL", NULL,
+		           "[BUG] EventDelReal called while e->owner is NULL. This cannot happen. Event name: $event_name",
+		           log_data_string("event_name", e->name));
 		abort();
 	}
 	safe_free(e->name);
@@ -153,7 +147,7 @@ static void CleanupEvents(void)
 	}
 }
 
-Event *EventFind(char *name)
+Event *EventFind(const char *name)
 {
 	Event *eventptr;
 
@@ -173,19 +167,7 @@ int EventMod(Event *event, EventInfo *mods)
 	}
 
 	if (mods->flags & EMOD_EVERY)
-	{
-		if (mods->every_msec < 100)
-		{
-			ircd_log(LOG_ERROR, "[BUG] EventMod() for '%s' from module '%s' with suspiciously low every_msec value (%lld). "
-					    "Note that it is in milliseconds now (1000 = 1 second)!",
-					    event->name,
-					    event->owner ? event->owner->header->name : "???",
-					    (long long)mods->every_msec);
-			mods->every_msec = 100;
-		}
-
 		event->every_msec = mods->every_msec;
-	}
 	if (mods->flags & EMOD_HOWMANY)
 		event->count = mods->count;
 	if (mods->flags & EMOD_NAME)
@@ -240,7 +222,7 @@ void SetupEvents(void)
 	EventAdd(NULL, "check_pings", check_pings, NULL, 1000, 0);
 	EventAdd(NULL, "check_deadsockets", check_deadsockets, NULL, 1000, 0);
 	EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0);
-	EventAdd(NULL, "try_connections", try_connections, NULL, 2000, 0);
 	EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0);
 	EventAdd(NULL, "unrealdb_expire_secret_cache", unrealdb_expire_secret_cache, NULL, 61000, 0);
+	EventAdd(NULL, "throttling_check_expire", throttling_check_expire, NULL, 1000, 0);
 }
diff --git a/src/api-extban.c b/src/api-extban.c
@@ -22,144 +22,285 @@
 
 #include "unrealircd.h"
 
-Extban MODVAR ExtBan_Table[EXTBANTABLESZ]; /* this should be fastest */
-unsigned MODVAR short ExtBan_highest = 0;
+/** List of all extbans, their handlers, etc */
+MODVAR Extban *extbans = NULL;
 
 void set_isupport_extban(void)
 {
-	int i;
-	char extbanstr[EXTBANTABLESZ+1], *m;
+	char extbanstr[512];
+	Extban *e;
+	char *p = extbanstr;
+
+	for (e = extbans; e; e = e->next)
+		*p++ = e->letter;
+	*p = '\0';
 
-	m = extbanstr;
-	for (i = 0; i <= ExtBan_highest; i++)
-	{
-		if (ExtBan_Table[i].flag)
-			*m++ = ExtBan_Table[i].flag;
-	}
-	*m = 0;
 	ISupportSetFmt(NULL, "EXTBAN", "~,%s", extbanstr);
 }
 
-Extban *findmod_by_bantype(char c)
+Extban *findmod_by_bantype(const char *str, const char **remainder)
 {
-int i;
+	Extban *e;
+	int ban_name_length;
+	const char *p = strchr(str, ':');
+
+	if (!p || !p[1])
+	{
+		if (remainder)
+			*remainder = NULL;
+		return NULL;
+	}
+	if (remainder)
+		*remainder = p+1;
 
-	for (i=0; i <= ExtBan_highest; i++)
-		if (ExtBan_Table[i].flag == c)
-			return &ExtBan_Table[i];
+	ban_name_length = p - str - 1;
+
+	for (e=extbans; e; e = e->next)
+	{
+		if ((ban_name_length == 1) && (e->letter == str[1]))
+			return e;
+		if (e->name)
+		{
+			int namelen = strlen(e->name);
+			if ((namelen == ban_name_length) && !strncmp(e->name, str+1, namelen))
+				return e;
+		}
+	}
 
 	 return NULL;
 }
 
+/* Check if this is a valid extended ban name */
+int is_valid_extban_name(const char *p)
+{
+	if (!*p)
+		return 0; /* empty name */
+	for (; *p; p++)
+		if (!isalnum(*p) && !strchr("_-", *p))
+			return 0;
+	return 1;
+}
+
+static void extban_add_sorted(Extban *n)
+{
+	Extban *m;
+
+	if (extbans == NULL)
+	{
+		extbans = n;
+		return;
+	}
+
+	for (m = extbans; m; m = m->next)
+	{
+		if (m->letter == '\0')
+			abort();
+		if (sort_character_lowercase_before_uppercase(n->letter, m->letter))
+		{
+			/* Insert us before */
+			if (m->prev)
+				m->prev->next = n;
+			else
+				extbans = n; /* new head */
+			n->prev = m->prev;
+
+			n->next = m;
+			m->prev = n;
+			return;
+		}
+		if (!m->next)
+		{
+			/* Append us at end */
+			m->next = n;
+			n->prev = m;
+			return;
+		}
+	}
+}
+
 Extban *ExtbanAdd(Module *module, ExtbanInfo req)
 {
-	int slot;
+	Extban *e;
+	int existing = 0;
+
+	if (!req.name)
+	{
+		module->errorcode = MODERR_INVALID;
+		unreal_log(ULOG_ERROR, "module", "EXTBANADD_API_ERROR", NULL,
+			   "ExtbanAdd(): name must be specified for ban (new in U6). Module: $module_name",
+			   log_data_string("module_name", module->header->name));
+		return NULL;
+	}
 
-	if (findmod_by_bantype(req.flag))
+	if (!req.is_banned_events && req.is_banned)
 	{
-		if (module)
-			module->errorcode = MODERR_EXISTS;
-		return NULL; 
+		module->errorcode = MODERR_INVALID;
+		unreal_log(ULOG_ERROR, "module", "EXTBANADD_API_ERROR", NULL,
+			   "ExtbanAdd(): module must indicate via .is_banned_events on which BANCHK_* "
+			   "events to listen on (new in U6). Module: $module_name",
+			   log_data_string("module_name", module->header->name));
+		return NULL;
 	}
 
-	/* TODO: perhaps some sanity checking on a-zA-Z0-9? */
-	for (slot = 0; slot < EXTBANTABLESZ; slot++)
-		if (ExtBan_Table[slot].flag == '\0')
-			break;
-	if (slot >= EXTBANTABLESZ - 1)
+	if (!isalnum(req.letter))
 	{
-		if (module)
-			module->errorcode = MODERR_NOSPACE;
+		module->errorcode = MODERR_INVALID;
+		unreal_log(ULOG_ERROR, "module", "EXTBANADD_API_ERROR", NULL,
+		           "ExtbanAdd(): module tried to add extban which is not alphanumeric. "
+		           "Module: $module_name",
+		           log_data_string("module_name", module->header->name));
 		return NULL;
 	}
-	ExtBan_Table[slot].flag = req.flag;
-	ExtBan_Table[slot].is_ok = req.is_ok;
-	ExtBan_Table[slot].conv_param = req.conv_param;
-	ExtBan_Table[slot].is_banned = req.is_banned;
-	ExtBan_Table[slot].owner = module;
-	ExtBan_Table[slot].options = req.options;
+
+	if (!is_valid_extban_name(req.name))
+	{
+		module->errorcode = MODERR_INVALID;
+		unreal_log(ULOG_ERROR, "module", "EXTBANADD_API_ERROR", NULL,
+		           "ExtbanAdd(): module tried to add extban with an invalid name ($extban_name). "
+		           "Module: $module_name",
+		           log_data_string("module_name", module->header->name),
+		           log_data_string("extban_name", req.name));
+		return NULL;
+	}
+
+	for (e=extbans; e; e = e->next)
+	{
+		if (e->letter == req.letter)
+		{
+			if (e->unloaded)
+			{
+				e->unloaded = 0;
+				existing = 1;
+				break;
+			} else {
+				if (module)
+					module->errorcode = MODERR_EXISTS;
+				return NULL;
+			}
+		}
+	}
+
+	if (!e)
+	{
+		/* Not found, create */
+		e = safe_alloc(sizeof(Extban));
+		e->letter = req.letter;
+		extban_add_sorted(e);
+	}
+	e->letter = req.letter;
+	safe_strdup(e->name, req.name);
+	e->is_ok = req.is_ok;
+	e->conv_param = req.conv_param;
+	e->is_banned = req.is_banned;
+	e->is_banned_events = req.is_banned_events;
+	e->owner = module;
+	e->options = req.options;
 	if (module)
 	{
 		ModuleObject *banobj = safe_alloc(sizeof(ModuleObject));
-		banobj->object.extban = &ExtBan_Table[slot];
+		banobj->object.extban = e;
 		banobj->type = MOBJ_EXTBAN;
 		AddListItem(banobj, module->objects);
 		module->errorcode = MODERR_NOERROR;
 	}
-	ExtBan_highest = slot;
 	set_isupport_extban();
-	return &ExtBan_Table[slot];
+	return e;
 }
 
-void ExtbanDel(Extban *eb)
+static void unload_extban_commit(Extban *e)
 {
-	/* Just zero it all away.. */
+	/* Should we mass unban everywhere?
+	 * Hmmm. Not needed per se, user can always unset
+	 * themselves. Leaning towards no atm.
+	 */
+	// noop
 
-	if (eb->owner)
+	/* Then unload the extban */
+	DelListItem(e, extbans);
+	safe_free(e);
+	set_isupport_extban();
+}
+
+void ExtbanDel(Extban *e)
+{
+	/* Always free the module object */
+	if (e->owner)
 	{
 		ModuleObject *banobj;
-		for (banobj = eb->owner->objects; banobj; banobj = banobj->next)
+		for (banobj = e->owner->objects; banobj; banobj = banobj->next)
 		{
-			if (banobj->type == MOBJ_EXTBAN && banobj->object.extban == eb)
+			if (banobj->type == MOBJ_EXTBAN && banobj->object.extban == e)
 			{
-				DelListItem(banobj, eb->owner->objects);
+				DelListItem(banobj, e->owner->objects);
 				safe_free(banobj);
 				break;
 			}
 		}
 	}
-	memset(eb, 0, sizeof(Extban));
-	set_isupport_extban();
-	/* Hmm do we want to go trough all chans and remove the bans?
-	 * I would say 'no' because perhaps we are just reloading,
-	 * and else.. well... screw them?
-	 */
-}
 
-/* NOTE: the routines below can safely assume the ban has at
- * least the '~t:' part (t=type). -- Syzop
- */
+	/* Whether we can actually (already) free the Extban, it depends... */
+	if (loop.rehashing)
+		e->unloaded = 1;
+	else
+		unload_extban_commit(e);
+}
 
 /** General is_ok for n!u@h stuff that also deals with recursive extbans.
  */
-int extban_is_ok_nuh_extban(Client *client, Channel* channel, char *para, int checkt, int what, int what2)
+int extban_is_ok_nuh_extban(BanContext *b)
 {
-	char *mask = (para + 3);
-	Extban *p = NULL;
 	int isok;
 	static int extban_is_ok_recursion = 0;
 
 	/* Mostly copied from clean_ban_mask - but note MyUser checks aren't needed here: extban->is_ok() according to cmd_mode isn't called for nonlocal. */
-	if (is_extended_ban(mask))
+	if (is_extended_ban(b->banstr))
 	{
+		const char *nextbanstr;
+		Extban *extban = NULL;
+
+		/* We're dealing with a stacked extended ban.
+		 * Rules:
+		 * 1) You can only stack once, so: ~x:~y:something and not ~x:~y:~z...
+		 * 2) The second item may never be an action modifier, nor have the
+		 *    EXTBOPT_NOSTACKCHILD letter set (for things like a textban).
+		 */
+
 		if (extban_is_ok_recursion)
-			return 0; /* Fail: more than one stacked extban */
+			return 0; /* Rule #1 violation (more than one stacked extban) */
 
-		if ((checkt == EXBCHK_PARAM) && RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",client,NULL,channel,NULL))
+		if ((b->is_ok_check == EXBCHK_PARAM) && RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,b->channel,NULL))
 		{
 			/* Test if this specific extban has been disabled.
 			 * (We can be sure RESTRICT_EXTENDEDBANS is not *. Else this extended ban wouldn't be happening at all.)
 			 */
-			if (strchr(RESTRICT_EXTENDEDBANS, mask[1]))
+			if (strchr(RESTRICT_EXTENDEDBANS, b->banstr[1]))
 			{
-				sendnotice(client, "Setting/removing of extended bantypes '%s' has been disabled.", RESTRICT_EXTENDEDBANS);
+				sendnotice(b->client, "Setting/removing of extended bantypes '%s' has been disabled.", RESTRICT_EXTENDEDBANS);
 				return 0; /* Fail */
 			}
 		}
-		p = findmod_by_bantype(mask[1]);
-		if (!p)
+		extban = findmod_by_bantype(b->banstr, &nextbanstr);
+		if (!extban)
 		{
-			if (what == MODE_DEL)
+			if (b->what == MODE_DEL)
 			{
 				return 1; /* Always allow killing unknowns. */
 			}
 			return 0; /* Don't add unknown extbans. */
 		}
+
+		if ((extban->options & EXTBOPT_ACTMODIFIER) || (extban->options & EXTBOPT_NOSTACKCHILD))
+		{
+			/* Rule #2 violation */
+			return 0;
+		}
+
 		/* Now we have to ask the stacked extban if it's ok. */
-		if (p->is_ok)
+		if (extban->is_ok)
 		{
+			b->banstr = nextbanstr;
 			extban_is_ok_recursion++;
-			isok = p->is_ok(client, channel, mask, checkt, what, what2);
+			isok = extban->is_ok(b);
 			extban_is_ok_recursion--;
 			return isok;
 		}
@@ -171,19 +312,15 @@ int extban_is_ok_nuh_extban(Client *client, Channel* channel, char *para, int ch
  * to ensure the parameter is nick!user@host.
  * most of the code is just copied from clean_ban_mask.
  */
-char *extban_conv_param_nuh(char *para)
+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];
-	char pfix[8];
-
-	if (strlen(para)<3)
-		return NULL; /* normally impossible */
 
-	strlcpy(tmpbuf, para, sizeof(retbuf));
-	mask = tmpbuf + 3;
-	strlcpy(pfix, tmpbuf, mask - tmpbuf + 1);
+	/* Work on a copy */
+	strlcpy(tmpbuf, b->banstr, sizeof(retbuf));
+	mask = tmpbuf;
 
 	if (!*mask)
 		return NULL; /* empty extban */
@@ -202,13 +339,13 @@ char *extban_conv_param_nuh(char *para)
 	if (!ret)
 		ret = make_nick_user_host(trim_str(cp,NICKLEN), trim_str(user,USERLEN), trim_str(host,HOSTLEN));
 
-	ircsnprintf(retbuf, sizeof(retbuf), "%s%s", pfix, ret);
+	strlcpy(retbuf, ret, sizeof(retbuf));
 	return retbuf;
 }
 
 /** conv_param to deal with stacked extbans.
  */
-char *extban_conv_param_nuh_or_extban(char *para)
+const char *extban_conv_param_nuh_or_extban(BanContext *b, Extban *self_extban)
 {
 #if (USERLEN + NICKLEN + HOSTLEN + 32) > 256
  #error "wtf?"
@@ -217,83 +354,77 @@ char *extban_conv_param_nuh_or_extban(char *para)
 	static char printbuf[256];
 	char *mask;
 	char tmpbuf[USERLEN + NICKLEN + HOSTLEN + 32];
-	char bantype = para[1];
-	char *ret = NULL;
-	Extban *p = NULL;
+	const char *ret = NULL;
+	const char *nextbanstr;
+	Extban *extban = NULL;
 	static int extban_recursion = 0;
 
-	if ((strlen(para)>3) && is_extended_ban(para+3))
+	if (!is_extended_ban(b->banstr))
+		return extban_conv_param_nuh(b, self_extban);
+
+	/* We're dealing with a stacked extended ban.
+	 * Rules:
+	 * 1) You can only stack once, so: ~x:~y:something and not ~x:~y:~z...
+	 * 2) The second item may never be an action modifier, nor have the
+	 *    EXTBOPT_NOSTACKCHILD letter set (for things like a textban).
+	 */
+	 
+	/* Rule #1. Yes the recursion check is also in extban_is_ok_nuh_extban,
+	 * but it's possible to get here without the is_ok() function ever
+	 * being called (think: non-local client). And no, don't delete it
+	 * there either. It needs to be in BOTH places. -- Syzop
+	 */
+	if (extban_recursion)
+		return NULL;
+
+	strlcpy(tmpbuf, b->banstr, sizeof(tmpbuf));
+	extban = findmod_by_bantype(tmpbuf, &nextbanstr);
+
+	if (!extban)
 	{
-		/* We're dealing with a stacked extended ban.
-		 * Rules:
-		 * 1) You can only stack once, so: ~x:~y:something and not ~x:~y:~z...
-		 * 2) The first item must be an action modifier, such as ~q/~n/~j
-		 * 3) The second item may never be an action modifier, nor have the
-		 *    EXTBOPT_NOSTACKCHILD flag set (for things like a textban).
-		 */
-		 
-		/* Rule #1. Yes the recursion check is also in extban_is_ok_nuh_extban,
-		 * but it's possible to get here without the is_ok() function ever
-		 * being called (think: non-local client). And no, don't delete it
-		 * there either. It needs to be in BOTH places. -- Syzop
-		 */
-		if (extban_recursion)
-			return NULL;
+		/* Handling unknown bantypes in is_ok. Assume that it's ok here. */
+		return b->banstr;
+	}
 
-		/* Rule #2 */
-		p = findmod_by_bantype(para[1]);
-		if (p && !(p->options & EXTBOPT_ACTMODIFIER))
-		{
-			/* Rule #2 violation */
-			return NULL;
-		}
-		
-		strlcpy(tmpbuf, para, sizeof(tmpbuf));
-		mask = tmpbuf + 3;
-		/* Already did restrict-extended bans check. */
-		p = findmod_by_bantype(mask[1]);
-		if (!p)
-		{
-			/* Handling unknown bantypes in is_ok. Assume that it's ok here. */
-			return para;
-		}
-		if ((p->options & EXTBOPT_ACTMODIFIER) || (p->options & EXTBOPT_NOSTACKCHILD))
-		{
-			/* Rule #3 violation */
-			return NULL;
-		}
-		
-		if (p->conv_param)
-		{
-			extban_recursion++;
-			ret = p->conv_param(mask);
-			extban_recursion--;
-			if (ret)
-			{
-				/*
-				 * If bans are stacked, then we have to use two buffers
-				 * to prevent ircsnprintf() from going into a loop.
-				 */
-				ircsnprintf(printbuf, sizeof(printbuf), "~%c:%s", bantype, ret); /* Make sure our extban prefix sticks. */
-				memcpy(retbuf, printbuf, sizeof(retbuf));
-				return retbuf;
-			}
-			else
-			{
-				return NULL; /* Fail. */
-			}
-		}
-		/* 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(para) > 80)
-		{
-			strlcpy(retbuf, para, 128);
-			return retbuf;
-		}
-		return para;
+	b->banstr = nextbanstr;
+
+	if ((extban->options & EXTBOPT_ACTMODIFIER) || (extban->options & EXTBOPT_NOSTACKCHILD))
+	{
+		/* Rule #2 violation */
+		return NULL;
 	}
-	else
+
+	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)
 	{
-		return extban_conv_param_nuh(para);
+		strlcpy(retbuf, b->banstr, 128);
+		return retbuf;
 	}
+	return b->banstr;
+}
+
+char *prefix_with_extban(const char *remainder, BanContext *b, Extban *extban, char *buf, size_t buflen)
+{
+	/* Yes, we support this because it makes code at the caller cleaner */
+	if (remainder == NULL)
+		return NULL;
+
+	if (iConf.named_extended_bans && !(b->conv_options & BCTX_CONV_OPTION_WRITE_LETTER_BANS))
+		snprintf(buf, buflen, "~%s:%s", extban->name, remainder);
+	else
+		snprintf(buf, buflen, "~%c:%s", extban->letter, remainder);
+
+	return buf;
 }
diff --git a/src/api-history-backend.c b/src/api-history-backend.c
@@ -60,13 +60,15 @@ HistoryBackend *HistoryBackendAdd(Module *module, HistoryBackendInfo *mreq)
 {
 	HistoryBackend *m;
 	int exists = 0;
+	ModuleObject *mobj;
 
 	if (!mreq->history_add || !mreq->history_request ||
 	    !mreq->history_destroy || !mreq->history_set_limit)
 	{
-		if (module)
-			module->errorcode = MODERR_INVALID;
-		ircd_log(LOG_ERROR, "HistoryBackendAdd(): missing a handler for add/del/request/destroy/set_limit");
+		module->errorcode = MODERR_INVALID;
+		unreal_log(ULOG_ERROR, "module", "HISTORYBACKENDADD_API_ERROR", NULL,
+			   "HistoryBackendAdd(): missing a handler for add/del/request/destroy/set_limit. Module: $module_name",
+			   log_data_string("module_name", module->header->name));
 		return NULL;
 	}
 	m = HistoryBackendFind(mreq->name);
@@ -77,8 +79,7 @@ HistoryBackend *HistoryBackendAdd(Module *module, HistoryBackendInfo *mreq)
 		{
 			m->unloaded = 0;
 		} else {
-			if (module)
-				module->errorcode = MODERR_EXISTS;
+			module->errorcode = MODERR_EXISTS;
 			return NULL;
 		}
 	} else {
@@ -97,14 +98,11 @@ HistoryBackend *HistoryBackendAdd(Module *module, HistoryBackendInfo *mreq)
 	if (!exists)
 		AddListItem(m, historybackends);
 
-	if (module)
-	{
-		ModuleObject *mobj = safe_alloc(sizeof(ModuleObject));
-		mobj->type = MOBJ_HISTORY_BACKEND;
-		mobj->object.history_backend = m;
-		AddListItem(mobj, module->objects);
-		module->errorcode = MODERR_NOERROR;
-	}
+	mobj = safe_alloc(sizeof(ModuleObject));
+	mobj->type = MOBJ_HISTORY_BACKEND;
+	mobj->object.history_backend = m;
+	AddListItem(mobj, module->objects);
+	module->errorcode = MODERR_NOERROR;
 
 	return m;
 }
@@ -138,7 +136,7 @@ void HistoryBackendDel(HistoryBackend *m)
 		m->owner = NULL;
 	}
 
-	if (loop.ircd_rehashing)
+	if (loop.rehashing)
 		m->unloaded = 1;
 	else
 		unload_history_backend_commit(m);
@@ -156,7 +154,7 @@ void unload_all_unused_history_backends(void)
 	}
 }
 
-int history_add(char *object, MessageTag *mtags, char *line)
+int history_add(const char *object, MessageTag *mtags, const char *line)
 {
 	HistoryBackend *hb;
 
@@ -166,7 +164,7 @@ int history_add(char *object, MessageTag *mtags, char *line)
 	return 1;
 }
 
-HistoryResult *history_request(char *object, HistoryFilter *filter)
+HistoryResult *history_request(const char *object, HistoryFilter *filter)
 {
 	HistoryBackend *hb = historybackends;
 	HistoryResult *r;
@@ -183,7 +181,7 @@ HistoryResult *history_request(char *object, HistoryFilter *filter)
 	return NULL;
 }
 
-int history_destroy(char *object)
+int history_destroy(const char *object)
 {
 	HistoryBackend *hb;
 
@@ -193,7 +191,7 @@ int history_destroy(char *object)
 	return 1;
 }
 
-int history_set_limit(char *object, int max_lines, long max_t)
+int history_set_limit(const char *object, int max_lines, long max_t)
 {
 	HistoryBackend *hb;
 
@@ -230,7 +228,7 @@ int can_receive_history(Client *client)
 	return 0;
 }
 
-static void history_send_result_line(Client *client, HistoryLogLine *l, char *batchid)
+static void history_send_result_line(Client *client, HistoryLogLine *l, const char *batchid)
 {
 	if (BadPtr(batchid))
 	{
@@ -238,9 +236,10 @@ static void history_send_result_line(Client *client, HistoryLogLine *l, char *ba
 	} else {
 		MessageTag *m = safe_alloc(sizeof(MessageTag));
 		m->name = "batch";
-		m->value = batchid;
+		m->value = strdup(batchid);
 		AddListItem(m, l->mtags);
 		sendto_one(client, l->mtags, "%s", l->line);
+		safe_free(m->value);
 		DelListItem(m, l->mtags);
 		safe_free(m);
 	}
diff --git a/src/api-isupport.c b/src/api-isupport.c
@@ -86,18 +86,12 @@ void isupport_init(void)
 {
 	ISupportSet(NULL, "INVEX", NULL);
 	ISupportSet(NULL, "EXCEPTS", NULL);
-#ifdef PREFIX_AQ
-	ISupportSet(NULL, "STATUSMSG", "~&@%+");
-#else
-	ISupportSet(NULL, "STATUSMSG", "@%+");
-#endif
 	ISupportSet(NULL, "ELIST", "MNUCT");
 	ISupportSet(NULL, "CASEMAPPING", "ascii");
-	ISupportSet(NULL, "NETWORK", ircnet005);
+	ISupportSet(NULL, "NETWORK", NETWORK_NAME_005);
 	ISupportSetFmt(NULL, "CHANMODES",
-	               CHPAR1 "%s," CHPAR2 "%s," CHPAR3 "%s," CHPAR4 "%s",
+	               CHPAR1 "%s,%s,%s,%s",
 	               EXPAR1, EXPAR2, EXPAR3, EXPAR4);
-	ISupportSet(NULL, "PREFIX", CHPFIX);
 	ISupportSet(NULL, "CHANTYPES", "#");
 	ISupportSetFmt(NULL, "MODES", "%d", MAXMODEPARAMS);
 	ISupportSetFmt(NULL, "SILENCE", "%d", SILENCE_LIMIT);
@@ -118,7 +112,6 @@ void isupport_init(void)
 	ISupportSetFmt(NULL, "MAXLIST", "b:%d,e:%d,I:%d", MAXBANS, MAXBANS, MAXBANS);
 	ISupportSetFmt(NULL, "CHANLIMIT", "#:%d", MAXCHANNELSPERUSER);
 	ISupportSetFmt(NULL, "MAXCHANNELS", "%d", MAXCHANNELSPERUSER);
-	ISupportSet(NULL, "HCN", NULL);
 	ISupportSet(NULL, "SAFELIST", NULL);
 	ISupportSet(NULL, "NAMESX", NULL);
 	if (UHNAMES_ENABLED)
diff --git a/src/api-messagetag.c b/src/api-messagetag.c
@@ -52,15 +52,21 @@ MessageTagHandler *MessageTagHandlerAdd(Module *module, MessageTagHandlerInfo *m
 	/* Some consistency checks to avoid a headache for module devs later on: */
 	if ((mreq->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED) && mreq->clicap_handler)
 	{
-		ircd_log(LOG_ERROR, "MessageTagHandlerAdd(): .flags is set to MTAG_HANDLER_FLAGS_NO_CAP_NEEDED "
-		                    "but a .clicap_handler is passed as well. These options are mutually "
-		                    "exclusive, choose one or the other.");
+		unreal_log(ULOG_ERROR, "module", "MESSAGETAGHANDLERADD_API_ERROR", NULL,
+			   "MessageTagHandlerAdd() from module $module_name: "
+			   ".flags is set to MTAG_HANDLER_FLAGS_NO_CAP_NEEDED "
+			   "but a .clicap_handler is passed as well. These options are mutually "
+			   "exclusive, choose one or the other.",
+			   log_data_string("module_name", module->header->name));
 		abort();
 	} else if (!(mreq->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED) && !mreq->clicap_handler)
 	{
-		ircd_log(LOG_ERROR, "MessageTagHandlerAdd(): no .clicap_handler is passed. If the "
-		                    "message tag really does not require a cap then you must "
-		                    "set .flags to MTAG_HANDLER_FLAGS_NO_CAP_NEEDED");
+		unreal_log(ULOG_ERROR, "module", "MESSAGETAGHANDLERADD_API_ERROR", NULL,
+			   "MessageTagHandlerAdd() from module $module_name: "
+			   "no .clicap_handler is passed. If the "
+		           "message tag really does not require a cap then you must "
+		           "set .flags to MTAG_HANDLER_FLAGS_NO_CAP_NEEDED",
+		           log_data_string("module_name", module->header->name));
 		abort();
 	}
 
@@ -85,7 +91,7 @@ MessageTagHandler *MessageTagHandlerAdd(Module *module, MessageTagHandlerInfo *m
 	m->owner = module;
 	m->flags = mreq->flags;
 	m->is_ok = mreq->is_ok;
-	m->can_send = mreq->can_send;
+	m->should_send_to_client = mreq->should_send_to_client;
 	m->clicap_handler = mreq->clicap_handler;
 
 	/* Update reverse dependency (if any) */
@@ -141,7 +147,7 @@ void MessageTagHandlerDel(MessageTagHandler *m)
 		m->owner = NULL;
 	}
 
-	if (loop.ircd_rehashing)
+	if (loop.rehashing)
 		m->unloaded = 1;
 	else
 		unload_mtag_handler_commit(m);
@@ -152,8 +158,9 @@ void MessageTagHandlerDel(MessageTagHandler *m)
 static void unload_mtag_handler_commit(MessageTagHandler *m)
 {
 	/* This is an unusual operation, I think we should log it. */
-	ircd_log(LOG_ERROR, "Unloading message-tag handler for '%s'", m->name);
-	sendto_realops("Unloading message-tag handler for '%s'", m->name);
+	unreal_log(ULOG_INFO, "module", "UNLOAD_MESSAGE_TAG", NULL,
+	           "Unloading message-tag handler for '$token'",
+	           log_data_string("token", m->name));
 
 	/* Remove reverse dependency, if any */
 	if (m->clicap_handler)
diff --git a/src/api-moddata.c b/src/api-moddata.c
@@ -65,6 +65,8 @@ ModDataInfo *ModDataAdd(Module *module, ModDataInfo req)
 	    ((req.type == MODDATATYPE_MEMBER) && (slotav >= MODDATA_MAX_MEMBER)) ||
 	    ((req.type == MODDATATYPE_MEMBERSHIP) && (slotav >= MODDATA_MAX_MEMBERSHIP)))
 	{
+		unreal_log(ULOG_ERROR, "module", "MOD_DATA_OUT_OF_SPACE", NULL,
+		           "ModDataAdd: out of space!!!");
 		if (module)
 			module->errorcode = MODERR_NOSPACE;
 		return NULL;
@@ -80,6 +82,8 @@ moddataadd_isok:
 	m->serialize = req.serialize;
 	m->unserialize = req.unserialize;
 	m->sync = req.sync;
+	m->remote_write = req.remote_write;
+	m->self_write = req.self_write;
 	m->owner = module;
 	
 	if (new_struct)
@@ -272,7 +276,7 @@ void ModDataDel(ModDataInfo *md)
 		md->owner = NULL;
 	}
 
-	if (loop.ircd_rehashing)
+	if (loop.rehashing)
 		md->unloaded = 1;
 	else
 		unload_moddata_commit(md);
@@ -290,7 +294,7 @@ ModDataInfo *md, *md_next;
 	}
 }
 
-ModDataInfo *findmoddata_byname(char *name, ModDataType type)
+ModDataInfo *findmoddata_byname(const char *name, ModDataType type)
 {
 ModDataInfo *md;
 
@@ -313,7 +317,7 @@ int module_has_moddata(Module *mod)
 }
 
 /** Set ModData for client (via variable name, string value) */
-int moddata_client_set(Client *client, char *varname, char *value)
+int moddata_client_set(Client *client, const char *varname, const char *value)
 {
 	ModDataInfo *md;
 
@@ -344,7 +348,7 @@ int moddata_client_set(Client *client, char *varname, char *value)
 }
 
 /** Get ModData for client (via variable name) */
-char *moddata_client_get(Client *client, char *varname)
+const char *moddata_client_get(Client *client, const char *varname)
 {
 	ModDataInfo *md;
 
@@ -356,8 +360,21 @@ char *moddata_client_get(Client *client, char *varname)
 	return md->serialize(&moddata_client(client, md)); /* can be NULL */
 }
 
+/** Get ModData for client (via variable name) */
+ModData *moddata_client_get_raw(Client *client, const char *varname)
+{
+	ModDataInfo *md;
+
+	md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
+
+	if (!md)
+		return NULL;
+
+	return &moddata_client(client, md); /* can be NULL */
+}
+
 /** Set ModData for LocalClient (via variable name, string value) */
-int moddata_local_client_set(Client *client, char *varname, char *value)
+int moddata_local_client_set(Client *client, const char *varname, const char *value)
 {
 	ModDataInfo *md;
 
@@ -391,7 +408,7 @@ int moddata_local_client_set(Client *client, char *varname, char *value)
 }
 
 /** Get ModData for LocalClient (via variable name) */
-char *moddata_local_client_get(Client *client, char *varname)
+const char *moddata_local_client_get(Client *client, const char *varname)
 {
 	ModDataInfo *md;
 
@@ -407,7 +424,7 @@ char *moddata_local_client_get(Client *client, char *varname)
 }
 
 /** Set local variable moddata (via variable name, string value) */
-int moddata_local_variable_set(char *varname, char *value)
+int moddata_local_variable_set(const char *varname, const char *value)
 {
 	ModDataInfo *md;
 
@@ -432,7 +449,7 @@ int moddata_local_variable_set(char *varname, char *value)
 }
 
 /** Set global variable moddata (via variable name, string value) */
-int moddata_global_variable_set(char *varname, char *value)
+int moddata_global_variable_set(const char *varname, const char *value)
 {
 	ModDataInfo *md;
 
diff --git a/src/api-usermode.c b/src/api-usermode.c
@@ -24,16 +24,11 @@
 
 char umodestring[UMODETABLESZ+1];
 
-Umode *Usermode_Table = NULL;
-short	 Usermode_highest = 0;
+/** User modes and their handlers */
+Umode *usermodes = NULL;
 
-Snomask *Snomask_Table = NULL;
-short	 Snomask_highest = 0;
-
-/* client->umodes (32 bits): 26 used, 6 free */
 long UMODE_INVISIBLE = 0L;     /* makes user invisible */
 long UMODE_OPER = 0L;          /* Operator */
-long UMODE_WALLOP = 0L;        /* send wallops to them */
 long UMODE_REGNICK = 0L;       /* Nick set by services as registered */
 long UMODE_SERVNOTICE = 0L;    /* server notices such as kill */
 long UMODE_HIDE = 0L;          /* Hide from Nukes */
@@ -63,32 +58,13 @@ long SendUmodes;	/* All umodes which are sent to other servers (global umodes) *
 
 /* Forward declarations */
 int umode_hidle_allow(Client *client, int what);
+static void unload_usermode_commit(Umode *m);
 
-void	umode_init(void)
+void umode_init(void)
 {
-	long val = 1;
-	int	i;
-	Usermode_Table = safe_alloc(sizeof(Umode) * UMODETABLESZ);
-	for (i = 0; i < UMODETABLESZ; i++)
-	{
-		Usermode_Table[i].mode = val;
-		val *= 2;
-	}
-	Usermode_highest = 0;
-
-	Snomask_Table = safe_alloc(sizeof(Snomask) * UMODETABLESZ);
-	val = 1;
-	for (i = 0; i < UMODETABLESZ; i++)
-	{
-		Snomask_Table[i].mode = val;
-		val *= 2;
-	}
-	Snomask_highest = 0;
-
-	/* Set up modes */
+	/* Some built-in modes */
 	UmodeAdd(NULL, 'i', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_INVISIBLE);
 	UmodeAdd(NULL, 'o', UMODE_GLOBAL, 1, umode_allow_opers, &UMODE_OPER);
-	UmodeAdd(NULL, 'w', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_WALLOP);
 	UmodeAdd(NULL, 'r', UMODE_GLOBAL, 0, umode_allow_none, &UMODE_REGNICK);
 	UmodeAdd(NULL, 's', UMODE_LOCAL, 0, umode_allow_all, &UMODE_SERVNOTICE);
 	UmodeAdd(NULL, 'x', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_HIDE);
@@ -97,34 +73,17 @@ void	umode_init(void)
 	UmodeAdd(NULL, 'H', UMODE_GLOBAL, 1, umode_allow_opers, &UMODE_HIDEOPER);
 	UmodeAdd(NULL, 't', UMODE_GLOBAL, 0, umode_allow_unset, &UMODE_SETHOST);
 	UmodeAdd(NULL, 'I', UMODE_GLOBAL, 0, umode_hidle_allow, &UMODE_HIDLE);
-	SnomaskAdd(NULL, 'k', umode_allow_opers, &SNO_KILLS);
-	SnomaskAdd(NULL, 'c', umode_allow_opers, &SNO_CLIENT);
-	SnomaskAdd(NULL, 'f', umode_allow_opers, &SNO_FLOOD);
-	SnomaskAdd(NULL, 'F', umode_allow_opers, &SNO_FCLIENT);
-	SnomaskAdd(NULL, 'j', umode_allow_opers, &SNO_JUNK);
-	SnomaskAdd(NULL, 'v', umode_allow_opers, &SNO_VHOST);
-	SnomaskAdd(NULL, 'e', umode_allow_opers, &SNO_EYES);
-	SnomaskAdd(NULL, 'G', umode_allow_opers, &SNO_TKL);
-	SnomaskAdd(NULL, 'n', umode_allow_opers, &SNO_NICKCHANGE);
-	SnomaskAdd(NULL, 'N', umode_allow_opers, &SNO_FNICKCHANGE);
-	SnomaskAdd(NULL, 'q', umode_allow_opers, &SNO_QLINE);
-	SnomaskAdd(NULL, 'S', umode_allow_opers, &SNO_SPAMF);
-	SnomaskAdd(NULL, 's', umode_allow_opers, &SNO_SNOTICE);
-	SnomaskAdd(NULL, 'o', umode_allow_opers, &SNO_OPER);
 }
 
 void make_umodestr(void)
 {
-	int i;
-	char *m;
+	Umode *um;
+	char *p = umodestring;
 
-	m = umodestring;
-	for (i = 0; i <= Usermode_highest; i++)
-	{
-		if (Usermode_Table[i].flag)
-			*m++ = Usermode_Table[i].flag;
-	}
-	*m = '\0';
+	for (um=usermodes; um; um = um->next)
+		if (um->letter)
+			*p++ = um->letter;
+	*p = '\0';
 }
 
 static char previous_umodestring[256];
@@ -132,7 +91,7 @@ static char previous_umodestring[256];
 void umodes_check_for_changes(void)
 {
 	make_umodestr();
-	safe_strdup(me.serv->features.usermodes, umodestring);
+	safe_strdup(me.server->features.usermodes, umodestring);
 
 	if (!*previous_umodestring)
 	{
@@ -142,10 +101,10 @@ void umodes_check_for_changes(void)
 
 	if (*previous_umodestring && strcmp(umodestring, previous_umodestring))
 	{
-		ircd_log(LOG_ERROR, "User modes changed at runtime: %s -> %s",
-			previous_umodestring, umodestring);
-		sendto_realops("User modes changed at runtime: %s -> %s",
-			previous_umodestring, umodestring);
+		unreal_log(ULOG_INFO, "mode", "USER_MODES_CHANGED", NULL,
+		           "User modes changed at runtime: $old_user_modes -> $new_user_modes",
+		           log_data_string("old_user_modes", previous_umodestring),
+		           log_data_string("new_user_modes", umodestring));
 		/* Broadcast change to all (locally connected) servers */
 		sendto_server(NULL, 0, 0, NULL, "PROTOCTL USERMODES=%s", umodestring);
 	}
@@ -153,203 +112,148 @@ void umodes_check_for_changes(void)
 	strlcpy(previous_umodestring, umodestring, sizeof(previous_umodestring));
 }
 
-/* UmodeAdd:
- * Add a usermode with character 'ch', if global is set to 1 the usermode is global
- * (sent to other servers) otherwise it's a local usermode
- */
-Umode *UmodeAdd(Module *module, char ch, int global, int unset_on_deoper, int (*allowed)(Client *client, int what), long *mode)
+void usermode_add_sorted(Umode *n)
 {
-	short	 i = 0;
-	short	 j = 0;
-	short 	 save = -1;
-	while (i < UMODETABLESZ)
+	Umode *m;
+
+	if (usermodes == NULL)
 	{
-		if (!Usermode_Table[i].flag && save == -1)
-			save = i;
-		else if (Usermode_Table[i].flag == ch)
+		usermodes = n;
+		return;
+	}
+
+	for (m = usermodes; m; m = m->next)
+	{
+		if (m->letter == '\0')
+			abort();
+		if (sort_character_lowercase_before_uppercase(n->letter, m->letter))
 		{
-			if (Usermode_Table[i].unloaded)
-			{
-				save = i;
-				Usermode_Table[i].unloaded = 0;
-				break;
-			}
+			/* Insert us before */
+			if (m->prev)
+				m->prev->next = n;
 			else
-			{
-				if (module)
-					module->errorcode = MODERR_EXISTS;
-				return NULL;
-			}
+				usermodes = n; /* new head */
+			n->prev = m->prev;
+
+			n->next = m;
+			m->prev = n;
+			return;
 		}
-		i++;
-	}
-	i = save;
-	if (i != UMODETABLESZ)
-	{
-		Usermode_Table[i].flag = ch;
-		Usermode_Table[i].allowed = allowed;
-		Usermode_Table[i].unset_on_deoper = unset_on_deoper;
-		Debug((DEBUG_DEBUG, "UmodeAdd(%c) returning %04lx",
-			ch, Usermode_Table[i].mode));
-		/* Update usermode table highest */
-		for (j = 0; j < UMODETABLESZ; j++)
-			if (Usermode_Table[i].flag)
-				if (i > Usermode_highest)
-					Usermode_highest = i;
-		make_umodestr();
-		AllUmodes |= Usermode_Table[i].mode;
-		if (global)
-			SendUmodes |= Usermode_Table[i].mode;
-		*mode = Usermode_Table[i].mode;
-		Usermode_Table[i].owner = module;
-		if (module)
+		if (!m->next)
 		{
-			ModuleObject *umodeobj = safe_alloc(sizeof(ModuleObject));
-			umodeobj->object.umode = &(Usermode_Table[i]);
-			umodeobj->type = MOBJ_UMODE;
-			AddListItem(umodeobj, module->objects);
-			module->errorcode = MODERR_NOERROR;
+			/* Append us at end */
+			m->next = n;
+			n->prev = m;
+			return;
 		}
-		return &(Usermode_Table[i]);
-	}
-	else
-	{
-		Debug((DEBUG_DEBUG, "UmodeAdd failed, no space"));
-		if (module)
-			module->errorcode = MODERR_NOSPACE;
-		return NULL;
 	}
 }
 
 
-void UmodeDel(Umode *umode)
+/* UmodeAdd:
+ * Add a usermode with character 'ch', if global is set to 1 the usermode is global
+ * (sent to other servers) otherwise it's a local usermode
+ */
+Umode *UmodeAdd(Module *module, char ch, int global, int unset_on_deoper, int (*allowed)(Client *client, int what), long *mode)
 {
-	if (loop.ircd_rehashing)
-		umode->unloaded = 1;
-	else	
-	{
-		Client *client;
-		list_for_each_entry(client, &client_list, client_node)
-		{
-			long oldumode = 0;
-			if (!IsUser(client))
-				continue;
-			oldumode = client->umodes;
-			client->umodes &= ~umode->mode;
-			if (MyUser(client))
-				send_umode_out(client, 1, oldumode);
-		}
-		umode->flag = '\0';
-		AllUmodes &= ~(umode->mode);
-		SendUmodes &= ~(umode->mode);
-		make_umodestr();
-	}
+	Umode *um;
+	int existing = 0;
 
-	if (umode->owner) {
-		ModuleObject *umodeobj;
-		for (umodeobj = umode->owner->objects; umodeobj; umodeobj = umodeobj->next) {
-			if (umodeobj->type == MOBJ_UMODE && umodeobj->object.umode == umode) {
-				DelListItem(umodeobj, umode->owner->objects);
-				safe_free(umodeobj);
-				break;
-			}
-		}
-		umode->owner = NULL;
-	}
-	return;
-}
-
-Snomask *SnomaskAdd(Module *module, char ch, int (*allowed)(Client *client, int what), long *mode)
-{
-	short	 i = 0;
-	short	 j = 0;
-	short 	 save = -1;
-	while (i < UMODETABLESZ)
+	for (um=usermodes; um; um = um->next)
 	{
-		if (!Snomask_Table[i].flag && save == -1)
-			save = i;
-		else if (Snomask_Table[i].flag == ch)
+		if (um->letter == ch)
 		{
-			if (Snomask_Table[i].unloaded)
+			if (um->unloaded)
 			{
-				save = i;
-				Snomask_Table[i].unloaded = 0;
+				um->unloaded = 0;
+				existing = 1;
 				break;
-			}
-			else
-			{
+			} else {
 				if (module)
 					module->errorcode = MODERR_EXISTS;
 				return NULL;
 			}
 		}
-		i++;
 	}
-	i = save;
-	if (i != UMODETABLESZ)
+
+	if (!um)
 	{
-		Snomask_Table[i].flag = ch;
-		Snomask_Table[i].allowed = allowed;
-		/* Update usermode table highest */
-		for (j = 0; j < UMODETABLESZ; j++)
-			if (Snomask_Table[i].flag)
-				if (i > Snomask_highest)
-					Snomask_highest = i;
-		*mode = Snomask_Table[i].mode;
-		Snomask_Table[i].owner = module;
-		if (module)
+		/* Not found, create */
+		long l, found = 0;
+		for (l = 1; l < LONG_MAX/2; l *= 2)
 		{
-			ModuleObject *snoobj = safe_alloc(sizeof(ModuleObject));
-			snoobj->object.snomask = &(Snomask_Table[i]);
-			snoobj->type = MOBJ_SNOMASK;
-			AddListItem(snoobj, module->objects);
-			module->errorcode = MODERR_NOERROR;
+			found = 0;
+			for (um=usermodes; um; um = um->next)
+			{
+				if (um->mode == l)
+				{
+					found = 1;
+					break;
+				}
+			}
+			if (!found)
+				break;
+		}
+		/* If 'found' is still true, then we are out of space */
+		if (found)
+		{
+			unreal_log(ULOG_ERROR, "module", "USER_MODE_OUT_OF_SPACE", NULL,
+				   "UmodeAdd: out of space!!!");
+			if (module)
+				module->errorcode = MODERR_NOSPACE;
+			return NULL;
 		}
-		return &(Snomask_Table[i]);
+		um = safe_alloc(sizeof(Umode));
+		um->letter = ch;
+		um->mode = l;
+		usermode_add_sorted(um);
 	}
-	else
+
+	um->letter = ch;
+	um->allowed = allowed;
+	um->unset_on_deoper = unset_on_deoper;
+	make_umodestr();
+	AllUmodes |= um->mode;
+	if (global)
+		SendUmodes |= um->mode;
+	*mode = um->mode;
+	um->owner = module;
+	if (module)
 	{
-		Debug((DEBUG_DEBUG, "SnomaskAdd failed, no space"));
-		*mode = 0;
-		if (module)
-			module->errorcode = MODERR_NOSPACE;
-		return NULL;
+		ModuleObject *umodeobj = safe_alloc(sizeof(ModuleObject));
+		umodeobj->object.umode = um;
+		umodeobj->type = MOBJ_UMODE;
+		AddListItem(umodeobj, module->objects);
+		module->errorcode = MODERR_NOERROR;
 	}
+	return um;
 }
 
-void SnomaskDel(Snomask *sno)
+
+void UmodeDel(Umode *umode)
 {
-	if (loop.ircd_rehashing)
-		sno->unloaded = 1;
-	else	
+	/* Always free the module object */
+	if (umode->owner)
 	{
-		Client *client;
-
-		list_for_each_entry(client, &lclient_list, lclient_node)
+		ModuleObject *umodeobj;
+		for (umodeobj = umode->owner->objects; umodeobj; umodeobj = umodeobj->next)
 		{
-			long oldsno;
-			if (!client || !IsUser(client))
-				continue;
-			oldsno = client->user->snomask;
-			client->user->snomask &= ~sno->mode;
-			if (oldsno != client->user->snomask)
-				sendnumeric(client, RPL_SNOMASK, get_snomask_string_raw(client->user->snomask));
-		}
-
-		sno->flag = '\0';
-	}
-	if (sno->owner) {
-		ModuleObject *snoobj;
-		for (snoobj = sno->owner->objects; snoobj; snoobj = snoobj->next) {
-			if (snoobj->type == MOBJ_SNOMASK && snoobj->object.snomask == sno) {
-				DelListItem(snoobj, sno->owner->objects);
-				safe_free(snoobj);
+			if (umodeobj->type == MOBJ_UMODE && umodeobj->object.umode == umode)
+			{
+				DelListItem(umodeobj, umode->owner->objects);
+				safe_free(umodeobj);
 				break;
 			}
 		}
-		sno->owner = NULL;
+		umode->owner = NULL;
 	}
-	return;
+
+	/* Whether we can actually (already) free the Umode depends... */
+
+	if (loop.rehashing)
+		umode->unloaded = 1;
+	else
+		unload_usermode_commit(umode);
 }
 
 int umode_allow_all(Client *client, int what)
@@ -392,68 +296,42 @@ int umode_hidle_allow(Client *client, int what)
 	return 0; /* if set::hide-idle-time is 'never' or 'always' then +I makes no sense */
 }
 
-void unload_all_unused_umodes(void)
+static void unload_usermode_commit(Umode *um)
 {
-	long removed_umode = 0;
-	int i;
 	Client *client;
-	for (i = 0; i < UMODETABLESZ; i++)
-	{
-		if (Usermode_Table[i].unloaded)
-			removed_umode |= Usermode_Table[i].mode;
-	}
-	if (!removed_umode) /* Nothing was unloaded */
+	long removed_umode;
+
+	if (!um)
 		return;
+
+	removed_umode = um->mode;
+
+	/* First send the -mode regarding all users */
 	list_for_each_entry(client, &lclient_list, lclient_node)
 	{
-		long oldumode = 0;
-		if (!IsUser(client))
-			continue;
-		oldumode = client->umodes;
-		client->umodes &= ~(removed_umode);
-		if (MyUser(client))
-			send_umode_out(client, 1, oldumode);
-	}
-	for (i = 0; i < UMODETABLESZ; i++)
-	{
-		if (Usermode_Table[i].unloaded)
+		if (MyUser(client) && (client->umodes & removed_umode))
 		{
-			AllUmodes &= ~(Usermode_Table[i].mode);
-			SendUmodes &= ~(Usermode_Table[i].mode);
-			Usermode_Table[i].flag = '\0';
-			Usermode_Table[i].unloaded = 0;
+			long oldumode = client->umodes;
+			client->umodes &= ~(removed_umode);
+			send_umode_out(client, 1, oldumode);
 		}
 	}
+
+	/* Then unload the mode */
+	DelListItem(um, usermodes);
+	safe_free(um);
 	make_umodestr();
 }
 
-void unload_all_unused_snomasks(void)
+void unload_all_unused_umodes(void)
 {
-	Client *client;
-	long removed_sno = 0;
-	int i;
+	Umode *um, *um_next;
 
-	for (i = 0; i < UMODETABLESZ; i++)
+	for (um=usermodes; um; um = um_next)
 	{
-		if (Snomask_Table[i].unloaded)
-		{
-			removed_sno |= Snomask_Table[i].mode;
-			Snomask_Table[i].flag = '\0';
-			Snomask_Table[i].unloaded = 0;
-		}
-	}
-	if (!removed_sno) /* Nothing was unloaded */
-		return;
-
-	list_for_each_entry(client, &lclient_list, lclient_node)
-	{
-		long oldsno;
-		if (!client || !IsUser(client))
-			continue;
-		oldsno = client->user->snomask;
-		client->user->snomask &= ~(removed_sno);
-		if (oldsno != client->user->snomask)
-			sendnumeric(client, RPL_SNOMASK, get_snomask_string_raw(client->user->snomask));
+		um_next = um->next;
+		if (um->letter && um->unloaded)
+			unload_usermode_commit(um);
 	}
 }
 
@@ -463,25 +341,24 @@ void unload_all_unused_snomasks(void)
  * This used to be a bit more complex but nowadays we just erase all
  * snomasks since all of them are IRCOp-only. Easy.
  */
-void remove_oper_snomasks(Client *client)
+void remove_all_snomasks(Client *client)
 {
-	client->user->snomask = 0;
+	safe_free(client->user->snomask);
+	client->umodes &= ~UMODE_SERVNOTICE;
 }
 
 /*
  * This function removes any oper-only user modes from the user.
- * You may also want to call remove_oper_snomasks(), see above.
+ * You may also want to call remove_all_snomasks(), see above.
  */
 void remove_oper_modes(Client *client)
 {
-int i;
+	Umode *um;
 
-	for (i = 0; i <= Usermode_highest; i++)
+	for (um = usermodes; um; um = um->next)
 	{
-		if (!Usermode_Table[i].flag)
-			continue;
-		if (Usermode_Table[i].unset_on_deoper)
-			client->umodes &= ~Usermode_Table[i].mode;
+		if (um->unset_on_deoper)
+			client->umodes &= ~um->mode;
 	}
 
 	/* Bit of a hack, since this is a dynamic permission umode */
@@ -493,7 +370,7 @@ void remove_oper_privileges(Client *client, int broadcast_mode_change)
 {
 	long oldumodes = client->umodes;
 	remove_oper_modes(client);
-	remove_oper_snomasks(client);
+	remove_all_snomasks(client);
 	if (broadcast_mode_change && (client->umodes != oldumodes))
 		send_umode_out(client, 1, oldumodes);
 	if (MyUser(client)) /* only do if it's our client, remote servers will send a SWHOIS cmd */
@@ -501,15 +378,14 @@ void remove_oper_privileges(Client *client, int broadcast_mode_change)
 }
 
 /** Return long integer mode for a user mode character (eg: 'x' -> 0x10) */
-long find_user_mode(char flag)
+long find_user_mode(char letter)
 {
-	int i;
+	Umode *um;
+
+	for (um = usermodes; um; um = um->next)
+		if ((um->letter == letter) && !um->unloaded)
+			return um->mode;
 
-	for (i = 0; i < UMODETABLESZ; i++)
-	{
-		if ((Usermode_Table[i].flag == flag) && !(Usermode_Table[i].unloaded))
-			return Usermode_Table[i].mode;
-	}
 	return 0;
 }
 
diff --git a/src/auth.c b/src/auth.c
@@ -46,10 +46,10 @@ AuthTypeList MODVAR AuthTypeLists[] = {
 };
 
 /* Helper function for Auth_AutoDetectHashType() */
-static int parsepass(char *str, char **salt, char **hash)
+static int parsepass(const char *str, char **salt, char **hash)
 {
 	static char saltbuf[512], hashbuf[512];
-	char *p;
+	const char *p;
 	int max;
 
 	/* Syntax: $<salt>$<hash> */
@@ -72,7 +72,7 @@ static int parsepass(char *str, char **salt, char **hash)
 /** Auto detect hash type for input hash 'hash'.
  * Will fallback to AUTHTYPE_PLAINTEXT when not found (or invalid).
  */
-int Auth_AutoDetectHashType(char *hash)
+int Auth_AutoDetectHashType(const char *hash)
 {
 	static char hashbuf[256];
 	char *saltstr, *hashstr;
@@ -80,12 +80,12 @@ int Auth_AutoDetectHashType(char *hash)
 
 	if (!strchr(hash, '$'))
 	{
-		/* SHA256 SSL fingerprint perhaps?
+		/* SHA256 certificate fingerprint perhaps?
 		 * These are exactly 64 bytes (00112233..etc..) or 95 bytes (00:11:22:33:etc) in size.
 		 */
 		if ((strlen(hash) == 64) || (strlen(hash) == 95))
 		{
-			char *p;
+			const char *p;
 			char *hexchars = "0123456789abcdefABCDEF";
 			for (p = hash; *p; p++)
 				if ((*p != ':') && !strchr(hexchars, *p))
@@ -96,7 +96,7 @@ int Auth_AutoDetectHashType(char *hash)
 
 		if (strlen(hash) == 44)
 		{
-			char *p;
+			const char *p;
 			char *b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
 			for (p = hash; *p; p++)
 				if (!strchr(b64chars, *p))
@@ -134,7 +134,7 @@ int Auth_AutoDetectHashType(char *hash)
  *               than trying to determine the type on the 'hash' parameter.
  *               Or leave NULL, then we use hash autodetection.
  */
-AuthenticationType Auth_FindType(char *hash, char *type)
+AuthenticationType Auth_FindType(const char *hash, const char *type)
 {
 	if (type)
 	{
@@ -163,25 +163,25 @@ int Auth_CheckError(ConfigEntry *ce)
 	AuthenticationType type = AUTHTYPE_PLAINTEXT;
 	X509 *x509_filecert = NULL;
 	FILE *x509_f = NULL;
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: authentication module failure: missing parameter",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return -1;
 	}
-	if (ce->ce_entries && ce->ce_entries->ce_next)
+	if (ce->items && ce->items->next)
 	{
 		config_error("%s:%i: you may not have multiple authentication methods",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return -1;
 	}
 
-	type = Auth_FindType(ce->ce_vardata, ce->ce_entries ? ce->ce_entries->ce_varname : NULL);
+	type = Auth_FindType(ce->value, ce->items ? ce->items->name : NULL);
 	if (type == -1)
 	{
 		config_error("%s:%i: authentication module failure: %s is not an implemented/enabled authentication method",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_entries->ce_varname);
+			ce->file->filename, ce->line_number,
+			ce->items->name);
 		return -1;
 	}
 
@@ -189,19 +189,19 @@ int Auth_CheckError(ConfigEntry *ce)
 	{
 		case AUTHTYPE_UNIXCRYPT:
 			/* If our data is like 1 or none, we just let em through .. */
-			if (strlen(ce->ce_vardata) < 2)
+			if (strlen(ce->value) < 2)
 			{
 				config_error("%s:%i: authentication module failure: AUTHTYPE_UNIXCRYPT: no salt (crypt strings will always be >2 in length)",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+					ce->file->filename, ce->line_number);
 				return -1;
 			}
 			break;
 		case AUTHTYPE_TLS_CLIENTCERT:
-			convert_to_absolute_path(&ce->ce_vardata, CONFDIR);
-			if (!(x509_f = fopen(ce->ce_vardata, "r")))
+			convert_to_absolute_path(&ce->value, CONFDIR);
+			if (!(x509_f = fopen(ce->value, "r")))
 			{
 				config_error("%s:%i: authentication module failure: AUTHTYPE_TLS_CLIENTCERT: error opening file %s: %s",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata, strerror(errno));
+					ce->file->filename, ce->line_number, ce->value, strerror(errno));
 				return -1;
 			}
 			x509_filecert = PEM_read_X509(x509_f, NULL, NULL, NULL);
@@ -209,7 +209,7 @@ int Auth_CheckError(ConfigEntry *ce)
 			if (!x509_filecert)
 			{
 				config_error("%s:%i: authentication module failure: AUTHTYPE_TLS_CLIENTCERT: PEM_read_X509 errored in file %s (format error?)",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+					ce->file->filename, ce->line_number, ce->value);
 				return -1;
 			}
 			X509_free(x509_filecert);
@@ -226,19 +226,19 @@ int Auth_CheckError(ConfigEntry *ce)
 	 * with normally at least 5000 rounds (unless deliberately weakened
 	 * by the user).
 	 */
-	if ((type == AUTHTYPE_UNIXCRYPT) && strncmp(ce->ce_vardata, "$5", 2) &&
-	    strncmp(ce->ce_vardata, "$6", 2) && !strstr(ce->ce_vardata, "$rounds"))
+	if ((type == AUTHTYPE_UNIXCRYPT) && strncmp(ce->value, "$5", 2) &&
+	    strncmp(ce->value, "$6", 2) && !strstr(ce->value, "$rounds"))
 	{
 		config_warn("%s:%i: Using simple crypt for authentication is not recommended. "
 		            "Consider using the more secure auth-type 'argon2' instead. "
 		            "See https://www.unrealircd.org/docs/Authentication_types for the complete list.",
-                            ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+                            ce->file->filename, ce->line_number);
 		/* do not return, not an error. */
 	}
-	if ((type == AUTHTYPE_PLAINTEXT) && (strlen(ce->ce_vardata) > PASSWDLEN))
+	if ((type == AUTHTYPE_PLAINTEXT) && (strlen(ce->value) > PASSWDLEN))
 	{
 		config_error("%s:%i: passwords length may not exceed %d",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum, PASSWDLEN);
+			ce->file->filename, ce->line_number, PASSWDLEN);
 		return -1;
 	}
 	return 1;
@@ -252,12 +252,12 @@ AuthConfig *AuthBlockToAuthConfig(ConfigEntry *ce)
 	AuthenticationType type = AUTHTYPE_PLAINTEXT;
 	AuthConfig *as = NULL;
 
-	type = Auth_FindType(ce->ce_vardata, ce->ce_entries ? ce->ce_entries->ce_varname : NULL);
+	type = Auth_FindType(ce->value, ce->items ? ce->items->name : NULL);
 	if (type == AUTHTYPE_INVALID)
 		type = AUTHTYPE_PLAINTEXT;
 
 	as = safe_alloc(sizeof(AuthConfig));
-	safe_strdup(as->data, ce->ce_vardata);
+	safe_strdup(as->data, ce->value);
 	as->type = type;
 	return as;
 }
@@ -279,7 +279,7 @@ void Auth_FreeAuthConfig(AuthConfig *as)
 #define RAWSALTLEN		6
 #define REALSALTLEN		12
 
-static int authcheck_argon2(Client *client, AuthConfig *as, char *para)
+static int authcheck_argon2(Client *client, AuthConfig *as, const char *para)
 {
 	argon2_type hashtype;
 
@@ -304,7 +304,7 @@ static int authcheck_argon2(Client *client, AuthConfig *as, char *para)
 	return 0; /* NO MATCH or error */
 }
 
-static int authcheck_bcrypt(Client *client, AuthConfig *as, char *para)
+static int authcheck_bcrypt(Client *client, AuthConfig *as, const char *para)
 {
 	char data[512]; /* NOTE: only 64 required by BF_crypt() */
 	char *str;
@@ -324,7 +324,7 @@ static int authcheck_bcrypt(Client *client, AuthConfig *as, char *para)
 	return 0; /* NO MATCH */
 }
 
-static int authcheck_tls_clientcert(Client *client, AuthConfig *as, char *para)
+static int authcheck_tls_clientcert(Client *client, AuthConfig *as, const char *para)
 {
 	X509 *x509_clientcert = NULL;
 	X509 *x509_filecert = NULL;
@@ -358,11 +358,11 @@ static int authcheck_tls_clientcert(Client *client, AuthConfig *as, char *para)
 	return 1;
 }
 
-static int authcheck_tls_clientcert_fingerprint(Client *client, AuthConfig *as, char *para)
+static int authcheck_tls_clientcert_fingerprint(Client *client, AuthConfig *as, const char *para)
 {
 	int i, k;
 	char hexcolon[EVP_MAX_MD_SIZE * 3 + 1];
-	char *fp;
+	const char *fp;
 
 	if (!client->local->ssl)
 		return 0;
@@ -389,12 +389,12 @@ static int authcheck_tls_clientcert_fingerprint(Client *client, AuthConfig *as, 
 	return 1;
 }
 
-static int authcheck_spkifp(Client *client, AuthConfig *as, char *para)
+static int authcheck_spkifp(Client *client, AuthConfig *as, const char *para)
 {
-	char *fp = spki_fingerprint(client);
+	const char *fp = spki_fingerprint(client);
 
 	if (!fp)
-		return 0; /* auth failed: not SSL (or other failure) */
+		return 0; /* auth failed: not TLS or some other failure */
 
 	if (strcasecmp(as->data, fp))
 		return 0; /* auth failed: mismatch */
@@ -420,7 +420,7 @@ static int authcheck_spkifp(Client *client, AuthConfig *as, char *para)
  * - The return value was different in versions before UnrealIRCd 5.0.0!
  * - In older versions a NULL 'as' was treated as an allow, now it's deny.
  */
-int Auth_Check(Client *client, AuthConfig *as, char *para)
+int Auth_Check(Client *client, AuthConfig *as, const char *para)
 {
 	extern char *crypt();
 	char *res;
@@ -435,8 +435,9 @@ int Auth_Check(Client *client, AuthConfig *as, char *para)
 				return 0;
 			if (!strcmp(as->data, "changemeplease") && !strcmp(para, as->data))
 			{
-				sendto_realops("Rejecting default password 'changemeplease'. "
-				               "Please change the password in the configuration file.");
+				unreal_log(ULOG_INFO, "auth", "AUTH_REJECT_DEFAULT_PASSWORD", client,
+				           "Rejecting default password 'changemeplease'. "
+				           "Please change the password in the configuration file.");
 				return 0;
 			}
 			/* plain text compare */
@@ -479,7 +480,7 @@ int Auth_Check(Client *client, AuthConfig *as, char *para)
 #define UNREALIRCD_ARGON2_DEFAULT_HASH_LENGTH           32
 #define UNREALIRCD_ARGON2_DEFAULT_SALT_LENGTH           (128/8)
 
-static char *mkpass_argon2(char *para)
+static char *mkpass_argon2(const char *para)
 {
 	static char buf[512];
 	char salt[UNREALIRCD_ARGON2_DEFAULT_SALT_LENGTH];
@@ -511,7 +512,7 @@ static char *mkpass_argon2(char *para)
 	return buf;
 }
 
-static char *mkpass_bcrypt(char *para)
+static char *mkpass_bcrypt(const char *para)
 {
 	static char buf[128];
 	char data[512]; /* NOTE: only 64 required by BF_crypt() */
@@ -547,7 +548,7 @@ static char *mkpass_bcrypt(char *para)
  * @param text  The password in plaintext.
  * @returns The hashed password.
  */
-char *Auth_Hash(AuthenticationType type, char *text)
+const char *Auth_Hash(AuthenticationType type, const char *text)
 {
 	switch (type)
 	{
diff --git a/src/buildmod b/src/buildmod
@@ -1,4 +1,5 @@
 #!/bin/sh
+MAKE="$1"
 echo ""
 echo "Checking for updates for third party modules..."
 # We can't use the "unrealircd" script, since possibly the ircd
@@ -13,7 +14,7 @@ if [ "$x" != "*.c" ]; then
 	x="`echo $x|sed 's/\.c//'`"
 	if [ ! -f $x.so -o $x.c -nt $x.so ]; then
 		echo "Building 3rd party module $x..."
-		make custommodule MODULEFILE=$x || (echo "*****"; echo "Building 3rd party module $x failed."; echo "Contact the module author of the $x module (not the UnrealIRCd team), or simply delete the $PWD/$x.c file"; echo "*****"; exit 1)
+		$MAKE custommodule MODULEFILE=$x || (echo "*****"; echo "Building 3rd party module $x failed."; echo "Contact the module author of the $x module (not the UnrealIRCd team), or simply delete the $PWD/$x.c file"; echo "*****"; exit 1)
 	fi
 fi
 done
diff --git a/src/channel.c b/src/channel.c
@@ -40,31 +40,15 @@ long sajoinmode = 0;
  */
 Channel *channels = NULL;
 
-/* some buffers for rebuilding channel/nick lists with comma's */
+/* A buffer for rebuilding channel/nick lists with comma's */
 static char buf[BUFSIZE];
-/** Mode buffer (eg: "+sntkl") */
-MODVAR char modebuf[BUFSIZE];
-/** Parameter buffer (eg: "key 123") */
-MODVAR char parabuf[BUFSIZE];
+
+static mp_pool_t *channel_pool = NULL;
 
 /** This describes the letters, modes and options for core channel modes.
  * These are +ntmispklr and also the list modes +vhoaq and +beI.
  */
 CoreChannelModeTable corechannelmodetable[] = {
-	{MODE_LIMIT, 'l', 1, 1},
-	{MODE_VOICE, 'v', 1, 1},
-	{MODE_HALFOP, 'h', 0, 1},
-	{MODE_CHANOP, 'o', 0, 1},
-	{MODE_PRIVATE, 'p', 0, 0},
-	{MODE_SECRET, 's', 0, 0},
-	{MODE_MODERATED, 'm', 1, 0},
-	{MODE_NOPRIVMSGS, 'n', 1, 0},
-	{MODE_TOPICLIMIT, 't', 1, 0},
-	{MODE_INVITEONLY, 'i', 1, 0},
-	{MODE_KEY, 'k', 1, 1},
-	{MODE_RGSTR, 'r', 0, 0},
-	{MODE_CHANADMIN, 'a', 0, 1},
-	{MODE_CHANOWNER, 'q', 0, 1},
 	{MODE_BAN, 'b', 1, 1},
 	{MODE_EXCEPT, 'e', 1, 1},	/* exception ban */
 	{MODE_INVEX, 'I', 1, 1},	/* invite-only exception */
@@ -74,14 +58,8 @@ CoreChannelModeTable corechannelmodetable[] = {
 /** The advertised supported channel modes in the 004 numeric */
 char cmodestring[512];
 
-/* Some forward declarations */
-char *clean_ban_mask(char *, int, Client *);
-void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel);
-int sub1_from_channel(Channel *);
-void del_invite(Client *, Channel *);
-
 /** Returns 1 if the IRCOp can override or is a remote connection */
-inline int op_can_override(char *acl, Client *client,Channel *channel,void* extra)
+inline int op_can_override(const char *acl, Client *client, Channel *channel, void* extra)
 {
 #ifndef NO_OPEROVERRIDE
 	if (MyUser(client) && !(ValidatePermissionsForPath(acl,client,NULL,channel,extra)))
@@ -106,17 +84,6 @@ int Halfop_mode(long mode)
 	return TRUE;
 }
 
-
-/** Returns the length (entry count) of a +beI list */
-static int list_length(Link *lp)
-{
-	int  count = 0;
-
-	for (; lp; lp = lp->next)
-		count++;
-	return count;
-}
-
 /** Find client in a Member linked list (eg: channel->members) */
 Member *find_member_link(Member *lp, Client *ptr)
 {
@@ -156,8 +123,6 @@ static Member *make_member(void)
 		for (i = 1; i <= (4072/sizeof(Member)); ++i)
 		{
 			lp = safe_alloc(sizeof(Member));
-			lp->client = NULL;
-			lp->flags = 0;
 			lp->next = freemember;
 			freemember = lp;
 		}
@@ -176,8 +141,6 @@ static void free_member(Member *lp)
 	moddata_free_member(lp);
 	memset(lp, 0, sizeof(Member));
 	lp->next = freemember;
-	lp->client = NULL;
-	lp->flags = 0;
 	freemember = lp;
 }
 
@@ -228,7 +191,7 @@ static void free_membership(Membership *m)
  *			only after searching through the nick history.
  * @returns The client (if found) or NULL (if not found).
  */
-Client *find_chasing(Client *client, char *user, int *chasing)
+Client *find_chasing(Client *client, const char *user, int *chasing)
 {
 	Client *who = find_client(user, NULL);
 
@@ -254,9 +217,10 @@ Client *find_chasing(Client *client, char *user, int *chasing)
 }
 
 /** Return 1 if the bans are identical, taking into account special handling for extbans */
-int identical_ban(char *one, char *two)
+int identical_ban(const char *one, const char *two)
 {
-	if (is_extended_ban(one))
+#if 0
+	if (is_extended_ban(one) && is_extended_ban(two))
 	{
 		/* compare the first 3 characters case-sensitive and if identical then compare
 		 * the remainder of the string case-insensitive.
@@ -267,6 +231,14 @@ int identical_ban(char *one, char *two)
 		if (!mycmp(one, two))
 			return 1;
 	}
+#else
+	/* Actually I think we can live with this nowadays.
+	 * We are pushing towards named extbans, and all the
+	 * letter extbans that could clash no longer exist.
+	 */
+	if (!mycmp(one, two))
+		return 1;
+#endif
 	return 0;
 }
 
@@ -274,14 +246,14 @@ int identical_ban(char *one, char *two)
  *  the specified channel. (Extended version with
  *  set by nick and set on timestamp)
  */
-int add_listmode_ex(Ban **list, Client *client, Channel *channel, char *banid, char *setby, time_t seton)
+int add_listmode_ex(Ban **list, Client *client, Channel *channel, const char *banid, const char *setby, time_t seton)
 {
 	Ban *ban;
 	int cnt = 0, len;
 	int do_not_add = 0;
 
-	if (MyUser(client))
-		collapse(banid);
+	//if (MyUser(client))
+	//	collapse(banid);
 
 	len = strlen(banid);
 	if (!*list && ((len > MAXBANLENGTH) || (MAXBANS < 1)))
@@ -289,7 +261,7 @@ int add_listmode_ex(Ban **list, Client *client, Channel *channel, char *banid, c
 		if (MyUser(client))
 		{
 			/* Only send the error to local clients */
-			sendnumeric(client, ERR_BANLISTFULL, channel->chname, banid);
+			sendnumeric(client, ERR_BANLISTFULL, channel->name, banid);
 		}
 		do_not_add = 1;
 	}
@@ -318,7 +290,7 @@ int add_listmode_ex(Ban **list, Client *client, Channel *channel, char *banid, c
 			if (MyUser(client))
 			{
 				/* Only send the error to local clients */
-				sendnumeric(client, ERR_BANLISTFULL, channel->chname, banid);
+				sendnumeric(client, ERR_BANLISTFULL, channel->name, banid);
 			}
 			return -1;
 		}
@@ -345,20 +317,20 @@ int add_listmode_ex(Ban **list, Client *client, Channel *channel, char *banid, c
 /** Add a listmode (+beI) with the specified banid to
  *  the specified channel. (Simplified version)
  */
-int add_listmode(Ban **list, Client *client, Channel *channel, char *banid)
+int add_listmode(Ban **list, Client *client, Channel *channel, const char *banid)
 {
 	char *setby = client->name;
 	char nuhbuf[NICKLEN+USERLEN+HOSTLEN+4];
 
 	if (IsUser(client) && (iConf.ban_setter == SETTER_NICK_USER_HOST))
-		setby = make_nick_user_host_r(nuhbuf, client->name, client->user->username, GetHost(client));
+		setby = make_nick_user_host_r(nuhbuf, sizeof(nuhbuf), client->name, client->user->username, GetHost(client));
 
 	return add_listmode_ex(list, client, channel, banid, setby, TStime());
 }
 
 /** Delete a listmode (+beI) from a channel that matches the specified banid.
  */
-int del_listmode(Ban **list, Channel *channel, char *banid)
+int del_listmode(Ban **list, Channel *channel, const char *banid)
 {
 	Ban **ban;
 	Ban *tmp;
@@ -390,43 +362,37 @@ int del_listmode(Ban **list, Channel *channel, char *banid)
  * @returns      A pointer to the ban struct if banned, otherwise NULL.
  * @comments     Simple wrapper for is_banned_with_nick()
  */
-inline Ban *is_banned(Client *client, Channel *channel, int type, char **msg, char **errmsg)
+inline Ban *is_banned(Client *client, Channel *channel, int type, const char **msg, const char **errmsg)
 {
 	return is_banned_with_nick(client, channel, type, NULL, msg, errmsg);
 }
 
 /** ban_check_mask - Checks if the user matches the specified n!u@h mask -or- run an extended ban.
- * @param client         Client to check (can be remote client)
- * @param channel        Channel to check
- * @param banstr       Mask string to check user
- * @param type         Type of ban to check for (BANCHK_*)
- * @param msg          Message, only for some BANCHK_* types, otherwise NULL.
- * @param errmsg       Error message, could be NULL
- * @param no_extbans   0 to check extbans, nonzero to disable extban checking.
- * @returns            Nonzero if the mask/extban succeeds. Zero if it doesn't.
- * @comments           This is basically extracting the mask and extban check from is_banned_with_nick, but with being a bit more strict in what an extban is.
- *                     Strange things could happen if this is called outside standard ban checking.
+ * This is basically extracting the mask and extban check from is_banned_with_nick,
+ * but with being a bit more strict in what an extban is.
+ * Strange things could happen if this is called outside standard ban checking.
+ * @param b	Ban context, see BanContext
+ * @returns	Nonzero if the mask/extban succeeds. Zero if it doesn't.
  */
-inline int ban_check_mask(Client *client, Channel *channel, char *banstr, int type, char **msg, char **errmsg, int no_extbans)
+inline int ban_check_mask(BanContext *b)
 {
-	Extban *extban = NULL;
-	if (!no_extbans && is_extended_ban(banstr))
+	if (!b->no_extbans && is_extended_ban(b->banstr))
 	{
 		/* Is an extended ban. */
-		extban = findmod_by_bantype(banstr[1]);
-		if (!extban)
+		const char *nextbanstr;
+		Extban *extban = findmod_by_bantype(b->banstr, &nextbanstr);
+		if (!extban || !(extban->is_banned_events & b->ban_check_types))
 		{
 			return 0;
-		}
-		else
-		{
-			return extban->is_banned(client, channel, banstr, type, msg, errmsg);
+		} else {
+			b->banstr = nextbanstr;
+			return extban->is_banned(b);
 		}
 	}
 	else
 	{
 		/* Is a n!u@h mask. */
-		return match_user(banstr, client, MATCH_CHECK_ALL);
+		return match_user(b->banstr, b->client, MATCH_CHECK_ALL);
 	}
 }
 
@@ -438,10 +404,11 @@ inline int ban_check_mask(Client *client, Channel *channel, char *banstr, int ty
  * @param msg    Message, only for some BANCHK_* types, otherwise NULL
  * @returns      A pointer to the ban struct if banned, otherwise NULL.
  */
-Ban *is_banned_with_nick(Client *client, Channel *channel, int type, char *nick, char **msg, char **errmsg)
+Ban *is_banned_with_nick(Client *client, Channel *channel, int type, const char *nick, const char **msg, const char **errmsg)
 {
 	Ban *ban, *ex;
 	char savednick[NICKLEN+1];
+	BanContext *b = safe_alloc(sizeof(BanContext));
 
 	/* It's not really doable to pass 'nick' to all the ban layers,
 	 * including extbans (with stacking) and so on. Or at least not
@@ -460,13 +427,20 @@ Ban *is_banned_with_nick(Client *client, Channel *channel, int type, char *nick,
 		strlcpy(client->name, nick, sizeof(client->name));
 	}
 
+	b->client = client;
+	b->channel = channel;
+	b->ban_check_types = type;
+	if (msg)
+		b->msg = *msg;
+
 	/* We check +b first, if a +b is found we then see if there is a +e.
 	 * If a +e was found we return NULL, if not, we return the ban.
 	 */
 
 	for (ban = channel->banlist; ban; ban = ban->next)
 	{
-		if (ban_check_mask(client, channel, ban->banstr, type, msg, errmsg, 0))
+		b->banstr = ban->banstr;
+		if (ban_check_mask(b))
 			break;
 	}
 
@@ -475,7 +449,8 @@ Ban *is_banned_with_nick(Client *client, Channel *channel, int type, char *nick,
 		/* Ban found, now check for +e */
 		for (ex = channel->exlist; ex; ex = ex->next)
 		{
-			if (ban_check_mask(client, channel, ex->banstr, type, msg, errmsg, 0))
+			b->banstr = ex->banstr;
+			if (ban_check_mask(b))
 			{
 				/* except matched */
 				ban = NULL;
@@ -491,6 +466,13 @@ Ban *is_banned_with_nick(Client *client, Channel *channel, int type, char *nick,
 		strlcpy(client->name, savednick, sizeof(client->name));
 	}
 
+	/* OUT: */
+	if (msg)
+		*msg = b->msg;
+	if (errmsg)
+		*errmsg = b->error_msg;
+
+	safe_free(b);
 	return ban;
 }
 
@@ -499,37 +481,43 @@ Ban *is_banned_with_nick(Client *client, Channel *channel, int type, char *nick,
  * and also the Membership struct to the client->user->channel linked list.
  * @note This does NOT send the JOIN, it only does the linked list stuff.
  */
-void add_user_to_channel(Channel *channel, Client *who, int flags)
+void add_user_to_channel(Channel *channel, Client *client, const char *modes)
 {
 	Member *m;
 	Membership *mb;
+	const char *p;
 
-	if (who->user)
-	{
-		m = make_member();
-		m->client = who;
-		m->flags = flags;
-		m->next = channel->members;
-		channel->members = m;
-		channel->users++;
-
-		mb = make_membership();
-		mb->channel = channel;
-		mb->next = who->user->channel;
-		mb->flags = flags;
-		who->user->channel = mb;
-		who->user->joined++;
-		RunHook2(HOOKTYPE_JOIN_DATA, who, channel);
-	}
+	if (!client->user)
+		return;
+
+	m = make_member();
+	m->client = client;
+	m->next = channel->members;
+	channel->members = m;
+	channel->users++;
+
+	mb = make_membership();
+	mb->channel = channel;
+	mb->next = client->user->channel;
+	client->user->channel = mb;
+	client->user->joined++;
+
+	for (p = modes; *p; p++)
+		add_member_mode_fast(m, mb, *p);
+
+	RunHook(HOOKTYPE_JOIN_DATA, client, channel);
 }
 
 /** Remove the user from the channel.
- * This doesn't send any PART etc. It does the free'ing of
- * membership etc. It will also DESTROY the channel if the
- * user was the last user (and the channel is not +P),
- * via sub1_from_channel(), that is.
+ * This frees the memberships, decreases the user counts,
+ * destroys the channel if needed, etc.
+ * This does not send any PART/KICK/..!
+ * @param client	The client that is removed from the channel
+ * @param channel	The channel
+ * @param dont_log	Set to 1 if it should not be logged as a part,
+ *                      for example if you are already logging it as a kick.
  */
-int remove_user_from_channel(Client *client, Channel *channel)
+int remove_user_from_channel(Client *client, Channel *channel, int dont_log)
 {
 	Member **m;
 	Member *m2;
@@ -561,56 +549,46 @@ int remove_user_from_channel(Client *client, Channel *channel)
 	/* Update user record to reflect 1 less joined */
 	client->user->joined--;
 
+	if (!dont_log)
+	{
+		if (MyUser(client))
+		{
+			unreal_log(ULOG_INFO, "part", "LOCAL_CLIENT_PART", client,
+				   "User $client left $channel",
+				   log_data_channel("channel", channel));
+		} else {
+			unreal_log(ULOG_INFO, "part", "REMOTE_CLIENT_PART", client,
+				   "User $client left $channel",
+				   log_data_channel("channel", channel));
+		}
+	}
+
 	/* Now sub1_from_channel() will deal with the channel record
 	 * and destroy the channel if needed.
 	 */
 	return sub1_from_channel(channel);
 }
 
-/** Get channel access flags (CHFL_*) for a client in a channel.
- * @param client	The client
- * @param channel	The channel
- * @returns One or more of CHFL_* (eg: CHFL_CHANOP|CHFL_CHANADMIN)
- * @note If the user is not found, then 0 is returned.
- *       If the user has no access rights, then 0 is returned as well.
- */
-long get_access(Client *client, Channel *channel)
-{
-	Membership *lp;
-	if (channel && IsUser(client))
-		if ((lp = find_membership_link(client->user->channel, channel)))
-			return lp->flags;
-	return 0;
-}
-
 /** Returns 1 if channel has this channel mode set and 0 if not */
 int has_channel_mode(Channel *channel, char mode)
 {
-	CoreChannelModeTable *tab = &corechannelmodetable[0];
-	int i;
+	Cmode *cm;
 
-	/* Extended channel modes */
-	for (i=0; i <= Channelmode_highest; i++)
-	{
-		if ((Channelmode_Table[i].flag == mode) && (channel->mode.extmode & Channelmode_Table[i].mode))
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->letter == mode) && (channel->mode.mode & cm->mode))
 			return 1;
-	}
 
-	/* Built-in channel modes */
-	while (tab->mode != 0x0)
-	{
-		if ((channel->mode.mode & tab->mode) && (tab->flag == mode))
-			return 1;
-		tab++;
-	}
+	return 0; /* Not found */
+}
 
-	/* Special handling for +l (needed??) */
-	if (channel->mode.limit && (mode == 'l'))
-		return 1;
+/** Returns 1 if channel has this mode is set and 0 if not */
+int has_channel_mode_raw(Cmode_t m, char mode)
+{
+	Cmode *cm;
 
-	/* Special handling for +k (needed??) */
-	if (channel->mode.key[0] && (mode == 'k'))
-		return 1;
+	for (cm=channelmodes; cm; cm = cm->next)
+		if ((cm->letter == mode) && (m & cm->mode))
+			return 1;
 
 	return 0; /* Not found */
 }
@@ -618,126 +596,88 @@ int has_channel_mode(Channel *channel, char mode)
 /** Get the extended channel mode 'bit' value (eg: 0x20) by character (eg: 'Z') */
 Cmode_t get_extmode_bitbychar(char m)
 {
-        int extm;
-        for (extm=0; extm <= Channelmode_highest; extm++)
-        {
-                if (Channelmode_Table[extm].flag == m)
-                        return Channelmode_Table[extm].mode;
-        }
-        return 0;
-}
+	Cmode *cm;
 
-/** Get the extended channel mode character (eg: 'Z') by the 'bit' value (eg: 0x20) */
-long get_mode_bitbychar(char m)
-{
-	CoreChannelModeTable *tab = &corechannelmodetable[0];
+	for (cm=channelmodes; cm; cm = cm->next)
+                if (cm->letter == m)
+                        return cm->mode;
 
-	while(tab->mode != 0x0)
-	{
-		if (tab->flag == m)
-			return tab->mode;
-		tab++;;
-	}
-	return 0;
+        return 0;
 }
 
 /** Write the "simple" list of channel modes for channel channel onto buffer mbuf with the parameters in pbuf.
+ * @param client		The client requesting the mode list (can be NULL)
+ * @param mbuf			Modes will be stored here
+ * @param pbuf			Mode parameters will be stored here
+ * @param mbuf_size		Length of the mbuf buffer
+ * @param pbuf_size		Length of the pbuf buffer
+ * @param channel		The channel to fetch modes from
+ * @param hide_local_modes	If set to 1 then we will hide local channel modes like Z and d
+ *				(eg: if you intend to send the buffer to a remote server)
  */
-/* TODO: this function has many security issues and needs an audit, maybe even a recode */
-void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel)
+void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel, int hide_local_modes)
 {
-	CoreChannelModeTable *tab = &corechannelmodetable[0];
-	int ismember;
-	int i;
+	int ismember = 0;
+	Cmode *cm;
 
-	if (!(mbuf_size && pbuf_size)) return;
+	if (!mbuf_size || !pbuf_size)
+		return;
 
-	ismember = (IsMember(client, channel) || IsServer(client) || IsMe(client) || IsULine(client)) ? 1 : 0;
+	if (!client || IsMember(client, channel) || IsServer(client) || IsMe(client) || IsULine(client))
+		ismember = 1;
 
 	*pbuf = '\0';
+	strlcpy(mbuf, "+", mbuf_size);
 
-	*mbuf++ = '+';
-	mbuf_size--;
 	/* Paramless first */
-	while (mbuf_size && tab->mode != 0x0)
-	{
-		if ((channel->mode.mode & tab->mode))
-			if (!tab->parameters) {
-				*mbuf++ = tab->flag;
-				mbuf_size--;
-			}
-		tab++;
-	}
-	for (i=0; i <= Channelmode_highest; i++)
+	for (cm=channelmodes; cm; cm = cm->next)
 	{
-		if (!mbuf_size) break;
-		if (Channelmode_Table[i].flag && !Channelmode_Table[i].paracount &&
-		    (channel->mode.extmode & Channelmode_Table[i].mode)) {
-			*mbuf++ = Channelmode_Table[i].flag;
-			mbuf_size--;
-		}
-	}
-	if (channel->mode.limit)
-	{
-		if (mbuf_size) {
-			*mbuf++ = 'l';
-			mbuf_size--;
-		}
-		if (ismember) {
-			ircsnprintf(pbuf, pbuf_size, "%d ", channel->mode.limit);
-			pbuf_size-=strlen(pbuf);
-			pbuf+=strlen(pbuf);
-		}
-	}
-	if (*channel->mode.key)
-	{
-		if (mbuf_size) {
-			*mbuf++ = 'k';
-			mbuf_size--;
-		}
-		if (ismember && pbuf_size) {
-			ircsnprintf(pbuf, pbuf_size, "%s ", channel->mode.key);
-			pbuf_size-=strlen(pbuf);
-			pbuf+=strlen(pbuf);
+		if (cm->letter &&
+		    !cm->paracount &&
+		    !(hide_local_modes && cm->local) &&
+		    (channel->mode.mode & cm->mode))
+		{
+			strlcat_letter(mbuf, cm->letter, mbuf_size);
 		}
 	}
 
-	for (i=0; i <= Channelmode_highest; i++)
+	for (cm=channelmodes; cm; cm = cm->next)
 	{
-		if (Channelmode_Table[i].flag && Channelmode_Table[i].paracount &&
-		    (channel->mode.extmode & Channelmode_Table[i].mode)) {
-		        char flag = Channelmode_Table[i].flag;
-			if (mbuf_size) {
-				*mbuf++ = flag;
-				mbuf_size--;
-			}
+		if (cm->letter &&
+		    cm->paracount &&
+		    !(hide_local_modes && cm->local) &&
+		    (channel->mode.mode & cm->mode))
+		{
+			char flag = cm->letter;
+
+			if (mbuf_size)
+				strlcat_letter(mbuf, flag, mbuf_size);
+
 			if (ismember)
 			{
-				ircsnprintf(pbuf, pbuf_size, "%s ", cm_getparameter(channel, flag));
-				pbuf_size-=strlen(pbuf);
-				pbuf+=strlen(pbuf);
+				strlcat(pbuf, cm_getparameter(channel, flag), pbuf_size);
+				strlcat(pbuf, " ", pbuf_size);
 			}
 		}
 	}
 
 	/* Remove the trailing space from the parameters -- codemastr */
-	if (*pbuf) pbuf[strlen(pbuf)-1]=0;
-
-	if (!mbuf_size) mbuf--;
-	*mbuf++ = '\0';
-	return;
+	if (*pbuf)
+		pbuf[strlen(pbuf)-1]='\0';
 }
 
 /** Make a pretty mask from the input string - only used by SILENCE
  */
-char *pretty_mask(char *mask)
+char *pretty_mask(const char *mask_in)
 {
-	char *cp;
-	char *user;
-	char *host;
+	char mask[512];
+	char *cp, *user, *host;
+
+	strlcpy(mask, mask_in, sizeof(mask));
 
 	if ((user = strchr((cp = mask), '!')))
 		*user++ = '\0';
+
 	if ((host = strrchr(user ? user : cp, '@')))
 	{
 		*host++ = '\0';
@@ -745,7 +685,9 @@ char *pretty_mask(char *mask)
 			return make_nick_user_host(NULL, cp, host);
 	}
 	else if (!user && strchr(cp, '.'))
+	{
 		return make_nick_user_host(NULL, NULL, cp);
+	}
 	return make_nick_user_host(cp, user, host);
 }
 
@@ -772,31 +714,30 @@ char *trim_str(char *str, int len)
  * @param mask		The ban mask
  * @param what		MODE_DEL or MODE_ADD
  * @param client	The client adding/removing this ban mask
+ * @param conv_options	Options for BanContext.conv_options (eg BCTX_CONV_OPTION_WRITE_LETTER_BANS)
  * @returns pointer to correct banmask or NULL in case of error
  * @note A pointer is returned to a static buffer, which is overwritten
  *       on next clean_ban_mask or make_nick_user_host call.
  */
-char *clean_ban_mask(char *mask, int what, Client *client)
+const char *clean_ban_mask(const char *mask_in, int what, Client *client, int conv_options)
 {
 	char *cp, *x;
 	char *user;
 	char *host;
-	Extban *p;
-	static char maskbuf[512];
+	static char mask[512];
+
+	/* Strip any ':' at beginning since that would cause a desync */
+	for (; (*mask_in && (*mask_in == ':')); mask_in++);
+	if (!*mask_in)
+		return NULL;
 
 	/* Work on a copy */
-	strlcpy(maskbuf, mask, sizeof(maskbuf));
-	mask = maskbuf;
+	strlcpy(mask, mask_in, sizeof(mask));
 
 	cp = strchr(mask, ' ');
 	if (cp)
 		*cp = '\0';
 
-	/* Strip any ':' at beginning since that would cause a desync */
-	for (; (*mask && (*mask == ':')); mask++);
-	if (!*mask)
-		return NULL;
-
 	/* Forbid ASCII <= 32 in all bans */
 	for (x = mask; *x; x++)
 		if (*x <= ' ')
@@ -805,6 +746,9 @@ char *clean_ban_mask(char *mask, int what, Client *client)
 	/* Extended ban? */
 	if (is_extended_ban(mask))
 	{
+		const char *nextbanstr;
+		Extban *extban;
+
 		if (RESTRICT_EXTENDEDBANS && MyUser(client) && !ValidatePermissionsForPath("immune:restrict-extendedbans",client,NULL,NULL,NULL))
 		{
 			if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
@@ -819,8 +763,9 @@ char *clean_ban_mask(char *mask, int what, Client *client)
 				return NULL;
 			}
 		}
-		p = findmod_by_bantype(mask[1]);
-		if (!p)
+
+		extban = findmod_by_bantype(mask, &nextbanstr);
+		if (!extban)
 		{
 			/* extended bantype not supported, what to do?
 			 * Here are the rules:
@@ -833,8 +778,21 @@ char *clean_ban_mask(char *mask, int what, Client *client)
 				return mask; /* allow it */
 			return NULL; /* reject */
 		}
-		if (p->conv_param)
-			return p->conv_param(mask);
+
+		if (extban->conv_param)
+		{
+			const char *ret;
+			static char retbuf[512];
+			BanContext *b = safe_alloc(sizeof(BanContext));
+			b->client = client;
+			b->what = what;
+			b->banstr = nextbanstr;
+			b->conv_options = conv_options;
+			ret = extban->conv_param(b, extban);
+			ret = prefix_with_extban(ret, b, extban, retbuf, sizeof(retbuf));
+			safe_free(b);
+			return ret;
+		}
 		/* else, do some basic sanity checks and cut it off at 80 bytes */
 		if ((mask[1] != ':') || (mask[2] == '\0'))
 		    return NULL; /* require a ":<char>" after extban type */
@@ -864,11 +822,23 @@ char *clean_ban_mask(char *mask, int what, Client *client)
 int find_invex(Channel *channel, Client *client)
 {
 	Ban *inv;
+	BanContext *b = safe_alloc(sizeof(BanContext));
+
+	b->client = client;
+	b->channel = channel;
+	b->ban_check_types = BANCHK_JOIN;
 
 	for (inv = channel->invexlist; inv; inv = inv->next)
-		if (ban_check_mask(client, channel, inv->banstr, BANCHK_JOIN, NULL, NULL, 0))
+	{
+		b->banstr = inv->banstr;
+		if (ban_check_mask(b))
+		{
+			safe_free(b);
 			return 1;
+		}
+	}
 
+	safe_free(b);
 	return 0;
 }
 
@@ -924,120 +894,66 @@ int valid_channelname(const char *cname)
 	return 1; /* Valid */
 }
 
-/** Get existing channel 'chname' or create a new one.
- * @param client	User creating or searching this channel
- * @param chname	Channel name
+void initlist_channels(void)
+{
+	channel_pool = mp_pool_new(sizeof(Channel), 512 * 1024);
+}
+
+/** Create channel 'name' (or if it exists, return the existing one)
+ * @param name		Channel name
  * @param flag		If set to 'CREATE' then the channel is
  *			created if it does not exist.
  * @returns Pointer to channel (new or existing).
  * @note Be sure to call valid_channelname() first before
  *       you blindly call this function!
  */
-Channel *get_channel(Client *client, char *chname, int flag)
+Channel *make_channel(const char *name)
 {
 	Channel *channel;
-	int  len;
+	int len;
+	char *p;
+	char namebuf[CHANNELLEN+1];
 
-	if (BadPtr(chname))
+	if (BadPtr(name))
 		return NULL;
 
-	len = strlen(chname);
-	if (MyUser(client) && len > CHANNELLEN)
-	{
-		len = CHANNELLEN;
-		*(chname + CHANNELLEN) = '\0';
-	}
-	if ((channel = find_channel(chname, NULL)))
-		return (channel);
-	if (flag == CREATE)
+	/* Copy and silently truncate */
+	strlcpy(namebuf, name, sizeof(namebuf));
+
+	/* Copied from valid_channelname(), the minimal requirements */
+	for (p = namebuf; *p; p++)
 	{
-		channel = safe_alloc(sizeof(Channel) + len);
-		strlcpy(channel->chname, chname, len + 1);
-		if (channels)
-			channels->prevch = channel;
-		channel->topic = NULL;
-		channel->topic_nick = NULL;
-		channel->prevch = NULL;
-		channel->nextch = channels;
-		channel->creationtime = MyUser(client) ? TStime() : 0;
-		channels = channel;
-		add_to_channel_hash_table(chname, channel);
-		irccounts.channels++;
-		RunHook2(HOOKTYPE_CHANNEL_CREATE, client, channel);
+		if (*p < 33 || *p == ',' || *p == ':')
+		{
+			*p = '\0';
+			break;
+		}
 	}
-	return channel;
-}
 
-/** Register an invite from someone to a channel - so they can bypass +i etc.
- * @param from		The person sending the invite
- * @param to		The person who is invited to join
- * @param channel	The channel
- * @param mtags		Message tags associated with this INVITE command
- */
-void add_invite(Client *from, Client *to, Channel *channel, MessageTag *mtags)
-{
-	Link *inv, *tmp;
+	/* Exists? Return it. */
+	if ((channel = find_channel(name)))
+		return channel;
 
-	del_invite(to, channel);
-	/* If too many invite entries then delete the oldest one */
-	if (list_length(to->user->invited) >= MAXCHANNELSPERUSER)
-	{
-		for (tmp = to->user->invited; tmp->next; tmp = tmp->next)
-			;
-		del_invite(to, tmp->value.channel);
+	channel = mp_pool_get(channel_pool);
+	memset(channel, 0, sizeof(Channel));
 
-	}
-	/* We get pissy over too many invites per channel as well now,
-	 * since otherwise mass-inviters could take up some major
-	 * resources -Donwulff
-	 */
-	if (list_length(channel->invites) >= MAXCHANNELSPERUSER)
-	{
-		for (tmp = channel->invites; tmp->next; tmp = tmp->next)
-			;
-		del_invite(tmp->value.client, channel);
-	}
-	/*
-	 * add client to the beginning of the channel invite list
-	 */
-	inv = make_link();
-	inv->value.client = to;
-	inv->next = channel->invites;
-	channel->invites = inv;
-	/*
-	 * add channel to the beginning of the client invite list
-	 */
-	inv = make_link();
-	inv->value.channel = channel;
-	inv->next = to->user->invited;
-	to->user->invited = inv;
+	strlcpy(channel->name, name, sizeof(channel->name));
 
-	RunHook4(HOOKTYPE_INVITE, from, to, channel, mtags);
-}
+	if (channels)
+		channels->prevch = channel;
 
-/** Delete a previous invite of someone to a channel.
- * @param client	The client who was invited
- * @param channel	The channel to which the person was invited
- */
-void del_invite(Client *client, Channel *channel)
-{
-	Link **inv, *tmp;
+	channel->topic = NULL;
+	channel->topic_nick = NULL;
+	channel->prevch = NULL;
+	channel->nextch = channels;
+	channel->creationtime = TStime();
+	channels = channel;
+	add_to_channel_hash_table(channel->name, channel);
+	irccounts.channels++;
 
-	for (inv = &(channel->invites); (tmp = *inv); inv = &tmp->next)
-		if (tmp->value.client == client)
-		{
-			*inv = tmp->next;
-			free_link(tmp);
-			break;
-		}
+	RunHook(HOOKTYPE_CHANNEL_CREATE, channel);
 
-	for (inv = &(client->user->invited); (tmp = *inv); inv = &tmp->next)
-		if (tmp->value.channel == channel)
-		{
-			*inv = tmp->next;
-			free_link(tmp);
-			break;
-		}
+	return channel;
 }
 
 /** Is the user 'client' invited to channel 'channel' by a chanop?
@@ -1046,12 +962,9 @@ void del_invite(Client *client, Channel *channel)
  */
 int is_invited(Client *client, Channel *channel)
 {
-	Link *lp;
-
-	for (lp = client->user->invited; lp; lp = lp->next)
-		if (lp->value.channel == channel)
-			return 1;
-	return 0;
+	int invited = 0;
+	RunHook(HOOKTYPE_IS_INVITED, client, channel, &invited);
+	return invited;
 }
 
 /** Subtract one user from channel i. Free the channel if it became empty.
@@ -1072,7 +985,7 @@ int sub1_from_channel(Channel *channel)
 	channel->users = 0; /* to be sure */
 
 	/* If the channel is +P then this hook will actually stop destruction. */
-	RunHook2(HOOKTYPE_CHANNEL_DESTROY, channel, &should_destroy);
+	RunHook(HOOKTYPE_CHANNEL_DESTROY, channel, &should_destroy);
 	if (!should_destroy)
 		return 0;
 
@@ -1082,9 +995,6 @@ int sub1_from_channel(Channel *channel)
 
 	moddata_free_channel(channel);
 
-	while ((lp = channel->invites))
-		del_invite(lp->value.client, channel);
-
 	while (channel->banlist)
 	{
 		ban = channel->banlist;
@@ -1111,7 +1021,7 @@ int sub1_from_channel(Channel *channel)
 	}
 
 	/* free extcmode params */
-	extcmode_free_paramlist(channel->mode.extmodeparams);
+	extcmode_free_paramlist(channel->mode.mode_params);
 
 	safe_free(channel->mode_lock);
 	safe_free(channel->topic);
@@ -1124,10 +1034,10 @@ int sub1_from_channel(Channel *channel)
 
 	if (channel->nextch)
 		channel->nextch->prevch = channel->prevch;
-	del_from_channel_hash_table(channel->chname, channel);
+	del_from_channel_hash_table(channel->name, channel);
 
 	irccounts.channels--;
-	safe_free(channel);
+	mp_pool_release(channel);
 	return 1;
 }
 
@@ -1143,7 +1053,7 @@ void set_channel_mlock(Client *client, Channel *channel, const char *newmlock, i
 	if (propagate)
 	{
 		sendto_server(client, 0, 0, NULL, ":%s MLOCK %lld %s :%s",
-			      client->id, (long long)channel->creationtime, channel->chname,
+			      client->id, (long long)channel->creationtime, channel->name,
 			      BadPtr(channel->mode_lock) ? "" : channel->mode_lock);
 	}
 }
@@ -1159,14 +1069,14 @@ void set_channel_mlock(Client *client, Channel *channel, const char *newmlock, i
  * int ret;
  * for (ret = parse_chanmode(&pm, modebuf, parabuf); ret; ret = parse_chanmode(&pm, NULL, NULL))
  * {
- *         ircd_log(LOG_ERROR, "Got %c%c %s",
- *                  pm.what == MODE_ADD ? '+' : '-',
- *                  pm.modechar,
- *                  pm.param ? pm.param : "");
+ *         unreal_log(ULOG_INFO, "test", "TEST", "Got %c%c %s",
+ *                    pm.what == MODE_ADD ? '+' : '-',
+ *                    pm.modechar,
+ *                    pm.param ? pm.param : "");
  * }
  * @endcode
  */
-int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in)
+int parse_chanmode(ParseMode *pm, const char *modebuf_in, const char *parabuf_in)
 {
 	if (modebuf_in)
 	{
@@ -1196,7 +1106,7 @@ int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in)
 		else
 		{
 			CoreChannelModeTable *tab = &corechannelmodetable[0];
-			int i;
+			Cmode *cm;
 			int eatparam = 0;
 
 			/* Set some defaults */
@@ -1216,32 +1126,31 @@ int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in)
 				/* INTERNAL MODE */
 				if (tab->parameters)
 				{
-					if ((pm->what == MODE_DEL) && (tab->flag == 'l'))
-						eatparam = 0; /* -l is special: no parameter required */
-					else
-						eatparam = 1; /* all other internal parameter modes do require a parameter on unset */
+					eatparam = 1;
 				}
 			} else {
 				/* EXTENDED CHANNEL MODE */
 				int found = 0;
-				for (i=0; i <= Channelmode_highest; i++)
-					if (Channelmode_Table[i].flag == *pm->modebuf)
+				for (cm=channelmodes; cm; cm = cm->next)
+				{
+					if (cm->letter == *pm->modebuf)
 					{
 						found = 1;
 						break;
 					}
+				}
 				if (!found)
 				{
 					/* Not found. Will be ignored, just move on.. */
 					pm->modebuf++;
 					continue;
 				}
-				pm->extm = &Channelmode_Table[i];
-				if (Channelmode_Table[i].paracount == 1)
+				pm->extm = cm;
+				if (cm->paracount == 1)
 				{
 					if (pm->what == MODE_ADD)
 						eatparam = 1;
-					else if (Channelmode_Table[i].unset_with_param)
+					else if (cm->unset_with_param)
 						eatparam = 1;
 					/* else 0 (if MODE_DEL && !unset_with_param) */
 				}
@@ -1252,7 +1161,7 @@ int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in)
 				/* Hungry.. */
 				if (pm->parabuf && *pm->parabuf)
 				{
-					char *start, *end;
+					const char *start, *end;
 					for (; *pm->parabuf == ' '; pm->parabuf++); /* skip whitespace */
 					start = pm->parabuf;
 					if (*pm->parabuf == '\0')
@@ -1274,6 +1183,7 @@ int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in)
 						strlcpy(pm->buf, start, sizeof(pm->buf));
 						pm->parabuf = pm->parabuf + strlen(pm->parabuf); /* point to \0 at end */
 					}
+					stripcrlf(pm->buf); /* needed for unreal_server_compat.c */
 					pm->param = pm->buf;
 				} else {
 					pm->modebuf++;
@@ -1319,7 +1229,7 @@ int user_can_see_member(Client *user, Client *target, Channel *channel)
 	}
 
 	/* We must ensure that user is allowed to "see" target */
-	if (j != 0 && !(is_skochanop(target, channel) || has_voice(target,channel)) && !is_skochanop(user, channel))
+	if (j != 0 && !(check_channel_access(target, channel, "hoaq") || check_channel_access(target,channel, "v")) && !check_channel_access(user, channel, "hoaq"))
 		return 0;
 
 	return 1;
@@ -1341,7 +1251,7 @@ int invisible_user_in_channel(Client *target, Channel *channel)
 	}
 
 	/* We must ensure that user is allowed to "see" target */
-	if (j != 0 && !(is_skochanop(target, channel) || has_voice(target,channel)))
+	if (j != 0 && !(check_channel_access(target, channel, "hoaq") || check_channel_access(target,channel, "v")))
 		return 1;
 
 	return 0;
@@ -1352,9 +1262,9 @@ int invisible_user_in_channel(Client *target, Channel *channel)
  * @param client      The client to send the message to.
  * @param channelname The (invalid) channel that the user tried to join.
  */
-void send_invalid_channelname(Client *client, char *channelname)
+void send_invalid_channelname(Client *client, const char *channelname)
 {
-	char *reason;
+	const char *reason;
 
 	if (*channelname != '#')
 	{
@@ -1383,10 +1293,60 @@ void send_invalid_channelname(Client *client, char *channelname)
 
 /** Is the provided string possibly an extended ban?
  * Note that it still may not exist, it just tests the first part.
+ * @param str	The string to check (eg "~account:xyz")
  */
 int is_extended_ban(const char *str)
 {
-	if ((str[0] == '~') && (str[1] != '\0') && (str[2] == ':'))
+	const char *p;
+
+	if (*str != '~')
+		return 0;
+	for (p = str+1; *p; p++)
+	{
+		if (!isalnum(*p))
+		{
+			if (*p == ':')
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/** Is the provided string possibly an extended server ban?
+ * Actually this is only a very light check.
+ * It may still not exist, it just tests the first part.
+ * @param str	The string to check (eg "~account:xyz")
+ * The only difference between this and is_extended_ban()
+ * is that we allow a % at the beginning for soft-bans.
+ * @see is_extended_ban()
+ */
+int is_extended_server_ban(const char *str)
+{
+	if (*str == '%')
+		str++;
+	return is_extended_ban(str);
+}
+
+/** Check if it is an empty (useless) mode, namely "", "+" or "-".
+ * Typically called as: empty_mode(modebuf)
+ */
+int empty_mode(const char *m)
+{
+	if (!*m || (((m[0] == '+') || (m[0] == '-')) && m[1] == '\0'))
 		return 1;
 	return 0;
 }
+
+/** Free everything of/in a MultiLineMode */
+void free_multilinemode(MultiLineMode *m)
+{
+	int i;
+	if (m == NULL)
+		return;
+	for (i=0; i < m->numlines; i++)
+	{
+		safe_free(m->modeline[i]);
+		safe_free(m->paramline[i]);
+	}
+	safe_free(m);
+}
diff --git a/src/conf.c b/src/conf.c
@@ -31,13 +31,6 @@ struct ConfigCommand
 	int 	(*testfunc)(ConfigFile *conf, ConfigEntry *ce);
 };
 
-typedef struct NameValue NameValue;
-struct NameValue
-{
-	long	flag;
-	char	*name;
-};
-
 
 /* Config commands */
 
@@ -65,7 +58,6 @@ static int	_conf_deny_version	(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_require		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_allow_channel	(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_loadmodule	(ConfigFile *conf, ConfigEntry *ce);
-static int	_conf_log		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_alias		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_help		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_offchans		(ConfigFile *conf, ConfigEntry *ce);
@@ -99,7 +91,6 @@ static int	_test_deny		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_allow_channel	(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_loadmodule	(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_blacklist_module	(ConfigFile *conf, ConfigEntry *ce);
-static int	_test_log		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_alias		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_help		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_offchans		(ConfigFile *conf, ConfigEntry *ce);
@@ -124,7 +115,7 @@ static ConfigCommand _ConfigCommands[] = {
 	{ "link", 		_conf_link,		_test_link	},
 	{ "listen", 		_conf_listen,		_test_listen	},
 	{ "loadmodule",		NULL,		 	_test_loadmodule},
-	{ "log",		_conf_log,		_test_log	},
+	{ "log",		config_run_log,		config_test_log	},
 	{ "me", 		_conf_me,		_test_me	},
 	{ "official-channels", 	_conf_offchans,		_test_offchans	},
 	{ "oper", 		_conf_oper,		_test_oper	},
@@ -159,22 +150,6 @@ static NameValue _LinkFlags[] = {
 };
 
 /* This MUST be alphabetized */
-static NameValue _LogFlags[] = {
-	{ LOG_CHGCMDS, "chg-commands" },
-	{ LOG_CLIENT, "connects" },
-	{ LOG_ERROR, "errors" },
-	{ LOG_FLOOD, "flood" },
-	{ LOG_KILL, "kills" },
-	{ LOG_KLINE, "kline" },
-	{ LOG_OPER, "oper" },
-	{ LOG_OVERRIDE, "oper-override" },
-	{ LOG_SACMDS, "sadmin-commands" },
-	{ LOG_SERVER, "server-connects" },
-	{ LOG_SPAMFILTER, "spamfilter" },
-	{ LOG_TKL, "tkl" },
-};
-
-/* This MUST be alphabetized */
 static NameValue _TLSFlags[] = {
 	{ TLSFLAG_FAILIFNOCERT, "fail-if-no-clientcert" },
 	{ TLSFLAG_DISABLECLIENTCERT, "no-client-certificate" },
@@ -194,28 +169,25 @@ struct SetCheck settings;
  * Utilities
 */
 
-void	port_range(char *string, int *start, int *end);
-long	config_checkval(char *value, unsigned short flags);
+void	port_range(const char *string, int *start, int *end);
+long	config_checkval(const char *value, unsigned short flags);
 
 /*
  * Parser
 */
 
-ConfigFile		*config_load(char *filename, char *displayname);
+ConfigFile		*config_load(const char *filename, const char *displayname);
 void			config_free(ConfigFile *cfptr);
-ConfigFile		*config_parse_with_offset(char *filename, char *confdata, unsigned int line_offset);
-ConfigFile	 	*config_parse(char *filename, char *confdata);
-ConfigEntry		*config_find_entry(ConfigEntry *ce, char *name);
+ConfigFile		*config_parse_with_offset(const char *filename, char *confdata, unsigned int line_offset);
+ConfigFile	 	*config_parse(const char *filename, char *confdata);
+ConfigEntry		*config_find_entry(ConfigEntry *ce, const char *name);
 
-extern void add_entropy_configfile(struct stat *st, char *buf);
-extern void unload_all_unused_snomasks(void);
+extern void add_entropy_configfile(struct stat *st, const char *buf);
 extern void unload_all_unused_umodes(void);
 extern void unload_all_unused_extcmodes(void);
 extern void unload_all_unused_caps(void);
 extern void unload_all_unused_history_backends(void);
-
 int reloadable_perm_module_unloaded(void);
-
 int tls_tests(void);
 
 /* Conf sub-sub-functions */
@@ -226,10 +198,11 @@ void free_tls_options(TLSOptions *tlsoptions);
 /*
  * Config parser (IRCd)
 */
-int			init_conf(char *rootconf, int rehash);
-int			load_conf(char *filename, const char *original_path);
+int			config_read_file(const char *filename, const char *display_name);
 void			config_rehash();
-int			config_run();
+int			config_run_blocks();
+int	config_test_blocks();
+
 /*
  * Configuration linked lists
 */
@@ -247,7 +220,6 @@ ConfigItem_operclass	*conf_operclass = NULL;
 ConfigItem_listen	*conf_listen = NULL;
 ConfigItem_sni		*conf_sni = NULL;
 ConfigItem_allow	*conf_allow = NULL;
-ConfigItem_except	*conf_except = NULL;
 ConfigItem_vhost	*conf_vhost = NULL;
 ConfigItem_link		*conf_link = NULL;
 ConfigItem_ban		*conf_ban = NULL;
@@ -255,9 +227,8 @@ 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_log		*conf_log = NULL;
 ConfigItem_alias	*conf_alias = NULL;
-ConfigItem_include	*conf_include = NULL;
+ConfigResource	*config_resources = NULL;
 ConfigItem_blacklist_module	*conf_blacklist_module = NULL;
 ConfigItem_help		*conf_help = NULL;
 ConfigItem_offchans	*conf_offchans = NULL;
@@ -274,25 +245,19 @@ MODVAR Client *remote_rehash_client = NULL;
 MODVAR int			config_error_flag = 0;
 int			config_verbose = 0;
 
-MODVAR int need_34_upgrade = 0;
 int need_operclass_permissions_upgrade = 0;
+int invalid_snomasks_encountered = 0;
 int have_tls_listeners = 0;
 char *port_6667_ip = NULL;
 
-void add_include(const char *filename, const char *included_from, int included_from_line);
-#ifdef USE_LIBCURL
-void add_remote_include(const char *, const char *, int, const char *, const char *included_from, int included_from_line);
-void update_remote_include(ConfigItem_include *inc, const char *file, int, const char *errorbuf);
-int remote_include(ConfigEntry *ce);
-#endif
-void unload_notloaded_includes(void);
-void load_includes(void);
-void unload_loaded_includes(void);
-int rehash_internal(Client *client, int sig);
-int is_blacklisted_module(char *name);
+int add_config_resource(const char *resource, int type, ConfigEntry *ce);
+void resource_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *rs_key);
+void free_all_config_resources(void);
+int rehash_internal(Client *client);
+int is_blacklisted_module(const char *name);
 
 /** Return the printable string of a 'cep' location, such as set::something::xyz */
-char *config_var(ConfigEntry *cep)
+const char *config_var(ConfigEntry *cep)
 {
 	static char buf[256];
 	ConfigEntry *e;
@@ -305,9 +270,9 @@ char *config_var(ConfigEntry *cep)
 	buf[0] = '\0';
 
 	/* First, walk back to the top */
-	for (e = cep; e; e = e->ce_prevlevel)
+	for (e = cep; e; e = e->parent)
 	{
-		elem[numel++] = e->ce_varname;
+		elem[numel++] = e->name;
 		if (numel == 15)
 			break;
 	}
@@ -323,9 +288,12 @@ char *config_var(ConfigEntry *cep)
 	return buf;
 }
 
-void port_range(char *string, int *start, int *end)
+void port_range(const char *string, int *start, int *end)
 {
-	char *c = strchr(string, '-');
+	char buf[256];
+	char *c;
+	strlcpy(buf, string, sizeof(buf));
+	c = strchr(buf, '-');
 	if (!c)
 	{
 		int tmp = atoi(string);
@@ -346,18 +314,21 @@ void port_range(char *string, int *start, int *end)
  * RETURNS: 0 for parse error, 1 if ok.
  * REMARK: times&period should be ints!
  */
-int config_parse_flood(char *orig, int *times, int *period)
+int config_parse_flood(const char *orig, int *times, int *period)
 {
-char *x;
+	char buf[256];
+	char *x;
+
+	strlcpy(buf, orig, sizeof(buf));
 
 	*times = *period = 0;
-	x = strchr(orig, ':');
+	x = strchr(buf, ':');
 	/* 'blah', ':blah', '1:' */
-	if (!x || (x == orig) || (*(x+1) == '\0'))
+	if (!x || (x == buf) || (*(x+1) == '\0'))
 		return 0;
 
 	*x = '\0';
-	*times = atoi(orig);
+	*times = atoi(buf);
 	*period = config_checkval(x+1, CFG_TIME);
 	*x = ':'; /* restore */
 	return 1;
@@ -441,6 +412,12 @@ int flood_option_is_for_everyone(const char *name)
 	return text_in_array(name, opts);
 }
 
+/** Free a FloodSettings struct */
+void free_floodsettings(FloodSettings *f)
+{
+	safe_free(f->name);
+	safe_free(f);
+}
 
 /** Parses a value like '5:60s' into a flood setting that we can store.
  * @param str		The string to parse (eg: '5:60s')
@@ -485,7 +462,8 @@ int config_parse_flood_generic(const char *str, Configuration *conf, char *block
 	return 1;
 }
 
-long config_checkval(char *orig, unsigned short flags) {
+long config_checkval(const char *orig, unsigned short flags)
+{
 	char *value = raw_strdup(orig);
 	char *text;
 	long ret = 0;
@@ -594,24 +572,20 @@ long config_checkval(char *orig, unsigned short flags) {
 /** Free configuration setting for set::modes-on-join */
 void free_conf_channelmodes(struct ChMode *store)
 {
-	int i;
-
-	store->mode = 0;
-	store->extmodes = 0;
-	for (i = 0; i < EXTCMODETABLESZ; i++)
-		safe_free(store->extparams[i]);
+	memset(store, 0, sizeof(struct ChMode));
 }
 
 /* Set configuration, used for set::modes-on-join */
-void conf_channelmodes(char *modes, struct ChMode *store, int warn)
+void conf_channelmodes(const char *modes, struct ChMode *store)
 {
-	CoreChannelModeTable *tab;
+	Cmode *cm;
+	const char *m;
 	char *params = strchr(modes, ' ');
 	char *parambuf = NULL;
-	char *param = NULL;
+	const char *param = NULL;
+	const char *param_in;
 	char *save = NULL;
-
-	warn = 0; // warn is broken
+	int found;
 
 	/* Free existing parameters first (no inheritance) */
 	free_conf_channelmodes(store);
@@ -623,107 +597,86 @@ void conf_channelmodes(char *modes, struct ChMode *store, int warn)
 		param = strtoken(&save, parambuf, " ");
 	}
 
-	for (; *modes && *modes != ' '; modes++)
+	for (m = modes; *m && *m != ' '; m++)
 	{
-		if (*modes == '+')
+		if (*m == '+')
 			continue;
-		if (*modes == '-')
-		/* When a channel is created it has no modes, so just ignore if the
-		 * user asks us to unset anything -- codemastr
-		 */
+
+		if (*m == '-')
 		{
-			while (*modes && *modes != '+')
-				modes++;
+			/* When a channel is created it has no modes, so just ignore if the
+			 * user asks us to unset anything -- codemastr
+			 */
+			while (*m && *m != '+')
+				m++;
 			continue;
 		}
-		for (tab = &corechannelmodetable[0]; tab->mode; tab++)
-		{
-			if (tab->flag == *modes)
-			{
-				if (tab->parameters)
-				{
-					/* INCOMPATIBLE */
-					break;
-				}
-				store->mode |= tab->mode;
-				break;
-			}
-		}
-		/* Try extcmodes */
-		if (!tab->mode)
+
+		found = 0;
+		for (cm=channelmodes; cm; cm = cm->next)
 		{
-			int i;
-			for (i=0; i <= Channelmode_highest; i++)
+			if (!(cm->letter))
+				continue;
+			if (*m == cm->letter)
 			{
-				if (!(Channelmode_Table[i].flag))
-					continue;
-				if (*modes == Channelmode_Table[i].flag)
+				found = 1;
+				if (cm->paracount)
 				{
-					if (Channelmode_Table[i].paracount)
+					if (!param)
 					{
-						if (!param)
-							break;
-						param = Channelmode_Table[i].conv_param(param, NULL, NULL);
-						if (!param)
-							break; /* invalid parameter fmt, do not set mode. */
-						store->extparams[i] = raw_strdup(param);
-						/* Get next parameter */
-						param = strtoken(&save, NULL, " ");
+						config_warn("set::modes-on-join '%s'. Parameter missing for mode %c.", modes, *m);
+						break;
 					}
-					store->extmodes |= Channelmode_Table[i].mode;
-					break;
+					param_in = param; /* save it */
+					param = cm->conv_param(param, NULL, NULL);
+					if (!param)
+					{
+						config_warn("set::modes-on-join '%s'. Parameter for mode %c is invalid (%s).", modes, *m, param_in);
+						break; /* invalid parameter fmt, do not set mode. */
+					}
+					safe_strdup(store->extparams[cm->letter], param);
+					/* Get next parameter */
+					param = strtoken(&save, NULL, " ");
 				}
+				store->extmodes |= cm->mode;
+				break;
 			}
 		}
+		if (!found)
+			config_warn("set::modes-on-join '%s'. Channel mode %c not found.", modes, *m);
 	}
 	safe_free(parambuf);
 }
 
 void chmode_str(struct ChMode *modes, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size)
 {
-	CoreChannelModeTable *tab;
-	int i;
+	Cmode *cm;
 
 	if (!(mbuf_size && pbuf_size))
 		return;
 
 	*pbuf = 0;
 	*mbuf++ = '+';
-	if (--mbuf_size == 0) return;
-	for (tab = &corechannelmodetable[0]; tab->mode; tab++)
-	{
-		if (modes->mode & tab->mode)
-		{
-			if (!tab->parameters)
-			{
-				*mbuf++ = tab->flag;
-				if (!--mbuf_size)
-				{
-					*--mbuf=0;
-					break;
-				}
-			}
-		}
-	}
-	for (i=0; i <= Channelmode_highest; i++)
+
+	for (cm=channelmodes; cm; cm = cm->next)
 	{
-		if (!(Channelmode_Table[i].flag))
+		if (!(cm->letter))
 			continue;
 
-		if (modes->extmodes & Channelmode_Table[i].mode)
+		if (modes->extmodes & cm->mode)
 		{
 			if (mbuf_size)
 			{
-				*mbuf++ = Channelmode_Table[i].flag;
+				*mbuf++ = cm->letter;
 				if (!--mbuf_size)
 				{
 					*--mbuf=0;
 					break;
 				}
 			}
-			if (Channelmode_Table[i].paracount)
+			if (cm->paracount)
 			{
-				strlcat(pbuf, modes->extparams[i], pbuf_size);
+				strlcat(pbuf, modes->extparams[cm->letter], pbuf_size);
 				strlcat(pbuf, " ", pbuf_size);
 			}
 		}
@@ -731,87 +684,26 @@ void chmode_str(struct ChMode *modes, char *mbuf, char *pbuf, size_t mbuf_size, 
 	*mbuf=0;
 }
 
-int channellevel_to_int(char *s)
+const char *channellevel_to_string(const char *s)
 {
 	/* Requested at http://bugs.unrealircd.org/view.php?id=3852 */
 	if (!strcmp(s, "none"))
-		return CHFL_DEOPPED;
+		return "";
 	if (!strcmp(s, "voice"))
-		return CHFL_VOICE;
+		return "v";
 	if (!strcmp(s, "halfop"))
-		return CHFL_HALFOP;
+		return "h";
 	if (!strcmp(s, "op") || !strcmp(s, "chanop"))
-		return CHFL_CHANOP;
-	if (!strcmp(s, "protect") || !strcmp(s, "chanprot"))
-#ifdef PREFIX_AQ
-		return CHFL_CHANADMIN;
-#else
-		return CHFL_CHANOP|CHFL_CHANADMIN;
-#endif
+		return "o";
+	if (!strcmp(s, "protect") || !strcmp(s, "chanprot") || !strcmp(s, "chanadmin") || !strcmp(s, "admin"))
+		return "a";
 	if (!strcmp(s, "owner") || !strcmp(s, "chanowner"))
-#ifdef PREFIX_AQ
-		return CHFL_CHANOWNER;
-#else
-		return CHFL_CHANOP|CHFL_CHANOWNER;
-#endif
-
-	return 0; /* unknown or unsupported */
-}
-
-/* Channel flag (eg: CHFL_CHANOWNER) to SJOIN symbol (eg: *).
- * WARNING: Do not confuse SJOIN symbols with prefixes in /NAMES!
- */
-char *chfl_to_sjoin_symbol(int s)
-{
-	switch(s)
-	{
-		case CHFL_VOICE:
-			return "+";
-		case CHFL_HALFOP:
-			return "%";
-		case CHFL_CHANOP:
-			return "@";
-		case CHFL_CHANADMIN:
-#ifdef PREFIX_AQ
-			return "~";
-#else
-			return "~@";
-#endif
-		case CHFL_CHANOWNER:
-#ifdef PREFIX_AQ
-			return "*";
-#else
-			return "*@";
-#endif
-		case CHFL_DEOPPED:
-		default:
-			return "";
-	}
-	/* NOT REACHED */
-}
+		return "q";
 
-char chfl_to_chanmode(int s)
-{
-	switch(s)
-	{
-		case CHFL_VOICE:
-			return 'v';
-		case CHFL_HALFOP:
-			return 'h';
-		case CHFL_CHANOP:
-			return 'o';
-		case CHFL_CHANADMIN:
-			return 'a';
-		case CHFL_CHANOWNER:
-			return 'q';
-		case CHFL_DEOPPED:
-		default:
-			return '\0';
-	}
-	/* NOT REACHED */
+	return NULL; /* unknown or unsupported */
 }
 
-Policy policy_strtoval(char *s)
+Policy policy_strtoval(const char *s)
 {
 	if (!s)
 		return 0;
@@ -828,7 +720,7 @@ Policy policy_strtoval(char *s)
 	return 0;
 }
 
-char *policy_valtostr(Policy policy)
+const char *policy_valtostr(Policy policy)
 {
 	if (policy == POLICY_ALLOW)
 		return "allow";
@@ -850,7 +742,7 @@ char policy_valtochar(Policy policy)
 	return '?';
 }
 
-AllowedChannelChars allowed_channelchars_strtoval(char *str)
+AllowedChannelChars allowed_channelchars_strtoval(const char *str)
 {
 	if (!strcmp(str, "ascii"))
 		return ALLOWED_CHANNELCHARS_ASCII;
@@ -861,7 +753,7 @@ AllowedChannelChars allowed_channelchars_strtoval(char *str)
 	return 0;
 }
 
-char *allowed_channelchars_valtostr(AllowedChannelChars v)
+const char *allowed_channelchars_valtostr(AllowedChannelChars v)
 {
 	switch(v)
 	{
@@ -879,7 +771,7 @@ char *allowed_channelchars_valtostr(AllowedChannelChars v)
 }
 
 /* Used for set::automatic-ban-target and set::manual-ban-target */
-BanTarget ban_target_strtoval(char *str)
+BanTarget ban_target_strtoval(const char *str)
 {
 	if (!strcmp(str, "ip"))
 		return BAN_TARGET_IP;
@@ -897,7 +789,7 @@ BanTarget ban_target_strtoval(char *str)
 }
 
 /* Used for set::automatic-ban-target and set::manual-ban-target */
-char *ban_target_valtostr(BanTarget v)
+const char *ban_target_valtostr(BanTarget v)
 {
 	switch(v)
 	{
@@ -918,7 +810,7 @@ char *ban_target_valtostr(BanTarget v)
 	}
 }
 
-HideIdleTimePolicy hideidletime_strtoval(char *str)
+HideIdleTimePolicy hideidletime_strtoval(const char *str)
 {
 	if (!strcmp(str, "never"))
 		return HIDE_IDLE_TIME_NEVER;
@@ -931,7 +823,7 @@ HideIdleTimePolicy hideidletime_strtoval(char *str)
 	return 0;
 }
 
-char *hideidletime_valtostr(HideIdleTimePolicy v)
+const char *hideidletime_valtostr(HideIdleTimePolicy v)
 {
 	switch(v)
 	{
@@ -948,7 +840,7 @@ char *hideidletime_valtostr(HideIdleTimePolicy v)
 	}
 }
 
-ConfigFile *config_load(char *filename, char *displayname)
+ConfigFile *config_load(const char *filename, const char *displayname)
 {
 	struct stat sb;
 	int			fd;
@@ -1013,10 +905,10 @@ void config_free(ConfigFile *cfptr)
 
 	for(;cfptr;cfptr=nptr)
 	{
-		nptr = cfptr->cf_next;
-		if (cfptr->cf_entries)
-			config_entry_free_all(cfptr->cf_entries);
-		safe_free(cfptr->cf_filename);
+		nptr = cfptr->next;
+		if (cfptr->items)
+			config_entry_free_all(cfptr->items);
+		safe_free(cfptr->filename);
 		safe_free(cfptr);
 	}
 }
@@ -1043,7 +935,7 @@ void unreal_del_quotes(char *i)
 }
 
 /** Add quotes to a line, eg some"thing becomes some\"thing - extended version */
-int unreal_add_quotes_r(char *i, char *o, size_t len)
+int unreal_add_quotes_r(const char *i, char *o, size_t len)
 {
 	if (len == 0)
 		return 0;
@@ -1079,7 +971,7 @@ int unreal_add_quotes_r(char *i, char *o, size_t len)
 }	
 
 /** Add quotes to a line, eg some"thing becomes some\"thing */
-char *unreal_add_quotes(char *str)
+const char *unreal_add_quotes(const char *str)
 {
 	static char qbuf[2048];
 	
@@ -1088,14 +980,15 @@ char *unreal_add_quotes(char *str)
 	return qbuf;
 }
 
-ConfigFile *config_parse(char *filename, char *confdata){
+ConfigFile *config_parse(const char *filename, char *confdata)
+{
 	return config_parse_with_offset(filename, confdata, 0);
 }
 
 /* This is the internal parser, made by Chris Behrens & Fred Jacobs <2005.
  * Enhanced (or mutilated) by Bram Matthys over the years (2015-2019).
  */
-ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned int line_offset)
+ConfigFile *config_parse_with_offset(const char *filename, char *confdata, unsigned int line_offset)
 {
 	char		*ptr;
 	char		*start;
@@ -1110,8 +1003,8 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 	ConditionalConfig *cc, *cc_list = NULL;
 
 	curcf = safe_alloc(sizeof(ConfigFile));
-	safe_strdup(curcf->cf_filename, filename);
-	lastce = &(curcf->cf_entries);
+	safe_strdup(curcf->filename, filename);
+	lastce = &(curcf->items);
 	curce = NULL;
 	cursection = NULL;
 	/* Replace \r's with spaces .. ugly ugly -Stskeeps */
@@ -1131,8 +1024,8 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					break;
 				}
 				*lastce = curce;
-				lastce = &(curce->ce_next);
-				curce->ce_fileposend = (ptr - confdata);
+				lastce = &(curce->next);
+				curce->file_position_end = (ptr - confdata);
 				curce = NULL;
 				break;
 			case '{':
@@ -1144,7 +1037,7 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					errors++;
 					continue;
 				}
-				else if (curce->ce_entries)
+				else if (curce->items)
 				{
 					config_error("%s:%i: New section start but previous section did not end properly. "
 					             "Check line %d and the line(s) before, you are likely missing a '};' there.\n",
@@ -1152,8 +1045,8 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					errors++;
 					continue;
 				}
-				curce->ce_sectlinenum = linenumber;
-				lastce = &(curce->ce_entries);
+				curce->section_linenumber = linenumber;
+				lastce = &(curce->items);
 				cursection = curce;
 				curce = NULL;
 				break;
@@ -1176,20 +1069,20 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					continue;
 				}
 				curce = cursection;
-				cursection->ce_fileposend = (ptr - confdata);
-				cursection = cursection->ce_prevlevel;
+				cursection->file_position_end = (ptr - confdata);
+				cursection = cursection->parent;
 				if (!cursection)
-					lastce = &(curcf->cf_entries);
+					lastce = &(curcf->items);
 				else
-					lastce = &(cursection->ce_entries);
-				for(;*lastce;lastce = &((*lastce)->ce_next))
+					lastce = &(cursection->items);
+				for(;*lastce;lastce = &((*lastce)->next))
 					continue;
 				if (*(ptr+1) != ';')
 				{
 					/* Simulate closing ; so you can get away with } instead of ugly }; */
 					*lastce = curce;
-					lastce = &(curce->ce_next);
-					curce->ce_fileposend = (ptr - confdata);
+					lastce = &(curce->next);
+					curce->file_position_end = (ptr - confdata);
 					curce = NULL;
 				}
 				break;
@@ -1239,17 +1132,21 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					}
 				}
 				break;
+			case '\'':
+				if (curce)
+					curce->escaped = 1;
+				/* fallthrough */
 			case '\"':
-				if (curce && curce->ce_varlinenum != linenumber && cursection)
+				if (curce && curce->line_number != linenumber && cursection)
 				{
 					config_error("%s:%i: Missing semicolon (';') at end of line. "
 					             "Line %d must end with a ; character\n",
-						filename, curce->ce_varlinenum, curce->ce_varlinenum);
+						filename, curce->line_number, curce->line_number);
 					errors++;
 
 					*lastce = curce;
-					lastce = &(curce->ce_next);
-					curce->ce_fileposend = (ptr - confdata);
+					lastce = &(curce->next);
+					curce->file_position_end = (ptr - confdata);
 					curce = NULL;
 				}
 
@@ -1258,14 +1155,18 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 				{
 					if (*ptr == '\\')
 					{
-						if ((ptr[1] == '\\') || (ptr[1] == '"'))
+						if (strchr("\\\"'", ptr[1]))
 						{
 							/* \\ or \" in config file (escaped) */
 							ptr++; /* skip */
 							continue;
 						}
 					}
-					else if ((*ptr == '\"') || (*ptr == '\n'))
+					else if (*ptr == '\n')
+						break;
+					else if (curce && curce->escaped && (*ptr == '\''))
+						break;
+					else if ((!curce || !curce->escaped) && (*ptr == '"'))
 						break;
 				}
 				if (!*ptr || (*ptr == '\n'))
@@ -1279,7 +1180,7 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 				}
 				if (curce)
 				{
-					if (curce->ce_vardata)
+					if (curce->value)
 					{
 						config_error("%s:%i: Extra data detected. Perhaps missing a ';' or one too many?\n",
 							filename, linenumber);
@@ -1287,22 +1188,22 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					}
 					else
 					{
-						safe_strldup(curce->ce_vardata, start, ptr-start+1);
-						preprocessor_replace_defines(&curce->ce_vardata, curce);
-						unreal_del_quotes(curce->ce_vardata);
+						safe_strldup(curce->value, start, ptr-start+1);
+						preprocessor_replace_defines(&curce->value, curce);
+						unreal_del_quotes(curce->value);
 					}
 				}
 				else
 				{
 					curce = safe_alloc(sizeof(ConfigEntry));
-					curce->ce_varlinenum = linenumber;
-					curce->ce_fileptr = curcf;
-					curce->ce_prevlevel = cursection;
-					curce->ce_fileposstart = (start - confdata);
-					safe_strldup(curce->ce_varname, start, ptr-start+1);
-					preprocessor_replace_defines(&curce->ce_varname, curce);
-					unreal_del_quotes(curce->ce_varname);
-					preprocessor_cc_duplicate_list(cc_list, &curce->ce_cond);
+					curce->line_number = linenumber;
+					curce->file = curcf;
+					curce->parent = cursection;
+					curce->file_position_start = (start - confdata);
+					safe_strldup(curce->name, start, ptr-start+1);
+					preprocessor_replace_defines(&curce->name, curce);
+					unreal_del_quotes(curce->name);
+					preprocessor_cc_duplicate_list(cc_list, &curce->conditional_config);
 				}
 				break;
 			case '\n':
@@ -1372,11 +1273,11 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					if (curce)
 						config_error("%s: End of file reached but directive or block at line %i did not end properly. "
 									 "Perhaps a missing ; (semicolon) somewhere?\n",
-							filename, curce->ce_varlinenum);
+							filename, curce->line_number);
 					else if (cursection)
 						config_error("%s: End of file reached but the section which starts at line %i did never end properly. "
 									 "Perhaps a missing }; ?\n",
-								filename, cursection->ce_sectlinenum);
+								filename, cursection->section_linenumber);
 					else
 						config_error("%s: Unexpected end of file. Some line or block did not end properly. "
 						             "Look for any missing } and };\n", filename);
@@ -1387,7 +1288,7 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 				}
 				if (curce)
 				{
-					if (curce->ce_vardata)
+					if (curce->value)
 					{
 						config_error("%s:%i: Extra data detected. Check for a missing ; character at or around line %d\n",
 							filename, linenumber, linenumber-1);
@@ -1395,23 +1296,23 @@ ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned in
 					}
 					else
 					{
-						safe_strldup(curce->ce_vardata, start, ptr-start+1);
-						preprocessor_replace_defines(&curce->ce_vardata, curce);
+						safe_strldup(curce->value, start, ptr-start+1);
+						preprocessor_replace_defines(&curce->value, curce);
 					}
 				}
 				else
 				{
 					curce = safe_alloc(sizeof(ConfigEntry));
 					memset(curce, 0, sizeof(ConfigEntry));
-					curce->ce_varlinenum = linenumber;
-					curce->ce_fileptr = curcf;
-					curce->ce_prevlevel = cursection;
-					curce->ce_fileposstart = (start - confdata);
-					safe_strldup(curce->ce_varname, start, ptr-start+1);
-					preprocessor_replace_defines(&curce->ce_varname, curce);
-					if (curce->ce_cond)
-						abort(); // hmm this can be reached? FIXME!
-					preprocessor_cc_duplicate_list(cc_list, &curce->ce_cond);
+					curce->line_number = linenumber;
+					curce->file = curcf;
+					curce->parent = cursection;
+					curce->file_position_start = (start - confdata);
+					safe_strldup(curce->name, start, ptr-start+1);
+					preprocessor_replace_defines(&curce->name, curce);
+					if (curce->conditional_config)
+						abort();
+					preprocessor_cc_duplicate_list(cc_list, &curce->conditional_config);
 				}
 				if ((*ptr == ';') || (*ptr == '\n'))
 					ptr--;
@@ -1425,7 +1326,7 @@ breakout:
 	{
 		config_error("%s: End of file reached but directive or block at line %i did not end properly. "
 		             "Perhaps a missing ; (semicolon) somewhere?\n",
-			filename, curce->ce_varlinenum);
+			filename, curce->line_number);
 		errors++;
 		config_entry_free_all(curce);
 	}
@@ -1433,7 +1334,7 @@ breakout:
 	{
 		config_error("%s: End of file reached but the section which starts at line %i did never end properly. "
 		             "Perhaps a missing }; ?\n",
-				filename, cursection->ce_sectlinenum);
+				filename, cursection->section_linenumber);
 		errors++;
 	}
 
@@ -1455,13 +1356,13 @@ void config_entry_free_all(ConfigEntry *ce)
 
 	for(;ce;ce=nptr)
 	{
-		nptr = ce->ce_next;
-		if (ce->ce_entries)
-			config_entry_free_all(ce->ce_entries);
-		safe_free(ce->ce_varname);
-		safe_free(ce->ce_vardata);
-		if (ce->ce_cond)
-			preprocessor_cc_free_list(ce->ce_cond);
+		nptr = ce->next;
+		if (ce->items)
+			config_entry_free_all(ce->items);
+		safe_free(ce->name);
+		safe_free(ce->value);
+		if (ce->conditional_config)
+			preprocessor_cc_free_list(ce->conditional_config);
 		safe_free(ce);
 	}
 }
@@ -1471,21 +1372,21 @@ void config_entry_free_all(ConfigEntry *ce)
  */
 void config_entry_free(ConfigEntry *ce)
 {
-	if (ce->ce_entries)
-		config_entry_free_all(ce->ce_entries);
-	safe_free(ce->ce_varname);
-	safe_free(ce->ce_vardata);
-	if (ce->ce_cond)
-		preprocessor_cc_free_list(ce->ce_cond);
+	if (ce->items)
+		config_entry_free_all(ce->items);
+	safe_free(ce->name);
+	safe_free(ce->value);
+	if (ce->conditional_config)
+		preprocessor_cc_free_list(ce->conditional_config);
 	safe_free(ce);
 }
 
-ConfigEntry *config_find_entry(ConfigEntry *ce, char *name)
+ConfigEntry *config_find_entry(ConfigEntry *ce, const char *name)
 {
 	ConfigEntry *cep;
 
-	for (cep = ce; cep; cep = cep->ce_next)
-		if (cep->ce_varname && !strcmp(cep->ce_varname, name))
+	for (cep = ce; cep; cep = cep->next)
+		if (cep->name && !strcmp(cep->name, name))
 			break;
 	return cep;
 }
@@ -1501,8 +1402,7 @@ void config_error(FORMAT_STRING(const char *format), ...)
 	va_end(ap);
 	if ((ptr = strchr(buffer, '\n')) != NULL)
 		*ptr = '\0';
-	ircd_log(LOG_ERROR, "config error: %s", buffer);
-	sendto_realops("error: %s", buffer);
+	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 */
@@ -1560,8 +1460,7 @@ void config_status(FORMAT_STRING(const char *format), ...)
 	va_end(ap);
 	if ((ptr = strchr(buffer, '\n')) != NULL)
 		*ptr = '\0';
-	ircd_log(LOG_ERROR, "%s", buffer);
-	sendto_realops("%s", buffer);
+	unreal_log_raw(ULOG_INFO, "config", "CONFIG_INFO_GENERIC", NULL, buffer);
 	if (remote_rehash_client)
 		sendnotice(remote_rehash_client, "%s", buffer);
 }
@@ -1577,8 +1476,7 @@ void config_warn(FORMAT_STRING(const char *format), ...)
 	va_end(ap);
 	if ((ptr = strchr(buffer, '\n')) != NULL)
 		*ptr = '\0';
-	ircd_log(LOG_ERROR, "[warning] %s", buffer);
-	sendto_realops("[warning] %s", buffer);
+	unreal_log_raw(ULOG_WARNING, "config", "CONFIG_WARNING_GENERIC", NULL, buffer);
 	if (remote_rehash_client)
 		sendnotice(remote_rehash_client, "[warning] %s", buffer);
 }
@@ -1593,50 +1491,38 @@ int config_test_openfile(ConfigEntry *cep, int flags, mode_t mode, const char *e
 {
 	int fd;
 
-	if(!cep->ce_vardata)
+	if (!cep->value)
 	{
-		if(fatal)
+		if (fatal)
 			config_error("%s:%i: %s: <no file specified>: no file specified",
-				     cep->ce_fileptr->cf_filename,
-				     cep->ce_varlinenum,
+				     cep->file->filename,
+				     cep->line_number,
 				     entry);
 		else
 
 			config_warn("%s:%i: %s: <no file specified>: no file specified",
-				    cep->ce_fileptr->cf_filename,
-				    cep->ce_varlinenum,
+				    cep->file->filename,
+				    cep->line_number,
 				    entry);
 		return 1;
 	}
 
 	/* There's not much checking that can be done for asynchronously downloaded files */
-#ifdef USE_LIBCURL
-	if(url_is_valid(cep->ce_vardata))
+	if (url_is_valid(cep->value))
 	{
-		if(allow_url)
+		if (allow_url)
 			return 0;
 
 		/* but we can check if a URL is used wrongly :-) */
 		config_warn("%s:%i: %s: %s: URL used where not allowed",
-			    cep->ce_fileptr->cf_filename,
-			    cep->ce_varlinenum,
-			    entry, cep->ce_vardata);
-		if(fatal)
+			    cep->file->filename,
+			    cep->line_number,
+			    entry, cep->value);
+		if (fatal)
 			return 1;
 		else
 			return 0;
 	}
-#else
-	if (strstr(cep->ce_vardata, "://"))
-	{
-		config_error("%s:%d: %s: UnrealIRCd was not compiled with remote includes support "
-		             "so you cannot use URLs here.",
-		             cep->ce_fileptr->cf_filename,
-		             cep->ce_varlinenum,
-		             entry);
-		return 1;
-	}
-#endif /* USE_LIBCURL */
 
 	/*
 	 * Make sure that files are created with the correct mode. This is
@@ -1645,25 +1531,25 @@ int config_test_openfile(ConfigEntry *cep, int flags, mode_t mode, const char *e
 	 * and that we deal with all of the bugs that come with complexity.
 	 * The only files we may be creating are the tunefile and pidfile so far.
 	 */
-	if(flags & O_CREAT)
-		fd = open(cep->ce_vardata, flags, mode);
+	if (flags & O_CREAT)
+		fd = open(cep->value, flags, mode);
 	else
-		fd = open(cep->ce_vardata, flags);
-	if(fd == -1)
+		fd = open(cep->value, flags);
+	if (fd == -1)
 	{
-		if(fatal)
+		if (fatal)
 			config_error("%s:%i: %s: %s: %s",
-				     cep->ce_fileptr->cf_filename,
-				     cep->ce_varlinenum,
+				     cep->file->filename,
+				     cep->line_number,
 				     entry,
-				     cep->ce_vardata,
+				     cep->value,
 				     strerror(errno));
 		else
 			config_warn("%s:%i: %s: %s: %s",
-				     cep->ce_fileptr->cf_filename,
-				     cep->ce_varlinenum,
+				     cep->file->filename,
+				     cep->line_number,
 				     entry,
-				     cep->ce_vardata,
+				     cep->value,
 				     strerror(errno));
 		return 1;
 	}
@@ -1673,16 +1559,16 @@ int config_test_openfile(ConfigEntry *cep, int flags, mode_t mode, const char *e
 
 int config_is_blankorempty(ConfigEntry *cep, const char *block)
 {
-	if (!cep->ce_vardata)
+	if (!cep->value)
 	{
-		config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, block,
-			cep->ce_varname);
+		config_error_empty(cep->file->filename, cep->line_number, block,
+			cep->name);
 		return 1;
 	}
 	return 0;
 }
 
-ConfigCommand *config_binary_search(char *cmd) {
+ConfigCommand *config_binary_search(const char *cmd) {
 	int start = 0;
 	int stop = ARRAY_SIZEOF(_ConfigCommands)-1;
 	int mid;
@@ -1702,7 +1588,8 @@ ConfigCommand *config_binary_search(char *cmd) {
 
 void	free_iConf(Configuration *i)
 {
-	safe_free(i->dns_bindip);
+	FloodSettings *f, *f_next;
+
 	safe_free(i->link_bindip);
 	safe_free(i->kline_address);
 	safe_free(i->gline_address);
@@ -1711,7 +1598,6 @@ void	free_iConf(Configuration *i)
 	safe_free(i->oper_auto_join_chans);
 	safe_free(i->allow_user_stats);
 	// allow_user_stats_ext is freed elsewhere
-	safe_free(i->egd_path);
 	safe_free(i->static_quit);
 	safe_free(i->static_part);
 	free_tls_options(i->tls_options);
@@ -1725,6 +1611,7 @@ void	free_iConf(Configuration *i)
 	safe_free(i->restrict_channelmodes);
 	safe_free(i->restrict_extendedbans);
 	safe_free(i->channel_command_prefix);
+	safe_free(i->level_on_join);
 	safe_free(i->spamfilter_ban_reason);
 	safe_free(i->spamfilter_virus_help_channel);
 	// spamexcept is freed elsewhere
@@ -1734,25 +1621,30 @@ void	free_iConf(Configuration *i)
 	safe_free(i->reject_message_unauthorized);
 	safe_free(i->reject_message_kline);
 	safe_free(i->reject_message_gline);
-	// network struct:
-	safe_free(i->network.x_ircnetwork);
-	safe_free(i->network.x_ircnet005);
-	safe_free(i->network.x_defserv);
-	safe_free(i->network.x_services_name);
-	safe_free(i->network.x_hidden_host);
-	safe_free(i->network.x_prefix_quit);
-	safe_free(i->network.x_helpchan);
-	safe_free(i->network.x_stats_server);
-	safe_free(i->network.x_sasl_server);
+	safe_free(i->network_name);
+	safe_free(i->network_name_005);
+	safe_free(i->default_server);
+	safe_free(i->services_name);
+	safe_free(i->cloak_prefix);
+	safe_free(i->prefix_quit);
+	safe_free(i->helpchan);
+	safe_free(i->stats_server);
+	safe_free(i->sasl_server);
+	// anti-flood:
+	for (f = i->floodsettings; f; f = f_next)
+	{
+		f_next = f->next;
+		free_floodsettings(f);
+	}
+	i->floodsettings = NULL;
 }
 
-int	config_test();
-
 void config_setdefaultsettings(Configuration *i)
 {
 	char tmp[512];
 
-	safe_strdup(i->oper_snomask, SNO_DEFOPER);
+	safe_strdup(i->oper_snomask, OPER_SNOMASKS);
+	i->server_notice_colors = 1;
 	i->ident_read_timeout = 7;
 	i->ident_connect_timeout = 3;
 	i->ban_version_tkl_time = 86400; /* 1d */
@@ -1765,12 +1657,11 @@ void config_setdefaultsettings(Configuration *i)
 	i->maxchannelsperuser = 10;
 	i->maxdccallow = 10;
 	safe_strdup(i->channel_command_prefix, "`!.");
-	conf_channelmodes("+nt", &i->modes_on_join, 0);
 	i->conn_modes = set_usermode("+ixw");
 	i->check_target_nick_bans = 1;
 	i->maxbans = 60;
 	i->maxbanlength = 2048;
-	i->level_on_join = CHFL_CHANOP;
+	safe_strdup(i->level_on_join, "o");
 	i->watch_away_notification = 1;
 	i->uhnames = 1;
 	i->ping_cookie = 1;
@@ -1783,10 +1674,10 @@ void config_setdefaultsettings(Configuration *i)
 	i->kick_length = 307;
 	i->quit_length = 307;
 	safe_strdup(i->link_bindip, "*");
-	safe_strdup(i->network.x_hidden_host, "Clk");
+	safe_strdup(i->cloak_prefix, "Clk");
 	if (!ipv6_capable())
 		DISABLE_IPV6 = 1;
-	safe_strdup(i->network.x_prefix_quit, "Quit");
+	safe_strdup(i->prefix_quit, "Quit");
 	i->max_unknown_connections_per_ip = 3;
 	i->handshake_timeout = 30;
 	i->sasl_timeout = 15;
@@ -1807,6 +1698,7 @@ void config_setdefaultsettings(Configuration *i)
 	config_parse_flood_generic("4:60", i, "known-users", FLD_INVITE); /* INVITE flood protection: max 4 per 60s */
 	config_parse_flood_generic("4:120", i, "known-users", FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
 	config_parse_flood_generic("10:15", i, "known-users", FLD_CONVERSATIONS); /* 10 users, new user every 15s */
+	config_parse_flood_generic("180:750", i, "known-users", FLD_LAG_PENALTY); /* 180 bytes / 750 msec */
 	/* - unknown-users */
 	config_parse_flood_generic("2:60", i, "unknown-users", FLD_NICK); /* NICK flood protection: max 2 per 60s */
 	config_parse_flood_generic("2:90", i, "unknown-users", FLD_JOIN); /* JOIN flood protection: max 2 per 90s */
@@ -1814,8 +1706,9 @@ void config_setdefaultsettings(Configuration *i)
 	config_parse_flood_generic("2:60", i, "unknown-users", FLD_INVITE); /* INVITE flood protection: max 2 per 60s */
 	config_parse_flood_generic("2:120", i, "unknown-users", FLD_KNOCK); /* KNOCK protection: max 2 per 120s */
 	config_parse_flood_generic("4:15", i, "unknown-users", FLD_CONVERSATIONS); /* 4 users, new user every 15s */
+	config_parse_flood_generic("90:1000", i, "unknown-users", FLD_LAG_PENALTY); /* 90 bytes / 1000 msec */
 
-	/* SSL/TLS options */
+	/* TLS options */
 	i->tls_options = safe_alloc(sizeof(TLSOptions));
 	snprintf(tmp, sizeof(tmp), "%s/tls/server.cert.pem", CONFDIR);
 	safe_strdup(i->tls_options->certificate_file, tmp);
@@ -1862,19 +1755,8 @@ void config_setdefaultsettings(Configuration *i)
 	i->hide_idle_time = HIDE_IDLE_TIME_OPER_USERMODE;
 
 	i->who_limit = 100;
-}
 
-static void make_default_logblock(void)
-{
-	ConfigItem_log *ca = safe_alloc(sizeof(ConfigItem_log));
-
-	config_status("No log { } block found -- logging everything to 'ircd.log'");
-
-	safe_strdup(ca->file, "ircd.log");
-	convert_to_absolute_path(&ca->file, LOGDIR);
-	ca->flags |= LOG_CHGCMDS|LOG_CLIENT|LOG_ERROR|LOG_KILL|LOG_KLINE|LOG_OPER|LOG_OVERRIDE|LOG_SACMDS|LOG_SERVER|LOG_SPAMFILTER|LOG_TKL;
-	ca->logfd = -1;
-	AddListItem(ca, conf_log);
+	i->named_extended_bans = 1;
 }
 
 /** Similar to config_setdefaultsettings but this one is applied *AFTER*
@@ -1886,13 +1768,20 @@ void postconf_defaults(void)
 	TKL *tk;
 	char *encoded;
 
+	if (!iConf.modes_on_join_set)
+	{
+		/* We could not do this in config_setdefaultsettings()
+		 * because the channel mode modules were not initialized yet.
+		 */
+		conf_channelmodes("+nt", &iConf.modes_on_join);
+	}
 	if (!iConf.plaintext_policy_user_message)
 	{
 		/* The message depends on whether it's reject or warn.. */
 		if (iConf.plaintext_policy_user == POLICY_DENY)
-			addmultiline(&iConf.plaintext_policy_user_message, "Insecure connection. Please reconnect using SSL/TLS.");
+			addmultiline(&iConf.plaintext_policy_user_message, "Insecure connection. Please reconnect using TLS.");
 		else if (iConf.plaintext_policy_user == POLICY_WARN)
-			addmultiline(&iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using SSL/TLS.");
+			addmultiline(&iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using TLS.");
 	}
 
 	if (!iConf.plaintext_policy_oper_message)
@@ -1900,76 +1789,32 @@ void postconf_defaults(void)
 		/* The message depends on whether it's reject or warn.. */
 		if (iConf.plaintext_policy_oper == POLICY_DENY)
 		{
-			addmultiline(&iConf.plaintext_policy_oper_message, "You need to use a secure connection (SSL/TLS) in order to /OPER.");
+			addmultiline(&iConf.plaintext_policy_oper_message, "You need to use a secure connection (TLS) in order to /OPER.");
 			addmultiline(&iConf.plaintext_policy_oper_message, "See https://www.unrealircd.org/docs/FAQ#oper-requires-tls");
 		}
 		else if (iConf.plaintext_policy_oper == POLICY_WARN)
-			addmultiline(&iConf.plaintext_policy_oper_message, "WARNING: You /OPER'ed up from an insecure connection. Please consider using SSL/TLS.");
+			addmultiline(&iConf.plaintext_policy_oper_message, "WARNING: You /OPER'ed up from an insecure connection. Please consider using TLS.");
 	}
 
 	if (!iConf.outdated_tls_policy_user_message)
 	{
 		/* The message depends on whether it's reject or warn.. */
 		if (iConf.outdated_tls_policy_user == POLICY_DENY)
-			safe_strdup(iConf.outdated_tls_policy_user_message, "Your IRC client is using an outdated SSL/TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
+			safe_strdup(iConf.outdated_tls_policy_user_message, "Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
 		else if (iConf.outdated_tls_policy_user == POLICY_WARN)
-			safe_strdup(iConf.outdated_tls_policy_user_message, "WARNING: Your IRC client is using an outdated SSL/TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
+			safe_strdup(iConf.outdated_tls_policy_user_message, "WARNING: Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
 	}
 
 	if (!iConf.outdated_tls_policy_oper_message)
 	{
 		/* The message depends on whether it's reject or warn.. */
 		if (iConf.outdated_tls_policy_oper == POLICY_DENY)
-			safe_strdup(iConf.outdated_tls_policy_oper_message, "Your IRC client is using an outdated SSL/TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
+			safe_strdup(iConf.outdated_tls_policy_oper_message, "Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
 		else if (iConf.outdated_tls_policy_oper == POLICY_WARN)
-			safe_strdup(iConf.outdated_tls_policy_oper_message, "WARNING: Your IRC client is using an outdated SSL/TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
-	}
-
-	/* We got a chicken-and-egg problem here.. antries added without reason or ban-time
-	 * field should use the config default (set::spamfilter::ban-reason/ban-time) but
-	 * this isn't (or might not) be known yet when parsing spamfilter entries..
-	 * so we do a VERY UGLY mass replace here.. unless someone else has a better idea.
-	 */
-
-	encoded = unreal_encodespace(SPAMFILTER_BAN_REASON);
-	if (!encoded)
-		abort(); /* hack to trace 'impossible' bug... */
-	// FIXME: remove this stuff with ~server~, why not just use -config-
-	//        which is more meaningful.
-	for (tk = tklines[tkl_hash('q')]; tk; tk = tk->next)
-	{
-		if (tk->type != TKL_NAME)
-			continue;
-		if (!tk->set_by)
-		{
-			if (me.name[0] != '\0')
-				safe_strdup(tk->set_by, me.name);
-			else
-				safe_strdup(tk->set_by, conf_me->name ? conf_me->name : "~server~");
-		}
-	}
-
-	for (tk = tklines[tkl_hash('f')]; tk; tk = tk->next)
-	{
-		if (tk->type != TKL_SPAMF)
-			continue; /* global entry or something else.. */
-		if (!strcmp(tk->ptr.spamfilter->tkl_reason, "<internally added by ircd>"))
-		{
-			safe_strdup(tk->ptr.spamfilter->tkl_reason, encoded);
-			tk->ptr.spamfilter->tkl_duration = SPAMFILTER_BAN_TIME;
-		}
-		/* This one is even more ugly, but our config crap is VERY confusing :[ */
-		if (!tk->set_by)
-		{
-			if (me.name[0] != '\0')
-				safe_strdup(tk->set_by, me.name);
-			else
-				safe_strdup(tk->set_by, conf_me->name ? conf_me->name : "~server~");
-		}
+			safe_strdup(iConf.outdated_tls_policy_oper_message, "WARNING: Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
 	}
 
-	if (!conf_log)
-		make_default_logblock();
+	postconf_defaults_log_block();
 }
 
 void postconf_fixes(void)
@@ -2007,7 +1852,7 @@ RealCommand *cmptr;
  * has been read and almost all values have been set. This is to deal with
  * things like adding a default log { } block if there is none and that kind
  * of things.
- * This function is called by init_conf(), both on boot and on rehash.
+ * This function is called by config_test(), both on boot and on rehash.
  */
 void postconf(void)
 {
@@ -2016,6 +1861,11 @@ void postconf(void)
 	do_weird_shun_stuff();
 	isupport_init(); /* for all the 005 values that changed.. */
 	tls_check_expiry(NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+	if (loop.rehashing)
+		reinit_tls();
+#endif
 }
 
 int isanyserverlinked(void)
@@ -2044,41 +1894,11 @@ void applymeblock(void)
 		strlcpy(me.id, conf_me->sid, sizeof(me.id));
 }
 
-void upgrade_conf_to_34(void)
-{
-	config_error("******************************************************************");
-	config_error("This *seems* an UnrealIRCd 3.2.x configuration file.");
-
-#ifdef _WIN32
-	if (!IsService)
-		config_error("In next screen you will be prompted to automatically upgrade the configuration file(s).");
-	else
-	{
-		config_error("We offer a configuration file converter to convert 3.2.x conf's to 4.x, however this "
-		             "is not available when running as a service. If you want to use it, make UnrealIRCd "
-		             "run in GUI mode by running 'unreal uninstall'. Then start UnrealIRCd.exe and when "
-		             "it prompts you to convert the configuration click 'Yes'. Check if UnrealIRCd boots properly. "
-		             "Once everything is looking good you can run 'unreal install' to make UnrealIRCd run "
-		             "as a service again."); /* TODO: make this unnecessary :D */
-	}
-#else
-	config_error("To upgrade it to the new 4.x format, run: ./unrealircd upgrade-conf");
-#endif
-
-	config_error("******************************************************************");
-	/* TODO: win32 may require a different error */
-}
-
-/** Reset config tests (before running the config test) */
-void config_test_reset(void)
-{
-}
-
 /** Run config test and all post config tests. */
 int config_test_all(void)
 {
-	if ((config_test() < 0) || (callbacks_check() < 0) || (efunctions_check() < 0) ||
-	    reloadable_perm_module_unloaded() || !tls_tests())
+	if ((config_test_blocks() < 0) || (callbacks_check() < 0) || (efunctions_check() < 0) ||
+	    reloadable_perm_module_unloaded() || !tls_tests() || !log_tests())
 	{
 		return 0;
 	}
@@ -2101,19 +1921,19 @@ int config_loadmodules(void)
 
 	int fatal_ret = 0, ret;
 
-	for (cfptr = conf; cfptr; cfptr = cfptr->cf_next)
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
 	{
 		if (config_verbose > 1)
-			config_status("Testing %s", cfptr->cf_filename);
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+			config_status("Testing %s", cfptr->filename);
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
-			if (!strcmp(ce->ce_varname, "loadmodule"))
+			if (!strcmp(ce->name, "loadmodule"))
 			{
-				if (ce->ce_cond)
+				if (ce->conditional_config)
 				{
 					config_error("%s:%d: Currently you cannot have a 'loadmodule' statement "
 						     "within an @if block, sorry.",
-						     ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+						     ce->file->filename, ce->line_number);
 					return 0;
 				}
 				ret = _conf_loadmodule(cfptr, ce);
@@ -2139,262 +1959,299 @@ int config_loadmodules(void)
 	return 1; /* SUCCESS */
 }
 
-int	init_conf(char *rootconf, int rehash)
+/** Reject the configuration load.
+ * This is called both from boot and from rehash.
+ */
+void config_load_failed(void)
+{
+	if (conf)
+		unreal_log(ULOG_ERROR, "config", "CONFIG_NOT_LOADED", NULL, "IRCd configuration failed to load");
+	Unload_all_testing_modules();
+	free_all_config_resources();
+	config_free(conf);
+	conf = NULL;
+	free_iConf(&tempiConf);
+#ifdef _WIN32
+	if (!loop.rehashing)
+		win_error(); /* GUI popup */
+#endif
+}
+
+int config_read_start(void)
 {
-	char *old_pid_file = NULL;
+	int ret;
 
 	config_status("Loading IRCd configuration..");
+	loop.config_load_failed = 0;
+
 	if (conf)
 	{
 		config_error("%s:%i - Someone forgot to clean up", __FILE__, __LINE__);
 		return -1;
 	}
+
+	/* We set this to 1 because otherwise we may call rehash_internal()
+	 * already from config_read_file() which is too soon (race).
+	 */
+	loop.rehash_download_busy = 1;
+	add_config_resource(configfile, RESOURCE_INCLUDE, NULL);
+	ret = config_read_file(configfile, configfile);
+	loop.rehash_download_busy = 0;
+	if (ret < 0)
+	{
+		config_load_failed();
+		return -1;
+	}
+	return 1;
+}
+
+int is_config_read_finished(void)
+{
+	ConfigResource *rs;
+
+	if (loop.rehash_download_busy)
+		return 0;
+
+	for (rs = config_resources; rs; rs = rs->next)
+	{
+		if (rs->type & RESOURCE_DLQUEUED)
+		{
+			//config_status("Waiting for %s...", rs->url);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+int config_test(void)
+{
+	char *old_pid_file = NULL;
+
+	if (loop.config_load_failed)
+	{
+		/* An error was already printed to the user.
+		 * This happens in case of a failed loaded remote URL
+		 */
+		config_load_failed();
+		return -1;
+	}
+
+	config_status("Testing IRCd configuration..");
+
 	memset(&tempiConf, 0, sizeof(iConf));
 	memset(&settings, 0, sizeof(settings));
 	memset(&requiredstuff, 0, sizeof(requiredstuff));
 	memset(&nicklengths, 0, sizeof(nicklengths));
 	config_setdefaultsettings(&tempiConf);
 	clicap_pre_rehash();
+	log_pre_rehash();
 	free_config_defines();
-	/*
-	 * the rootconf must be listed in the conf_include for include
-	 * recursion prevention code and sanity checking code to be
-	 * made happy :-). Think of it as us implicitly making an
-	 * in-memory config file that looks like:
-	 *
-	 * include "unrealircd.conf";
-	 */
-	add_include(rootconf, "[thin air]", -1);
-	if ((load_conf(rootconf, rootconf) > 0) && config_loadmodules())
+
+	if (!config_loadmodules())
 	{
-		preprocessor_resolve_conditionals_all(PREPROCESSOR_PHASE_MODULE);
-		config_test_reset();
-		if (!config_test_all())
-		{
-			config_error("IRCd configuration failed to pass testing");
-#ifdef _WIN32
-			if (!rehash)
-				win_error();
-#endif
-			Unload_all_testing_modules();
-			unload_notloaded_includes();
-			config_free(conf);
-			conf = NULL;
-			free_iConf(&tempiConf);
-			return -1;
-		}
-		callbacks_switchover();
-		efunctions_switchover();
-		set_targmax_defaults();
-		set_security_group_defaults();
-		if (rehash)
-		{
-			Hook *h;
-			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)
-		        {
-				if (!h->owner)
-					continue;
-				if (!(h->owner->options & MOD_OPT_PERM))
-					continue;
-				(*(h->func.intfunc))();
-			}
-			unload_loaded_includes();
-		}
-		load_includes();
-		Init_all_testing_modules();
-		if (config_run() < 0)
-		{
-			config_error("Bad case of config errors. Server will now die. This really shouldn't happen");
-#ifdef _WIN32
-			if (!rehash)
-				win_error();
-#endif
-			abort();
-		}
-		applymeblock();
-		if (old_pid_file && strcmp(old_pid_file, conf_files->pid_file))
+		config_load_failed();
+		return -1;
+	}
+
+	preprocessor_resolve_conditionals_all(PREPROCESSOR_PHASE_MODULE);
+
+	if (!config_test_all())
+	{
+		config_error("IRCd configuration failed to pass testing");
+		config_load_failed();
+		return -1;
+	}
+	callbacks_switchover();
+	efunctions_switchover();
+	set_targmax_defaults();
+	set_security_group_defaults();
+	if (loop.rehashing)
+	{
+		Hook *h;
+		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)
 		{
-			sendto_ops("pidfile is being rewritten to %s, please delete %s",
-				   conf_files->pid_file,
-				   old_pid_file);
-			write_pidfile();
+			if (!h->owner)
+				continue;
+			if (!(h->owner->options & MOD_OPT_PERM))
+				continue;
+			(*(h->func.intfunc))();
 		}
-		safe_free(old_pid_file);
 	}
-	else
+	config_pre_run_log();
+
+	Init_all_testing_modules();
+
+	if (config_run_blocks() < 0)
 	{
-		config_error("IRCd configuration failed to load");
-		Unload_all_testing_modules();
-		unload_notloaded_includes();
-		config_free(conf);
-		conf = NULL;
-		free_iConf(&tempiConf);
+		config_error("Bad case of config errors. Server will now die. This really shouldn't happen");
 #ifdef _WIN32
-		if (!rehash)
+		if (!loop.rehashing)
 			win_error();
 #endif
-		return -1;
+		abort();
+	}
+
+	applymeblock();
+
+	if (old_pid_file && strcmp(old_pid_file, conf_files->pid_file))
+	{
+		write_pidfile();
+		unlink(old_pid_file);
 	}
+	safe_free(old_pid_file);
+
 	config_free(conf);
 	conf = NULL;
-	if (rehash)
+	if (loop.rehashing)
 	{
 		module_loadall();
-		RunHook0(HOOKTYPE_REHASH_COMPLETE);
+		RunHook(HOOKTYPE_REHASH_COMPLETE);
 	}
 	postconf();
-	config_status("Configuration loaded.");
+	unreal_log(ULOG_INFO, "config", "CONFIG_LOADED", NULL, "Configuration loaded");
 	clicap_post_rehash();
 	unload_all_unused_mtag_handlers();
 	return 0;
 }
 
+void config_parse_and_queue_urls(ConfigEntry *ce)
+{
+	for (; ce; ce = ce->next)
+	{
+		if (loop.config_load_failed)
+			break;
+		if (ce->name && !strcmp(ce->name, "include"))
+			continue; /* handled elsewhere */
+		if (ce->value && !ce->escaped && url_is_valid(ce->value))
+			add_config_resource(ce->value, 0, ce);
+		if (ce->items)
+			config_parse_and_queue_urls(ce->items);
+	}
+}
+
 /**
- * Processes filename as part of the IRCd's configuration.
+ * Read configuration file into ConfigEntry items and add it to the 'conf'
+ * list. This checks the file for parse errors, but doesn't do much
+ * otherwise. Only: module blacklist checking and checking for "include"
+ * items to see if we need to read and parse more configuration files
+ * that are included from this one.
  *
- * One _must_ call add_include() or add_remote_include() before
- * calling load_conf(). This way, include recursion may be detected
- * and reported to the user as an error instead of causing the IRCd to
- * hang in an infinite recursion, eat up memory, and eventually
- * overflow its stack ;-). (reported by warg).
- *
- * This function will set INCLUDE_USED on the config_include list
- * entry if the config file loaded without error.
+ * One _must_ call add_config_resource() before calling config_read_file().
+ * This way, include recursion may be detected and reported to the user
+ * as an error instead of causing the IRCd to hang in an infinite
+ * recursion, eat up memory, and eventually overflow its stack ;-).
  *
  * @param filename the file where the conf may be read from
- * @param original_path the path or URL used to refer to this file.
+ * @param display_name The path or URL used to refer to this file.
  *        (mostly to support remote includes' URIs for recursive include detection).
  * @return 1 on success, a negative number on error
  */
-int	load_conf(char *filename, const char *original_path)
+int config_read_file(const char *filename, const char *display_name)
 {
 	ConfigFile 	*cfptr, *cfptr2, **cfptr3;
 	ConfigEntry 	*ce;
-	ConfigItem_include *inc, *my_inc;
+	ConfigResource *rs;
 	int ret;
 	int counter;
 
 	if (config_verbose > 0)
 		config_status("Loading config file %s ..", filename);
 
-	need_34_upgrade = 0;
 	need_operclass_permissions_upgrade = 0;
 
-	/*
-	 * Check if we're accidentally including a file a second
+	/* Check if we're accidentally including a file a second
 	 * time. We should expect to find one entry in this list: the
 	 * entry for our current file.
+	 * Note that no user should be able to trigger this, this
+	 * can only happen if we have buggy code somewhere.
 	 */
 	counter = 0;
-	my_inc = NULL;
-	for (inc = conf_include; inc; inc = inc->next)
+	for (rs = config_resources; rs; rs = rs->next)
 	{
-		/*
-		 * ignore files which were part of a _previous_
-		 * successful rehash.
-		 */
-		if (!(inc->flag.type & INCLUDE_NOTLOADED))
-			continue;
-
-		if (!counter)
-			my_inc = inc;
-
-		if (!strcmp(filename, inc->file))
-		{
-			counter ++;
-			continue;
-		}
-#ifdef _WIN32
-		if (!strcasecmp(filename, inc->file))
+#ifndef _WIN32
+		if (rs->file && !strcmp(filename, rs->file))
+#else
+		if (rs->file && !strcasecmp(filename, rs->file))
+#endif
 		{
 			counter ++;
 			continue;
 		}
-#endif
-#ifdef USE_LIBCURL
-		if (inc->url && !strcmp(original_path, inc->url))
+		if (rs->url && !strcmp(display_name, rs->url))
 		{
 			counter ++;
 			continue;
 		}
-#endif
-	}
-	if (counter < 1 || !my_inc)
-	{
-		/*
-		 * The following is simply for debugging/[sanity
-		 * checking]. To make sure that functions call
-		 * add_include() or add_remote_include() before
-		 * calling us.
-		 */
-		config_error("I don't have a record for %s being included."
-			     " Perhaps someone forgot to call add_include()?",
-			     filename);
-		abort();
 	}
-	if (counter > 1 || my_inc->flag.type & INCLUDE_USED)
+	if (counter > 1)
 	{
-		config_error("%s:%d:include: Config file %s has been loaded before %d time."
-			     " You may include each file only once.",
-			     my_inc->included_from, my_inc->included_from_line,
-			     filename, counter - 1);
+		unreal_log(ULOG_ERROR, "config", "CONFIG_BUG_DUPLICATE_RESOURCE", NULL,
+		           "[BUG] Config file $file has been loaded $counter times. "
+		           "This should not happen. Someone forgot to call "
+		           "add_config_resource() or check its return value!",
+		           log_data_string("file", filename),
+		           log_data_integer("counter", counter));
 		return -1;
 	}
 	/* end include recursion checking code */
 
-	if ((cfptr = config_load(filename, NULL)))
+	if ((cfptr = config_load(filename, display_name)))
 	{
-		for (cfptr3 = &conf, cfptr2 = conf; cfptr2; cfptr2 = cfptr2->cf_next)
-			cfptr3 = &cfptr2->cf_next;
+		for (cfptr3 = &conf, cfptr2 = conf; cfptr2; cfptr2 = cfptr2->next)
+			cfptr3 = &cfptr2->next;
 		*cfptr3 = cfptr;
 
 		if (config_verbose > 1)
 			config_status("Loading module blacklist in %s", filename);
 
-		preprocessor_resolve_conditionals_ce(&cfptr->cf_entries, PREPROCESSOR_PHASE_INITIAL);
+		preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_INITIAL);
 
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
-			if (!strcmp(ce->ce_varname, "blacklist-module"))
+		for (ce = cfptr->items; ce; ce = ce->next)
+			if (!strcmp(ce->name, "blacklist-module"))
 				 _test_blacklist_module(cfptr, ce);
 
-		/* Load modules */
-		if (config_verbose > 1)
-			config_status("Loading modules in %s", filename);
-		if (need_34_upgrade)
-			upgrade_conf_to_34();
+		/* Load urls */
+		config_parse_and_queue_urls(cfptr->items);
+
+		if(loop.config_load_failed) /* something bad happened while processing urls */
+			return -1;
 
 		/* Load includes */
 		if (config_verbose > 1)
 			config_status("Searching through %s for include files..", filename);
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
-			if (!strcmp(ce->ce_varname, "include"))
+
+		for (ce = cfptr->items; ce; ce = ce->next)
+		{
+			if (!strcmp(ce->name, "include"))
 			{
-				if (ce->ce_cond)
+				if (ce->conditional_config)
 				{
 					config_error("%s:%d: Currently you cannot have an 'include' statement "
 					             "within an @if block, sorry. However, you CAN do it the other "
 					             "way around, that is: put the @if within the included file itself.",
-					             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+					             ce->file->filename, ce->line_number);
 					return -1;
 				}
 				ret = _conf_include(cfptr, ce);
-				if (need_34_upgrade)
-					upgrade_conf_to_34();
 				if (ret < 0)
 					return ret;
 			}
-		my_inc->flag.type |= INCLUDE_USED;
+		}
 		return 1;
 	}
 	else
 	{
-		config_error("Could not load config file %s", filename);
+		unreal_log(ULOG_ERROR, "config", "CONFIG_LOAD_FILE_FAILED", NULL,
+		           "Could not load configuration file: $resource",
+		           log_data_string("resource", display_name),
+		           log_data_string("filename", filename));
 #ifdef _WIN32
 		if (!strcmp(filename, "conf/unrealircd.conf"))
 		{
@@ -2445,13 +2302,12 @@ void remove_config_tkls(void)
 	}
 }
 
-void	config_rehash()
+void config_rehash()
 {
 	ConfigItem_oper			*oper_ptr;
 	ConfigItem_class 		*class_ptr;
 	ConfigItem_ulines 		*uline_ptr;
 	ConfigItem_allow 		*allow_ptr;
-	ConfigItem_except 		*except_ptr;
 	ConfigItem_ban 			*ban_ptr;
 	ConfigItem_link 		*link_ptr;
 	ConfigItem_listen	 	*listen_ptr;
@@ -2462,7 +2318,6 @@ void	config_rehash()
 	ConfigItem_allow_channel	*allow_channel_ptr;
 	ConfigItem_admin		*admin_ptr;
 	ConfigItem_deny_version		*deny_version_ptr;
-	ConfigItem_log			*log_ptr;
 	ConfigItem_alias		*alias_ptr;
 	ConfigItem_help			*help_ptr;
 	ConfigItem_offchans		*of_ptr;
@@ -2507,13 +2362,10 @@ void	config_rehash()
 		next = (ListStruct *)link_ptr->next;
 		if (link_ptr->refcount == 0)
 		{
-			Debug((DEBUG_ERROR, "s_conf: deleting block %s (refcount 0)", link_ptr->servername));
 			delete_linkblock(link_ptr);
 		}
 		else
 		{
-			Debug((DEBUG_ERROR, "s_conf: marking block %s (refcount %d) as temporary",
-				link_ptr->servername, link_ptr->refcount));
 			link_ptr->flag.temporary = 1;
 		}
 	}
@@ -2540,19 +2392,11 @@ void	config_rehash()
 	for (allow_ptr = conf_allow; allow_ptr; allow_ptr = (ConfigItem_allow *) next)
 	{
 		next = (ListStruct *)allow_ptr->next;
-		safe_free(allow_ptr->ip);
-		safe_free(allow_ptr->hostname);
+		unreal_delete_masks(allow_ptr->mask);
 		Auth_FreeAuthConfig(allow_ptr->auth);
 		DelListItem(allow_ptr, conf_allow);
 		safe_free(allow_ptr);
 	}
-	for (except_ptr = conf_except; except_ptr; except_ptr = (ConfigItem_except *) next)
-	{
-		next = (ListStruct *)except_ptr->next;
-		safe_free(except_ptr->mask);
-		DelListItem(except_ptr, conf_except);
-		safe_free(except_ptr);
-	}
 	/* Free ban realname { }, ban server { } and ban version { } */
 	for (ban_ptr = conf_ban; ban_ptr; ban_ptr = (ConfigItem_ban *) next)
 	{
@@ -2614,7 +2458,7 @@ void	config_rehash()
 	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);
-		safe_free(deny_link_ptr->mask);
+		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);
@@ -2657,14 +2501,6 @@ void	config_rehash()
 		conf_drpass->dieauth = NULL;
 		safe_free(conf_drpass);
 	}
-	for (log_ptr = conf_log; log_ptr; log_ptr = (ConfigItem_log *)next) {
-		next = (ListStruct *)log_ptr->next;
-		if (log_ptr->logfd != -1)
-			fd_close(log_ptr->logfd);
-		safe_free(log_ptr->file);
-		DelListItem(log_ptr, conf_log);
-		safe_free(log_ptr);
-	}
 	for (alias_ptr = conf_alias; alias_ptr; alias_ptr = (ConfigItem_alias *)next) {
 		RealCommand *cmptr = find_command(alias_ptr->alias, 0);
 		ConfigItem_alias_format *fmt;
@@ -2797,7 +2633,16 @@ int	config_post_test()
 	return errors;
 }
 
-int	config_run()
+/** Make the "read" config the "live" config */
+void config_switchover(void)
+{
+	free_iConf(&iConf);
+	memcpy(&iConf, &tempiConf, sizeof(iConf));
+	memset(&tempiConf, 0, sizeof(tempiConf));
+	log_blocks_switchover();
+}
+
+int	config_run_blocks()
 {
 	ConfigEntry 	*ce;
 	ConfigFile	*cfptr;
@@ -2807,13 +2652,13 @@ int	config_run()
 	ConfigItem_allow *allow;
 
 	/* Stage 1: set block first */
-	for (cfptr = conf; cfptr; cfptr = cfptr->cf_next)
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
 	{
 		if (config_verbose > 1)
-			config_status("Running %s", cfptr->cf_filename);
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+			config_status("Running %s", cfptr->filename);
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
-			if (!strcmp(ce->ce_varname, "set"))
+			if (!strcmp(ce->name, "set"))
 			{
 				if (_conf_set(cfptr, ce) < 0)
 					errors++;
@@ -2822,13 +2667,13 @@ int	config_run()
 	}
 
 	/* Stage 2: now class blocks */
-	for (cfptr = conf; cfptr; cfptr = cfptr->cf_next)
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
 	{
 		if (config_verbose > 1)
-			config_status("Running %s", cfptr->cf_filename);
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+			config_status("Running %s", cfptr->filename);
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
-			if (!strcmp(ce->ce_varname, "class"))
+			if (!strcmp(ce->name, "class"))
 			{
 				if (_conf_class(cfptr, ce) < 0)
 					errors++;
@@ -2837,23 +2682,23 @@ int	config_run()
 	}
 
 	/* Stage 3: now all the rest */
-	for (cfptr = conf; cfptr; cfptr = cfptr->cf_next)
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
 	{
 		if (config_verbose > 1)
-			config_status("Running %s", cfptr->cf_filename);
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+			config_status("Running %s", cfptr->filename);
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
 			/* These are already processed above (set, class)
-			 * or via config_test() (secret).
+			 * or via config_test_blocks() (secret).
 			 */
-			if (!strcmp(ce->ce_varname, "set") ||
-			    !strcmp(ce->ce_varname, "class") ||
-			    !strcmp(ce->ce_varname, "secret"))
+			if (!strcmp(ce->name, "set") ||
+			    !strcmp(ce->name, "class") ||
+			    !strcmp(ce->name, "secret"))
 			{
 				continue;
 			}
 
-			if ((cc = config_binary_search(ce->ce_varname))) {
+			if ((cc = config_binary_search(ce->name))) {
 				if ((cc->conffunc) && (cc->conffunc(cfptr, ce) < 0))
 					errors++;
 			}
@@ -2870,31 +2715,15 @@ int	config_run()
 		}
 	}
 
-	/*
-	 * transfer default values from set::ipv6_clones_mask into
-	 * each individual allow block. If other similar things like
-	 * this stack up here, perhaps this shoul be moved to another
-	 * function.
-	 */
-	for(allow = conf_allow; allow; allow = allow->next)
-		if(!allow->ipv6_clone_mask)
-			allow->ipv6_clone_mask = tempiConf.default_ipv6_clone_mask;
-
-	/* ^^^ TODO: due to the two-stage model now we can do it in conf_allow again
-	 *     and remove it here.
-	 */
-
 	close_unbound_listeners();
 	listen_cleanup();
 	close_unbound_listeners();
 	loop.do_bancheck = 1;
-	free_iConf(&iConf);
-	memcpy(&iConf, &tempiConf, sizeof(iConf));
-	memset(&tempiConf, 0, sizeof(tempiConf));
+	config_switchover();
 	update_throttling_timer_settings();
 
 	/* initialize conf_files with defaults if the block isn't set: */
-	if(!conf_files)
+	if (!conf_files)
 	  _conf_files(NULL, NULL);
 
 	if (errors > 0)
@@ -2905,26 +2734,7 @@ int	config_run()
 }
 
 
-NameValue *config_binary_flags_search(NameValue *table, char *cmd, int size) {
-	int start = 0;
-	int stop = size-1;
-	int mid;
-	while (start <= stop) {
-		mid = (start+stop)/2;
-
-		if (smycmp(cmd,table[mid].name) < 0) {
-			stop = mid-1;
-		}
-		else if (strcmp(cmd,table[mid].name) == 0) {
-			return &(table[mid]);
-		}
-		else
-			start = mid+1;
-	}
-	return NULL;
-}
-
-int	config_test()
+int	config_test_blocks()
 {
 	ConfigEntry 	*ce;
 	ConfigFile	*cfptr;
@@ -2932,16 +2742,29 @@ int	config_test()
 	int		errors = 0;
 	Hook *h;
 
-	need_34_upgrade = 0;
+	invalid_snomasks_encountered = 0;
+
+	/* First, all the log { } blocks everywhere */
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
+	{
+		if (config_verbose > 1)
+			config_status("Testing %s", cfptr->filename);
+		/* First test and run the log { } blocks */
+		for (ce = cfptr->items; ce; ce = ce->next)
+		{
+			if (!strcmp(ce->name, "log"))
+				errors += config_test_log(cfptr, ce);
+		}
+	}
 
-	for (cfptr = conf; cfptr; cfptr = cfptr->cf_next)
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
 	{
 		if (config_verbose > 1)
-			config_status("Testing %s", cfptr->cf_filename);
+			config_status("Testing %s", cfptr->filename);
 		/* First test and run the secret { } blocks */
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
-			if (!strcmp(ce->ce_varname, "secret"))
+			if (!strcmp(ce->name, "secret"))
 			{
 				int n = _test_secret(cfptr, ce);
 				errors += n;
@@ -2950,21 +2773,22 @@ int	config_test()
 			}
 		}
 		/* First test the set { } block */
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
-			if (!strcmp(ce->ce_varname, "set"))
+			if (!strcmp(ce->name, "set"))
 				errors += _test_set(cfptr, ce);
 		}
 		/* Now test all the rest */
-		for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+		for (ce = cfptr->items; ce; ce = ce->next)
 		{
 			/* These are already processed, so skip them here.. */
-			if (!strcmp(ce->ce_varname, "secret") ||
-			    !strcmp(ce->ce_varname, "set"))
+			if (!strcmp(ce->name, "secret") ||
+			    !strcmp(ce->name, "set") ||
+			    !strcmp(ce->name, "log"))
 			{
 				continue;
 			}
-			if ((cc = config_binary_search(ce->ce_varname))) {
+			if ((cc = config_binary_search(ce->name))) {
 				if (cc->testfunc)
 					errors += (cc->testfunc(cfptr, ce));
 			}
@@ -3003,10 +2827,10 @@ int	config_test()
 				if (!used)
 				{
 					config_error("%s:%i: unknown directive %s",
-						ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-						ce->ce_varname);
+						ce->file->filename, ce->line_number,
+						ce->name);
 					errors++;
-					if (strchr(ce->ce_varname, ':'))
+					if (strchr(ce->name, ':'))
 					{
 						config_error("You cannot use :: in a directive, you have to write them out. "
 						             "For example 'set::auto-join #something' needs to be written as: "
@@ -3023,10 +2847,12 @@ int	config_test()
 		config_error("%i errors encountered", errors);
 	}
 
-	if (need_34_upgrade)
+	if (invalid_snomasks_encountered)
 	{
-		upgrade_conf_to_34();
+		config_error("It seems your set::snomask-on-oper and/or oper::snomask needs to be updated. Are you perhaps upgrading from an older version to UnrealIRCd 6?");
+		config_error("See https://www.unrealircd.org/docs/Upgrading_from_5.x#Update_your_snomasks");
 	}
+
 	return (errors > 0 ? -1 : 1);
 }
 
@@ -3034,7 +2860,7 @@ int	config_test()
  * Service functions
 */
 
-ConfigItem_alias *find_alias(char *name)
+ConfigItem_alias *find_alias(const char *name)
 {
 	ConfigItem_alias *e;
 
@@ -3049,7 +2875,7 @@ ConfigItem_alias *find_alias(char *name)
 	return NULL;
 }
 
-ConfigItem_class *find_class(char *name)
+ConfigItem_class *find_class(const char *name)
 {
 	ConfigItem_class *e;
 
@@ -3065,7 +2891,7 @@ ConfigItem_class *find_class(char *name)
 }
 
 
-ConfigItem_oper	*find_oper(char *name)
+ConfigItem_oper	*find_oper(const char *name)
 {
 	ConfigItem_oper	*e;
 
@@ -3080,7 +2906,7 @@ ConfigItem_oper	*find_oper(char *name)
 	return NULL;
 }
 
-ConfigItem_operclass *find_operclass(char *name)
+ConfigItem_operclass *find_operclass(const char *name)
 {
 	ConfigItem_operclass *e;
 
@@ -3095,7 +2921,7 @@ ConfigItem_operclass *find_operclass(char *name)
 	return NULL;
 }
 
-int count_oper_sessions(char *name)
+int count_oper_sessions(const char *name)
 {
 	int count = 0;
 	Client *client;
@@ -3109,7 +2935,7 @@ int count_oper_sessions(char *name)
 	return count;
 }
 
-ConfigItem_listen *find_listen(char *ipmask, int port, int ipv6)
+ConfigItem_listen *find_listen(const char *ipmask, int port, int ipv6)
 {
 	ConfigItem_listen *e;
 
@@ -3126,7 +2952,7 @@ ConfigItem_listen *find_listen(char *ipmask, int port, int ipv6)
 /** Find an SNI match.
  * @param name The hostname to look for (eg: irc.xyz.com).
  */
-ConfigItem_sni *find_sni(char *name)
+ConfigItem_sni *find_sni(const char *name)
 {
 	ConfigItem_sni *e;
 
@@ -3141,7 +2967,7 @@ ConfigItem_sni *find_sni(char *name)
 	return NULL;
 }
 
-ConfigItem_ulines *find_uline(char *host)
+ConfigItem_ulines *find_uline(const char *host)
 {
 	ConfigItem_ulines *ulines;
 
@@ -3157,28 +2983,13 @@ ConfigItem_ulines *find_uline(char *host)
 }
 
 
-ConfigItem_except *find_except(Client *client, short type)
-{
-	ConfigItem_except *excepts;
-
-	for(excepts = conf_except; excepts; excepts = excepts->next)
-	{
-		if (excepts->flag.type == type)
-		{
-			if (match_user(excepts->mask, client, MATCH_CHECK_REAL))
-				return excepts;
-		}
-	}
-	return NULL;
-}
-
 ConfigItem_tld *find_tld(Client *client)
 {
 	ConfigItem_tld *tld;
 
 	for (tld = conf_tld; tld; tld = tld->next)
 	{
-		if (match_user(tld->mask, client, MATCH_CHECK_REAL))
+		if (unreal_mask_match(client, tld->mask))
 		{
 			if ((tld->options & TLD_TLS) && !IsSecureConnect(client))
 				continue;
@@ -3192,7 +3003,7 @@ ConfigItem_tld *find_tld(Client *client)
 }
 
 
-ConfigItem_link *find_link(char *servername, Client *client)
+ConfigItem_link *find_link(const char *servername, Client *client)
 {
 	ConfigItem_link	*link;
 
@@ -3209,7 +3020,7 @@ ConfigItem_link *find_link(char *servername, Client *client)
 /** Find a ban of type CONF_BAN_*, which is currently only
  * CONF_BAN_SERVER, CONF_BAN_VERSION and CONF_BAN_REALNAME
  */
-ConfigItem_ban *find_ban(Client *client, char *host, short type)
+ConfigItem_ban *find_ban(Client *client, const char *host, short type)
 {
 	ConfigItem_ban *ban;
 
@@ -3233,7 +3044,7 @@ ConfigItem_ban *find_ban(Client *client, char *host, short type)
  * CONF_BAN_SERVER, CONF_BAN_VERSION and CONF_BAN_REALNAME
  * This is the extended version, only used by cmd_svsnline.
  */
-ConfigItem_ban 	*find_banEx(Client *client, char *host, short type, short type2)
+ConfigItem_ban 	*find_banEx(Client *client, const char *host, short type, short type2)
 {
 	ConfigItem_ban *ban;
 
@@ -3253,7 +3064,7 @@ ConfigItem_ban 	*find_banEx(Client *client, char *host, short type, short type2)
 	return NULL;
 }
 
-ConfigItem_vhost *find_vhost(char *name)
+ConfigItem_vhost *find_vhost(const char *name)
 {
 	ConfigItem_vhost *vhost;
 
@@ -3268,7 +3079,7 @@ ConfigItem_vhost *find_vhost(char *name)
 
 
 /** returns NULL if allowed and struct if denied */
-ConfigItem_deny_channel *find_channel_allowed(Client *client, char *name)
+ConfigItem_deny_channel *find_channel_allowed(Client *client, const char *name)
 {
 	ConfigItem_deny_channel *dchannel;
 	ConfigItem_allow_channel *achannel;
@@ -3313,29 +3124,33 @@ void init_dynconf(void)
 	memset(&tempiConf, 0, sizeof(iConf));
 }
 
-char *pretty_time_val(long timeval)
+const char *pretty_time_val_r(char *buf, size_t buflen, long timeval)
 {
-	static char buf[512];
-
 	if (timeval == 0)
 		return "0";
 
 	buf[0] = 0;
 
 	if (timeval/86400)
-		snprintf(buf, sizeof(buf), "%ldd", timeval/86400);
+		snprintf(buf, buflen, "%ldd", timeval/86400);
 	if ((timeval/3600) % 24)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ldh", (timeval/3600)%24);
+		snprintf(buf+strlen(buf), buflen-strlen(buf), "%ldh", (timeval/3600)%24);
 	if ((timeval/60)%60)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ldm", (timeval/60)%60);
+		snprintf(buf+strlen(buf), buflen-strlen(buf), "%ldm", (timeval/60)%60);
 	if ((timeval%60))
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%lds", timeval%60);
+		snprintf(buf+strlen(buf), buflen-strlen(buf), "%lds", timeval%60);
 
 	return buf;
 }
 
+const char *pretty_time_val(long timeval)
+{
+	static char buf[512];
+	return pretty_time_val_r(buf, sizeof(buf), timeval);
+}
+
 /* This converts a relative path to an absolute path, but only if necessary. */
-void convert_to_absolute_path(char **path, char *reldir)
+void convert_to_absolute_path(char **path, const char *reldir)
 {
 	char *s;
 
@@ -3345,6 +3160,11 @@ void convert_to_absolute_path(char **path, char *reldir)
 	if (strstr(*path, "://"))
 		return; /* URL: don't touch */
 
+#ifdef _WIN32
+	if (!strncmp(*path, "cache/", 6))
+		return; /* downloaded from URL: don't touch (is only relative path on Windows) */
+#endif
+
 	if ((**path == '/') || (**path == '\\'))
 		return; /* already absolute path */
 
@@ -3371,7 +3191,7 @@ char *convert_to_absolute_path_duplicate(char *path, char *reldir)
  * Actual config parser funcs
 */
 
-int	_conf_include(ConfigFile *conf, ConfigEntry *ce)
+int _conf_include(ConfigFile *conf, ConfigEntry *ce)
 {
 	int	ret = 0;
 #ifdef GLOBH
@@ -3382,75 +3202,64 @@ int	_conf_include(ConfigFile *conf, ConfigEntry *ce)
 	WIN32_FIND_DATA FindData;
 	char cPath[MAX_PATH], *cSlash = NULL, *path;
 #endif
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_status("%s:%i: include: no filename given",
-			ce->ce_fileptr->cf_filename,
-			ce->ce_varlinenum);
+			ce->file->filename,
+			ce->line_number);
 		return -1;
 	}
 
-	if (!strcmp(ce->ce_vardata, "help.conf"))
-		need_34_upgrade = 1;
-
-	convert_to_absolute_path(&ce->ce_vardata, CONFDIR);
+	convert_to_absolute_path(&ce->value, CONFDIR);
 
-#ifdef USE_LIBCURL
-	if (url_is_valid(ce->ce_vardata))
-		return remote_include(ce);
-#else
-	if (strstr(ce->ce_vardata, "://"))
-	{
-		config_error("%s:%d: URL specified: %s",
-		             ce->ce_fileptr->cf_filename,
-		             ce->ce_varlinenum,
-		             ce->ce_vardata);
-		config_error("UnrealIRCd was not compiled with remote includes support "
-		             "so you cannot use URLs. You are suggested to re-run ./Config "
-		             "and answer YES to the question about remote includes.");
-		return -1;
+	if (url_is_valid(ce->value))
+	{
+		add_config_resource(ce->value, RESOURCE_INCLUDE, ce);
+		return 0;
 	}
-#endif
 #if !defined(_WIN32) && !defined(_AMIGA) && !defined(OSXTIGER) && DEFAULT_PERMISSIONS != 0
-	(void)chmod(ce->ce_vardata, DEFAULT_PERMISSIONS);
+	(void)chmod(ce->value, DEFAULT_PERMISSIONS);
 #endif
 #ifdef GLOBH
 #if defined(__OpenBSD__) && defined(GLOB_LIMIT)
-	glob(ce->ce_vardata, GLOB_NOSORT|GLOB_NOCHECK|GLOB_LIMIT, NULL, &files);
+	glob(ce->value, GLOB_NOSORT|GLOB_NOCHECK|GLOB_LIMIT, NULL, &files);
 #else
-	glob(ce->ce_vardata, GLOB_NOSORT|GLOB_NOCHECK, NULL, &files);
+	glob(ce->value, GLOB_NOSORT|GLOB_NOCHECK, NULL, &files);
 #endif
 	if (!files.gl_pathc) {
 		globfree(&files);
 		config_status("%s:%i: include %s: invalid file given",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_vardata);
+			ce->file->filename, ce->line_number,
+			ce->value);
 		return -1;
 	}
-	for (i = 0; i < files.gl_pathc; i++) {
-		add_include(files.gl_pathv[i], ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		ret = load_conf(files.gl_pathv[i], files.gl_pathv[i]);
-		if (ret < 0)
+	for (i = 0; i < files.gl_pathc; i++)
+	{
+		if (add_config_resource(files.gl_pathv[i], RESOURCE_INCLUDE, ce))
 		{
-			globfree(&files);
-			return ret;
+			ret = config_read_file(files.gl_pathv[i], files.gl_pathv[i]);
+			if (ret < 0)
+			{
+				globfree(&files);
+				return ret;
+			}
 		}
 	}
 	globfree(&files);
 #elif defined(_WIN32)
 	memset(cPath, 0, MAX_PATH);
-	if (strchr(ce->ce_vardata, '/') || strchr(ce->ce_vardata, '\\')) {
-		strlcpy(cPath,ce->ce_vardata,MAX_PATH);
+	if (strchr(ce->value, '/') || strchr(ce->value, '\\')) {
+		strlcpy(cPath,ce->value,MAX_PATH);
 		cSlash=cPath+strlen(cPath);
 		while(*cSlash != '\\' && *cSlash != '/' && cSlash > cPath)
 			cSlash--;
 		*(cSlash+1)=0;
 	}
-	if ( (hFind = FindFirstFile(ce->ce_vardata, &FindData)) == INVALID_HANDLE_VALUE )
+	if ( (hFind = FindFirstFile(ce->value, &FindData)) == INVALID_HANDLE_VALUE )
 	{
 		config_status("%s:%i: include %s: invalid file given",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_vardata);
+			ce->file->filename, ce->line_number,
+			ce->value);
 		return -1;
 	}
 	if (cPath) {
@@ -3458,15 +3267,16 @@ int	_conf_include(ConfigFile *conf, ConfigEntry *ce)
 		strcpy(path, cPath);
 		strcat(path, FindData.cFileName);
 
-		add_include(path, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		ret = load_conf(path, path);
-		safe_free(path);
-
+		if (add_config_resource(path, RESOURCE_INCLUDE, ce))
+		{
+			ret = config_read_file(path, path);
+			safe_free(path);
+		}
 	}
 	else
 	{
-		add_include(FindData.cFileName, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		ret = load_conf(FindData.cFileName, FindData.cFileName);
+		if (add_config_resource(FindData.cFileName, RESOURCE_INCLUDE, ce))
+			ret = config_read_file(FindData.cFileName, FindData.cFileName);
 	}
 	if (ret < 0)
 	{
@@ -3481,24 +3291,26 @@ int	_conf_include(ConfigFile *conf, ConfigEntry *ce)
 			strcpy(path,cPath);
 			strcat(path,FindData.cFileName);
 
-			add_include(path, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-			ret = load_conf(path, path);
-			safe_free(path);
-			if (ret < 0)
-				break;
+			if (add_config_resource(path, RESOURCE_INCLUDE, ce))
+			{
+				ret = config_read_file(path, path);
+				safe_free(path);
+				if (ret < 0)
+					break;
+			}
 		}
 		else
 		{
-			add_include(FindData.cFileName, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-			ret = load_conf(FindData.cFileName, FindData.cFileName);
+			if (add_config_resource(FindData.cFileName, RESOURCE_INCLUDE, ce))
+				ret = config_read_file(FindData.cFileName, FindData.cFileName);
 		}
 	}
 	FindClose(hFind);
 	if (ret < 0)
 		return ret;
 #else
-	add_include(ce->ce_vardata, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-	ret = load_conf(ce->ce_vardata, ce->ce_vardata);
+	if (add_config_resource(ce->value, RESOURCE_INCLUDE, ce))
+		ret = config_read_file(ce->value, ce->value);
 	return ret;
 #endif
 	return 1;
@@ -3514,12 +3326,12 @@ int	_conf_admin(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	ConfigItem_admin *ca;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		ca = safe_alloc(sizeof(ConfigItem_admin));
 		if (!conf_admin)
 			conf_admin_tail = ca;
-		safe_strdup(ca->line, cep->ce_varname);
+		safe_strdup(ca->line, cep->name);
 		AddListItem(ca, conf_admin);
 	}
 	return 1;
@@ -3532,17 +3344,17 @@ int	_test_admin(ConfigFile *conf, ConfigEntry *ce)
 
 	if (requiredstuff.conf_admin)
 	{
-		config_warn_duplicate(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "admin");
+		config_warn_duplicate(ce->file->filename, ce->line_number, "admin");
 		return 0;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (strlen(cep->ce_varname) > 500)
+		if (strlen(cep->name) > 500)
 		{
 			config_error("%s:%i: oversized data in admin block",
-				cep->ce_fileptr->cf_filename,
-				cep->ce_varlinenum);
+				cep->file->filename,
+				cep->line_number);
 			errors++;
 			continue;
 		}
@@ -3558,19 +3370,19 @@ int	_conf_me(ConfigFile *conf, ConfigEntry *ce)
 	if (!conf_me)
 		conf_me = safe_alloc(sizeof(ConfigItem_me));
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "name"))
+		if (!strcmp(cep->name, "name"))
 		{
-			safe_strdup(conf_me->name, cep->ce_vardata);
+			safe_strdup(conf_me->name, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "info"))
+		else if (!strcmp(cep->name, "info"))
 		{
-			safe_strdup(conf_me->info, cep->ce_vardata);
+			safe_strdup(conf_me->info, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "sid"))
+		else if (!strcmp(cep->name, "sid"))
 		{
-			safe_strdup(conf_me->sid, cep->ce_vardata);
+			safe_strdup(conf_me->sid, cep->value);
 		}
 	}
 	return 1;
@@ -3584,69 +3396,69 @@ int	_test_me(ConfigFile *conf, ConfigEntry *ce)
 
 	if (requiredstuff.conf_me)
 	{
-		config_warn_duplicate(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me");
+		config_warn_duplicate(ce->file->filename, ce->line_number, "me");
 		return 0;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "me"))
 			continue;
 
 		/* me::name */
-		if (!strcmp(cep->ce_varname, "name"))
+		if (!strcmp(cep->name, "name"))
 		{
 			if (has_name)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "me::name");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "me::name");
 				continue;
 			}
 			has_name = 1;
-			if (!strchr(cep->ce_vardata, '.'))
+			if (!strchr(cep->value, '.'))
 			{
 				config_error("%s:%i: illegal me::name, must be fully qualified hostname",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum);
+					cep->file->filename,
+					cep->line_number);
 				errors++;
 			}
-			if (!valid_host(cep->ce_vardata))
+			if (strlen(cep->value) > HOSTLEN)
 			{
-				config_error("%s:%i: illegal me::name contains invalid character(s) [only a-z, 0-9, _, -, . are allowed]",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum);
+				config_error("%s:%i: illegal me::name, must be less or equal to %i characters",
+					cep->file->filename,
+					cep->line_number, HOSTLEN);
 				errors++;
 			}
-			if (strlen(cep->ce_vardata) > HOSTLEN)
+			if (!valid_server_name(cep->value))
 			{
-				config_error("%s:%i: illegal me::name, must be less or equal to %i characters",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, HOSTLEN);
+				config_error("%s:%i: illegal me::name contains invalid character(s) [only a-z, 0-9, _, -, . are allowed]",
+					cep->file->filename,
+					cep->line_number);
 				errors++;
 			}
 		}
 		/* me::info */
-		else if (!strcmp(cep->ce_varname, "info"))
+		else if (!strcmp(cep->name, "info"))
 		{
 			char *p;
 			char valid = 0;
 			if (has_info)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "me::info");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "me::info");
 				continue;
 			}
 			has_info = 1;
-			if (strlen(cep->ce_vardata) > (REALLEN-1))
+			if (strlen(cep->value) > (REALLEN-1))
 			{
 				config_error("%s:%i: too long me::info, must be max. %i characters",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+					cep->file->filename, cep->line_number,
 					REALLEN-1);
 				errors++;
 			}
 
 			/* Valid me::info? Any data except spaces is ok */
-			for (p=cep->ce_vardata; *p; p++)
+			for (p=cep->value; *p; p++)
 			{
 				if (*p != ' ')
 				{
@@ -3657,65 +3469,65 @@ int	_test_me(ConfigFile *conf, ConfigEntry *ce)
 			if (!valid)
 			{
 				config_error("%s:%i: empty me::info, should be a server description.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "numeric"))
+		else if (!strcmp(cep->name, "numeric"))
 		{
 			config_error("%s:%i: me::numeric has been removed, you must now specify a Server ID (SID) instead. "
 			             "Edit your configuration file and change 'numeric' to 'sid' and make up "
 			             "a server id of exactly 3 characters, starting with a digit, eg: \"001\" or \"0AB\".",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			             cep->file->filename, cep->line_number);
 			errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "sid"))
+		else if (!strcmp(cep->name, "sid"))
 		{
 			if (has_sid)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "me::sid");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "me::sid");
 				continue;
 			}
 			has_sid = 1;
 
-			if (!valid_sid(cep->ce_vardata))
+			if (!valid_sid(cep->value))
 			{
 				config_error("%s:%i: me::sid must be 3 characters long, begin with a number, "
 				             "and the 2nd and 3rd character must be a number or uppercase letter. "
 				             "Example: \"001\" and \"0AB\" is good. \"AAA\" and \"0ab\" are bad. ",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				             cep->file->filename, cep->line_number);
 				errors++;
 			}
 
-			if (!isdigit(*cep->ce_vardata))
+			if (!isdigit(*cep->value))
 			{
 				config_error("%s:%i: me::sid must be 3 characters long and begin with a number",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
 		/* Unknown entry */
 		else
 		{
-			config_error_unknown(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				"me", cep->ce_varname);
+			config_error_unknown(ce->file->filename, ce->line_number,
+				"me", cep->name);
 			errors++;
 		}
 	}
 	if (!has_name)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me::name");
+		config_error_missing(ce->file->filename, ce->line_number, "me::name");
 		errors++;
 	}
 	if (!has_info)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me::info");
+		config_error_missing(ce->file->filename, ce->line_number, "me::info");
 		errors++;
 	}
 	if (!has_sid)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me::sid");
+		config_error_missing(ce->file->filename, ce->line_number, "me::sid");
 		errors++;
 	}
 	requiredstuff.conf_me = 1;
@@ -3750,29 +3562,29 @@ int	_conf_files(ConfigFile *conf, ConfigEntry *ce)
 	 * hack to allow initialization of conf_files (above) when there is no files block in
 	 * CPATH. The caller calls _conf_files(NULL, NULL); to do this. We return here because
 	 * the for loop's initialization of cep would segfault otherwise. We return 1 because
-	 * if config_run() calls us with a NULL ce, it's got a bug...but we can't detect that.
+	 * if config_run_blocks() calls us with a NULL ce, it's got a bug...but we can't detect that.
 	 */
-	if(!ce)
+	if (!ce)
 	  return 1;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "motd"))
-			safe_strdup(conf_files->motd_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "shortmotd"))
-			safe_strdup(conf_files->smotd_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "opermotd"))
-			safe_strdup(conf_files->opermotd_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "svsmotd"))
-			safe_strdup(conf_files->svsmotd_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "botmotd"))
-			safe_strdup(conf_files->botmotd_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "rules"))
-			safe_strdup(conf_files->rules_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "tunefile"))
-			safe_strdup(conf_files->tune_file, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "pidfile"))
-			safe_strdup(conf_files->pid_file, cep->ce_vardata);
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "motd"))
+			safe_strdup(conf_files->motd_file, cep->value);
+		else if (!strcmp(cep->name, "shortmotd"))
+			safe_strdup(conf_files->smotd_file, cep->value);
+		else if (!strcmp(cep->name, "opermotd"))
+			safe_strdup(conf_files->opermotd_file, cep->value);
+		else if (!strcmp(cep->name, "svsmotd"))
+			safe_strdup(conf_files->svsmotd_file, cep->value);
+		else if (!strcmp(cep->name, "botmotd"))
+			safe_strdup(conf_files->botmotd_file, cep->value);
+		else if (!strcmp(cep->name, "rules"))
+			safe_strdup(conf_files->rules_file, cep->value);
+		else if (!strcmp(cep->name, "tunefile"))
+			safe_strdup(conf_files->tune_file, cep->value);
+		else if (!strcmp(cep->name, "pidfile"))
+			safe_strdup(conf_files->pid_file, cep->value);
 	}
 	return 1;
 }
@@ -3785,120 +3597,120 @@ int	_test_files(ConfigFile *conf, ConfigEntry *ce)
 	char has_botmotd = 0, has_opermotd = 0, has_svsmotd = 0;
 	char has_pidfile = 0, has_tunefile = 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		/* files::motd */
-		if (!strcmp(cep->ce_varname, "motd"))
+		if (!strcmp(cep->name, "motd"))
 		{
 			if (has_motd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::motd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::motd");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+			convert_to_absolute_path(&cep->value, CONFDIR);
 			config_test_openfile(cep, O_RDONLY, 0, "files::motd", 0, 1);
 			has_motd = 1;
 		}
 		/* files::smotd */
-		else if (!strcmp(cep->ce_varname, "shortmotd"))
+		else if (!strcmp(cep->name, "shortmotd"))
 		{
 			if (has_smotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::shortmotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::shortmotd");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+			convert_to_absolute_path(&cep->value, CONFDIR);
 			config_test_openfile(cep, O_RDONLY, 0, "files::shortmotd", 0, 1);
 			has_smotd = 1;
 		}
 		/* files::rules */
-		else if (!strcmp(cep->ce_varname, "rules"))
+		else if (!strcmp(cep->name, "rules"))
 		{
 			if (has_rules)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::rules");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::rules");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+			convert_to_absolute_path(&cep->value, CONFDIR);
 			config_test_openfile(cep, O_RDONLY, 0, "files::rules", 0, 1);
 			has_rules = 1;
 		}
 		/* files::botmotd */
-		else if (!strcmp(cep->ce_varname, "botmotd"))
+		else if (!strcmp(cep->name, "botmotd"))
 		{
 			if (has_botmotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::botmotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::botmotd");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+			convert_to_absolute_path(&cep->value, CONFDIR);
 			config_test_openfile(cep, O_RDONLY, 0, "files::botmotd", 0, 1);
 			has_botmotd = 1;
 		}
 		/* files::opermotd */
-		else if (!strcmp(cep->ce_varname, "opermotd"))
+		else if (!strcmp(cep->name, "opermotd"))
 		{
 			if (has_opermotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::opermotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::opermotd");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+			convert_to_absolute_path(&cep->value, CONFDIR);
 			config_test_openfile(cep, O_RDONLY, 0, "files::opermotd", 0, 1);
 			has_opermotd = 1;
 		}
 		/* files::svsmotd
 		 * This config stuff should somehow be inside of modules/svsmotd.c!!!... right?
 		 */
-		else if (!strcmp(cep->ce_varname, "svsmotd"))
+		else if (!strcmp(cep->name, "svsmotd"))
 		{
 			if (has_svsmotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::svsmotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::svsmotd");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+			convert_to_absolute_path(&cep->value, CONFDIR);
 			/* svsmotd can't be a URL because we have to be able to write to it */
 			config_test_openfile(cep, O_RDONLY, 0, "files::svsmotd", 0, 0);
 			has_svsmotd = 1;
 		}
 		/* files::pidfile */
-		else if (!strcmp(cep->ce_varname, "pidfile"))
+		else if (!strcmp(cep->name, "pidfile"))
 		{
 			if (has_pidfile)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::pidfile");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::pidfile");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
 			errors += config_test_openfile(cep, O_WRONLY | O_CREAT, 0600, "files::pidfile", 1, 0);
 			has_pidfile = 1;
 		}
 		/* files::tunefile */
-		else if (!strcmp(cep->ce_varname, "tunefile"))
+		else if (!strcmp(cep->name, "tunefile"))
 		{
 			if (has_tunefile)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "files::tunefile");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "files::tunefile");
 				continue;
 			}
-			convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
 			errors += config_test_openfile(cep, O_RDWR | O_CREAT, 0600, "files::tunefile", 1, 0);
 			has_tunefile = 1;
 		}
 		/* <random directive here> */
 		else
 		{
-			config_error("%s:%d: Unknown directive: \"%s\" in files {}", cep->ce_fileptr->cf_filename,
-				     cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%d: Unknown directive: \"%s\" in files {}", cep->file->filename,
+				     cep->line_number, cep->name);
 			errors ++;
 		}
 	}
@@ -3915,18 +3727,18 @@ OperClassACLEntry* _conf_parseACLEntry(ConfigEntry *ce)
 	OperClassACLEntry *entry = NULL;
 	entry = safe_alloc(sizeof(OperClassACLEntry));
 
-	if (!strcmp(ce->ce_varname,"allow"))
+	if (!strcmp(ce->name,"allow"))
 		entry->type = OPERCLASSENTRY_ALLOW;
 	else
 		entry->type = OPERCLASSENTRY_DENY;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		OperClassACLEntryVar *var = safe_alloc(sizeof(OperClassACLEntryVar));
-		safe_strdup(var->name, cep->ce_varname);
-		if (cep->ce_vardata)
+		safe_strdup(var->name, cep->name);
+		if (cep->value)
 		{
-			safe_strdup(var->value, cep->ce_vardata);
+			safe_strdup(var->value, cep->value);
 		}
 		AddListItem(var,entry->variables);
 	}
@@ -3934,7 +3746,7 @@ OperClassACLEntry* _conf_parseACLEntry(ConfigEntry *ce)
 	return entry;
 }
 
-OperClassACL* _conf_parseACL(char *name, ConfigEntry *ce)
+OperClassACL* _conf_parseACL(const char *name, ConfigEntry *ce)
 {
 	ConfigEntry *cep;
 	OperClassACL *acl = NULL;
@@ -3942,15 +3754,15 @@ OperClassACL* _conf_parseACL(char *name, ConfigEntry *ce)
 	acl = safe_alloc(sizeof(OperClassACL));
 	safe_strdup(acl->name, name);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "deny") || !strcmp(cep->ce_varname, "allow"))
+		if (!strcmp(cep->name, "deny") || !strcmp(cep->name, "allow"))
 		{
 			OperClassACLEntry *entry = _conf_parseACLEntry(cep);
 			AddListItem(entry,acl->entries);
 		}
 		else {
-			OperClassACL *subAcl = _conf_parseACL(cep->ce_varname,cep);
+			OperClassACL *subAcl = _conf_parseACL(cep->name,cep);
 			AddListItem(subAcl,acl->acls);
 		}
 	}
@@ -3965,19 +3777,19 @@ int	_conf_operclass(ConfigFile *conf, ConfigEntry *ce)
 	ConfigItem_operclass *operClass = NULL;
 	operClass = safe_alloc(sizeof(ConfigItem_operclass));
 	operClass->classStruct = safe_alloc(sizeof(OperClass));
-	safe_strdup(operClass->classStruct->name, ce->ce_vardata);
+	safe_strdup(operClass->classStruct->name, ce->value);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "parent"))
+		if (!strcmp(cep->name, "parent"))
 		{
-			safe_strdup(operClass->classStruct->ISA, cep->ce_vardata);
+			safe_strdup(operClass->classStruct->ISA, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "permissions"))
+		else if (!strcmp(cep->name, "permissions"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				OperClassACL *acl = _conf_parseACL(cepp->ce_varname,cepp);
+				OperClassACL *acl = _conf_parseACL(cepp->name,cepp);
 				AddListItem(acl,operClass->classStruct->acls);
 			}
 		}
@@ -3993,7 +3805,7 @@ void new_permissions_system(ConfigFile *conf, ConfigEntry *ce)
 		return; /* error already shown */
 
 	config_error("%s:%i: UnrealIRCd 4.2.1 and higher have a new operclass permissions system.",
-	             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+	             ce->file->filename, ce->line_number);
 	config_error("Please see https://www.unrealircd.org/docs/FAQ#New_operclass_permissions");
 	config_error("(additional errors regarding this are suppressed)");
 	/*
@@ -4011,44 +3823,44 @@ int 	_test_operclass(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	int	errors = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
-		config_error_noname(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "operclass");
+		config_error_noname(ce->file->filename, ce->line_number, "operclass");
 		errors++;
 	}
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "parent"))
+		if (!strcmp(cep->name, "parent"))
 		{
 			if (has_parent)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "operclass::parent");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "operclass::parent");
 				continue;
 			}
 			has_parent = 1;
 			continue;
 		} else
-		if (!strcmp(cep->ce_varname, "permissions"))
+		if (!strcmp(cep->name, "permissions"))
 		{
 			if (has_permissions)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-				cep->ce_varlinenum, "oper::permissions");
+				config_warn_duplicate(cep->file->filename,
+				cep->line_number, "oper::permissions");
 				continue;
 			}
 			has_permissions = 1;
 			continue;
 		} else
-		if (!strcmp(cep->ce_varname, "privileges"))
+		if (!strcmp(cep->name, "privileges"))
 		{
 			new_permissions_system(conf, cep);
 			errors++;
 			return errors;
 		} else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename,
-				cep->ce_varlinenum, "operclass", cep->ce_varname);
+			config_error_unknown(cep->file->filename,
+				cep->line_number, "operclass", cep->name);
 			errors++;
 			continue;
 		}
@@ -4056,7 +3868,7 @@ int 	_test_operclass(ConfigFile *conf, ConfigEntry *ce)
 
 	if (!has_permissions)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"oper::permissions");
 		errors++;
 	}
@@ -4075,69 +3887,75 @@ int	_conf_oper(ConfigFile *conf, ConfigEntry *ce)
 	ConfigItem_oper *oper = NULL;
 
 	oper =  safe_alloc(sizeof(ConfigItem_oper));
-	safe_strdup(oper->name, ce->ce_vardata);
+	safe_strdup(oper->name, ce->value);
+
+	oper->server_notice_colors = tempiConf.server_notice_colors; /* default */
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "operclass"))
-			safe_strdup(oper->operclass, cep->ce_vardata);
-		if (!strcmp(cep->ce_varname, "password"))
+		if (!strcmp(cep->name, "operclass"))
+			safe_strdup(oper->operclass, cep->value);
+		if (!strcmp(cep->name, "password"))
 			oper->auth = AuthBlockToAuthConfig(cep);
-		else if (!strcmp(cep->ce_varname, "class"))
+		else if (!strcmp(cep->name, "class"))
 		{
-			oper->class = find_class(cep->ce_vardata);
+			oper->class = find_class(cep->value);
 			if (!oper->class || (oper->class->flag.temporary == 1))
 			{
 				config_status("%s:%i: illegal oper::class, unknown class '%s' using default of class 'default'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata);
+					cep->file->filename, cep->line_number,
+					cep->value);
 				oper->class = default_class;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "swhois"))
+		else if (!strcmp(cep->name, "swhois"))
 		{
 			SWhois *s;
-			if (cep->ce_entries)
+			if (cep->items)
 			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+				for (cepp = cep->items; cepp; cepp = cepp->next)
 				{
 					s = safe_alloc(sizeof(SWhois));
-					safe_strdup(s->line, cepp->ce_varname);
+					safe_strdup(s->line, cepp->name);
 					safe_strdup(s->setby, "oper");
 					AddListItem(s, oper->swhois);
 				}
 			} else
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
 				s = safe_alloc(sizeof(SWhois));
-				safe_strdup(s->line, cep->ce_vardata);
+				safe_strdup(s->line, cep->value);
 				safe_strdup(s->setby, "oper");
 				AddListItem(s, oper->swhois);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "snomask"))
+		else if (!strcmp(cep->name, "snomask"))
 		{
-			safe_strdup(oper->snomask, cep->ce_vardata);
+			safe_strdup(oper->snomask, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "modes"))
+		else if (!strcmp(cep->name, "server-notice-colors"))
 		{
-			oper->modes = set_usermode(cep->ce_vardata);
+			oper->server_notice_colors = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "require-modes"))
+		else if (!strcmp(cep->name, "modes"))
 		{
-			oper->require_modes = set_usermode(cep->ce_vardata);
+			oper->modes = set_usermode(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "maxlogins"))
+		else if (!strcmp(cep->name, "require-modes"))
 		{
-			oper->maxlogins = atoi(cep->ce_vardata);
+			oper->require_modes = set_usermode(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "mask"))
+		else if (!strcmp(cep->name, "maxlogins"))
+		{
+			oper->maxlogins = atoi(cep->value);
+		}
+		else if (!strcmp(cep->name, "mask"))
 		{
 			unreal_add_masks(&oper->mask, cep);
 		}
-		else if (!strcmp(cep->ce_varname, "vhost"))
+		else if (!strcmp(cep->name, "vhost"))
 		{
-			safe_strdup(oper->vhost, cep->ce_vardata);
+			safe_strdup(oper->vhost, cep->value);
 		}
 	}
 	AddListItem(oper, conf_oper);
@@ -4152,15 +3970,15 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	int errors = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
-		config_error_noname(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "oper");
+		config_error_noname(ce->file->filename, ce->line_number, "oper");
 		errors++;
 	}
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		/* Regular variables */
-		if (!cep->ce_entries)
+		if (!cep->items)
 		{
 			if (config_is_blankorempty(cep, "oper"))
 			{
@@ -4168,154 +3986,157 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 				continue;
 			}
 			/* oper::password */
-			if (!strcmp(cep->ce_varname, "password"))
+			if (!strcmp(cep->name, "password"))
 			{
 				if (has_password)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::password");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::password");
 					continue;
 				}
 				has_password = 1;
 				if (Auth_CheckError(cep) < 0)
 					errors++;
 
-				if (ce->ce_vardata && cep->ce_vardata &&
-					!strcmp(ce->ce_vardata, "bobsmith") &&
-					!strcmp(cep->ce_vardata, "test"))
+				if (ce->value && cep->value &&
+					!strcmp(ce->value, "bobsmith") &&
+					!strcmp(cep->value, "test"))
 				{
 					config_error("%s:%i: please change the the name and password of the "
 								 "default 'bobsmith' oper block",
-								 ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+								 ce->file->filename, ce->line_number);
 					errors++;
 				}
 				continue;
 			}
 			/* oper::operclass */
-			else if (!strcmp(cep->ce_varname, "operclass"))
+			else if (!strcmp(cep->name, "operclass"))
 			{
 				if (has_operclass)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "oper::operclass");
+					config_warn_duplicate(cep->file->filename,
+					cep->line_number, "oper::operclass");
 					continue;
 				}
 				has_operclass = 1;
 				continue;
 			}
 			/* oper::class */
-			else if (!strcmp(cep->ce_varname, "class"))
+			else if (!strcmp(cep->name, "class"))
 			{
 				if (has_class)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::class");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::class");
 					continue;
 				}
 				has_class = 1;
 			}
 			/* oper::swhois */
-			else if (!strcmp(cep->ce_varname, "swhois"))
+			else if (!strcmp(cep->name, "swhois"))
 			{
 			}
 			/* oper::vhost */
-			else if (!strcmp(cep->ce_varname, "vhost"))
+			else if (!strcmp(cep->name, "vhost"))
 			{
 				if (has_vhost)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::vhost");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::vhost");
 					continue;
 				}
 				has_vhost = 1;
 			}
 			/* oper::snomask */
-			else if (!strcmp(cep->ce_varname, "snomask"))
+			else if (!strcmp(cep->name, "snomask"))
 			{
+				char *wrong_snomask;
 				if (has_snomask)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::snomask");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::snomask");
 					continue;
 				}
+				if (!is_valid_snomask_string_testing(cep->value, &wrong_snomask))
+				{
+					config_error("%s:%i: oper::snomask contains unknown snomask letter(s) '%s'",
+					             cep->file->filename, cep->line_number, wrong_snomask);
+					errors++;
+					invalid_snomasks_encountered++;
+				}
 				has_snomask = 1;
 			}
+			else if (!strcmp(cep->name, "server-notice-colors"))
+			{
+			}
 			/* oper::modes */
-			else if (!strcmp(cep->ce_varname, "modes"))
+			else if (!strcmp(cep->name, "modes"))
 			{
 				char *p;
-				for (p = cep->ce_vardata; *p; p++)
+				for (p = cep->value; *p; p++)
 					if (strchr("orzS", *p))
 					{
 						config_error("%s:%i: oper::modes may not include mode '%c'",
-							cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p);
+							cep->file->filename, cep->line_number, *p);
 						errors++;
 					}
 				if (has_modes)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::modes");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::modes");
 					continue;
 				}
 				has_modes = 1;
 			}
 			/* oper::require-modes */
-			else if (!strcmp(cep->ce_varname, "require-modes"))
+			else if (!strcmp(cep->name, "require-modes"))
 			{
 				char *p;
-				for (p = cep->ce_vardata; *p; p++)
+				for (p = cep->value; *p; p++)
 					if (strchr("o", *p))
 					{
 						config_warn("%s:%i: oper::require-modes probably shouldn't include mode '%c'",
-							cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p);
+							cep->file->filename, cep->line_number, *p);
 					}
 				if (has_require_modes)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::require-modes");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::require-modes");
 					continue;
 				}
 				has_require_modes = 1;
 			}
 			/* oper::maxlogins */
-			else if (!strcmp(cep->ce_varname, "maxlogins"))
+			else if (!strcmp(cep->name, "maxlogins"))
 			{
 				int l;
 
 				if (has_maxlogins)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::maxlogins");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::maxlogins");
 					continue;
 				}
 				has_maxlogins = 1;
 
-				l = atoi(cep->ce_vardata);
+				l = atoi(cep->value);
 				if ((l < 0) || (l > 5000))
 				{
 					config_error("%s:%i: oper::maxlogins: value out of range (%d) should be 0-5000",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, l);
+						cep->file->filename, cep->line_number, l);
 					errors++;
 					continue;
 				}
 			}
-			/* oper::flags */
-			else if (!strcmp(cep->ce_varname, "flags"))
+			else if (!strcmp(cep->name, "mask"))
 			{
-				config_error("%s:%i: oper::flags no longer exists. UnrealIRCd 4 uses a new style oper block.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-				errors++;
-				need_34_upgrade = 1;
-			}
-			else if (!strcmp(cep->ce_varname, "mask"))
-			{
-				if (cep->ce_vardata || cep->ce_entries)
+				if (cep->value || cep->items)
 					has_mask = 1;
 			}
 			else
 			{
-				config_error_unknown(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "oper", cep->ce_varname);
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "oper", cep->name);
 				errors++;
 				continue;
 			}
@@ -4323,39 +4144,21 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 		/* Sections */
 		else
 		{
-			/* oper::flags {} */
-			if (!strcmp(cep->ce_varname, "flags"))
-			{
-				config_error("%s:%i: oper::flags no longer exists. UnrealIRCd 4 uses a new style oper block.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-				errors++;
-				need_34_upgrade = 1;
-				continue;
-			}
-			/* oper::from {} */
-			else if (!strcmp(cep->ce_varname, "from"))
-			{
-				config_error("%s:%i: oper::from::userhost is now called oper::mask",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-				errors++;
-				need_34_upgrade = 1;
-				continue;
-			}
-			else if (!strcmp(cep->ce_varname, "swhois"))
+			if (!strcmp(cep->name, "swhois"))
 			{
 				/* ok */
 			}
-			else if (!strcmp(cep->ce_varname, "mask"))
+			else if (!strcmp(cep->name, "mask"))
 			{
-				if (cep->ce_vardata || cep->ce_entries)
+				if (cep->value || cep->items)
 					has_mask = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "password"))
+			else if (!strcmp(cep->name, "password"))
 			{
 				if (has_password)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "oper::password");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "oper::password");
 					continue;
 				}
 				has_password = 1;
@@ -4364,8 +4167,8 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 			}
 			else
 			{
-				config_error_unknown(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "oper", cep->ce_varname);
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "oper", cep->name);
 				errors++;
 				continue;
 			}
@@ -4373,27 +4176,26 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 	}
 	if (!has_password)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"oper::password");
 		errors++;
 	}
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"oper::mask");
 		errors++;
 	}
 	if (!has_class)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"oper::class");
 		errors++;
 	}
 	if (!has_operclass)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"oper::operclass");
-		need_34_upgrade = 1;
 		errors++;
 	}
 
@@ -4410,10 +4212,10 @@ int	_conf_class(ConfigFile *conf, ConfigEntry *ce)
 	ConfigItem_class *class;
 	unsigned char isnew = 0;
 
-	if (!(class = find_class(ce->ce_vardata)))
+	if (!(class = find_class(ce->value)))
 	{
 		class = safe_alloc(sizeof(ConfigItem_class));
-		safe_strdup(class->name, ce->ce_vardata);
+		safe_strdup(class->name, ce->value);
 		isnew = 1;
 	}
 	else
@@ -4422,26 +4224,26 @@ int	_conf_class(ConfigFile *conf, ConfigEntry *ce)
 		class->flag.temporary = 0;
 		class->options = 0; /* RESET OPTIONS */
 	}
-	safe_strdup(class->name, ce->ce_vardata);
+	safe_strdup(class->name, ce->value);
 
 	class->connfreq = 15; /* default */
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "pingfreq"))
-			class->pingfreq = config_checkval(cep->ce_vardata,CFG_TIME);
-		else if (!strcmp(cep->ce_varname, "connfreq"))
-			class->connfreq = config_checkval(cep->ce_vardata,CFG_TIME);
-		else if (!strcmp(cep->ce_varname, "maxclients"))
-			class->maxclients = atol(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "sendq"))
-			class->sendq = config_checkval(cep->ce_vardata,CFG_SIZE);
-		else if (!strcmp(cep->ce_varname, "recvq"))
-			class->recvq = config_checkval(cep->ce_vardata,CFG_SIZE);
-		else if (!strcmp(cep->ce_varname, "options"))
-		{
-			for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
-				if (!strcmp(cep2->ce_varname, "nofakelag"))
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "pingfreq"))
+			class->pingfreq = config_checkval(cep->value,CFG_TIME);
+		else if (!strcmp(cep->name, "connfreq"))
+			class->connfreq = config_checkval(cep->value,CFG_TIME);
+		else if (!strcmp(cep->name, "maxclients"))
+			class->maxclients = atol(cep->value);
+		else if (!strcmp(cep->name, "sendq"))
+			class->sendq = config_checkval(cep->value,CFG_SIZE);
+		else if (!strcmp(cep->name, "recvq"))
+			class->recvq = config_checkval(cep->value,CFG_SIZE);
+		else if (!strcmp(cep->name, "options"))
+		{
+			for (cep2 = cep->items; cep2; cep2 = cep2->next)
+				if (!strcmp(cep2->name, "nofakelag"))
 					class->options |= CLASS_OPT_NOFAKELAG;
 		}
 	}
@@ -4457,32 +4259,32 @@ int	_test_class(ConfigFile *conf, ConfigEntry *ce)
 	char has_pingfreq = 0, has_connfreq = 0, has_maxclients = 0, has_sendq = 0;
 	char has_recvq = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
-		config_error_noname(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "class");
+		config_error_noname(ce->file->filename, ce->line_number, "class");
 		return 1;
 	}
-	if (!strcasecmp(ce->ce_vardata, "default"))
+	if (!strcasecmp(ce->value, "default"))
 	{
 		config_error("%s:%d: Class cannot be named 'default', this class name is reserved for internal use.",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "options"))
+		if (!strcmp(cep->name, "options"))
 		{
-			for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
+			for (cep2 = cep->items; cep2; cep2 = cep2->next)
 			{
 #ifdef FAKELAG_CONFIGURABLE
-				if (!strcmp(cep2->ce_varname, "nofakelag"))
+				if (!strcmp(cep2->name, "nofakelag"))
 					;
 				else
 #endif
 				{
 					config_error("%s:%d: Unknown option '%s' in class::options",
-						cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep2->ce_varname);
+						cep2->file->filename, cep2->line_number, cep2->name);
 					errors++;
 				}
 			}
@@ -4493,124 +4295,124 @@ int	_test_class(ConfigFile *conf, ConfigEntry *ce)
 			continue;
 		}
 		/* class::pingfreq */
-		else if (!strcmp(cep->ce_varname, "pingfreq"))
+		else if (!strcmp(cep->name, "pingfreq"))
 		{
-			int v = config_checkval(cep->ce_vardata,CFG_TIME);
+			int v = config_checkval(cep->value,CFG_TIME);
 			if (has_pingfreq)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "class::pingfreq");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "class::pingfreq");
 				continue;
 			}
 			has_pingfreq = 1;
 			if ((v < 30) || (v > 600))
 			{
 				config_error("%s:%i: class::pingfreq should be a reasonable value (30-600)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 				continue;
 			}
 		}
 		/* class::maxclients */
-		else if (!strcmp(cep->ce_varname, "maxclients"))
+		else if (!strcmp(cep->name, "maxclients"))
 		{
 			long l;
 			if (has_maxclients)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "class::maxclients");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "class::maxclients");
 				continue;
 			}
 			has_maxclients = 1;
-			l = atol(cep->ce_vardata);
+			l = atol(cep->value);
 			if ((l < 1) || (l > 1000000))
 			{
 				config_error("%s:%i: class::maxclients with illegal value",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
 		/* class::connfreq */
-		else if (!strcmp(cep->ce_varname, "connfreq"))
+		else if (!strcmp(cep->name, "connfreq"))
 		{
 			long l;
 			if (has_connfreq)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "class::connfreq");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "class::connfreq");
 				continue;
 			}
 			has_connfreq = 1;
-			l = config_checkval(cep->ce_vardata,CFG_TIME);
+			l = config_checkval(cep->value,CFG_TIME);
 			if ((l < 5) || (l > 604800))
 			{
 				config_error("%s:%i: class::connfreq with illegal value (must be >5 and <7d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
 		/* class::sendq */
-		else if (!strcmp(cep->ce_varname, "sendq"))
+		else if (!strcmp(cep->name, "sendq"))
 		{
 			long l;
 			if (has_sendq)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "class::sendq");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "class::sendq");
 				continue;
 			}
 			has_sendq = 1;
-			l = config_checkval(cep->ce_vardata,CFG_SIZE);
+			l = config_checkval(cep->value,CFG_SIZE);
 			if ((l <= 0) || (l > 2000000000))
 			{
 				config_error("%s:%i: class::sendq with illegal value",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
 		/* class::recvq */
-		else if (!strcmp(cep->ce_varname, "recvq"))
+		else if (!strcmp(cep->name, "recvq"))
 		{
 			long l;
 			if (has_recvq)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "class::recvq");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "class::recvq");
 				continue;
 			}
 			has_recvq = 1;
-			l = config_checkval(cep->ce_vardata,CFG_SIZE);
+			l = config_checkval(cep->value,CFG_SIZE);
 			if ((l < 512) || (l > 32768))
 			{
 				config_error("%s:%i: class::recvq with illegal value (must be >512 and <32k)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
 		/* Unknown */
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"class", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"class", cep->name);
 			errors++;
 			continue;
 		}
 	}
 	if (!has_pingfreq)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"class::pingfreq");
 		errors++;
 	}
 	if (!has_maxclients)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"class::maxclients");
 		errors++;
 	}
 	if (!has_sendq)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"class::sendq");
 		errors++;
 	}
@@ -4627,16 +4429,16 @@ int     _conf_drpass(ConfigFile *conf, ConfigEntry *ce)
 		conf_drpass =  safe_alloc(sizeof(ConfigItem_drpass));
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "restart"))
+		if (!strcmp(cep->name, "restart"))
 		{
 			if (conf_drpass->restartauth)
 				Auth_FreeAuthConfig(conf_drpass->restartauth);
 
 			conf_drpass->restartauth = AuthBlockToAuthConfig(cep);
 		}
-		else if (!strcmp(cep->ce_varname, "die"))
+		else if (!strcmp(cep->name, "die"))
 		{
 			if (conf_drpass->dieauth)
 				Auth_FreeAuthConfig(conf_drpass->dieauth);
@@ -4653,7 +4455,7 @@ int     _test_drpass(ConfigFile *conf, ConfigEntry *ce)
 	int errors = 0;
 	char has_restart = 0, has_die = 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "drpass"))
 		{
@@ -4661,12 +4463,12 @@ int     _test_drpass(ConfigFile *conf, ConfigEntry *ce)
 			continue;
 		}
 		/* drpass::restart */
-		if (!strcmp(cep->ce_varname, "restart"))
+		if (!strcmp(cep->name, "restart"))
 		{
 			if (has_restart)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "drpass::restart");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "drpass::restart");
 				continue;
 			}
 			has_restart = 1;
@@ -4675,12 +4477,12 @@ int     _test_drpass(ConfigFile *conf, ConfigEntry *ce)
 			continue;
 		}
 		/* drpass::die */
-		else if (!strcmp(cep->ce_varname, "die"))
+		else if (!strcmp(cep->name, "die"))
 		{
 			if (has_die)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "drpass::die");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "drpass::die");
 				continue;
 			}
 			has_die = 1;
@@ -4691,8 +4493,8 @@ int     _test_drpass(ConfigFile *conf, ConfigEntry *ce)
 		/* Unknown */
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"drpass", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"drpass", cep->name);
 			errors++;
 			continue;
 		}
@@ -4708,10 +4510,10 @@ int	_conf_ulines(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	ConfigItem_ulines *ca;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		ca = safe_alloc(sizeof(ConfigItem_ulines));
-		safe_strdup(ca->servername, cep->ce_varname);
+		safe_strdup(ca->servername, cep->name);
 		AddListItem(ca, conf_ulines);
 	}
 	return 1;
@@ -4730,48 +4532,48 @@ int     _conf_tld(ConfigFile *conf, ConfigEntry *ce)
 
 	ca = safe_alloc(sizeof(ConfigItem_tld));
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
-			safe_strdup(ca->mask, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "motd"))
+		if (!strcmp(cep->name, "mask"))
+			unreal_add_masks(&ca->mask, cep);
+		else if (!strcmp(cep->name, "motd"))
 		{
-			safe_strdup(ca->motd_file, cep->ce_vardata);
-			read_motd(cep->ce_vardata, &ca->motd);
+			safe_strdup(ca->motd_file, cep->value);
+			read_motd(cep->value, &ca->motd);
 		}
-		else if (!strcmp(cep->ce_varname, "shortmotd"))
+		else if (!strcmp(cep->name, "shortmotd"))
 		{
-			safe_strdup(ca->smotd_file, cep->ce_vardata);
-			read_motd(cep->ce_vardata, &ca->smotd);
+			safe_strdup(ca->smotd_file, cep->value);
+			read_motd(cep->value, &ca->smotd);
 		}
-		else if (!strcmp(cep->ce_varname, "opermotd"))
+		else if (!strcmp(cep->name, "opermotd"))
 		{
-			safe_strdup(ca->opermotd_file, cep->ce_vardata);
-			read_motd(cep->ce_vardata, &ca->opermotd);
+			safe_strdup(ca->opermotd_file, cep->value);
+			read_motd(cep->value, &ca->opermotd);
 		}
-		else if (!strcmp(cep->ce_varname, "botmotd"))
+		else if (!strcmp(cep->name, "botmotd"))
 		{
-			safe_strdup(ca->botmotd_file, cep->ce_vardata);
-			read_motd(cep->ce_vardata, &ca->botmotd);
+			safe_strdup(ca->botmotd_file, cep->value);
+			read_motd(cep->value, &ca->botmotd);
 		}
-		else if (!strcmp(cep->ce_varname, "rules"))
+		else if (!strcmp(cep->name, "rules"))
 		{
-			safe_strdup(ca->rules_file, cep->ce_vardata);
-			read_motd(cep->ce_vardata, &ca->rules);
+			safe_strdup(ca->rules_file, cep->value);
+			read_motd(cep->value, &ca->rules);
 		}
-		else if (!strcmp(cep->ce_varname, "options"))
+		else if (!strcmp(cep->name, "options"))
 		{
 			ConfigEntry *cepp;
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls"))
+				if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
 					ca->options |= TLD_TLS;
-				else if (!strcmp(cepp->ce_varname, "remote"))
+				else if (!strcmp(cepp->name, "remote"))
 					ca->options |= TLD_REMOTE;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "channel"))
-			safe_strdup(ca->channel, cep->ce_vardata);
+		else if (!strcmp(cep->name, "channel"))
+			safe_strdup(ca->channel, cep->value);
 	}
 	AddListItem(ca, conf_tld);
 	return 1;
@@ -4785,189 +4587,184 @@ int     _test_tld(ConfigFile *conf, ConfigEntry *ce)
 	char has_mask = 0, has_motd = 0, has_rules = 0, has_shortmotd = 0, has_channel = 0;
 	char has_opermotd = 0, has_botmotd = 0, has_options = 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata && strcmp(cep->ce_varname, "options"))
+		if (!cep->value && strcmp(cep->name, "options"))
 		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"tld", cep->ce_varname);
+			config_error_empty(cep->file->filename, cep->line_number,
+				"tld", cep->name);
 			errors++;
 			continue;
 		}
 		/* tld::mask */
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			if (has_mask)
-			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::mask");
-				continue;
-			}
-			has_mask = 1;
+			if (cep->value || cep->items)
+				has_mask = 1;
 		}
 		/* tld::motd */
-		else if (!strcmp(cep->ce_varname, "motd"))
+		else if (!strcmp(cep->name, "motd"))
 		{
 			if (has_motd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::motd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::motd");
 				continue;
 			}
 			has_motd = 1;
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
-			if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1))
+			convert_to_absolute_path(&cep->value, CONFDIR);
+			if (((fd = open(cep->value, O_RDONLY)) == -1))
 			{
 				config_error("%s:%i: tld::motd: %s: %s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata, strerror(errno));
+					cep->file->filename, cep->line_number,
+					cep->value, strerror(errno));
 				errors++;
 			}
 			else
 				close(fd);
 		}
 		/* tld::rules */
-		else if (!strcmp(cep->ce_varname, "rules"))
+		else if (!strcmp(cep->name, "rules"))
 		{
 			if (has_rules)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::rules");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::rules");
 				continue;
 			}
 			has_rules = 1;
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
-			if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1))
+			convert_to_absolute_path(&cep->value, CONFDIR);
+			if (((fd = open(cep->value, O_RDONLY)) == -1))
 			{
 				config_error("%s:%i: tld::rules: %s: %s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata, strerror(errno));
+					cep->file->filename, cep->line_number,
+					cep->value, strerror(errno));
 				errors++;
 			}
 			else
 				close(fd);
 		}
 		/* tld::channel */
-		else if (!strcmp(cep->ce_varname, "channel"))
+		else if (!strcmp(cep->name, "channel"))
 		{
 			if (has_channel)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::channel");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::channel");
 				continue;
 			}
 			has_channel = 1;
 		}
 		/* tld::shortmotd */
-		else if (!strcmp(cep->ce_varname, "shortmotd"))
+		else if (!strcmp(cep->name, "shortmotd"))
 		{
 			if (has_shortmotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::shortmotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::shortmotd");
 				continue;
 			}
 			has_shortmotd = 1;
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
-			if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1))
+			convert_to_absolute_path(&cep->value, CONFDIR);
+			if (((fd = open(cep->value, O_RDONLY)) == -1))
 			{
 				config_error("%s:%i: tld::shortmotd: %s: %s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata, strerror(errno));
+					cep->file->filename, cep->line_number,
+					cep->value, strerror(errno));
 				errors++;
 			}
 			else
 				close(fd);
 		}
 		/* tld::opermotd */
-		else if (!strcmp(cep->ce_varname, "opermotd"))
+		else if (!strcmp(cep->name, "opermotd"))
 		{
 			if (has_opermotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::opermotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::opermotd");
 				continue;
 			}
 			has_opermotd = 1;
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
-			if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1))
+			convert_to_absolute_path(&cep->value, CONFDIR);
+			if (((fd = open(cep->value, O_RDONLY)) == -1))
 			{
 				config_error("%s:%i: tld::opermotd: %s: %s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata, strerror(errno));
+					cep->file->filename, cep->line_number,
+					cep->value, strerror(errno));
 				errors++;
 			}
 			else
 				close(fd);
 		}
 		/* tld::botmotd */
-		else if (!strcmp(cep->ce_varname, "botmotd"))
+		else if (!strcmp(cep->name, "botmotd"))
 		{
 			if (has_botmotd)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::botmotd");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::botmotd");
 				continue;
 			}
 			has_botmotd = 1;
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
-			if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1))
+			convert_to_absolute_path(&cep->value, CONFDIR);
+			if (((fd = open(cep->value, O_RDONLY)) == -1))
 			{
 				config_error("%s:%i: tld::botmotd: %s: %s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata, strerror(errno));
+					cep->file->filename, cep->line_number,
+					cep->value, strerror(errno));
 				errors++;
 			}
 			else
 				close(fd);
 		}
 		/* tld::options */
-		else if (!strcmp(cep->ce_varname, "options")) {
+		else if (!strcmp(cep->name, "options")) {
 			ConfigEntry *cep2;
 
 			if (has_options)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "tld::options");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "tld::options");
 				continue;
 			}
 			has_options = 1;
 
-			for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
+			for (cep2 = cep->items; cep2; cep2 = cep2->next)
 			{
-				if (strcmp(cep2->ce_varname, "ssl") &&
-				    strcmp(cep2->ce_varname, "tls") &&
-				    strcmp(cep2->ce_varname, "remote"))
+				if (strcmp(cep2->name, "ssl") &&
+				    strcmp(cep2->name, "tls") &&
+				    strcmp(cep2->name, "remote"))
 				{
-					config_error_unknownopt(cep2->ce_fileptr->cf_filename,
-						cep2->ce_varlinenum, "tld", cep2->ce_varname);
+					config_error_unknownopt(cep2->file->filename,
+						cep2->line_number, "tld", cep2->name);
 					errors++;
 				}
 			}
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"tld", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"tld", cep->name);
 			errors++;
 			continue;
 		}
 	}
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"tld::mask");
 		errors++;
 	}
 	if (!has_motd)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"tld::motd");
 		errors++;
 	}
 	if (!has_rules)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"tld::rules");
 		errors++;
 	}
@@ -4985,26 +4782,26 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 	int tmpflags =0;
 	Hook *h;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "ip"))
+		if (!strcmp(cep->name, "ip"))
 		{
-			ip = cep->ce_vardata;
+			ip = cep->value;
 		} else
-		if (!strcmp(cep->ce_varname, "port"))
+		if (!strcmp(cep->name, "port"))
 		{
-			port_range(cep->ce_vardata, &start, &end);
+			port_range(cep->value, &start, &end);
 			if ((start < 0) || (start > 65535) || (end < 0) || (end > 65535))
 				return -1; /* this is already validated in _test_listen, but okay.. */
 		} else
-		if (!strcmp(cep->ce_varname, "options"))
+		if (!strcmp(cep->name, "options"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				NameValue *ofp;
-				if ((ofp = config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags))))
+				long v;
+				if ((v = nv_find_by_name(_ListenerFlags, cepp->name)))
 				{
-					tmpflags |= ofp->flag;
+					tmpflags |= v;
 				} else {
 					for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
 					{
@@ -5015,7 +4812,7 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 				}
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options"))
+		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
 		{
 			tlsconfig = cep;
 		} else
@@ -5070,23 +4867,25 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 				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->ce_entries; cep; cep = cep->ce_next)
+			for (cep = ce->items; cep; cep = cep->next)
 			{
-				if (!strcmp(cep->ce_varname, "ip"))
+				if (!strcmp(cep->name, "ip"))
 					;
-				else if (!strcmp(cep->ce_varname, "port"))
+				else if (!strcmp(cep->name, "port"))
 					;
-				else if (!strcmp(cep->ce_varname, "options"))
+				else if (!strcmp(cep->name, "options"))
 				{
-					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+					for (cepp = cep->items; cepp; cepp = cepp->next)
 					{
 						NameValue *ofp;
-						if (!config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags)))
+						if (!nv_find_by_name(_ListenerFlags, cepp->name))
 						{
 							for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
 							{
@@ -5097,7 +4896,7 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 						}
 					}
 				} else
-				if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options"))
+				if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
 					;
 				else
 				{
@@ -5153,21 +4952,23 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 					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->ce_entries; cep; cep = cep->ce_next)
+				for (cep = ce->items; cep; cep = cep->next)
 				{
-					if (!strcmp(cep->ce_varname, "ip"))
+					if (!strcmp(cep->name, "ip"))
 						;
-					else if (!strcmp(cep->ce_varname, "port"))
+					else if (!strcmp(cep->name, "port"))
 						;
-					else if (!strcmp(cep->ce_varname, "options"))
+					else if (!strcmp(cep->name, "options"))
 					{
-						for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+						for (cepp = cep->items; cepp; cepp = cepp->next)
 						{
-							NameValue *ofp;
-							if (!config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags)))
+							if (!nv_find_by_name(_ListenerFlags, cepp->name))
 							{
 								for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
 								{
@@ -5178,7 +4979,7 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 							}
 						}
 					} else
-					if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options"))
+					if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
 						;
 					else
 					{
@@ -5205,16 +5006,14 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 	char *ip = NULL;
 	Hook *h;
 
-	if (ce->ce_vardata)
+	if (ce->value)
 	{
 		config_error("%s:%i: listen block has a new syntax, see https://www.unrealircd.org/docs/Listen_block",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-
-		need_34_upgrade = 1;
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		int used_by_module = 0;
 
@@ -5247,19 +5046,18 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 				errors += errs;
 			}
 		}
-		if (!strcmp(cep->ce_varname, "options"))
+		if (!strcmp(cep->name, "options"))
 		{
 			if (has_options)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "listen::options");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "listen::options");
 				continue;
 			}
 			has_options = 1;
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				NameValue *ofp;
-				if (!(ofp = config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags))))
+				if (!nv_find_by_name(_ListenerFlags, cepp->name))
 				{
 					/* Check if a module knows about this listen::options::something */
 					int used_by_module = 0;
@@ -5293,63 +5091,63 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 					}
 					if (!used_by_module)
 					{
-						config_error_unknownopt(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum, "listen::options", cepp->ce_varname);
+						config_error_unknownopt(cepp->file->filename,
+							cepp->line_number, "listen::options", cepp->name);
 						errors++;
 						continue;
 					}
 				}
-				if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls"))
+				if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
 					have_tls_listeners = 1; /* for ssl config test */
 			}
 		}
 		else
-		if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options"))
+		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
 		{
 			test_tlsblock(conf, cep, &errors);
 		}
 		else
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
 			if (!used_by_module)
 			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "listen", cep->ce_varname);
+				config_error_empty(cep->file->filename,
+					cep->line_number, "listen", cep->name);
 				errors++;
 			}
 			continue; /* always */
 		} else
-		if (!strcmp(cep->ce_varname, "ip"))
+		if (!strcmp(cep->name, "ip"))
 		{
 			has_ip = 1;
 
-			if (strcmp(cep->ce_vardata, "*") && !is_valid_ip(cep->ce_vardata))
+			if (strcmp(cep->value, "*") && !is_valid_ip(cep->value))
 			{
 				config_error("%s:%i: listen: illegal listen::ip (%s). Must be either '*' or contain a valid IP.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				return 1;
 			}
-			ip = cep->ce_vardata;
+			ip = cep->value;
 		} else
-		if (!strcmp(cep->ce_varname, "host"))
+		if (!strcmp(cep->name, "host"))
 		{
 			config_error("%s:%i: listen: unknown option listen::host, did you mean listen::ip?",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				cep->file->filename, cep->line_number);
 			errors++;
 		} else
-		if (!strcmp(cep->ce_varname, "port"))
+		if (!strcmp(cep->name, "port"))
 		{
 			int start = 0, end = 0;
 
 			has_port = 1;
 
-			port_range(cep->ce_vardata, &start, &end);
+			port_range(cep->value, &start, &end);
 			if (start == end)
 			{
 				if ((start < 1) || (start > 65535))
 				{
 					config_error("%s:%i: listen: illegal port (must be 1..65535)",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 					return 1;
 				}
 			}
@@ -5358,21 +5156,21 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 				if (end < start)
 				{
 					config_error("%s:%i: listen: illegal port range end value is less than starting value",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 					return 1;
 				}
 				if (end - start >= 100)
 				{
 					config_error("%s:%i: listen: you requested port %d-%d, that's %d ports "
 						"(and thus consumes %d sockets) this is probably not what you want.",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, start, end,
+						cep->file->filename, cep->line_number, start, end,
 						end - start + 1, end - start + 1);
 					return 1;
 				}
 				if ((start < 1) || (start > 65535) || (end < 1) || (end > 65535))
 				{
 					config_error("%s:%i: listen: illegal port range values must be between 1 and 65535",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 					return 1;
 				}
 			}
@@ -5383,8 +5181,8 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 		{
 			if (!used_by_module)
 			{
-				config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					"listen", cep->ce_varname);
+				config_error_unknown(cep->file->filename, cep->line_number,
+					"listen", cep->name);
 				errors++;
 			}
 			continue; /* always */
@@ -5394,14 +5192,14 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 	if (!has_ip)
 	{
 		config_error("%s:%d: listen block requires an listen::ip",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 
 	if (!has_port)
 	{
 		config_error("%s:%d: listen block requires an listen::port",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 
@@ -5419,9 +5217,9 @@ int	_conf_allow(ConfigFile *conf, ConfigEntry *ce)
 	ConfigItem_allow *allow;
 	Hook *h;
 
-	if (ce->ce_vardata)
+	if (ce->value)
 	{
-		if (!strcmp(ce->ce_vardata, "channel"))
+		if (!strcmp(ce->value, "channel"))
 			return (_conf_allow_channel(conf, ce));
 		else
 		{
@@ -5436,68 +5234,61 @@ int	_conf_allow(ConfigFile *conf, ConfigEntry *ce)
 		}
 	}
 	allow = safe_alloc(sizeof(ConfigItem_allow));
+	allow->ipv6_clone_mask = tempiConf.default_ipv6_clone_mask;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "ip"))
+		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "ip") || !strcmp(cep->name, "hostname"))
 		{
-			safe_strdup(allow->ip, cep->ce_vardata);
+			unreal_add_masks(&allow->mask, cep);
 		}
-		else if (!strcmp(cep->ce_varname, "hostname"))
-			safe_strdup(allow->hostname, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 			allow->auth = AuthBlockToAuthConfig(cep);
-		else if (!strcmp(cep->ce_varname, "class"))
+		else if (!strcmp(cep->name, "class"))
 		{
-			allow->class = find_class(cep->ce_vardata);
+			allow->class = find_class(cep->value);
 			if (!allow->class || (allow->class->flag.temporary == 1))
 			{
 				config_status("%s:%i: illegal allow::class, unknown class '%s' using default of class 'default'",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum,
-					cep->ce_vardata);
+					cep->file->filename,
+					cep->line_number,
+					cep->value);
 					allow->class = default_class;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "maxperip"))
-			allow->maxperip = atoi(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "global-maxperip"))
-			allow->global_maxperip = atoi(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "redirect-server"))
-			safe_strdup(allow->server, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "redirect-port"))
-			allow->port = atoi(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "ipv6-clone-mask"))
+		else if (!strcmp(cep->name, "maxperip"))
+			allow->maxperip = atoi(cep->value);
+		else if (!strcmp(cep->name, "global-maxperip"))
+			allow->global_maxperip = atoi(cep->value);
+		else if (!strcmp(cep->name, "redirect-server"))
+			safe_strdup(allow->server, cep->value);
+		else if (!strcmp(cep->name, "redirect-port"))
+			allow->port = atoi(cep->value);
+		else if (!strcmp(cep->name, "ipv6-clone-mask"))
 		{
 			/*
 			 * If this item isn't set explicitly by the
 			 * user, the value will temporarily be
-			 * zero. Defaults are applied in config_run().
+			 * zero. Defaults are applied in config_run_blocks().
 			 */
-			allow->ipv6_clone_mask = atoi(cep->ce_vardata);
+			allow->ipv6_clone_mask = atoi(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "options"))
+		else if (!strcmp(cep->name, "options"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "noident"))
+				if (!strcmp(cepp->name, "noident"))
 					allow->flags.noident = 1;
-				else if (!strcmp(cepp->ce_varname, "useip"))
+				else if (!strcmp(cepp->name, "useip"))
 					allow->flags.useip = 1;
-				else if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls"))
+				else if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
 					allow->flags.tls = 1;
-				else if (!strcmp(cepp->ce_varname, "reject-on-auth-failure"))
+				else if (!strcmp(cepp->name, "reject-on-auth-failure"))
 					allow->flags.reject_on_auth_failure = 1;
 			}
 		}
 	}
 
-	if (!allow->hostname)
-		safe_strdup(allow->hostname, "*@NOMATCH");
-
-	if (!allow->ip)
-		safe_strdup(allow->ip, "*@NOMATCH");
-
 	/* Default: global-maxperip = maxperip+1 */
 	if (allow->global_maxperip == 0)
 		allow->global_maxperip = allow->maxperip+1;
@@ -5515,13 +5306,14 @@ int	_test_allow(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep, *cepp;
 	int		errors = 0;
 	Hook *h;
-	char has_ip = 0, has_hostname = 0, has_maxperip = 0, has_global_maxperip = 0, has_password = 0, has_class = 0;
+	char has_ip = 0, has_hostname = 0, has_mask = 0;
+	char has_maxperip = 0, has_global_maxperip = 0, has_password = 0, has_class = 0;
 	char has_redirectserver = 0, has_redirectport = 0, has_options = 0;
 	int hostname_possible_silliness = 0;
 
-	if (ce->ce_vardata)
+	if (ce->value)
 	{
-		if (!strcmp(ce->ce_vardata, "channel"))
+		if (!strcmp(ce->value, "channel"))
 			return (_test_allow_channel(conf, ce));
 		else
 		{
@@ -5554,106 +5346,112 @@ int	_test_allow(ConfigFile *conf, ConfigEntry *ce)
 			}
 			if (!used) {
 				config_error("%s:%i: allow item with unknown type",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+					ce->file->filename, ce->line_number);
 				return 1;
 			}
 			return errors;
 		}
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (strcmp(cep->ce_varname, "options") && config_is_blankorempty(cep, "allow"))
+		if (strcmp(cep->name, "options") &&
+		    strcmp(cep->name, "mask") &&
+		    config_is_blankorempty(cep, "allow"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "ip"))
+		if (!strcmp(cep->name, "ip"))
 		{
 			if (has_ip)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::ip");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::ip");
 				continue;
 			}
 			has_ip = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "maxperip"))
+		else if (!strcmp(cep->name, "hostname"))
 		{
-			int v = atoi(cep->ce_vardata);
+			if (has_hostname)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::hostname");
+				continue;
+			}
+			has_hostname = 1;
+			if (!strcmp(cep->value, "*@*") || !strcmp(cep->value, "*"))
+				hostname_possible_silliness = 1;
+		}
+		else if (!strcmp(cep->name, "mask"))
+		{
+			has_mask = 1;
+		}
+		else if (!strcmp(cep->name, "maxperip"))
+		{
+			int v = atoi(cep->value);
 			if (has_maxperip)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::maxperip");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::maxperip");
 				continue;
 			}
 			has_maxperip = 1;
 			if ((v <= 0) || (v > 1000000))
 			{
 				config_error("%s:%i: allow::maxperip with illegal value (must be 1-1000000)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "global-maxperip"))
+		else if (!strcmp(cep->name, "global-maxperip"))
 		{
-			int v = atoi(cep->ce_vardata);
+			int v = atoi(cep->value);
 			if (has_global_maxperip)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::global-maxperip");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::global-maxperip");
 				continue;
 			}
 			has_global_maxperip = 1;
 			if ((v <= 0) || (v > 1000000))
 			{
 				config_error("%s:%i: allow::global-maxperip with illegal value (must be 1-1000000)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ipv6-clone-mask"))
+		else if (!strcmp(cep->name, "ipv6-clone-mask"))
 		{
 			/* keep this in sync with _test_set() */
 			int ipv6mask;
-			ipv6mask = atoi(cep->ce_vardata);
+			ipv6mask = atoi(cep->value);
 			if (ipv6mask == 0)
 			{
 				config_error("%s:%d: allow::ipv6-clone-mask given a value of zero. This cannnot be correct, as it would treat all IPv6 hosts as one host.",
-					     cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					     cep->file->filename, cep->line_number);
 				errors++;
 			}
 			if (ipv6mask > 128)
 			{
 				config_error("%s:%d: set::default-ipv6-clone-mask was set to %d. The maximum value is 128.",
-					     cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+					     cep->file->filename, cep->line_number,
 					     ipv6mask);
 				errors++;
 			}
 			if (ipv6mask <= 32)
 			{
 				config_warn("%s:%d: allow::ipv6-clone-mask was given a very small value.",
-					    cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-			}
-		}
-		else if (!strcmp(cep->ce_varname, "hostname"))
-		{
-			if (has_hostname)
-			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::hostname");
-				continue;
+					    cep->file->filename, cep->line_number);
 			}
-			has_hostname = 1;
-			if (!strcmp(cep->ce_vardata, "*@*") || !strcmp(cep->ce_vardata, "*"))
-				hostname_possible_silliness = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 		{
 			if (has_password)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::password");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::password");
 				continue;
 			}
 			has_password = 1;
@@ -5661,112 +5459,131 @@ int	_test_allow(ConfigFile *conf, ConfigEntry *ce)
 			if (Auth_CheckError(cep) < 0)
 				errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "class"))
+		else if (!strcmp(cep->name, "class"))
 		{
 			if (has_class)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::class");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::class");
 				continue;
 			}
 			has_class = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "redirect-server"))
+		else if (!strcmp(cep->name, "redirect-server"))
 		{
 			if (has_redirectserver)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::redirect-server");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::redirect-server");
 				continue;
 			}
 			has_redirectserver = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "redirect-port"))
+		else if (!strcmp(cep->name, "redirect-port"))
 		{
 			if (has_redirectport)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::redirect-port");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::redirect-port");
 				continue;
 			}
 			has_redirectport = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "options"))
+		else if (!strcmp(cep->name, "options"))
 		{
 			if (has_options)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow::options");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow::options");
 				continue;
 			}
 			has_options = 1;
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "noident"))
+				if (!strcmp(cepp->name, "noident"))
 				{}
-				else if (!strcmp(cepp->ce_varname, "useip"))
+				else if (!strcmp(cepp->name, "useip"))
 				{}
-				else if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls"))
+				else if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
 				{}
-				else if (!strcmp(cepp->ce_varname, "reject-on-auth-failure"))
+				else if (!strcmp(cepp->name, "reject-on-auth-failure"))
 				{}
-				else if (!strcmp(cepp->ce_varname, "sasl"))
+				else if (!strcmp(cepp->name, "sasl"))
 				{
 					config_error("%s:%d: The option allow::options::sasl no longer exists. "
 					             "Please use a require authentication { } block instead, which "
 					             "is more flexible and provides the same functionality. See "
 					             "https://www.unrealircd.org/docs/Require_authentication_block",
-					             cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+					             cepp->file->filename, cepp->line_number);
 					errors++;
 				}
 				else
 				{
-					config_error_unknownopt(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "allow", cepp->ce_varname);
+					config_error_unknownopt(cepp->file->filename,
+						cepp->line_number, "allow", cepp->name);
 					errors++;
 				}
 			}
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"allow", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"allow", cep->name);
 			errors++;
 			continue;
 		}
 	}
 
-	if (!has_ip && !has_hostname)
+	if (has_mask && (has_ip || has_hostname))
+	{
+		config_error("%s:%d: The allow block uses allow::mask, but you also have an allow::ip and allow::hostname.",
+			ce->file->filename, ce->line_number);
+		config_error("Please delete your allow::ip and allow::hostname entries and/or integrate them into allow::mask");
+	} else
+	if (has_ip)
+	{
+		config_warn("%s:%d: The allow block uses allow::mask nowadays. Rename your allow::ip item to allow::mask.",
+			ce->file->filename, ce->line_number);
+		config_warn("See https://www.unrealircd.org/docs/FAQ#allow-mask for more information");
+	} else
+	if (has_hostname)
+	{
+		config_warn("%s:%d: The allow block uses allow::mask nowadays. Rename your allow::hostname item to allow::mask.",
+			ce->file->filename, ce->line_number);
+		config_warn("See https://www.unrealircd.org/docs/FAQ#allow-mask for more information");
+	} else
+	if (!has_mask)
 	{
-		config_error("%s:%d: allow block needs an allow::ip or allow::hostname",
-				 ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		config_error("%s:%d: allow block needs an allow::mask",
+				 ce->file->filename, ce->line_number);
 		errors++;
 	}
 
 	if (has_ip && has_hostname)
 	{
-		config_warn("%s:%d: allow block has both allow::ip and allow::hostname which is no longer permitted.",
-		            ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		need_34_upgrade = 1;
+		config_error("%s:%d: allow block has both allow::ip and allow::hostname, this is no longer permitted.",
+		             ce->file->filename, ce->line_number);
+		config_error("Please integrate your allow::ip and allow::hostname items into a single allow::mask block");
+		errors++;
 	} else
 	if (hostname_possible_silliness)
 	{
-		config_warn("%s:%d: allow block contains 'hostname *;'. This means means that users "
-		            "without a valid hostname (unresolved IP's) will be unable to connect. "
-		            "You most likely want to use 'ip *;' instead.",
-		            ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		config_error("%s:%d: allow block contains 'hostname *;'. This means means that users "
+		             "without a valid hostname (unresolved IP's) will be unable to connect. "
+		             "You most likely want to use 'mask *;' instead.",
+		             ce->file->filename, ce->line_number);
 	}
 
 	if (!has_class)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"allow::class");
 		errors++;
 	}
 
 	if (!has_maxperip)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"allow::maxperip");
 		errors++;
 	}
@@ -5781,21 +5598,21 @@ int	_conf_allow_channel(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *mask = NULL;
 
 	/* First, search for ::class, if any */
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "class"))
-			class = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "class"))
+			class = cep->value;
+		else if (!strcmp(cep->name, "mask"))
 			mask = cep;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "channel"))
+		if (!strcmp(cep->name, "channel"))
 		{
 			/* This way, we permit multiple ::channel items in one allow block */
 			allow = safe_alloc(sizeof(ConfigItem_allow_channel));
-			safe_strdup(allow->channel, cep->ce_vardata);
+			safe_strdup(allow->channel, cep->value);
 			if (class)
 				safe_strdup(allow->class, class);
 			if (mask)
@@ -5811,7 +5628,7 @@ int	_test_allow_channel(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry		*cep;
 	int			errors = 0;
 	char			has_channel = 0, has_class = 0;
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "allow channel"))
 		{
@@ -5819,34 +5636,34 @@ int	_test_allow_channel(ConfigFile *conf, ConfigEntry *ce)
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "channel"))
+		if (!strcmp(cep->name, "channel"))
 		{
 			has_channel = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "class"))
+		else if (!strcmp(cep->name, "class"))
 		{
 
 			if (has_class)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow channel::class");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow channel::class");
 				continue;
 			}
 			has_class = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "mask"))
+		else if (!strcmp(cep->name, "mask"))
 		{
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"allow channel", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"allow channel", cep->name);
 			errors++;
 		}
 	}
 	if (!has_channel)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"allow channel::channel");
 		errors++;
 	}
@@ -5874,20 +5691,20 @@ int _test_except(ConfigFile *conf, ConfigEntry *ce)
 	Hook *h;
 	int used = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: except without type",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
 
-	if (!strcmp(ce->ce_vardata, "tkl"))
+	if (!strcmp(ce->value, "tkl"))
 	{
 		config_warn("%s:%i: except tkl { } is now called except ban { }. "
 		            "Simply rename the block from 'except tkl' to 'except ban' "
 		            "to get rid of this warning.",
-		            ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		safe_strdup(ce->ce_vardata, "ban"); /* awww */
+		            ce->file->filename, ce->line_number);
+		safe_strdup(ce->value, "ban"); /* awww */
 	}
 
 	for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
@@ -5920,8 +5737,8 @@ int _test_except(ConfigFile *conf, ConfigEntry *ce)
 	if (!used)
 	{
 		config_error("%s:%i: unknown except type %s",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_vardata);
+			ce->file->filename, ce->line_number,
+			ce->value);
 		return 1;
 	}
 
@@ -5937,12 +5754,12 @@ int	_conf_vhost(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep, *cepp;
 	vhost = safe_alloc(sizeof(ConfigItem_vhost));
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "vhost"))
+		if (!strcmp(cep->name, "vhost"))
 		{
 			char *user, *host;
-			user = strtok(cep->ce_vardata, "@");
+			user = strtok(cep->value, "@");
 			host = strtok(NULL, "");
 			if (!host)
 				safe_strdup(vhost->virthost, user);
@@ -5952,31 +5769,31 @@ int	_conf_vhost(ConfigFile *conf, ConfigEntry *ce)
 				safe_strdup(vhost->virthost, host);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "login"))
-			safe_strdup(vhost->login, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "login"))
+			safe_strdup(vhost->login, cep->value);
+		else if (!strcmp(cep->name, "password"))
 			vhost->auth = AuthBlockToAuthConfig(cep);
-		else if (!strcmp(cep->ce_varname, "mask"))
+		else if (!strcmp(cep->name, "mask"))
 		{
 			unreal_add_masks(&vhost->mask, cep);
 		}
-		else if (!strcmp(cep->ce_varname, "swhois"))
+		else if (!strcmp(cep->name, "swhois"))
 		{
 			SWhois *s;
-			if (cep->ce_entries)
+			if (cep->items)
 			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+				for (cepp = cep->items; cepp; cepp = cepp->next)
 				{
 					s = safe_alloc(sizeof(SWhois));
-					safe_strdup(s->line, cepp->ce_varname);
+					safe_strdup(s->line, cepp->name);
 					safe_strdup(s->setby, "vhost");
 					AddListItem(s, vhost->swhois);
 				}
 			} else
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
 				s = safe_alloc(sizeof(SWhois));
-				safe_strdup(s->line, cep->ce_vardata);
+				safe_strdup(s->line, cep->value);
 				safe_strdup(s->setby, "vhost");
 				AddListItem(s, vhost->swhois);
 			}
@@ -5992,30 +5809,30 @@ int	_test_vhost(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	char has_vhost = 0, has_login = 0, has_password = 0, has_mask = 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "vhost"))
+		if (!strcmp(cep->name, "vhost"))
 		{
 			char *at, *tmp, *host;
 			if (has_vhost)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "vhost::vhost");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "vhost::vhost");
 				continue;
 			}
 			has_vhost = 1;
-			if (!cep->ce_vardata)
+			if (!cep->value)
 			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "vhost", "vhost");
+				config_error_empty(cep->file->filename,
+					cep->line_number, "vhost", "vhost");
 				errors++;
 				continue;
 			}
-			if ((at = strchr(cep->ce_vardata, '@')))
+			if ((at = strchr(cep->value, '@')))
 			{
-				for (tmp = cep->ce_vardata; tmp != at; tmp++)
+				for (tmp = cep->value; tmp != at; tmp++)
 				{
-					if (*tmp == '~' && tmp == cep->ce_vardata)
+					if (*tmp == '~' && tmp == cep->value)
 						continue;
 					if (!isallowed(*tmp))
 						break;
@@ -6023,112 +5840,103 @@ int	_test_vhost(ConfigFile *conf, ConfigEntry *ce)
 				if (tmp != at)
 				{
 					config_error("%s:%i: vhost::vhost contains an invalid ident",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 					errors++;
 				}
 				host = at+1;
 			}
 			else
-				host = cep->ce_vardata;
+				host = cep->value;
 			if (!*host)
 			{
 				config_error("%s:%i: vhost::vhost does not have a host set",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 			else
 			{
-				if (!valid_host(host))
+				if (!valid_host(host, 0))
 				{
 					config_error("%s:%i: vhost::vhost contains an invalid host",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 					errors++;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "login"))
+		else if (!strcmp(cep->name, "login"))
 		{
 			if (has_login)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "vhost::login");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "vhost::login");
 			}
 			has_login = 1;
-			if (!cep->ce_vardata)
+			if (!cep->value)
 			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "vhost", "login");
+				config_error_empty(cep->file->filename,
+					cep->line_number, "vhost", "login");
 				errors++;
 				continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 		{
 			if (has_password)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "vhost::password");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "vhost::password");
 			}
 			has_password = 1;
-			if (!cep->ce_vardata)
+			if (!cep->value)
 			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "vhost", "password");
+				config_error_empty(cep->file->filename,
+					cep->line_number, "vhost", "password");
 				errors++;
 				continue;
 			}
 			if (Auth_CheckError(cep) < 0)
 				errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "from"))
-		{
-			config_error("%s:%i: vhost::from::userhost is now called oper::mask",
-						 cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-			errors++;
-			need_34_upgrade = 1;
-			continue;
-		}
-		else if (!strcmp(cep->ce_varname, "mask"))
+		else if (!strcmp(cep->name, "mask"))
 		{
 			has_mask = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "swhois"))
+		else if (!strcmp(cep->name, "swhois"))
 		{
 			/* multiple is ok */
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"vhost", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"vhost", cep->name);
 			errors++;
 		}
 	}
 	if (!has_vhost)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"vhost::vhost");
 		errors++;
 	}
 	if (!has_login)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"vhost::login");
 		errors++;
 
 	}
 	if (!has_password)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"vhost::password");
 		errors++;
 	}
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"vhost::mask");
 		errors++;
 	}
-	// TODO: 3.2.x -> 4.x upgrading hints
 	return errors;
 }
 
@@ -6137,22 +5945,22 @@ int	_test_sni(ConfigFile *conf, ConfigEntry *ce)
 	int errors = 0;
 	ConfigEntry *cep;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: sni block needs a name, eg: sni irc.xyz.com {",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options"))
+		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
 		{
 			test_tlsblock(conf, cep, &errors);
 		} else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"sni", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"sni", cep->name);
 			errors++;
 			continue;
 		}
@@ -6168,13 +5976,13 @@ int	_conf_sni(ConfigFile *conf, ConfigEntry *ce)
 	char *name;
 	ConfigItem_sni *sni = NULL;
 
-	name = ce->ce_vardata;
+	name = ce->value;
 	if (!name)
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options"))
+		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
 		{
 			tlsconfig = cep;
 		}
@@ -6200,15 +6008,15 @@ int     _conf_help(ConfigFile *conf, ConfigEntry *ce)
 	MOTDLine *last = NULL, *temp;
 	ca = safe_alloc(sizeof(ConfigItem_help));
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 		ca->command = NULL;
 	else
-		safe_strdup(ca->command, ce->ce_vardata);
+		safe_strdup(ca->command, ce->value);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		temp = safe_alloc(sizeof(MOTDLine));
-		safe_strdup(temp->line, cep->ce_varname);
+		safe_strdup(temp->line, cep->name);
 		temp->next = NULL;
 		if (!last)
 			ca->text = temp;
@@ -6224,18 +6032,18 @@ int     _conf_help(ConfigFile *conf, ConfigEntry *ce)
 int _test_help(ConfigFile *conf, ConfigEntry *ce) {
 	int errors = 0;
 	ConfigEntry *cep;
-	if (!ce->ce_entries)
+	if (!ce->items)
 	{
 		config_error("%s:%i: empty help block",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (strlen(cep->ce_varname) > 500)
+		if (strlen(cep->name) > 500)
 		{
 			config_error("%s:%i: oversized help item",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				ce->file->filename, ce->line_number);
 			errors++;
 			continue;
 		}
@@ -6243,179 +6051,47 @@ int _test_help(ConfigFile *conf, ConfigEntry *ce) {
 	return errors;
 }
 
-int     _conf_log(ConfigFile *conf, ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-	ConfigItem_log *ca;
-	NameValue *ofp = NULL;
-
-	ca = safe_alloc(sizeof(ConfigItem_log));
-	ca->logfd = -1;
-	if (strchr(ce->ce_vardata, '%'))
-		safe_strdup(ca->filefmt, ce->ce_vardata);
-	else
-		safe_strdup(ca->file, ce->ce_vardata);
-
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "maxsize"))
-		{
-			ca->maxsize = config_checkval(cep->ce_vardata,CFG_SIZE);
-		}
-		else if (!strcmp(cep->ce_varname, "flags"))
-		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-			{
-				if ((ofp = config_binary_flags_search(_LogFlags, cepp->ce_varname, ARRAY_SIZEOF(_LogFlags))))
-					ca->flags |= ofp->flag;
-			}
-		}
-	}
-	AddListItem(ca, conf_log);
-	return 1;
-
-}
-
-int _test_log(ConfigFile *conf, ConfigEntry *ce) {
-	int fd, errors = 0;
-	ConfigEntry *cep, *cepp;
-	char has_flags = 0, has_maxsize = 0;
-	char *fname;
-
-	if (!ce->ce_vardata)
-	{
-		config_error("%s:%i: log block without filename",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		return 1;
-	}
-	if (!ce->ce_entries)
-	{
-		config_error("%s:%i: empty log block",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		return 1;
-	}
-
-	/* Convert to absolute path (if needed) unless it's "syslog" */
-	if (strcmp(ce->ce_vardata, "syslog"))
-		convert_to_absolute_path(&ce->ce_vardata, LOGDIR);
-
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "flags"))
-		{
-			if (has_flags)
-			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "log::flags");
-				continue;
-			}
-			has_flags = 1;
-			if (!cep->ce_entries)
-			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "log", cep->ce_varname);
-				errors++;
-				continue;
-			}
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-			{
-				if (!config_binary_flags_search(_LogFlags, cepp->ce_varname, ARRAY_SIZEOF(_LogFlags)))
-				{
-					config_error_unknownflag(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "log", cepp->ce_varname);
-					errors++;
-				}
-			}
-		}
-		else if (!strcmp(cep->ce_varname, "maxsize"))
-		{
-			if (has_maxsize)
-			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "log::maxsize");
-				continue;
-			}
-			has_maxsize = 1;
-			if (!cep->ce_vardata)
-			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "log", cep->ce_varname);
-				errors++;
-			}
-		}
-		else
-		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"log", cep->ce_varname);
-			errors++;
-			continue;
-		}
-	}
-
-	if (!has_flags)
-	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			"log::flags");
-		errors++;
-	}
-
-	fname = unreal_strftime(ce->ce_vardata);
-	if ((fd = fd_fileopen(fname, O_WRONLY|O_CREAT)) == -1)
-	{
-		config_error("%s:%i: Couldn't open logfile (%s) for writing: %s",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			fname, strerror(errno));
-		errors++;
-	} else
-	{
-		fd_close(fd);
-	}
-
-	return errors;
-}
-
 int	_conf_link(ConfigFile *conf, ConfigEntry *ce)
 {
 	ConfigEntry *cep, *cepp, *ceppp;
 	ConfigItem_link *link = NULL;
-	NameValue *ofp;
 
 	link = safe_alloc(sizeof(ConfigItem_link));
-	safe_strdup(link->servername, ce->ce_vardata);
+	safe_strdup(link->servername, ce->value);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "incoming"))
+		if (!strcmp(cep->name, "incoming"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "mask"))
+				if (!strcmp(cepp->name, "mask"))
 				{
 					unreal_add_masks(&link->incoming.mask, cepp);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "outgoing"))
+		else if (!strcmp(cep->name, "outgoing"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "bind-ip"))
-					safe_strdup(link->outgoing.bind_ip, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "hostname"))
-					safe_strdup(link->outgoing.hostname, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "port"))
-					link->outgoing.port = atoi(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "options"))
+				if (!strcmp(cepp->name, "bind-ip"))
+					safe_strdup(link->outgoing.bind_ip, cepp->value);
+				else if (!strcmp(cepp->name, "hostname"))
+					safe_strdup(link->outgoing.hostname, cepp->value);
+				else if (!strcmp(cepp->name, "port"))
+					link->outgoing.port = atoi(cepp->value);
+				else if (!strcmp(cepp->name, "options"))
 				{
-					/* TODO: options still need to be split */
 					link->outgoing.options = 0;
-					for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 					{
-						if ((ofp = config_binary_flags_search(_LinkFlags, ceppp->ce_varname, ARRAY_SIZEOF(_LinkFlags))))
-							link->outgoing.options |= ofp->flag;
+						long v;
+						if ((v = nv_find_by_name(_LinkFlags, ceppp->name)))
+							link->outgoing.options |= v;
 					}
 				}
-				else if (!strcmp(cepp->ce_varname, "ssl-options") || !strcmp(cepp->ce_varname, "tls-options"))
+				else if (!strcmp(cepp->name, "ssl-options") || !strcmp(cepp->name, "tls-options"))
 				{
 					link->tls_options = safe_alloc(sizeof(TLSOptions));
 					conf_tlsblock(conf, cepp, link->tls_options);
@@ -6423,38 +6099,39 @@ int	_conf_link(ConfigFile *conf, ConfigEntry *ce)
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 			link->auth = AuthBlockToAuthConfig(cep);
-		else if (!strcmp(cep->ce_varname, "hub"))
-			safe_strdup(link->hub, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "leaf"))
-			safe_strdup(link->leaf, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "leaf-depth") || !strcmp(cep->ce_varname, "leafdepth"))
-			link->leaf_depth = atoi(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "class"))
-		{
-			link->class = find_class(cep->ce_vardata);
+		else if (!strcmp(cep->name, "hub"))
+			safe_strdup(link->hub, cep->value);
+		else if (!strcmp(cep->name, "leaf"))
+			safe_strdup(link->leaf, cep->value);
+		else if (!strcmp(cep->name, "leaf-depth") || !strcmp(cep->name, "leafdepth"))
+			link->leaf_depth = atoi(cep->value);
+		else if (!strcmp(cep->name, "class"))
+		{
+			link->class = find_class(cep->value);
 			if (!link->class || (link->class->flag.temporary == 1))
 			{
 				config_status("%s:%i: illegal link::class, unknown class '%s' using default of class 'default'",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum,
-					cep->ce_vardata);
+					cep->file->filename,
+					cep->line_number,
+					cep->value);
 				link->class = default_class;
 			}
 			link->class->xrefcount++;
 		}
-		else if (!strcmp(cep->ce_varname, "verify-certificate"))
+		else if (!strcmp(cep->name, "verify-certificate"))
 		{
-			link->verify_certificate = config_checkval(cep->ce_vardata, CFG_YESNO);
+			link->verify_certificate = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "options"))
+		else if (!strcmp(cep->name, "options"))
 		{
 			link->options = 0;
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if ((ofp = config_binary_flags_search(_LinkFlags, cepp->ce_varname, ARRAY_SIZEOF(_LinkFlags))))
-					link->options |= ofp->flag;
+				long v;
+				if ((v = nv_find_by_name(_LinkFlags, cepp->name)))
+					link->options |= v;
 			}
 		}
 	}
@@ -6463,20 +6140,19 @@ int	_conf_link(ConfigFile *conf, ConfigEntry *ce)
 	if (!link->hub && !link->leaf)
 		safe_strdup(link->hub, "*");
 
-	AddListItem(link, conf_link);
+	AppendListItem(link, conf_link);
 	return 0;
 }
 
 /** Helper function for erroring on duplicate items.
- * TODO: make even more friendy for dev's?
  */
 int config_detect_duplicate(int *var, ConfigEntry *ce, int *errors)
 {
 	if (*var)
 	{
 		config_error("%s:%d: Duplicate %s directive",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_varname);
+			ce->file->filename, ce->line_number,
+			ce->name);
 		(*errors)++;
 		return 1;
 	} else {
@@ -6495,31 +6171,31 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 	int has_outgoing_options = 0, has_hub = 0, has_leaf = 0, has_leaf_depth = 0;
 	int has_password = 0, has_class = 0, has_options = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: link without servername. Expected: link servername { ... }",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 
 	}
 
-	if (!strchr(ce->ce_vardata, '.'))
+	if (!strchr(ce->value, '.'))
 	{
 		config_error("%s:%i: link: bogus server name. Expected: link servername { ... }",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "incoming"))
+		if (!strcmp(cep->name, "incoming"))
 		{
 			config_detect_duplicate(&has_incoming, cep, &errors);
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "mask"))
+				if (!strcmp(cepp->name, "mask"))
 				{
-					if (cepp->ce_vardata || cepp->ce_entries)
+					if (cepp->value || cepp->items)
 						has_incoming_mask = 1;
 					else
 					if (config_is_blankorempty(cepp, "link::incoming"))
@@ -6530,12 +6206,12 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "outgoing"))
+		else if (!strcmp(cep->name, "outgoing"))
 		{
 			config_detect_duplicate(&has_outgoing, cep, &errors);
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "bind-ip"))
+				if (!strcmp(cepp->name, "bind-ip"))
 				{
 					if (config_is_blankorempty(cepp, "link::outgoing"))
 					{
@@ -6545,7 +6221,7 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 					config_detect_duplicate(&has_outgoing_bind_ip, cepp, &errors);
 					// todo: ipv4 vs ipv6
 				}
-				else if (!strcmp(cepp->ce_varname, "hostname"))
+				else if (!strcmp(cepp->name, "hostname"))
 				{
 					if (config_is_blankorempty(cepp, "link::outgoing"))
 					{
@@ -6553,14 +6229,14 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 						continue;
 					}
 					config_detect_duplicate(&has_outgoing_hostname, cepp, &errors);
-					if (strchr(cepp->ce_vardata, '*') || strchr(cepp->ce_vardata, '?'))
+					if (strchr(cepp->value, '*') || strchr(cepp->value, '?'))
 					{
 						config_error("%s:%i: hostname in link::outgoing(!) cannot contain wildcards",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+							cepp->file->filename, cepp->line_number);
 						errors++;
 					}
 				}
-				else if (!strcmp(cepp->ce_varname, "port"))
+				else if (!strcmp(cepp->name, "port"))
 				{
 					if (config_is_blankorempty(cepp, "link::outgoing"))
 					{
@@ -6569,40 +6245,39 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 					}
 					config_detect_duplicate(&has_outgoing_port, cepp, &errors);
 				}
-				else if (!strcmp(cepp->ce_varname, "options"))
+				else if (!strcmp(cepp->name, "options"))
 				{
 					config_detect_duplicate(&has_outgoing_options, cepp, &errors);
-					for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 					{
-						if (!strcmp(ceppp->ce_varname, "autoconnect"))
+						if (!strcmp(ceppp->name, "autoconnect"))
 							;
-						else if (!strcmp(ceppp->ce_varname, "ssl") || !strcmp(ceppp->ce_varname, "tls"))
+						else if (!strcmp(ceppp->name, "ssl") || !strcmp(ceppp->name, "tls"))
 							;
-						else if (!strcmp(ceppp->ce_varname, "insecure"))
+						else if (!strcmp(ceppp->name, "insecure"))
 							;
 						else
 						{
-							config_error_unknownopt(ceppp->ce_fileptr->cf_filename,
-								ceppp->ce_varlinenum, "link::outgoing", ceppp->ce_varname);
+							config_error_unknownopt(ceppp->file->filename,
+								ceppp->line_number, "link::outgoing", ceppp->name);
 							errors++;
 						}
-						// TODO: validate more options (?) and use list rather than code here...
 					}
 				}
-				else if (!strcmp(cepp->ce_varname, "ssl-options") || !strcmp(cepp->ce_varname, "tls-options"))
+				else if (!strcmp(cepp->name, "ssl-options") || !strcmp(cepp->name, "tls-options"))
 				{
 					test_tlsblock(conf, cepp, &errors);
 				}
 				else
 				{
 					config_error("%s:%d: Unknown directive '%s'",
-					             cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
+					             cepp->file->filename, cepp->line_number,
 					             config_var(cepp));
 					errors++;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 		{
 			config_detect_duplicate(&has_password, cep, &errors);
 			if (Auth_CheckError(cep) < 0)
@@ -6615,15 +6290,14 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 				    (auth->type != AUTHTYPE_TLS_CLIENTCERTFP) && (auth->type != AUTHTYPE_SPKIFP))
 				{
 					config_error("%s:%i: password in link block should be plaintext OR should be the "
-					             "SSL or SPKI fingerprint of the remote link (=better)",
-					             /* TODO: mention some faq or wiki item for more information */
-					             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					             "certificate or SPKI fingerprint of the remote link (=better)",
+					             cep->file->filename, cep->line_number);
 					errors++;
 				}
 				Auth_FreeAuthConfig(auth);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "hub"))
+		else if (!strcmp(cep->name, "hub"))
 		{
 			if (config_is_blankorempty(cep, "link"))
 			{
@@ -6632,7 +6306,7 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 			}
 			config_detect_duplicate(&has_hub, cep, &errors);
 		}
-		else if (!strcmp(cep->ce_varname, "leaf"))
+		else if (!strcmp(cep->name, "leaf"))
 		{
 			if (config_is_blankorempty(cep, "link"))
 			{
@@ -6641,7 +6315,7 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 			}
 			config_detect_duplicate(&has_leaf, cep, &errors);
 		}
-		else if (!strcmp(cep->ce_varname, "leaf-depth") || !strcmp(cep->ce_varname, "leafdepth"))
+		else if (!strcmp(cep->name, "leaf-depth") || !strcmp(cep->name, "leafdepth"))
 		{
 			if (config_is_blankorempty(cep, "link"))
 			{
@@ -6650,7 +6324,7 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 			}
 			config_detect_duplicate(&has_leaf_depth, cep, &errors);
 		}
-		else if (!strcmp(cep->ce_varname, "class"))
+		else if (!strcmp(cep->name, "class"))
 		{
 			if (config_is_blankorempty(cep, "link"))
 			{
@@ -6659,14 +6333,14 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 			}
 			config_detect_duplicate(&has_class, cep, &errors);
 		}
-		else if (!strcmp(cep->ce_varname, "ciphers"))
+		else if (!strcmp(cep->name, "ciphers"))
 		{
 			config_error("%s:%d: link::ciphers has been moved to link::outgoing::ssl-options::ciphers, "
 			             "see https://www.unrealircd.org/docs/FAQ#link::ciphers_no_longer_works",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			             cep->file->filename, cep->line_number);
 			errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "verify-certificate"))
+		else if (!strcmp(cep->name, "verify-certificate"))
 		{
 			if (config_is_blankorempty(cep, "link"))
 			{
@@ -6674,27 +6348,27 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 				continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "options"))
+		else if (!strcmp(cep->name, "options"))
 		{
 			config_detect_duplicate(&has_options, cep, &errors);
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "quarantine"))
+				if (!strcmp(cepp->name, "quarantine"))
 					;
 				else
 				{
 					config_error("%s:%d: link::options only has one possible option ('quarantine', rarely used). "
 					             "Option '%s' is unrecognized. "
 					             "Perhaps you meant to set an outgoing option in link::outgoing::options instead?",
-					             cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname);
+					             cepp->file->filename, cepp->line_number, cepp->name);
 					errors++;
 				}
 			}
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename,
-			    cep->ce_varlinenum, "link", cep->ce_varname);
+			config_error_unknown(cep->file->filename,
+			    cep->line_number, "link", cep->name);
 			errors++;
 			continue;
 		}
@@ -6703,9 +6377,8 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 	if (!has_incoming && !has_outgoing)
 	{
 		config_error("%s:%d: link block needs at least an incoming or outgoing section.",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
-		need_34_upgrade = 1;
 	}
 
 	if (has_incoming)
@@ -6713,7 +6386,7 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 		/* If we have an incoming sub-block then we need at least 'mask' and 'password' */
 		if (!has_incoming_mask)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::incoming::mask");
+			config_error_missing(ce->file->filename, ce->line_number, "link::incoming::mask");
 			errors++;
 		}
 	}
@@ -6723,12 +6396,12 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 		/* If we have an outgoing sub-block then we need at least a hostname and port */
 		if (!has_outgoing_hostname)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::outgoing::hostname");
+			config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::hostname");
 			errors++;
 		}
 		if (!has_outgoing_port)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::outgoing::port");
+			config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::port");
 			errors++;
 		}
 	}
@@ -6736,12 +6409,12 @@ int	_test_link(ConfigFile *conf, ConfigEntry *ce)
 	/* The only other generic options that are required are 'class' and 'password' */
 	if (!has_password)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::password");
+		config_error_missing(ce->file->filename, ce->line_number, "link::password");
 		errors++;
 	}
 	if (!has_class)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"link::class");
 		errors++;
 	}
@@ -6756,11 +6429,11 @@ int     _conf_ban(ConfigFile *conf, ConfigEntry *ce)
 	Hook *h;
 
 	ca = safe_alloc(sizeof(ConfigItem_ban));
-	if (!strcmp(ce->ce_vardata, "realname"))
+	if (!strcmp(ce->value, "realname"))
 		ca->flag.type = CONF_BAN_REALNAME;
-	else if (!strcmp(ce->ce_vardata, "server"))
+	else if (!strcmp(ce->value, "server"))
 		ca->flag.type = CONF_BAN_SERVER;
-	else if (!strcmp(ce->ce_vardata, "version"))
+	else if (!strcmp(ce->value, "version"))
 	{
 		ca->flag.type = CONF_BAN_VERSION;
 		tempiConf.use_ban_version = 1; /* enable CTCP VERSION on connect */
@@ -6776,16 +6449,16 @@ int     _conf_ban(ConfigFile *conf, ConfigEntry *ce)
 		}
 		return 0;
 	}
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			safe_strdup(ca->mask, cep->ce_vardata);
+			safe_strdup(ca->mask, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
-			safe_strdup(ca->reason, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "action"))
-			ca->action = banact_stringtoval(cep->ce_vardata);
+		else if (!strcmp(cep->name, "reason"))
+			safe_strdup(ca->reason, cep->value);
+		else if (!strcmp(cep->name, "action"))
+			ca->action = banact_stringtoval(cep->value);
 	}
 	AddListItem(ca, conf_ban);
 	return 0;
@@ -6799,17 +6472,17 @@ int     _test_ban(ConfigFile *conf, ConfigEntry *ce)
 	char type = 0;
 	char has_mask = 0, has_action = 0, has_reason = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: ban without type",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	else if (!strcmp(ce->ce_vardata, "server"))
+	else if (!strcmp(ce->value, "server"))
 	{}
-	else if (!strcmp(ce->ce_vardata, "realname"))
+	else if (!strcmp(ce->value, "realname"))
 	{}
-	else if (!strcmp(ce->ce_vardata, "version"))
+	else if (!strcmp(ce->value, "version"))
 		type = 'v';
 	else
 	{
@@ -6842,53 +6515,53 @@ int     _test_ban(ConfigFile *conf, ConfigEntry *ce)
 		}
 		if (!used) {
 			config_error("%s:%i: unknown ban type %s",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				ce->ce_vardata);
+				ce->file->filename, ce->line_number,
+				ce->value);
 			return 1;
 		}
 		return errors;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "ban"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
 			if (has_mask)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "ban::mask");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::mask");
 				continue;
 			}
 			has_mask = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
 			if (has_reason)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "ban::reason");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::reason");
 				continue;
 			}
 			has_reason = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
 			if (has_action)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "ban::action");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::action");
 			}
 			has_action = 1;
-			if (!banact_stringtoval(cep->ce_vardata))
+			if (!banact_stringtoval(cep->value))
 			{
 				config_error("%s:%i: ban::action has unknown action type '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata);
+					cep->file->filename, cep->line_number,
+					cep->value);
 				errors++;
 			}
 		}
@@ -6896,20 +6569,20 @@ int     _test_ban(ConfigFile *conf, ConfigEntry *ce)
 
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"ban::mask");
 		errors++;
 	}
 	if (!has_reason)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"ban::reason");
 		errors++;
 	}
 	if (has_action && type != 'v')
 	{
 		config_error("%s:%d: ban::action specified even though type is not 'version'",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 	return errors;
@@ -6923,7 +6596,7 @@ int _conf_require(ConfigFile *conf, ConfigEntry *ce)
 	char *hostmask = NULL;
 	char *reason = NULL;
 
-	if (strcmp(ce->ce_vardata, "authentication") && strcmp(ce->ce_vardata, "sasl"))
+	if (strcmp(ce->value, "authentication") && strcmp(ce->value, "sasl"))
 	{
 		/* Some other block... run modules... */
 		int value;
@@ -6936,12 +6609,12 @@ int _conf_require(ConfigFile *conf, ConfigEntry *ce)
 		return 0;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
 			char buf[512], *p;
-			strlcpy(buf, cep->ce_vardata, sizeof(buf));
+			strlcpy(buf, cep->value, sizeof(buf));
 			p = strchr(buf, '@');
 			if (p)
 			{
@@ -6949,11 +6622,11 @@ int _conf_require(ConfigFile *conf, ConfigEntry *ce)
 				safe_strdup(usermask, buf);
 				safe_strdup(hostmask, p);
 			} else {
-				safe_strdup(hostmask, cep->ce_vardata);
+				safe_strdup(hostmask, cep->value);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
-			safe_strdup(reason, cep->ce_vardata);
+		else if (!strcmp(cep->name, "reason"))
+			safe_strdup(reason, cep->value);
 	}
 
 	if (!usermask)
@@ -6976,18 +6649,18 @@ int _test_require(ConfigFile *conf, ConfigEntry *ce)
 	Hook *h;
 	char has_mask = 0, has_reason = 0;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: require without type, did you mean 'require authentication'?",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	if (!strcmp(ce->ce_vardata, "authentication"))
+	if (!strcmp(ce->value, "authentication"))
 	{}
-	else if (!strcmp(ce->ce_vardata, "sasl"))
+	else if (!strcmp(ce->value, "sasl"))
 	{
 		config_warn("%s:%i: the 'require sasl' block is now called 'require authentication'",
-		            ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		            ce->file->filename, ce->line_number);
 	}
 	else
 	{
@@ -7020,36 +6693,36 @@ int _test_require(ConfigFile *conf, ConfigEntry *ce)
 		}
 		if (!used) {
 			config_error("%s:%i: unknown require type '%s'",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				ce->ce_vardata);
+				ce->file->filename, ce->line_number,
+				ce->value);
 			return 1;
 		}
 		return errors;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "require"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
 			if (has_mask)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "require::mask");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "require::mask");
 				continue;
 			}
 			has_mask = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
 			if (has_reason)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "require::reason");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "require::reason");
 				continue;
 			}
 			has_reason = 1;
@@ -7058,45 +6731,43 @@ int _test_require(ConfigFile *conf, ConfigEntry *ce)
 
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"require::mask");
 		errors++;
 	}
 	if (!has_reason)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"require::reason");
 		errors++;
 	}
 	return errors;
 }
 
-#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
-#define CheckNullAllowEmpty(x) if ((!(x)->ce_vardata)) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
-#define CheckDuplicate(cep, name, display) if (settings.has_##name) { config_warn_duplicate((cep)->ce_fileptr->cf_filename, cep->ce_varlinenum, "set::" display); continue; } else settings.has_##name = 1
+#define CheckDuplicate(cep, name, display) if (settings.has_##name) { config_warn_duplicate((cep)->file->filename, cep->line_number, "set::" display); continue; } else settings.has_##name = 1
 
 void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 {
 	ConfigEntry *cepp, *ceppp;
 	int errors = 0;
 
-	for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+	for (cepp = cep->items; cepp; cepp = cepp->next)
 	{
-		if (!strcmp(cepp->ce_varname, "renegotiate-timeout"))
+		if (!strcmp(cepp->name, "renegotiate-timeout"))
 		{
 		}
-		else if (!strcmp(cepp->ce_varname, "renegotiate-bytes"))
+		else if (!strcmp(cepp->name, "renegotiate-bytes"))
 		{
 		}
-		else if (!strcmp(cepp->ce_varname, "ciphers") || !strcmp(cepp->ce_varname, "server-cipher-list"))
+		else if (!strcmp(cepp->name, "ciphers") || !strcmp(cepp->name, "server-cipher-list"))
 		{
 			CheckNull(cepp);
 		}
-		else if (!strcmp(cepp->ce_varname, "ciphersuites"))
+		else if (!strcmp(cepp->name, "ciphersuites"))
 		{
 			CheckNull(cepp);
 		}
-		else if (!strcmp(cepp->ce_varname, "ecdh-curves"))
+		else if (!strcmp(cepp->name, "ecdh-curves"))
 		{
 			CheckNull(cepp);
 #ifndef HAS_SSL_CTX_SET1_CURVES_LIST
@@ -7107,7 +6778,7 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			errors++;
 #endif
 		}
-		else if (!strcmp(cepp->ce_varname, "protocols"))
+		else if (!strcmp(cepp->name, "protocols"))
 		{
 			char copy[512], *p, *name;
 			int v = 0;
@@ -7115,7 +6786,7 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			char modifier;
 
 			CheckNull(cepp);
-			strlcpy(copy, cepp->ce_vardata, sizeof(copy));
+			strlcpy(copy, cepp->value, sizeof(copy));
 			for (name = strtoken(&p, copy, ","); name; name = strtoken(&p, NULL, ","))
 			{
 				modifier = '\0';
@@ -7142,11 +6813,11 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 #ifdef SSL_OP_NO_TLSv1_3
 					config_warn("%s:%i: %s: unknown protocol '%s'. "
 								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2,TLSv1.3",
-								 cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name);
+								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
 #else
 					config_warn("%s:%i: %s: unknown protocol '%s'. "
 								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2",
-								 cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name);
+								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
 #endif
 				}
 
@@ -7163,28 +6834,28 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			if (v == 0)
 			{
 				config_error("%s:%i: %s: no protocols enabled. Hint: set at least TLSv1.2",
-					cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp));
+					cepp->file->filename, cepp->line_number, config_var(cepp));
 				errors++;
 			}
 		}
-		else if (!strcmp(cepp->ce_varname, "certificate") ||
-		         !strcmp(cepp->ce_varname, "key") ||
-		         !strcmp(cepp->ce_varname, "trusted-ca-file"))
+		else if (!strcmp(cepp->name, "certificate") ||
+		         !strcmp(cepp->name, "key") ||
+		         !strcmp(cepp->name, "trusted-ca-file"))
 		{
 			char *path;
 			CheckNull(cepp);
-			path = convert_to_absolute_path_duplicate(cepp->ce_vardata, CONFDIR);
+			path = convert_to_absolute_path_duplicate(cepp->value, CONFDIR);
 			if (!file_exists(path))
 			{
 				config_error("%s:%i: %s: could not open '%s': %s",
-					cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp),
+					cepp->file->filename, cepp->line_number, config_var(cepp),
 					path, strerror(errno));
 				safe_free(path);
 				errors++;
 			}
 			safe_free(path);
 		}
-		else if (!strcmp(cepp->ce_varname, "dh"))
+		else if (!strcmp(cepp->name, "dh"))
 		{
 			/* Support for this undocumented option was silently dropped in 5.0.0.
 			 * Since 5.0.7 we print a warning about it, since you never know
@@ -7192,10 +6863,10 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			 */
 			config_warn("%s:%d: Not reading DH file '%s'. UnrealIRCd does not support old DH(E), we use modern ECDHE/EECDH. "
 			            "Just remove the 'dh' directive from your config file to get rid of this warning.",
-				cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-				cepp->ce_vardata ? cepp->ce_vardata : "");
+				cepp->file->filename, cepp->line_number,
+				cepp->value ? cepp->value : "");
 		}
-		else if (!strcmp(cepp->ce_varname, "outdated-protocols"))
+		else if (!strcmp(cepp->name, "outdated-protocols"))
 		{
 			char copy[512], *p, *name;
 			int v = 0;
@@ -7203,7 +6874,7 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			char modifier;
 
 			CheckNull(cepp);
-			strlcpy(copy, cepp->ce_vardata, sizeof(copy));
+			strlcpy(copy, cepp->value, sizeof(copy));
 			for (name = strtoken(&p, copy, ","); name; name = strtoken(&p, NULL, ","))
 			{
 				if (!strcasecmp(name, "All"))
@@ -7221,64 +6892,66 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 #ifdef SSL_OP_NO_TLSv1_3
 					config_warn("%s:%i: %s: unknown protocol '%s'. "
 								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2,TLSv1.3",
-								 cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name);
+								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
 #else
 					config_warn("%s:%i: %s: unknown protocol '%s'. "
 								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2",
-								 cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name);
+								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
 #endif
 		                }
 			}
 		}
-		else if (!strcmp(cepp->ce_varname, "outdated-ciphers"))
+		else if (!strcmp(cepp->name, "outdated-ciphers"))
 		{
 			CheckNull(cepp);
 		}
-		else if (!strcmp(cepp->ce_varname, "options"))
+		else if (!strcmp(cepp->name, "options"))
 		{
-			for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
-				if (!config_binary_flags_search(_TLSFlags, ceppp->ce_varname, ARRAY_SIZEOF(_TLSFlags)))
+			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
+			{
+				if (!nv_find_by_name(_TLSFlags, ceppp->name))
 				{
-					config_error("%s:%i: unknown SSL/TLS option '%s'",
-							 ceppp->ce_fileptr->cf_filename,
-							 ceppp->ce_varlinenum, ceppp->ce_varname);
+					config_error("%s:%i: unknown TLS option '%s'",
+							 ceppp->file->filename,
+							 ceppp->line_number, ceppp->name);
 					errors ++;
 				}
+			}
 		}
-		else if (!strcmp(cepp->ce_varname, "sts-policy"))
+		else if (!strcmp(cepp->name, "sts-policy"))
 		{
 			int has_port = 0;
 			int has_duration = 0;
 
-			for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 			{
-				if (!strcmp(ceppp->ce_varname, "port"))
+				if (!strcmp(ceppp->name, "port"))
 				{
 					int port;
 					CheckNull(ceppp);
-					port = atoi(ceppp->ce_vardata);
+					port = atoi(ceppp->value);
 					if ((port < 1) || (port > 65535))
 					{
 						config_error("%s:%i: invalid port number specified in sts-policy::port (%d)",
-						             ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, port);
+						             ceppp->file->filename, ceppp->line_number, port);
 						errors++;
 					}
 					has_port = 1;
 				}
-				else if (!strcmp(ceppp->ce_varname, "duration"))
+				else if (!strcmp(ceppp->name, "duration"))
 				{
 					long duration;
 					CheckNull(ceppp);
-					duration = config_checkval(ceppp->ce_vardata, CFG_TIME);
+					duration = config_checkval(ceppp->value, CFG_TIME);
 					if (duration < 1)
 					{
 						config_error("%s:%i: invalid duration specified in sts-policy::duration (%ld seconds)",
-						             ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, duration);
+						             ceppp->file->filename, ceppp->line_number, duration);
 						errors++;
 					}
 					has_duration = 1;
 				}
-				else if (!strcmp(ceppp->ce_varname, "preload"))
+				else if (!strcmp(ceppp->name, "preload"))
 				{
 					CheckNull(ceppp);
 				}
@@ -7286,20 +6959,20 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			if (!has_port)
 			{
 				config_error("%s:%i: sts-policy block without port",
-				             cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+				             cepp->file->filename, cepp->line_number);
 				errors++;
 			}
 			if (!has_duration)
 			{
 				config_error("%s:%i: sts-policy block without duration",
-				             cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+				             cepp->file->filename, cepp->line_number);
 				errors++;
 			}
 		}
 		else
 		{
 			config_error("%s:%i: unknown directive %s",
-				cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
+				cepp->file->filename, cepp->line_number,
 				config_var(cepp));
 			errors++;
 		}
@@ -7351,27 +7024,27 @@ void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions)
 	}
 
 	/* Now process the options */
-	for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+	for (cepp = cep->items; cepp; cepp = cepp->next)
 	{
-		if (!strcmp(cepp->ce_varname, "ciphers") || !strcmp(cepp->ce_varname, "server-cipher-list"))
+		if (!strcmp(cepp->name, "ciphers") || !strcmp(cepp->name, "server-cipher-list"))
 		{
-			safe_strdup(tlsoptions->ciphers, cepp->ce_vardata);
+			safe_strdup(tlsoptions->ciphers, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "ciphersuites"))
+		else if (!strcmp(cepp->name, "ciphersuites"))
 		{
-			safe_strdup(tlsoptions->ciphersuites, cepp->ce_vardata);
+			safe_strdup(tlsoptions->ciphersuites, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "ecdh-curves"))
+		else if (!strcmp(cepp->name, "ecdh-curves"))
 		{
-			safe_strdup(tlsoptions->ecdh_curves, cepp->ce_vardata);
+			safe_strdup(tlsoptions->ecdh_curves, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "protocols"))
+		else if (!strcmp(cepp->name, "protocols"))
 		{
 			char copy[512], *p, *name;
 			int option;
 			char modifier;
 
-			strlcpy(copy, cepp->ce_vardata, sizeof(copy));
+			strlcpy(copy, cepp->value, sizeof(copy));
 			tlsoptions->protocols = 0;
 			for (name = strtoken(&p, copy, ","); name; name = strtoken(&p, NULL, ","))
 			{
@@ -7406,61 +7079,60 @@ void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions)
 				}
 			}
 		}
-		else if (!strcmp(cepp->ce_varname, "certificate"))
+		else if (!strcmp(cepp->name, "certificate"))
 		{
-			convert_to_absolute_path(&cepp->ce_vardata, CONFDIR);
-			safe_strdup(tlsoptions->certificate_file, cepp->ce_vardata);
+			convert_to_absolute_path(&cepp->value, CONFDIR);
+			safe_strdup(tlsoptions->certificate_file, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "key"))
+		else if (!strcmp(cepp->name, "key"))
 		{
-			convert_to_absolute_path(&cepp->ce_vardata, CONFDIR);
-			safe_strdup(tlsoptions->key_file, cepp->ce_vardata);
+			convert_to_absolute_path(&cepp->value, CONFDIR);
+			safe_strdup(tlsoptions->key_file, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "trusted-ca-file"))
+		else if (!strcmp(cepp->name, "trusted-ca-file"))
 		{
-			convert_to_absolute_path(&cepp->ce_vardata, CONFDIR);
-			safe_strdup(tlsoptions->trusted_ca_file, cepp->ce_vardata);
+			convert_to_absolute_path(&cepp->value, CONFDIR);
+			safe_strdup(tlsoptions->trusted_ca_file, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "outdated-protocols"))
+		else if (!strcmp(cepp->name, "outdated-protocols"))
 		{
-			safe_strdup(tlsoptions->outdated_protocols, cepp->ce_vardata);
+			safe_strdup(tlsoptions->outdated_protocols, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "outdated-ciphers"))
+		else if (!strcmp(cepp->name, "outdated-ciphers"))
 		{
-			safe_strdup(tlsoptions->outdated_ciphers, cepp->ce_vardata);
+			safe_strdup(tlsoptions->outdated_ciphers, cepp->value);
 		}
-		else if (!strcmp(cepp->ce_varname, "renegotiate-bytes"))
+		else if (!strcmp(cepp->name, "renegotiate-bytes"))
 		{
-			tlsoptions->renegotiate_bytes = config_checkval(cepp->ce_vardata, CFG_SIZE);
+			tlsoptions->renegotiate_bytes = config_checkval(cepp->value, CFG_SIZE);
 		}
-		else if (!strcmp(cepp->ce_varname, "renegotiate-timeout"))
+		else if (!strcmp(cepp->name, "renegotiate-timeout"))
 		{
-			tlsoptions->renegotiate_timeout = config_checkval(cepp->ce_vardata, CFG_TIME);
+			tlsoptions->renegotiate_timeout = config_checkval(cepp->value, CFG_TIME);
 		}
-		else if (!strcmp(cepp->ce_varname, "options"))
+		else if (!strcmp(cepp->name, "options"))
 		{
 			tlsoptions->options = 0;
-			for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 			{
-				ofl = config_binary_flags_search(_TLSFlags, ceppp->ce_varname, ARRAY_SIZEOF(_TLSFlags));
-				if (ofl) /* this should always be true */
-					tlsoptions->options |= ofl->flag;
+				long v = nv_find_by_name(_TLSFlags, ceppp->name);
+				tlsoptions->options |= v;
 			}
 		}
-		else if (!strcmp(cepp->ce_varname, "sts-policy"))
+		else if (!strcmp(cepp->name, "sts-policy"))
 		{
 			/* We do not inherit ::sts-policy if there is a specific block for this one... */
 			tlsoptions->sts_port = 0;
 			tlsoptions->sts_duration = 0;
 			tlsoptions->sts_preload = 0;
-			for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 			{
-				if (!strcmp(ceppp->ce_varname, "port"))
-					tlsoptions->sts_port = atoi(ceppp->ce_vardata);
-				else if (!strcmp(ceppp->ce_varname, "duration"))
-					tlsoptions->sts_duration = config_checkval(ceppp->ce_vardata, CFG_TIME);
-				else if (!strcmp(ceppp->ce_varname, "preload"))
-					tlsoptions->sts_preload = config_checkval(ceppp->ce_vardata, CFG_YESNO);
+				if (!strcmp(ceppp->name, "port"))
+					tlsoptions->sts_port = atoi(ceppp->value);
+				else if (!strcmp(ceppp->name, "duration"))
+					tlsoptions->sts_duration = config_checkval(ceppp->value, CFG_TIME);
+				else if (!strcmp(ceppp->name, "preload"))
+					tlsoptions->sts_preload = config_checkval(ceppp->value, CFG_YESNO);
 			}
 		}
 	}
@@ -7471,258 +7143,291 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep, *cepp, *ceppp, *cep4;
 	Hook *h;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "kline-address")) {
-			safe_strdup(tempiConf.kline_address, cep->ce_vardata);
+		if (!strcmp(cep->name, "kline-address")) {
+			safe_strdup(tempiConf.kline_address, cep->value);
 		}
-		if (!strcmp(cep->ce_varname, "gline-address")) {
-			safe_strdup(tempiConf.gline_address, cep->ce_vardata);
+		if (!strcmp(cep->name, "gline-address")) {
+			safe_strdup(tempiConf.gline_address, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "modes-on-connect")) {
-			tempiConf.conn_modes = (long) set_usermode(cep->ce_vardata);
+		else if (!strcmp(cep->name, "modes-on-connect")) {
+			tempiConf.conn_modes = (long) set_usermode(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "modes-on-oper")) {
-			tempiConf.oper_modes = (long) set_usermode(cep->ce_vardata);
+		else if (!strcmp(cep->name, "modes-on-oper")) {
+			tempiConf.oper_modes = (long) set_usermode(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "modes-on-join")) {
-			conf_channelmodes(cep->ce_vardata, &tempiConf.modes_on_join, 0);
+		else if (!strcmp(cep->name, "modes-on-join")) {
+			conf_channelmodes(cep->value, &tempiConf.modes_on_join);
+			tempiConf.modes_on_join_set = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "snomask-on-oper")) {
-			safe_strdup(tempiConf.oper_snomask, cep->ce_vardata);
+		else if (!strcmp(cep->name, "snomask-on-oper")) {
+			safe_strdup(tempiConf.oper_snomask, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "level-on-join")) {
-			tempiConf.level_on_join = channellevel_to_int(cep->ce_vardata);
+		else if (!strcmp(cep->name, "server-notice-colors")) {
+			tempiConf.server_notice_colors = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "static-quit")) {
-			safe_strdup(tempiConf.static_quit, cep->ce_vardata);
+		else if (!strcmp(cep->name, "level-on-join")) {
+			const char *res = channellevel_to_string(cep->value); /* 'halfop', etc */
+			if (!res)
+			{
+				/* This check needs to be here, in config run, because
+				 * now the channel modules are initialized and we know
+				 * which ones are available. This same information is
+				 * not available during config test, so we can't test
+				 * for it there like we normally do.
+				 */
+				if (!valid_channel_access_mode_letter(*cep->value))
+				{
+					config_warn("%s:%d: set::level-on-join: Unknown mode (access level) '%c'. "
+					            "That mode does not exist or is not a valid access mode "
+					            "like vhoaq.",
+					            cep->file->filename, cep->line_number,
+					            *cep->value);
+					config_warn("Falling back to to set::level-on-join none; now. "
+					            "This is probably not what you want!!!");
+				}
+				res = cep->value; /* if we reach this.. then it is a single letter */
+			}
+			safe_strdup(tempiConf.level_on_join, res);
+		}
+		else if (!strcmp(cep->name, "static-quit")) {
+			safe_strdup(tempiConf.static_quit, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "static-part")) {
-			safe_strdup(tempiConf.static_part, cep->ce_vardata);
+		else if (!strcmp(cep->name, "static-part")) {
+			safe_strdup(tempiConf.static_part, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "who-limit")) {
-			tempiConf.who_limit = atol(cep->ce_vardata);
+		else if (!strcmp(cep->name, "who-limit")) {
+			tempiConf.who_limit = atol(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "maxbans")) {
-			tempiConf.maxbans = atol(cep->ce_vardata);
+		else if (!strcmp(cep->name, "maxbans")) {
+			tempiConf.maxbans = atol(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "maxbanlength")) {
-			tempiConf.maxbanlength = atol(cep->ce_vardata);
+		else if (!strcmp(cep->name, "maxbanlength")) {
+			tempiConf.maxbanlength = atol(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "silence-limit")) {
-			tempiConf.silence_limit = atol(cep->ce_vardata);
+		else if (!strcmp(cep->name, "silence-limit")) {
+			tempiConf.silence_limit = atol(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "auto-join")) {
-			safe_strdup(tempiConf.auto_join_chans, cep->ce_vardata);
+		else if (!strcmp(cep->name, "auto-join")) {
+			safe_strdup(tempiConf.auto_join_chans, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "oper-auto-join")) {
-			safe_strdup(tempiConf.oper_auto_join_chans, cep->ce_vardata);
+		else if (!strcmp(cep->name, "oper-auto-join")) {
+			safe_strdup(tempiConf.oper_auto_join_chans, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "check-target-nick-bans")) {
-			tempiConf.check_target_nick_bans = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->name, "check-target-nick-bans")) {
+			tempiConf.check_target_nick_bans = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "ping-cookie")) {
-			tempiConf.ping_cookie = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->name, "ping-cookie")) {
+			tempiConf.ping_cookie = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "watch-away-notification")) {
-			tempiConf.watch_away_notification = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->name, "watch-away-notification")) {
+			tempiConf.watch_away_notification = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "uhnames")) {
-			tempiConf.uhnames = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->name, "uhnames")) {
+			tempiConf.uhnames = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "allow-userhost-change")) {
-			if (!strcasecmp(cep->ce_vardata, "always"))
+		else if (!strcmp(cep->name, "allow-userhost-change")) {
+			if (!strcasecmp(cep->value, "always"))
 				tempiConf.userhost_allowed = UHALLOW_ALWAYS;
-			else if (!strcasecmp(cep->ce_vardata, "never"))
+			else if (!strcasecmp(cep->value, "never"))
 				tempiConf.userhost_allowed = UHALLOW_NEVER;
-			else if (!strcasecmp(cep->ce_vardata, "not-on-channels"))
+			else if (!strcasecmp(cep->value, "not-on-channels"))
 				tempiConf.userhost_allowed = UHALLOW_NOCHANS;
 			else
 				tempiConf.userhost_allowed = UHALLOW_REJOIN;
 		}
-		else if (!strcmp(cep->ce_varname, "channel-command-prefix")) {
-			safe_strdup(tempiConf.channel_command_prefix, cep->ce_vardata);
+		else if (!strcmp(cep->name, "channel-command-prefix")) {
+			safe_strdup(tempiConf.channel_command_prefix, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "restrict-usermodes")) {
+		else if (!strcmp(cep->name, "restrict-usermodes")) {
 			int i;
-			char *p = safe_alloc(strlen(cep->ce_vardata) + 1), *x = p;
+			char *p = safe_alloc(strlen(cep->value) + 1), *x = p;
 			/* The data should be something like 'Gw' or something,
 			 * but just in case users use '+Gw' then ignore the + (and -).
 			 */
-			for (i=0; i < strlen(cep->ce_vardata); i++)
-				if ((cep->ce_vardata[i] != '+') && (cep->ce_vardata[i] != '-'))
-					*x++ = cep->ce_vardata[i];
+			for (i=0; i < strlen(cep->value); i++)
+				if ((cep->value[i] != '+') && (cep->value[i] != '-'))
+					*x++ = cep->value[i];
 			*x = '\0';
 			tempiConf.restrict_usermodes = p;
 		}
-		else if (!strcmp(cep->ce_varname, "restrict-channelmodes")) {
+		else if (!strcmp(cep->name, "restrict-channelmodes")) {
 			int i;
-			char *p = safe_alloc(strlen(cep->ce_vardata) + 1), *x = p;
+			char *p = safe_alloc(strlen(cep->value) + 1), *x = p;
 			/* The data should be something like 'GL' or something,
 			 * but just in case users use '+GL' then ignore the + (and -).
 			 */
-			for (i=0; i < strlen(cep->ce_vardata); i++)
-				if ((cep->ce_vardata[i] != '+') && (cep->ce_vardata[i] != '-'))
-					*x++ = cep->ce_vardata[i];
+			for (i=0; i < strlen(cep->value); i++)
+				if ((cep->value[i] != '+') && (cep->value[i] != '-'))
+					*x++ = cep->value[i];
 			*x = '\0';
 			tempiConf.restrict_channelmodes = p;
 		}
-		else if (!strcmp(cep->ce_varname, "restrict-extendedbans")) {
-			safe_strdup(tempiConf.restrict_extendedbans, cep->ce_vardata);
+		else if (!strcmp(cep->name, "restrict-extendedbans")) {
+			safe_strdup(tempiConf.restrict_extendedbans, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "anti-spam-quit-message-time")) {
-			tempiConf.anti_spam_quit_message_time = config_checkval(cep->ce_vardata,CFG_TIME);
+		else if (!strcmp(cep->name, "named-extended-bans")) {
+			tempiConf.named_extended_bans = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "allow-user-stats")) {
-			if (!cep->ce_entries)
+		else if (!strcmp(cep->name, "anti-spam-quit-message-time")) {
+			tempiConf.anti_spam_quit_message_time = config_checkval(cep->value,CFG_TIME);
+		}
+		else if (!strcmp(cep->name, "allow-user-stats")) {
+			if (!cep->items)
 			{
-				safe_strdup(tempiConf.allow_user_stats, cep->ce_vardata);
+				safe_strdup(tempiConf.allow_user_stats, cep->value);
 			}
 			else
 			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+				for (cepp = cep->items; cepp; cepp = cepp->next)
 				{
 					OperStat *os = safe_alloc(sizeof(OperStat));
-					safe_strdup(os->flag, cepp->ce_varname);
+					safe_strdup(os->flag, cepp->name);
 					AddListItem(os, tempiConf.allow_user_stats_ext);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "maxchannelsperuser")) {
-			tempiConf.maxchannelsperuser = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "maxchannelsperuser")) {
+			tempiConf.maxchannelsperuser = atoi(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "ping-warning")) {
-			tempiConf.ping_warning = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "ping-warning")) {
+			tempiConf.ping_warning = atoi(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "maxdccallow")) {
-			tempiConf.maxdccallow = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "maxdccallow")) {
+			tempiConf.maxdccallow = atoi(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "max-targets-per-command"))
+		else if (!strcmp(cep->name, "max-targets-per-command"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				int v;
-				if (!strcmp(cepp->ce_vardata, "max"))
+				if (!strcmp(cepp->value, "max"))
 					v = MAXTARGETS_MAX;
 				else
-					v = atoi(cepp->ce_vardata);
-				setmaxtargets(cepp->ce_varname, v);
+					v = atoi(cepp->value);
+				setmaxtargets(cepp->name, v);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "network-name")) {
+		else if (!strcmp(cep->name, "network-name")) {
 			char *tmp;
-			safe_strdup(tempiConf.network.x_ircnetwork, cep->ce_vardata);
-			for (tmp = cep->ce_vardata; *cep->ce_vardata; cep->ce_vardata++) {
-				if (*cep->ce_vardata == ' ')
-					*cep->ce_vardata='-';
+			safe_strdup(tempiConf.network_name, cep->value);
+			for (tmp = cep->value; *cep->value; cep->value++) {
+				if (*cep->value == ' ')
+					*cep->value='-';
 			}
-			safe_strdup(tempiConf.network.x_ircnet005, tmp);
-			cep->ce_vardata = tmp;
+			safe_strdup(tempiConf.network_name_005, tmp);
+			cep->value = tmp;
 		}
-		else if (!strcmp(cep->ce_varname, "default-server")) {
-			safe_strdup(tempiConf.network.x_defserv, cep->ce_vardata);
+		else if (!strcmp(cep->name, "default-server")) {
+			safe_strdup(tempiConf.default_server, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "services-server")) {
-			safe_strdup(tempiConf.network.x_services_name, cep->ce_vardata);
+		else if (!strcmp(cep->name, "services-server")) {
+			safe_strdup(tempiConf.services_name, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "sasl-server")) {
-			safe_strdup(tempiConf.network.x_sasl_server, cep->ce_vardata);
+		else if (!strcmp(cep->name, "sasl-server")) {
+			safe_strdup(tempiConf.sasl_server, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "stats-server")) {
-			safe_strdup(tempiConf.network.x_stats_server, cep->ce_vardata);
+		else if (!strcmp(cep->name, "stats-server")) {
+			safe_strdup(tempiConf.stats_server, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "help-channel")) {
-			safe_strdup(tempiConf.network.x_helpchan, cep->ce_vardata);
+		else if (!strcmp(cep->name, "help-channel")) {
+			safe_strdup(tempiConf.helpchan, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "hiddenhost-prefix")) {
-			safe_strdup(tempiConf.network.x_hidden_host, cep->ce_vardata);
+		else if (!strcmp(cep->name, "cloak-prefix") || !strcmp(cep->name, "hiddenhost-prefix")) {
+			safe_strdup(tempiConf.cloak_prefix, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "hide-ban-reason")) {
-			tempiConf.hide_ban_reason = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->name, "hide-ban-reason")) {
+			tempiConf.hide_ban_reason = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "prefix-quit")) {
-			if (!strcmp(cep->ce_vardata, "0") || !strcmp(cep->ce_vardata, "no"))
-				safe_free(tempiConf.network.x_prefix_quit);
+		else if (!strcmp(cep->name, "prefix-quit")) {
+			if (!strcmp(cep->value, "0") || !strcmp(cep->value, "no"))
+				safe_free(tempiConf.prefix_quit);
 			else
-				safe_strdup(tempiConf.network.x_prefix_quit, cep->ce_vardata);
+				safe_strdup(tempiConf.prefix_quit, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "link")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
-				if (!strcmp(cepp->ce_varname, "bind-ip")) {
-					safe_strdup(tempiConf.link_bindip, cepp->ce_vardata);
+		else if (!strcmp(cep->name, "link")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next) {
+				if (!strcmp(cepp->name, "bind-ip")) {
+					safe_strdup(tempiConf.link_bindip, cepp->value);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "dns")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
-				if (!strcmp(cepp->ce_varname, "bind-ip")) {
-					safe_strdup(tempiConf.dns_bindip, cepp->ce_vardata);
-				}
-			}
-		}
-		else if (!strcmp(cep->ce_varname, "anti-flood")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+		else if (!strcmp(cep->name, "anti-flood")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+				int lag_penalty = -1;
+				int lag_penalty_bytes = -1;
+				for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 				{
-					if (!strcmp(ceppp->ce_varname, "handshake-data-flood"))
+					if (!strcmp(ceppp->name, "handshake-data-flood"))
 					{
-						for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
 						{
-							if (!strcmp(cep4->ce_varname, "amount"))
-								tempiConf.handshake_data_flood_amount = config_checkval(cep4->ce_vardata, CFG_SIZE);
-							else if (!strcmp(cep4->ce_varname, "ban-time"))
-								tempiConf.handshake_data_flood_ban_time = config_checkval(cep4->ce_vardata, CFG_TIME);
-							else if (!strcmp(cep4->ce_varname, "ban-action"))
-								tempiConf.handshake_data_flood_ban_action = banact_stringtoval(cep4->ce_vardata);
+							if (!strcmp(cep4->name, "amount"))
+								tempiConf.handshake_data_flood_amount = config_checkval(cep4->value, CFG_SIZE);
+							else if (!strcmp(cep4->name, "ban-time"))
+								tempiConf.handshake_data_flood_ban_time = config_checkval(cep4->value, CFG_TIME);
+							else if (!strcmp(cep4->name, "ban-action"))
+								tempiConf.handshake_data_flood_ban_action = banact_stringtoval(cep4->value);
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "away-flood"))
+					else if (!strcmp(ceppp->name, "away-flood"))
 					{
-						config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_AWAY);
+						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_AWAY);
 					}
-					else if (!strcmp(ceppp->ce_varname, "nick-flood"))
+					else if (!strcmp(ceppp->name, "nick-flood"))
 					{
-						config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_NICK);
+						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_NICK);
 					}
-					else if (!strcmp(ceppp->ce_varname, "join-flood"))
+					else if (!strcmp(ceppp->name, "join-flood"))
 					{
-						config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_JOIN);
+						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_JOIN);
 					}
-					else if (!strcmp(ceppp->ce_varname, "invite-flood"))
+					else if (!strcmp(ceppp->name, "invite-flood"))
 					{
-						config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_INVITE);
+						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_INVITE);
 					}
-					else if (!strcmp(ceppp->ce_varname, "knock-flood"))
+					else if (!strcmp(ceppp->name, "knock-flood"))
 					{
-						config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_KNOCK);
+						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_KNOCK);
 					}
-					else if (!strcmp(ceppp->ce_varname, "connect-flood"))
+					else if (!strcmp(ceppp->name, "lag-penalty"))
+					{
+						lag_penalty = atoi(ceppp->value);
+					}
+					else if (!strcmp(ceppp->name, "lag-penalty-bytes"))
+					{
+						lag_penalty_bytes = config_checkval(ceppp->value, CFG_SIZE);
+						if (lag_penalty_bytes <= 0)
+							lag_penalty_bytes = INT_MAX;
+					}
+					else if (!strcmp(ceppp->name, "connect-flood"))
 					{
 						int cnt, period;
-						config_parse_flood(ceppp->ce_vardata, &cnt, &period);
+						config_parse_flood(ceppp->value, &cnt, &period);
 						tempiConf.throttle_count = cnt;
 						tempiConf.throttle_period = period;
 					}
-					if (!strcmp(ceppp->ce_varname, "max-concurrent-conversations"))
+					if (!strcmp(ceppp->name, "max-concurrent-conversations"))
 					{
 						/* We use a hack here to make it fit our storage format */
 						char buf[64];
 						int users=0;
 						long every=0;
-						for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
 						{
-							if (!strcmp(cep4->ce_varname, "users"))
+							if (!strcmp(cep4->name, "users"))
 							{
-								users = atoi(cep4->ce_vardata);
+								users = atoi(cep4->value);
 							} else
-							if (!strcmp(cep4->ce_varname, "new-user-every"))
+							if (!strcmp(cep4->name, "new-user-every"))
 							{
-								every = config_checkval(cep4->ce_vardata, CFG_TIME);
+								every = config_checkval(cep4->value, CFG_TIME);
 							}
 						}
 						snprintf(buf, sizeof(buf), "%d:%ld", users, every);
-						config_parse_flood_generic(buf, &tempiConf, cepp->ce_varname, FLD_CONVERSATIONS);
+						config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_CONVERSATIONS);
 					}
 					else
 					{
@@ -7734,52 +7439,59 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 						}
 					}
 				}
+				if ((lag_penalty != -1) && (lag_penalty_bytes != -1))
+				{
+					/* We use a hack here to make it fit our storage format */
+					char buf[64];
+					snprintf(buf, sizeof(buf), "%d:%d", lag_penalty_bytes, lag_penalty);
+					config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_LAG_PENALTY);
+				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "options")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
-				if (!strcmp(cepp->ce_varname, "hide-ulines")) {
+		else if (!strcmp(cep->name, "options")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next) {
+				if (!strcmp(cepp->name, "hide-ulines")) {
 					tempiConf.hide_ulines = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "flat-map")) {
+				else if (!strcmp(cepp->name, "flat-map")) {
 					tempiConf.flat_map = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "show-opermotd")) {
-					tempiConf.som = 1;
+				else if (!strcmp(cepp->name, "show-opermotd")) {
+					tempiConf.show_opermotd = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "identd-check")) {
+				else if (!strcmp(cepp->name, "identd-check")) {
 					tempiConf.ident_check = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "fail-oper-warn")) {
+				else if (!strcmp(cepp->name, "fail-oper-warn")) {
 					tempiConf.fail_oper_warn = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "show-connect-info")) {
+				else if (!strcmp(cepp->name, "show-connect-info")) {
 					tempiConf.show_connect_info = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "no-connect-tls-info")) {
+				else if (!strcmp(cepp->name, "no-connect-tls-info")) {
 					tempiConf.no_connect_tls_info = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "dont-resolve")) {
+				else if (!strcmp(cepp->name, "dont-resolve")) {
 					tempiConf.dont_resolve = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "mkpasswd-for-everyone")) {
+				else if (!strcmp(cepp->name, "mkpasswd-for-everyone")) {
 					tempiConf.mkpasswd_for_everyone = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "allow-insane-bans")) {
+				else if (!strcmp(cepp->name, "allow-insane-bans")) {
 					tempiConf.allow_insane_bans = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "allow-part-if-shunned")) {
+				else if (!strcmp(cepp->name, "allow-part-if-shunned")) {
 					tempiConf.allow_part_if_shunned = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "disable-cap")) {
+				else if (!strcmp(cepp->name, "disable-cap")) {
 					tempiConf.disable_cap = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "disable-ipv6")) {
+				else if (!strcmp(cepp->name, "disable-ipv6")) {
 					/* other code handles this */
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "cloak-keys"))
+		else if (!strcmp(cep->name, "cloak-keys"))
 		{
 			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
 			{
@@ -7789,34 +7501,34 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 					break;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ident"))
+		else if (!strcmp(cep->name, "ident"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "connect-timeout"))
-					tempiConf.ident_connect_timeout = config_checkval(cepp->ce_vardata,CFG_TIME);
-				if (!strcmp(cepp->ce_varname, "read-timeout"))
-					tempiConf.ident_read_timeout = config_checkval(cepp->ce_vardata,CFG_TIME);
+				if (!strcmp(cepp->name, "connect-timeout"))
+					tempiConf.ident_connect_timeout = config_checkval(cepp->value,CFG_TIME);
+				if (!strcmp(cepp->name, "read-timeout"))
+					tempiConf.ident_read_timeout = config_checkval(cepp->value,CFG_TIME);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "spamfilter"))
+		else if (!strcmp(cep->name, "spamfilter"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "ban-time"))
-					tempiConf.spamfilter_ban_time = config_checkval(cepp->ce_vardata,CFG_TIME);
-				else if (!strcmp(cepp->ce_varname, "ban-reason"))
-					safe_strdup(tempiConf.spamfilter_ban_reason, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "virus-help-channel"))
-					safe_strdup(tempiConf.spamfilter_virus_help_channel, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "virus-help-channel-deny"))
-					tempiConf.spamfilter_vchan_deny = config_checkval(cepp->ce_vardata,CFG_YESNO);
-				else if (!strcmp(cepp->ce_varname, "except"))
+				if (!strcmp(cepp->name, "ban-time"))
+					tempiConf.spamfilter_ban_time = config_checkval(cepp->value,CFG_TIME);
+				else if (!strcmp(cepp->name, "ban-reason"))
+					safe_strdup(tempiConf.spamfilter_ban_reason, cepp->value);
+				else if (!strcmp(cepp->name, "virus-help-channel"))
+					safe_strdup(tempiConf.spamfilter_virus_help_channel, cepp->value);
+				else if (!strcmp(cepp->name, "virus-help-channel-deny"))
+					tempiConf.spamfilter_vchan_deny = config_checkval(cepp->value,CFG_YESNO);
+				else if (!strcmp(cepp->name, "except"))
 				{
 					char *name, *p;
 					SpamExcept *e;
-					safe_strdup(tempiConf.spamexcept_line, cepp->ce_vardata);
-					for (name = strtoken(&p, cepp->ce_vardata, ","); name; name = strtoken(&p, NULL, ","))
+					safe_strdup(tempiConf.spamexcept_line, cepp->value);
+					for (name = strtoken(&p, cepp->value, ","); name; name = strtoken(&p, NULL, ","))
 					{
 						if (*name == ' ')
 							name++;
@@ -7828,186 +7540,185 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 						}
 					}
 				}
-				else if (!strcmp(cepp->ce_varname, "detect-slow-warn"))
+				else if (!strcmp(cepp->name, "detect-slow-warn"))
 				{
-					tempiConf.spamfilter_detectslow_warn = atol(cepp->ce_vardata);
+					tempiConf.spamfilter_detectslow_warn = atol(cepp->value);
 				}
-				else if (!strcmp(cepp->ce_varname, "detect-slow-fatal"))
+				else if (!strcmp(cepp->name, "detect-slow-fatal"))
 				{
-					tempiConf.spamfilter_detectslow_fatal = atol(cepp->ce_vardata);
+					tempiConf.spamfilter_detectslow_fatal = atol(cepp->value);
 				}
-				else if (!strcmp(cepp->ce_varname, "stop-on-first-match"))
+				else if (!strcmp(cepp->name, "stop-on-first-match"))
 				{
-					tempiConf.spamfilter_stop_on_first_match = config_checkval(cepp->ce_vardata, CFG_YESNO);
+					tempiConf.spamfilter_stop_on_first_match = config_checkval(cepp->value, CFG_YESNO);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "default-bantime"))
+		else if (!strcmp(cep->name, "default-bantime"))
 		{
-			tempiConf.default_bantime = config_checkval(cep->ce_vardata,CFG_TIME);
+			tempiConf.default_bantime = config_checkval(cep->value,CFG_TIME);
 		}
-		else if (!strcmp(cep->ce_varname, "ban-version-tkl-time"))
+		else if (!strcmp(cep->name, "ban-version-tkl-time"))
 		{
-			tempiConf.ban_version_tkl_time = config_checkval(cep->ce_vardata,CFG_TIME);
+			tempiConf.ban_version_tkl_time = config_checkval(cep->value,CFG_TIME);
 		}
-		else if (!strcmp(cep->ce_varname, "min-nick-length")) {
-			int v = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "min-nick-length")) {
+			int v = atoi(cep->value);
 			tempiConf.min_nick_length = v;
 		}
-		else if (!strcmp(cep->ce_varname, "nick-length")) {
-			int v = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "nick-length")) {
+			int v = atoi(cep->value);
 			tempiConf.nick_length = v;
 		}
-		else if (!strcmp(cep->ce_varname, "topic-length")) {
-			int v = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "topic-length")) {
+			int v = atoi(cep->value);
 			tempiConf.topic_length = v;
 		}
-		else if (!strcmp(cep->ce_varname, "away-length")) {
-			int v = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "away-length")) {
+			int v = atoi(cep->value);
 			tempiConf.away_length = v;
 		}
-		else if (!strcmp(cep->ce_varname, "kick-length")) {
-			int v = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "kick-length")) {
+			int v = atoi(cep->value);
 			tempiConf.kick_length = v;
 		}
-		else if (!strcmp(cep->ce_varname, "quit-length")) {
-			int v = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->name, "quit-length")) {
+			int v = atoi(cep->value);
 			tempiConf.quit_length = v;
 		}
-		else if (!strcmp(cep->ce_varname, "ssl") || !strcmp(cep->ce_varname, "tls")) {
+		else if (!strcmp(cep->name, "ssl") || !strcmp(cep->name, "tls")) {
 			/* no need to alloc tempiConf.tls_options since config_defaults() already ensures it exists */
 			conf_tlsblock(conf, cep, tempiConf.tls_options);
 		}
-		else if (!strcmp(cep->ce_varname, "plaintext-policy"))
+		else if (!strcmp(cep->name, "plaintext-policy"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "user"))
-					tempiConf.plaintext_policy_user = policy_strtoval(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "oper"))
-					tempiConf.plaintext_policy_oper = policy_strtoval(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "server"))
-					tempiConf.plaintext_policy_server = policy_strtoval(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "user-message"))
-					addmultiline(&tempiConf.plaintext_policy_user_message, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "oper-message"))
-					addmultiline(&tempiConf.plaintext_policy_oper_message, cepp->ce_vardata);
+				if (!strcmp(cepp->name, "user"))
+					tempiConf.plaintext_policy_user = policy_strtoval(cepp->value);
+				else if (!strcmp(cepp->name, "oper"))
+					tempiConf.plaintext_policy_oper = policy_strtoval(cepp->value);
+				else if (!strcmp(cepp->name, "server"))
+					tempiConf.plaintext_policy_server = policy_strtoval(cepp->value);
+				else if (!strcmp(cepp->name, "user-message"))
+					addmultiline(&tempiConf.plaintext_policy_user_message, cepp->value);
+				else if (!strcmp(cepp->name, "oper-message"))
+					addmultiline(&tempiConf.plaintext_policy_oper_message, cepp->value);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "outdated-tls-policy"))
+		else if (!strcmp(cep->name, "outdated-tls-policy"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "user"))
-					tempiConf.outdated_tls_policy_user = policy_strtoval(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "oper"))
-					tempiConf.outdated_tls_policy_oper = policy_strtoval(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "server"))
-					tempiConf.outdated_tls_policy_server = policy_strtoval(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "user-message"))
-					safe_strdup(tempiConf.outdated_tls_policy_user_message, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "oper-message"))
-					safe_strdup(tempiConf.outdated_tls_policy_oper_message, cepp->ce_vardata);
+				if (!strcmp(cepp->name, "user"))
+					tempiConf.outdated_tls_policy_user = policy_strtoval(cepp->value);
+				else if (!strcmp(cepp->name, "oper"))
+					tempiConf.outdated_tls_policy_oper = policy_strtoval(cepp->value);
+				else if (!strcmp(cepp->name, "server"))
+					tempiConf.outdated_tls_policy_server = policy_strtoval(cepp->value);
+				else if (!strcmp(cepp->name, "user-message"))
+					safe_strdup(tempiConf.outdated_tls_policy_user_message, cepp->value);
+				else if (!strcmp(cepp->name, "oper-message"))
+					safe_strdup(tempiConf.outdated_tls_policy_oper_message, cepp->value);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "default-ipv6-clone-mask"))
+		else if (!strcmp(cep->name, "default-ipv6-clone-mask"))
 		{
-			tempiConf.default_ipv6_clone_mask = atoi(cep->ce_vardata);
+			tempiConf.default_ipv6_clone_mask = atoi(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "hide-list")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+		else if (!strcmp(cep->name, "hide-list")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "deny-channel"))
+				if (!strcmp(cepp->name, "deny-channel"))
 				{
 					tempiConf.hide_list = 1;
 					/* if we would expand this later then change this to a bitmask or struct or whatever */
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "max-unknown-connections-per-ip"))
+		else if (!strcmp(cep->name, "max-unknown-connections-per-ip"))
 		{
-			tempiConf.max_unknown_connections_per_ip = atoi(cep->ce_vardata);
+			tempiConf.max_unknown_connections_per_ip = atoi(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "handshake-timeout"))
+		else if (!strcmp(cep->name, "handshake-timeout"))
 		{
-			tempiConf.handshake_timeout = config_checkval(cep->ce_vardata, CFG_TIME);
+			tempiConf.handshake_timeout = config_checkval(cep->value, CFG_TIME);
 		}
-		else if (!strcmp(cep->ce_varname, "sasl-timeout"))
+		else if (!strcmp(cep->name, "sasl-timeout"))
 		{
-			tempiConf.sasl_timeout = config_checkval(cep->ce_vardata, CFG_TIME);
+			tempiConf.sasl_timeout = config_checkval(cep->value, CFG_TIME);
 		}
-		else if (!strcmp(cep->ce_varname, "handshake-delay"))
+		else if (!strcmp(cep->name, "handshake-delay"))
 		{
-			tempiConf.handshake_delay = config_checkval(cep->ce_vardata, CFG_TIME);
+			tempiConf.handshake_delay = config_checkval(cep->value, CFG_TIME);
 		}
-		else if (!strcmp(cep->ce_varname, "automatic-ban-target"))
+		else if (!strcmp(cep->name, "automatic-ban-target"))
 		{
-			tempiConf.automatic_ban_target = ban_target_strtoval(cep->ce_vardata);
+			tempiConf.automatic_ban_target = ban_target_strtoval(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "manual-ban-target"))
+		else if (!strcmp(cep->name, "manual-ban-target"))
 		{
-			tempiConf.manual_ban_target = ban_target_strtoval(cep->ce_vardata);
+			tempiConf.manual_ban_target = ban_target_strtoval(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "reject-message"))
+		else if (!strcmp(cep->name, "reject-message"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "too-many-connections"))
-					safe_strdup(tempiConf.reject_message_too_many_connections, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "server-full"))
-					safe_strdup(tempiConf.reject_message_server_full, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "unauthorized"))
-					safe_strdup(tempiConf.reject_message_unauthorized, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "kline"))
-					safe_strdup(tempiConf.reject_message_kline, cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "gline"))
-					safe_strdup(tempiConf.reject_message_gline, cepp->ce_vardata);
+				if (!strcmp(cepp->name, "too-many-connections"))
+					safe_strdup(tempiConf.reject_message_too_many_connections, cepp->value);
+				else if (!strcmp(cepp->name, "server-full"))
+					safe_strdup(tempiConf.reject_message_server_full, cepp->value);
+				else if (!strcmp(cepp->name, "unauthorized"))
+					safe_strdup(tempiConf.reject_message_unauthorized, cepp->value);
+				else if (!strcmp(cepp->name, "kline"))
+					safe_strdup(tempiConf.reject_message_kline, cepp->value);
+				else if (!strcmp(cepp->name, "gline"))
+					safe_strdup(tempiConf.reject_message_gline, cepp->value);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "topic-setter"))
+		else if (!strcmp(cep->name, "topic-setter"))
 		{
-			if (!strcmp(cep->ce_vardata, "nick"))
+			if (!strcmp(cep->value, "nick"))
 				tempiConf.topic_setter = SETTER_NICK;
-			else if (!strcmp(cep->ce_vardata, "nick-user-host"))
+			else if (!strcmp(cep->value, "nick-user-host"))
 				tempiConf.topic_setter = SETTER_NICK_USER_HOST;
 		}
-		else if (!strcmp(cep->ce_varname, "ban-setter"))
+		else if (!strcmp(cep->name, "ban-setter"))
 		{
-			if (!strcmp(cep->ce_vardata, "nick"))
+			if (!strcmp(cep->value, "nick"))
 				tempiConf.ban_setter = SETTER_NICK;
-			else if (!strcmp(cep->ce_vardata, "nick-user-host"))
+			else if (!strcmp(cep->value, "nick-user-host"))
 				tempiConf.ban_setter = SETTER_NICK_USER_HOST;
 		}
-		else if (!strcmp(cep->ce_varname, "ban-setter-sync") || !strcmp(cep->ce_varname, "ban-setter-synch"))
+		else if (!strcmp(cep->name, "ban-setter-sync") || !strcmp(cep->name, "ban-setter-synch"))
 		{
-			tempiConf.ban_setter_sync = config_checkval(cep->ce_vardata, CFG_YESNO);
+			tempiConf.ban_setter_sync = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "part-instead-of-quit-on-comment-change"))
+		else if (!strcmp(cep->name, "part-instead-of-quit-on-comment-change"))
 		{
-			tempiConf.part_instead_of_quit_on_comment_change = config_checkval(cep->ce_vardata, CFG_YESNO);
+			tempiConf.part_instead_of_quit_on_comment_change = config_checkval(cep->value, CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "broadcast-channel-messages"))
+		else if (!strcmp(cep->name, "broadcast-channel-messages"))
 		{
-			if (!strcmp(cep->ce_vardata, "auto"))
+			if (!strcmp(cep->value, "auto"))
 				tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO;
-			else if (!strcmp(cep->ce_vardata, "always"))
+			else if (!strcmp(cep->value, "always"))
 				tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_ALWAYS;
-			else if (!strcmp(cep->ce_vardata, "never"))
+			else if (!strcmp(cep->value, "never"))
 				tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_NEVER;
 		}
-		else if (!strcmp(cep->ce_varname, "allowed-channelchars"))
+		else if (!strcmp(cep->name, "allowed-channelchars"))
 		{
-			tempiConf.allowed_channelchars = allowed_channelchars_strtoval(cep->ce_vardata);
+			tempiConf.allowed_channelchars = allowed_channelchars_strtoval(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "hide-idle-time"))
+		else if (!strcmp(cep->name, "hide-idle-time"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "policy"))
-					tempiConf.hide_idle_time = hideidletime_strtoval(cepp->ce_vardata);
+				if (!strcmp(cepp->name, "policy"))
+					tempiConf.hide_idle_time = hideidletime_strtoval(cepp->value);
 			}
-		}
-		else
+		} else
 		{
 			int value;
 			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
@@ -8028,62 +7739,61 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 	int errors = 0;
 	Hook *h;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "kline-address")) {
+		if (!strcmp(cep->name, "kline-address")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, kline_address, "kline-address");
-			if (!strchr(cep->ce_vardata, '@') && !strchr(cep->ce_vardata, ':'))
+			if (!strchr(cep->value, '@') && !strchr(cep->value, ':'))
 			{
 				config_error("%s:%i: set::kline-address must be an e-mail or an URL",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 				continue;
 			}
-			else if (match_simple("*@unrealircd.com", cep->ce_vardata) || match_simple("*@unrealircd.org",cep->ce_vardata) || match_simple("unreal-*@lists.sourceforge.net",cep->ce_vardata))
+			else if (match_simple("*@unrealircd.com", cep->value) || match_simple("*@unrealircd.org",cep->value) || match_simple("unreal-*@lists.sourceforge.net",cep->value))
 			{
 				config_error("%s:%i: set::kline-address may not be an UnrealIRCd Team address",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++; continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "gline-address")) {
+		else if (!strcmp(cep->name, "gline-address")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, gline_address, "gline-address");
-			if (!strchr(cep->ce_vardata, '@') && !strchr(cep->ce_vardata, ':'))
+			if (!strchr(cep->value, '@') && !strchr(cep->value, ':'))
 			{
 				config_error("%s:%i: set::gline-address must be an e-mail or an URL",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 				continue;
 			}
-			else if (match_simple("*@unrealircd.com", cep->ce_vardata) || match_simple("*@unrealircd.org",cep->ce_vardata) || match_simple("unreal-*@lists.sourceforge.net",cep->ce_vardata))
+			else if (match_simple("*@unrealircd.com", cep->value) || match_simple("*@unrealircd.org",cep->value) || match_simple("unreal-*@lists.sourceforge.net",cep->value))
 			{
 				config_error("%s:%i: set::gline-address may not be an UnrealIRCd Team address",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++; continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "modes-on-connect")) {
+		else if (!strcmp(cep->name, "modes-on-connect")) {
 			char *p;
 			CheckNull(cep);
 			CheckDuplicate(cep, modes_on_connect, "modes-on-connect");
-			for (p = cep->ce_vardata; *p; p++)
+			for (p = cep->value; *p; p++)
 				if (strchr("orzSHqtW", *p))
 				{
 					config_error("%s:%i: set::modes-on-connect may not include mode '%c'",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p);
+						cep->file->filename, cep->line_number, *p);
 					errors++;
 				}
-			set_usermode(cep->ce_vardata);
 		}
-		else if (!strcmp(cep->ce_varname, "modes-on-join")) {
+		else if (!strcmp(cep->name, "modes-on-join")) {
 			char *c;
 			struct ChMode temp;
 			memset(&temp, 0, sizeof(temp));
 			CheckNull(cep);
 			CheckDuplicate(cep, modes_on_join, "modes-on-join");
-			for (c = cep->ce_vardata; *c; c++)
+			for (c = cep->value; *c; c++)
 			{
 				if (*c == ' ')
 					break; /* don't check the parameter ;p */
@@ -8097,340 +7807,340 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 					case 'b':
 					case 'e':
 					case 'I':
-					case 'k':
-					case 'l':
 						config_error("%s:%i: set::modes-on-join may not contain +%c",
-							cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *c);
+							cep->file->filename, cep->line_number, *c);
 						errors++;
 						break;
 				}
 			}
-			conf_channelmodes(cep->ce_vardata, &temp, 1);
-			if (temp.mode & MODE_SECRET && temp.mode & MODE_PRIVATE)
-			{
-				config_error("%s:%i: set::modes-on-join has both +s and +p",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-				errors++;
-			}
-
+			/* We can't really verify much here.
+			 * The channel mode modules have not been initialized
+			 * yet at this point, so we can't really verify much
+			 * here.
+			 */
 		}
-		else if (!strcmp(cep->ce_varname, "modes-on-oper")) {
+		else if (!strcmp(cep->name, "modes-on-oper")) {
 			char *p;
 			CheckNull(cep);
 			CheckDuplicate(cep, modes_on_oper, "modes-on-oper");
-			for (p = cep->ce_vardata; *p; p++)
+			for (p = cep->value; *p; p++)
 				if (strchr("orzS", *p))
 				{
 					config_error("%s:%i: set::modes-on-oper may not include mode '%c'",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p);
+						cep->file->filename, cep->line_number, *p);
 					errors++;
 				}
-			set_usermode(cep->ce_vardata);
+			set_usermode(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "snomask-on-oper")) {
+		else if (!strcmp(cep->name, "snomask-on-oper")) {
+			char *wrong_snomask;
 			CheckNull(cep);
 			CheckDuplicate(cep, snomask_on_oper, "snomask-on-oper");
+			if (!is_valid_snomask_string_testing(cep->value, &wrong_snomask))
+			{
+				config_error("%s:%i: set::snomask-on-oper contains unknown snomask letter(s) '%s'",
+					     cep->file->filename, cep->line_number, wrong_snomask);
+				errors++;
+				invalid_snomasks_encountered++;
+			}
+		}
+		else if (!strcmp(cep->name, "server-notice-colors")) {
+			CheckNull(cep);
 		}
-		else if (!strcmp(cep->ce_varname, "level-on-join")) {
+		else if (!strcmp(cep->name, "level-on-join")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, level_on_join, "level-on-join");
-			if (!channellevel_to_int(cep->ce_vardata))
+			if (!channellevel_to_string(cep->value) && (strlen(cep->value) != 1))
 			{
-				config_error("%s:%i: set::level-on-join: unknown value '%s', should be one of: none, voice, halfop, op, protect, owner",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+				config_error("%s:%i: set::level-on-join: unknown value '%s', should be one of: "
+				             "'none', 'voice', 'halfop', 'op', 'admin', 'owner', or a single letter (eg 'o')",
+				             cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "static-quit")) {
+		else if (!strcmp(cep->name, "static-quit")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, static_quit, "static-quit");
 		}
-		else if (!strcmp(cep->ce_varname, "static-part")) {
+		else if (!strcmp(cep->name, "static-part")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, static_part, "static-part");
 		}
-		else if (!strcmp(cep->ce_varname, "who-limit")) {
+		else if (!strcmp(cep->name, "who-limit")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, who_limit, "who-limit");
-			if (!config_checkval(cep->ce_vardata,CFG_SIZE))
+			if (!config_checkval(cep->value,CFG_SIZE))
 			{
 				config_error("%s:%i: set::who-limit: value must be at least 1",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "maxbans")) {
+		else if (!strcmp(cep->name, "maxbans")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, maxbans, "maxbans");
 		}
-		else if (!strcmp(cep->ce_varname, "maxbanlength")) {
+		else if (!strcmp(cep->name, "maxbanlength")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, maxbanlength, "maxbanlength");
 		}
-		else if (!strcmp(cep->ce_varname, "silence-limit")) {
+		else if (!strcmp(cep->name, "silence-limit")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, silence_limit, "silence-limit");
 		}
-		else if (!strcmp(cep->ce_varname, "auto-join")) {
+		else if (!strcmp(cep->name, "auto-join")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, auto_join, "auto-join");
 		}
-		else if (!strcmp(cep->ce_varname, "oper-auto-join")) {
+		else if (!strcmp(cep->name, "oper-auto-join")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, oper_auto_join, "oper-auto-join");
 		}
-		else if (!strcmp(cep->ce_varname, "check-target-nick-bans")) {
+		else if (!strcmp(cep->name, "check-target-nick-bans")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, check_target_nick_bans, "check-target-nick-bans");
 		}
-		else if (!strcmp(cep->ce_varname, "pingpong-warning")) {
+		else if (!strcmp(cep->name, "pingpong-warning")) {
 			config_error("%s:%i: set::pingpong-warning no longer exists (the warning is always off)",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
-			need_34_upgrade = 1;
+			             cep->file->filename, cep->line_number);
 			errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "ping-cookie")) {
+		else if (!strcmp(cep->name, "ping-cookie")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, ping_cookie, "ping-cookie");
 		}
-		else if (!strcmp(cep->ce_varname, "watch-away-notification")) {
+		else if (!strcmp(cep->name, "watch-away-notification")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, watch_away_notification, "watch-away-notification");
 		}
-		else if (!strcmp(cep->ce_varname, "uhnames")) {
+		else if (!strcmp(cep->name, "uhnames")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, uhnames, "uhnames");
 		}
-		else if (!strcmp(cep->ce_varname, "channel-command-prefix")) {
+		else if (!strcmp(cep->name, "channel-command-prefix")) {
 			CheckNullAllowEmpty(cep);
 			CheckDuplicate(cep, channel_command_prefix, "channel-command-prefix");
 		}
-		else if (!strcmp(cep->ce_varname, "allow-userhost-change")) {
+		else if (!strcmp(cep->name, "allow-userhost-change")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, allow_userhost_change, "allow-userhost-change");
-			if (strcasecmp(cep->ce_vardata, "always") &&
-			    strcasecmp(cep->ce_vardata, "never") &&
-			    strcasecmp(cep->ce_vardata, "not-on-channels") &&
-			    strcasecmp(cep->ce_vardata, "force-rejoin"))
+			if (strcasecmp(cep->value, "always") &&
+			    strcasecmp(cep->value, "never") &&
+			    strcasecmp(cep->value, "not-on-channels") &&
+			    strcasecmp(cep->value, "force-rejoin"))
 			{
 				config_error("%s:%i: set::allow-userhost-change is invalid",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum);
+					cep->file->filename,
+					cep->line_number);
 				errors++;
 				continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "anti-spam-quit-message-time")) {
+		else if (!strcmp(cep->name, "anti-spam-quit-message-time")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, anti_spam_quit_message_time, "anti-spam-quit-message-time");
 		}
-		else if (!strcmp(cep->ce_varname, "oper-only-stats"))
+		else if (!strcmp(cep->name, "oper-only-stats"))
 		{
 			config_warn("%s:%d: We no longer use a blacklist for stats (set::oper-only-stats) but "
 			             "have a whitelist now instead (set::allow-user-stats). ",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			             cep->file->filename, cep->line_number);
 			config_warn("Simply delete the oper-only-stats line from your configuration file %s around line %d to get rid of this warning",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			             cep->file->filename, cep->line_number);
 			continue;
 		}
-		else if (!strcmp(cep->ce_varname, "allow-user-stats"))
+		else if (!strcmp(cep->name, "allow-user-stats"))
 		{
 			CheckDuplicate(cep, allow_user_stats, "allow-user-stats");
-			if (!cep->ce_entries)
-			{
-				CheckNull(cep);
-			}
-			else
-			{
-				/* TODO: check the entries for existence?
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-				{
-				} */
-			}
+			CheckNull(cep);
 		}
-		else if (!strcmp(cep->ce_varname, "maxchannelsperuser")) {
+		else if (!strcmp(cep->name, "maxchannelsperuser")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, maxchannelsperuser, "maxchannelsperuser");
-			tempi = atoi(cep->ce_vardata);
+			tempi = atoi(cep->value);
 			if (tempi < 1)
 			{
 				config_error("%s:%i: set::maxchannelsperuser must be > 0",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum);
+					cep->file->filename,
+					cep->line_number);
 				errors++;
 				continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ping-warning")) {
+		else if (!strcmp(cep->name, "ping-warning")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, ping_warning, "ping-warning");
-			tempi = atoi(cep->ce_vardata);
+			tempi = atoi(cep->value);
 			/* it is pointless to allow setting higher than 170 */
 			if (tempi > 170)
 			{
 				config_error("%s:%i: set::ping-warning must be < 170",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum);
+					cep->file->filename,
+					cep->line_number);
 				errors++;
 				continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "maxdccallow")) {
+		else if (!strcmp(cep->name, "maxdccallow")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, maxdccallow, "maxdccallow");
 		}
-		else if (!strcmp(cep->ce_varname, "max-targets-per-command"))
+		else if (!strcmp(cep->name, "max-targets-per-command"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcasecmp(cepp->ce_varname, "NAMES") || !strcasecmp(cepp->ce_varname, "WHOWAS"))
+				if (!strcasecmp(cepp->name, "NAMES") || !strcasecmp(cepp->name, "WHOWAS"))
 				{
-					if (atoi(cepp->ce_vardata) != 1)
+					if (atoi(cepp->value) != 1)
 					{
 						config_error("%s:%i: set::max-targets-per-command::%s: "
 						             "this command is hardcoded at a maximum of 1 "
 						             "and cannot be configured to accept more.",
-						             cepp->ce_fileptr->cf_filename,
-						             cepp->ce_varlinenum,
-						             cepp->ce_varname);
+						             cepp->file->filename,
+						             cepp->line_number,
+						             cepp->name);
 						errors++;
 					}
 				} else
-				if (!strcasecmp(cepp->ce_varname, "USERHOST") ||
-				    !strcasecmp(cepp->ce_varname, "USERIP") ||
-				    !strcasecmp(cepp->ce_varname, "ISON") ||
-				    !strcasecmp(cepp->ce_varname, "WATCH"))
+				if (!strcasecmp(cepp->name, "USERHOST") ||
+				    !strcasecmp(cepp->name, "USERIP") ||
+				    !strcasecmp(cepp->name, "ISON") ||
+				    !strcasecmp(cepp->name, "WATCH"))
 				{
-					if (strcmp(cepp->ce_vardata, "max"))
+					if (strcmp(cepp->value, "max"))
 					{
 						config_error("%s:%i: set::max-targets-per-command::%s: "
 						             "this command is hardcoded at a maximum of 'max' "
 						             "and cannot be changed. This because it is "
 						             "highly discouraged to change it.",
-						             cepp->ce_fileptr->cf_filename,
-						             cepp->ce_varlinenum,
-						             cepp->ce_varname);
+						             cepp->file->filename,
+						             cepp->line_number,
+						             cepp->name);
 						errors++;
 					}
 				}
 				/* Now check the value syntax in general: */
-				if (strcmp(cepp->ce_vardata, "max")) /* anything other than 'max'.. */
+				if (strcmp(cepp->value, "max")) /* anything other than 'max'.. */
 				{
-					int v = atoi(cepp->ce_vardata);
+					int v = atoi(cepp->value);
 					if ((v < 1) || (v > 20))
 					{
 						config_error("%s:%i: set::max-targets-per-command::%s: "
 						             "value should be 1-20 or 'max'",
-						             cepp->ce_fileptr->cf_filename,
-						             cepp->ce_varlinenum,
-						             cepp->ce_varname);
+						             cepp->file->filename,
+						             cepp->line_number,
+						             cepp->name);
 						errors++;
 					}
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "network-name")) {
+		else if (!strcmp(cep->name, "network-name")) {
 			char *p;
 			CheckNull(cep);
 			CheckDuplicate(cep, network_name, "network-name");
-			for (p = cep->ce_vardata; *p; p++)
+			for (p = cep->value; *p; p++)
 				if ((*p < ' ') || (*p > '~'))
 				{
 					config_error("%s:%i: set::network-name can only contain ASCII characters 33-126. Invalid character = '%c'",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p);
+						cep->file->filename, cep->line_number, *p);
 					errors++;
 					break;
 				}
 		}
-		else if (!strcmp(cep->ce_varname, "default-server")) {
+		else if (!strcmp(cep->name, "default-server")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, default_server, "default-server");
 		}
-		else if (!strcmp(cep->ce_varname, "services-server")) {
+		else if (!strcmp(cep->name, "services-server")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, services_server, "services-server");
 		}
-		else if (!strcmp(cep->ce_varname, "sasl-server")) {
+		else if (!strcmp(cep->name, "sasl-server")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, sasl_server, "sasl-server");
 		}
-		else if (!strcmp(cep->ce_varname, "stats-server")) {
+		else if (!strcmp(cep->name, "stats-server")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, stats_server, "stats-server");
 		}
-		else if (!strcmp(cep->ce_varname, "help-channel")) {
+		else if (!strcmp(cep->name, "help-channel")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, help_channel, "help-channel");
 		}
-		else if (!strcmp(cep->ce_varname, "hiddenhost-prefix")) {
+		else if (!strcmp(cep->name, "cloak-prefix") || !strcmp(cep->name, "hiddenhost-prefix")) {
 			CheckNull(cep);
-			CheckDuplicate(cep, hiddenhost_prefix, "hiddenhost-prefix");
-			if (strchr(cep->ce_vardata, ' ') || (*cep->ce_vardata == ':'))
+			CheckDuplicate(cep, hiddenhost_prefix, "cloak-prefix");
+			if (strchr(cep->value, ' ') || (*cep->value == ':'))
 			{
-				config_error("%s:%i: set::hiddenhost-prefix must not contain spaces or be prefixed with ':'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				config_error("%s:%i: set::cloak-prefix must not contain spaces or be prefixed with ':'",
+					cep->file->filename, cep->line_number);
 				errors++;
 				continue;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "prefix-quit")) {
+		else if (!strcmp(cep->name, "prefix-quit")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, prefix_quit, "prefix-quit");
 		}
-		else if (!strcmp(cep->ce_varname, "hide-ban-reason")) {
+		else if (!strcmp(cep->name, "hide-ban-reason")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, hide_ban_reason, "hide-ban-reason");
 		}
-		else if (!strcmp(cep->ce_varname, "restrict-usermodes"))
+		else if (!strcmp(cep->name, "restrict-usermodes"))
 		{
 			CheckNull(cep);
 			CheckDuplicate(cep, restrict_usermodes, "restrict-usermodes");
-			if (cep->ce_varname) {
+			if (cep->name) {
 				int warn = 0;
 				char *p;
-				for (p = cep->ce_vardata; *p; p++)
+				for (p = cep->value; *p; p++)
 					if ((*p == '+') || (*p == '-'))
 						warn = 1;
 				if (warn) {
 					config_status("%s:%i: warning: set::restrict-usermodes: should only contain modechars, no + or -.\n",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "restrict-channelmodes"))
+		else if (!strcmp(cep->name, "restrict-channelmodes"))
 		{
 			CheckNull(cep);
 			CheckDuplicate(cep, restrict_channelmodes, "restrict-channelmodes");
-			if (cep->ce_varname) {
+			if (cep->name) {
 				int warn = 0;
 				char *p;
-				for (p = cep->ce_vardata; *p; p++)
+				for (p = cep->value; *p; p++)
 					if ((*p == '+') || (*p == '-'))
 						warn = 1;
 				if (warn) {
 					config_status("%s:%i: warning: set::restrict-channelmodes: should only contain modechars, no + or -.\n",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "restrict-extendedbans"))
+		else if (!strcmp(cep->name, "restrict-extendedbans"))
 		{
 			CheckDuplicate(cep, restrict_extendedbans, "restrict-extendedbans");
 			CheckNull(cep);
 		}
-		else if (!strcmp(cep->ce_varname, "link")) {
-					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
+		else if (!strcmp(cep->name, "named-extended-bans"))
+		{
+			CheckNull(cep);
+		}
+		else if (!strcmp(cep->name, "link")) {
+					for (cepp = cep->items; cepp; cepp = cepp->next) {
 						CheckNull(cepp);
-						if (!strcmp(cepp->ce_varname, "bind-ip")) {
+						if (!strcmp(cepp->name, "bind-ip")) {
 							CheckDuplicate(cepp, link_bind_ip, "link::bind-ip");
-							if (strcmp(cepp->ce_vardata, "*"))
+							if (strcmp(cepp->value, "*"))
 							{
-								if (!is_valid_ip(cepp->ce_vardata))
+								if (!is_valid_ip(cepp->value))
 								{
 									config_error("%s:%i: set::link::bind-ip (%s) is not a valid IP",
-										cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-										cepp->ce_vardata);
+										cepp->file->filename, cepp->line_number,
+										cepp->value);
 									errors++;
 									continue;
 								}
@@ -8438,66 +8148,33 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 						}
 					}
 		}
-		else if (!strcmp(cep->ce_varname, "dns")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
-				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "nameserver") ||
-				    !strcmp(cepp->ce_varname, "timeout") ||
-				    !strcmp(cepp->ce_varname, "retries"))
-				{
-					config_error("%s:%i: set::dns::%s no longer exist in UnrealIRCd 4. "
-					             "Please remove it from your configuration file.",
-						cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname);
-					errors++;
-				} else
-				if (!strcmp(cepp->ce_varname, "bind-ip")) {
-					CheckDuplicate(cepp, dns_bind_ip, "dns::bind-ip");
-					if (strcmp(cepp->ce_vardata, "*"))
-					{
-						if (!is_valid_ip(cepp->ce_vardata))
-						{
-							config_error("%s:%i: set::dns::bind-ip (%s) is not a valid IP",
-								cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-								cepp->ce_vardata);
-							errors++;
-							continue;
-						}
-					}
-				}
-				else
-				{
-					config_error_unknownopt(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::dns",
-						cepp->ce_varname);
-						errors++;
-				}
-			}
-		}
-		else if (!strcmp(cep->ce_varname, "throttle")) {
+		else if (!strcmp(cep->name, "throttle")) {
 			config_error("%s:%i: set::throttle has been renamed. you now use "
 			             "set::anti-flood::connect-flood <connections>:<period>. "
 			             "Or just remove the throttle block and you get the default "
 			             "of 3 per 60 seconds.",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			             cep->file->filename, cep->line_number);
 			errors++;
-			need_34_upgrade = 1;
 			continue;
 		}
-		else if (!strcmp(cep->ce_varname, "anti-flood"))
+		else if (!strcmp(cep->name, "anti-flood"))
 		{
 			int anti_flood_old = 0;
 			int anti_flood_old_and_default = 0;
 
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
+				int has_lag_penalty = 0;
+				int has_lag_penalty_bytes = 0;
+
 				/* Test for old options: */
-				if (flood_option_is_old(cepp->ce_varname))
+				if (flood_option_is_old(cepp->name))
 				{
 					/* Special code if the user is using 100% of the defaults */
-					if (cepp->ce_vardata &&
-					    ((!strcmp(cepp->ce_varname, "nick-flood") && !strcmp(cepp->ce_vardata, "3:60")) ||
-					     (!strcmp(cepp->ce_varname, "connect-flood") && cepp->ce_vardata && !strcmp(cepp->ce_vardata, "3:60")) ||
-					     (!strcmp(cepp->ce_varname, "away-flood") && cepp->ce_vardata && !strcmp(cepp->ce_vardata, "4:120"))))
+					if (cepp->value &&
+					    ((!strcmp(cepp->name, "nick-flood") && !strcmp(cepp->value, "3:60")) ||
+					     (!strcmp(cepp->name, "connect-flood") && cepp->value && !strcmp(cepp->value, "3:60")) ||
+					     (!strcmp(cepp->name, "away-flood") && cepp->value && !strcmp(cepp->value, "4:120"))))
 					{
 						anti_flood_old_and_default = 1;
 					} else
@@ -8507,219 +8184,237 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 					continue;
 				}
 
-				for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+				for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 				{
-					int everyone = !strcmp(cepp->ce_varname, "everyone") ? 1 : 0;
-					int for_everyone = flood_option_is_for_everyone(ceppp->ce_varname);
+					int everyone = !strcmp(cepp->name, "everyone") ? 1 : 0;
+					int 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. "
 						             "You can put it in 'known-users' or 'unknown-users' instead.",
-							ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum,
-							ceppp->ce_varname);
+							ceppp->file->filename, ceppp->line_number,
+							ceppp->name);
 						errors++;
 						continue;
 					} else
 					if (!everyone && for_everyone)
 					{
 						config_error("%s:%i: %s must be in the set::anti-flood::everyone block, not anywhere else.",
-							ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum,
-							ceppp->ce_varname);
+							ceppp->file->filename, ceppp->line_number,
+							ceppp->name);
 						errors++;
 						continue;
 					}
 
 					/* Now comes the actual config check for each element... */
-					if (!strcmp(ceppp->ce_varname, "max-concurrent-conversations"))
+					if (!strcmp(ceppp->name, "max-concurrent-conversations"))
 					{
-						for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
 						{
 							CheckNull(cep4);
-							if (!strcmp(cep4->ce_varname, "users"))
+							if (!strcmp(cep4->name, "users"))
 							{
-								int v = atoi(cep4->ce_vardata);
+								int v = atoi(cep4->value);
 								if ((v < 1) || (v > MAXCCUSERS))
 								{
 									config_error("%s:%i: set::anti-flood::max-concurrent-conversations::users: "
 										     "value should be between 1 and %d",
-										     cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum, MAXCCUSERS);
+										     cep4->file->filename, cep4->line_number, MAXCCUSERS);
 									errors++;
 								}
 							} else
-							if (!strcmp(cep4->ce_varname, "new-user-every"))
+							if (!strcmp(cep4->name, "new-user-every"))
 							{
-								long v = config_checkval(cep4->ce_vardata, CFG_TIME);
+								long v = config_checkval(cep4->value, CFG_TIME);
 								if ((v < 1) || (v > 120))
 								{
 									config_error("%s:%i: set::anti-flood::max-concurrent-conversations::new-user-every: "
 										     "value should be between 1 and 120 seconds",
-										     cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
+										     cep4->file->filename, cep4->line_number);
 									errors++;
 								}
 							} else
 							{
-								config_error_unknownopt(cep4->ce_fileptr->cf_filename,
-									cep4->ce_varlinenum, "set::anti-flood",
-									cep4->ce_varname);
+								config_error_unknownopt(cep4->file->filename,
+									cep4->line_number, "set::anti-flood",
+									cep4->name);
 								errors++;
 							}
 						}
 						continue; /* required here, due to checknull directly below */
 					}
-					else if (!strcmp(ceppp->ce_varname, "unknown-flood-amount") ||
-						 !strcmp(ceppp->ce_varname, "unknown-flood-bantime"))
+					else if (!strcmp(ceppp->name, "unknown-flood-amount") ||
+						 !strcmp(ceppp->name, "unknown-flood-bantime"))
 					{
 						config_error("%s:%i: set::anti-flood::%s: this setting has been moved. "
 							     "See https://www.unrealircd.org/docs/Anti-flood_settings#handshake-data-flood",
-							     ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, ceppp->ce_varname);
+							     ceppp->file->filename, ceppp->line_number, ceppp->name);
 						errors++;
 						continue;
 					}
-					else if (!strcmp(ceppp->ce_varname, "handshake-data-flood"))
+					else if (!strcmp(ceppp->name, "handshake-data-flood"))
 					{
-						for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
 						{
-							if (!strcmp(cep4->ce_varname, "amount"))
+							if (!strcmp(cep4->name, "amount"))
 							{
 								long v;
 								CheckNull(cep4);
-								v = config_checkval(cep4->ce_vardata, CFG_SIZE);
+								v = config_checkval(cep4->value, CFG_SIZE);
 								if (v < 1024)
 								{
 									config_error("%s:%i: set::anti-flood::handshake-data-flood::amount must be at least 1024 bytes",
-										cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
+										cep4->file->filename, cep4->line_number);
 									errors++;
 								}
 							} else
-							if (!strcmp(cep4->ce_varname, "ban-action"))
+							if (!strcmp(cep4->name, "ban-action"))
 							{
 								CheckNull(cep4);
-								if (!banact_stringtoval(cep4->ce_vardata))
+								if (!banact_stringtoval(cep4->value))
 								{
 									config_error("%s:%i: set::anti-flood::handshake-data-flood::ban-action has unknown action type '%s'",
-										cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum,
-										cep4->ce_vardata);
+										cep4->file->filename, cep4->line_number,
+										cep4->value);
 									errors++;
 								}
 							} else
-							if (!strcmp(cep4->ce_varname, "ban-time"))
+							if (!strcmp(cep4->name, "ban-time"))
 							{
 								CheckNull(cep4);
 							} else
 							{
-								config_error_unknownopt(cep4->ce_fileptr->cf_filename,
-									cep4->ce_varlinenum, "set::anti-flood::handshake-data-flood",
-									cep4->ce_varname);
+								config_error_unknownopt(cep4->file->filename,
+									cep4->line_number, "set::anti-flood::handshake-data-flood",
+									cep4->name);
 								errors++;
 							}
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "away-count"))
+					else if (!strcmp(ceppp->name, "away-count"))
 					{
-						int temp = atol(ceppp->ce_vardata);
+						int temp = atol(ceppp->value);
 						CheckNull(ceppp);
 						if (temp < 1 || temp > 255)
 						{
 							config_error("%s:%i: set::anti-flood::away-count must be between 1 and 255",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "away-period"))
+					else if (!strcmp(ceppp->name, "away-period"))
 					{
 						CheckNull(ceppp);
-						int temp = config_checkval(ceppp->ce_vardata, CFG_TIME);
+						int temp = config_checkval(ceppp->value, CFG_TIME);
 						if (temp < 10)
 						{
 							config_error("%s:%i: set::anti-flood::away-period must be greater than 9",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "away-flood"))
+					else if (!strcmp(ceppp->name, "away-flood"))
 					{
 						int cnt, period;
 						CheckNull(ceppp);
-						if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
 						    (cnt < 1) || (cnt > 255) || (period < 10))
 						{
 							config_error("%s:%i: set::anti-flood::away-flood error. Syntax is '<count>:<period>' (eg 5:60), "
 								     "count should be 1-255, period should be greater than 9",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "nick-flood"))
+					else if (!strcmp(ceppp->name, "nick-flood"))
 					{
 						int cnt, period;
 						CheckNull(ceppp);
-						if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
 						    (cnt < 1) || (cnt > 255) || (period < 5))
 						{
 							config_error("%s:%i: set::anti-flood::nick-flood error. Syntax is '<count>:<period>' (eg 5:60), "
 								     "count should be 1-255, period should be greater than 4",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "join-flood"))
+					else if (!strcmp(ceppp->name, "join-flood"))
 					{
 						int cnt, period;
 						CheckNull(ceppp);
 
-						if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
 						    (cnt < 1) || (cnt > 255) || (period < 5))
 						{
 							config_error("%s:%i: join-flood error. Syntax is '<count>:<period>' (eg 5:60), "
 								     "count should be 1-255, period should be greater than 4",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "invite-flood"))
+					else if (!strcmp(ceppp->name, "invite-flood"))
 					{
 						int cnt, period;
 						CheckNull(ceppp);
-						if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
 						    (cnt < 1) || (cnt > 255) || (period < 5))
 						{
 							config_error("%s:%i: set::anti-flood::invite-flood error. Syntax is '<count>:<period>' (eg 5:60), "
 								     "count should be 1-255, period should be greater than 4",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "knock-flood"))
+					else if (!strcmp(ceppp->name, "knock-flood"))
 					{
 						int cnt, period;
 						CheckNull(ceppp);
-						if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
 						    (cnt < 1) || (cnt > 255) || (period < 5))
 						{
 							config_error("%s:%i: set::anti-flood::knock-flood error. Syntax is '<count>:<period>' (eg 5:60), "
 								     "count should be 1-255, period should be greater than 4",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
+							errors++;
+						}
+					}
+					else if (!strcmp(ceppp->name, "lag-penalty"))
+					{
+						int v;
+						CheckNull(ceppp);
+						v = atoi(ceppp->value);
+						has_lag_penalty = 1;
+						if ((v < 0) || (v > 10000))
+						{
+							config_error("%s:%i: set::anti-flood::%s::lag-penalty: value is in milliseconds and should be between 0 and 10000",
+								ceppp->file->filename, ceppp->line_number, cepp->name);
 							errors++;
 						}
 					}
-					else if (!strcmp(ceppp->ce_varname, "connect-flood"))
+					else if (!strcmp(ceppp->name, "lag-penalty-bytes"))
+					{
+						has_lag_penalty_bytes = 1;
+						CheckNull(ceppp);
+					}
+					else if (!strcmp(ceppp->name, "connect-flood"))
 					{
 						int cnt, period;
 						CheckNull(ceppp);
-						if (strcmp(cepp->ce_varname, "everyone"))
+						if (strcmp(cepp->name, "everyone"))
 						{
 							config_error("%s:%i: connect-flood must be in the set::anti-flood::everyone block, not anywhere else.",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 							continue;
 						}
-						if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
 						    (cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600))
 						{
 							config_error("%s:%i: set::anti-flood::connect-flood: Syntax is '<count>:<period>' (eg 5:60), "
 								     "count should be 1-255, period should be 1-3600",
-								ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+								ceppp->file->filename, ceppp->line_number);
 							errors++;
 						}
 					}
@@ -8756,21 +8451,27 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 						}
 						if (!used)
 						{
-							config_error_unknownopt(ceppp->ce_fileptr->cf_filename,
-								ceppp->ce_varlinenum, "set::anti-flood",
-								ceppp->ce_varname);
+							config_error_unknownopt(ceppp->file->filename,
+								ceppp->line_number, "set::anti-flood",
+								ceppp->name);
 							errors++;
 						}
 						continue;
 					}
 				}
+				if (has_lag_penalty+has_lag_penalty_bytes == 1)
+				{
+					config_error("%s:%i: set::anti-flood::%s: if you use lag-penalty then you must also add an lag-penalty-bytes item (and vice-versa)",
+						cepp->file->filename, cepp->line_number, cepp->name);
+					errors++;
+				}
 			}
 			/* Now the warnings: */
 			if (anti_flood_old == 1)
 			{
 				config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible. "
 				            "Your custom anti-flood settings have NOT been read.",
-				            cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				            cep->file->filename, cep->line_number);
 				config_warn("See https://www.unrealircd.org/docs/Anti-flood_settings for the new block style,");
 				config_warn("OR: simply remove all the anti-flood options from the conf to get rid of this "
 				            "warning and use the built-in defaults.");
@@ -8778,74 +8479,73 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 			if (anti_flood_old_and_default == 1)
 			{
 				config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible.",
-					    cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					    cep->file->filename, cep->line_number);
 				config_warn("To fix this warning, delete the anti-flood block from your configuration file "
 				            "(file %s around line %d), this will make UnrealIRCd use the built-in defaults.",
-				            cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				            cep->file->filename, cep->line_number);
 				config_warn("If you want to learn more about the new functionality you can visit "
 				            "https://www.unrealircd.org/docs/Anti-flood_settings");
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "options")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
-				if (!strcmp(cepp->ce_varname, "hide-ulines"))
+		else if (!strcmp(cep->name, "options")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next) {
+				if (!strcmp(cepp->name, "hide-ulines"))
 				{
 					CheckDuplicate(cepp, options_hide_ulines, "options::hide-ulines");
 				}
-				else if (!strcmp(cepp->ce_varname, "flat-map")) {
+				else if (!strcmp(cepp->name, "flat-map")) {
 					CheckDuplicate(cepp, options_flat_map, "options::flat-map");
 				}
-				else if (!strcmp(cepp->ce_varname, "show-opermotd")) {
+				else if (!strcmp(cepp->name, "show-opermotd")) {
 					CheckDuplicate(cepp, options_show_opermotd, "options::show-opermotd");
 				}
-				else if (!strcmp(cepp->ce_varname, "identd-check")) {
+				else if (!strcmp(cepp->name, "identd-check")) {
 					CheckDuplicate(cepp, options_identd_check, "options::identd-check");
 				}
-				else if (!strcmp(cepp->ce_varname, "fail-oper-warn")) {
+				else if (!strcmp(cepp->name, "fail-oper-warn")) {
 					CheckDuplicate(cepp, options_fail_oper_warn, "options::fail-oper-warn");
 				}
-				else if (!strcmp(cepp->ce_varname, "show-connect-info")) {
+				else if (!strcmp(cepp->name, "show-connect-info")) {
 					CheckDuplicate(cepp, options_show_connect_info, "options::show-connect-info");
 				}
-				else if (!strcmp(cepp->ce_varname, "no-connect-tls-info")) {
+				else if (!strcmp(cepp->name, "no-connect-tls-info")) {
 					CheckDuplicate(cepp, options_no_connect_tls_info, "options::no-connect-tls-info");
 				}
-				else if (!strcmp(cepp->ce_varname, "dont-resolve")) {
+				else if (!strcmp(cepp->name, "dont-resolve")) {
 					CheckDuplicate(cepp, options_dont_resolve, "options::dont-resolve");
 				}
-				else if (!strcmp(cepp->ce_varname, "mkpasswd-for-everyone")) {
+				else if (!strcmp(cepp->name, "mkpasswd-for-everyone")) {
 					CheckDuplicate(cepp, options_mkpasswd_for_everyone, "options::mkpasswd-for-everyone");
 				}
-				else if (!strcmp(cepp->ce_varname, "allow-insane-bans")) {
+				else if (!strcmp(cepp->name, "allow-insane-bans")) {
 					CheckDuplicate(cepp, options_allow_insane_bans, "options::allow-insane-bans");
 				}
-				else if (!strcmp(cepp->ce_varname, "allow-part-if-shunned")) {
+				else if (!strcmp(cepp->name, "allow-part-if-shunned")) {
 					CheckDuplicate(cepp, options_allow_part_if_shunned, "options::allow-part-if-shunned");
 				}
-				else if (!strcmp(cepp->ce_varname, "disable-cap")) {
+				else if (!strcmp(cepp->name, "disable-cap")) {
 					CheckDuplicate(cepp, options_disable_cap, "options::disable-cap");
 				}
-				else if (!strcmp(cepp->ce_varname, "disable-ipv6")) {
+				else if (!strcmp(cepp->name, "disable-ipv6")) {
 					CheckDuplicate(cepp, options_disable_ipv6, "options::disable-ipv6");
 					DISABLE_IPV6 = 1; /* ugly ugly. needs to be done here because at conf runtime is too late. */
 				}
 				else
 				{
-					config_error_unknownopt(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::options",
-						cepp->ce_varname);
+					config_error_unknownopt(cepp->file->filename,
+						cepp->line_number, "set::options",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "hosts")) {
-			config_error("%s:%i: set::hosts has been removed in UnrealIRCd 4. You can use oper::vhost now.",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+		else if (!strcmp(cep->name, "hosts")) {
+			config_error("%s:%i: set::hosts has been removed. You can use oper::vhost now.",
+				cep->file->filename, cep->line_number);
 			errors++;
-			need_34_upgrade = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "cloak-keys"))
+		else if (!strcmp(cep->name, "cloak-keys"))
 		{
 			CheckDuplicate(cep, cloak_keys, "cloak-keys");
 			for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
@@ -8867,488 +8567,486 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 					errors += errs;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ident")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+		else if (!strcmp(cep->name, "ident")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				int is_ok = 0;
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "connect-timeout"))
+				if (!strcmp(cepp->name, "connect-timeout"))
 				{
 					is_ok = 1;
 					CheckDuplicate(cepp, ident_connect_timeout, "ident::connect-timeout");
 				}
-				else if (!strcmp(cepp->ce_varname, "read-timeout"))
+				else if (!strcmp(cepp->name, "read-timeout"))
 				{
 					is_ok = 1;
 					CheckDuplicate(cepp, ident_read_timeout, "ident::read-timeout");
 				}
 				if (is_ok)
 				{
-					int v = config_checkval(cepp->ce_vardata,CFG_TIME);
+					int v = config_checkval(cepp->value,CFG_TIME);
 					if ((v > 60) || (v < 1))
 					{
 						config_error("%s:%i: set::ident::%s value out of range (%d), should be between 1 and 60.",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname, v);
+							cepp->file->filename, cepp->line_number, cepp->name, v);
 						errors++;
 						continue;
 					}
 				} else {
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::ident",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::ident",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "timesync") || !strcmp(cep->ce_varname, "timesynch"))
+		else if (!strcmp(cep->name, "timesync") || !strcmp(cep->name, "timesynch"))
 		{
 			config_warn("%s:%i: Timesync support has been removed from UnrealIRCd. "
 			            "Please remove any set::timesync blocks you may have.",
-			            cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			            cep->file->filename, cep->line_number);
 			config_warn("Use the time synchronization feature of your OS/distro instead!");
 		}
-		else if (!strcmp(cep->ce_varname, "spamfilter")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+		else if (!strcmp(cep->name, "spamfilter")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "ban-time"))
+				if (!strcmp(cepp->name, "ban-time"))
 				{
 					long x;
 					CheckDuplicate(cepp, spamfilter_ban_time, "spamfilter::ban-time");
-					x = config_checkval(cepp->ce_vardata,CFG_TIME);
+					x = config_checkval(cepp->value,CFG_TIME);
 					if ((x < 0) > (x > 2000000000))
 					{
 						config_error("%s:%i: set::spamfilter:ban-time: value '%ld' out of range",
-							cep->ce_fileptr->cf_filename, cep->ce_varlinenum, x);
+							cep->file->filename, cep->line_number, x);
 						errors++;
 						continue;
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "ban-reason"))
+				if (!strcmp(cepp->name, "ban-reason"))
 				{
 					CheckDuplicate(cepp, spamfilter_ban_reason, "spamfilter::ban-reason");
 
 				}
-				else if (!strcmp(cepp->ce_varname, "virus-help-channel"))
+				else if (!strcmp(cepp->name, "virus-help-channel"))
 				{
 					CheckDuplicate(cepp, spamfilter_virus_help_channel, "spamfilter::virus-help-channel");
-					if ((cepp->ce_vardata[0] != '#') || (strlen(cepp->ce_vardata) > CHANNELLEN))
+					if ((cepp->value[0] != '#') || (strlen(cepp->value) > CHANNELLEN))
 					{
 						config_error("%s:%i: set::spamfilter:virus-help-channel: "
 						             "specified channelname is too long or contains invalid characters (%s)",
-						             cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-						             cepp->ce_vardata);
+						             cep->file->filename, cep->line_number,
+						             cepp->value);
 						errors++;
 						continue;
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "virus-help-channel-deny"))
+				if (!strcmp(cepp->name, "virus-help-channel-deny"))
 				{
 					CheckDuplicate(cepp, spamfilter_virus_help_channel_deny, "spamfilter::virus-help-channel-deny");
 				} else
-				if (!strcmp(cepp->ce_varname, "except"))
+				if (!strcmp(cepp->name, "except"))
 				{
 					CheckDuplicate(cepp, spamfilter_except, "spamfilter::except");
 				} else
 #ifdef SPAMFILTER_DETECTSLOW
-				if (!strcmp(cepp->ce_varname, "detect-slow-warn"))
+				if (!strcmp(cepp->name, "detect-slow-warn"))
 				{
 				} else
-				if (!strcmp(cepp->ce_varname, "detect-slow-fatal"))
+				if (!strcmp(cepp->name, "detect-slow-fatal"))
 				{
 				} else
 #endif
-				if (!strcmp(cepp->ce_varname, "stop-on-first-match"))
+				if (!strcmp(cepp->name, "stop-on-first-match"))
 				{
 				} else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::spamfilter",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::spamfilter",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-/* TODO: FIX THIS */
-		else if (!strcmp(cep->ce_varname, "default-bantime"))
+		else if (!strcmp(cep->name, "default-bantime"))
 		{
 			long x;
 			CheckDuplicate(cep, default_bantime, "default-bantime");
 			CheckNull(cep);
-			x = config_checkval(cep->ce_vardata,CFG_TIME);
+			x = config_checkval(cep->value,CFG_TIME);
 			if ((x < 0) > (x > 2000000000))
 			{
 				config_error("%s:%i: set::default-bantime: value '%ld' out of range",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, x);
+					cep->file->filename, cep->line_number, x);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ban-version-tkl-time")) {
+		else if (!strcmp(cep->name, "ban-version-tkl-time")) {
 			long x;
 			CheckDuplicate(cep, ban_version_tkl_time, "ban-version-tkl-time");
 			CheckNull(cep);
-			x = config_checkval(cep->ce_vardata,CFG_TIME);
+			x = config_checkval(cep->value,CFG_TIME);
 			if ((x < 0) > (x > 2000000000))
 			{
 				config_error("%s:%i: set::ban-version-tkl-time: value '%ld' out of range",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, x);
+					cep->file->filename, cep->line_number, x);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "min-nick-length")) {
+		else if (!strcmp(cep->name, "min-nick-length")) {
 			int v;
 			CheckDuplicate(cep, min_nick_length, "min-nick-length");
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v <= 0) || (v > NICKLEN))
 			{
 				config_error("%s:%i: set::min-nick-length: value '%d' out of range (should be 1-%d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, NICKLEN);
+					cep->file->filename, cep->line_number, v, NICKLEN);
 				errors++;
 			}
 			else
 				nicklengths.min = v;
 		}
-		else if (!strcmp(cep->ce_varname, "nick-length")) {
+		else if (!strcmp(cep->name, "nick-length")) {
 			int v;
 			CheckDuplicate(cep, nick_length, "nick-length");
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v <= 0) || (v > NICKLEN))
 			{
 				config_error("%s:%i: set::nick-length: value '%d' out of range (should be 1-%d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, NICKLEN);
+					cep->file->filename, cep->line_number, v, NICKLEN);
 				errors++;
 			}
 			else
 				nicklengths.max = v;
 		}
-		else if (!strcmp(cep->ce_varname, "topic-length")) {
+		else if (!strcmp(cep->name, "topic-length")) {
 			int v;
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v <= 0) || (v > MAXTOPICLEN))
 			{
 				config_error("%s:%i: set::topic-length: value '%d' out of range (should be 1-%d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXTOPICLEN);
+					cep->file->filename, cep->line_number, v, MAXTOPICLEN);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "away-length")) {
+		else if (!strcmp(cep->name, "away-length")) {
 			int v;
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v <= 0) || (v > MAXAWAYLEN))
 			{
 				config_error("%s:%i: set::away-length: value '%d' out of range (should be 1-%d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXAWAYLEN);
+					cep->file->filename, cep->line_number, v, MAXAWAYLEN);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "kick-length")) {
+		else if (!strcmp(cep->name, "kick-length")) {
 			int v;
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v <= 0) || (v > MAXKICKLEN))
 			{
 				config_error("%s:%i: set::kick-length: value '%d' out of range (should be 1-%d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXKICKLEN);
+					cep->file->filename, cep->line_number, v, MAXKICKLEN);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "quit-length")) {
+		else if (!strcmp(cep->name, "quit-length")) {
 			int v;
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v <= 0) || (v > MAXQUITLEN))
 			{
 				config_error("%s:%i: set::quit-length: value '%d' out of range (should be 1-%d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXQUITLEN);
+					cep->file->filename, cep->line_number, v, MAXQUITLEN);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ssl") || !strcmp(cep->ce_varname, "tls")) {
+		else if (!strcmp(cep->name, "ssl") || !strcmp(cep->name, "tls")) {
 			test_tlsblock(conf, cep, &errors);
 		}
-		else if (!strcmp(cep->ce_varname, "plaintext-policy"))
+		else if (!strcmp(cep->name, "plaintext-policy"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "user") ||
-					!strcmp(cepp->ce_varname, "oper") ||
-					!strcmp(cepp->ce_varname, "server"))
+				if (!strcmp(cepp->name, "user") ||
+					!strcmp(cepp->name, "oper") ||
+					!strcmp(cepp->name, "server"))
 				{
 					Policy policy;
 					CheckNull(cepp);
-					policy = policy_strtoval(cepp->ce_vardata);
+					policy = policy_strtoval(cepp->value);
 					if (!policy)
 					{
 						config_error("%s:%i: set::plaintext-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname);
+							cepp->file->filename, cepp->line_number, cepp->name);
 						errors++;
 					}
-				} else if (!strcmp(cepp->ce_varname, "user-message") ||
-				           !strcmp(cepp->ce_varname, "oper-message"))
+				} else if (!strcmp(cepp->name, "user-message") ||
+				           !strcmp(cepp->name, "oper-message"))
 				{
 					CheckNull(cepp);
 				} else {
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::plaintext-policy",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::plaintext-policy",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "outdated-tls-policy"))
+		else if (!strcmp(cep->name, "outdated-tls-policy"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "user") ||
-					!strcmp(cepp->ce_varname, "oper") ||
-					!strcmp(cepp->ce_varname, "server"))
+				if (!strcmp(cepp->name, "user") ||
+					!strcmp(cepp->name, "oper") ||
+					!strcmp(cepp->name, "server"))
 				{
 					Policy policy;
 					CheckNull(cepp);
-					policy = policy_strtoval(cepp->ce_vardata);
+					policy = policy_strtoval(cepp->value);
 					if (!policy)
 					{
 						config_error("%s:%i: set::outdated-tls-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname);
+							cepp->file->filename, cepp->line_number, cepp->name);
 						errors++;
 					}
-				} else if (!strcmp(cepp->ce_varname, "user-message") ||
-				           !strcmp(cepp->ce_varname, "oper-message"))
+				} else if (!strcmp(cepp->name, "user-message") ||
+				           !strcmp(cepp->name, "oper-message"))
 				{
 					CheckNull(cepp);
 				} else {
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::outdated-tls-policy",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::outdated-tls-policy",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "default-ipv6-clone-mask"))
+		else if (!strcmp(cep->name, "default-ipv6-clone-mask"))
 		{
 			/* keep this in sync with _test_allow() */
 			int ipv6mask;
-			ipv6mask = atoi(cep->ce_vardata);
+			ipv6mask = atoi(cep->value);
 			if (ipv6mask == 0)
 			{
 				config_error("%s:%d: set::default-ipv6-clone-mask given a value of zero. This cannnot be correct, as it would treat all IPv6 hosts as one host.",
-					     cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					     cep->file->filename, cep->line_number);
 				errors++;
 			}
 			if (ipv6mask > 128)
 			{
 				config_error("%s:%d: set::default-ipv6-clone-mask was set to %d. The maximum value is 128.",
-					     cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+					     cep->file->filename, cep->line_number,
 					     ipv6mask);
 				errors++;
 			}
 			if (ipv6mask <= 32)
 			{
 				config_warn("%s:%d: set::default-ipv6-clone-mask was given a very small value.",
-					    cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					    cep->file->filename, cep->line_number);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "hide-list")) {
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+		else if (!strcmp(cep->name, "hide-list")) {
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "deny-channel"))
+				if (!strcmp(cepp->name, "deny-channel"))
 				{
 				} else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::hide-list",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::hide-list",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "max-unknown-connections-per-ip")) {
+		else if (!strcmp(cep->name, "max-unknown-connections-per-ip")) {
 			int v;
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if (v < 1)
 			{
 				config_error("%s:%i: set::max-unknown-connections-per-ip: value should be at least 1.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "handshake-timeout")) {
+		else if (!strcmp(cep->name, "handshake-timeout")) {
 			int v;
 			CheckNull(cep);
-			v = config_checkval(cep->ce_vardata, CFG_TIME);
+			v = config_checkval(cep->value, CFG_TIME);
 			if (v < 5)
 			{
 				config_error("%s:%i: set::handshake-timeout: value should be at least 5 seconds.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "sasl-timeout")) {
+		else if (!strcmp(cep->name, "sasl-timeout")) {
 			int v;
 			CheckNull(cep);
-			v = config_checkval(cep->ce_vardata, CFG_TIME);
+			v = config_checkval(cep->value, CFG_TIME);
 			if (v < 5)
 			{
 				config_error("%s:%i: set::sasl-timeout: value should be at least 5 seconds.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "handshake-delay"))
+		else if (!strcmp(cep->name, "handshake-delay"))
 		{
 			int v;
 			CheckNull(cep);
-			v = config_checkval(cep->ce_vardata, CFG_TIME);
+			v = config_checkval(cep->value, CFG_TIME);
 			if (v >= 10)
 			{
 				config_error("%s:%i: set::handshake-delay: value should be less than 10 seconds.",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ban-include-username"))
+		else if (!strcmp(cep->name, "ban-include-username"))
 		{
 			config_error("%s:%i: set::ban-include-username is no longer supported. "
 			             "Use set { automatic-ban-target userip; }; instead.",
-			             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			             cep->file->filename, cep->line_number);
 			config_error("See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target "
 			             "for more information and options.");
 			errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "automatic-ban-target"))
+		else if (!strcmp(cep->name, "automatic-ban-target"))
 		{
 			CheckNull(cep);
-			if (!ban_target_strtoval(cep->ce_vardata))
+			if (!ban_target_strtoval(cep->value))
 			{
 				config_error("%s:%i: set::automatic-ban-target: value '%s' is not recognized. "
 				             "See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+				             cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "manual-ban-target"))
+		else if (!strcmp(cep->name, "manual-ban-target"))
 		{
 			CheckNull(cep);
-			if (!ban_target_strtoval(cep->ce_vardata))
+			if (!ban_target_strtoval(cep->value))
 			{
 				config_error("%s:%i: set::manual-ban-target: value '%s' is not recognized. "
 				             "See https://www.unrealircd.org/docs/Set_block#set::manual-ban-target",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+				             cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "reject-message"))
+		else if (!strcmp(cep->name, "reject-message"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "password-mismatch"))
+				if (!strcmp(cepp->name, "password-mismatch"))
 					;
-				else if (!strcmp(cepp->ce_varname, "too-many-connections"))
+				else if (!strcmp(cepp->name, "too-many-connections"))
 					;
-				else if (!strcmp(cepp->ce_varname, "server-full"))
+				else if (!strcmp(cepp->name, "server-full"))
 					;
-				else if (!strcmp(cepp->ce_varname, "unauthorized"))
+				else if (!strcmp(cepp->name, "unauthorized"))
 					;
-				else if (!strcmp(cepp->ce_varname, "kline"))
+				else if (!strcmp(cepp->name, "kline"))
 					;
-				else if (!strcmp(cepp->ce_varname, "gline"))
+				else if (!strcmp(cepp->name, "gline"))
 					;
 				else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::reject-message",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::reject-message",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "topic-setter"))
+		else if (!strcmp(cep->name, "topic-setter"))
 		{
 			CheckNull(cep);
-			if (strcmp(cep->ce_vardata, "nick") && strcmp(cep->ce_vardata, "nick-user-host"))
+			if (strcmp(cep->value, "nick") && strcmp(cep->value, "nick-user-host"))
 			{
 				config_error("%s:%i: set::topic-setter: value should be 'nick' or 'nick-user-host'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ban-setter"))
+		else if (!strcmp(cep->name, "ban-setter"))
 		{
 			CheckNull(cep);
-			if (strcmp(cep->ce_vardata, "nick") && strcmp(cep->ce_vardata, "nick-user-host"))
+			if (strcmp(cep->value, "nick") && strcmp(cep->value, "nick-user-host"))
 			{
 				config_error("%s:%i: set::ban-setter: value should be 'nick' or 'nick-user-host'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ban-setter-sync") || !strcmp(cep->ce_varname, "ban-setter-synch"))
+		else if (!strcmp(cep->name, "ban-setter-sync") || !strcmp(cep->name, "ban-setter-synch"))
 		{
 			CheckNull(cep);
 		}
-		else if (!strcmp(cep->ce_varname, "part-instead-of-quit-on-comment-change"))
+		else if (!strcmp(cep->name, "part-instead-of-quit-on-comment-change"))
 		{
 			CheckNull(cep);
 		}
-		else if (!strcmp(cep->ce_varname, "broadcast-channel-messages"))
+		else if (!strcmp(cep->name, "broadcast-channel-messages"))
 		{
 			CheckNull(cep);
-			if (strcmp(cep->ce_vardata, "auto") &&
-			    strcmp(cep->ce_vardata, "always") &&
-			    strcmp(cep->ce_vardata, "never"))
+			if (strcmp(cep->value, "auto") &&
+			    strcmp(cep->value, "always") &&
+			    strcmp(cep->value, "never"))
 			{
 				config_error("%s:%i: set::broadcast-channel-messages: value should be 'auto', 'always' or 'never'",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				             cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "allowed-channelchars"))
+		else if (!strcmp(cep->name, "allowed-channelchars"))
 		{
 			CheckNull(cep);
-			if (!allowed_channelchars_strtoval(cep->ce_vardata))
+			if (!allowed_channelchars_strtoval(cep->value))
 			{
 				config_error("%s:%i: set::allowed-channelchars: value should be one of: 'ascii', 'utf8' or 'any'",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				             cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "hide-idle-time"))
+		else if (!strcmp(cep->name, "hide-idle-time"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "policy"))
+				if (!strcmp(cepp->name, "policy"))
 				{
-					if (!hideidletime_strtoval(cepp->ce_vardata))
+					if (!hideidletime_strtoval(cepp->value))
 					{
 						config_error("%s:%i: set::hide-idle-time::policy: value should be one of: 'never', 'always', 'usermode' or 'oper-usermode'",
-							     cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+							     cepp->file->filename, cepp->line_number);
 						errors++;
 					}
 				}
 				else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "set::hide-idle-time",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "set::hide-idle-time",
+						cepp->name);
 					errors++;
 					continue;
 				}
 			}
-		}
-		else
+		} else
 		{
 			int used = 0;
 			for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
@@ -9379,8 +9077,8 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 			}
 			if (!used) {
 				config_error("%s:%i: unknown directive set::%s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_varname);
+					cep->file->filename, cep->line_number,
+					cep->name);
 				errors++;
 			}
 		}
@@ -9390,44 +9088,25 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 
 int	_conf_loadmodule(ConfigFile *conf, ConfigEntry *ce)
 {
-	char *ret;
-	if (!ce->ce_vardata)
+	const char *ret;
+	if (!ce->value)
 	{
 		config_status("%s:%i: loadmodule without filename",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		return -1;
-	}
-	if (strstr(ce->ce_vardata, "commands.so") || strstr(ce->ce_vardata, "commands.dll"))
-	{
-		config_error("%s:%i: You are trying to load the 'commands' module, this is no longer supported. "
-		             "Fix this by editing your configuration file: remove the loadmodule line for commands and add the following line instead: "
-		             "include \"modules.default.conf\";",
-		             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		need_34_upgrade = 1;
+			ce->file->filename, ce->line_number);
 		return -1;
 	}
-	if (strstr(ce->ce_vardata, "modules/cloak") && !strcmp(conf->cf_filename, "modules.conf"))
-	{
-		config_error("You seem to have an include for 'modules.conf'.");
-		config_error("If you have this because you are upgrading from 3.4-alpha3 to");
-		config_error("UnrealIRCd 4 then please change the include \"modules.conf\";");
-		config_error("into an include \"modules.default.conf\"; (probably in your");
-		config_error("conf/unrealircd.conf). Yeah, we changed the file name.");
-		// TODO ^: silly win32 wrapping prevents this from being displayed otherwise. PLZ FIX! !
-		/* let it continue to load anyway? */
-	}
 
-	if (is_blacklisted_module(ce->ce_vardata))
+	if (is_blacklisted_module(ce->value))
 	{
 		/* config_warn("%s:%i: Module '%s' is blacklisted, not loading",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); */
+			ce->file->filename, ce->line_number, ce->value); */
 		return 1;
 	}
 
-	if ((ret = Module_Create(ce->ce_vardata))) {
-		config_status("%s:%i: loadmodule %s: failed to load: %s",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_vardata, ret);
+	if ((ret = Module_Create(ce->value))) {
+		config_error("%s:%i: loadmodule %s: failed to load: %s",
+			ce->file->filename, ce->line_number,
+			ce->value, ret);
 		return -1;
 	}
 	return 1;
@@ -9440,17 +9119,17 @@ int	_test_loadmodule(ConfigFile *conf, ConfigEntry *ce)
 
 int	_test_blacklist_module(ConfigFile *conf, ConfigEntry *ce)
 {
-	char *path;
+	const char *path;
 	ConfigItem_blacklist_module *m;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_status("%s:%i: blacklist-module: no module name given to blacklist",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return -1;
 	}
 
-	path = Module_TransformPath(ce->ce_vardata);
+	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
@@ -9462,20 +9141,20 @@ int	_test_blacklist_module(ConfigFile *conf, ConfigEntry *ce)
 	if (!file_exists(path))
 	{
 		config_warn("%s:%i: blacklist-module for '%s' but module does not exist anyway",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+			ce->file->filename, ce->line_number, ce->value);
 		/* fallthrough */
 	}
 
 	m = safe_alloc(sizeof(ConfigItem_blacklist_module));
-	safe_strdup(m->name, ce->ce_vardata);
+	safe_strdup(m->name, ce->value);
 	AddListItem(m, conf_blacklist_module);
 
 	return 0;
 }
 
-int is_blacklisted_module(char *name)
+int is_blacklisted_module(const char *name)
 {
-	char *path = Module_TransformPath(name);
+	const char *path = Module_TransformPath(name);
 	ConfigItem_blacklist_module *m;
 
 	for (m = conf_blacklist_module; m; m = m->next)
@@ -9487,37 +9166,39 @@ int is_blacklisted_module(char *name)
 
 void start_listeners(void)
 {
-	ConfigItem_listen *listenptr;
+	ConfigItem_listen *listener;
 	int failed = 0, ports_bound = 0;
 	char boundmsg_ipv4[512], boundmsg_ipv6[512];
+	int last_errno = 0;
 
 	*boundmsg_ipv4 = *boundmsg_ipv6 = '\0';
 
-	for (listenptr = conf_listen; listenptr; listenptr = listenptr->next)
+	for (listener = conf_listen; listener; listener = listener->next)
 	{
 		/* Try to bind to any ports that are not yet bound and not marked as temporary */
-		if (!(listenptr->options & LISTENER_BOUND) && !listenptr->flag.temporary)
+		if (!(listener->options & LISTENER_BOUND) && !listener->flag.temporary)
 		{
-			if (add_listener(listenptr) == -1)
+			if (add_listener(listener) == -1)
 			{
-				ircd_log(LOG_ERROR, "Failed to bind to %s:%i", listenptr->ip, listenptr->port);
+				/* Error already printed upstream */
 				failed = 1;
+				last_errno = ERRNO;
 			} else {
-				if (loop.ircd_booted)
+				if (loop.booted)
 				{
-					ircd_log(LOG_ERROR, "UnrealIRCd is now also listening on %s:%d (%s)%s",
-						listenptr->ip, listenptr->port,
-						listenptr->ipv6 ? "IPv6" : "IPv4",
-						listenptr->options & LISTENER_TLS ? " (SSL/TLS)" : "");
+					unreal_log(ULOG_INFO, "listen", "LISTEN_ADDED", NULL,
+					           "UnrealIRCd is now also listening on $listen_ip:$listen_port",
+					           log_data_string("listen_ip", listener->ip),
+					           log_data_integer("listen_port", listener->port));
 				} else {
-					if (listenptr->ipv6)
+					if (listener->ipv6)
 						snprintf(boundmsg_ipv6+strlen(boundmsg_ipv6), sizeof(boundmsg_ipv6)-strlen(boundmsg_ipv6),
-							"%s:%d%s, ", listenptr->ip, listenptr->port,
-							listenptr->options & LISTENER_TLS ? "(SSL/TLS)" : "");
+							"%s:%d%s, ", listener->ip, listener->port,
+							listener->options & LISTENER_TLS ? "(TLS)" : "");
 					else
 						snprintf(boundmsg_ipv4+strlen(boundmsg_ipv4), sizeof(boundmsg_ipv4)-strlen(boundmsg_ipv4),
-							"%s:%d%s, ", listenptr->ip, listenptr->port,
-							listenptr->options & LISTENER_TLS ? "(SSL/TLS)" : "");
+							"%s:%d%s, ", listener->ip, listener->port,
+							listener->options & LISTENER_TLS ? "(TLS)" : "");
 				}
 			}
 		}
@@ -9525,58 +9206,83 @@ void start_listeners(void)
 		/* NOTE: do not merge this with code above (nor in an else block),
 		 * as add_listener() affects this flag.
 		 */
-		if (listenptr->options & LISTENER_BOUND)
+		if (listener->options & LISTENER_BOUND)
 			ports_bound++;
 	}
 
 	if (ports_bound == 0)
 	{
-		ircd_log(LOG_ERROR, "IRCd could not listen on any ports. If you see 'Address already in use' errors "
-		                    "above then most likely the IRCd is already running (or something else is using the "
-		                    "specified ports). If you are sure the IRCd is not running then verify your "
-		                    "listen blocks, maybe you have to bind to a specific IP rather than \"*\".");
+#ifdef _WIN32
+		if (last_errno == WSAEADDRINUSE)
+#else
+		if (last_errno == EADDRINUSE)
+#endif
+		{
+			/* We can be specific */
+			unreal_log(ULOG_FATAL, "listen", "ALL_LISTEN_PORTS_FAILED", NULL,
+				   "Unable to listen on any ports. "
+				   "Most likely UnrealIRCd is already running.");
+		} else {
+			unreal_log(ULOG_FATAL, "listen", "ALL_LISTEN_PORTS_FAILED", NULL,
+				   "Unable to listen on any ports. "
+				   "Please verify that no other process is using the ports. "
+				   "Also, on some IRCd shells you may have to use listen::bind-ip "
+				   "with a specific IP assigned to you (rather than \"*\").");
+		}
 		exit(-1);
 	}
 
-	if (failed && !loop.ircd_booted)
+	if (failed && !loop.booted)
 	{
-		ircd_log(LOG_ERROR, "Could not listen on all specified addresses/ports. See errors above. "
-		                    "Please fix your listen { } blocks and/or make sure no other programs "
-		                    "are listening on the same port.");
+		unreal_log(ULOG_FATAL, "listen", "SOME_LISTEN_PORTS_FAILED", NULL,
+			   "Unable to listen on all ports (some of them succeeded, some of them failed). "
+			   "Please verify that no other process is using the port(s). "
+			   "Also, on some IRCd shells you may have to use listen::bind-ip "
+			   "with a specific IP assigned to you (rather than \"*\").");
 		exit(-1);
 	}
 
-	if (!loop.ircd_booted)
+	if (!loop.booted)
 	{
 		if (strlen(boundmsg_ipv4) > 2)
 			boundmsg_ipv4[strlen(boundmsg_ipv4)-2] = '\0';
 		if (strlen(boundmsg_ipv6) > 2)
 			boundmsg_ipv6[strlen(boundmsg_ipv6)-2] = '\0';
 
-		ircd_log(LOG_ERROR, "UnrealIRCd is now listening on the following addresses/ports:");
-		ircd_log(LOG_ERROR, "IPv4: %s", *boundmsg_ipv4 ? boundmsg_ipv4 : "<none>");
-		ircd_log(LOG_ERROR, "IPv6: %s", *boundmsg_ipv6 ? boundmsg_ipv6 : "<none>");
+		if (!*boundmsg_ipv4)
+			strlcpy(boundmsg_ipv4, "<none>", sizeof(boundmsg_ipv4));
+		if (!*boundmsg_ipv6)
+			strlcpy(boundmsg_ipv6, "<none>", sizeof(boundmsg_ipv6));
+
+		unreal_log(ULOG_INFO, "listen", "LISTENING", NULL,
+		           "UnrealIRCd is now listening on the following addresses/ports:\n"
+		           "IPv4: $ipv4_port_list\n"
+		           "IPv6: $ipv6_port_list\n",
+		           log_data_string("ipv4_port_list", boundmsg_ipv4),
+		           log_data_string("ipv6_port_list", boundmsg_ipv6));
 	}
 }
 
 /* Actually use configuration */
-void run_configuration(void)
+void config_run(void)
 {
+	extcmodes_check_for_changes();
 	start_listeners();
+	free_all_config_resources();
 }
 
 int	_conf_offchans(ConfigFile *conf, ConfigEntry *ce)
 {
 	ConfigEntry *cep, *cepp;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		ConfigItem_offchans *of = safe_alloc(sizeof(ConfigItem_offchans));
-		strlcpy(of->chname, cep->ce_varname, CHANNELLEN+1);
-		for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+		strlcpy(of->name, cep->name, CHANNELLEN+1);
+		for (cepp = cep->items; cepp; cepp = cepp->next)
 		{
-			if (!strcmp(cepp->ce_varname, "topic"))
-				safe_strdup(of->topic, cepp->ce_vardata);
+			if (!strcmp(cepp->name, "topic"))
+				safe_strdup(of->topic, cepp->value);
 		}
 		AddListItem(of, conf_offchans);
 	}
@@ -9588,10 +9294,10 @@ int	_test_offchans(ConfigFile *conf, ConfigEntry *ce)
 	int errors = 0;
 	ConfigEntry *cep, *cep2;
 
-	if (!ce->ce_entries)
+	if (!ce->items)
 	{
 		config_error("%s:%i: empty official-channels block",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
 
@@ -9600,45 +9306,45 @@ int	_test_offchans(ConfigFile *conf, ConfigEntry *ce)
 	            "and then making the channel permanent (MODE #channel +P). "
 	            "The channel will then be stored in a database to preserve it between restarts.");
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (strlen(cep->ce_varname) > CHANNELLEN)
+		if (strlen(cep->name) > CHANNELLEN)
 		{
 			config_error("%s:%i: official-channels: '%s' name too long (max %d characters).",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname, CHANNELLEN);
+				cep->file->filename, cep->line_number, cep->name, CHANNELLEN);
 			errors++;
 			continue;
 		}
-		if (!valid_channelname(cep->ce_varname))
+		if (!valid_channelname(cep->name))
 		{
 			config_error("%s:%i: official-channels: '%s' is not a valid channel name.",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
-		for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
 		{
-			if (!cep2->ce_vardata)
+			if (!cep2->value)
 			{
-				config_error_empty(cep2->ce_fileptr->cf_filename,
-					cep2->ce_varlinenum, "official-channels",
-					cep2->ce_varname);
+				config_error_empty(cep2->file->filename,
+					cep2->line_number, "official-channels",
+					cep2->name);
 				errors++;
 				continue;
 			}
-			if (!strcmp(cep2->ce_varname, "topic"))
+			if (!strcmp(cep2->name, "topic"))
 			{
-				if (strlen(cep2->ce_vardata) > MAXTOPICLEN)
+				if (strlen(cep2->value) > MAXTOPICLEN)
 				{
 					config_error("%s:%i: official-channels::%s: topic too long (max %d characters).",
-						cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname, MAXTOPICLEN);
+						cep2->file->filename, cep2->line_number, cep->name, MAXTOPICLEN);
 					errors++;
 					continue;
 				}
 			} else {
-				config_error_unknown(cep2->ce_fileptr->cf_filename,
-					cep2->ce_varlinenum, "official-channels",
-					cep2->ce_varname);
+				config_error_unknown(cep2->file->filename,
+					cep2->line_number, "official-channels",
+					cep2->name);
 				errors++;
 				continue;
 			}
@@ -9654,70 +9360,70 @@ int	_conf_alias(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry 	    	*cep, *cepp;
 	RealCommand *cmptr;
 
-	if ((cmptr = find_command(ce->ce_vardata, CMD_ALIAS)))
+	if ((cmptr = find_command(ce->value, CMD_ALIAS)))
 		CommandDelX(NULL, cmptr);
-	if (find_command_simple(ce->ce_vardata))
+	if (find_command_simple(ce->value))
 	{
 		config_warn("%s:%i: Alias '%s' would conflict with command (or server token) '%s', alias not added.",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			ce->ce_vardata, ce->ce_vardata);
+			ce->file->filename, ce->line_number,
+			ce->value, ce->value);
 		return 0;
 	}
-	if ((alias = find_alias(ce->ce_vardata)))
+	if ((alias = find_alias(ce->value)))
 		DelListItem(alias, conf_alias);
 	alias = safe_alloc(sizeof(ConfigItem_alias));
-	safe_strdup(alias->alias, ce->ce_vardata);
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	safe_strdup(alias->alias, ce->value);
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "format")) {
+		if (!strcmp(cep->name, "format")) {
 			format = safe_alloc(sizeof(ConfigItem_alias_format));
-			safe_strdup(format->format, cep->ce_vardata);
-			format->expr = unreal_create_match(MATCH_PCRE_REGEX, cep->ce_vardata, NULL);
+			safe_strdup(format->format, cep->value);
+			format->expr = unreal_create_match(MATCH_PCRE_REGEX, cep->value, NULL);
 			if (!format->expr)
 				abort(); /* Impossible due to _test_alias earlier */
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
-				if (!strcmp(cepp->ce_varname, "nick") ||
-				    !strcmp(cepp->ce_varname, "target") ||
-				    !strcmp(cepp->ce_varname, "command")) {
-					safe_strdup(format->nick, cepp->ce_vardata);
+			for (cepp = cep->items; cepp; cepp = cepp->next) {
+				if (!strcmp(cepp->name, "nick") ||
+				    !strcmp(cepp->name, "target") ||
+				    !strcmp(cepp->name, "command")) {
+					safe_strdup(format->nick, cepp->value);
 				}
-				else if (!strcmp(cepp->ce_varname, "parameters")) {
-					safe_strdup(format->parameters, cepp->ce_vardata);
+				else if (!strcmp(cepp->name, "parameters")) {
+					safe_strdup(format->parameters, cepp->value);
 				}
-				else if (!strcmp(cepp->ce_varname, "type")) {
-					if (!strcmp(cepp->ce_vardata, "services"))
+				else if (!strcmp(cepp->name, "type")) {
+					if (!strcmp(cepp->value, "services"))
 						format->type = ALIAS_SERVICES;
-					else if (!strcmp(cepp->ce_vardata, "stats"))
+					else if (!strcmp(cepp->value, "stats"))
 						format->type = ALIAS_STATS;
-					else if (!strcmp(cepp->ce_vardata, "normal"))
+					else if (!strcmp(cepp->value, "normal"))
 						format->type = ALIAS_NORMAL;
-					else if (!strcmp(cepp->ce_vardata, "channel"))
+					else if (!strcmp(cepp->value, "channel"))
 						format->type = ALIAS_CHANNEL;
-					else if (!strcmp(cepp->ce_vardata, "real"))
+					else if (!strcmp(cepp->value, "real"))
 						format->type = ALIAS_REAL;
 				}
 			}
 			AddListItem(format, alias->format);
 		}
 
-		else if (!strcmp(cep->ce_varname, "nick") || !strcmp(cep->ce_varname, "target"))
+		else if (!strcmp(cep->name, "nick") || !strcmp(cep->name, "target"))
 		{
-			safe_strdup(alias->nick, cep->ce_vardata);
+			safe_strdup(alias->nick, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "type")) {
-			if (!strcmp(cep->ce_vardata, "services"))
+		else if (!strcmp(cep->name, "type")) {
+			if (!strcmp(cep->value, "services"))
 				alias->type = ALIAS_SERVICES;
-			else if (!strcmp(cep->ce_vardata, "stats"))
+			else if (!strcmp(cep->value, "stats"))
 				alias->type = ALIAS_STATS;
-			else if (!strcmp(cep->ce_vardata, "normal"))
+			else if (!strcmp(cep->value, "normal"))
 				alias->type = ALIAS_NORMAL;
-			else if (!strcmp(cep->ce_vardata, "channel"))
+			else if (!strcmp(cep->value, "channel"))
 				alias->type = ALIAS_CHANNEL;
-			else if (!strcmp(cep->ce_vardata, "command"))
+			else if (!strcmp(cep->value, "command"))
 				alias->type = ALIAS_COMMAND;
 		}
-		else if (!strcmp(cep->ce_varname, "spamfilter"))
-			alias->spamfilter = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->name, "spamfilter"))
+			alias->spamfilter = config_checkval(cep->value, CFG_YESNO);
 	}
 	if (BadPtr(alias->nick) && alias->type != ALIAS_COMMAND) {
 		safe_strdup(alias->nick, alias->alias);
@@ -9735,99 +9441,97 @@ int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
 	char has_type = 0, has_target = 0, has_format = 0;
 	char type = 0;
 
-	if (!ce->ce_entries)
+	if (!ce->items)
 	{
 		config_error("%s:%i: empty alias block",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: alias without name",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
-	else if (!find_command(ce->ce_vardata, CMD_ALIAS) && find_command(ce->ce_vardata, 0)) {
+	else if (!find_command(ce->value, CMD_ALIAS) && find_command(ce->value, 0)) {
 		config_status("%s:%i: %s is an existing command, can not add alias",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+			ce->file->filename, ce->line_number, ce->value);
 		errors++;
 	}
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "alias"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "format")) {
+		if (!strcmp(cep->name, "format")) {
 			char *err = NULL;
 			Match *expr;
 			char has_type = 0, has_target = 0, has_parameters = 0;
 
 			has_format = 1;
-			expr = unreal_create_match(MATCH_PCRE_REGEX, cep->ce_vardata, &err);
+			expr = unreal_create_match(MATCH_PCRE_REGEX, cep->value, &err);
 			if (!expr)
 			{
 				config_error("%s:%i: alias::format contains an invalid regex: %s",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
-				config_error("Upgrading from 3.2.x to UnrealIRCd 4? Note that regex changed from POSIX Regex "
-				             "to PCRE Regex!"); /* TODO: refer to some url ? */
+					cep->file->filename, cep->line_number, err);
 			} else {
 				unreal_delete_match(expr);
 			}
 
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
+			for (cepp = cep->items; cepp; cepp = cepp->next) {
 				if (config_is_blankorempty(cepp, "alias::format"))
 				{
 					errors++;
 					continue;
 				}
-				if (!strcmp(cepp->ce_varname, "nick") ||
-				    !strcmp(cepp->ce_varname, "command") ||
-				    !strcmp(cepp->ce_varname, "target"))
+				if (!strcmp(cepp->name, "nick") ||
+				    !strcmp(cepp->name, "command") ||
+				    !strcmp(cepp->name, "target"))
 				{
 					if (has_target)
 					{
-						config_warn_duplicate(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum,
+						config_warn_duplicate(cepp->file->filename,
+							cepp->line_number,
 							"alias::format::target");
 						continue;
 					}
 					has_target = 1;
 				}
-				else if (!strcmp(cepp->ce_varname, "type"))
+				else if (!strcmp(cepp->name, "type"))
 				{
 					if (has_type)
 					{
-						config_warn_duplicate(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum,
+						config_warn_duplicate(cepp->file->filename,
+							cepp->line_number,
 							"alias::format::type");
 						continue;
 					}
 					has_type = 1;
-					if (!strcmp(cepp->ce_vardata, "services"))
+					if (!strcmp(cepp->value, "services"))
 						;
-					else if (!strcmp(cepp->ce_vardata, "stats"))
+					else if (!strcmp(cepp->value, "stats"))
 						;
-					else if (!strcmp(cepp->ce_vardata, "normal"))
+					else if (!strcmp(cepp->value, "normal"))
 						;
-					else if (!strcmp(cepp->ce_vardata, "channel"))
+					else if (!strcmp(cepp->value, "channel"))
 						;
-					else if (!strcmp(cepp->ce_vardata, "real"))
+					else if (!strcmp(cepp->value, "real"))
 						;
 					else
 					{
 						config_error("%s:%i: unknown alias type",
-						cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+						cepp->file->filename, cepp->line_number);
 						errors++;
 					}
 				}
-				else if (!strcmp(cepp->ce_varname, "parameters"))
+				else if (!strcmp(cepp->name, "parameters"))
 				{
 					if (has_parameters)
 					{
-						config_warn_duplicate(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum,
+						config_warn_duplicate(cepp->file->filename,
+							cepp->line_number,
 							"alias::format::parameters");
 						continue;
 					}
@@ -9835,89 +9539,89 @@ int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
 				}
 				else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename,
-						cepp->ce_varlinenum, "alias::format",
-						cepp->ce_varname);
+					config_error_unknown(cepp->file->filename,
+						cepp->line_number, "alias::format",
+						cepp->name);
 					errors++;
 				}
 			}
 			if (!has_target)
 			{
-				config_error_missing(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "alias::format::target");
+				config_error_missing(cep->file->filename,
+					cep->line_number, "alias::format::target");
 				errors++;
 			}
 			if (!has_type)
 			{
-				config_error_missing(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "alias::format::type");
+				config_error_missing(cep->file->filename,
+					cep->line_number, "alias::format::type");
 				errors++;
 			}
 			if (!has_parameters)
 			{
-				config_error_missing(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "alias::format::parameters");
+				config_error_missing(cep->file->filename,
+					cep->line_number, "alias::format::parameters");
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "nick") || !strcmp(cep->ce_varname, "target"))
+		else if (!strcmp(cep->name, "nick") || !strcmp(cep->name, "target"))
 		{
 			if (has_target)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "alias::target");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "alias::target");
 				continue;
 			}
 			has_target = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "type")) {
+		else if (!strcmp(cep->name, "type")) {
 			if (has_type)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "alias::type");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "alias::type");
 				continue;
 			}
 			has_type = 1;
-			if (!strcmp(cep->ce_vardata, "services"))
+			if (!strcmp(cep->value, "services"))
 				;
-			else if (!strcmp(cep->ce_vardata, "stats"))
+			else if (!strcmp(cep->value, "stats"))
 				;
-			else if (!strcmp(cep->ce_vardata, "normal"))
+			else if (!strcmp(cep->value, "normal"))
 				;
-			else if (!strcmp(cep->ce_vardata, "channel"))
+			else if (!strcmp(cep->value, "channel"))
 				;
-			else if (!strcmp(cep->ce_vardata, "command"))
+			else if (!strcmp(cep->value, "command"))
 				type = 'c';
 			else {
 				config_error("%s:%i: unknown alias type",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "spamfilter"))
+		else if (!strcmp(cep->name, "spamfilter"))
 			;
 		else {
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"alias", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"alias", cep->name);
 			errors++;
 		}
 	}
 	if (!has_type)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"alias::type");
 		errors++;
 	}
 	if (!has_format && type == 'c')
 	{
 		config_error("%s:%d: alias::type is 'command' but no alias::format was specified",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 	else if (has_format && type != 'c')
 	{
 		config_error("%s:%d: alias::format specified when type is not 'command'",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 	return errors;
@@ -9927,11 +9631,11 @@ int	_conf_deny(ConfigFile *conf, ConfigEntry *ce)
 {
 Hook *h;
 
-	if (!strcmp(ce->ce_vardata, "channel"))
+	if (!strcmp(ce->value, "channel"))
 		_conf_deny_channel(conf, ce);
-	else if (!strcmp(ce->ce_vardata, "link"))
+	else if (!strcmp(ce->value, "link"))
 		_conf_deny_link(conf, ce);
-	else if (!strcmp(ce->ce_vardata, "version"))
+	else if (!strcmp(ce->value, "version"))
 		_conf_deny_version(conf, ce);
 	else
 	{
@@ -9953,29 +9657,29 @@ int	_conf_deny_channel(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry 	    	*cep;
 
 	deny = safe_alloc(sizeof(ConfigItem_deny_channel));
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "channel"))
+		if (!strcmp(cep->name, "channel"))
 		{
-			safe_strdup(deny->channel, cep->ce_vardata);
+			safe_strdup(deny->channel, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "redirect"))
+		else if (!strcmp(cep->name, "redirect"))
 		{
-			safe_strdup(deny->redirect, cep->ce_vardata);
+			safe_strdup(deny->redirect, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
-			safe_strdup(deny->reason, cep->ce_vardata);
+			safe_strdup(deny->reason, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "warn"))
+		else if (!strcmp(cep->name, "warn"))
 		{
-			deny->warn = config_checkval(cep->ce_vardata,CFG_YESNO);
+			deny->warn = config_checkval(cep->value,CFG_YESNO);
 		}
-		else if (!strcmp(cep->ce_varname, "class"))
+		else if (!strcmp(cep->name, "class"))
 		{
-			safe_strdup(deny->class, cep->ce_vardata);
+			safe_strdup(deny->class, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "mask"))
+		else if (!strcmp(cep->name, "mask"))
 		{
 			unreal_add_masks(&deny->mask, cep);
 		}
@@ -9989,21 +9693,21 @@ int	_conf_deny_link(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry 	    	*cep;
 
 	deny = safe_alloc(sizeof(ConfigItem_deny_link));
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			safe_strdup(deny->mask, cep->ce_vardata);
+			unreal_add_masks(&deny->mask, cep);
 		}
-		else if (!strcmp(cep->ce_varname, "rule"))
+		else if (!strcmp(cep->name, "rule"))
 		{
-			deny->rule = (char *)crule_parse(cep->ce_vardata);
-			safe_strdup(deny->prettyrule, cep->ce_vardata);
+			deny->rule = (char *)crule_parse(cep->value);
+			safe_strdup(deny->prettyrule, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "type")) {
-			if (!strcmp(cep->ce_vardata, "all"))
+		else if (!strcmp(cep->name, "type")) {
+			if (!strcmp(cep->value, "all"))
 				deny->flag.type = CRULE_ALL;
-			else if (!strcmp(cep->ce_vardata, "auto"))
+			else if (!strcmp(cep->value, "auto"))
 				deny->flag.type = CRULE_AUTO;
 		}
 	}
@@ -10017,19 +9721,19 @@ int	_conf_deny_version(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry 	    	*cep;
 
 	deny = safe_alloc(sizeof(ConfigItem_deny_version));
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			safe_strdup(deny->mask, cep->ce_vardata);
+			safe_strdup(deny->mask, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "version"))
+		else if (!strcmp(cep->name, "version"))
 		{
-			safe_strdup(deny->version, cep->ce_vardata);
+			safe_strdup(deny->version, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "flags"))
+		else if (!strcmp(cep->name, "flags"))
 		{
-			safe_strdup(deny->flags, cep->ce_vardata);
+			safe_strdup(deny->flags, cep->value);
 		}
 	}
 	AddListItem(deny, conf_deny_version);
@@ -10042,241 +9746,253 @@ int     _test_deny(ConfigFile *conf, ConfigEntry *ce)
 	int	    errors = 0;
 	Hook	*h;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: deny without type",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	if (!strcmp(ce->ce_vardata, "channel"))
+	if (!strcmp(ce->value, "channel"))
 	{
 		char has_channel = 0, has_warn = 0, has_reason = 0, has_redirect = 0, has_class = 0;
-		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+		for (cep = ce->items; cep; cep = cep->next)
 		{
 			if (config_is_blankorempty(cep, "deny channel"))
 			{
 				errors++;
 				continue;
 			}
-			if (!strcmp(cep->ce_varname, "channel"))
+			if (!strcmp(cep->name, "channel"))
 			{
 				if (has_channel)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny channel::channel");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny channel::channel");
 					continue;
 				}
 				has_channel = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "redirect"))
+			else if (!strcmp(cep->name, "redirect"))
 			{
 				if (has_redirect)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny channel::redirect");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny channel::redirect");
 					continue;
 				}
 				has_redirect = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "reason"))
+			else if (!strcmp(cep->name, "reason"))
 			{
 				if (has_reason)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny channel::reason");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny channel::reason");
 					continue;
 				}
 				has_reason = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "warn"))
+			else if (!strcmp(cep->name, "warn"))
 			{
 				if (has_warn)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny channel::warn");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny channel::warn");
 					continue;
 				}
 				has_warn = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "class"))
+			else if (!strcmp(cep->name, "class"))
 			{
 				if (has_class)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny channel::class");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny channel::class");
 					continue;
 				}
 				has_class = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "mask"))
+			else if (!strcmp(cep->name, "mask"))
 			{
 			}
 			else
 			{
-				config_error_unknown(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "deny channel", cep->ce_varname);
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "deny channel", cep->name);
 				errors++;
 			}
 		}
 		if (!has_channel)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny channel::channel");
 			errors++;
 		}
 		if (!has_reason)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny channel::reason");
 			errors++;
 		}
 	}
-	else if (!strcmp(ce->ce_vardata, "link"))
+	else if (!strcmp(ce->value, "link"))
 	{
 		char has_mask = 0, has_rule = 0, has_type = 0;
-		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+		for (cep = ce->items; cep; cep = cep->next)
 		{
-			if (config_is_blankorempty(cep, "deny link"))
-			{
-				errors++;
-				continue;
-			}
-			if (!strcmp(cep->ce_varname, "mask"))
+			if (!cep->items)
 			{
-				if (has_mask)
+				if (config_is_blankorempty(cep, "deny link"))
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny link::mask");
+					errors++;
 					continue;
 				}
-				has_mask = 1;
-			}
-			else if (!strcmp(cep->ce_varname, "rule"))
-			{
-				int val = 0;
-				if (has_rule)
+				else if (!strcmp(cep->name, "mask"))
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny link::rule");
-					continue;
+					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++;
+					}
 				}
-				has_rule = 1;
-				if ((val = crule_test(cep->ce_vardata)))
+				else
 				{
-					config_error("%s:%i: deny link::rule contains an invalid expression: %s",
-						cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum,
-						crule_errstring(val));
+					config_error_unknown(cep->file->filename,
+						cep->line_number, "deny link", cep->name);
 					errors++;
 				}
 			}
-			else if (!strcmp(cep->ce_varname, "type"))
+			else
 			{
-				if (has_type)
+				// Sections
+				if (!strcmp(cep->name, "mask"))
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny link::type");
-					continue;
+					if (cep->value || cep->items)
+						has_mask = 1;
 				}
-				has_type = 1;
-				if (!strcmp(cep->ce_vardata, "auto"))
-				;
-				else if (!strcmp(cep->ce_vardata, "all"))
-				;
-				else {
-					config_status("%s:%i: unknown deny link type",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				else
+				{
+					config_error_unknown(cep->file->filename,
+						cep->line_number, "deny link", cep->name);
 					errors++;
+					continue;
 				}
 			}
-			else
-			{
-				config_error_unknown(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "deny link", cep->ce_varname);
-				errors++;
-			}
 		}
 		if (!has_mask)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny link::mask");
 			errors++;
 		}
 		if (!has_rule)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny link::rule");
 			errors++;
 		}
 		if (!has_type)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny link::type");
 			errors++;
 		}
 	}
-	else if (!strcmp(ce->ce_vardata, "version"))
+	else if (!strcmp(ce->value, "version"))
 	{
 		char has_mask = 0, has_version = 0, has_flags = 0;
-		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+		for (cep = ce->items; cep; cep = cep->next)
 		{
 			if (config_is_blankorempty(cep, "deny version"))
 			{
 				errors++;
 				continue;
 			}
-			if (!strcmp(cep->ce_varname, "mask"))
+			if (!strcmp(cep->name, "mask"))
 			{
 				if (has_mask)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny version::mask");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny version::mask");
 					continue;
 				}
 				has_mask = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "version"))
+			else if (!strcmp(cep->name, "version"))
 			{
 				if (has_version)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny version::version");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny version::version");
 					continue;
 				}
 				has_version = 1;
 			}
-			else if (!strcmp(cep->ce_varname, "flags"))
+			else if (!strcmp(cep->name, "flags"))
 			{
 				if (has_flags)
 				{
-					config_warn_duplicate(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "deny version::flags");
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny version::flags");
 					continue;
 				}
 				has_flags = 1;
 			}
 			else
 			{
-				config_error_unknown(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "deny version", cep->ce_varname);
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "deny version", cep->name);
 				errors++;
 			}
 		}
 		if (!has_mask)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny version::mask");
 			errors++;
 		}
 		if (!has_version)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny version::version");
 			errors++;
 		}
 		if (!has_flags)
 		{
-			config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+			config_error_missing(ce->file->filename, ce->line_number,
 				"deny version::flags");
 			errors++;
 		}
@@ -10312,8 +10028,8 @@ int     _test_deny(ConfigFile *conf, ConfigEntry *ce)
 		}
 		if (!used) {
 			config_error("%s:%i: unknown deny type %s",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				ce->ce_vardata);
+				ce->file->filename, ce->line_number,
+				ce->value);
 			return 1;
 		}
 		return errors;
@@ -10327,59 +10043,62 @@ int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
 	int errors = 0;
 	ConfigEntry *cep;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: security-group block needs a name, eg: security-group web-users {",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	} else {
-		if (!strcasecmp(ce->ce_vardata, "unknown-users"))
+		if (!strcasecmp(ce->value, "unknown-users"))
 		{
 			config_error("%s:%i: The 'unknown-users' group is a special group that is the "
 			             "inverse of 'known-users', you cannot create or adjust it in the "
 			             "config file, as it is created automatically by UnrealIRCd.",
-			             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			             ce->file->filename, ce->line_number);
 			errors++;
 			return errors;
 		}
-		if (!security_group_valid_name(ce->ce_vardata))
+		if (!security_group_valid_name(ce->value))
 		{
 			config_error("%s:%i: security-group block name '%s' contains invalid characters or is too long. "
 			             "Only letters, numbers, underscore and hyphen are allowed.",
-			             ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+			             ce->file->filename, ce->line_number, ce->value);
 			errors++;
 		}
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "webirc"))
+		if (!strcmp(cep->name, "webirc"))
 		{
 			CheckNull(cep);
 		} else
-		if (!strcmp(cep->ce_varname, "identified"))
+		if (!strcmp(cep->name, "identified"))
 		{
 			CheckNull(cep);
 		} else
-		if (!strcmp(cep->ce_varname, "tls"))
+		if (!strcmp(cep->name, "tls"))
 		{
 			CheckNull(cep);
 		} else
-		if (!strcmp(cep->ce_varname, "reputation-score"))
+		if (!strcmp(cep->name, "reputation-score"))
 		{
 			int v;
 			CheckNull(cep);
-			v = atoi(cep->ce_vardata);
+			v = atoi(cep->value);
 			if ((v < 1) || (v > 10000))
 			{
 				config_error("%s:%i: security-group::reputation-score needs to be a value of 1-10000",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 		} else
+		if (!strcmp(cep->name, "include-mask"))
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"security-group", cep->ce_varname);
+		} else
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"security-group", cep->name);
 			errors++;
 			continue;
 		}
@@ -10391,29 +10110,33 @@ int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
 int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
 {
 	ConfigEntry *cep;
-	SecurityGroup *s = add_security_group(ce->ce_vardata, 1);
+	SecurityGroup *s = add_security_group(ce->value, 1);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "webirc"))
-			s->webirc = config_checkval(cep->ce_vardata, CFG_YESNO);
-		else if (!strcmp(cep->ce_varname, "identified"))
-			s->identified = config_checkval(cep->ce_vardata, CFG_YESNO);
-		else if (!strcmp(cep->ce_varname, "tls"))
-			s->tls = config_checkval(cep->ce_vardata, CFG_YESNO);
-		else if (!strcmp(cep->ce_varname, "reputation-score"))
-			s->reputation_score = atoi(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "priority"))
+		if (!strcmp(cep->name, "webirc"))
+			s->webirc = 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"))
+			s->tls = config_checkval(cep->value, CFG_YESNO);
+		else if (!strcmp(cep->name, "reputation-score"))
+			s->reputation_score = atoi(cep->value);
+		else if (!strcmp(cep->name, "priority"))
 		{
-			s->priority = atoi(cep->ce_vardata);
+			s->priority = atoi(cep->value);
 			DelListItem(s, securitygroups);
 			AddListItemPrio(s, securitygroups, s->priority);
 		}
+		else if (!strcmp(cep->name, "include-mask"))
+		{
+			unreal_add_masks(&s->include_mask, cep);
+		}
 	}
 	return 1;
 }
 
-Secret *find_secret(char *secret_name)
+Secret *find_secret(const char *secret_name)
 {
 	Secret *s;
 	for (s = secrets; s; s = s->next)
@@ -10444,7 +10167,7 @@ void free_secret(Secret *s)
 	safe_free(s);
 }
 
-char *_conf_secret_read_password_file(char *fname)
+char *_conf_secret_read_password_file(const char *fname)
 {
 	char *pwd, *err;
 	int fd, n;
@@ -10482,7 +10205,7 @@ char *_conf_secret_read_password_file(char *fname)
 	return pwd;
 }
 
-char *_conf_secret_read_prompt(char *blockname)
+char *_conf_secret_read_prompt(const char *blockname)
 {
 	char *pwd, *pwd_prompt;
 	char buf[256];
@@ -10513,54 +10236,54 @@ int _test_secret(ConfigFile *conf, ConfigEntry *ce)
 	char *err;
 	Secret *existing;
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: secret block needs a name, eg: secret xyz {",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
-		return errors; /* need to return here since we dereference ce->ce_vardata later.. */
+		return errors; /* need to return here since we dereference ce->value later.. */
 	} else {
-		if (!security_group_valid_name(ce->ce_vardata))
+		if (!security_group_valid_name(ce->value))
 		{
 			config_error("%s:%i: secret block name '%s' contains invalid characters or is too long. "
 			             "Only letters, numbers, underscore and hyphen are allowed.",
-			             ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+			             ce->file->filename, ce->line_number, ce->value);
 			errors++;
 		}
 	}
 
-	existing = find_secret(ce->ce_vardata);
+	existing = find_secret(ce->value);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "password"))
+		if (!strcmp(cep->name, "password"))
 		{
 			int n;
 			has_password = 1;
 			CheckNull(cep);
-			if (cep->ce_entries ||
-			    (((n = Auth_AutoDetectHashType(cep->ce_vardata))) && ((n == AUTHTYPE_BCRYPT) || (n == AUTHTYPE_ARGON2))))
+			if (cep->items ||
+			    (((n = Auth_AutoDetectHashType(cep->value))) && ((n == AUTHTYPE_BCRYPT) || (n == AUTHTYPE_ARGON2))))
 			{
 				config_error("%s:%d: you cannot use hashed passwords here, see "
 				             "https://www.unrealircd.org/docs/Secret_block#secret-plaintext",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				             cep->file->filename, cep->line_number);
 				errors++;
 				continue;
 			}
-			if (!valid_secret_password(cep->ce_vardata, &err))
+			if (!valid_secret_password(cep->value, &err))
 			{
 				config_error("%s:%d: secret::password does not meet password complexity requirements: %s",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+				             cep->file->filename, cep->line_number, err);
 				errors++;
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "password-file"))
+		if (!strcmp(cep->name, "password-file"))
 		{
 			char *str;
 			has_password_file = 1;
 			CheckNull(cep);
-			convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
-			if (!file_exists(cep->ce_vardata) && existing && existing->password)
+			convert_to_absolute_path(&cep->value, CONFDIR);
+			if (!file_exists(cep->value) && existing && existing->password)
 			{
 				/* Silently ignore the case where a secret block already
 				 * has the password read and now the file is no longer available.
@@ -10569,59 +10292,59 @@ int _test_secret(ConfigFile *conf, ConfigEntry *ce)
 				 */
 			} else
 			{
-				str = _conf_secret_read_password_file(cep->ce_vardata);
+				str = _conf_secret_read_password_file(cep->value);
 				if (!str)
 				{
 					config_error("%s:%d: secret::password-file: error reading password from file, see error from above.",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+						cep->file->filename, cep->line_number);
 					errors++;
 				}
 				safe_free_sensitive(str);
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "password-prompt"))
+		if (!strcmp(cep->name, "password-prompt"))
 		{
 #ifdef _WIN32
 			config_error("%s:%d: secret::password-prompt is not implemented in Windows at the moment, sorry!",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				cep->file->filename, cep->line_number);
 			config_error("Choose a different method to enter passwords or use *NIX");
 			errors++;
 			return errors;
 #endif
 			has_password_prompt = 1;
-			if (loop.ircd_booted && !find_secret(ce->ce_vardata))
+			if (loop.booted && !find_secret(ce->value))
 			{
 				config_error("%s:%d: you cannot add a new secret { } block that uses password-prompt and then /REHASH. "
 				             "With 'password-prompt' you can only add such a password on boot.",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				             cep->file->filename, cep->line_number);
 				config_error("Either use a different method to enter passwords or restart the IRCd on the console.");
 				errors++;
 			}
-			if (!loop.ircd_booted && !running_interactively())
+			if (!loop.booted && !running_interactively())
 			{
 				config_error("ERROR: IRCd is not running interactively, but via a cron job or something similar.");
 				config_error("%s:%d: unable to prompt for password since IRCd is not started in a terminal",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				config_error("Either use a different method to enter passwords or start the IRCd in a terminal/SSH/..");
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "password-url"))
+		if (!strcmp(cep->name, "password-url"))
 		{
 			config_error("%s:%d: secret::password-url is not supported yet in this UnrealIRCd version.",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				cep->file->filename, cep->line_number);
 			errors++;
 		} else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"secret", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"secret", cep->name);
 			errors++;
 			continue;
 		}
-		if (cep->ce_entries)
+		if (cep->items)
 		{
 			config_error("%s:%d: secret::%s does not support sub-options (%s)",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				cep->ce_varname, cep->ce_entries->ce_varname);
+				cep->file->filename, cep->line_number,
+				cep->name, cep->items->name);
 			errors++;
 		}
 	}
@@ -10629,7 +10352,7 @@ int _test_secret(ConfigFile *conf, ConfigEntry *ce)
 	if (!has_password && !has_password_file && !has_password_prompt)
 	{
 		config_error("%s:%d: secret { } block must contain 1 of: password OR password-file OR password-prompt",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 
@@ -10645,21 +10368,21 @@ int _conf_secret(ConfigFile *conf, ConfigEntry *ce)
 {
 	ConfigEntry *cep;
 	Secret *s;
-	Secret *existing = find_secret(ce->ce_vardata);
+	Secret *existing = find_secret(ce->value);
 
 	s = safe_alloc(sizeof(Secret));
-	safe_strdup(s->name, ce->ce_vardata);
+	safe_strdup(s->name, ce->value);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "password"))
+		if (!strcmp(cep->name, "password"))
 		{
-			safe_strdup_sensitive(s->password, cep->ce_vardata);
-			destroy_string(cep->ce_vardata); /* destroy the original */
+			safe_strdup_sensitive(s->password, cep->value);
+			destroy_string(cep->value); /* destroy the original */
 		} else
-		if (!strcmp(cep->ce_varname, "password-file"))
+		if (!strcmp(cep->name, "password-file"))
 		{
-			if (!file_exists(cep->ce_vardata) && existing && existing->password)
+			if (!file_exists(cep->value) && existing && existing->password)
 			{
 				/* Silently ignore the case where a secret block already
 				 * has the password read and now the file is no longer available.
@@ -10668,14 +10391,14 @@ int _conf_secret(ConfigFile *conf, ConfigEntry *ce)
 				 */
 			} else
 			{
-				s->password = _conf_secret_read_password_file(cep->ce_vardata);
+				s->password = _conf_secret_read_password_file(cep->value);
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "password-prompt"))
+		if (!strcmp(cep->name, "password-prompt"))
 		{
-			if (!loop.ircd_booted && running_interactively())
+			if (!loop.booted && running_interactively())
 			{
-				s->password = _conf_secret_read_prompt(ce->ce_vardata);
+				s->password = _conf_secret_read_prompt(ce->value);
 				if (!s->password || !valid_secret_password(s->password, NULL))
 				{
 					config_error("Invalid password entered on console (does not meet complexity requirements)");
@@ -10713,150 +10436,128 @@ int _conf_secret(ConfigFile *conf, ConfigEntry *ce)
 	return 1;
 }
 
-#ifdef USE_LIBCURL
-static void conf_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *inc_key)
+void resource_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *rs_key)
 {
-	ConfigItem_include *inc;
+	ConfigResource *rs = (ConfigResource *)rs_key;
 
-	if (!loop.ircd_rehashing)
-		return;
+	rs->type &= ~RESOURCE_DLQUEUED;
 
-	/*
-	  use inc_key to find the correct include block. This
-	  should be cheaper than using the full URL.
-	 */
-	for (inc = conf_include; inc; inc = inc->next)
-	{
-		if ( inc_key != (void *)inc )
-			continue;
-		if (!(inc->flag.type & INCLUDE_REMOTE))
-			continue;
-		if (inc->flag.type & INCLUDE_NOTLOADED)
-			continue;
-		if (strcasecmp(url, inc->url))
-			continue;
+	if (config_verbose)
+		config_status("resource_download_complete() for %s [%s]", url, errorbuf?errorbuf:"success");
 
-		inc->flag.type &= ~INCLUDE_DLQUEUED;
-		break;
-	}
-	if (!inc)
+	if (!file && !cached)
 	{
-		ircd_log(LOG_ERROR, "Downloaded remote include which matches no include statement.");
-		return;
+		/* DOWNLOAD FAILED */
+		if (rs->cache_file)
+		{
+			unreal_log(ULOG_ERROR, "config", "DOWNLOAD_FAILED_SOFT", NULL,
+				   "$file:$line_number: Failed to download '$url': $error_message\n"
+				   "Using a cached copy instead.",
+				   log_data_string("file", rs->wce->ce->file->filename),
+				   log_data_integer("line_number", rs->wce->ce->line_number),
+				   log_data_string("url", displayurl(url)),
+				   log_data_string("error_message", errorbuf));
+			safe_strdup(rs->file, rs->cache_file);
+		} else {
+			unreal_log(ULOG_ERROR, "config", "DOWNLOAD_FAILED_HARD", NULL,
+				   "$file:$line_number: Failed to download '$url': $error_message",
+				   log_data_string("file", rs->wce->ce->file->filename),
+				   log_data_integer("line_number", rs->wce->ce->line_number),
+				   log_data_string("url", displayurl(url)),
+				   log_data_string("error_message", errorbuf));
+			/* Set error condition, this so config_read_file() later will stop. */
+			loop.config_load_failed = 1;
+			/* We keep the other transfers running since they may raise (more) errors.
+			 * Which can be helpful so you can differentiate between an error of an
+			 * include on one server, or complete lack of internet connectvitity.
+			 */
+		}
 	}
-
-	if (!file && !cached)
-		update_remote_include(inc, file, 0, errorbuf); /* DOWNLOAD FAILED */
 	else
 	{
-		char *urlfile = url_getfilename(url);
-		char *file_basename = unreal_getfilename(urlfile);
-		char *tmp = unreal_mktemp(TMPDIR, file_basename ? file_basename : "download.conf");
-		safe_free(urlfile);
-
 		if (cached)
 		{
-			unreal_copyfileex(inc->file, tmp, 1);
-			unreal_copyfileex(inc->file, unreal_mkcache(url), 0);
-			update_remote_include(inc, tmp, 0, NULL);
-		}
-		else
-		{
-			/*
-			  copy/hardlink file to another file because our caller will
-			  remove(file).
-			*/
-			unreal_copyfileex(file, tmp, 1);
-			update_remote_include(inc, tmp, 0, NULL);
-			unreal_copyfileex(file, unreal_mkcache(url), 0);
+			/* Copy from cache */
+			safe_strdup(rs->file, rs->cache_file);
+		} else {
+			/* Copy to cache */
+			const char *cache_file = unreal_mkcache(url);
+			unreal_copyfileex(file, cache_file, 1);
+			safe_strdup(rs->file, cache_file);
 		}
 	}
-	for (inc = conf_include; inc; inc = inc->next)
+
+	if (rs->file)
 	{
-		if (inc->flag.type & INCLUDE_DLQUEUED)
-			return;
+		if (rs->type & RESOURCE_INCLUDE)
+		{
+			if (config_read_file(rs->file, (char *)displayurl(rs->url)) < 0)
+				loop.config_load_failed = 1;
+		} else {
+			ConfigEntryWrapper *wce;
+			for (wce = rs->wce; wce; wce = wce->next)
+				safe_strdup(wce->ce->value, rs->file); // now information of url is lost, hm!!
+		}
 	}
-	rehash_internal(loop.rehash_save_client, loop.rehash_save_sig);
+
+	/* If rehashing, check if we are done.
+	 * If booting (not rehashing), this is done from the
+	 * startup loop where it also checks is_config_read_finished().
+	 */
+	if (loop.rehashing && is_config_read_finished())
+		rehash_internal(loop.rehash_save_client);
 }
-#endif
 
-int     rehash(Client *client, int sig)
+/** 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).
+ * @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)
 {
-#ifdef USE_LIBCURL
-	ConfigItem_include *inc;
-	char found_remote = 0;
-	if (loop.ircd_rehashing)
+	if (loop.rehashing)
 	{
-		if (!sig)
+		if (client)
 			sendnotice(client, "A rehash is already in progress");
-		return 0;
-	}
-
-	/* Log who or what did the rehash: */
-	if (sig)
-	{
-		ircd_log(LOG_ERROR, "Rehashing configuration file (SIGHUP signal received)");
-	} else
-	if (client && client->user)
-	{
-		ircd_log(LOG_ERROR, "Rehashing configuration file (requested by %s!%s@%s)",
-			client->name, client->user->username, client->user->realhost);
-	} else
-	if (client)
-	{
-		ircd_log(LOG_ERROR, "Rehashing configuration file (requested by %s)",
-			client->name);
+		return;
 	}
 
-	loop.ircd_rehashing = 1;
+	loop.rehashing = 1;
 	loop.rehash_save_client = client;
-	loop.rehash_save_sig = sig;
-	for (inc = conf_include; inc; inc = inc->next)
+	config_read_start();
+	/* If we already have everything, then can we proceed with the rehash */
+	if (is_config_read_finished())
 	{
-		time_t modtime;
-		if (!(inc->flag.type & INCLUDE_REMOTE))
-			continue;
-
-		if (inc->flag.type & INCLUDE_NOTLOADED)
-			continue;
-		found_remote = 1;
-		modtime = unreal_getfilemodtime(inc->file);
-		inc->flag.type |= INCLUDE_DLQUEUED;
-
-		/*
-		  use (void *)inc as the key for finding which
-		  include block conf_download_complete() should use.
-		*/
-		download_file_async(inc->url, modtime, conf_download_complete, (void *)inc);
-	}
-	if (!found_remote)
-		return rehash_internal(client, sig);
-	return 0;
-#else
-	loop.ircd_rehashing = 1;
-	return rehash_internal(client, sig);
-#endif
+		rehash_internal(client);
+		return;
+	}
+	/* Otherwise, I/O events will take care of it later
+	 * after all remote includes have been downloaded.
+	 */
 }
 
-int	rehash_internal(Client *client, int sig)
+int rehash_internal(Client *client)
 {
-	if (sig == 1)
-		sendto_ops("Got signal SIGHUP, reloading %s file", configfile);
-	loop.ircd_rehashing = 1; /* double checking.. */
-	if (init_conf(configfile, 1) == 0)
-		run_configuration();
-	if (sig == 1)
-		reread_motdsandrules();
-	unload_all_unused_snomasks();
+	/* Log it here if it is by a signal */
+	if (client == NULL)
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]");
+
+	loop.rehashing = 1; /* double checking.. */
+
+	if (config_test() == 0)
+		config_run();
+	/* TODO: uh.. are we supposed to do all this for a failed rehash too? maybe some but not all? */
+	reread_motdsandrules();
 	unload_all_unused_umodes();
 	unload_all_unused_extcmodes();
 	unload_all_unused_caps();
 	unload_all_unused_history_backends();
 	// unload_all_unused_moddata(); -- this will crash
-	extcmodes_check_for_changes();
 	umodes_check_for_changes();
 	charsys_check_for_changes();
-	loop.ircd_rehashing = 0;
+	loop.rehashing = 0;
 	remote_rehash_client = NULL;
 	return 1;
 }
@@ -10884,8 +10585,6 @@ void link_cleanup(ConfigItem_link *link_ptr)
 
 void delete_linkblock(ConfigItem_link *link_ptr)
 {
-	Debug((DEBUG_ERROR, "delete_linkblock: deleting %s, refcount=%d",
-		link_ptr->servername, link_ptr->refcount));
 	if (link_ptr->class)
 	{
 		link_ptr->class->xrefcount--;
@@ -10904,8 +10603,6 @@ void delete_linkblock(ConfigItem_link *link_ptr)
 
 void delete_classblock(ConfigItem_class *class_ptr)
 {
-	Debug((DEBUG_ERROR, "delete_classblock: deleting %s, clients=%d, xrefcount=%d",
-		class_ptr->name, class_ptr->clients, class_ptr->xrefcount));
 	safe_free(class_ptr->name);
 	DelListItem(class_ptr, conf_class);
 	safe_free(class_ptr);
@@ -10924,6 +10621,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->websocket_forward);
 			safe_free(listen_ptr);
 			i++;
 		}
@@ -10933,279 +10631,153 @@ void	listen_cleanup()
 		close_unbound_listeners();
 }
 
-#ifdef USE_LIBCURL
-char *find_remote_include(char *url, char **errorbuf)
-{
-	ConfigItem_include *inc;
-
-	for (inc = conf_include; inc; inc = inc->next)
-	{
-		if (!(inc->flag.type & INCLUDE_NOTLOADED))
-			continue;
-		if (!(inc->flag.type & INCLUDE_REMOTE))
-			continue;
-		if (!strcasecmp(url, inc->url))
-		{
-			*errorbuf = inc->errorbuf;
-			return inc->file;
-		}
-	}
-	return NULL;
-}
-
-char *find_loaded_remote_include(char *url)
+ConfigResource *find_config_resource(const char *resource)
 {
-	ConfigItem_include *inc;
+	ConfigResource *rs;
 
-	for (inc = conf_include; inc; inc = inc->next)
+	for (rs = config_resources; rs; rs = rs->next)
 	{
-		if ((inc->flag.type & INCLUDE_NOTLOADED))
-			continue;
-		if (!(inc->flag.type & INCLUDE_REMOTE))
-			continue;
-		if (!strcasecmp(url, inc->url))
-			return inc->file;
+#ifdef _WIN32
+		if (rs->file && !strcasecmp(resource, rs->file))
+			return rs;
+#else
+		if (rs->file && !strcmp(resource, rs->file))
+			return rs;
+#endif
+		if (rs->url && !strcasecmp(resource, rs->url))
+			return rs;
 	}
-
 	return NULL;
 }
 
-/**
- * Non-asynchronous remote inclusion to give a user better feedback
- * when first starting his IRCd.
- *
- * The asynchronous friend is rehash() which merely queues remote
- * includes for download using download_file_async().
+/* Add configuration resource to list.
+ * For files this doesn't do terribly much, except that you can use
+ * the return value to judge on whether you should call config_read_file() or not.
+ * For urls this adds the resource to the list of links to be downloaded.
+ * @param resource	File or URL of the resource
+ * @param type		A RESOURCE_ type such as RESOURCE_INCLUDE
+ * @param ce		The ConfigEntry where the add_config_resource() happened
+ *			for, such as the include block, etc.
+ * @returns 0 if the file is already on our list (so no need to load it!)
  */
-int remote_include(ConfigEntry *ce)
+int add_config_resource(const char *resource, int type, ConfigEntry *ce)
 {
-	char *errorbuf = NULL;
-	char *url = ce->ce_vardata;
-	char *file = find_remote_include(url, &errorbuf);
-	int ret;
-	if (!loop.ircd_rehashing || (loop.ircd_rehashing && !file && !errorbuf))
-	{
-		char *error;
-		if (config_verbose > 0)
-			config_status("Downloading %s", displayurl(url));
-		file = download_file(url, &error);
-		if (!file)
-		{
-			if (has_cached_version(url))
-			{
-				config_warn("%s:%i: include: error downloading '%s': %s -- using cached version instead.",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-					displayurl(url), error);
-				safe_strdup(file, unreal_mkcache(url));
-				/* Let it pass to load_conf()... */
-			} else {
-				config_error("%s:%i: include: error downloading '%s': %s",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-					 displayurl(url), error);
-				return -1;
-			}
-		} else {
-			unreal_copyfileex(file, unreal_mkcache(url), 0);
-		}
-		add_remote_include(file, url, 0, NULL, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		ret = load_conf(file, url);
-		safe_free(file);
-		return ret;
-	}
-	else
-	{
-		if (errorbuf)
-		{
-			if (has_cached_version(url))
-			{
-				config_warn("%s:%i: include: error downloading '%s': %s -- using cached version instead.",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-					displayurl(url), errorbuf);
-				/* Let it pass to load_conf()... */
-				safe_strdup(file, unreal_mkcache(url));
-			} else {
-				config_error("%s:%i: include: error downloading '%s': %s",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-					displayurl(url), errorbuf);
-				return -1;
-			}
-		}
-		if (config_verbose > 0)
-			config_status("Loading %s from download", url);
-		add_remote_include(file, url, 0, NULL, ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		ret = load_conf(file, url);
-		return ret;
-	}
-	return 0;
-}
-#endif
+	ConfigResource *rs;
+	ConfigEntryWrapper *wce;
 
-/**
- * Add an item to the conf_include list for the specified file.
- *
- * Checks for whether or not we're performing recursive includes
- * belong in conf_load() because that function is able to return an
- * error code. Any checks in here will end up being ignored by callers
- * and thus will gain us nothing.
- *
- * @param file path to the include file.
- */
-void add_include(const char *file, const char *included_from, int included_from_line)
-{
-	ConfigItem_include *inc;
-
-	inc = safe_alloc(sizeof(ConfigItem_include));
-	safe_strdup(inc->file, file);
-	inc->flag.type = INCLUDE_NOTLOADED;
-	safe_strdup(inc->included_from, included_from);
-	inc->included_from_line = included_from_line;
-	AddListItem(inc, conf_include);
-}
+	if (config_verbose)
+		config_status("add_config_resource() for '%s", resource);
 
-#ifdef USE_LIBCURL
-/**
- * Adds a remote include entry to the config_include list.
- *
- * This is to be called whenever the included_from and
- * included_from_line parameters are known. This means that during a
- * rehash when downloads are done asynchronously, you call this with
- * the inclued_from and included_from_line information. After the
- * download is complete and you know there it is stored in the FS,
- * call update_remote_include().
- */
-void add_remote_include(const char *file, const char *url, int flags, const char *errorbuf, const char *included_from, int included_from_line)
-{
-	ConfigItem_include *inc;
+	wce = safe_alloc(sizeof(ConfigEntryWrapper));
+	wce->ce = ce;
 
-	/* we rely on safe_alloc() zeroing the ConfigItem_include */
-	inc = safe_alloc(sizeof(ConfigItem_include));
-	if (included_from)
+	rs = find_config_resource(resource);
+	if (rs)
 	{
-		safe_strdup(inc->included_from, included_from);
-		inc->included_from_line = included_from_line;
+		/* Existing entry, add us to the list of
+		 * items who are interested in this resource ;)
+		 */
+		AddListItem(wce, rs->wce);
+		return 0;
 	}
-	safe_strdup(inc->url, url);
 
-	update_remote_include(inc, file, INCLUDE_NOTLOADED|INCLUDE_REMOTE|flags, errorbuf);
-	AddListItem(inc, conf_include);
-}
+	/* New entry */
+	rs = safe_alloc(sizeof(ConfigResource));
+	rs->wce = wce;
+	AddListItem(rs, config_resources);
 
-/**
- * Update certain information in a remote include's config_include list entry.
- *
- * @param file the place on disk where the downloaded remote include
- *        may be found
- * @param flags additional flags to set on the config_include entry
- * @param errorbuf non-NULL if there were errors encountered in
- *        downloading. The error will be stored into the config_include
- *        entry.
- */
-void update_remote_include(ConfigItem_include *inc, const char *file, int flags, const char *errorbuf)
-{
-	/*
-	 * file may be NULL when errorbuf is non-NULL and vice-versa.
-	 */
-	if (file)
-		safe_strdup(inc->file, file);
-	inc->flag.type |= flags;
-
-	if (errorbuf)
-		safe_strdup(inc->errorbuf, errorbuf);
-}
-#endif
-
-/**
- * Clean up conf_include after a rehash fails because of a
- * configuration file error.
- *
- * Duplicates some in unload_loaded_include().
- */
-void unload_notloaded_includes(void)
-{
-	ConfigItem_include *inc, *next;
-
-	for (inc = conf_include; inc; inc = next)
+	if (!url_is_valid(resource))
 	{
-		next = inc->next;
-		if ((inc->flag.type & INCLUDE_NOTLOADED) || !(inc->flag.type & INCLUDE_USED))
-		{
-#ifdef USE_LIBCURL
-			if (inc->flag.type & INCLUDE_REMOTE)
-			{
-				/* Delete the file, but only if it's not a cached version */
-				if (strncmp(inc->file, CACHEDIR, strlen(CACHEDIR)))
-				{
-					remove(inc->file);
+		safe_strdup(rs->file, resource);
+	} else {
+		const char *cache_file;
+		time_t modtime;
+
+		safe_strdup(rs->url, resource);
+		rs->type = type|RESOURCE_REMOTE|RESOURCE_DLQUEUED;
+
+		cache_file = unreal_mkcache(rs->url);
+		modtime = unreal_getfilemodtime(cache_file);
+		if (modtime > 0)
+		{
+			safe_strdup(rs->cache_file, cache_file); /* Cached copy is available */
+			/* Check if there is an "url-refresh" argument */
+			ConfigEntry *cep, *prev = NULL;
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				if (!strcmp(cep->name, "url-refresh"))
+				{
+					/* First find out the time value of url-refresh... (eg '7d' -> 86400*7) */
+					long refresh_time = 0;
+					if (cep->value)
+						refresh_time = config_checkval(cep->value, CFG_TIME);
+					/* Then remove the config item so it is not seen by the rest of unrealircd.
+					 * Can't use DelListItem() here as ConfigEntry has no ->prev, only ->next.
+					 */
+					if (prev)
+						prev->next = cep->next; /* (skip over us) */
+					else
+						ce->items = cep->next; /* (new head) */
+					/* ..and free it */
+					config_entry_free(cep);
+					/* And now check if the current cached copy is recent enough */
+					if (TStime() - modtime < refresh_time)
+					{
+						/* Don't download, use cached copy */
+						//config_status("DEBUG: using cached copy due to url-refresh %ld", refresh_time);
+						resource_download_complete(rs->url, NULL, NULL, 1, rs);
+						return 1;
+					} else {
+						//config_status("DEBUG: requires download attempt, out of date url-refresh %ld < %ld", refresh_time, TStime() - modtime);
+					}
+					break; // MUST break now as we touched the linked list.
 				}
-				safe_free(inc->url);
-				safe_free(inc->errorbuf);
+				prev = cep;
 			}
-#endif
-			safe_free(inc->file);
-			safe_free(inc->included_from);
-			DelListItem(inc, conf_include);
-			safe_free(inc);
 		}
+		download_file_async(rs->url, modtime, resource_download_complete, (void *)rs, NULL, DOWNLOAD_MAX_REDIRECTS);
 	}
+	return 1;
 }
 
-/**
- * Clean up conf_include after a successful rehash to make way for
- * load_includes().
- */
-void unload_loaded_includes(void)
+void free_all_config_resources(void)
 {
-	ConfigItem_include *inc, *next;
+	ConfigResource *rs, *next;
+	ConfigEntryWrapper *wce, *wce_next;
 
-	for (inc = conf_include; inc; inc = next)
+	for (rs = config_resources; rs; rs = next)
 	{
-		next = inc->next;
-		if (!(inc->flag.type & INCLUDE_NOTLOADED) || !(inc->flag.type & INCLUDE_USED))
+		next = rs->next;
+		for (wce = rs->wce; wce; wce = wce_next)
+		{
+			wce_next = wce->next;
+			safe_free(wce);
+		}
+		rs->wce = NULL;
+		if (rs->type & RESOURCE_REMOTE)
 		{
-#ifdef USE_LIBCURL
-			if (inc->flag.type & INCLUDE_REMOTE)
+			/* Delete the file, but only if it's not a cached version */
+			if (rs->file && strncmp(rs->file, CACHEDIR, strlen(CACHEDIR)))
 			{
-				/* Delete the file, but only if it's not a cached version */
-				if (strncmp(inc->file, CACHEDIR, strlen(CACHEDIR)))
-				{
-					remove(inc->file);
-				}
-				safe_free(inc->url);
-				safe_free(inc->errorbuf);
+				remove(rs->file);
 			}
-#endif
-			safe_free(inc->file);
-			safe_free(inc->included_from);
-			DelListItem(inc, conf_include);
-			safe_free(inc);
+			safe_free(rs->url);
 		}
+		safe_free(rs->file);
+		safe_free(rs->cache_file);
+		DelListItem(rs, config_resources);
+		safe_free(rs);
 	}
 }
 
-/**
- * Mark loaded includes as loaded by removing the INCLUDE_NOTLOADED
- * flag. Meant to be called only after calling
- * unload_loaded_includes().
- */
-void load_includes(void)
-{
-	ConfigItem_include *inc;
-
-	/* Doing this for all the includes should actually be faster
-	 * than only doing it for includes that are not-loaded
-	 */
-	for (inc = conf_include; inc; inc = inc->next)
-		inc->flag.type &= ~INCLUDE_NOTLOADED;
-}
-
 int tls_tests(void)
 {
 	if (have_tls_listeners == 0)
 	{
-		config_error("Your server is not listening on any SSL/TLS ports.");
+		config_error("Your server is not listening on any TLS ports.");
 		config_status("Add this to your unrealircd.conf: listen { ip %s; port 6697; options { tls; }; };",
 		            port_6667_ip ? port_6667_ip : "*");
-		config_status("See https://www.unrealircd.org/docs/FAQ#Your_server_is_not_listening_on_any_SSL_ports");
+		config_status("See https://www.unrealircd.org/docs/FAQ#no-tls-ports");
 		return 0;
 	}
 
@@ -11245,7 +10817,7 @@ int reloadable_perm_module_unloaded(void)
 	return ret;
 }
 
-char *link_generator_spkifp(TLSOptions *tlsoptions)
+const char *link_generator_spkifp(TLSOptions *tlsoptions)
 {
 	SSL_CTX *ctx;
 	SSL *ssl;
@@ -11267,7 +10839,7 @@ void link_generator(void)
 	TLSOptions *tlsopt = iConf.tls_options; /* never null */
 	int port = 0;
 	char *ip = NULL;
-	char *spkifp;
+	const char *spkifp;
 
 	for (lstn = conf_listen; lstn; lstn = lstn->next)
 	{
@@ -11286,7 +10858,7 @@ void link_generator(void)
 
 	if (!port)
 	{
-		printf("You don't have any listen { } blocks that are serversonly.\n");
+		printf("You don't have any listen { } blocks that are serversonly (and have tls enabled).\n");
 		printf("It is recommended to have at least one. Add this to your configuration file:\n");
 		printf("listen { ip *; port 6900; options { tls; serversonly; }; };\n");
 		exit(1);
@@ -11295,7 +10867,7 @@ void link_generator(void)
 	spkifp = link_generator_spkifp(tlsopt);
 	if (!spkifp)
 	{
-		printf("Could not calculate spkifp. Maybe you have uncommon SSL/TLS options set? Odd...\n");
+		printf("Could not calculate spkifp. Maybe you have uncommon TLS options set? Odd...\n");
 		exit(1);
 	}
 
diff --git a/src/conf_preprocessor.c b/src/conf_preprocessor.c
@@ -19,7 +19,7 @@ static inline int ValidVarCharacter(char x)
 	return 0;
 }
 
-PreprocessorItem evaluate_preprocessor_if(char *statement, char *filename, int linenumber, ConditionalConfig **cc_out)
+PreprocessorItem evaluate_preprocessor_if(char *statement, const char *filename, int linenumber, ConditionalConfig **cc_out)
 {
 	char *p=statement, *name;
 	int negative = 0;
@@ -176,7 +176,7 @@ PreprocessorItem evaluate_preprocessor_if(char *statement, char *filename, int l
 	return PREPROCESSOR_ERROR;
 }
 
-PreprocessorItem  evaluate_preprocessor_define(char *statement, char *filename, int linenumber)
+PreprocessorItem  evaluate_preprocessor_define(char *statement, const char *filename, int linenumber)
 {
 	char *p = statement;
 	char *name, *name_terminator;
@@ -246,7 +246,7 @@ PreprocessorItem  evaluate_preprocessor_define(char *statement, char *filename, 
 	return PREPROCESSOR_DEFINE;
 }
 
-PreprocessorItem  parse_preprocessor_item(char *start, char *end, char *filename, int linenumber, ConditionalConfig **cc)
+PreprocessorItem  parse_preprocessor_item(char *start, char *end, const char *filename, int linenumber, ConditionalConfig **cc)
 {
 	char buf[512];
 	int max;
@@ -262,7 +262,7 @@ PreprocessorItem  parse_preprocessor_item(char *start, char *end, char *filename
 		return evaluate_preprocessor_define(buf+7, filename, linenumber);
 	else if (!strncmp(buf, "@if ", 4))
 		return evaluate_preprocessor_if(buf+4, filename, linenumber, cc);
-	else if (!strcmp(buf, "@endif"))
+	else if (!strncmp(buf, "@endif", 6))
 		return PREPROCESSOR_ENDIF;
 
 	config_error("%s:%i: Unknown preprocessor directive: %s", filename, linenumber, buf);
@@ -382,29 +382,29 @@ int preprocessor_resolve_if(ConditionalConfig *cc, PreprocessorPhase phase)
 
 void preprocessor_resolve_conditionals_ce(ConfigEntry **ce_list, PreprocessorPhase phase)
 {
-	ConfigEntry *ce, *ce_next, *ce_prev;
+	ConfigEntry *ce, *next, *ce_prev;
 	ConfigEntry *cep, *cep_next, *cep_prev;
 
 	ce_prev = NULL;
-	for (ce = *ce_list; ce; ce = ce_next)
+	for (ce = *ce_list; ce; ce = next)
 	{
-		ce_next = ce->ce_next;
+		next = ce->next;
 		/* This is for an @if before a block start */
-		if (!preprocessor_resolve_if(ce->ce_cond, phase))
+		if (!preprocessor_resolve_if(ce->conditional_config, phase))
 		{
 			/* Delete this entry */
 			if (ce == *ce_list)
 			{
 				/* we are head, so new head */
-				*ce_list = ce->ce_next; /* can be NULL now */
+				*ce_list = ce->next; /* can be NULL now */
 			} else {
 				/* non-head */
-				ce_prev->ce_next = ce->ce_next; /* can be NULL now */
+				ce_prev->next = ce->next; /* can be NULL now */
 			}
 			config_entry_free(ce);
 			continue;
 		}
-		preprocessor_resolve_conditionals_ce(&ce->ce_entries, phase);
+		preprocessor_resolve_conditionals_ce(&ce->items, phase);
 		ce_prev = ce;
 	}
 }
@@ -413,8 +413,8 @@ void preprocessor_resolve_conditionals_all(PreprocessorPhase phase)
 {
 	ConfigFile *cfptr;
 
-	for (cfptr = conf; cfptr; cfptr = cfptr->cf_next)
-		preprocessor_resolve_conditionals_ce(&cfptr->cf_entries, phase);
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
+		preprocessor_resolve_conditionals_ce(&cfptr->items, phase);
 }
 
 /** Frees the list of config_defines, so all @defines */
@@ -502,7 +502,7 @@ void preprocessor_replace_defines(char **item, ConfigEntry *ce)
 				if ((limit > 2) && ((*varend == '\0') || strchr("\t ,.", *varend)))
 				{
 					config_warn("%s:%d: Variable %s used here but there's no @define for it earlier.",
-						    ce->ce_fileptr->cf_filename, ce->ce_varlinenum, varname);
+						    ce->file->filename, ce->line_number, varname);
 				}
 #endif
 				value = varname; /* not found? then use varname, including the '$' */
diff --git a/src/crashreport.c b/src/crashreport.c
@@ -109,7 +109,7 @@ char *find_best_asan_log(void)
 		}
 	}
 	closedir(fd);
-	return BadPtr(best_fname) ? NULL : best_fname;
+	return *best_fname ? best_fname : NULL;
 #else
 	return NULL;
 #endif
@@ -531,7 +531,7 @@ char *generate_crash_report(char *coredump, int *thirdpartymods)
 
 #define CRASH_REPORT_HOST "crash.unrealircd.org"
 
-SSL_CTX *crashreport_init_ssl(void)
+SSL_CTX *crashreport_init_tls(void)
 {
 	SSL_CTX *ctx_client;
 	char buf[512];
@@ -587,7 +587,7 @@ int crashreport_send(char *fname)
 	                           delimiter);
 	snprintf(footer, sizeof(footer), "\r\n--%s--\r\n", delimiter);
 
-	ctx_client = crashreport_init_ssl();
+	ctx_client = crashreport_init_tls();
 	if (!ctx_client)
 	{
 		printf("ERROR: TLS initalization failure (I)\n");
diff --git a/src/crule.c b/src/crule.c
@@ -186,7 +186,7 @@ int  crule_via(int numargs, void *crulearg[])
 	{
 		if (!match_simple((char *)crulearg[1], client->name))
 			continue;
-		if (!match_simple((char *)crulearg[0], client->serv->up))
+		if (!match_simple((char *)crulearg[0], client->uplink->name))
 			continue;
 		return (1);
 	}
@@ -372,11 +372,6 @@ char *crule_parse(char *rule)
 	}
 	if (ruleroot != NULL)
 		crule_free((char **)&ruleroot);
-#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
-	Debug((DEBUG_ERROR, "%s in rule: %s", crule_errstr[errcode], rule));
-#else
-	(void)fprintf(stderr, "%s in rule: %s\n", crule_errstr[errcode], rule);
-#endif
 	return NULL;
 }
 
diff --git a/src/dbuf.c b/src/dbuf.c
@@ -61,7 +61,7 @@ void dbuf_queue_init(dbuf *dyn)
 	INIT_LIST_HEAD(&dyn->dbuf_list);
 }
 
-void dbuf_put(dbuf *dyn, char *buf, size_t length)
+void dbuf_put(dbuf *dyn, const char *buf, size_t length)
 {
 	struct dbufbuf *block;
 	size_t amount;
diff --git a/src/debug.c b/src/debug.c
@@ -48,9 +48,6 @@ MODVAR char serveropts[] = {
 	'Y',
 #endif
 	'6',
-#ifdef USE_SSL
-	'e',
-#endif
 #ifndef NO_OPEROVERRIDE
 	'O',
 #endif
@@ -150,11 +147,16 @@ void debug(int level, FORMAT_STRING(const char *form), ...)
 	SET_ERRNO(err);
 }
 
-int checkprotoflags(Client *client, int flags, char *file, int line)
+int checkprotoflags(Client *client, int flags, const char *file, int line)
 {
 	if (!MyConnect(client))
-		ircd_log(LOG_ERROR, "[Debug] [BUG] ERROR: %s:%d: IsToken(<%s>,%d) on remote client",
-		         file, line, client->name, flags);
+	{
+		unreal_log(ULOG_ERROR, "main", "BUG_ISTOKEN_REMOTE_CLIENT", client,
+		           "IsToken($token_value) used on remote client in $file:$line",
+		           log_data_integer("token_value", flags),
+		           log_data_string("file", file),
+		           log_data_integer("line", line));
+	}
 	return ((client->local->proto & flags) == flags) ? 1 : 0;
 }
 #endif
diff --git a/src/dispatch.c b/src/dispatch.c
@@ -39,6 +39,11 @@
 #include <sys/ioctl.h>
 #endif
 
+/* Not sure if this is suitable for production,
+ * but let's turn it on for U6 development.
+ */
+//#define DETECT_HIGH_CPU
+
 /***************************************************************************************
  * Backend-independent functions.  fd_setselect() and friends                          *
  ***************************************************************************************/
@@ -47,14 +52,18 @@ void fd_setselect(int fd, int flags, IOCallbackFunc iocb, void *data)
 	FDEntry *fde;
 	int changed = 0;
 #if 0
-	ircd_log(LOG_ERROR, "fd_setselect(): fd %d flags %d func %p", fd, flags, &iocb);
+	unreal_log(ULOG_DEBUG, "io", "IO_DEBUG_FD_SETSELECT", NULL,
+	           "fd_setselect(): fd $fd flags $fd_flags function $function_pointer",
+	           log_data_integer("fd", fd),
+	           log_data_integer("fd_flags", flags),
+	           log_data_integer("function_pointer", (long long)iocb));
 #endif
 	if ((fd < 0) || (fd >= MAXCONNECTIONS))
 	{
-		sendto_realops("[BUG] trying to modify fd #%d in fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
-		ircd_log(LOG_ERROR, "[BUG] trying to modify fd #%d in fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
+		unreal_log(ULOG_ERROR, "io", "BUG_FD_SETSELECT_OUT_OF_RANGE", NULL,
+		           "[BUG] trying to modify fd $fd in fd table, but MAXCONNECTIONS is $maxconnections",
+		           log_data_integer("fd", fd),
+		           log_data_integer("maxconnections", MAXCONNECTIONS));
 #ifdef DEBUGMODE
 		abort();
 #endif
@@ -143,7 +152,11 @@ void fd_debug(fd_set *f, int highest, char *name)
 			//if (fcntl(i, F_GETFL) < 0)
 			int nonb = 1;
 			if (ioctlsocket(i, FIONBIO, &nonb) < 0)
-				ircd_log(LOG_ERROR, "fd_debug: FD #%d is invalid!!!", i);
+			{
+				unreal_log(ULOG_ERROR, "io", "FD_DEBUG", NULL,
+					   "[BUG] fd_debug: fd $fd is invalid!!!",
+					   log_data_integer("fd", i));
+			}
 		}
 	}
 }
@@ -168,10 +181,6 @@ void fd_select(time_t delay)
 	to.tv_sec = delay / 1000;
 	to.tv_usec = (delay % 1000) * 1000;
 
-#ifdef DEBUGMODE
-	ircd_log(LOG_ERROR, "fd_select() on 0-%d...", highest_fd+1);
-#endif
-
 #ifdef _WIN32
 	num = select(highest_fd + 1, &work_read_fds, &work_write_fds, &work_except_fds, &to);
 #else
@@ -179,8 +188,9 @@ void fd_select(time_t delay)
 #endif
 	if (num < 0)
 	{
-		extern void report_baderror(char *text, Client *client);
-		report_baderror("select %s:%s", &me);
+		unreal_log(ULOG_FATAL, "io", "SELECT_ERROR", NULL,
+		           "select() returned error ($socket_error) -- SERIOUS TROUBLE!",
+		           log_data_socket_error(-1));
 		/* DEBUG the actual problem: */
 		memcpy(&work_read_fds, &read_fds, sizeof(fd_set));
 		memcpy(&work_write_fds, &write_fds, sizeof(fd_set));
@@ -204,10 +214,6 @@ void fd_select(time_t delay)
 		if (!fde->is_open)
 			continue;
 
-#ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "fd_select(): checking %d...", fd);
-#endif
-
 		if (FD_ISSET(fd, &work_read_fds))
 			evflags |= FD_SELECT_READ;
 
@@ -223,10 +229,6 @@ void fd_select(time_t delay)
 		if (!evflags)
 			continue;
 
-#ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "fd_select(): events for %d (%d)... processing...", fd, evflags);
-#endif
-
 		if (evflags & FD_SELECT_READ)
 		{
 			iocb = fde->read_callback;
@@ -280,7 +282,9 @@ void fd_fork()
 					continue;
 					
 #ifdef DEBUGMODE
-				ircd_log(LOG_ERROR, "[BUG?] kevent returned %d", errno);
+				unreal_log(ULOG_ERROR, "io", "KEVENT_FAILED", NULL,
+				           "[io] fd_fork(): kevent returned error: $system_error",
+				           log_data_string("system_error", strerror(errno)));
 #endif
 			}
 		}
@@ -308,8 +312,13 @@ void fd_refresh(int fd)
 #ifdef DEBUGMODE
 			if (ERRNO != P_EWOULDBLOCK && ERRNO != P_EAGAIN)
 			{
-				ircd_log(LOG_ERROR, "[BUG?] fd_refresh(): kevent returned %d for fd %d for read callback (%s)",
-				         errno, fd, (fde->read_callback ? "add" : "delete"));
+				int save_err = errno;
+				unreal_log(ULOG_ERROR, "io", "KEVENT_FAILED_REFRESH", NULL,
+				           "fd_refresh(): kevent returned error for fd $fd ($fd_action) ($callback): $system_error",
+				           log_data_string("system_error", strerror(save_err)),
+				           log_data_integer("fd", fd),
+				           log_data_string("fd_action", (fde->read_callback ? "add" : "delete")),
+				           log_data_string("callback", "read_callback"));
 			}
 #endif
 		}
@@ -323,8 +332,13 @@ void fd_refresh(int fd)
 #ifdef DEBUGMODE
 			if (ERRNO != P_EWOULDBLOCK && ERRNO != P_EAGAIN && fde->write_callback)
 			{
-				ircd_log(LOG_ERROR, "[BUG?] fd_refresh(): kevent returned %d for fd %d for write callback (%s)",
-				         errno, fd, "add" /*(fde->write_callback ? "add" : "delete")*/);
+				int save_err = errno;
+				unreal_log(ULOG_ERROR, "io", "KEVENT_FAILED_REFRESH", NULL,
+				           "[io] fd_refresh(): kevent returned error for fd $fd ($fd_action) ($callback): $system_error",
+				           log_data_string("system_error", strerror(save_err)),
+				           log_data_integer("fd", fd),
+				           log_data_string("fd_action", "add"),
+				           log_data_string("callback", "write_callback"));
 			}
 #endif
 		}
@@ -440,11 +454,15 @@ void fd_refresh(int fd)
 
 	if (epoll_ctl(epoll_fd, op, fd, &ep_event) != 0)
 	{
-		if (ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN)
+		int save_errno = errno;
+		if ((save_errno == P_EWOULDBLOCK) || (save_errno == P_EAGAIN))
 			return;
 
-		ircd_log(LOG_ERROR, "[BUG] fd_refresh(): epoll_ctl returned error %d (%s) for fd %d (%s)",
-			errno, STRERROR(ERRNO), fd, fde->desc);
+		unreal_log(ULOG_ERROR, "io", "EPOLL_CTL_FAILED", NULL,
+			   "[io] fd_refresh(): epoll_ctl returned error for fd $fd ($fd_description): $system_error",
+			   log_data_string("system_error", strerror(save_errno)),
+			   log_data_integer("fd", fd),
+			   log_data_string("fd_description", fde->desc));
 		return;
 	}
 
@@ -455,7 +473,7 @@ void fd_select(time_t delay)
 {
 	int num, p, revents, fd;
 	struct epoll_event *epfd;
-#ifdef DEBUG_IOENGINE
+#ifdef DETECT_HIGH_CPU
 	int read_callbacks = 0, write_callbacks = 0;
 	struct timeval oldt, t;
 	long long tdiff;
@@ -467,7 +485,7 @@ void fd_select(time_t delay)
 	if (num <= 0)
 		return;
 
-#ifdef DEBUG_IOENGINE
+#ifdef DETECT_HIGH_CPU
 	gettimeofday(&oldt, NULL);
 #endif
 
@@ -499,7 +517,7 @@ void fd_select(time_t delay)
 			if (iocb != NULL)
 				iocb(fd, evflags, fde->data);
 
-#ifdef DEBUG_IOENGINE
+#ifdef DETECT_HIGH_CPU
 			read_callbacks++;
 #endif
 		}
@@ -511,7 +529,7 @@ void fd_select(time_t delay)
 			if (iocb != NULL)
 				iocb(fd, evflags, fde->data);
 
-#ifdef DEBUG_IOENGINE
+#ifdef DETECT_HIGH_CPU
 			write_callbacks++;
 #endif
 		}
@@ -524,14 +542,18 @@ void fd_select(time_t delay)
 #endif
 	}
 
-#ifdef DEBUG_IOENGINE
+#ifdef DETECT_HIGH_CPU
 	gettimeofday(&t, NULL);
 	tdiff = ((t.tv_sec - oldt.tv_sec) * 1000000) + (t.tv_usec - oldt.tv_usec);
 
 	if (tdiff > 1000000)
 	{
-		sendto_realops_and_log("WARNING: Slow I/O engine or high load: fd_select() took %lld ms! read_callbacks=%d, write_callbacks=%d",
-			tdiff / 1000, read_callbacks, write_callbacks);
+		unreal_log(ULOG_WARNING, "io", "HIGH_LOAD", NULL,
+		           "HIGH CPU LOAD! fd_select() took $time_msec msec "
+		           "(read: $num_read_callbacks, write: $num_write_callbacks)",
+		           log_data_integer("time_msec", tdiff/1000),
+		           log_data_integer("num_read_callbacks", read_callbacks),
+		           log_data_integer("num_write_callbacks", write_callbacks));
 	}
 #endif
 }
@@ -601,7 +623,7 @@ void fd_select(time_t delay)
 		pfd = &pollfds[p];
 
 		revents = pfd->revents;
-		fd = pfd->local->fd;
+		fd = pfd->fd;
 		if (revents == 0 || fd == -1)
 			continue;
 
diff --git a/src/dns.c b/src/dns.c
@@ -40,9 +40,9 @@ void unrealdns_cb_nametoip_verify(void *arg, int status, int timeouts, struct ho
 void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct hostent *he);
 void unrealdns_delasyncconnects(void);
 static uint64_t unrealdns_hash_ip(const char *ip);
-static void unrealdns_addtocache(char *name, char *ip);
-static char *unrealdns_findcache_ip(char *ip);
-struct hostent *unreal_create_hostent(char *name, char *ip);
+static void unrealdns_addtocache(const char *name, const char *ip);
+static const char *unrealdns_findcache_ip(const char *ip);
+struct hostent *unreal_create_hostent(const char *name, const char *ip);
 static void unrealdns_freeandremovereq(DNSReq *r);
 void unrealdns_removecacherecord(DNSCache *c);
 
@@ -87,10 +87,7 @@ static void unrealdns_sock_state_cb(void *data, ares_socket_t fd, int read, int 
 
 	if (!read && !write)
 	{
-		/* Socket is going to be closed *BY C-ARES*..
-		 * so don't call fd_close() but fd_unmap().
-		 */
-		fd_unmap(fd);
+		fd_close(fd);
 		return;
 	}
 	
@@ -108,7 +105,11 @@ static void unrealdns_sock_state_cb(void *data, ares_socket_t fd, int read, int 
  */
 static int unrealdns_sock_create_cb(ares_socket_t fd, int type, void *data)
 {
-	fd_open(fd, "DNS Resolver Socket");
+	/* NOTE: We use FDCLOSE_NONE here because c-ares
+	 * will take care of the closing. So *WE* must
+	 * never close the socket.
+	 */
+	fd_open(fd, "DNS Resolver Socket", FDCLOSE_NONE);
 	return ARES_SUCCESS;
 }
 
@@ -174,12 +175,10 @@ void reinit_resolver(Client *client)
 {
 	EventDel(unrealdns_timeout_hdl);
 
-	sendto_ops_and_log("%s requested reinitalization of resolver!", client->name);
-	sendto_realops("Destroying resolver channel, along with all currently pending queries...");
+	unreal_log(ULOG_INFO, "dns", "REINIT_RESOLVER", client,
+	           "$client requested reinitalization of the DNS resolver");
 	ares_destroy(resolver_channel);
-	sendto_realops("Initializing resolver again...");
 	init_resolver(0);
-	sendto_realops("Reinitalization finished successfully.");
 }
 
 void unrealdns_addreqtolist(DNSReq *r)
@@ -202,7 +201,7 @@ void unrealdns_addreqtolist(DNSReq *r)
 struct hostent *unrealdns_doclient(Client *client)
 {
 	DNSReq *r;
-	char *cache_name;
+	const char *cache_name;
 
 	cache_name = unrealdns_findcache_ip(client->ip);
 	if (cache_name)
@@ -233,7 +232,7 @@ struct hostent *unrealdns_doclient(Client *client)
 
 /** Resolve a name to an IP, for a link block.
  */
-void unrealdns_gethostbyname_link(char *name, ConfigItem_link *conf, int ipv4_only)
+void unrealdns_gethostbyname_link(const char *name, ConfigItem_link *conf, int ipv4_only)
 {
 	DNSReq *r;
 
@@ -283,27 +282,6 @@ void unrealdns_cb_iptoname(void *arg, int status, int timeouts, struct hostent *
 	ares_gethostbyname(resolver_channel, he->h_name, ipv6 ? AF_INET6 : AF_INET, unrealdns_cb_nametoip_verify, newr);
 }
 
-/*
-  returns:
-  1 = good hostname
-  0 = bad hostname
- */
-int verify_hostname(char *name)
-{
-char *p;
-
-	if (strlen(name) > HOSTLEN)
-		return 0; 
-
-	/* No underscores or other illegal characters */
-	for (p = name; *p; p++)
-		if (!isalnum(*p) && !strchr(".-", *p))
-			return 0;
-
-	return 1;
-}
-
-
 void unrealdns_cb_nametoip_verify(void *arg, int status, int timeouts, struct hostent *he)
 {
 	DNSReq *r = (DNSReq *)arg;
@@ -348,13 +326,16 @@ void unrealdns_cb_nametoip_verify(void *arg, int status, int timeouts, struct ho
 		goto bad;
 	}
 
-	if (!verify_hostname(r->name))
+	if (!valid_host(r->name, 1))
 	{
 		/* Hostname is bad, don't cache and consider unresolved */
 		proceed_normal_client_handshake(client, NULL);
 		goto bad;
 	}
 
+	/* Get rid of stupid uppercase DNS names... */
+	strtolower(r->name);
+
 	/* Entry was found, verified, and can be added to cache */
 
 	unrealdns_addtocache(r->name, client->ip);
@@ -372,7 +353,7 @@ void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct host
 	int n;
 	struct hostent *he2;
 	char ipbuf[HOSTLEN+1];
-	char *ip = NULL;
+	const char *ip = NULL;
 
 	if (!r->linkblock)
 	{
@@ -393,8 +374,9 @@ void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct host
 		}
 
 		/* fatal error while resolving */
-		sendto_ops_and_log("Unable to resolve hostname '%s', when trying to connect to server %s.",
-			r->name, r->linkblock->servername);
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_RESOLVING", NULL,
+			   "Unable to resolve hostname $link_block.hostname, when trying to connect to server $link_block.",
+			   log_data_link_block(r->linkblock));
 		r->linkblock->refcount--;
 		unrealdns_freeandremovereq(r);
 		return;
@@ -405,8 +387,9 @@ void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct host
 	    !(ip = inetntop(r->ipv6 ? AF_INET6 : AF_INET, he->h_addr_list[0], ipbuf, sizeof(ipbuf))))
 	{
 		/* Illegal response -- fatal */
-		sendto_ops_and_log("Unable to resolve hostname '%s', when trying to connect to server %s.",
-			r->name, r->linkblock->servername);
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_RESOLVING", NULL,
+		           "Unable to resolve hostname $link_block.hostname, when trying to connect to server $link_block.",
+		           log_data_link_block(r->linkblock));
 		unrealdns_freeandremovereq(r);
 		return;
 	}
@@ -417,22 +400,9 @@ void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct host
 	safe_strdup(r->linkblock->connect_ip, ip);
 	he2 = unreal_create_hostent(he->h_name, ip);
 
-	switch ((n = connect_server(r->linkblock, r->client, he2)))
-	{
-		case 0:
-			sendto_ops_and_log("Trying to activate link with server %s[%s]...", r->linkblock->servername, ip);
-			break;
-		case -1:
-			sendto_ops_and_log("Couldn't connect to server %s[%s].", r->linkblock->servername, ip);
-			break;
-		case -2:
-			/* Should not happen since he is not NULL */
-			sendto_ops_and_log("Hostname %s is unknown for server %s (!?).", r->linkblock->outgoing.hostname, r->linkblock->servername);
-			break;
-		default:
-			sendto_ops_and_log("Connection to server %s failed: %s", r->linkblock->servername, STRERROR(n));
-	}
-	
+	/* Try to connect to the server */
+	connect_server(r->linkblock, r->client, he2);
+
 	unrealdns_freeandremovereq(r);
 	/* DONE */
 }
@@ -442,7 +412,7 @@ static uint64_t unrealdns_hash_ip(const char *ip)
         return siphash(ip, siphashkey_dns_ip) % DNS_HASH_SIZE;
 }
 
-static void unrealdns_addtocache(char *name, char *ip)
+static void unrealdns_addtocache(const char *name, const char *ip)
 {
 	unsigned int hashv;
 	DNSCache *c;
@@ -494,7 +464,7 @@ static void unrealdns_addtocache(char *name, char *ip)
 /** Search the cache for a confirmed ip->name and name->ip match, by address.
  * @returns The resolved hostname, or NULL if not found in cache.
  */
-static char *unrealdns_findcache_ip(char *ip)
+static const char *unrealdns_findcache_ip(const char *ip)
 {
 	unsigned int hashv;
 	DNSCache *c;
@@ -562,17 +532,11 @@ DNSCache *c, *next;
 	{
 		next = c->next;
 		if (c->expires < TStime())
-		{
-#if 0
-			sendto_realops(client, "[Syzop/DNS] Expire: %s [%s] (%ld < %ld)",
-				c->name, c->ip, c->expires, TStime());
-#endif
 			unrealdns_removecacherecord(c);
-		}
 	}
 }
 
-struct hostent *unreal_create_hostent(char *name, char *ip)
+struct hostent *unreal_create_hostent(const char *name, const char *ip)
 {
 struct hostent *he;
 
@@ -646,7 +610,7 @@ CMD_FUNC(cmd_dns)
 {
 	DNSCache *c;
 	DNSReq *r;
-	char *param;
+	const char *param;
 
 	if (!ValidatePermissionsForPath("server:dns",client,NULL,NULL,NULL))
 	{
@@ -673,8 +637,8 @@ CMD_FUNC(cmd_dns)
 	} else
 	if (*param == 'c') /* CLEAR CACHE */
 	{
-		sendto_realops("%s (%s@%s) cleared the DNS cache list (/QUOTE DNS c)",
-			client->name, client->user->username, client->user->realhost);
+		unreal_log(ULOG_INFO, "dns", "DNS_CACHE_CLEARED", client,
+		            "DNS cache cleared by $client");
 		
 		while (cache_list)
 		{
@@ -702,7 +666,8 @@ CMD_FUNC(cmd_dns)
 		ares_get_servers(resolver_channel, &serverlist);
 		for (ns = serverlist; ns; ns = ns->next)
 		{
-			char ipbuf[128], *ip;
+			char ipbuf[128];
+			const char *ip;
 			i++;
 			
 			ip = inetntop(ns->family, &ns->addr, ipbuf, sizeof(ipbuf));
diff --git a/src/fdlist.c b/src/fdlist.c
@@ -24,16 +24,23 @@
  */
 FDEntry fd_table[MAXCONNECTIONS + 1];
 
-int fd_open(int fd, const char *desc)
+/** Notify I/O engine that a file descriptor opened.
+ * @param fd		The file descriptor
+ * @param desc		Description for in the fd table
+ * @param close_method	Tell what a subsequent call to fd_close() should do,
+ *                      eg close the socket, file or don't close anything.
+ * @returns The file descriptor 'fd' or -1 in case of fatal error.
+ */
+int fd_open(int fd, const char *desc, FDCloseMethod close_method)
 {
 	FDEntry *fde;
 
 	if ((fd < 0) || (fd >= MAXCONNECTIONS))
 	{
-		sendto_realops("[BUG] trying to add fd #%d to fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
-		ircd_log(LOG_ERROR, "[BUG] trying to add fd #%d to fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
+		unreal_log(ULOG_ERROR, "io", "BUG_FD_OPEN_OUT_OF_RANGE", NULL,
+		           "[BUG] trying to add fd $fd to fd table, but MAXCONNECTIONS is $maxconnections",
+		           log_data_integer("fd", fd),
+		           log_data_integer("maxconnections", MAXCONNECTIONS));
 #ifdef DEBUGMODE
 		abort();
 #endif
@@ -46,6 +53,7 @@ int fd_open(int fd, const char *desc)
 	fde->fd = fd;
 	fde->is_open = 1;
 	fde->backend_flags = 0;
+	fde->close_method = close_method;
 	strlcpy(fde->desc, desc, FD_DESC_SZ);
 
 	return fde->fd;
@@ -71,20 +79,28 @@ int fd_fileopen(const char *path, unsigned int flags)
 
 	snprintf(comment, sizeof comment, "File: %s", unreal_getfilename(pathbuf));
 
-	return fd_open(fd, comment);
+	return fd_open(fd, comment, FDCLOSE_FILE);
 }
 
-int fd_unmap(int fd)
+/** Internal function to unmap and optionally close the fd.
+ */
+/** Remove file descriptor from our table and possibly close the fd.
+ * The fd is closed (or not) according to the method specified in fd_open().
+ * @param fd	The file descriptor
+ * @returns 1 on success, 0 on failure
+ */
+int fd_close(int fd)
 {
 	FDEntry *fde;
 	unsigned int befl;
+	FDCloseMethod close_method;
 
 	if ((fd < 0) || (fd >= MAXCONNECTIONS))
 	{
-		sendto_realops("[BUG] trying to close fd #%d in fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
-		ircd_log(LOG_ERROR, "[BUG] trying to close fd #%d in fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
+		unreal_log(ULOG_ERROR, "io", "BUG_FD_CLOSE_OUT_OF_RANGE", NULL,
+		           "[BUG] trying to close fd $fd to fd table, but MAXCONNECTIONS is $maxconnections",
+		           log_data_integer("fd", fd),
+		           log_data_integer("maxconnections", MAXCONNECTIONS));
 #ifdef DEBUGMODE
 		abort();
 #endif
@@ -94,10 +110,9 @@ int fd_unmap(int fd)
 	fde = &fd_table[fd];
 	if (!fde->is_open)
 	{
-		sendto_realops("[BUG] trying to close fd #%d in fd table, but this FD isn't reported open",
-				fd);
-		ircd_log(LOG_ERROR, "[BUG] trying to close fd #%d in fd table, but this FD isn't reported open",
-				fd);
+		unreal_log(ULOG_ERROR, "io", "BUG_FD_CLOSE_NOT_OPEN", NULL,
+		           "[BUG] trying to close fd $fd to fd table, but FD is (already) closed",
+		           log_data_integer("fd", fd));
 #ifdef DEBUGMODE
 		abort();
 #endif
@@ -105,6 +120,7 @@ int fd_unmap(int fd)
 	}
 
 	befl = fde->backend_flags;
+	close_method = fde->close_method;
 	memset(fde, 0, sizeof(FDEntry));
 
 	fde->fd = fd;
@@ -112,25 +128,29 @@ int fd_unmap(int fd)
 	/* only notify the backend if it is actively tracking the FD */
 	if (befl)
 		fd_refresh(fd);
-	
-	return 1;
-}
 
-void fd_close(int fd)
-{
-	if (!fd_unmap(fd))
-		return;
+	/* Finally, close the file or socket if requested to do so */
+	switch (close_method)
+	{
+		case FDCLOSE_SOCKET:
+			CLOSE_SOCK(fd);
+			break;
+		case FDCLOSE_FILE:
+			close(fd);
+			break;
+		case FDCLOSE_NONE:
+		default:
+			break;
+	}
 
-	CLOSE_SOCK(fd);
+	return 1;
 }
 
 /* Deregister I/O notification for this file descriptor */
 void fd_unnotify(int fd)
 {
-FDEntry *fde;
-#ifdef DEBUGMODE
-	ircd_log(LOG_ERROR, "fd_unnotify(): fd=%d", fd);
-#endif
+	FDEntry *fde;
+
 	if ((fd < 0) || (fd >= MAXCONNECTIONS))
 		return;
 	
@@ -150,7 +170,7 @@ int fd_socket(int family, int type, int protocol, const char *desc)
 	if (fd < 0)
 		return -1;
 
-	return fd_open(fd, desc);
+	return fd_open(fd, desc, FDCLOSE_SOCKET);
 }
 
 int fd_accept(int sockfd)
@@ -162,7 +182,7 @@ int fd_accept(int sockfd)
 	if (fd < 0)
 		return -1;
 
-	return fd_open(fd, buf);
+	return fd_open(fd, buf, FDCLOSE_SOCKET);
 }
 
 void fd_desc(int fd, const char *desc)
@@ -171,10 +191,10 @@ void fd_desc(int fd, const char *desc)
 
 	if ((fd < 0) || (fd >= MAXCONNECTIONS))
 	{
-		sendto_realops("[BUG] trying to modify fd #%d in fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
-		ircd_log(LOG_ERROR, "[BUG] trying to modify fd #%d in fd table, but MAXCONNECTIONS is %d",
-				fd, MAXCONNECTIONS);
+		unreal_log(ULOG_ERROR, "io", "BUG_FD_DESC_OUT_OF_RANGE", NULL,
+		           "[BUG] trying to fd_desc fd $fd in fd table, but MAXCONNECTIONS is $maxconnections",
+		           log_data_integer("fd", fd),
+		           log_data_integer("maxconnections", MAXCONNECTIONS));
 #ifdef DEBUGMODE
 		abort();
 #endif
@@ -184,10 +204,9 @@ void fd_desc(int fd, const char *desc)
 	fde = &fd_table[fd];
 	if (!fde->is_open)
 	{
-		sendto_realops("[BUG] trying to modify fd #%d in fd table, but this FD isn't reported open",
-				fd);
-		ircd_log(LOG_ERROR, "[BUG] trying to modify fd #%d in fd table, but this FD isn't reported open",
-				fd);
+		unreal_log(ULOG_ERROR, "io", "BUG_FD_DESC_NOT_OPEN", NULL,
+		           "[BUG] trying to fd_desc fd $fd in fd table, but FD is (already) closed",
+		           log_data_integer("fd", fd));
 #ifdef DEBUGMODE
 		abort();
 #endif
diff --git a/src/hash.c b/src/hash.c
@@ -260,11 +260,9 @@ void siphash_generate_key(char *k)
 static struct list_head clientTable[NICK_HASH_TABLE_SIZE];
 static struct list_head idTable[NICK_HASH_TABLE_SIZE];
 static Channel *channelTable[CHAN_HASH_TABLE_SIZE];
-static Watch *watchTable[WATCH_HASH_TABLE_SIZE];
 
 static char siphashkey_nick[SIPHASH_KEY_LENGTH];
 static char siphashkey_chan[SIPHASH_KEY_LENGTH];
-static char siphashkey_watch[SIPHASH_KEY_LENGTH];
 static char siphashkey_whowas[SIPHASH_KEY_LENGTH];
 static char siphashkey_throttling[SIPHASH_KEY_LENGTH];
 
@@ -277,7 +275,6 @@ void init_hash(void)
 
 	siphash_generate_key(siphashkey_nick);
 	siphash_generate_key(siphashkey_chan);
-	siphash_generate_key(siphashkey_watch);
 	siphash_generate_key(siphashkey_whowas);
 	siphash_generate_key(siphashkey_throttling);
 
@@ -288,7 +285,6 @@ void init_hash(void)
 		INIT_LIST_HEAD(&idTable[i]);
 
 	memset(channelTable, 0, sizeof(channelTable));
-	memset(watchTable, 0, sizeof(watchTable));
 
 	memset(ThrottlingHash, 0, sizeof(ThrottlingHash));
 	/* do not call init_throttling() here, as
@@ -310,11 +306,6 @@ uint64_t hash_channel_name(const char *name)
 	return siphash_nocase(name, siphashkey_chan) % CHAN_HASH_TABLE_SIZE;
 }
 
-uint64_t hash_watch_nick_name(const char *name)
-{
-	return siphash_nocase(name, siphashkey_watch) % WATCH_HASH_TABLE_SIZE;
-}
-
 uint64_t hash_whowas_name(const char *name)
 {
 	return siphash_nocase(name, siphashkey_whowas) % WHOWAS_HASH_TABLE_SIZE;
@@ -323,7 +314,7 @@ uint64_t hash_whowas_name(const char *name)
 /*
  * add_to_client_hash_table
  */
-int add_to_client_hash_table(char *name, Client *client)
+int add_to_client_hash_table(const char *name, Client *client)
 {
 	unsigned int hashv;
 	/*
@@ -349,7 +340,7 @@ int add_to_client_hash_table(char *name, Client *client)
 /*
  * add_to_client_hash_table
  */
-int add_to_id_hash_table(char *name, Client *client)
+int add_to_id_hash_table(const char *name, Client *client)
 {
 	unsigned int hashv;
 	hashv = hash_client_name(name);
@@ -360,7 +351,7 @@ int add_to_id_hash_table(char *name, Client *client)
 /*
  * add_to_channel_hash_table
  */
-int add_to_channel_hash_table(char *name, Channel *channel)
+int add_to_channel_hash_table(const char *name, Channel *channel)
 {
 	unsigned int hashv;
 
@@ -372,7 +363,7 @@ int add_to_channel_hash_table(char *name, Channel *channel)
 /*
  * del_from_client_hash_table
  */
-int del_from_client_hash_table(char *name, Client *client)
+int del_from_client_hash_table(const char *name, Client *client)
 {
 	if (!list_empty(&client->client_hash))
 		list_del(&client->client_hash);
@@ -382,7 +373,7 @@ int del_from_client_hash_table(char *name, Client *client)
 	return 0;
 }
 
-int del_from_id_hash_table(char *name, Client *client)
+int del_from_id_hash_table(const char *name, Client *client)
 {
 	if (!list_empty(&client->id_hash))
 		list_del(&client->id_hash);
@@ -395,7 +386,7 @@ int del_from_id_hash_table(char *name, Client *client)
 /*
  * del_from_channel_hash_table
  */
-void del_from_channel_hash_table(char *name, Channel *channel)
+void del_from_channel_hash_table(const char *name, Channel *channel)
 {
 	Channel *tmp, *prev = NULL;
 	unsigned int hashv;
@@ -465,7 +456,7 @@ Client *hash_find_nickatserver(const char *str, Client *def)
 	if (serv)
 		*serv++ = '\0';
 
-	client = find_person(nick, NULL);
+	client = find_user(nick, NULL);
 	if (!client)
 		return NULL; /* client not found */
 	
@@ -509,14 +500,14 @@ Client *hash_find_server(const char *server, Client *def)
 
 /** Find a client by name.
  * This searches in the list of all types of clients, user/person, servers or an unregistered clients.
- * If you know what type of client to search for, then use find_server() or find_person() instead!
+ * If you know what type of client to search for, then use find_server() or find_user() instead!
  * @param name        The name to search for (eg: "nick" or "irc.example.net")
  * @param requester   The client that is searching for this name
  * @note  If 'requester' is a server or NULL, then we also check
  *        the ID table, otherwise not.
  * @returns If the client is found then the Client is returned, otherwise NULL.
  */
-Client *find_client(char *name, Client *requester)
+Client *find_client(const char *name, Client *requester)
 {
 	if (requester == NULL || IsServer(requester))
 	{
@@ -537,7 +528,7 @@ Client *find_client(char *name, Client *requester)
  *        the ID table, otherwise not.
  * @returns If the server is found then the Client is returned, otherwise NULL.
  */
-Client *find_server(char *name, Client *requester)
+Client *find_server(const char *name, Client *requester)
 {
 	if (name)
 	{
@@ -550,14 +541,14 @@ Client *find_server(char *name, Client *requester)
 	return NULL;
 }
 
-/** Find a person (a user).
+/** Find a user (a person)
  * @param name        The name to search for (eg: "nick" or "001ABCDEFG")
  * @param requester   The client that is searching for this name
  * @note  If 'requester' is a server or NULL, then we also check
  *        the ID table, otherwise not.
  * @returns If the user is found then the Client is returned, otherwise NULL.
  */
-Client *find_person(char *name, Client *requester) /* TODO: this should have been called find_user() to be consistent */
+Client *find_user(const char *name, Client *requester)
 {
 	Client *c2ptr;
 
@@ -572,22 +563,20 @@ Client *find_person(char *name, Client *requester) /* TODO: this should have bee
 
 /** Find a channel by name.
  * @param name			The channel name to search for
- * @param default_result	If the channel is not found, this value is returned.
- * @returns If the channel exists then the Channel is returned, otherwise default_result is returned.
+ * @returns If the channel exists then the Channel is returned, otherwise NULL.
  */
-Channel *find_channel(char *name, Channel *default_result)
+Channel *find_channel(const char *name)
 {
 	unsigned int hashv;
-	Channel *tmp;
+	Channel *channel;
 
 	hashv = hash_channel_name(name);
 
-	for (tmp = channelTable[hashv]; tmp; tmp = tmp->hnextch)
-	{
-		if (smycmp(name, tmp->chname) == 0)
-			return tmp;
-	}
-	return default_result;
+	for (channel = channelTable[hashv]; channel; channel = channel->hnextch)
+		if (smycmp(name, channel->name) == 0)
+			return channel;
+
+	return NULL;
 }
 
 /** @} */
@@ -599,303 +588,6 @@ Channel *hash_get_chan_bucket(uint64_t hashv)
 	return channelTable[hashv];
 }
 
-void  count_watch_memory(int *count, u_long *memory)
-{
-	int i = WATCH_HASH_TABLE_SIZE;
-	Watch *anptr;
-
-	while (i--)
-	{
-		anptr = watchTable[i];
-		while (anptr)
-		{
-			(*count)++;
-			(*memory) += sizeof(Watch)+strlen(anptr->nick);
-			anptr = anptr->hnext;
-		}
-	}
-}
-
-/*
- * add_to_watch_hash_table
- */
-int add_to_watch_hash_table(char *nick, Client *client, int awaynotify)
-{
-	unsigned int hashv;
-	Watch  *anptr;
-	Link  *lp;
-	
-	
-	/* Get the right bucket... */
-	hashv = hash_watch_nick_name(nick);
-	
-	/* Find the right nick (header) in the bucket, or NULL... */
-	if ((anptr = (Watch *)watchTable[hashv]))
-	  while (anptr && mycmp(anptr->nick, nick))
-		 anptr = anptr->hnext;
-	
-	/* If found NULL (no header for this nick), make one... */
-	if (!anptr) {
-		anptr = (Watch *)safe_alloc(sizeof(Watch)+strlen(nick));
-		anptr->lasttime = timeofday;
-		strcpy(anptr->nick, nick);
-		
-		anptr->watch = NULL;
-		
-		anptr->hnext = watchTable[hashv];
-		watchTable[hashv] = anptr;
-	}
-	/* Is this client already on the watch-list? */
-	if ((lp = anptr->watch))
-	  while (lp && (lp->value.client != client))
-		 lp = lp->next;
-	
-	/* No it isn't, so add it in the bucket and client addint it */
-	if (!lp) {
-		lp = anptr->watch;
-		anptr->watch = make_link();
-		anptr->watch->value.client = client;
-		anptr->watch->flags = awaynotify;
-		anptr->watch->next = lp;
-		
-		lp = make_link();
-		lp->next = client->local->watch;
-		lp->value.wptr = anptr;
-		lp->flags = awaynotify;
-		client->local->watch = lp;
-		client->local->watches++;
-	}
-	
-	return 0;
-}
-
-/*
- *  hash_check_watch
- */
-int hash_check_watch(Client *client, int reply)
-{
-	unsigned int hashv;
-	Watch  *anptr;
-	Link  *lp;
-	int awaynotify = 0;
-	
-	if ((reply == RPL_GONEAWAY) || (reply == RPL_NOTAWAY) || (reply == RPL_REAWAY))
-		awaynotify = 1;
-
-	/* Get us the right bucket */
-	hashv = hash_watch_nick_name(client->name);
-	
-	/* Find the right header in this bucket */
-	if ((anptr = (Watch *)watchTable[hashv]))
-	  while (anptr && mycmp(anptr->nick, client->name))
-		 anptr = anptr->hnext;
-	if (!anptr)
-	  return 0;   /* This nick isn't on watch */
-	
-	/* Update the time of last change to item */
-	anptr->lasttime = TStime();
-	
-	/* Send notifies out to everybody on the list in header */
-	for (lp = anptr->watch; lp; lp = lp->next)
-	{
-		if (!awaynotify)
-		{
-			sendnumeric(lp->value.client, reply,
-			    client->name,
-			    (IsUser(client) ? client->user->username : "<N/A>"),
-			    (IsUser(client) ?
-			    (IsHidden(client) ? client->user->virthost : client->
-			    user->realhost) : "<N/A>"), anptr->lasttime, client->info);
-		}
-		else
-		{
-			/* AWAY or UNAWAY */
-			if (!lp->flags)
-				continue; /* skip away/unaway notification for users not interested in them */
-
-			if (reply == RPL_NOTAWAY)
-				sendnumeric(lp->value.client, reply,
-				    client->name,
-				    (IsUser(client) ? client->user->username : "<N/A>"),
-				    (IsUser(client) ?
-				    (IsHidden(client) ? client->user->virthost : client->
-				    user->realhost) : "<N/A>"), client->user->lastaway);
-			else /* RPL_GONEAWAY / RPL_REAWAY */
-				sendnumeric(lp->value.client, reply,
-				    client->name,
-				    (IsUser(client) ? client->user->username : "<N/A>"),
-				    (IsUser(client) ?
-				    (IsHidden(client) ? client->user->virthost : client->
-				    user->realhost) : "<N/A>"), client->user->lastaway, client->user->away);
-		}
-	}
-	
-	return 0;
-}
-
-/*
- * hash_get_watch
- */
-Watch  *hash_get_watch(char *nick)
-{
-	unsigned int hashv;
-	Watch  *anptr;
-	
-	hashv = hash_watch_nick_name(nick);
-	
-	if ((anptr = (Watch *)watchTable[hashv]))
-	  while (anptr && mycmp(anptr->nick, nick))
-		 anptr = anptr->hnext;
-	
-	return anptr;
-}
-
-/*
- * del_from_watch_hash_table
- */
-int del_from_watch_hash_table(char *nick, Client *client)
-{
-	unsigned int hashv;
-	Watch  *anptr, *nlast = NULL;
-	Link  *lp, *last = NULL;
-
-	/* Get the bucket for this nick... */
-	hashv = hash_watch_nick_name(nick);
-	
-	/* Find the right header, maintaining last-link pointer... */
-	if ((anptr = (Watch *)watchTable[hashv]))
-	  while (anptr && mycmp(anptr->nick, nick)) {
-		  nlast = anptr;
-		  anptr = anptr->hnext;
-	  }
-	if (!anptr)
-	  return 0;   /* No such watch */
-	
-	/* Find this client from the list of notifies... with last-ptr. */
-	if ((lp = anptr->watch))
-	  while (lp && (lp->value.client != client)) {
-		  last = lp;
-		  lp = lp->next;
-	  }
-	if (!lp)
-	  return 0;   /* No such client to watch */
-	
-	/* Fix the linked list under header, then remove the watch entry */
-	if (!last)
-	  anptr->watch = lp->next;
-	else
-	  last->next = lp->next;
-	free_link(lp);
-	
-	/* Do the same regarding the links in client-record... */
-	last = NULL;
-	if ((lp = client->local->watch))
-	  while (lp && (lp->value.wptr != anptr)) {
-		  last = lp;
-		  lp = lp->next;
-	  }
-	
-	/*
-	 * Give error on the odd case... probobly not even neccessary
-	 * No error checking in ircd is unneccessary ;) -Cabal95
-	 */
-	if (!lp)
-	  sendto_ops("WATCH debug error: del_from_watch_hash_table "
-					 "found a watch entry with no client "
-					 "counterpoint processing nick %s on client %p!",
-					 nick, client->user);
-	else {
-		if (!last) /* First one matched */
-		  client->local->watch = lp->next;
-		else
-		  last->next = lp->next;
-		free_link(lp);
-	}
-	/* In case this header is now empty of notices, remove it */
-	if (!anptr->watch) {
-		if (!nlast)
-		  watchTable[hashv] = anptr->hnext;
-		else
-		  nlast->hnext = anptr->hnext;
-		safe_free(anptr);
-	}
-	
-	/* Update count of notifies on nick */
-	client->local->watches--;
-	
-	return 0;
-}
-
-/*
- * hash_del_watch_list
- */
-int   hash_del_watch_list(Client *client)
-{
-	unsigned int   hashv;
-	Watch  *anptr;
-	Link  *np, *lp, *last;
-	
-	
-	if (!(np = client->local->watch))
-	  return 0;   /* Nothing to do */
-	
-	client->local->watch = NULL; /* Break the watch-list for client */
-	while (np) {
-		/* Find the watch-record from hash-table... */
-		anptr = np->value.wptr;
-		last = NULL;
-		for (lp = anptr->watch; lp && (lp->value.client != client);
-			  lp = lp->next)
-		  last = lp;
-		
-		/* Not found, another "worst case" debug error */
-		if (!lp)
-		  sendto_ops("WATCH Debug error: hash_del_watch_list "
-						 "found a WATCH entry with no table "
-						 "counterpoint processing client %s!",
-						 client->name);
-		else {
-			/* Fix the watch-list and remove entry */
-			if (!last)
-			  anptr->watch = lp->next;
-			else
-			  last->next = lp->next;
-			free_link(lp);
-			
-			/*
-			 * If this leaves a header without notifies,
-			 * remove it. Need to find the last-pointer!
-			 */
-			if (!anptr->watch) {
-				Watch  *np2, *nl;
-				
-				hashv = hash_watch_nick_name(anptr->nick);
-				
-				nl = NULL;
-				np2 = watchTable[hashv];
-				while (np2 != anptr) {
-					nl = np2;
-					np2 = np2->hnext;
-				}
-				
-				if (nl)
-				  nl->hnext = anptr->hnext;
-				else
-				  watchTable[hashv] = anptr->hnext;
-				safe_free(anptr);
-			}
-		}
-		
-		lp = np; /* Save last pointer processed */
-		np = np->next; /* Jump to the next pointer */
-		free_link(lp); /* Free the previous */
-	}
-	
-	client->local->watches = 0;
-	
-	return 0;
-}
-
 /* Throttling - originally by Stskeeps */
 
 /* Note that we call this set::anti-flood::connect-flood nowadays */
@@ -925,15 +617,7 @@ void update_throttling_timer_settings(void)
 	EventMod(EventFind("throttling_check_expire"), &eInfo);
 }
 
-void init_throttling()
-{
-	EventAdd(NULL, "throttling_check_expire", throttling_check_expire, NULL, 123456, 0);
-	/* Note: the every_ms value (123,456) will be adjusted on boot and rehash
-	 * via the update_throttling_timer_settings() function.
-	 */
-}
-
-uint64_t hash_throttling(char *ip)
+uint64_t hash_throttling(const char *ip)
 {
 	return siphash(ip, siphashkey_throttling) % THROTTLING_HASH_TABLE_SIZE;
 }
diff --git a/src/ircd.c b/src/ircd.c
@@ -34,7 +34,6 @@ time_t timeofday = 0;
 struct timeval timeofday_tv;
 int  tainted = 0;
 LoopStruct loop;
-MODVAR MemoryInfo StatsZ;
 #ifndef _WIN32
 uid_t irc_uid = 0;
 gid_t irc_gid = 0;
@@ -51,12 +50,8 @@ extern SERVICE_STATUS IRCDStatus;
 
 MODVAR unsigned char conf_debuglevel = 0;
 
-#ifdef USE_LIBCURL
-extern void url_init(void);
-#endif
-
-void server_reboot(char *);
-void restart(char *);
+void server_reboot(const char *);
+void restart(const char *);
 static void open_debugfile(), setup_signals();
 extern void init_glines(void);
 extern void tkl_init(void);
@@ -82,7 +77,7 @@ void s_die()
 	Client *client;
 	if (!IsService)
 	{
-		loop.ircd_terminating = 1;
+		loop.terminating = 1;
 		unload_all_modules();
 
 		list_for_each_entry(client, &lclient_list, lclient_node)
@@ -97,7 +92,7 @@ void s_die()
 		ControlService(hService, SERVICE_CONTROL_STOP, &status);
 	}
 #else
-	loop.ircd_terminating = 1;
+	loop.terminating = 1;
 	unload_all_modules();
 	unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE);
 	exit(0);
@@ -128,7 +123,7 @@ static void s_reloadcert()
 }
 #endif // #ifndef _WIN32
 
-void restart(char *mesg)
+void restart(const char *mesg)
 {
 	server_reboot(mesg);
 }
@@ -174,12 +169,13 @@ void ignore_this_signal()
 #endif /* #ifndef _WIN32 */
 
 
-void server_reboot(char *mesg)
+void server_reboot(const char *mesg)
 {
 	int i;
 	Client *client;
-	sendto_realops("Aieeeee!!!  Restarting server... %s", mesg);
-	Debug((DEBUG_NOTICE, "Restarting server... %s", mesg));
+	unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL,
+	           "Restarting server: $reason",
+	           log_data_string("reason", mesg));
 
 	list_for_each_entry(client, &lclient_list, lclient_node)
 		(void) send_queued(client);
@@ -207,12 +203,6 @@ void server_reboot(char *mesg)
 		WinExec(cmdLine, SW_SHOWDEFAULT);
 	}
 #endif
-#ifndef _WIN32
-	Debug((DEBUG_FATAL, "Couldn't restart server: %s", strerror(errno)));
-#else
-	Debug((DEBUG_FATAL, "Couldn't restart server: %s",
-	    strerror(GetLastError())));
-#endif
 	unload_all_modules();
 #ifdef _WIN32
 	if (IsService)
@@ -254,7 +244,7 @@ EVENT(garbage_collect)
 	int  ii;
 
 	if (loop.do_garbage_collect == 1)
-		sendto_realops("Doing garbage collection ..");
+		unreal_log(ULOG_INFO, "main", "GARBAGE_COLLECT_STARTED", NULL, "Doing garbage collection...");
 	if (freelinks > HOW_MANY_FREELINKS_ALLOWED) {
 		ii = freelinks;
 		while (freelink && (freelinks > HOW_MANY_FREELINKS_ALLOWED)) {
@@ -265,65 +255,20 @@ EVENT(garbage_collect)
 		}
 		if (loop.do_garbage_collect == 1) {
 			loop.do_garbage_collect = 0;
-			sendto_realops
-			    ("Cleaned up %i garbage blocks", (ii - freelinks));
+			unreal_log(ULOG_INFO, "main", "GARBAGE_COLLECT_STARTED", NULL, "Cleaned up $count garbage blocks",
+			           (ii - freelinks));
 		}
 	}
 	if (loop.do_garbage_collect == 1)
 		loop.do_garbage_collect = 0;
 }
 
-/** Perform autoconnect to servers that are not linked yet. */
-EVENT(try_connections)
-{
-	ConfigItem_link *aconf;
-	ConfigItem_deny_link *deny;
-	Client *client;
-	int  confrq;
-	ConfigItem_class *class;
-
-	for (aconf = conf_link; aconf; aconf = aconf->next)
-	{
-		/* We're only interested in autoconnect blocks that are valid. Also, we ignore temporary link blocks. */
-		if (!(aconf->outgoing.options & CONNECT_AUTO) || !aconf->outgoing.hostname || (aconf->flag.temporary == 1))
-			continue;
-
-		class = aconf->class;
-
-		/* Only do one connection attempt per <connfreq> seconds (for the same server) */
-		if ((aconf->hold > TStime()))
-			continue;
-
-		confrq = class->connfreq;
-		aconf->hold = TStime() + confrq;
-
-		client = find_client(aconf->servername, NULL);
-		if (client)
-			continue; /* Server already connected (or connecting) */
-
-		if (class->clients >= class->maxclients)
-			continue; /* 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 (match_simple(deny->mask, aconf->servername) && crule_eval(deny->rule))
-				break;
-
-		if (!deny && connect_server(aconf, NULL, NULL) == 0)
-			sendto_ops_and_log("Trying to activate link with server %s[%s]...",
-				aconf->servername, aconf->outgoing.hostname);
-
-	}
-}
-
 /** Does this user match any TKL's? */
 int match_tkls(Client *client)
 {
 	ConfigItem_ban *bconf = NULL;
 	char banbuf[1024];
 
-	char killflag = 0;
-
 	/* Process dynamic *LINES */
 	if (find_tkline_match(client, 0))
 		return 1; /* user killed */
@@ -334,35 +279,25 @@ int match_tkls(Client *client)
 	{
 		/* Check ban realname { } */
 		if (!ValidatePermissionsForPath("immune",client,NULL,NULL,NULL) && (bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME)))
-			killflag++;
-	}
-
-	/* If user is meant to be killed, take action: */
-	if (killflag)
-	{
-		if (IsUser(client))
-			sendto_realops("Ban active for %s (%s)",
-				get_client_name(client, FALSE),
-				bconf->reason ? bconf->reason : "no reason");
-
-		if (IsServer(client))
-			sendto_realops("Ban active for server %s (%s)",
-				get_client_name(client, FALSE),
-				bconf->reason ? bconf->reason : "no reason");
-
-		if (bconf->reason) {
-			if (IsUser(client))
-				snprintf(banbuf, sizeof(banbuf), "User has been banned (%s)", bconf->reason);
-			else
-				snprintf(banbuf, sizeof(banbuf), "Banned (%s)", bconf->reason);
-			exit_client(client, NULL, banbuf);
-		} else {
-			if (IsUser(client))
-				exit_client(client, NULL, "User has been banned");
-			else
-				exit_client(client, NULL, "Banned");
+		{
+			unreal_log(ULOG_INFO, "tkl", "BAN_REALNAME", client,
+			           "Banned client $client.details due to realname ban: $reason",
+			           bconf->reason ? bconf->reason : "no reason");
+
+			if (bconf->reason) {
+				if (IsUser(client))
+					snprintf(banbuf, sizeof(banbuf), "User has been banned (%s)", bconf->reason);
+				else
+					snprintf(banbuf, sizeof(banbuf), "Banned (%s)", bconf->reason);
+				exit_client(client, NULL, banbuf);
+			} else {
+				if (IsUser(client))
+					exit_client(client, NULL, "User has been banned");
+				else
+					exit_client(client, NULL, "Banned");
+			}
+			return 1; /* stop processing, client is dead now */
 		}
-		return 1; /* stop processing this user, as (s)he is dead now. */
 	}
 
 	if (loop.do_bancheck_spamf_user && IsUser(client) && find_spamfilter_user(client, SPAMFLAG_NOWARN))
@@ -385,14 +320,10 @@ EVENT(handshake_timeout)
 
 	list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
 	{
-		if (client->local->firsttime && ((TStime() - client->local->firsttime) > iConf.handshake_timeout))
+		if (client->local->creationtime && ((TStime() - client->local->creationtime) > iConf.handshake_timeout))
 		{
-			if (client->serv && *client->serv->by)
-			{
-				/* If this is a handshake timeout to an outgoing server then notify ops & log it */
-				sendto_ops_and_log("Connection handshake timeout while trying to link to server '%s' (%s)",
-					client->name, client->ip?client->ip:"<unknown ip>");
-			}
+			if (client->server && *client->server->by)
+				continue; /* handled by server module */
 
 			exit_client(client, NULL, "Registration Timeout");
 			continue;
@@ -407,37 +338,31 @@ void check_ping(Client *client)
 	int ping = 0;
 
 	ping = client->local->class ? client->local->class->pingfreq : iConf.handshake_timeout;
-	Debug((DEBUG_DEBUG, "c(%s)=%d p %d a %lld", client->name,
-		client->status, ping,
-		(long long)(TStime() - client->local->lasttime)));
 
 	/* If ping is less than or equal to the last time we received a command from them */
-	if (ping > (TStime() - client->local->lasttime))
+	if (ping > (TStime() - client->local->last_msg_received))
 		return; /* some recent command was executed */
 
 	if (
 		/* If we have sent a ping */
 		(IsPingSent(client)
 		/* And they had 2x ping frequency to respond */
-		&& ((TStime() - client->local->lasttime) >= (2 * ping)))
+		&& ((TStime() - client->local->last_msg_received) >= (2 * ping)))
 		||
 		/* Or isn't registered and time spent is larger than ping (CONNECTTIMEOUT).. */
-		(!IsRegistered(client) && (TStime() - client->local->since >= ping))
+		(!IsRegistered(client) && (TStime() - client->local->fake_lag >= ping))
 		)
 	{
 		if (IsServer(client) || IsConnecting(client) ||
 		    IsHandshake(client) || IsTLSConnectHandshake(client))
 		{
-			sendto_umode_global(UMODE_OPER, "No response from %s, closing link",
-			                    get_client_name(client, FALSE));
-			ircd_log(LOG_ERROR, "No response from %s, closing link",
-			         get_client_name(client, FALSE));
+			unreal_log(ULOG_ERROR, "link", "LINK_DISCONNECTED", client,
+			           "Lost server link to $client [$client.ip]: No response (Ping timeout)",
+			           client->server->conf ? log_data_link_block(client->server->conf) : NULL);
+			SetServerDisconnectLogged(client);
 		}
-		if (IsTLSAcceptHandshake(client))
-			Debug((DEBUG_DEBUG, "ssl accept handshake timeout: %s (%lld-%lld > %lld)", client->local->sockhost,
-				(long long)TStime(), (long long)client->local->since, (long long)ping));
 		ircsnprintf(scratch, sizeof(scratch), "Ping timeout: %lld seconds",
-			(long long) (TStime() - client->local->lasttime));
+			(long long) (TStime() - client->local->last_msg_received));
 		exit_client(client, NULL, scratch);
 		return;
 	}
@@ -447,17 +372,19 @@ void check_ping(Client *client)
 		SetPingSent(client);
 		ClearPingWarning(client);
 		/* not nice but does the job */
-		client->local->lasttime = TStime() - ping;
+		client->local->last_msg_received = TStime() - ping;
 		sendto_one(client, NULL, "PING :%s", me.name);
 	}
 	else if (!IsPingWarning(client) && PINGWARNING > 0 &&
 		(IsServer(client) || IsHandshake(client) || IsConnecting(client) ||
 		IsTLSConnectHandshake(client)) &&
-		(TStime() - client->local->lasttime) >= (ping + PINGWARNING))
+		(TStime() - client->local->last_msg_received) >= (ping + PINGWARNING))
 	{
 		SetPingWarning(client);
-		sendto_realops("Warning, no response from %s for %d seconds",
-			get_client_name(client, FALSE), PINGWARNING);
+		unreal_log(ULOG_WARNING, "link", "LINK_UNRELIABLE", client,
+			   "Warning, no response from $client for $time_delta seconds",
+			   log_data_integer("time_delta", PINGWARNING),
+			   client->server->conf ? log_data_link_block(client->server->conf) : NULL);
 	}
 
 	return;
@@ -496,9 +423,6 @@ EVENT(check_deadsockets)
 		/* No need to notify opers here. It's already done when dead socket is set */
 		if (IsDeadSocket(client))
 		{
-#ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "Closing deadsock: %d/%s", client->local->fd, client->name);
-#endif
 			ClearDeadSocket(client); /* CPR. So we send the error. */
 			exit_client(client, NULL, client->local->error_str ? client->local->error_str : "Dead socket");
 			continue;
@@ -510,9 +434,6 @@ EVENT(check_deadsockets)
 		/* No need to notify opers here. It's already done when dead socket is set */
 		if (IsDeadSocket(client))
 		{
-#ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "Closing deadsock: %d/%s", client->local->fd, client->name);
-#endif
 			ClearDeadSocket(client); /* CPR. So we send the error. */
 			exit_client(client, NULL, client->local->error_str ? client->local->error_str : "Dead socket");
 			continue;
@@ -562,21 +483,6 @@ char chess[] = {
 	85, 110, 114, 101, 97, 108, 0
 };
 
-static void version_check_logerror(char *fmt, ...)
-{
-va_list va;
-char buf[1024];
-
-	va_start(va, fmt);
-	vsnprintf(buf, sizeof(buf), fmt, va);
-	va_end(va);
-#ifndef _WIN32
-	fprintf(stderr, "[!!!] %s\n", buf);
-#else
-	win_log("[!!!] %s", buf);
-#endif
-}
-
 extern void applymeblock(void);
 
 extern MODVAR Event *events;
@@ -595,41 +501,20 @@ void fix_timers(void)
 
 	list_for_each_entry(client, &lclient_list, lclient_node)
 	{
-		if (client->local->since > TStime())
-		{
-			Debug((DEBUG_DEBUG, "fix_timers(): %s: client->local->since %ld -> %ld",
-				client->name, client->local->since, TStime()));
-			client->local->since = TStime();
-		}
-		if (client->local->lasttime > TStime())
-		{
-			Debug((DEBUG_DEBUG, "fix_timers(): %s: client->local->lasttime %ld -> %ld",
-				client->name, client->local->lasttime, TStime()));
-			client->local->lasttime = TStime();
-		}
-		if (client->local->last > TStime())
-		{
-			Debug((DEBUG_DEBUG, "fix_timers(): %s: client->local->last %ld -> %ld",
-				client->name, client->local->last, TStime()));
-			client->local->last = TStime();
-		}
+		if (client->local->fake_lag > TStime())
+			client->local->fake_lag = TStime();
+		if (client->local->last_msg_received > TStime())
+			client->local->last_msg_received = TStime();
+		if (client->local->idle_since > TStime())
+			client->local->idle_since = TStime();
 
 		/* users */
 		if (MyUser(client))
 		{
-			if (client->local->nextnick > TStime())
-			{
-				Debug((DEBUG_DEBUG, "fix_timers(): %s: client->local->nextnick %ld -> %ld",
-					client->name, client->local->nextnick, TStime()));
-				client->local->nextnick = TStime();
-			}
+			if (client->local->next_nick_allowed > TStime())
+				client->local->next_nick_allowed = TStime();
 			if (client->local->nexttarget > TStime())
-			{
-				Debug((DEBUG_DEBUG, "fix_timers(): %s: client->local->nexttarget %ld -> %ld",
-					client->name, client->local->nexttarget, TStime()));
 				client->local->nexttarget = TStime();
-			}
-
 		}
 	}
 
@@ -658,7 +543,6 @@ void fix_timers(void)
 				thr->since = TStime();
 		}
 	}
-	Debug((DEBUG_DEBUG, "fix_timers(): removed %d throttling item(s)", cnt));
 
 	/* Make sure autoconnect for servers still works (lnk->hold) */
 	for (lnk = conf_link; lnk; lnk = lnk->next)
@@ -668,37 +552,34 @@ void fix_timers(void)
 		if (lnk->hold > TStime() + t)
 		{
 			lnk->hold = TStime() + (t / 2); /* compromise */
-			Debug((DEBUG_DEBUG, "fix_timers(): link '%s' hold-time adjusted to %ld", lnk->servername, lnk->hold));
 		}
 	}
 }
 
 
 #ifndef _WIN32
+/* Generate 3 cloak keys and print to console */
 static void generate_cloakkeys()
 {
-	/* Generate 3 cloak keys */
-#define GENERATE_CLOAKKEY_MINLEN 50
-#define GENERATE_CLOAKKEY_MAXLEN 60 /* Length of cloak keys to generate. */
-	char keyBuf[GENERATE_CLOAKKEY_MAXLEN + 1];
+	#define GENERATE_CLOAKKEY_LEN 80 /* Length of cloak keys to generate. */
+	char keyBuf[GENERATE_CLOAKKEY_LEN + 1];
 	int keyNum;
-	int keyLen;
 	int charIndex;
 
 	short has_upper;
 	short has_lower;
 	short has_num;
 
-	fprintf(stderr, "Here are 3 random cloak keys:\n");
+	fprintf(stderr, "Here are 3 random cloak keys that you can copy-paste to your configuration file:\n\n");
 
+	fprintf(stderr, "set {\n\tcloak-keys {\n");
 	for (keyNum = 0; keyNum < 3; ++keyNum)
 	{
 		has_upper = 0;
 		has_lower = 0;
 		has_num = 0;
 
-		keyLen = (getrandom8() % (GENERATE_CLOAKKEY_MAXLEN - GENERATE_CLOAKKEY_MINLEN + 1)) + GENERATE_CLOAKKEY_MINLEN;
-		for (charIndex = 0; charIndex < keyLen; ++charIndex)
+		for (charIndex = 0; charIndex < sizeof(keyBuf)-1; ++charIndex)
 		{
 			switch (getrandom8() % 3)
 			{
@@ -716,14 +597,15 @@ static void generate_cloakkeys()
 					break;
 			}
 		}
-		keyBuf[keyLen] = '\0';
+		keyBuf[sizeof(keyBuf)-1] = '\0';
 
 		if (has_upper && has_lower && has_num)
-			(void)fprintf(stderr, "%s\n", keyBuf);
+			fprintf(stderr, "\t\t\"%s\";\n", keyBuf);
 		else
 			/* Try again. For this reason, keyNum must be signed. */
 			keyNum--;
 	}
+	fprintf(stderr, "\t}\n}\n\n");
 }
 #endif
 
@@ -743,38 +625,35 @@ void detect_timeshift_and_warn(void)
 	if (oldtimeofday == 0)
 		oldtimeofday = timeofday; /* pretend everything is ok the first time.. */
 
-	if (mytdiff(timeofday, oldtimeofday) < NEGATIVE_SHIFT_WARN) {
+	if (mytdiff(timeofday, oldtimeofday) < NEGATIVE_SHIFT_WARN)
+	{
 		/* tdiff = # of seconds of time set backwards (positive number! eg: 60) */
 		time_t tdiff = oldtimeofday - timeofday;
-		ircd_log(LOG_ERROR, "WARNING: Time running backwards! Clock set back ~%lld seconds (%lld -> %lld)",
-			(long long)tdiff, (long long)oldtimeofday, (long long)timeofday);
-		ircd_log(LOG_ERROR, "[TimeShift] Resetting a few timers to prevent IRCd freeze!");
-		sendto_realops("WARNING: Time running backwards! Clock set back ~%lld seconds (%lld -> %lld)",
-			(long long)tdiff, (long long)oldtimeofday, (long long)timeofday);
-		sendto_realops("Incorrect time for IRC servers is a serious problem. "
-			       "Time being set backwards (system clock changed) is "
-			       "even more serious and can cause clients to freeze, channels to be "
-			       "taken over, and other issues.");
-		sendto_realops("Please be sure your clock is always synchronized before "
-			       "the IRCd is started!");
-		sendto_realops("[TimeShift] Resetting a few timers to prevent IRCd freeze!");
+		unreal_log(ULOG_WARNING, "system", "SYSTEM_CLOCK_JUMP_BACKWARDS", NULL,
+		           "System clock jumped back in time ~$time_delta seconds ($time_from -> $time_to)\n"
+		           "Incorrect time for IRC servers is a serious problem. "
+		           "Time being set backwards (system clock changed) is "
+		           "even more serious and can cause clients to freeze, channels to be "
+		           "taken over, and other issues.\n"
+		           "Please be sure your clock is always synchronized before the IRCd is started!",
+		           log_data_integer("time_delta", tdiff),
+		           log_data_timestamp("time_from", oldtimeofday),
+		           log_data_timestamp("time_to", timeofday));
 		fix_timers();
 	} else
 	if (mytdiff(timeofday, oldtimeofday) > POSITIVE_SHIFT_WARN) /* do not set too low or you get false positives */
 	{
 		/* tdiff = # of seconds of time set forward (eg: 60) */
 		time_t tdiff = timeofday - oldtimeofday;
-		ircd_log(LOG_ERROR, "WARNING: Time jumped ~%lld seconds ahead! (%lld -> %lld)",
-			(long long)tdiff, (long long)oldtimeofday, (long long)timeofday);
-		ircd_log(LOG_ERROR, "[TimeShift] Resetting some timers!");
-		sendto_realops("WARNING: Time jumped ~%lld seconds ahead! (%lld -> %lld)",
-			(long long)tdiff, (long long)oldtimeofday, (long long)timeofday);
-		sendto_realops("Incorrect time for IRC servers is a serious problem. "
-			       "Time being adjusted (by changing the system clock) "
-			       "more than a few seconds forward/backward can lead to serious issues.");
-		sendto_realops("Please be sure your clock is always synchronized before "
-			       "the IRCd is started!");
-		sendto_realops("[TimeShift] Resetting some timers!");
+		unreal_log(ULOG_WARNING, "system", "SYSTEM_CLOCK_JUMP_FORWARDS", NULL,
+		           "System clock jumped ~$time_delta seconds forward ($time_from -> $time_to)\n"
+		           "Incorrect time for IRC servers is a serious problem. "
+		           "Time being adjusted (by changing the system clock) "
+		           "more than a few seconds forward/backward can lead to serious issues.\n"
+		           "Please be sure your clock is always synchronized before the IRCd is started!",
+		           log_data_integer("time_delta", tdiff),
+		           log_data_timestamp("time_from", oldtimeofday),
+		           log_data_timestamp("time_to", timeofday));
 		fix_timers();
 	}
 
@@ -784,13 +663,11 @@ void detect_timeshift_and_warn(void)
 			lasthighwarn = timeofday;
 		if (timeofday - lasthighwarn > 300)
 		{
-			ircd_log(LOG_ERROR, "[TimeShift] The (IRCd) clock was set backwards. "
-				"Waiting for time to be OK again. This will be in %lld seconds",
-				(long long)(highesttimeofday - timeofday));
-			sendto_realops("[TimeShift] The (IRCd) clock was set backwards. Timers, nick- "
-				       "and channel-timestamps are possibly incorrect. This message will "
-				       "repeat itself until we catch up with the original time, which will be "
-				       "in %lld seconds", (long long)(highesttimeofday - timeofday));
+			unreal_log(ULOG_WARNING, "system", "SYSTEM_CLOCK_JUMP_BACKWARDS_PREVIOUSLY", NULL,
+				   "The system clock previously went backwards. Waiting for time to be OK again. This will be in $time_delta seconds.",
+				   log_data_integer("time_delta", highesttimeofday - timeofday),
+				   log_data_timestamp("time_from", highesttimeofday),
+				   log_data_timestamp("time_to", timeofday));
 			lasthighwarn = timeofday;
 		}
 	} else {
@@ -909,7 +786,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 #else
 	WSAStartup(wVersionRequested, &wsaData);
 #endif
-	memset(&StatsZ, 0, sizeof(StatsZ));
 	setup_signals();
 
 	memset(&irccounts, '\0', sizeof(irccounts));
@@ -918,10 +794,10 @@ int InitUnrealIRCd(int argc, char *argv[])
 	mp_pool_init();
 	dbuf_init();
 	initlists();
+	initlist_channels();
 
-#ifdef USE_LIBCURL
+	early_init_tls();
 	url_init();
-#endif
 	tkl_init();
 	umode_init();
 	extcmode_init();
@@ -967,7 +843,7 @@ int InitUnrealIRCd(int argc, char *argv[])
 #ifndef _WIN32
 		  case 'P':{
 			  short type;
-			  char *result;
+			  const char *result;
 			  srandom(TStime());
 			  type = Auth_FindType(NULL, p);
 			  if (type == -1)
@@ -1001,11 +877,10 @@ int InitUnrealIRCd(int argc, char *argv[])
 			  exit(0);
 		  }
 #endif
-#if 1
+#if 0
 		case 'S':
-			//charsys_dump_table(p ? p : "*");
-			unrealdb_test();
-			exit(0);
+			charsys_dump_table(p ? p : "*");
+			//unrealdb_test();
 #endif
 #ifndef _WIN32
 		  case 't':
@@ -1065,14 +940,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 			  	fprintf(stderr, "It is impossible to get here\n");
 			  	exit(0);
 			  }
-		  case 'U':
-		      if (chdir(CONFDIR) < 0)
-	{
-		      	fprintf(stderr, "Unable to change to '%s' directory\n", CONFDIR);
-		      	exit(1);
-		      }
-		      update_conf();
-		      exit(0);
 		  case 'R':
 		      report_crash();
 		      exit(0);
@@ -1142,7 +1009,8 @@ int InitUnrealIRCd(int argc, char *argv[])
 #ifndef _WIN32
 	fprintf(stderr, "%s", unreallogo);
 	fprintf(stderr, "                           v%s\n\n", VERSIONONLY);
-	fprintf(stderr, "UnrealIRCd is brought to you by Bram Matthys (Syzop), Gottem and i\n\n");
+	fprintf(stderr, "UnrealIRCd is brought to you by Bram Matthys (Syzop),\n"
+	                "Krzysztof Beresztant (k4be), Gottem and i\n\n");
 
 	fprintf(stderr, "Using the following libraries:\n");
 	fprintf(stderr, "* %s\n", SSLeay_version(SSLEAY_VERSION));
@@ -1170,7 +1038,7 @@ int InitUnrealIRCd(int argc, char *argv[])
 	(void)chmod(CPATH, DEFAULT_PERMISSIONS);
 #endif
 	init_dynconf();
-	early_init_ssl();
+	init_sys();
 	/*
 	 * Add default class
 	 */
@@ -1181,10 +1049,17 @@ int InitUnrealIRCd(int argc, char *argv[])
 	default_class->sendq = DEFAULT_RECVQ;
 	default_class->name = "default";
 	AddListItem(default_class, conf_class);
-	if (init_conf(configfile, 0) < 0)
-	{
+	if (config_read_start() < 0)
 		exit(-1);
+	while (!is_config_read_finished())
+	{
+		gettimeofday(&timeofday_tv, NULL);
+		timeofday = timeofday_tv.tv_sec;
+		url_socket_timeout(NULL);
+		fd_select(500);
 	}
+	if (config_test() < 0)
+		exit(-1);
 	booted = TRUE;
 	load_tunefile();
 	make_umodestr();
@@ -1192,7 +1067,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 	me.local->fd = -1;
 	SetMe(&me);
 	make_server(&me);
-	extcmodes_check_for_changes();
 	umodes_check_for_changes();
 	charsys_check_for_changes();
 	clicap_init();
@@ -1202,37 +1076,30 @@ int InitUnrealIRCd(int argc, char *argv[])
 		exit(-4);
 	}
 
-#ifndef _WIN32
-	fprintf(stderr, "Initializing TLS..\n");
-#endif
-	if (!init_ssl())
+	if (!init_tls())
 	{
-		config_error("Failed to load SSL/TLS (see errors above). UnrealIRCd can not start.");
+		config_error("Failed to load TLS (see errors above). UnrealIRCd can not start.");
 #ifdef _WIN32
 		win_error(); /* display error dialog box */
 #endif
 		exit(9);
 	}
+	unreal_log(ULOG_INFO, "config", "CONFIG_PASSED", NULL, "Configuration test passed OK");
 	if (loop.config_test)
 	{
-		ircd_log(LOG_ERROR, "Configuration test passed OK");
 		fflush(stderr);
 		exit(0);
 	}
 	if (loop.boot_function)
 		loop.boot_function();
-#ifndef _WIN32
-	fprintf(stderr, "Dynamic configuration initialized.. booting IRCd.\n");
-#endif
 	open_debugfile();
 	me.local->port = 6667; /* pointless? */
-	init_sys();
 	applymeblock();
 #ifdef HAVE_SYSLOG
 	openlog("ircd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
 #endif
-	run_configuration();
-	ircd_log(LOG_ERROR, "UnrealIRCd started.");
+	config_run();
+	unreal_log(ULOG_INFO, "main", "UNREALIRCD_START", NULL, "UnrealIRCd started.");
 
 	read_motd(conf_files->botmotd_file, &botmotd);
 	read_motd(conf_files->rules_file, &rules);
@@ -1250,11 +1117,10 @@ int InitUnrealIRCd(int argc, char *argv[])
 	 * This listener will never go away
 	 */
 	me_hash = find_or_add(me.name);
-	me.serv->up = me_hash;
 	timeofday = time(NULL);
-	me.local->lasttime = me.local->since = me.local->firsttime = me.serv->boottime = TStime();
-	me.serv->features.protocol = UnrealProtocol;
-	safe_strdup(me.serv->features.software, version);
+	me.local->last_msg_received = me.local->fake_lag = me.local->creationtime = me.server->boottime = TStime();
+	me.server->features.protocol = UnrealProtocol;
+	safe_strdup(me.server->features.software, version);
 	add_to_client_hash_table(me.name, &me);
 	add_to_id_hash_table(me.id, &me);
 	list_add(&me.client_node, &global_server_list);
@@ -1277,18 +1143,16 @@ int InitUnrealIRCd(int argc, char *argv[])
 		/* Background process (child) continues below... */
 		close_std_descriptors();
 		fd_fork();
-		loop.ircd_forked = 1;
+		loop.forked = 1;
 	}
 #endif
 #ifdef _WIN32
-	loop.ircd_forked = 1;
+	loop.forked = 1;
 #endif
 
 	fix_timers();
 	write_pidfile();
-	Debug((DEBUG_NOTICE, "Server ready..."));
-	init_throttling();
-	loop.ircd_booted = 1;
+	loop.booted = 1;
 #if defined(HAVE_SETPROCTITLE)
 	setproctitle("%s", me.name);
 #elif defined(HAVE_PSTAT)
@@ -1346,7 +1210,7 @@ void SocketLoop(void *dummy)
 		 */
 		if (dorehash)
 		{
-			(void)rehash(&me, 1);
+			request_rehash(NULL);
 			dorehash = 0;
 		}
 		if (dorestart)
@@ -1355,7 +1219,8 @@ void SocketLoop(void *dummy)
 		}
 		if (doreloadcert)
 		{
-			reinit_ssl(NULL);
+			unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", NULL, "Reloading all TLS related data (./unrealircd reloadtls)");
+			reinit_tls();
 			doreloadcert = 0;
 		}
 	}
@@ -1408,9 +1273,6 @@ static void open_debugfile(void)
 		else
 # endif
 			strlcpy(client->name, "FD2-Pipe", sizeof(client->name));
-		Debug((DEBUG_FATAL,
-		    "Debug: File <%s> Level: %d at %s", client->name,
-		    client->local->port, myctime(time(NULL))));
 	}
 #endif
 }
diff --git a/src/list.c b/src/list.c
@@ -104,14 +104,14 @@ Client *make_client(Client *from, Client *servr)
 
 	/* Note: all fields are already NULL/0, no need to set here */
 	client->direction = from ? from : client;	/* 'from' of local client is self! */
-	client->srvptr = servr;
+	client->uplink = servr;
 	client->status = CLIENT_STATUS_UNKNOWN;
 
 	INIT_LIST_HEAD(&client->client_node);
 	INIT_LIST_HEAD(&client->client_hash);
 	INIT_LIST_HEAD(&client->id_hash);
 
-	strcpy(client->ident, "unknown");
+	strlcpy(client->ident, "unknown", sizeof(client->ident));
 	if (!from)
 	{
 		/* Local client */
@@ -123,9 +123,9 @@ Client *make_client(Client *from, Client *servr)
 		INIT_LIST_HEAD(&client->lclient_node);
 		INIT_LIST_HEAD(&client->special_node);
 
-		client->local->since = client->local->lasttime =
-		client->lastnick = client->local->firsttime =
-		client->local->last = TStime();
+		client->local->fake_lag = client->local->last_msg_received =
+		client->lastnick = client->local->creationtime =
+		client->local->idle_since = TStime();
 		client->local->class = NULL;
 		client->local->passwd = NULL;
 		client->local->sockhost[0] = '\0';
@@ -197,7 +197,7 @@ User *make_user(Client *client)
 #ifdef	DEBUGMODE
 		users.inuse++;
 #endif
-		strlcpy(user->svid, "0", sizeof(user->svid));
+		strlcpy(user->account, "0", sizeof(user->account));
 		if (client->ip)
 		{
 			/* initially set client->user->realhost to IP */
@@ -213,7 +213,7 @@ User *make_user(Client *client)
 
 Server *make_server(Client *client)
 {
-	Server *serv = client->serv;
+	Server *serv = client->server;
 
 	if (!serv)
 	{
@@ -223,8 +223,7 @@ Server *make_server(Client *client)
 #endif
 		*serv->by = '\0';
 		serv->users = 0;
-		serv->up = NULL;
-		client->serv = serv;
+		client->server = serv;
 	}
 	if (strlen(client->id) > 3)
 	{
@@ -234,7 +233,7 @@ Server *make_server(Client *client)
 		del_from_id_hash_table(client->id, client);
 		*client->id = '\0';
 	}
-	return client->serv;
+	return client->server;
 }
 
 /*
@@ -260,6 +259,7 @@ void free_user(Client *client)
 	}
 	safe_free(client->user->virthost);
 	safe_free(client->user->operlogin);
+	safe_free(client->user->snomask);
 	mp_pool_release(client->user);
 #ifdef	DEBUGMODE
 	users.inuse--;
@@ -297,8 +297,8 @@ void remove_client_from_list(Client *client)
 			VERIFY_OPERCOUNT(client, "rmvlist");
 		}
 		irccounts.clients--;
-		if (client->srvptr && client->srvptr->serv)
-			client->srvptr->serv->users--;
+		if (client->uplink && client->uplink->server)
+			client->uplink->server->users--;
 	}
 	if (IsUnknown(client) || IsConnecting(client) || IsHandshake(client)
 		|| IsTLSHandshake(client)
@@ -313,16 +313,16 @@ void remove_client_from_list(Client *client)
 	
 	if (client->user)
 		free_user(client);
-	if (client->serv)
+	if (client->server)
 	{
-		safe_free(client->serv->features.usermodes);
-		safe_free(client->serv->features.chanmodes[0]);
-		safe_free(client->serv->features.chanmodes[1]);
-		safe_free(client->serv->features.chanmodes[2]);
-		safe_free(client->serv->features.chanmodes[3]);
-		safe_free(client->serv->features.software);
-		safe_free(client->serv->features.nickchars);
-		safe_free(client->serv);
+		safe_free(client->server->features.usermodes);
+		safe_free(client->server->features.chanmodes[0]);
+		safe_free(client->server->features.chanmodes[1]);
+		safe_free(client->server->features.chanmodes[2]);
+		safe_free(client->server->features.chanmodes[3]);
+		safe_free(client->server->features.software);
+		safe_free(client->server->features.nickchars);
+		safe_free(client->server);
 #ifdef	DEBUGMODE
 		servs.inuse--;
 #endif
@@ -383,6 +383,16 @@ void free_link(Link *lp)
 #endif
 }
 
+/** Returns the length (entry count) of a +beI list */
+int link_list_length(Link *lp)
+{
+	int  count = 0;
+
+	for (; lp; lp = lp->next)
+		count++;
+	return count;
+}
+
 Ban *make_ban(void)
 {
 	Ban *lp;
@@ -491,7 +501,7 @@ void add_ListItemPrio(ListStructPrio *new, ListStructPrio **list, int priority)
 
 /* NameList functions */
 
-void _add_name_list(NameList **list, char *name)
+void _add_name_list(NameList **list, const char *name)
 {
 	NameList *e = safe_alloc(sizeof(NameList)+strlen(name));
 	strcpy(e->name, name); /* safe, allocated above */
@@ -509,7 +519,7 @@ void _free_entire_name_list(NameList *n)
 	}
 }
 
-void _del_name_list(NameList **list, char *name)
+void _del_name_list(NameList **list, const char *name)
 {
 	NameList *e = find_name_list(*list, name);
 	if (e)
@@ -523,7 +533,7 @@ void _del_name_list(NameList **list, char *name)
 /** Find an entry in a NameList - case insensitive comparisson.
  * @ingroup ListFunctions
  */
-NameList *find_name_list(NameList *list, char *name)
+NameList *find_name_list(NameList *list, const char *name)
 {
 	NameList *e;
 
@@ -540,7 +550,7 @@ NameList *find_name_list(NameList *list, char *name)
 /** Find an entry in a NameList by running match_simple() on it.
  * @ingroup ListFunctions
  */
-NameList *find_name_list_match(NameList *list, char *name)
+NameList *find_name_list_match(NameList *list, const char *name)
 {
 	NameList *e;
 
@@ -554,7 +564,7 @@ NameList *find_name_list_match(NameList *list, char *name)
 	return NULL;
 }
 
-void add_nvplist(NameValuePrioList **lst, int priority, char *name, char *value)
+void add_nvplist(NameValuePrioList **lst, int priority, const char *name, const char *value)
 {
 	va_list vl;
 	NameValuePrioList *e = safe_alloc(sizeof(NameValuePrioList));
@@ -564,7 +574,7 @@ void add_nvplist(NameValuePrioList **lst, int priority, char *name, char *value)
 	AddListItemPrio(e, *lst, priority);
 }
 
-NameValuePrioList *find_nvplist(NameValuePrioList *list, char *name)
+NameValuePrioList *find_nvplist(NameValuePrioList *list, const char *name)
 {
 	NameValuePrioList *e;
 
@@ -578,7 +588,7 @@ NameValuePrioList *find_nvplist(NameValuePrioList *list, char *name)
 	return NULL;
 }
 
-void add_fmt_nvplist(NameValuePrioList **lst, int priority, char *name, FORMAT_STRING(const char *format), ...)
+void add_fmt_nvplist(NameValuePrioList **lst, int priority, const char *name, FORMAT_STRING(const char *format), ...)
 {
 	char value[512];
 	va_list vl;
@@ -603,3 +613,37 @@ void free_nvplist(NameValuePrioList *lst)
 		safe_free(e);
 	}
 }
+
+#define nv_find_by_name(stru, name)	do_nv_find_by_name(stru, name, ARRAY_SIZEOF((stru)))
+
+long do_nv_find_by_name(NameValue *table, const char *cmd, int numelements)
+{
+	int start = 0;
+	int stop = numelements-1;
+	int mid;
+	while (start <= stop) {
+		mid = (start+stop)/2;
+
+		if (smycmp(cmd,table[mid].name) < 0) {
+			stop = mid-1;
+		}
+		else if (strcmp(cmd,table[mid].name) == 0) {
+			return table[mid].value;
+		}
+		else
+			start = mid+1;
+	}
+	return 0;
+}
+
+#define nv_find_by_value(stru, value)	do_nv_find_by_value(stru, value, ARRAY_SIZEOF((stru)))
+const char *do_nv_find_by_value(NameValue *table, long value, int numelements)
+{
+	int i;
+
+	for (i=0; i < numelements; i++)
+		if (table[i].value == value)
+			return table[i].name;
+
+	return NULL;
+}
diff --git a/src/log.c b/src/log.c
@@ -0,0 +1,1871 @@
+/************************************************************************
+ * IRC - Internet Relay Chat, src/api-channelmode.c
+ * (C) 2021 Bram Matthys (Syzop) and the UnrealIRCd Team
+ *
+ * See file AUTHORS in IRC package for additional names of
+ * the programmers. 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief The logging API
+ */
+
+#define UNREAL_LOGGER_CODE
+#include "unrealircd.h"
+
+// TODO: Make configurable at compile time (runtime won't do, as we haven't read the config file)
+#define show_event_id_console 0
+
+/* Variables */
+Log *logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL };
+Log *temp_logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL };
+static int snomask_num_destinations = 0;
+
+static char snomasks_in_use[257] = { '\0' };
+static char snomasks_in_use_testing[257] = { '\0' };
+
+/* Forward declarations */
+int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsystem, const char *event_id, int matched_already);
+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);
+
+/** 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)
+{
+	static char buf[8192];
+	char *verified_s;
+
+	if (s == NULL)
+		return json_null();
+
+	verified_s = unrl_utf8_make_valid(s, buf, sizeof(buf), 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"))
+		return LOG_TYPE_JSON;
+	if (!strcmp(str, "text"))
+		return LOG_TYPE_TEXT;
+	return LOG_TYPE_INVALID;
+}
+
+const char *log_type_valtostring(LogType v)
+{
+	switch(v)
+	{
+		case LOG_TYPE_TEXT:
+			return "text";
+		case LOG_TYPE_JSON:
+			return "json";
+		default:
+			return NULL;
+	}
+}
+
+/***** CONFIGURATION ******/
+
+LogSource *add_log_source(const char *str)
+{
+	LogSource *ls;
+	char buf[256];
+	char *p;
+	LogLevel loglevel = ULOG_INVALID;
+	char *subsystem = NULL;
+	char *event_id = NULL;
+	int negative = 0;
+
+	if (*str == '!')
+	{
+		negative = 1;
+		strlcpy(buf, str+1, sizeof(buf));
+	} else
+	{
+		strlcpy(buf, str, sizeof(buf));
+	}
+
+	p = strchr(buf, '.');
+	if (p)
+		*p++ = '\0';
+
+	loglevel = log_level_stringtoval(buf);
+	if (loglevel == ULOG_INVALID)
+	{
+		if (isupper(*buf))
+			event_id = buf;
+		else
+			subsystem = buf;
+	}
+	if (p)
+	{
+		if (isupper(*p))
+		{
+			event_id = p;
+		} else
+		if (loglevel == ULOG_INVALID)
+		{
+			loglevel = log_level_stringtoval(p);
+			if ((loglevel == ULOG_INVALID) && !subsystem)
+				subsystem = p;
+		} else if (!subsystem)
+		{
+			subsystem = p;
+		}
+	}
+	ls = safe_alloc(sizeof(LogSource));
+	ls->loglevel = loglevel;
+	ls->negative = negative;
+	if (!BadPtr(subsystem))
+		strlcpy(ls->subsystem, subsystem, sizeof(ls->subsystem));
+	if (!BadPtr(event_id))
+		strlcpy(ls->event_id, event_id, sizeof(ls->event_id));
+
+	return ls;
+}
+
+void free_log_source(LogSource *l)
+{
+	safe_free(l);
+}
+
+void free_log_sources(LogSource *l)
+{
+	LogSource *l_next;
+	for (; l; l = l_next)
+	{
+		l_next = l->next;
+		free_log_source(l);
+	}
+}
+
+int config_test_log(ConfigFile *conf, ConfigEntry *block)
+{
+	int errors = 0;
+	int any_sources = 0;
+	ConfigEntry *ce, *cep, *cepp;
+	int destinations = 0;
+
+	for (ce = block->items; ce; ce = ce->next)
+	{
+		if (!strcmp(ce->name, "source"))
+		{
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				/* TODO: Validate the sources lightly for formatting issues */
+				any_sources = 1;
+			}
+		}
+		if (!strcmp(ce->name, "destination"))
+		{
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				if (!strcmp(cep->name, "snomask"))
+				{
+					destinations++;
+					snomask_num_destinations++;
+					/* We need to validate the parameter here as well */
+					if (!cep->value)
+					{
+						config_error_blank(cep->file->filename, cep->line_number, "set::logging::snomask");
+						errors++;
+					} else
+					if ((strlen(cep->value) != 1) || !(islower(cep->value[0]) || isupper(cep->value[0])))
+					{
+						config_error("%s:%d: snomask must be a single letter",
+							cep->file->filename, cep->line_number);
+						errors++;
+					} else {
+						strlcat(snomasks_in_use_testing, cep->value, sizeof(snomasks_in_use_testing));
+					}
+				} else
+				if (!strcmp(cep->name, "channel"))
+				{
+					destinations++;
+					/* We need to validate the parameter here as well */
+					if (!cep->value)
+					{
+						config_error_blank(cep->file->filename, cep->line_number, "set::logging::channel");
+						errors++;
+					} else
+					if (!valid_channelname(cep->value))
+					{
+						config_error("%s:%d: Invalid channel name '%s'",
+							cep->file->filename, cep->line_number, cep->value);
+						errors++;
+					}
+				} else
+				if (!strcmp(cep->name, "file"))
+				{
+					destinations++;
+					if (!cep->value)
+					{
+						config_error_blank(cep->file->filename, cep->line_number, "set::logging::file");
+						errors++;
+						continue;
+					}
+					convert_to_absolute_path(&cep->value, LOGDIR);
+					for (cepp = cep->items; cepp; cepp = cepp->next)
+					{
+						if (!strcmp(cepp->name, "type"))
+						{
+							if (!cepp->value)
+							{
+								config_error_empty(cepp->file->filename,
+									cepp->line_number, "log", cepp->name);
+								errors++;
+								continue;
+							}
+							if (!log_type_stringtoval(cepp->value))
+							{
+								config_error("%s:%i: unknown log type '%s'",
+									cepp->file->filename, cepp->line_number,
+									cepp->value);
+								errors++;
+							}
+						} else
+						if (!strcmp(cepp->name, "maxsize"))
+						{
+							if (!cepp->value)
+							{
+								config_error_empty(cepp->file->filename,
+									cepp->line_number, "log", cepp->name);
+								errors++;
+							}
+						} else
+						{
+							config_error_unknown(cepp->file->filename, cepp->line_number, "log::destination::file", cepp->name);
+							errors++;
+						}
+					}
+				} else
+				if (!strcmp(cep->name, "remote"))
+				{
+					destinations++;
+				} else
+				if (!strcmp(cep->name, "syslog"))
+				{
+					destinations++;
+					for (cepp = cep->items; cepp; cepp = cepp->next)
+					{
+						if (!strcmp(cepp->name, "type"))
+						{
+							if (!cepp->value)
+							{
+								config_error_empty(cepp->file->filename,
+									cepp->line_number, "log", cepp->name);
+								errors++;
+								continue;
+							}
+							if (!log_type_stringtoval(cepp->value))
+							{
+								config_error("%s:%i: unknown log type '%s'",
+									cepp->file->filename, cepp->line_number,
+									cepp->value);
+								errors++;
+							}
+						} else
+						{
+							config_error_unknown(cepp->file->filename, cepp->line_number, "log::destination::syslog", cepp->name);
+							errors++;
+						}
+					}
+				} else
+				{
+					config_error_unknownopt(cep->file->filename, cep->line_number, "log::destination", cep->name);
+					errors++;
+					continue;
+				}
+			}
+		}
+	}
+
+	if (!any_sources && !destinations)
+	{
+		unreal_log(ULOG_ERROR, "config", "CONFIG_OLD_LOG_BLOCK", NULL,
+		           "$config_file:$line_number: Your log block contains no sources and no destinations.\n"
+		           "The log block changed between UnrealIRCd 5 and UnrealIRCd 6, "
+		           "see https://www.unrealircd.org/docs/FAQ#old-log-block on how "
+		           "to convert it to the new syntax.",
+		           log_data_string("config_file", block->file->filename),
+		           log_data_integer("line_number", block->line_number));
+		errors++;
+		return errors;
+	}
+
+	if (!any_sources)
+	{
+		config_error("%s:%d: log block contains no sources. Old log block perhaps?",
+			block->file->filename, block->line_number);
+		errors++;
+	}
+	if (destinations == 0)
+	{
+		config_error("%s:%d: log block contains no destinations. Old log block perhaps?",
+			block->file->filename, block->line_number);
+		errors++;
+	}
+	if (destinations > 1)
+	{
+		config_error("%s:%d: log block contains multiple destinations. This is not support... YET!",
+			block->file->filename, block->line_number);
+		errors++;
+	}
+	return errors;
+}
+
+int config_run_log(ConfigFile *conf, ConfigEntry *block)
+{
+	ConfigEntry *ce, *cep, *cepp;
+	LogSource *sources = NULL;
+	int type;
+
+	/* If we later allow multiple destination entries later,
+	 * then we need to 'clone' sources or work with reference counts.
+	 */
+
+	/* First, gather the source... */
+	for (ce = block->items; ce; ce = ce->next)
+	{
+		if (!strcmp(ce->name, "source"))
+		{
+			LogSource *s;
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				s = add_log_source(cep->name);
+				AddListItem(s, sources);
+			}
+		}
+	}
+
+	/* Now deal with destinations... */
+	for (ce = block->items; ce; ce = ce->next)
+	{
+		if (!strcmp(ce->name, "destination"))
+		{
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				if (!strcmp(cep->name, "snomask"))
+				{
+					Log *log = safe_alloc(sizeof(Log));
+					strlcpy(log->destination, cep->value, sizeof(log->destination)); /* destination is the snomask */
+					strlcat(snomasks_in_use, cep->value, sizeof(snomasks_in_use));
+					log->sources = sources;
+					if (!strcmp(cep->value, "s"))
+						AddListItem(log, temp_logs[LOG_DEST_OPER]);
+					else
+						AddListItem(log, temp_logs[LOG_DEST_SNOMASK]);
+				} else
+				if (!strcmp(cep->name, "channel"))
+				{
+					Log *log = safe_alloc(sizeof(Log));
+					strlcpy(log->destination, cep->value, sizeof(log->destination)); /* destination is the channel */
+					log->sources = sources;
+					AddListItem(log, temp_logs[LOG_DEST_CHANNEL]);
+				} else
+				if (!strcmp(cep->name, "remote"))
+				{
+					Log *log = safe_alloc(sizeof(Log));
+					/* destination stays empty */
+					log->sources = sources;
+					AddListItem(log, temp_logs[LOG_DEST_REMOTE]);
+				} else
+				if (!strcmp(cep->name, "file") || !strcmp(cep->name, "syslog"))
+				{
+					Log *log;
+					/* First check if already exists... yeah this is a bit late
+					 * and ideally would have been done in config_test but...
+					 * that would have been lots of work for a (hopefully) rare case.
+					 */
+					for (log = temp_logs[LOG_DEST_DISK]; log; log = log->next)
+					{
+						if ((log->file && !strcmp(log->file, cep->value)) ||
+						    (log->filefmt && !strcmp(log->filefmt, cep->value)))
+						{
+							config_warn("%s:%d: Ignoring duplicate log block for file '%s'. "
+							            "You cannot have multiple log blocks logging to the same file.",
+							            cep->file->filename, cep->line_number,
+							            cep->value);
+							free_log_sources(sources);
+							return 0;
+						}
+					}
+					log = safe_alloc(sizeof(Log));
+					log->sources = sources;
+					log->logfd = -1;
+					log->type = LOG_TYPE_TEXT; /* default */
+					if (!strcmp(cep->name, "syslog"))
+						safe_strdup(log->file, "syslog");
+					else if (strchr(cep->value, '%'))
+						safe_strdup(log->filefmt, cep->value);
+					else
+						safe_strdup(log->file, cep->value);
+					for (cepp = cep->items; cepp; cepp = cepp->next)
+					{
+						if (!strcmp(cepp->name, "maxsize"))
+						{
+							log->maxsize = config_checkval(cepp->value,CFG_SIZE);
+						}
+						else if (!strcmp(cepp->name, "type"))
+						{
+							log->type = log_type_stringtoval(cepp->value);
+						}
+					}
+					AddListItem(log, temp_logs[LOG_DEST_DISK]);
+				}
+			}
+		}
+	}
+
+	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));
+
+	/* 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->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 (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));
+	} 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));
+	}
+}
+
+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...
+}
+
+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));
+	d->type = LOG_FIELD_STRING;
+	safe_strdup(d->key, key);
+	safe_strdup(d->value.string, str);
+	return d;
+}
+
+LogData *log_data_char(const char *key, const char c)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_STRING;
+	safe_strdup(d->key, key);
+	d->value.string = safe_alloc(2);
+	d->value.string[0] = c;
+	d->value.string[1] = '\0';
+	return d;
+}
+
+LogData *log_data_integer(const char *key, int64_t integer)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_INTEGER;
+	safe_strdup(d->key, key);
+	d->value.integer = integer;
+	return d;
+}
+
+LogData *log_data_timestamp(const char *key, time_t ts)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_STRING;
+	safe_strdup(d->key, key);
+	safe_strdup(d->value.string, timestamp_iso8601(ts));
+	return d;
+}
+
+LogData *log_data_client(const char *key, Client *client)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_CLIENT;
+	safe_strdup(d->key, key);
+	d->value.client = client;
+	return d;
+}
+
+LogData *log_data_channel(const char *key, Channel *channel)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_CHANNEL;
+	safe_strdup(d->key, key);
+	d->value.channel = channel;
+	return d;
+}
+
+LogData *log_data_source(const char *file, int line, const char *function)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	json_t *j;
+
+	d->type = LOG_FIELD_OBJECT;
+	safe_strdup(d->key, "source");
+	d->value.object = j = json_object();
+	json_object_set_new(j, "file", json_string_unreal(file));
+	json_object_set_new(j, "line", json_integer(line));
+	json_object_set_new(j, "function", json_string_unreal(function));
+	return d;
+}
+
+LogData *log_data_socket_error(int fd)
+{
+	/* First, grab the error number very early here: */
+#ifndef _WIN32
+	int sockerr = errno;
+#else
+	int sockerr = WSAGetLastError();
+#endif
+	int v;
+	int len = sizeof(v);
+	LogData *d;
+	json_t *j;
+
+#ifdef SO_ERROR
+	/* Try to get the "real" error from the underlying socket.
+	 * If we succeed then we will override "sockerr" with it.
+	 */
+	if ((fd >= 0) && !getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&v, &len) && v)
+		sockerr = v;
+#endif
+
+	d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_OBJECT;
+	safe_strdup(d->key, "socket_error");
+	d->value.object = j = json_object();
+	json_object_set_new(j, "error_code", json_integer(sockerr));
+	json_object_set_new(j, "error_string", json_string_unreal(STRERROR(sockerr)));
+	return d;
+}
+
+/** Populate log with the TLS error(s) stack */
+LogData *log_data_tls_error(void)
+{
+	LogData *d;
+	json_t *j;
+	json_t *error_stack;
+	json_t *name = NULL;
+	json_t *jt;
+	unsigned long e;
+	char buf[512];
+	static char all_errors[8192];
+
+	d = safe_alloc(sizeof(LogData));
+	d->type = LOG_FIELD_OBJECT;
+	safe_strdup(d->key, "tls_error");
+	d->value.object = j = json_object();
+
+	error_stack = json_array();
+	json_object_set_new(j, "error_stack", error_stack);
+	*all_errors = '\0';
+
+	do {
+		json_t *obj;
+
+		e = ERR_get_error();
+		if (e == 0)
+			break;
+		ERR_error_string_n(e, buf, sizeof(buf));
+
+		obj = json_object();
+		json_object_set_new(obj, "code", json_integer(e));
+		json_object_set_new(obj, "string", json_string_unreal(buf));
+		json_array_append_new(error_stack, obj);
+
+		if (name == NULL)
+		{
+			/* Set tls_error.name to the first error that was encountered */
+			json_object_set_new(j, "name", json_string_unreal(buf));
+		}
+		strlcat(all_errors, buf, sizeof(all_errors));
+		strlcat(all_errors, "\n", sizeof(all_errors));
+	} while(e);
+
+	json_object_set_new(j, "all", json_string_unreal(all_errors));
+
+	return d;
+}
+
+LogData *log_data_link_block(ConfigItem_link *link)
+{
+	LogData *d = safe_alloc(sizeof(LogData));
+	json_t *j;
+	char *bind_ip;
+
+	d->type = LOG_FIELD_OBJECT;
+	safe_strdup(d->key, "link_block");
+	d->value.object = j = json_object();
+	json_object_set_new(j, "name", json_string_unreal(link->servername));
+	json_object_set_new(j, "hostname", json_string_unreal(link->outgoing.hostname));
+	json_object_set_new(j, "ip", json_string_unreal(link->connect_ip));
+	json_object_set_new(j, "port", json_integer(link->outgoing.port));
+
+	if (!link->outgoing.bind_ip && iConf.link_bindip)
+		bind_ip = iConf.link_bindip;
+	else
+		bind_ip = link->outgoing.bind_ip;
+	if (!bind_ip)
+		bind_ip = "*";
+	json_object_set_new(j, "bind_ip", json_string_unreal(bind_ip));
+
+	return d;
+}
+
+LogData *log_data_tkl(const char *key, TKL *tkl)
+{
+	char buf[BUFSIZE];
+	LogData *d = safe_alloc(sizeof(LogData));
+	json_t *j;
+
+	d->type = LOG_FIELD_OBJECT;
+	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)));
+	}
+
+	return d;
+}
+
+void log_data_free(LogData *d)
+{
+	if (d->type == LOG_FIELD_STRING)
+		safe_free(d->value.string);
+	else if ((d->type == LOG_FIELD_OBJECT) && d->value.object)
+		json_decref(d->value.object);
+
+	safe_free(d->key);
+	safe_free(d);
+}
+
+const char *log_level_valtostring(LogLevel loglevel)
+{
+	switch(loglevel)
+	{
+		case ULOG_DEBUG:
+			return "debug";
+		case ULOG_INFO:
+			return "info";
+		case ULOG_WARNING:
+			return "warn";
+		case ULOG_ERROR:
+			return "error";
+		case ULOG_FATAL:
+			return "fatal";
+		default:
+			return NULL;
+	}
+}
+
+static NameValue log_colors_irc[] = {
+	{ ULOG_INVALID,	"\0030,01" },
+	{ ULOG_DEBUG,	"\0030,01" },
+	{ ULOG_INFO,	"\00303" },
+	{ ULOG_WARNING,	"\00307" },
+	{ ULOG_ERROR,	"\00304" },
+	{ ULOG_FATAL,	"\00313" },
+};
+
+static NameValue log_colors_terminal[] = {
+	{ ULOG_INVALID,	"\033[90m" },
+	{ ULOG_DEBUG,	"\033[37m" },
+	{ ULOG_INFO,	"\033[92m" },
+	{ ULOG_WARNING,	"\033[93m" },
+	{ ULOG_ERROR,	"\033[91m" },
+	{ ULOG_FATAL,	"\033[95m" },
+};
+#define TERMINAL_COLOR_RESET "\033[0m"
+
+const char *log_level_irc_color(LogLevel loglevel)
+{
+	return nv_find_by_value(log_colors_irc, loglevel);
+}
+
+const char *log_level_terminal_color(LogLevel loglevel)
+{
+	return nv_find_by_value(log_colors_terminal, loglevel);
+}
+
+LogLevel log_level_stringtoval(const char *str)
+{
+	if (!strcmp(str, "info"))
+		return ULOG_INFO;
+	if (!strcmp(str, "warn"))
+		return ULOG_WARNING;
+	if (!strcmp(str, "error"))
+		return ULOG_ERROR;
+	if (!strcmp(str, "fatal"))
+		return ULOG_FATAL;
+	if (!strcmp(str, "debug"))
+		return ULOG_DEBUG;
+	return ULOG_INVALID;
+}
+
+#define validvarcharacter(x)	(isalnum((x)) || ((x) == '_'))
+#define valideventidcharacter(x)	(isupper((x)) || isdigit((x)) || ((x) == '_'))
+#define validsubsystemcharacter(x)	(islower((x)) || isdigit((x)) || ((x) == '_') || ((x) == '-'))
+
+int valid_event_id(const char *s)
+{
+	if (!*s)
+		return 0;
+	for (; *s; s++)
+		if (!valideventidcharacter(*s))
+			return 0;
+	return 1;
+}
+
+int valid_subsystem(const char *s)
+{
+	if (!*s)
+		return 0;
+	if (log_level_stringtoval(s) != ULOG_INVALID)
+		return 0;
+	for (; *s; s++)
+		if (!validsubsystemcharacter(*s))
+			return 0;
+	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,
+// if this gets more than we should use some kind of array for it,
+// especially for the hardcoded name shit like $socket_error.
+
+/** Build a string and replace $variables where needed.
+ * See src/modules/blacklist.c for an example.
+ * @param inbuf		The input string
+ * @param outbuf	The output string
+ * @param len		The maximum size of the output string (including NUL)
+ * @param name		Array of variables names
+ * @param value		Array of variable values
+ */
+void buildlogstring(const char *inbuf, char *outbuf, size_t len, json_t *details)
+{
+	const char *i, *p;
+	char *o;
+	int left = len - 1;
+	int cnt, found;
+	char varname[256], *varp, *varpp;
+	json_t *t;
+
+#ifdef DEBUGMODE
+	if (len <= 0)
+		abort();
+#endif
+
+	for (i = inbuf, o = outbuf; *i; i++)
+	{
+		if (*i == '$')
+		{
+			i++;
+
+			/* $$ = literal $ */
+			if (*i == '$')
+				goto literal;
+
+			if (!validvarcharacter(*i))
+			{
+				/* What do we do with things like '$/' ? -- treat literal */
+				i--;
+				goto literal;
+			}
+
+			/* find termination */
+			for (p=i; validvarcharacter(*p) || ((*p == '.') && validvarcharacter(p[1])); p++);
+
+			/* find variable name in list */
+			strlncpy(varname, i, sizeof(varname), p - i);
+			varp = strchr(varname, '.');
+			if (varp)
+				*varp = '\0';
+			t = json_object_get(details, varname);
+			if (t)
+			{
+				const char *output = NULL;
+				if (varp)
+				{
+					char *varpp;
+					do {
+						varpp = strchr(varp+1, '.');
+						if (varpp)
+							*varpp = '\0';
+						/* Fetch explicit object.key */
+						t = json_object_get(t, varp+1);
+						varp = varpp;
+					} while(t && varpp);
+					if (t)
+						output = json_get_value(t);
+				} else
+				if (!strcmp(varname, "socket_error"))
+				{
+					/* Fetch socket_error.error_string */
+					t = json_object_get(t, "error_string");
+					if (t)
+						output = json_get_value(t);
+				} else
+				if (json_is_object(t))
+				{
+					/* Fetch object.name */
+					t = json_object_get(t, "name");
+					if (t)
+						output = json_get_value(t);
+				} else
+				{
+					output = json_get_value(t);
+				}
+				if (output)
+				{
+					strlcpy(o, output, left);
+					left -= strlen(output); /* may become <0 */
+					if (left <= 0)
+						return; /* return - don't write \0 to 'o'. ensured by strlcpy already */
+					o += strlen(output); /* value entirely written */
+				}
+			} else
+			{
+				/* variable name does not exist -- treat as literal string */
+				i--;
+				goto literal;
+			}
+
+			/* value written. we're done. */
+			i = p - 1;
+			continue;
+		}
+literal:
+		if (!left)
+			break;
+		*o++ = *i;
+		left--;
+		if (!left)
+			break;
+	}
+	*o = '\0';
+}
+
+/** 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)
+{
+	static int last_log_file_warning = 0;
+	Log *l;
+	char timebuf[128];
+	struct stat fstats;
+	int n;
+	int write_error;
+	long snomask;
+	MultiLine *m;
+
+	snprintf(timebuf, sizeof(timebuf), "[%s] ", myctime(TStime()));
+
+	RunHook(HOOKTYPE_LOG, loglevel, subsystem, event_id, msg, json_serialized, timebuf);
+
+	if (!loop.forked && (loglevel > ULOG_DEBUG))
+	{
+		for (m = msg; m; m = m->next)
+		{
+#ifdef _WIN32
+			if (show_event_id_console)
+				win_log("* %s.%s%s [%s] %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
+			else
+				win_log("* [%s] %s\n", log_level_valtostring(loglevel), m->line);
+#else
+			if (terminal_supports_color())
+			{
+				if (show_event_id_console)
+				{
+					fprintf(stderr, "%s%s.%s%s %s[%s]%s %s\n",
+							log_level_terminal_color(ULOG_INVALID), subsystem, event_id, TERMINAL_COLOR_RESET,
+							log_level_terminal_color(loglevel), log_level_valtostring(loglevel), TERMINAL_COLOR_RESET,
+							m->line);
+				} else {
+					fprintf(stderr, "%s[%s]%s %s\n",
+							log_level_terminal_color(loglevel), log_level_valtostring(loglevel), TERMINAL_COLOR_RESET,
+							m->line);
+				}
+			} else {
+				if (show_event_id_console)
+					fprintf(stderr, "%s.%s%s [%s] %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
+				else
+					fprintf(stderr, "[%s] %s\n", log_level_valtostring(loglevel), m->line);
+			}
+#endif
+		}
+	}
+
+	/* In case of './unrealircd configtest': don't write to log file, only to stderr */
+	if (loop.config_test)
+		return;
+
+	for (l = logs[LOG_DEST_DISK]; l; l = l->next)
+	{
+		if (!log_sources_match(l->sources, loglevel, subsystem, event_id, 0))
+			continue;
+
+#ifdef HAVE_SYSLOG
+		if (l->file && !strcasecmp(l->file, "syslog"))
+		{
+			if (l->type == LOG_TYPE_JSON)
+			{
+				syslog(LOG_INFO, "%s", json_serialized);
+			} else
+			if (l->type == LOG_TYPE_TEXT)
+			{
+				for (m = msg; m; m = m->next)
+					syslog(LOG_INFO, "%s.%s%s %s: %s", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
+			}
+			continue;
+		}
+#endif
+
+		/* This deals with dynamic log file names, such as ircd.%Y-%m-%d.log */
+		if (l->filefmt)
+		{
+			char *fname = unreal_strftime(l->filefmt);
+			if (l->file && (l->logfd != -1) && strcmp(l->file, fname))
+			{
+				/* We are logging already and need to switch over */
+				fd_close(l->logfd);
+				l->logfd = -1;
+			}
+			safe_strdup(l->file, fname);
+		}
+
+		/* log::maxsize code */
+		if (l->maxsize && (stat(l->file, &fstats) != -1) && fstats.st_size >= l->maxsize)
+		{
+			char oldlog[512];
+			if (l->logfd == -1)
+			{
+				/* Try to open, so we can write the 'Max file size reached' message. */
+				l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY);
+			}
+			if (l->logfd != -1)
+			{
+				if (write(l->logfd, "Max file size reached, starting new log file\n", 45) < 0)
+				{
+					/* We already handle the unable to write to log file case for normal data.
+					 * I think we can get away with not handling this one.
+					 */
+					;
+				}
+				fd_close(l->logfd);
+			}
+			l->logfd = -1;
+
+			/* Rename log file to xxxxxx.old */
+			snprintf(oldlog, sizeof(oldlog), "%s.old", l->file);
+			unlink(oldlog); /* windows rename cannot overwrite, so unlink here.. ;) */
+			rename(l->file, oldlog);
+		}
+
+		/* generic code for opening log if not open yet.. */
+		if (l->logfd == -1)
+		{
+			l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY);
+			if (l->logfd == -1)
+			{
+				if (!loop.booted)
+				{
+					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;
+			}
+		}
+
+		/* Now actually WRITE to the log... */
+		write_error = 0;
+		if ((l->type == LOG_TYPE_JSON) && strcmp(subsystem, "rawtraffic"))
+		{
+			n = write(l->logfd, json_serialized, strlen(json_serialized));
+			if (n < strlen(json_serialized))
+			{
+				write_error = 1;
+			} else {
+				if (write(l->logfd, "\n", 1) < 1) // FIXME: no.. we should do it this way..... and why do we use direct I/O at all?
+					write_error = 1;
+			}
+		} else
+		if (l->type == LOG_TYPE_TEXT)
+		{
+			for (m = msg; m; m = m->next)
+			{
+				char text_buf[8192];
+				snprintf(text_buf, sizeof(text_buf), "%s.%s%s %s: %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
+				// FIXME: don't write in 2 stages, waste of slow system calls
+				if (write(l->logfd, timebuf, strlen(timebuf)) < 0)
+				{
+					/* Let's ignore any write errors for this one. Next write() will catch it... */
+					;
+				}
+				n = write(l->logfd, text_buf, strlen(text_buf));
+				if (n < strlen(text_buf))
+				{
+					write_error = 1;
+					break;
+				}
+			}
+		}
+
+		if (write_error)
+		{
+			if (!loop.booted)
+			{
+				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();
+				}
+			}
+		}
+	}
+}
+
+int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsystem, const char *event_id, int matched_already)
+{
+	int retval = 0;
+	LogSource *ls;
+
+	// NOTE: This routine works by exclusion, so a bad struct would
+	//       cause everything to match!!
+
+	for (ls = logsource; ls; ls = ls->next)
+	{
+		/* First deal with all positive matchers.. */
+		if (ls->negative)
+			continue;
+		if (!strcmp(ls->subsystem, "all"))
+		{
+			retval = 1;
+			break;
+		}
+		if (!strcmp(ls->subsystem, "nomatch") && !matched_already)
+		{
+			/* catch-all */
+			retval = 1;
+			break;
+		}
+		if (*ls->event_id && strcmp(ls->event_id, event_id))
+			continue;
+		if (*ls->subsystem && strcmp(ls->subsystem, subsystem))
+			continue;
+		if ((ls->loglevel != ULOG_INVALID) && (ls->loglevel != loglevel))
+			continue;
+		/* MATCH */
+		retval = 1;
+		break;
+	}
+
+	/* No matches? Then we can stop here */
+	if (retval == 0)
+		return 0;
+
+	/* There was a match, now check for exemptions, eg !operoverride */
+	for (ls = logsource; ls; ls = ls->next)
+	{
+		/* Only deal with negative matches... */
+		if (!ls->negative)
+			continue;
+		if (!strcmp(ls->subsystem, "nomatch") || !strcmp(ls->subsystem, "all"))
+			continue; /* !nomatch and !all make no sense, so just ignore it */
+		if (*ls->event_id && strcmp(ls->event_id, event_id))
+			continue;
+		if (*ls->subsystem && strcmp(ls->subsystem, subsystem))
+			continue;
+		if ((ls->loglevel != ULOG_INVALID) && (ls->loglevel != loglevel))
+			continue;
+		/* NEGATIVE MATCH */
+		return 0;
+	}
+
+	return 1;
+}
+
+/** Convert loglevel/subsystem/event_id to a snomask.
+ * @returns The snomask letters (may be more than one),
+ *          an asterisk (for all ircops), or NULL (no delivery)
+ */
+const char *log_to_snomask(LogLevel loglevel, const char *subsystem, const char *event_id)
+{
+	Log *ld;
+	static char snomasks[64];
+	int matched = 0;
+
+	*snomasks = '\0';
+	for (ld = logs[LOG_DEST_SNOMASK]; ld; ld = ld->next)
+	{
+		if (log_sources_match(ld->sources, loglevel, subsystem, event_id, 0))
+		{
+			strlcat(snomasks, ld->destination, sizeof(snomasks));
+			matched = 1;
+		}
+	}
+
+	if (logs[LOG_DEST_OPER] && log_sources_match(logs[LOG_DEST_OPER]->sources, loglevel, subsystem, event_id, matched))
+		strlcat(snomasks, "s", sizeof(snomasks));
+
+	return *snomasks ? snomasks : NULL;
+}
+
+#define COLOR_NONE "\xf"
+#define COLOR_DARKGREY "\00314"
+/** Do the actual writing to log files */
+void do_unreal_log_opers(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
+{
+	Client *client;
+	const char *snomask_destinations, *p;
+	MessageTag *mtags = NULL, *mtags_loop;
+	MultiLine *m;
+
+	/* If not fully booted then we don't have a logging to snomask mapping so can't do much.. */
+	if (!loop.booted)
+		return;
+
+	/* Never send these */
+	if (!strcmp(subsystem, "rawtraffic"))
+		return;
+
+	snomask_destinations = log_to_snomask(loglevel, subsystem, event_id);
+	if (!snomask_destinations)
+		return;
+
+	/* Prepare message tag for those who have CAP unrealircd.org/json-log */
+	if (json_serialized)
+	{
+		mtags = safe_alloc(sizeof(MessageTag));
+		safe_strdup(mtags->name, "unrealircd.org/json-log");
+		safe_strdup(mtags->value, json_serialized);
+	}
+
+	/* To specific snomasks... */
+	list_for_each_entry(client, &oper_list, special_node)
+	{
+		const char *operlogin;
+		ConfigItem_oper *oper;
+		int colors = iConf.server_notice_colors;
+
+		if (snomask_destinations)
+		{
+			char found = 0;
+			if (!client->user->snomask)
+				continue; /* None set, so will never match */
+			for (p = snomask_destinations; *p; p++)
+			{
+				if (strchr(client->user->snomask, *p))
+				{
+					found = 1;
+					break;
+				}
+			}
+			if (!found)
+				continue;
+		}
+
+		operlogin = get_operlogin(client);
+		if (operlogin && (oper = find_oper(operlogin)))
+			colors = oper->server_notice_colors;
+
+		mtags_loop = mtags;
+		for (m = msg; m; m = m->next)
+		{
+			if (colors)
+			{
+				sendto_one(client, mtags_loop, ":%s NOTICE %s :%s%s.%s%s%s %s[%s]%s %s",
+					from_server->name, client->name,
+					COLOR_DARKGREY, subsystem, event_id, m->next?"+":"", COLOR_NONE,
+					log_level_irc_color(loglevel), log_level_valtostring(loglevel), COLOR_NONE,
+					m->line);
+			} else {
+				sendto_one(client, mtags_loop, ":%s NOTICE %s :%s.%s%s [%s] %s",
+					from_server->name, client->name,
+					subsystem, event_id, m->next?"+":"",
+					log_level_valtostring(loglevel),
+					m->line);
+			}
+			mtags_loop = NULL; /* this way we only send the JSON in the first msg */
+		}
+	}
+
+	safe_free_message_tags(mtags);
+}
+
+void do_unreal_log_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized)
+{
+	Log *l;
+	int found = 0;
+
+	for (l = logs[LOG_DEST_REMOTE]; l; l = l->next)
+	{
+		if (log_sources_match(l->sources, loglevel, subsystem, event_id, 0))
+		{
+			found = 1;
+			break;
+		}
+	}
+	if (found == 0)
+		return;
+
+	do_unreal_log_remote_deliver(loglevel, subsystem, event_id, msg, json_serialized);
+}
+
+void do_unreal_log_free_args(va_list vl)
+{
+	LogData *d;
+
+	while ((d = va_arg(vl, LogData *)))
+	{
+		log_data_free(d);
+	}
+}
+
+static int unreal_log_recursion_trap = 0;
+
+/* Logging function, called by the unreal_log() macro. */
+void do_unreal_log(LogLevel loglevel, const char *subsystem, const char *event_id,
+                   Client *client, const char *msg, ...)
+{
+	va_list vl;
+
+	if (unreal_log_recursion_trap)
+	{
+		va_start(vl, msg);
+		do_unreal_log_free_args(vl);
+		va_end(vl);
+		return;
+	}
+
+	unreal_log_recursion_trap = 1;
+	va_start(vl, msg);
+	do_unreal_log_internal(loglevel, subsystem, event_id, client, 1, msg, vl);
+	va_end(vl);
+	unreal_log_recursion_trap = 0;
+}
+
+/* Logging function, called by the unreal_log_raw() macro. */
+void do_unreal_log_raw(LogLevel loglevel, const char *subsystem, const char *event_id,
+                       Client *client, const char *msg, ...)
+{
+	va_list vl;
+
+	if (unreal_log_recursion_trap)
+	{
+		va_start(vl, msg);
+		do_unreal_log_free_args(vl);
+		va_end(vl);
+		return;
+	}
+
+	unreal_log_recursion_trap = 1;
+	va_start(vl, msg);
+	do_unreal_log_internal(loglevel, subsystem, event_id, client, 0, msg, vl);
+	va_end(vl);
+	unreal_log_recursion_trap = 0;
+}
+
+void do_unreal_log_norecursioncheck(LogLevel loglevel, const char *subsystem, const char *event_id,
+                                    Client *client, const char *msg, ...)
+{
+	va_list vl;
+
+	va_start(vl, msg);
+	do_unreal_log_internal(loglevel, subsystem, event_id, client, 1, msg, vl);
+	va_end(vl);
+}
+
+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)
+{
+	LogData *d;
+	char *json_serialized;
+	const char *str;
+	json_t *j = NULL;
+	json_t *j_details = NULL;
+	json_t *t;
+	char msgbuf[8192];
+	const char *loglevel_string = log_level_valtostring(loglevel);
+	MultiLine *mmsg;
+	Client *from_server = NULL;
+
+	if (loglevel_string == NULL)
+	{
+		do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_LOGLEVEL", NULL,
+		                       "[BUG] Next log message had an invalid log level -- corrected to ULOG_ERROR",
+		                       NULL);
+		loglevel = ULOG_ERROR;
+		loglevel_string = log_level_valtostring(loglevel);
+	}
+	if (!valid_subsystem(subsystem))
+	{
+		do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_SUBSYSTEM", NULL,
+		                       "[BUG] Next log message had an invalid subsystem -- changed to 'unknown'",
+		                       NULL);
+		subsystem = "unknown";
+	}
+	if (!valid_event_id(event_id))
+	{
+		do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_EVENT_ID", NULL,
+		                       "[BUG] Next log message had an invalid event id -- changed to 'unknown'",
+		                       NULL);
+		event_id = "unknown";
+	}
+	/* This one is probably temporary since it should not be a real error, actually (but often is) */
+	if (expand_msg && strchr(msg, '%'))
+	{
+		do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_MESSAGE_PERCENT", NULL,
+		                       "[BUG] Next log message contains a percent sign -- possibly accidental format string!",
+		                       NULL);
+	}
+
+	j = json_object();
+	j_details = json_object();
+
+	json_object_set_new(j, "timestamp", json_string_unreal(timestamp_iso8601_now()));
+	json_object_set_new(j, "level", json_string_unreal(loglevel_string));
+	json_object_set_new(j, "subsystem", json_string_unreal(subsystem));
+	json_object_set_new(j, "event_id", json_string_unreal(event_id));
+	json_object_set_new(j, "log_source", json_string_unreal(*me.name ? me.name : "local"));
+
+	/* We put all the rest in j_details because we want to enforce
+	 * a certain ordering of the JSON output. We will merge these
+	 * details later on.
+	 */
+	if (client)
+		json_expand_client(j_details, "client", client, 0);
+	/* Additional details (if any) */
+	while ((d = va_arg(vl, LogData *)))
+	{
+		switch(d->type)
+		{
+			case LOG_FIELD_INTEGER:
+				json_object_set_new(j_details, d->key, json_integer(d->value.integer));
+				break;
+			case LOG_FIELD_STRING:
+				if (d->value.string)
+					json_object_set_new(j_details, d->key, json_string_unreal(d->value.string));
+				else
+					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);
+				break;
+			case LOG_FIELD_CHANNEL:
+				json_expand_channel(j_details, d->key, d->value.channel, 0);
+				break;
+			case LOG_FIELD_OBJECT:
+				json_object_set_new(j_details, d->key, d->value.object);
+				d->value.object = NULL; /* don't let log_data_free() free it */
+				break;
+			default:
+#ifdef DEBUGMODE
+				abort();
+#endif
+				break;
+		}
+		log_data_free(d);
+	}
+
+	if (expand_msg)
+		buildlogstring(msg, msgbuf, sizeof(msgbuf), j_details);
+	else
+		strlcpy(msgbuf, msg, sizeof(msgbuf));
+
+	json_object_set_new(j, "msg", json_string_unreal(msgbuf));
+
+	/* Now merge the details into root object 'j': */
+	json_object_update_missing(j, j_details);
+	/* Generate the JSON */
+	json_serialized = json_dumps(j, JSON_COMPACT);
+
+	/* Convert the message buffer to MultiLine */
+	mmsg = line2multiline(msgbuf);
+
+	/* Now call the disk loggers */
+	do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, json_serialized);
+
+	/* And the ircops stuff */
+	t = json_object_get(j_details, "from_server_name");
+	if (t && (str = json_get_value(t)))
+		from_server = find_server(str, NULL);
+	if (from_server == NULL)
+		from_server = &me;
+	do_unreal_log_opers(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
+
+	do_unreal_log_remote(loglevel, subsystem, event_id, mmsg, json_serialized);
+
+	// NOTE: code duplication further down!
+
+	/* Free everything */
+	safe_free(json_serialized);
+	safe_free_multiline(mmsg);
+	json_decref(j_details);
+	json_decref(j);
+}
+
+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)
+{
+	if (unreal_log_recursion_trap)
+		return;
+	unreal_log_recursion_trap = 1;
+
+	/* Call the disk loggers */
+	do_unreal_log_disk(loglevel, subsystem, event_id, msg, json_serialized);
+
+	/* And the ircops stuff */
+	do_unreal_log_opers(loglevel, subsystem, event_id, msg, json_serialized, from_server);
+
+	unreal_log_recursion_trap = 0;
+}
+
+
+void free_log_block(Log *l)
+{
+	Log *l_next;
+	LogSource *src, *src_next;
+	for (; l; l = l_next)
+	{
+		l_next = l->next;
+		if (l->logfd > 0)
+		{
+			fd_close(l->logfd);
+			l->logfd = -1;
+		}
+		free_log_sources(l->sources);
+		safe_free(l->file);
+		safe_free(l->filefmt);
+		safe_free(l);
+	}
+}
+
+int log_tests(void)
+{
+	if (snomask_num_destinations <= 1)
+	{
+		unreal_log(ULOG_ERROR, "config", "LOG_SNOMASK_BLOCK_MISSING", NULL,
+		           "Missing snomask logging configuration:\n"
+		           "Please add the following line to your unrealircd.conf: "
+		           "include \"snomasks.default.conf\";");
+		return 0;
+	}
+	snomask_num_destinations = 0;
+	return 1;
+}
+
+void postconf_defaults_log_block(void)
+{
+	Log *l;
+	LogSource *ls;
+
+	/* Is there any log block to disk? Then nothing to do. */
+	if (logs[LOG_DEST_DISK])
+		return;
+
+	unreal_log(ULOG_WARNING, "log", "NO_DISK_LOG_BLOCK", NULL,
+	           "No log { } block found that logs to disk -- "
+	           "logging everything in text format to 'ircd.log'");
+
+	/* Create a default log block */
+	l = safe_alloc(sizeof(Log));
+	l->logfd = -1;
+	l->type = LOG_TYPE_TEXT; /* text */
+	l->maxsize = 100000000; /* maxsize 100M */
+	safe_strdup(l->file, "ircd.log");
+	convert_to_absolute_path(&l->file, LOGDIR);
+	AddListItem(l, logs[LOG_DEST_DISK]);
+
+	/* And the source filter */
+	ls = add_log_source("all");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!debug");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!join.LOCAL_CLIENT_JOIN");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!join.REMOTE_CLIENT_JOIN");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!part.LOCAL_CLIENT_PART");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!part.REMOTE_CLIENT_PART");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!kick.LOCAL_CLIENT_KICK");
+	AppendListItem(ls, l->sources);
+	ls = add_log_source("!kick.REMOTE_CLIENT_KICK");
+	AppendListItem(ls, l->sources);
+}
+
+/* Called before CONFIG_TEST */
+void log_pre_rehash(void)
+{
+	*snomasks_in_use_testing = '\0';
+}
+
+/* Called after CONFIG_TEST right before CONFIG_RUN */
+void config_pre_run_log(void)
+{
+	*snomasks_in_use = '\0';
+}
+
+/* Called after CONFIG_RUN is complete */
+void log_blocks_switchover(void)
+{
+	int i;
+	for (i=0; i < NUM_LOG_DESTINATIONS; i++)
+		free_log_block(logs[i]);
+	memcpy(logs, temp_logs, sizeof(logs));
+	memset(temp_logs, 0, sizeof(temp_logs));
+}
+
+/** Check if a letter is a valid snomask (that is:
+ * one that exists in the log block configuration).
+ * @param c	the snomask letter to check
+ * @returns	1 if exists, 0 if not.
+ */
+int is_valid_snomask(char c)
+{
+	return strchr(snomasks_in_use, c) ? 1 : 0;
+}
+
+/** Check if a letter is a valid snomask during or after CONFIG_TEST
+ * (the snomasks exist in the log block configuration read during config_test).
+ * @param c	the snomask letter to check
+ * @returns	1 if exists, 0 if not.
+ */
+int is_valid_snomask_testing(char c)
+{
+	return strchr(snomasks_in_use_testing, c) ? 1 : 0;
+}
+
+/** Check if a string all consists of valid snomasks during or after CONFIG_TEST
+ * (the snomasks exist in the log block configuration read during config_test).
+ * @param str			the snomask string to check
+ * @param invalid_snomasks	list of unknown snomask letters
+ * @returns			1 if exists, 0 if not.
+ */
+int is_valid_snomask_string_testing(const char *str, char **invalid_snomasks)
+{
+	static char invalid_snomasks_buf[256];
+
+	*invalid_snomasks_buf = '\0';
+	for (; *str; str++)
+	{
+		if ((*str == '+') || (*str == '-'))
+			continue;
+		if (!strchr(snomasks_in_use_testing, *str))
+			strlcat_letter(invalid_snomasks_buf, *str, sizeof(invalid_snomasks_buf));
+	}
+	*invalid_snomasks = invalid_snomasks_buf;
+	return *invalid_snomasks_buf ? 0 : 1;
+}
diff --git a/src/macosx/UnrealIRCd/AppModel.swift b/src/macosx/UnrealIRCd/AppModel.swift
@@ -13,7 +13,7 @@ class AppModel : ChangeNotifierDelegate
 {
     var menuItem : NSStatusItem
     static let logoName = "logo.png"
-    static let helpURL = "https://www.unrealircd.org/docs/UnrealIRCd_5_documentation"
+    static let helpURL = "https://www.unrealircd.org/docs/UnrealIRCd_6_documentation"
     var daemonModel : DaemonModel
     var configurationModel : ConfigurationModel
     var windowController : NSWindowController?
diff --git a/src/match.c b/src/match.c
@@ -384,7 +384,7 @@ void unreal_delete_match(Match *m)
 	safe_free(m);
 }
 
-Match *unreal_create_match(MatchType type, char *str, char **error)
+Match *unreal_create_match(MatchType type, const char *str, char **error)
 {
 	Match *m = safe_alloc(sizeof(Match));
 	static char errorbuf[512];
@@ -438,7 +438,7 @@ Match *unreal_create_match(MatchType type, char *str, char **error)
  * @returns 1 if matched, 0 if not.
  * @note These (more logical) return values are opposite to the match_simple() function.
  */
-int unreal_match(Match *m, char *str)
+int unreal_match(Match *m, const char *str)
 {
 	if (m->type == MATCH_SIMPLE)
 	{
@@ -463,7 +463,7 @@ int unreal_match(Match *m, char *str)
 	return 0;
 }
 
-int unreal_match_method_strtoval(char *str)
+int unreal_match_method_strtoval(const char *str)
 {
 	if (!strcmp(str, "regex") || !strcmp(str, "pcre"))
 		return MATCH_PCRE_REGEX;
@@ -489,10 +489,11 @@ char *unreal_match_method_valtostr(int val)
  * Moved here from the censor channel and user mode module
  * (previously was present in both modules, code duplication)
  */
-int fast_badword_match(ConfigItem_badword *badword, char *line)
+int fast_badword_match(ConfigItem_badword *badword, const char *line)
 {
-	char *p;
+	const char *p;
 	int bwlen = strlen(badword->word);
+
 	if ((badword->type & BADW_TYPE_FAST_L) && (badword->type & BADW_TYPE_FAST_R))
 		return (our_strcasestr(line, badword->word) ? 1 : 0);
 
@@ -523,21 +524,20 @@ next:
  * buf is used for the result and max is sizeof(buf).
  * Assumptions[!]: max > 0 AND max > strlen(line)+1
  */
-int fast_badword_replace(ConfigItem_badword *badword, char *line, char *buf, int max)
+int fast_badword_replace(ConfigItem_badword *badword, const char *line, char *buf, int max)
 {
 	/* Some aliases ;P */
 	char *replacew = badword->replace ? badword->replace : REPLACEWORD;
-	char *pold = line, *pnew = buf; /* Pointers to old string and new string */
-	char *poldx = line;
+	const char *pold = line; /* pointer to the old string */
+	const char *poldx = line;
+	char *pnew = buf; /* pointer to the new string */
 	int replacen = -1; /* Only calculated if needed. w00t! saves us a few nanosecs? lol */
 	int searchn = -1;
-	char *startw, *endw;
+	const char *startw, *endw; /* start and end of the word */
 	char *c_eol = buf + max - 1; /* Cached end of (new) line */
 	int run = 1;
 	int cleaned = 0;
 
-	Debug((DEBUG_NOTICE, "replacing %s -> %s in '%s'", badword->word, replacew, line));
-
 	while(run) {
 		pold = our_strcasestr(pold, badword->word);
 		if (!pold)
@@ -617,7 +617,7 @@ int fast_badword_replace(ConfigItem_badword *badword, char *line, char *buf, int
  * the loadbadwords() function.  It's primary use is to filter swearing
  * in both private and public messages
  */
-char *stripbadwords(char *str, ConfigItem_badword *start_bw, int *blocked)
+const char *stripbadwords(const char *str, ConfigItem_badword *start_bw, int *blocked)
 {
 	static char cleanstr[4096];
 	char buf[4096];
@@ -692,14 +692,17 @@ char *stripbadwords(char *str, ConfigItem_badword *start_bw, int *blocked)
 					ret = pcre2_match(this_word->pcre2_expr, ptr, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL); /* run the regex */
 					if (ret > 0)
 					{
-						ircd_log(LOG_ERROR, "pcre2_get_ovector_count: %d", pcre2_get_ovector_count(md));
 						dd = pcre2_get_ovector_pointer(md);
 						start = (int)dd[0];
 						end = (int)dd[1];
 						if ((start < 0) || (end < 0) || (start > strlen(ptr)) || (end > strlen(ptr)+1))
 						{
-							ircd_log(LOG_ERROR, "pcre2_match() returned an ovector with OOB start/end: %d/%d, str (%d): '%s'",
-								(int)start, (int)end, (int)strlen(ptr), ptr);
+							unreal_log(ULOG_FATAL, "main", "BUG_STRIPBADWORDS_PCRE2_MATCH_OOB", NULL,
+							           "[BUG] pcre2_match() returned an ovector with OOB start/end: $start/$end, len $length: '$buf'",
+							           log_data_integer("start", start),
+							           log_data_integer("end", end),
+							           log_data_integer("length", strlen(ptr)),
+							           log_data_string("buf", ptr));
 							abort();
 						}
 						m = end - start;
@@ -743,10 +746,10 @@ char *stripbadwords(char *str, ConfigItem_badword *start_bw, int *blocked)
  * if check_broadness is 1, the function will attempt to determine
  * if the given regex string is too broad (i.e. matches everything)
  */
-char *badword_config_check_regex(char *str, int fastsupport, int check_broadness)
+const char *badword_config_check_regex(const char *str, int fastsupport, int check_broadness)
 {
 	int regex=0;
-	char *tmp;
+	const char *tmp;
 	static char errorbuf[512];
 
 	if (fastsupport)
@@ -787,9 +790,9 @@ char *badword_config_check_regex(char *str, int fastsupport, int check_broadness
 	return NULL;
 }
 
-int badword_config_process(ConfigItem_badword *ca, char *str)
+int badword_config_process(ConfigItem_badword *ca, const char *str)
 {
-	char *tmp;
+	const char *tmp;
 	short regex = 0;
 	int ast_l = 0, ast_r = 0;
 
diff --git a/src/misc.c b/src/misc.c
@@ -32,17 +32,26 @@
 
 static void exit_one_client(Client *, MessageTag *mtags_i, const char *);
 
-static char *months[] = {
+static const char *months[] = {
 	"January", "February", "March", "April",
 	"May", "June", "July", "August",
 	"September", "October", "November", "December"
 };
 
-static char *weekdays[] = {
+static const char *weekdays[] = {
 	"Sunday", "Monday", "Tuesday", "Wednesday",
 	"Thursday", "Friday", "Saturday"
 };
 
+static const char *short_months[12] = {
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+};
+
+static const char *short_weekdays[7] = {
+    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+};
+
 typedef struct {
 	int value;			/** Unique integer value of item */
 	char character;		/** Unique character assigned to item */
@@ -98,167 +107,8 @@ SpamfilterTargetTable spamfiltertargettable[] = {
 /** IRC Statistics (quite useless?) */
 struct IRCStatistics ircstats;
 
-/** Main IRCd logging function.
- * @param flags		One of LOG_* (eg: LOG_ERROR)
- * @param format	Format string
- * @param ...		Arguments
- * @note This function is safe to call at all times. It provides
- *       protection against recursion.
- */
-void ircd_log(int flags, FORMAT_STRING(const char *format), ...)
-{
-	static int last_log_file_warning = 0;
-	static char recursion_trap=0;
-
-	va_list ap;
-	ConfigItem_log *logs;
-	char buf[2048], timebuf[128];
-	struct stat fstats;
-	int written = 0;
-	int n;
-
-	/* Trap infinite recursions to avoid crash if log file is unavailable,
-	 * this will also avoid calling ircd_log from anything else called
-	 */
-	if (recursion_trap == 1)
-		return;
-
-	recursion_trap = 1;
-
-	/* NOTE: past this point you CANNOT just 'return'.
-	 * You must set 'recursion_trap = 0;' before 'return'!
-	 */
-
-	va_start(ap, format);
-	ircvsnprintf(buf, sizeof(buf), format, ap);
-	va_end(ap);
-	snprintf(timebuf, sizeof(timebuf), "[%s] - ", myctime(TStime()));
-
-	RunHook3(HOOKTYPE_LOG, flags, timebuf, buf);
-	strlcat(buf, "\n", sizeof(buf));
-
-	if (!loop.ircd_forked && (flags & LOG_ERROR))
-	{
-#ifdef _WIN32
-		win_log("* %s", buf);
-#else
-		fprintf(stderr, "%s", buf);
-#endif
-	}
-
-	/* In case of './unrealircd configtest': don't write to log file, only to stderr */
-	if (loop.config_test)
-	{
-		recursion_trap = 0;
-		return;
-	}
-
-	for (logs = conf_log; logs; logs = logs->next)
-	{
-		if (!(logs->flags & flags))
-			continue;
-
-#ifdef HAVE_SYSLOG
-		if (logs->file && !strcasecmp(logs->file, "syslog"))
-		{
-			syslog(LOG_INFO, "%s", buf);
-			written++;
-			continue;
-		}
-#endif
-
-		/* This deals with dynamic log file names, such as ircd.%Y-%m-%d.log */
-		if (logs->filefmt)
-		{
-			char *fname = unreal_strftime(logs->filefmt);
-			if (logs->file && (logs->logfd != -1) && strcmp(logs->file, fname))
-			{
-				/* We are logging already and need to switch over */
-				fd_close(logs->logfd);
-				logs->logfd = -1;
-			}
-			safe_strdup(logs->file, fname);
-		}
-
-		/* log::maxsize code */
-		if (logs->maxsize && (stat(logs->file, &fstats) != -1) && fstats.st_size >= logs->maxsize)
-		{
-			char oldlog[512];
-			if (logs->logfd == -1)
-			{
-				/* Try to open, so we can write the 'Max file size reached' message. */
-				logs->logfd = fd_fileopen(logs->file, O_CREAT|O_APPEND|O_WRONLY);
-			}
-			if (logs->logfd != -1)
-			{
-				if (write(logs->logfd, "Max file size reached, starting new log file\n", 45) < 0)
-				{
-					/* We already handle the unable to write to log file case for normal data.
-					 * I think we can get away with not handling this one.
-					 */
-					;
-				}
-				fd_close(logs->logfd);
-			}
-			logs->logfd = -1;
-
-			/* Rename log file to xxxxxx.old */
-			snprintf(oldlog, sizeof(oldlog), "%s.old", logs->file);
-			unlink(oldlog); /* windows rename cannot overwrite, so unlink here.. ;) */
-			rename(logs->file, oldlog);
-		}
-
-		/* generic code for opening log if not open yet.. */
-		if (logs->logfd == -1)
-		{
-			logs->logfd = fd_fileopen(logs->file, O_CREAT|O_APPEND|O_WRONLY);
-			if (logs->logfd == -1)
-			{
-				if (!loop.ircd_booted)
-				{
-					config_status("WARNING: Unable to write to '%s': %s", logs->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.", logs->file, strerror(ERRNO));
-						last_log_file_warning = TStime();
-					}
-				}
-				continue;
-			}
-		}
-
-		/* Now actually WRITE to the log... */
-		if (write(logs->logfd, timebuf, strlen(timebuf)) < 0)
-		{
-			/* Let's ignore any write errors for this one. Next write() will catch it... */
-			;
-		}
-		n = write(logs->logfd, buf, strlen(buf));
-		if (n == strlen(buf))
-		{
-			written++;
-		}
-		else
-		{
-			if (!loop.ircd_booted)
-			{
-				config_status("WARNING: Unable to write to '%s': %s", logs->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.", logs->file, strerror(ERRNO));
-					last_log_file_warning = TStime();
-				}
-			}
-		}
-	}
-
-	recursion_trap = 0;
-}
-
 /** Returns the date in rather long string */
-char *long_date(time_t clock)
+const char *long_date(time_t clock)
 {
 	static char buf[80], plus;
 	struct tm *lt, *gm;
@@ -300,10 +150,9 @@ char *long_date(time_t clock)
  * @param buf  The buffer to store the string (minimum size: 128 bytes),
  *             or NULL to use temporary static storage.
  */
-char *short_date(time_t ts, char *buf)
+const char *short_date(time_t ts, char *buf)
 {
 	struct tm *t = gmtime(&ts);
-	char *timestr;
 	static char retbuf[128];
 
 	if (!buf)
@@ -313,17 +162,14 @@ char *short_date(time_t ts, char *buf)
 	if (!t)
 		return NULL;
 
-	timestr = asctime(t);
-	if (!timestr)
+	if (!strftime(buf, 128, "%a %b %d %H:%M:%S %Y", t))
 		return NULL;
 
-	strlcpy(buf, timestr, 128);
-	stripcrlf(buf);
 	return buf;
 }
 
 /** Return a string with the "pretty date" - yeah, another variant */
-char *pretty_date(time_t t)
+const char *pretty_date(time_t t)
 {
 	static char buf[128];
 	struct tm *tm;
@@ -347,62 +193,53 @@ char *pretty_date(time_t t)
  * string marker (`\-`).  returns the 'fixed' string or "*" if the string
  * was NULL length or a NULL pointer.
  */
-char *check_string(char *s)
+const char *check_string(const char *s)
 {
+	static char buf[512];
 	static char star[2] = "*";
-	char *str = s;
+	const char *str = s;
 
 	if (BadPtr(s))
 		return star;
 
 	for (; *s; s++)
+	{
 		if (isspace(*s))
 		{
-			*s = '\0';
+			/* Because this is an unlikely scenario, we have
+			 * delayed the copy until here:
+			 */
+			strlncpy(buf, s, sizeof(buf), s - str);
+			str = buf;
 			break;
 		}
+	}
 
 	return (BadPtr(str)) ? star : str;
 }
 
 /** Create a user@host based on the provided name and host */
-char *make_user_host(char *name, char *host)
+char *make_user_host(const char *name, const char *host)
 {
 	static char namebuf[USERLEN + HOSTLEN + 6];
-	char *s = namebuf;
 
-	memset(namebuf, 0, sizeof(namebuf));
-	name = check_string(name);
-	strlcpy(s, name, USERLEN + 1);
-	s += strlen(s);
-	*s++ = '@';
-	host = check_string(host);
-	strlcpy(s, host, HOSTLEN + 1);
-	s += strlen(s);
-	*s = '\0';
-	return (namebuf);
+	strlncpy(namebuf, check_string(name), sizeof(namebuf), USERLEN+1);
+	strlcat(namebuf, "@", sizeof(namebuf));
+	strlncat(namebuf, check_string(host), sizeof(namebuf), HOSTLEN+1);
+	return namebuf;
 }
 
 /** Create a nick!user@host string based on the provided variables.
  * If any of the variables are NULL, it becomes * (asterisk)
  * This is the reentrant safe version.
  */
-char *make_nick_user_host_r(char *namebuf, char *nick, char *name, char *host)
-{
-	char *s = namebuf;
-
-	nick = check_string(nick);
-	strlcpy(namebuf, nick, NICKLEN + 1);
-	s += strlen(s);
-	*s++ = '!';
-	name = check_string(name);
-	strlcpy(s, name, USERLEN + 1);
-	s += strlen(s);
-	*s++ = '@';
-	host = check_string(host);
-	strlcpy(s, host, HOSTLEN + 1);
-	s += strlen(s);
-	*s = '\0';
+char *make_nick_user_host_r(char *namebuf, size_t namebuflen, const char *nick, const char *name, const char *host)
+{
+	strlncpy(namebuf, check_string(nick), namebuflen, NICKLEN+1);
+	strlcat(namebuf, "!", namebuflen);
+	strlncat(namebuf, check_string(name), namebuflen, USERLEN+1);
+	strlcat(namebuf, "@", namebuflen);
+	strlncat(namebuf, check_string(host), namebuflen, HOSTLEN+1);
 	return namebuf;
 }
 
@@ -410,18 +247,18 @@ char *make_nick_user_host_r(char *namebuf, char *nick, char *name, char *host)
  * If any of the variables are NULL, it becomes * (asterisk)
  * This version uses static storage.
  */
-char *make_nick_user_host(char *nick, char *name, char *host)
+char *make_nick_user_host(const char *nick, const char *name, const char *host)
 {
 	static char namebuf[NICKLEN + USERLEN + HOSTLEN + 24];
 
-	return make_nick_user_host_r(namebuf, nick, name, host);
+	return make_nick_user_host_r(namebuf, sizeof(namebuf), nick, name, host);
 }
 
 
 /** Similar to ctime() but without a potential newline and
  * also takes a time_t value rather than a pointer.
  */
-char *myctime(time_t value)
+const char *myctime(time_t value)
 {
 	static char buf[28];
 	char *p;
@@ -457,7 +294,7 @@ char *myctime(time_t value)
 **	to internal buffer (nbuf). *NEVER* use the returned pointer
 **	to modify what it points!!!
 */
-char *get_client_name(Client *client, int showip)
+const char *get_client_name(Client *client, int showip)
 {
 	static char nbuf[HOSTLEN * 2 + USERLEN + 5];
 
@@ -482,7 +319,7 @@ char *get_client_name(Client *client, int showip)
 	return client->name;
 }
 
-char *get_client_host(Client *client)
+const char *get_client_host(Client *client)
 {
 	static char nbuf[HOSTLEN * 2 + USERLEN + 5];
 
@@ -500,9 +337,9 @@ char *get_client_host(Client *client)
 /*
  * Set sockhost to 'host'. Skip the user@ part of 'host' if necessary.
  */
-void set_sockhost(Client *client, char *host)
+void set_sockhost(Client *client, const char *host)
 {
-	char *s;
+	const char *s;
 	if ((s = strchr(host, '@')))
 		s++;
 	else
@@ -516,7 +353,7 @@ int on_dccallow_list(Client *to, Client *from)
 	Link *lp;
 
 	for(lp = to->user->dccallow; lp; lp = lp->next)
-		if(lp->flags == DCC_LINK_ME && lp->value.client == from)
+		if (lp->flags == DCC_LINK_ME && lp->value.client == from)
 			return 1;
 	return 0;
 }
@@ -538,11 +375,11 @@ void remove_dcc_references(Client *client)
 		acptr = lp->value.client;
 		for(found = 0, lpp = &(acptr->user->dccallow); *lpp; lpp=&((*lpp)->next))
 		{
-			if(lp->flags == (*lpp)->flags)
+			if (lp->flags == (*lpp)->flags)
 				continue; /* match only opposite types for sanity */
-			if((*lpp)->value.client == client)
+			if ((*lpp)->value.client == client)
 			{
-				if((*lpp)->flags == DCC_LINK_ME)
+				if ((*lpp)->flags == DCC_LINK_ME)
 				{
 					sendto_one(acptr, NULL, ":%s %d %s :%s has been removed from "
 						"your DCC allow list for signing off",
@@ -556,10 +393,13 @@ void remove_dcc_references(Client *client)
 			}
 		}
 
-		if(!found)
-			sendto_realops("[BUG] remove_dcc_references:  %s was in dccallowme "
-				"list[%d] of %s but not in dccallowrem list!",
-				acptr->name, lp->flags, client->name);
+		if (!found)
+		{
+			unreal_log(ULOG_WARNING, "main", "BUG_REMOVE_DCC_REFERENCES", acptr,
+			           "[BUG] remove_dcc_references: $client was in dccallowme "
+			           "list of $existing_client but not in dccallowrem list!",
+			           log_data_client("existing_client", client));
+		}
 
 		free_link(lp);
 		lp = nextlp;
@@ -567,28 +407,6 @@ void remove_dcc_references(Client *client)
 }
 
 /*
- * Recursively send QUITs and SQUITs for cptr and all of it's dependent
- * clients.  A server needs the client QUITs if it does not support NOQUIT.
- *    - kaniini
- */
-static void recurse_send_quits(Client *cptr, Client *client, Client *from, Client *to,
-                               MessageTag *mtags, const char *comment, const char *splitstr)
-{
-	Client *acptr, *next;
-
-	list_for_each_entry_safe(acptr, next, &global_server_list, client_node)
-	{
-		if (acptr->srvptr != client)
-			continue;
-
-		recurse_send_quits(cptr, acptr, from, to, mtags, comment, splitstr);
-	}
-
-	if (cptr == client && to != from && !(to->direction && (to->direction == from)))
-		sendto_one(to, mtags, "SQUIT %s :%s", client->name, comment);
-}
-
-/*
  * Remove all clients that depend on source_p; assumes all (S)QUITs have
  * already been sent.  we make sure to exit a server's dependent clients
  * and servers before the server itself; exit_one_client takes care of
@@ -600,7 +418,7 @@ static void recurse_remove_clients(Client *client, MessageTag *mtags, const char
 
 	list_for_each_entry_safe(acptr, next, &client_list, client_node)
 	{
-		if (acptr->srvptr != client)
+		if (acptr->uplink != client)
 			continue;
 
 		exit_one_client(acptr, mtags, comment);
@@ -608,7 +426,7 @@ static void recurse_remove_clients(Client *client, MessageTag *mtags, const char
 
 	list_for_each_entry_safe(acptr, next, &global_server_list, client_node)
 	{
-		if (acptr->srvptr != client)
+		if (acptr->uplink != client)
 			continue;
 
 		recurse_remove_clients(acptr, mtags, comment);
@@ -626,7 +444,10 @@ static void remove_dependents(Client *client, Client *from, MessageTag *mtags, c
 	Client *acptr;
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
-		recurse_send_quits(client, client, from, acptr, mtags, comment, splitstr);
+	{
+		if (acptr != from && !(acptr->direction && (acptr->direction == from)))
+			sendto_one(acptr, mtags, "SQUIT %s :%s", client->name, comment);
+	}
 
 	recurse_remove_clients(client, mtags, splitstr);
 }
@@ -647,18 +468,14 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com
 		MessageTag *mtags_o = NULL;
 
 		if (!MyUser(client))
-			RunHook3(HOOKTYPE_REMOTE_QUIT, client, mtags_i, comment);
+			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);
 		free_message_tags(mtags_o);
 
 		while ((mp = client->user->channel))
-			remove_user_from_channel(client, mp->channel);
-
-		/* Clean up invitefield */
-		while ((lp = client->user->invited))
-			del_invite(client, lp->value.channel);
+			remove_user_from_channel(client, mp->channel, 1);
 		/* again, this is all that is needed */
 
 		/* Clean up dccallow list and (if needed) notify other clients
@@ -687,8 +504,6 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com
 	}
 	if (*client->name)
 		del_from_client_hash_table(client->name, client);
-	if (IsUser(client))
-		hash_check_watch(client, RPL_LOGOFF);
 	if (remote_rehash_client == client)
 		remote_rehash_client = NULL; /* client did a /REHASH and QUIT before rehash was complete */
 	remove_client_from_list(client);
@@ -699,8 +514,25 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com
  * @param recv_mtags  Message tags to use as a base (if any).
  * @param comment     The (s)quit message
  */
-void exit_client(Client *client, MessageTag *recv_mtags, char *comment)
+void exit_client(Client *client, MessageTag *recv_mtags, const char *comment)
+{
+	exit_client_ex(client, client->direction, recv_mtags, comment);
+}
+
+/** Exit this IRC client, and all the dependents (users, servers) if this is a server.
+ * @param client        The client to exit.
+ * @param recv_mtags  Message tags to use as a base (if any).
+ * @param comment     The (s)quit message
+ */
+void exit_client_fmt(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...)
 {
+	char comment[512];
+
+	va_list vl;
+	va_start(vl, pattern);
+	vsnprintf(comment, sizeof(comment), pattern, vl);
+	va_end(vl);
+
 	exit_client_ex(client, client->direction, recv_mtags, comment);
 }
 
@@ -709,7 +541,7 @@ void exit_client(Client *client, MessageTag *recv_mtags, char *comment)
  * @param recv_mtags  Message tags to use as a base (if any).
  * @param comment     The (s)quit message
  */
-void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char *comment)
+void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment)
 {
 	long long on_for;
 	ConfigItem_listen *listen_conf;
@@ -741,23 +573,25 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char
 		}
 		if (IsUser(client))
 			irccounts.me_clients--;
-		if (client->serv && client->serv->conf)
+		if (client->server && client->server->conf)
 		{
-			client->serv->conf->refcount--;
-			Debug((DEBUG_ERROR, "reference count for %s (%s) is now %d",
-				client->name, client->serv->conf->servername, client->serv->conf->refcount));
-			if (!client->serv->conf->refcount
-			  && client->serv->conf->flag.temporary)
+			client->server->conf->refcount--;
+			if (!client->server->conf->refcount
+			  && client->server->conf->flag.temporary)
 			{
-				Debug((DEBUG_ERROR, "deleting temporary block %s", client->serv->conf->servername));
-				delete_linkblock(client->serv->conf);
-				client->serv->conf = NULL;
+				delete_linkblock(client->server->conf);
+				client->server->conf = NULL;
 			}
 		}
 		if (IsServer(client))
 		{
 			irccounts.me_servers--;
-			ircd_log(LOG_SERVER, "SQUIT %s (%s)", client->name, comment);
+			if (!IsServerDisconnectLogged(client))
+			{
+				unreal_log(ULOG_ERROR, "link", "LINK_DISCONNECTED", client,
+					   "Lost server link to $client [$client.ip]: $reason",
+					   log_data_string("reason", comment));
+			}
 		}
 		free_pending_net(client);
 		if (client->local->listener)
@@ -774,24 +608,17 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char
 		SetClosing(client);
 		if (IsUser(client))
 		{
-			RunHook3(HOOKTYPE_LOCAL_QUIT, client, recv_mtags, comment);
-			sendto_connectnotice(client, 1, comment);
-			/* Clean out list and watch structures -Donwulff */
-			hash_del_watch_list(client);
-			on_for = TStime() - client->local->firsttime;
-			if (IsHidden(client))
-				ircd_log(LOG_CLIENT, "Disconnect - (%lld:%lld:%lld) %s!%s@%s [%s] [vhost: %s] (%s)",
-					on_for / 3600, (on_for % 3600) / 60, on_for % 60,
-					client->name, client->user->username,
-					client->user->realhost, GetIP(client), client->user->virthost, comment);
-			else
-				ircd_log(LOG_CLIENT, "Disconnect - (%lld:%lld:%lld) %s!%s@%s [%s] (%s)",
-					on_for / 3600, (on_for % 3600) / 60, on_for % 60,
-					client->name, client->user->username, client->user->realhost, GetIP(client), comment);
+			long connected_time = TStime() - client->local->creationtime;
+			RunHook(HOOKTYPE_LOCAL_QUIT, client, recv_mtags, comment);
+			unreal_log(ULOG_INFO, "connect", "LOCAL_CLIENT_DISCONNECT", client,
+				   "Client exiting: $client ($client.user.username@$client.hostname) [$client.ip] ($reason)",
+				   log_data_string("extended_client_info", get_connect_extinfo(client)),
+				   log_data_string("reason", comment),
+				   log_data_integer("connected_time", connected_time));
 		} else
 		if (IsUnknown(client))
 		{
-			RunHook3(HOOKTYPE_UNKUSER_QUIT, client, recv_mtags, comment);
+			RunHook(HOOKTYPE_UNKUSER_QUIT, client, recv_mtags, comment);
 		}
 
 		if (client->local->fd >= 0 && !IsConnecting(client))
@@ -803,8 +630,14 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char
 	}
 	else if (IsUser(client) && !IsULine(client))
 	{
-		if (client->srvptr != &me)
-			sendto_fconnectnotice(client, 1, comment);
+		if (client->uplink != &me)
+		{
+			unreal_log(ULOG_INFO, "connect", "REMOTE_CLIENT_DISCONNECT", client,
+				   "Client exiting: $client ($client.user.username@$client.hostname) [$client.ip] ($reason)",
+				   log_data_string("extended_client_info", get_connect_extinfo(client)),
+				   log_data_string("reason", comment),
+				   log_data_string("from_server_name", client->user->server));
+		}
 	}
 
 	/*
@@ -816,16 +649,16 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char
 	{
 		char splitstr[HOSTLEN + HOSTLEN + 2];
 
-		assert(client->serv != NULL && client->srvptr != NULL);
+		assert(client->server != NULL && client->uplink != NULL);
 
 		if (FLAT_MAP)
 			strlcpy(splitstr, "*.net *.split", sizeof splitstr);
 		else
-			ircsnprintf(splitstr, sizeof splitstr, "%s %s", client->srvptr->name, client->name);
+			ircsnprintf(splitstr, sizeof splitstr, "%s %s", client->uplink->name, client->name);
 
 		remove_dependents(client, origin, recv_mtags, comment, splitstr);
 
-		RunHook2(HOOKTYPE_SERVER_QUIT, client, recv_mtags);
+		RunHook(HOOKTYPE_SERVER_QUIT, client, recv_mtags);
 	}
 	else if (IsUser(client) && !IsKilled(client))
 	{
@@ -845,7 +678,7 @@ void initstats(void)
 }
 
 /** Verify operator count, to catch bugs introduced by flawed services */
-void verify_opercount(Client *orig, char *tag)
+void verify_opercount(Client *orig, const char *tag)
 {
 	int counted = 0;
 	Client *client;
@@ -858,31 +691,44 @@ void verify_opercount(Client *orig, char *tag)
 	}
 	if (counted == irccounts.operators)
 		return;
-	snprintf(text, sizeof(text), "[BUG] operator count bug! value in /lusers is '%d', we counted '%d', "
-	               "user='%s', userserver='%s', tag=%s. Corrected. ",
-	               irccounts.operators, counted, orig->name,
-	               orig->srvptr ? orig->srvptr->name : "<null>", tag ? tag : "<null>");
-#ifdef DEBUGMODE
-	sendto_realops("%s", text);
-#endif
-	ircd_log(LOG_ERROR, "%s", text);
+	unreal_log(ULOG_WARNING, "main", "BUG_LUSERS_OPERS", orig,
+	           "[BUG] Operator count bug at $where! Value in /LUSERS is $opers, "
+	           "we counted $counted_opers, "
+	           "triggered by $client.details on $client.user.servername",
+	           log_data_integer("opers", irccounts.operators),
+	           log_data_integer("counted_opers", counted),
+	           log_data_string("where", tag));
 	irccounts.operators = counted;
 }
 
 /** Check if the specified hostname does not contain forbidden characters.
- * RETURNS:
- * 1 if ok, 0 if rejected.
+ * @param host		The host name to check
+ * @param strict	If set to 1 then we also check if the hostname
+ *                      resembles an IP address (eg contains ':') and
+ *                      some other stuff that we don't consider valid
+ *                      in actual DNS names (eg '/').
+ * @returns 1 if valid, 0 if not.
  */
-int valid_host(char *host)
+int valid_host(const char *host, int strict)
 {
-	char *p;
+	const char *p;
+
+	if (!*host)
+		return 0; /* must at least contain something */
 
 	if (strlen(host) > HOSTLEN)
 		return 0; /* too long hosts are invalid too */
 
-	for (p=host; *p; p++)
-		if (!isalnum(*p) && (*p != '_') && (*p != '-') && (*p != '.') && (*p != ':') && (*p != '/'))
-			return 0;
+	if (strict)
+	{
+		for (p=host; *p; p++)
+			if (!isalnum(*p) && !strchr("_-.", *p))
+				return 0;
+	} else {
+		for (p=host; *p; p++)
+			if (!isalnum(*p) && !strchr("_-.:/", *p))
+				return 0;
+	}
 
 	return 1;
 }
@@ -890,7 +736,7 @@ int valid_host(char *host)
 /*|| BAN ACTION ROUTINES FOLLOW ||*/
 
 /** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */
-BanAction banact_stringtoval(char *s)
+BanAction banact_stringtoval(const char *s)
 {
 	BanActTable *b;
 
@@ -923,7 +769,7 @@ char banact_valtochar(BanAction val)
 }
 
 /** Converts a banaction value (eg: BAN_ACT_KLINE) to a string (eg: "kline") */
-char *banact_valtostring(BanAction val)
+const char *banact_valtostring(BanAction val)
 {
 	BanActTable *b;
 
@@ -936,7 +782,7 @@ char *banact_valtostring(BanAction val)
 /*|| BAN TARGET ROUTINES FOLLOW ||*/
 
 /** Extract target flags from string 's'. */
-int spamfilter_gettargets(char *s, Client *client)
+int spamfilter_gettargets(const char *s, Client *client)
 {
 SpamfilterTargetTable *e;
 int flags = 0;
@@ -959,7 +805,7 @@ int flags = 0;
 }
 
 /** Convert a string with a targetname to an integer value */
-int spamfilter_getconftargets(char *s)
+int spamfilter_getconftargets(const char *s)
 {
 SpamfilterTargetTable *e;
 
@@ -972,9 +818,9 @@ SpamfilterTargetTable *e;
 /** Create a string with (multiple) targets from an integer mask */
 char *spamfilter_target_inttostring(int v)
 {
-static char buf[128];
-SpamfilterTargetTable *e;
-char *p = buf;
+	static char buf[128];
+	SpamfilterTargetTable *e;
+	char *p = buf;
 
 	for (e = &spamfiltertargettable[0]; e->value; e++)
 		if (v & e->value)
@@ -1033,7 +879,7 @@ char *unreal_encodespace(char *s)
 }
 
 /** This is basically only used internally by match_spamfilter()... */
-char *cmdname_by_spamftarget(int target)
+const char *cmdname_by_spamftarget(int target)
 {
 	SpamfilterTargetTable *e;
 
@@ -1044,7 +890,7 @@ char *cmdname_by_spamftarget(int target)
 }
 
 /** Returns 1 if this is a channel from set::auto-join or set::oper-auto-join */
-int is_autojoin_chan(char *chname)
+int is_autojoin_chan(const char *chname)
 {
 	char buf[512];
 	char *p, *name;
@@ -1070,40 +916,6 @@ int is_autojoin_chan(char *chname)
 	return 0;
 }
 
-/** Convert a character like 'o' to the corresponding channel flag
- *  like CHFL_CHANOP.
- * @param c   The mode character. The only valid values are: vhoaq
- * @returns One of CHFL_* or 0 if an invalid mode character is specified.
- */
-int char_to_channelflag(char c)
-{
-	if (c == 'v')
-		return CHFL_VOICE;
-	else if (c == 'h')
-		return CHFL_HALFOP;
-	else if (c == 'o')
-		return CHFL_CHANOP;
-	else if (c == 'a')
-		return CHFL_CHANADMIN;
-	else if (c == 'q')
-		return CHFL_CHANOWNER;
-	return 0;
-}
-
-// FIXME: should detect <U5 ;)
-int mixed_network(void)
-{
-	Client *client;
-
-	list_for_each_entry(client, &server_list, special_node)
-	{
-		if (!IsServer(client) || IsULine(client))
-			continue; /* skip u-lined servers (=non-unreal, unless you configure your ulines badly, that is) */
-		// uh.. right.. bit hard to detect u4 this way now :D
-	}
-	return 0;
-}
-
 /** Free all masks in the mask list */
 void unreal_delete_masks(ConfigItem_mask *m)
 {
@@ -1125,10 +937,10 @@ static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce)
 	ConfigItem_mask *m = safe_alloc(sizeof(ConfigItem_mask));
 
 	/* Since we allow both mask "xyz"; and mask { abc; def; };... */
-	if (ce->ce_vardata)
-		safe_strdup(m->mask, ce->ce_vardata);
+	if (ce->value)
+		safe_strdup(m->mask, ce->value);
 	else
-		safe_strdup(m->mask, ce->ce_varname);
+		safe_strdup(m->mask, ce->name);
 
 	add_ListItem((ListStruct *)m, (ListStruct **)head);
 }
@@ -1136,10 +948,10 @@ static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce)
 /** Add mask entries from config */
 void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce)
 {
-	if (ce->ce_entries)
+	if (ce->items)
 	{
 		ConfigEntry *cep;
-		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+		for (cep = ce->items; cep; cep = cep->next)
 			unreal_add_mask(head, cep);
 	} else
 	{
@@ -1147,29 +959,108 @@ void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce)
 	}
 }
 
-/** Check if a client matches any of the masks in the mask list */
-int unreal_mask_match(Client *client, ConfigItem_mask *m)
+/** Check if a client matches any of the masks in the mask list.
+ * The following rules apply:
+ * - If you have only negating entries, like '!abc' and '!def', then
+ *   we assume an implicit * rule first, since that is clearly what
+ *   the user wants.
+ * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the
+ *   implicit * is dropped and we assume you only want to match *.com,
+ *   with the exception of irc1*.com and irc2*.com.
+ * - If you only have normal entries without ! then things are
+ *   as they always are.
+ * @param client	The client to run the mask match against
+ * @param mask		The mask entry from the config file
+ * @returns 1 on match, 0 on non-match.
+ */
+int unreal_mask_match(Client *client, ConfigItem_mask *mask)
 {
-	for (; m; m = m->next)
+	int retval = 1;
+	ConfigItem_mask *m;
+
+	if (!mask)
+		return 0; /* Empty mask block is no match */
+
+	/* First check normal matches (without ! prefix) */
+	for (m = mask; m; m = m->next)
 	{
-		/* With special support for '!' prefix (negative matching like "!192.168.*") */
-		if (m->mask[0] == '!')
+		if (m->mask[0] != '!')
 		{
-			if (!match_user(m->mask+1, client, MATCH_CHECK_REAL))
-				return 1;
-		} else {
-			if (match_user(m->mask, client, MATCH_CHECK_REAL))
-				return 1;
+			retval = 0; /* no implicit * */
+			if (match_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
+			{
+				retval = 1;
+				break;
+			}
 		}
 	}
 
-	return 0;
+	if (retval)
+	{
+		/* We matched. Check for exceptions (with ! prefix) */
+		for (m = mask; m; m = m->next)
+		{
+			if ((m->mask[0] == '!') && match_user(m->mask+1, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
+				return 0;
+		}
+	}
+
+	return retval;
+}
+
+/** Check if a string matches any of the masks in the mask list.
+ * The following rules apply:
+ * - If you have only negating entries, like '!abc' and '!def', then
+ *   we assume an implicit * rule first, since that is clearly what
+ *   the user wants.
+ * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the
+ *   implicit * is dropped and we assume you only want to match *.com,
+ *   with the exception of irc1*.com and irc2*.com.
+ * - If you only have normal entries without ! then things are
+ *   as they always are.
+ * @param name	The name to run the mask matching on
+ * @param mask	The mask entry from the config file
+ * @returns 1 on match, 0 on non-match.
+ */
+int unreal_mask_match_string(const char *name, ConfigItem_mask *mask)
+{
+	int retval = 1;
+	ConfigItem_mask *m;
+
+	if (!mask)
+		return 0; /* Empty mask block is no match */
+
+	/* First check normal matches (without ! prefix) */
+	for (m = mask; m; m = m->next)
+	{
+		if (m->mask[0] != '!')
+		{
+			retval = 0; /* no implicit * */
+			if (match_simple(m->mask, name))
+			{
+				retval = 1;
+				break;
+			}
+		}
+	}
+
+	if (retval)
+	{
+		/* We matched. Check for exceptions (with ! prefix) */
+		for (m = mask; m; m = m->next)
+		{
+			if ((m->mask[0] == '!') && match_simple(m->mask+1, name))
+				return 0;
+		}
+	}
+
+	return retval;
 }
 
 /** Our own strcasestr implementation because strcasestr is
  * often not available or is not working correctly.
  */
-char *our_strcasestr(char *haystack, char *needle)
+char *our_strcasestr(const char *haystack, const char *needle)
 {
 	int i;
 	int nlength = strlen(needle);
@@ -1182,12 +1073,12 @@ char *our_strcasestr(char *haystack, char *needle)
 		return NULL;
 
 	if (nlength <= 0)
-		return haystack;
+		return (char *)haystack;
 
 	for (i = 0; i <= (hlength - nlength); i++)
 	{
 		if (strncasecmp (haystack + i, needle, nlength) == 0)
-			return haystack + i;
+			return (char *)(haystack + i);
 	}
 
 	return NULL; /* not found */
@@ -1203,7 +1094,7 @@ char *our_strcasestr(char *haystack, char *needle)
  * @param from		Who added this entry
  * @param skip		Which server(-side) to skip broadcasting this entry to.
  */
-int swhois_add(Client *client, char *tag, int priority, char *swhois, Client *from, Client *skip)
+int swhois_add(Client *client, const char *tag, int priority, const char *swhois, Client *from, Client *skip)
 {
 	SWhois *s;
 
@@ -1237,7 +1128,7 @@ int swhois_add(Client *client, char *tag, int priority, char *swhois, Client *fr
  * @param skip		Which server(-side) to skip broadcasting this entry to.
  * @note If you use swhois "*" then it will remove all swhois titles for that tag
  */
-int swhois_delete(Client *client, char *tag, char *swhois, Client *from, Client *skip)
+int swhois_delete(Client *client, const char *tag, const char *swhois, Client *from, Client *skip)
 {
 	SWhois *s, *s_next;
 	int ret = -1; /* default to 'not found' */
@@ -1277,8 +1168,6 @@ int IsWebsocket(Client *client)
 	return (MyConnect(client) && moddata_client(client, md).ptr) ? 1 : 0;
 }
 
-extern void send_raw_direct(Client *user, FORMAT_STRING(const char *pattern), ...);
-
 /** Generic function to inform the user he/she has been banned.
  * @param client   The affected client.
  * @param bantype  The ban type, such as: "K-Lined", "G-Lined" or "realname".
@@ -1290,7 +1179,7 @@ extern void send_raw_direct(Client *user, FORMAT_STRING(const char *pattern), ..
  *
  * @note This function will call exit_client() appropriately.
  */
-void banned_client(Client *client, char *bantype, char *reason, int global, int noexit)
+void banned_client(Client *client, const char *bantype, const char *reason, int global, int noexit)
 {
 	char buf[512];
 	char *fmt = global ? iConf.reject_message_gline : iConf.reject_message_kline;
@@ -1369,7 +1258,7 @@ char *mystpcpy(char *dst, const char *src)
  *         so similar to what strlen() would have returned.
  * @note Caller must ensure that the buffer 'buf' is of sufficient size.
  */
-size_t add_sjsby(char *buf, char *setby, time_t seton)
+size_t add_sjsby(char *buf, const char *setby, time_t seton)
 {
 	char tbuf[32];
 	char *p = buf;
@@ -1399,14 +1288,14 @@ size_t add_sjsby(char *buf, char *setby, time_t seton)
  * sendto_server(client, 0, 0, recv_mtags, ":%s SOMECOMMAND %s", client->name, buf);
  * @endcode
  */
-void concat_params(char *buf, int len, int parc, char *parv[])
+void concat_params(char *buf, int len, int parc, const char *parv[])
 {
 	int i;
 
 	*buf = '\0';
 	for (i = 1; i < parc; i++)
 	{
-		char *param = parv[i];
+		const char *param = parv[i];
 
 		if (!param)
 			break;
@@ -1476,7 +1365,6 @@ void new_message(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list)
  * This function calls modules so they can add tags, such as:
  * msgid, time and account.
  * This special version deals in a special way with msgid in particular.
- * TODO: document
  * The pattern and vararg create a 'signature', this is normally
  * identical to the message that is sent to clients (end-users).
  * For example ":xyz JOIN #chan".
@@ -1509,7 +1397,7 @@ void parse_message_tags_default_handler(Client *client, char **str, MessageTag *
  * This is only used if the 'mtags' module is NOT loaded,
  * which would be quite unusual, but possible.
  */
-char *mtags_to_string_default_handler(MessageTag *m, Client *client)
+const char *mtags_to_string_default_handler(MessageTag *m, Client *client)
 {
 	return NULL;
 }
@@ -1568,6 +1456,11 @@ void labeled_response_force_end_default_handler(void)
 {
 }
 
+/** Ad default handler for if the slog module is not loaded */
+void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized)
+{
+}
+
 /** 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
@@ -1612,16 +1505,10 @@ time_t server_time_to_unix_time(const char *tbuf)
 	time_t ret;
 
 	if (!tbuf)
-	{
-		ircd_log(LOG_ERROR, "[BUG] server_time_to_unix_time() failed for NULL item. Incorrect S2S traffic?");
 		return 0;
-	}
 
 	if (strlen(tbuf) < 20)
-	{
-		ircd_log(LOG_ERROR, "[BUG] server_time_to_unix_time() failed for short item '%s'", tbuf);
 		return 0;
-	}
 
 	memset(&tm, 0, sizeof(tm));
 	ret = sscanf(tbuf, "%d-%d-%dT%d:%d:%d.%dZ",
@@ -1634,10 +1521,7 @@ time_t server_time_to_unix_time(const char *tbuf)
 		&dontcare);
 
 	if (ret != 7)
-	{
-		ircd_log(LOG_ERROR, "[BUG] server_time_to_unix_time() failed for '%s'", tbuf);
 		return 0;
-	}
 
 	tm.tm_year -= 1900;
 	tm.tm_mon -= 1;
@@ -1646,6 +1530,68 @@ time_t server_time_to_unix_time(const char *tbuf)
 	return ret;
 }
 
+/** Convert an RFC 2616 timestamp (used in HTTP headers) to UNIX time */
+time_t rfc2616_time_to_unix_time(const char *tbuf)
+{
+	struct tm tm;
+	int dontcare = 0;
+	time_t ret;
+	char month[8];
+	int i;
+
+	if (!tbuf)
+		return 0;
+
+	if (strlen(tbuf) < 20)
+		return 0;
+
+	memset(&tm, 0, sizeof(tm));
+	*month = '\0';
+	ret = sscanf(tbuf, "%*[a-zA-Z,] %d %3s %d %d:%d:%d",
+	             &tm.tm_mday, month, &tm.tm_year,
+	             &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+	if (ret < 6)
+		return 0;
+
+	for (i=0; i < 12; i++)
+	{
+		if (!strcmp(short_months[i], month))
+		{
+			tm.tm_mon = i;
+			break;
+		}
+	}
+	if (i == 12)
+		return 0; /* Month not found */
+	if (tm.tm_year < 1900)
+		return 0;
+
+	tm.tm_year -= 1900;
+	ret = my_timegm(&tm);
+	return ret; /* can still be 0 */
+}
+
+/** Returns an RFC 2616 timestamp (used in HTTP headers) */
+const char *rfc2616_time(time_t clock)
+{
+	static char buf[80], plus;
+	struct tm *lt, *gm;
+	struct tm gmbuf;
+	int  minswest;
+
+	if (!clock)
+		time(&clock);
+	gm = gmtime(&clock);
+
+	snprintf(buf, sizeof(buf),
+	         "%s, %02d %.3s %4d %02d:%02d:%02d GMT",
+	         short_weekdays[gm->tm_wday], gm->tm_mday, short_months[gm->tm_mon],
+	         gm->tm_year + 1900, gm->tm_hour, gm->tm_min, gm->tm_sec);
+
+	return buf;
+}
+
 /** Write a 64 bit integer to a file.
  * @param fd   File descriptor
  * @param t    The value to write
@@ -1735,7 +1681,7 @@ int write_data(FILE *fd, const void *buf, size_t len)
  *        Note that 'x' can safely be NULL.
  * @returns 1 on success, 0 on failure.
  */
-int write_str(FILE *fd, char *x)
+int write_str(FILE *fd, const char *x)
 {
 	uint16_t len;
 
@@ -1814,19 +1760,31 @@ void binarytohex(void *data, size_t len, char *str)
 	str[n] = '\0';
 }
 
-/** Generates an MD5 checksum.
+/** Generates an MD5 checksum - binary version.
  * @param mdout[out] Buffer to store result in, the result will be 16 bytes in binary
  *                   (not ascii printable!).
  * @param src[in]    The input data used to generate the checksum.
  * @param n[in]      Length of data.
+ * @deprecated       The MD5 algorithm is deprecated and insecure,
+ *                   so only use this if absolutely needed.
  */
 void DoMD5(char *mdout, const char *src, unsigned long n)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	unsigned int md_len;
+	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+	if (EVP_DigestInit_ex(mdctx, md5_function, NULL) != 1)
+		abort();
+	EVP_DigestUpdate(mdctx, src, n);
+	EVP_DigestFinal_ex(mdctx, mdout, &md_len);
+	EVP_MD_CTX_free(mdctx);
+#else
 	MD5_CTX hash;
 
 	MD5_Init(&hash);
 	MD5_Update(&hash, src, n);
 	MD5_Final(mdout, &hash);
+#endif
 }
 
 /** Generates an MD5 checksum - ASCII printable string (0011223344..etc..).
@@ -1834,6 +1792,8 @@ void DoMD5(char *mdout, const char *src, unsigned long n)
  *                  32 characters + nul terminator, so needs to be at least 33 characters.
  * @param src[in]   The input data used to generate the checksum.
  * @param n[in]     Length of data.
+ * @deprecated      The MD5 algorithm is deprecated and insecure,
+ *                  so only use this if absolutely needed.
  */
 char *md5hash(char *dst, const char *src, unsigned long n)
 {
@@ -1844,6 +1804,32 @@ char *md5hash(char *dst, const char *src, unsigned long n)
 	return dst;
 }
 
+/** Generates a SHA256 checksum - binary version.
+ * Most people will want to use sha256hash() instead which outputs hex.
+ * @param dst[out]  Buffer to store result in, which needs to be 32 bytes in length
+ *                  (SHA256_DIGEST_LENGTH).
+ * @param src[in]   The input data used to generate the checksum.
+ * @param n[in]     Length of data.
+ */
+void sha256hash_binary(char *dst, const char *src, unsigned long n)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	unsigned int md_len;
+	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+	if (EVP_DigestInit_ex(mdctx, sha256_function, NULL) != 1)
+		abort();
+	EVP_DigestUpdate(mdctx, src, n);
+	EVP_DigestFinal_ex(mdctx, dst, &md_len);
+	EVP_MD_CTX_free(mdctx);
+#else
+	SHA256_CTX hash;
+
+	SHA256_Init(&hash);
+	SHA256_Update(&hash, src, n);
+	SHA256_Final(dst, &hash);
+#endif
+}
+
 /** Generates a SHA256 checksum - ASCII printable string (0011223344..etc..).
  * @param dst[out]  Buffer to store result in, which needs to be 65 bytes minimum.
  * @param src[in]   The input data used to generate the checksum.
@@ -1851,18 +1837,15 @@ char *md5hash(char *dst, const char *src, unsigned long n)
  */
 char *sha256hash(char *dst, const char *src, unsigned long n)
 {
-	SHA256_CTX hash;
 	char binaryhash[SHA256_DIGEST_LENGTH];
 
-	SHA256_Init(&hash);
-	SHA256_Update(&hash, src, n);
-	SHA256_Final(binaryhash, &hash);
+	sha256hash_binary(binaryhash, src, n);
 	binarytohex(binaryhash, sizeof(binaryhash), dst);
 	return dst;
 }
 
 /** Calculate the SHA256 checksum of a file */
-char *sha256sum_file(const char *fname)
+const char *sha256sum_file(const char *fname)
 {
 	FILE *fd;
 	char buf[2048];
@@ -1870,22 +1853,68 @@ char *sha256sum_file(const char *fname)
 	char binaryhash[SHA256_DIGEST_LENGTH];
 	static char hexhash[SHA256_DIGEST_LENGTH*2+1];
 	int n;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	unsigned int md_len;
+	EVP_MD_CTX *mdctx;
+
+	mdctx = EVP_MD_CTX_new();
+	if (EVP_DigestInit_ex(mdctx, sha256_function, NULL) != 1)
+		abort();
+#else
+	SHA256_Init(&hash);
+#endif
 
 	fd = fopen(fname, "rb");
 	if (!fd)
 		return NULL;
 
-	SHA256_Init(&hash);
 	while ((n = fread(buf, 1, sizeof(buf), fd)) > 0)
 	{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+		EVP_DigestUpdate(mdctx, buf, n);
+#else
 		SHA256_Update(&hash, buf, n);
+#endif
 	}
 	fclose(fd);
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	EVP_DigestFinal_ex(mdctx, binaryhash, &md_len);
+	EVP_MD_CTX_free(mdctx);
+#else
 	SHA256_Final(binaryhash, &hash);
+#endif
 	binarytohex(binaryhash, sizeof(binaryhash), hexhash);
 	return hexhash;
 }
 
+/** Generates a SHA1 checksum - binary version.
+ * @param dst[out]  Buffer to store result in, which needs to be 32 bytes in length
+ *                  (SHA1_DIGEST_LENGTH).
+ * @param src[in]   The input data used to generate the checksum.
+ * @param n[in]     Length of data.
+ * @deprecated      The SHA1 algorithm is deprecated and insecure,
+ *                  so only use this if absolutely needed.
+ */
+void sha1hash_binary(char *dst, const char *src, unsigned long n)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	unsigned int md_len;
+	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+	if (EVP_DigestInit_ex(mdctx, sha1_function, NULL) != 1)
+		abort();
+	EVP_DigestUpdate(mdctx, src, n);
+	EVP_DigestFinal_ex(mdctx, dst, &md_len);
+	EVP_MD_CTX_free(mdctx);
+#else
+	SHA_CTX hash;
+
+	SHA1_Init(&hash);
+	SHA1_Update(&hash, src, n);
+	SHA1_Final(dst, &hash);
+#endif
+}
+
 /** Remove a suffix from a filename, eg ".c" (if it is present) */
 char *filename_strip_suffix(const char *fname, const char *suffix)
 {
@@ -1932,21 +1961,20 @@ int filename_has_suffix(const char *fname, const char *suffix)
 	return 0;
 }
 
-/** Check if the specified file exists */
-int file_exists(char *file)
+/** Check if the specified file or directory exists */
+int file_exists(const char *file)
 {
-	FILE *fd;
-
-	fd = fopen(file, "r");
-	if (!fd)
-		return 0;
-
-	fclose(fd);
-	return 1;
+#ifdef _WIN32
+	if (_access(file, 0) == 0)
+#else
+	if (access(file, 0) == 0)
+#endif
+		return 1;
+	return 0;
 }
 
 /** Get the file creation time */
-time_t get_file_time(char *fname)
+time_t get_file_time(const char *fname)
 {
 	struct stat st;
 
@@ -1957,7 +1985,7 @@ time_t get_file_time(char *fname)
 }
 
 /** Get the size of a file */
-long get_file_size(char *fname)
+long get_file_size(const char *fname)
 {
 	struct stat st;
 
@@ -1968,7 +1996,7 @@ long get_file_size(char *fname)
 }
 
 /** Add a line to a MultiLine list */
-void addmultiline(MultiLine **l, char *line)
+void addmultiline(MultiLine **l, const char *line)
 {
 	MultiLine *m = safe_alloc(sizeof(MultiLine));
 	safe_strdup(m->line, line);
@@ -1987,8 +2015,27 @@ void freemultiline(MultiLine *l)
 	}
 }
 
+/** Convert a line regular string containing \n's to a MultiLine linked list */
+MultiLine *line2multiline(const char *str)
+{
+	static char buf[8192];
+	char *p, *p2;
+	MultiLine *ml = NULL;
+
+	strlcpy(buf, str, sizeof(buf));
+	p = buf;
+	do {
+		p2 = strchr(p, '\n');
+		if (p2)
+			*p2++ = '\0';
+		addmultiline(&ml, p);
+		p = p2;
+	} while(p2 && *p2);
+	return ml;
+}
+
 /** Convert a sendtype to a command string */
-char *sendtype_to_cmd(SendType sendtype)
+const char *sendtype_to_cmd(SendType sendtype)
 {
 	if (sendtype == SEND_TYPE_PRIVMSG)
 		return "PRIVMSG";
@@ -2005,11 +2052,11 @@ char *sendtype_to_cmd(SendType sendtype)
  * @param strict	Whether to require UPPER+lower+digits
  * @returns 1 if good, 0 if not.
  */
-int check_password_strength(char *pass, int min_length, int strict, char **err)
+int check_password_strength(const char *pass, int min_length, int strict, char **err)
 {
-	char has_lowercase=0, has_uppercase=0, has_digit=0;
-	char *p;
 	static char buf[256];
+	char has_lowercase=0, has_uppercase=0, has_digit=0;
+	const char *p;
 
 	if (err)
 		*err = NULL;
@@ -2059,7 +2106,7 @@ int check_password_strength(char *pass, int min_length, int strict, char **err)
 	return 1;
 }
 
-int valid_secret_password(char *pass, char **err)
+int valid_secret_password(const char *pass, char **err)
 {
 	return check_password_strength(pass, 10, 1, err);
 }
@@ -2082,6 +2129,34 @@ int running_interactively(void)
 #endif
 }
 
+int terminal_supports_color(void)
+{
+#ifndef _WIN32
+	char *s;
+
+	/* Yeah we check all of stdin, stdout, stderr, because
+	 * or more may be redirected (bin/unrealircd >log 2>&1),
+	 * and then we want to say no to color support.
+	 */
+	if (!isatty(0) || !isatty(1) || !isatty(2))
+		return 0;
+
+	s = getenv("TERM");
+	/* Yeah this is a lazy way to detect color-capable terminals
+	 * but it is good enough for me.
+	 */
+	if (s)
+	{
+		if (strstr(s, "color") || strstr(s, "ansi"))
+			return 1;
+	}
+
+	return 0;
+#else
+	return 0;
+#endif
+}
+
 /** Skip whitespace (if any) */
 void skip_whitespace(char **p)
 {
@@ -2096,3 +2171,201 @@ void read_until(char **p, char *stopchars)
 {
 	for (; **p && !strchr(stopchars, **p); *p = *p + 1);
 }
+
+void write_pidfile_failed(void)
+{
+	char *errstr = strerror(errno);
+	unreal_log(ULOG_WARNING, "config", "WRITE_PID_FILE_FAILED", NULL,
+		   "Unable to write to pid file '$filename': $system_error",
+		   log_data_string("filename", conf_files->pid_file),
+		   log_data_string("system_error", errstr));
+}
+
+/** Write PID file */
+void write_pidfile(void)
+{
+#ifdef IRCD_PIDFILE
+	int fd;
+	char buff[20];
+	if ((fd = open(conf_files->pid_file, O_CREAT | O_WRONLY, 0600)) < 0)
+	{
+		write_pidfile_failed();
+		return;
+	}
+	ircsnprintf(buff, sizeof(buff), "%5d\n", (int)getpid());
+	if (write(fd, buff, strlen(buff)) < 0)
+		write_pidfile_failed();
+	if (close(fd) < 0)
+		write_pidfile_failed();
+#endif
+}
+
+/*
+ * Determines if the given string is a valid URL. Since libcurl
+ * supports telnet, ldap, and dict such strings are treated as
+ * invalid URLs here since we don't want them supported in
+ * unreal.
+ */
+int url_is_valid(const char *string)
+{
+	if (strstr(string, " ") || strstr(string, "\t"))
+		return 0;
+
+	if (strstr(string, "telnet://") == string ||
+	    strstr(string, "ldap://") == string ||
+	    strstr(string, "dict://") == string)
+	{
+		return 0;
+	}
+	return (strstr(string, "://") != NULL);
+}
+
+/** A displayable URL for in error messages and such.
+ * This leaves out any authentication information (user:pass)
+ * the URL may contain.
+ */
+const char *displayurl(const char *url)
+{
+	static char buf[512];
+	char *proto, *rest;
+
+	/* protocol://user:pass@host/etc.. */
+	rest = strchr(url, '@');
+
+	if (!rest)
+		return url; /* contains no auth information */
+
+	rest++; /* now points to the rest (remainder) of the URL */
+
+	proto = strstr(url, "://");
+	if (!proto || (proto > rest) || (proto == url))
+		return url; /* incorrectly formatted, just show entire URL. */
+
+	strlncpy(buf, url, sizeof(buf), proto - url);
+	strlcat(buf, "://***:***@", sizeof(buf));
+	strlcat(buf, rest, sizeof(buf));
+
+	return buf;
+}
+
+/*
+ * Returns the filename portion of the URL. The returned string
+ * is malloc()'ed and must be freed by the caller. If the specified
+ * URL does not contain a filename, a '-' is allocated and returned.
+ */
+char *url_getfilename(const char *url)
+{
+	const char *c, *start;
+
+	if ((c = strstr(url, "://")))
+		c += 3;
+	else
+		c = url;
+
+	while (*c && *c != '/')
+		c++;
+
+	if (*c == '/')
+	{
+		c++;
+		if (!*c || *c == '?')
+			return raw_strdup("-");
+		start = c;
+		while (*c && *c != '?')
+			c++;
+		if (!*c)
+			return raw_strdup(start);
+		else
+			return raw_strldup(start, c-start+1);
+
+	}
+	return raw_strdup("-");
+}
+
+#ifdef _WIN32
+ // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
+ // mode value   Checks file for
+ // 04 	         Read-only
+ #define R_OK 04
+#endif
+
+/*
+ * Checks whether a file can be opened for reading.
+ */
+int is_file_readable(const char *file, const char *dir)
+{
+	char *filename = strdup(file);
+	convert_to_absolute_path(&filename, dir);
+	if (access(filename, R_OK)){
+		safe_free(filename);
+		return 0;
+	}
+	safe_free(filename);
+	return 1;
+}
+
+void delletterfromstring(char *s, char letter)
+{
+	if (s == NULL)
+		return;
+	for (; *s; s++)
+	{
+		if (*s == letter)
+		{
+			for (; *s; s++)
+				*s = s[1];
+			break;
+		}
+	}
+}
+
+int sort_character_lowercase_before_uppercase(char x, char y)
+{
+	/* Lower before upper */
+	if (islower(x) && isupper(y))
+		return 1;
+	if (isupper(x) && islower(y))
+		return 0;
+	/* Other than that, easy */
+	return x < y ? 1 : 0;
+}
+
+/* Helper function, mainly used by snomask code */
+void addlettertodynamicstringsorted(char **str, char letter)
+{
+	char *i, *o;
+	char *newbuf;
+	size_t newbuflen;
+
+	/* NULL string? Easy! */
+	if (*str == NULL)
+	{
+		*str = safe_alloc(2);
+		**str = letter;
+		return;
+	}
+
+	/* Exists? Then nothing to do */
+	if (strchr(*str, letter))
+		return;
+
+	/* Ok, we really need to add it */
+	newbuflen = strlen(*str) + 2;
+	newbuf = safe_alloc(newbuflen);
+	for (i = *str, o = newbuf; *i; i++)
+	{
+		/* Insert before a higher letter */
+		if (letter && sort_character_lowercase_before_uppercase(letter, *i))
+		{
+			*o++ = letter;
+			letter = '\0';
+		}
+		*o++ = *i;
+	}
+	/* Or maybe we should be at the final spot? */
+	if (letter)
+		*o++ = letter;
+	*o = '\0';
+	safe_free_raw(*str);
+	*str = newbuf;
+}
diff --git a/src/modulemanager.c b/src/modulemanager.c
@@ -44,7 +44,7 @@ int mm_valid_module_name(char *name);
 void free_managed_module(ManagedModule *m);
 
 
-SSL_CTX *mm_init_ssl(void)
+SSL_CTX *mm_init_tls(void)
 {
 	SSL_CTX *ctx_client;
 	char buf1[512], buf2[512];
@@ -101,8 +101,7 @@ int parse_url(const char *url, char **host, int *port, char **document)
 	if (!p)
 		return 0;
 
-	*hostbuf = '\0';
-	strlncat(hostbuf, url, sizeof(hostbuf), p - url);
+	strlncpy(hostbuf, url, sizeof(hostbuf), p - url);
 
 	strlcpy(documentbuf, p, sizeof(documentbuf));
 
@@ -134,7 +133,7 @@ int mm_http_request(char *url, char *fname, int follow_redirects)
 
 	snprintf(hostandport, sizeof(hostandport), "%s:%d", host, port);
 
-	ctx_client = mm_init_ssl();
+	ctx_client = mm_init_tls();
 	if (!ctx_client)
 	{
 		fprintf(stderr, "ERROR: TLS initalization failure (I)\n");
@@ -163,14 +162,14 @@ int mm_http_request(char *url, char *fname, int follow_redirects)
 	if (BIO_do_connect(socket) != 1)
 	{
 		fprintf(stderr, "ERROR: Could not connect to %s\n", hostandport);
-		config_report_ssl_error();
+		//config_report_ssl_error(); FIXME?
 		goto out2;
 	}
 	
 	if (BIO_do_handshake(socket) != 1)
 	{
 		fprintf(stderr, "ERROR: Could not connect to %s (TLS handshake failed)\n", hostandport);
-		config_report_ssl_error();
+		//config_report_ssl_error(); FIXME?
 		goto out2;
 	}
 
@@ -339,63 +338,64 @@ int parse_quoted_string(char *buf, char *dest, size_t destlen)
 	return 1;
 }
 
-#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", m->name, (x)->ce_varlinenum); return 0; }
+#undef CheckNull
+#define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", m->name, (x)->line_number); return 0; }
 
 /** Parse a module { } line from a module (not repo!!) */
 int mm_module_file_config(ManagedModule *m, ConfigEntry *ce)
 {
 	ConfigEntry *cep;
 
-	if (ce->ce_vardata)
+	if (ce->value)
 	{
 		config_error("%s:%d: module { } block should not have a name.",
-			m->name, ce->ce_varlinenum);
+			m->name, ce->line_number);
 		return 0;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "source") ||
-		    !strcmp(cep->ce_varname, "version") ||
-		    !strcmp(cep->ce_varname, "author") ||
-		    !strcmp(cep->ce_varname, "sha256sum") || 
-		    !strcmp(cep->ce_varname, "description")
+		if (!strcmp(cep->name, "source") ||
+		    !strcmp(cep->name, "version") ||
+		    !strcmp(cep->name, "author") ||
+		    !strcmp(cep->name, "sha256sum") || 
+		    !strcmp(cep->name, "description")
 		    )
 		{
 			config_error("%s:%d: module::%s should not be in here (it only exists in repository entries)",
-				m->name, cep->ce_varlinenum, cep->ce_varname);
+				m->name, cep->line_number, cep->name);
 			return 0;
 		}
-		else if (!strcmp(cep->ce_varname, "troubleshooting"))
+		else if (!strcmp(cep->name, "troubleshooting"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->troubleshooting, cep->ce_vardata);
+			safe_strdup(m->troubleshooting, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "documentation"))
+		else if (!strcmp(cep->name, "documentation"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->documentation, cep->ce_vardata);
+			safe_strdup(m->documentation, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "min-unrealircd-version"))
+		else if (!strcmp(cep->name, "min-unrealircd-version"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->min_unrealircd_version, cep->ce_vardata);
+			safe_strdup(m->min_unrealircd_version, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "max-unrealircd-version"))
+		else if (!strcmp(cep->name, "max-unrealircd-version"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->max_unrealircd_version, cep->ce_vardata);
+			safe_strdup(m->max_unrealircd_version, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "post-install-text"))
+		else if (!strcmp(cep->name, "post-install-text"))
 		{
-			if (cep->ce_entries)
+			if (cep->items)
 			{
 				ConfigEntry *cepp;
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					addmultiline(&m->post_install_text, cepp->ce_varname);
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+					addmultiline(&m->post_install_text, cepp->name);
 			} else {
 				CheckNull(cep);
-				addmultiline(&m->post_install_text, cep->ce_vardata);
+				addmultiline(&m->post_install_text, cep->value);
 			}
 		}
 		/* unknown items are silently ignored for future compatibility */
@@ -403,19 +403,19 @@ int mm_module_file_config(ManagedModule *m, ConfigEntry *ce)
 
 	if (!m->documentation)
 	{
-		config_error("%s:%d: module::documentation missing", m->name, ce->ce_varlinenum);
+		config_error("%s:%d: module::documentation missing", m->name, ce->line_number);
 		return 0;
 	}
 
 	if (!m->troubleshooting)
 	{
-		config_error("%s:%d: module::troubleshooting missing", m->name, ce->ce_varlinenum);
+		config_error("%s:%d: module::troubleshooting missing", m->name, ce->line_number);
 		return 0;
 	}
 
 	if (!m->min_unrealircd_version)
 	{
-		config_error("%s:%d: module::min-unrealircd-version missing", m->name, ce->ce_varlinenum);
+		config_error("%s:%d: module::min-unrealircd-version missing", m->name, ce->line_number);
 		return 0;
 	}
 
@@ -438,9 +438,9 @@ int mm_parse_module_file(ManagedModule *m, char *buf, unsigned int line_offset)
 		return 0; /* eg: parse errors */
 
 	/* Parse the module { } block (only one!) */
-	for (ce = cf->cf_entries; ce; ce = ce->ce_next)
+	for (ce = cf->items; ce; ce = ce->next)
 	{
-		if (!strcmp(ce->ce_varname, "module"))
+		if (!strcmp(ce->name, "module"))
 		{
 			int n = mm_module_file_config(m, ce);
 			config_free(cf);
@@ -652,7 +652,8 @@ int mm_valid_module_name(char *name)
 	return 1;
 }
 
-#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", repo_url, (x)->ce_varlinenum); goto fail_mm_repo_module_config; }
+#undef CheckNull
+#define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", repo_url, (x)->line_number); goto fail_mm_repo_module_config; }
 
 /** Parse a module { } line from a repository */
 ManagedModule *mm_repo_module_config(char *repo_url, ConfigEntry *ce)
@@ -660,84 +661,84 @@ ManagedModule *mm_repo_module_config(char *repo_url, ConfigEntry *ce)
 	ConfigEntry *cep;
 	ManagedModule *m = safe_alloc(sizeof(ManagedModule));
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%d: module { } with no name",
-			repo_url, ce->ce_varlinenum);
+			repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
-	if (strncmp(ce->ce_vardata, "third/", 6))
+	if (strncmp(ce->value, "third/", 6))
 	{
 		config_error("%s:%d: module { } name must start with: third/",
-			repo_url, ce->ce_varlinenum);
+			repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
-	if (!mm_valid_module_name(ce->ce_vardata))
+	if (!mm_valid_module_name(ce->value))
 	{
 		config_error("%s:%d: module { } with illegal name: %s",
-			repo_url, ce->ce_varlinenum, ce->ce_vardata);
+			repo_url, ce->line_number, ce->value);
 		goto fail_mm_repo_module_config;
 	}
-	safe_strdup(m->name, ce->ce_vardata);
+	safe_strdup(m->name, ce->value);
 	safe_strdup(m->repo_url, repo_url);
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "source"))
+		if (!strcmp(cep->name, "source"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->source, cep->ce_vardata);
+			safe_strdup(m->source, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "sha256sum"))
+		else if (!strcmp(cep->name, "sha256sum"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->sha256sum, cep->ce_vardata);
+			safe_strdup(m->sha256sum, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "version"))
+		else if (!strcmp(cep->name, "version"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->version, cep->ce_vardata);
+			safe_strdup(m->version, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "author"))
+		else if (!strcmp(cep->name, "author"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->author, cep->ce_vardata);
+			safe_strdup(m->author, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "troubleshooting"))
+		else if (!strcmp(cep->name, "troubleshooting"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->troubleshooting, cep->ce_vardata);
+			safe_strdup(m->troubleshooting, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "documentation"))
+		else if (!strcmp(cep->name, "documentation"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->documentation, cep->ce_vardata);
+			safe_strdup(m->documentation, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "min-unrealircd-version"))
+		else if (!strcmp(cep->name, "min-unrealircd-version"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->min_unrealircd_version, cep->ce_vardata);
+			safe_strdup(m->min_unrealircd_version, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "max-unrealircd-version"))
+		else if (!strcmp(cep->name, "max-unrealircd-version"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->max_unrealircd_version, cep->ce_vardata);
+			safe_strdup(m->max_unrealircd_version, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "description"))
+		else if (!strcmp(cep->name, "description"))
 		{
 			CheckNull(cep);
-			safe_strdup(m->description, cep->ce_vardata);
+			safe_strdup(m->description, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "post-install-text"))
+		else if (!strcmp(cep->name, "post-install-text"))
 		{
-			if (cep->ce_entries)
+			if (cep->items)
 			{
 				ConfigEntry *cepp;
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					addmultiline(&m->post_install_text, cepp->ce_varname);
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+					addmultiline(&m->post_install_text, cepp->name);
 			} else {
 				CheckNull(cep);
-				addmultiline(&m->post_install_text, cep->ce_vardata);
+				addmultiline(&m->post_install_text, cep->value);
 			}
 		}
 		/* unknown items are silently ignored for future compatibility */
@@ -745,43 +746,43 @@ ManagedModule *mm_repo_module_config(char *repo_url, ConfigEntry *ce)
 
 	if (!m->source)
 	{
-		config_error("%s:%d: module::source missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::source missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	if (!m->sha256sum)
 	{
-		config_error("%s:%d: module::sha256sum missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::sha256sum missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	if (!m->version)
 	{
-		config_error("%s:%d: module::version missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::version missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	if (!m->author)
 	{
-		config_error("%s:%d: module::author missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::author missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	if (!m->documentation)
 	{
-		config_error("%s:%d: module::documentation missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::documentation missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	if (!m->troubleshooting)
 	{
-		config_error("%s:%d: module::troubleshooting missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::troubleshooting missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	if (!m->min_unrealircd_version)
 	{
-		config_error("%s:%d: module::min-unrealircd-version missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::min-unrealircd-version missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	/* max_unrealircd_version is optional */
 	if (!m->description)
 	{
-		config_error("%s:%d: module::description missing", repo_url, ce->ce_varlinenum);
+		config_error("%s:%d: module::description missing", repo_url, ce->line_number);
 		goto fail_mm_repo_module_config;
 	}
 	/* post_install_text is optional */
@@ -805,9 +806,9 @@ int mm_parse_repo_db(char *url, char *filename)
 	if (!cf)
 		return 0; /* eg: parse errors */
 
-	for (ce = cf->cf_entries; ce; ce = ce->ce_next)
+	for (ce = cf->items; ce; ce = ce->next)
 	{
-		if (!strcmp(ce->ce_varname, "module"))
+		if (!strcmp(ce->name, "module"))
 		{
 			m = mm_repo_module_config(url, ce);
 			if (!m)
@@ -957,7 +958,7 @@ int mm_get_module_status(ManagedModule *m)
 {
 	FILE *fd;
 	char fname[512];
-	char *our_sha256sum;
+	const char *our_sha256sum;
 
 	snprintf(fname, sizeof(fname), "%s/src/modules/%s.c", BUILDDIR, m->name);
 	if (!file_exists(fname))
@@ -1142,7 +1143,7 @@ int mm_compile(ManagedModule *m, char *tmpfile, int test)
 {
 	char newpath[512];
 	char cmd[512];
-	char *basename;
+	const char *basename;
 	char *p;
 	FILE *fd;
 	char buf[512];
@@ -1213,15 +1214,15 @@ int mm_compile(ManagedModule *m, char *tmpfile, int test)
  */
 void mm_install_module(ManagedModule *m)
 {
-	char *basename = unreal_getfilename(m->source);
+	const char *basename = unreal_getfilename(m->source);
 	char *tmpfile;
-	char *sha256;
+	const char *sha256;
 
 	if (!basename)
 		basename = "mod.c";
 	tmpfile = unreal_mktemp(TMPDIR, basename);
 
-	printf("Downloading %s from %s...\n", m->name, m->source);
+	printf("ConfigResourceing %s from %s...\n", m->name, m->source);
 	if (!mm_http_request(m->source, tmpfile, 1))
 	{
 		fprintf(stderr, "Repository %s seems to list a module file that cannot be retrieved (%s).\n", m->repo_url, m->source);
@@ -1535,7 +1536,7 @@ void print_md_block(FILE *fdo, ManagedModule *m)
 
 void mm_generate_repository_usage(void)
 {
-	fprintf(stderr, "Usage: ./unrealircd module generate-repository <url base path> <directory-with-modules> <name of output file>\n");
+	fprintf(stderr, "Usage: ./unrealircd module generate-repository <url base path> <directory-with-modules> <name of output file> [optional-minimum-version-filter]\n");
 	fprintf(stderr, "For example: ./unrealircd module generate-repository https://www.unrealircd.org/modules/ src/modules/third modules.lst\n");
 }
 
@@ -1547,6 +1548,7 @@ void mm_generate_repository(int argc, char *args[])
 	char *urlbasepath;
 	char *dirname;
 	char *outputfile;
+	char *minversion;
 	char modname[128];
 	char fullname[512];
 	ManagedModule *m;
@@ -1555,6 +1557,8 @@ void mm_generate_repository(int argc, char *args[])
 	urlbasepath = args[1];
 	dirname = args[2];
 	outputfile = args[3];
+	minversion = args[4];
+
 	if (!urlbasepath || !dirname || !outputfile)
 	{
 		mm_generate_repository_usage();
@@ -1587,6 +1591,7 @@ void mm_generate_repository(int argc, char *args[])
 		char *fname = dir->d_name;
 		if (filename_has_suffix(fname, ".c"))
 		{
+			int hide = 0;
 			snprintf(fullname, sizeof(fullname), "%s/%s", dirname, fname);
 			snprintf(modname, sizeof(modname), "third/%s", filename_strip_suffix(fname, ".c"));
 			printf("Processing: %s\n", modname);
@@ -1599,7 +1604,12 @@ void mm_generate_repository(int argc, char *args[])
 			m->sha256sum = strdup(sha256sum_file(fullname));
 			m->source = safe_alloc(512);
 			snprintf(m->source, 512, "%s%s.c", urlbasepath, modname + 6);
-			print_md_block(fdo, m);
+			/* filter */
+			if (minversion && m->min_unrealircd_version && strncmp(minversion, m->min_unrealircd_version, strlen(minversion)))
+				hide = 1;
+			/* /filter */
+			if (!hide)
+				print_md_block(fdo, m);
 			free_managed_module(m);
 			m = NULL;
 		}
@@ -1611,7 +1621,7 @@ void mm_generate_repository(int argc, char *args[])
 void mm_parse_c_file(int argc, char *args[])
 {
 	char *fullname = args[1];
-	char *basename;
+	const char *basename;
 	char modname[256];
 	ManagedModule *m;
 
diff --git a/src/modules.c b/src/modules.c
@@ -52,7 +52,7 @@ Module *Module_make(ModuleHeader *header,
 
 #ifdef UNDERSCORE
 /* dlsym for OpenBSD */
-void *obsd_dlsym(void *handle, char *symbol)
+void *obsd_dlsym(void *handle, const char *symbol)
 {
 	size_t buflen = strlen(symbol) + 2;
 	char *obsdsymbol = safe_alloc(buflen);
@@ -69,7 +69,7 @@ void *obsd_dlsym(void *handle, char *symbol)
 }
 #endif
 
-void deletetmp(char *path)
+void deletetmp(const char *path)
 {
 #ifndef NOREMOVETMP
 	if (!loop.config_test)
@@ -88,7 +88,7 @@ void DeleteTempModules(void)
 	{
 		config_error("Unable to open temp directory %s: %s, please create one with the appropriate permissions",
 			TMPDIR, strerror(errno));
-		if (!loop.ircd_booted)
+		if (!loop.booted)
 			exit(7);
 		return; 
 	}
@@ -131,7 +131,7 @@ void DeleteTempModules(void)
 #endif	
 }
 
-Module *Module_Find(char *name)
+Module *Module_Find(const char *name)
 {
 	Module *p;
 	
@@ -149,16 +149,16 @@ Module *Module_Find(char *name)
 	
 }
 
-int parse_modsys_version(char *version)
+int parse_modsys_version(const char *version)
 {
-	if (!strcmp(version, "unrealircd-5"))
-		return 0x500000;
+	if (!strcmp(version, "unrealircd-6"))
+		return 0x600000;
 	return 0;
 }
 
 void make_compiler_string(char *buf, size_t buflen, unsigned int ver)
 {
-unsigned int maj, min, plevel;
+	unsigned int maj, min, plevel;
 
 	if (ver == 0)
 	{
@@ -180,7 +180,7 @@ unsigned int maj, min, plevel;
  * something like "/home/xyz/unrealircd/modules/third/la.so
  * (and other tricks)
  */
-char *Module_TransformPath(char *path_)
+const char *Module_TransformPath(const char *path_)
 {
 	static char path[1024];
 
@@ -202,17 +202,18 @@ char *Module_TransformPath(char *path_)
 }
 
 /** This function is the inverse of Module_TransformPath() */
-char *Module_GetRelPath(char *fullpath)
+const char *Module_GetRelPath(const char *fullpath)
 {
 	static char buf[512];
 	char prefix[512];
-	char *s = fullpath;
+	const char *without_prefix = fullpath;
+	char *s;
 
 	/* Strip the prefix */
 	snprintf(prefix, sizeof(prefix), "%s/", MODULESDIR);
 	if (!strncasecmp(fullpath, prefix, strlen(prefix)))
-		s += strlen(prefix);
-	strlcpy(buf, s, sizeof(buf));
+		without_prefix += strlen(prefix);
+	strlcpy(buf, without_prefix, sizeof(buf));
 
 	/* Strip the suffix */
 	s = strstr(buf, MODULE_SUFFIX);
@@ -225,7 +226,7 @@ char *Module_GetRelPath(char *fullpath)
 /** Validate a modules' ModuleHeader.
  * @returns Error message is returned, or NULL if everything is OK.
  */
-static char *validate_mod_header(char *relpath, ModuleHeader *mod_header)
+static const char *validate_mod_header(const char *relpath, ModuleHeader *mod_header)
 {
 	char *p;
 	static char buf[256];
@@ -260,7 +261,7 @@ static char *validate_mod_header(char *relpath, ModuleHeader *mod_header)
 	return NULL; /* SUCCESS */
 }
 
-int module_already_in_testing(char *relpath)
+int module_already_in_testing(const char *relpath)
 {
 	Module *m;
 	for (m = Modules; m; m = m->next)
@@ -277,7 +278,7 @@ int module_already_in_testing(char *relpath)
 /*
  * Returns an error if insucessful .. yes NULL is OK! 
 */
-char  *Module_Create(char *path_)
+const char *Module_Create(const char *path_)
 {
 #ifdef _WIN32
 	HMODULE 	Mod;
@@ -291,15 +292,14 @@ char  *Module_Create(char *path_)
 	char    *Mod_Version;
 	unsigned int *compiler_version;
 	static char 	errorbuf[1024];
-	char 		*path, *relpath, *tmppath;
+	const char	*path, *relpath, *tmppath;
 	ModuleHeader    *mod_header = NULL;
 	int		ret = 0;
-	char		*reterr;
+	const char	*reterr;
 	Module          *mod = NULL, **Mod_Handle = NULL;
 	char *expectedmodversion = our_mod_version;
 	unsigned int expectedcompilerversion = our_compiler_version;
 	long modsys_ver = 0;
-	Debug((DEBUG_DEBUG, "Attempting to load module from %s", path_));
 
 	path = Module_TransformPath(path_);
 
@@ -447,7 +447,7 @@ char  *Module_Create(char *path_)
 	else
 	{
 		/* Return the error .. */
-		return ((char *)irc_dlerror());
+		return irc_dlerror();
 	}
 }
 
@@ -524,9 +524,6 @@ void FreeModObj(ModuleObject *obj, Module *m)
 	else if (obj->type == MOBJ_VERSIONFLAG) {
 		VersionflagDel(obj->object.versionflag, m);
 	}
-	else if (obj->type == MOBJ_SNOMASK) {
-		SnomaskDel(obj->object.snomask);
-	}
 	else if (obj->type == MOBJ_UMODE) {
 		UmodeDel(obj->object.umode);
 	}
@@ -565,7 +562,9 @@ void FreeModObj(ModuleObject *obj, Module *m)
 	}
 	else
 	{
-		ircd_log(LOG_ERROR, "FreeModObj() called for unknown object");
+		unreal_log(ULOG_FATAL, "module", "FREEMODOBJ_UNKNOWN_TYPE", NULL,
+		           "[BUG] FreeModObj() called for unknown object (type $type)",
+		           log_data_integer("type", obj->type));
 		abort();
 	}
 }
@@ -655,8 +654,6 @@ int    Module_free(Module *mod)
 
 	for (cp = mod->children; cp; cp = cp->next)
 	{
-		sendto_realops("Unloading child module %s",
-			      cp->child->header->name);
 		Module_Unload(cp->child->header->name);
 	}
 	for (objs = mod->objects; objs; objs = next) {
@@ -693,7 +690,7 @@ int    Module_free(Module *mod)
  *      1                Module unloaded
  *      2                Module wishes delayed unloading, has placed event
  */
-int Module_Unload(char *name)
+int Module_Unload(const char *name)
 {
 	Module *m;
 	int    (*Mod_Unload)();
@@ -734,11 +731,6 @@ void module_loadall(void)
 	iFP	fp;
 	Module *mi, *next;
 	
-	if (!loop.ircd_booted)
-	{
-		sendto_realops("Ehh, !loop.ircd_booted in module_loadall()");
-		return ;
-	}
 	/* Run through all modules and check for module load */
 	for (mi = Modules; mi; mi = next)
 	{
@@ -799,12 +791,12 @@ CMD_FUNC(cmd_module)
 		all = 1;
 
 	if (MyUser(client) && !IsOper(client) && all)
-		client->local->since += 7; /* Lag them up. Big list. */
+		add_fake_lag(client, 7000); /* Lag them up. Big list. */
 
-	if ((parc > 2) && (hunt_server(client, recv_mtags, ":%s MODULE %s :%s", 2, parc, parv) != HUNTED_ISME))
+	if ((parc > 2) && (hunt_server(client, recv_mtags, "MODULE", 2, parc, parv) != HUNTED_ISME))
 		return;
 
-	if ((parc == 2) && (parv[1][0] != '-') && (hunt_server(client, recv_mtags, ":%s MODULE :%s", 1, parc, parv) != HUNTED_ISME))
+	if ((parc == 2) && (parv[1][0] != '-') && (hunt_server(client, recv_mtags, "MODULE", 1, parc, parv) != HUNTED_ISME))
 		return;
 
 	if (all)
@@ -884,7 +876,7 @@ CMD_FUNC(cmd_module)
 	sendtxtnumeric(client, "Override: %s", tmp);
 }
 
-Hooktype *HooktypeFind(char *string) {
+Hooktype *HooktypeFind(const char *string) {
 	Hooktype *hooktype;
 	for (hooktype = Hooktypes; hooktype->string ;hooktype++) {
 		if (!strcasecmp(hooktype->string, string))
@@ -984,7 +976,7 @@ void VersionflagDel(Versionflag *vflag, Module *module)
 	}
 }
 
-Hook *HookAddMain(Module *module, int hooktype, int priority, int (*func)(), void (*vfunc)(), char *(*cfunc)())
+Hook *HookAddMain(Module *module, int hooktype, int priority, int (*func)(), void (*vfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
 {
 	Hook *p;
 	
@@ -993,8 +985,10 @@ Hook *HookAddMain(Module *module, int hooktype, int priority, int (*func)(), voi
 		p->func.intfunc = func;
 	if (vfunc)
 		p->func.voidfunc = vfunc;
-	if (cfunc)
-		p->func.pcharfunc = cfunc;
+	if (stringfunc)
+		p->func.stringfunc = stringfunc;
+	if (conststringfunc)
+		p->func.conststringfunc = conststringfunc;
 	p->type = hooktype;
 	p->owner = module;
 	p->priority = priority;
@@ -1036,17 +1030,40 @@ Hook *HookDel(Hook *hook)
 	return NULL;
 }
 
-Callback	*CallbackAddMain(Module *module, int cbtype, int (*func)(), void (*vfunc)(), char *(*cfunc)())
+static int num_callbacks(int cbtype)
+{
+Callback *e;
+int cnt = 0;
+
+	for (e = Callbacks[cbtype]; e; e = e->next)
+		if (!e->willberemoved)
+			cnt++;
+			
+	return cnt;
+}
+
+Callback *CallbackAddMain(Module *module, int cbtype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
 {
 	Callback *p;
 	
+	if (num_callbacks(cbtype) > 0)
+	{
+		if (module)
+			module->errorcode = MODERR_EXISTS;
+		return NULL;
+	}
+	
 	p = safe_alloc(sizeof(Callback));
 	if (func)
 		p->func.intfunc = func;
 	if (vfunc)
 		p->func.voidfunc = vfunc;
-	if (cfunc)
-		p->func.pcharfunc = cfunc;
+	if (pvfunc)
+		p->func.pvoidfunc = pvfunc;
+	if (stringfunc)
+		p->func.stringfunc = stringfunc;
+	if (conststringfunc)
+		p->func.conststringfunc = conststringfunc;
 	p->type = cbtype;
 	p->owner = module;
 	AddListItem(p, Callbacks[cbtype]);
@@ -1086,7 +1103,7 @@ Callback *CallbackDel(Callback *cb)
 	return NULL;
 }
 
-CommandOverride *CommandOverrideAddEx(Module *module, char *name, int priority, OverrideCmdFunc function)
+CommandOverride *CommandOverrideAdd(Module *module, const char *name, int priority, OverrideCmdFunc function)
 {
 	RealCommand *p;
 	CommandOverride *ovr;
@@ -1108,7 +1125,7 @@ CommandOverride *CommandOverrideAddEx(Module *module, char *name, int priority, 
 	}
 	ovr = safe_alloc(sizeof(CommandOverride));
 	ovr->func = function;
-	ovr->owner = module; /* TODO: module objects */
+	ovr->owner = module;
 	ovr->priority = priority;
 	if (module)
 	{
@@ -1127,11 +1144,6 @@ CommandOverride *CommandOverrideAddEx(Module *module, char *name, int priority, 
 	return ovr;
 }
 
-CommandOverride *CommandOverrideAdd(Module *module, char *name, OverrideCmdFunc function)
-{
-	return CommandOverrideAddEx(module, name, 0, function);
-}
-
 void CommandOverrideDel(CommandOverride *cmd)
 {
 	DelListItem(cmd, cmd->command->overriders);
@@ -1157,7 +1169,7 @@ void CommandOverrideDel(CommandOverride *cmd)
 	safe_free(cmd);
 }
 
-void CallCommandOverride(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, char *parv[])
+void CallCommandOverride(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, const char *parv[])
 {
 	if (ovr->next)
 		ovr->next->func(ovr->next, client, mtags, parc, parv);
@@ -1171,21 +1183,27 @@ EVENT(e_unload_module_delayed)
 	int i; 
 	i = Module_Unload(name);
 	if (i == 1)
-		sendto_realops("Unloaded module %s", name);
+	{
+		unreal_log(ULOG_INFO, "module", "MODULE_UNLOADING_DELAYED", NULL,
+		           "Unloading module $module_name (was delayed earlier)",
+		           log_data_string("module_name", name));
+	}
 	safe_free(name);
 	extcmodes_check_for_changes();
 	umodes_check_for_changes();
 	return;
 }
 
-void	unload_all_modules(void)
+void unload_all_modules(void)
 {
 	Module *m;
 	int	(*Mod_Unload)();
 	for (m = Modules; m; m = m->next)
 	{
 #ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "Unloading %s...", m->header->name);
+		unreal_log(ULOG_DEBUG, "module", "MODULE_UNLOADING", NULL,
+		           "Unloading module $module_name",
+		           log_data_string("module_name", m->header->name));
 #endif
 		irc_dlsym(m->dll, "Mod_Unload", Mod_Unload);
 		if (Mod_Unload)
@@ -1237,18 +1255,6 @@ const char *ModuleGetErrorStr(Module *module)
 	return module_error_str[module->errorcode];
 }
 
-static int num_callbacks(int cbtype)
-{
-Callback *e;
-int cnt = 0;
-
-	for (e = Callbacks[cbtype]; e; e = e->next)
-		if (!e->willberemoved)
-			cnt++;
-			
-	return cnt;
-}
-
 /** Ensure that all required callbacks are in place and meet
  * all specified requirements
  */
@@ -1262,11 +1268,21 @@ int i;
 		{
 			config_error("ERROR: Multiple callbacks loaded for type %d. "
 			             "Make sure you only load 1 module of 1 type (eg: only 1 cloaking module)",
-			             i); /* TODO: make more clear? */
+			             i);
 			return -1;
 		}
 	}
-		
+
+	if (!Callbacks[CALLBACKTYPE_CLOAK_KEY_CHECKSUM])
+	{
+		unreal_log(ULOG_ERROR, "config", "NO_CLOAKING_MODULE", NULL,
+		           "No cloaking module loaded, you must load 1 of these modulese:\n"
+		           "1) cloak_sha256 - if you are a new network starting with UnrealIRCd 6\n"
+		           "2) cloak_md5 - the old one if migrating an existing network from UnrealIRCd 3.2/4/5\n"
+		           "3) cloak_none - if you don't want to use cloaking at all\n"
+		           "See also https://www.unrealircd.org/docs/FAQ#choose-a-cloaking-module");
+		return -1;
+	}
 	return 0;
 }
 
@@ -1311,7 +1327,7 @@ const char *our_dlerror(void)
  * @note  The name is checked against the module name,
  *        this can be a problem if two modules have the same name.
  */
-int is_module_loaded(char *name)
+int is_module_loaded(const char *name)
 {
 	Module *mi;
 	for (mi = Modules; mi; mi = mi->next)
@@ -1325,17 +1341,17 @@ int is_module_loaded(char *name)
 	return 0;
 }
 
-static char *mod_var_name(ModuleInfo *modinfo, char *varshortname)
+static const char *mod_var_name(ModuleInfo *modinfo, const char *varshortname)
 {
 	static char fullname[512];
 	snprintf(fullname, sizeof(fullname), "%s:%s", modinfo->handle->header->name, varshortname);
 	return fullname;
 }
 
-int LoadPersistentPointerX(ModuleInfo *modinfo, char *varshortname, void **var, void (*free_variable)(ModData *m))
+int LoadPersistentPointerX(ModuleInfo *modinfo, const char *varshortname, void **var, void (*free_variable)(ModData *m))
 {
 	ModDataInfo *m;
-	char *fullname = mod_var_name(modinfo, varshortname);
+	const char *fullname = mod_var_name(modinfo, varshortname);
 
 	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
 	if (m)
@@ -1346,27 +1362,28 @@ int LoadPersistentPointerX(ModuleInfo *modinfo, char *varshortname, void **var, 
 		ModDataInfo mreq;
 		memset(&mreq, 0, sizeof(mreq));
 		mreq.type = MODDATATYPE_LOCAL_VARIABLE;
-		mreq.name = fullname;
+		mreq.name = strdup(fullname);
 		mreq.free = free_variable;
 		m = ModDataAdd(modinfo->handle, mreq);
 		moddata_local_variable(m).ptr = NULL;
+		safe_free(mreq.name);
 		return 0;
 	}
 }
 
-void SavePersistentPointerX(ModuleInfo *modinfo, char *varshortname, void *var)
+void SavePersistentPointerX(ModuleInfo *modinfo, const char *varshortname, void *var)
 {
 	ModDataInfo *m;
-	char *fullname = mod_var_name(modinfo, varshortname);
+	const char *fullname = mod_var_name(modinfo, varshortname);
 
 	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
 	moddata_local_variable(m).ptr = var;
 }
 
-int LoadPersistentIntX(ModuleInfo *modinfo, char *varshortname, int *var)
+int LoadPersistentIntX(ModuleInfo *modinfo, const char *varshortname, int *var)
 {
 	ModDataInfo *m;
-	char *fullname = mod_var_name(modinfo, varshortname);
+	const char *fullname = mod_var_name(modinfo, varshortname);
 
 	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
 	if (m)
@@ -1377,27 +1394,28 @@ int LoadPersistentIntX(ModuleInfo *modinfo, char *varshortname, int *var)
 		ModDataInfo mreq;
 		memset(&mreq, 0, sizeof(mreq));
 		mreq.type = MODDATATYPE_LOCAL_VARIABLE;
-		mreq.name = fullname;
+		mreq.name = strdup(fullname);
 		mreq.free = NULL;
 		m = ModDataAdd(modinfo->handle, mreq);
 		moddata_local_variable(m).i = 0;
+		safe_free(mreq.name);
 		return 0;
 	}
 }
 
-void SavePersistentIntX(ModuleInfo *modinfo, char *varshortname, int var)
+void SavePersistentIntX(ModuleInfo *modinfo, const char *varshortname, int var)
 {
 	ModDataInfo *m;
-	char *fullname = mod_var_name(modinfo, varshortname);
+	const char *fullname = mod_var_name(modinfo, varshortname);
 
 	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
 	moddata_local_variable(m).i = var;
 }
 
-int LoadPersistentLongX(ModuleInfo *modinfo, char *varshortname, long *var)
+int LoadPersistentLongX(ModuleInfo *modinfo, const char *varshortname, long *var)
 {
 	ModDataInfo *m;
-	char *fullname = mod_var_name(modinfo, varshortname);
+	const char *fullname = mod_var_name(modinfo, varshortname);
 
 	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
 	if (m)
@@ -1408,18 +1426,19 @@ int LoadPersistentLongX(ModuleInfo *modinfo, char *varshortname, long *var)
 		ModDataInfo mreq;
 		memset(&mreq, 0, sizeof(mreq));
 		mreq.type = MODDATATYPE_LOCAL_VARIABLE;
-		mreq.name = fullname;
+		mreq.name = strdup(fullname);
 		mreq.free = NULL;
 		m = ModDataAdd(modinfo->handle, mreq);
 		moddata_local_variable(m).l = 0;
+		safe_free(mreq.name);
 		return 0;
 	}
 }
 
-void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long var)
+void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, long var)
 {
 	ModDataInfo *m;
-	char *fullname = mod_var_name(modinfo, varshortname);
+	const char *fullname = mod_var_name(modinfo, varshortname);
 
 	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
 	moddata_local_variable(m).l = var;
diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in
@@ -28,13 +28,14 @@ INCLUDES = ../include/channel.h \
 	../include/ircsprintf.h \
 	../include/license.h \
 	../include/modules.h ../include/modversion.h ../include/msg.h \
-	../include/numeric.h ../include/proto.h ../include/dns.h \
+	../include/numeric.h ../include/dns.h \
 	../include/resource.h ../include/setup.h \
 	../include/struct.h ../include/sys.h \
-	../include/types.h ../include/url.h \
+	../include/types.h \
 	../include/version.h ../include/whowas.h
 
-R_MODULES= \
+MODULES= \
+	cloak_md5.so cloak_sha256.so cloak_none.so \
 	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 \
@@ -49,7 +50,7 @@ R_MODULES= \
 	invite.so list.so time.so svskill.so sjoin.so \
 	pass.so userhost.so ison.so silence.so knock.so \
 	umode2.so squit.so protoctl.so addomotd.so \
-	wallops.so admin.so globops.so locops.so \
+	admin.so globops.so locops.so \
 	trace.so netinfo.so links.so help.so rules.so \
 	close.so map.so eos.so server.so stats.so \
 	dccdeny.so whowas.so \
@@ -71,604 +72,48 @@ R_MODULES= \
 	account-tag.so labeled-response.so link-security.so \
 	message-ids.so plaintext-policy.so server-time.so sts.so \
 	echo-message.so userip-tag.so userhost-tag.so \
-	bot-tag.so \
-	reply-tag.so typing-indicator.so \
+	bot-tag.so reply-tag.so json-log-tag.so \
+	typing-indicator.so \
 	ident_lookup.so history.so chathistory.so \
-	targetfloodprot.so clienttagdeny.so
+	targetfloodprot.so clienttagdeny.so watch-backend.so \
+	monitor.so slog.so tls_cipher.so operinfo.so \
+	unreal_server_compat.so \
+	extended-monitor.so geoip_csv.so \
+	geoip_base.so extjwt.so \
+	$(GEOIP_CLASSIC_OBJECTS) $(GEOIP_MAXMIND_OBJECTS)
 
-MODULES=cloak.so $(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
 RM=@RM@
 
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
 all: build
 
 build: $(MODULES)
 	cd chanmodes; $(MAKE) all
 	cd usermodes; $(MAKE) all
-	cd snomasks; $(MAKE) all
 	cd extbans; $(MAKE) all
 	cd third; $(MAKE) all
 
-#############################################################################
-#             .so's section
-#############################################################################
-
-chgname.so: chgname.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o chgname.so chgname.c
-
-kill.so: kill.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o kill.so kill.c
-
-lag.so: lag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o lag.so lag.c
-
-message.so: message.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o message.so message.c
-
-oper.so: oper.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o oper.so oper.c
-
-pingpong.so: pingpong.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o pingpong.so pingpong.c
-
-quit.so: quit.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o quit.so quit.c
-
-sendumode.so: sendumode.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sendumode.so sendumode.c
-
-sqline.so: sqline.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sqline.so sqline.c
-
-tsctl.so: tsctl.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o tsctl.so tsctl.c
-
-unsqline.so: unsqline.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o unsqline.so unsqline.c
-
-whois.so: whois.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o whois.so whois.c
-
-sethost.so: sethost.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sethost.so sethost.c
-
-chghost.so: chghost.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o chghost.so chghost.c
-
-chgident.so: chgident.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o chgident.so chgident.c
-
-setident.so: setident.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o setident.so setident.c
-
-setname.so: setname.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o setname.so setname.c
-
-sdesc.so: sdesc.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o sdesc.so sdesc.c
-
-svsmode.so: svsmode.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o svsmode.so svsmode.c
-
-swhois.so: swhois.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o swhois.so swhois.c
-
-svsmotd.so: svsmotd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o svsmotd.so svsmotd.c
-
-svsnline.so: svsnline.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o svsnline.so svsnline.c
-
-who_old.so: who_old.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o who_old.so who_old.c
-
-whox.so: whox.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		 -o whox.so whox.c
-
-mkpasswd.so: mkpasswd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o mkpasswd.so mkpasswd.c
-
-away.so: away.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o away.so away.c
-
-svsnoop.so: svsnoop.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svsnoop.so svsnoop.c
-
-svsnick.so: svsnick.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svsnick.so svsnick.c
-
-tkl.so: tkl.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o tkl.so tkl.c
-
-vhost.so: vhost.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o vhost.so vhost.c
-
-cycle.so: cycle.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o cycle.so cycle.c
-
-svsjoin.so: svsjoin.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svsjoin.so svsjoin.c
-
-svspart.so: svspart.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svspart.so svspart.c
-
-svslusers.so: svslusers.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svslusers.so svslusers.c
-
-svswatch.so: svswatch.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svswatch.so svswatch.c
-
-svssilence.so: svssilence.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svssilence.so svssilence.c
-
-sendsno.so: sendsno.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sendsno.so sendsno.c
-
-svssno.so: svssno.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svssno.so svssno.c
-
-sajoin.so: sajoin.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sajoin.so sajoin.c
-
-sapart.so: sapart.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sapart.so sapart.c
-
-samode.so: samode.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o samode.so samode.c
-
-kick.so: kick.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o kick.so kick.c
-
-topic.so: topic.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o topic.so topic.c
-
-invite.so: invite.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o invite.so invite.c
-
-list.so: list.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o list.so list.c
-
-time.so: time.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o time.so time.c
-
-svskill.so: svskill.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svskill.so svskill.c
-
-sjoin.so: sjoin.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sjoin.so sjoin.c
-
-pass.so: pass.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o pass.so pass.c
-
-userhost.so: userhost.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o userhost.so userhost.c
-
-ison.so: ison.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o ison.so ison.c
-
-silence.so: silence.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o silence.so silence.c
-
-knock.so: knock.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o knock.so knock.c
-
-umode2.so: umode2.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o umode2.so umode2.c
-
-squit.so: squit.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o squit.so squit.c
-
-protoctl.so: protoctl.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o protoctl.so protoctl.c
-
-addmotd.so: addmotd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o addmotd.so addmotd.c
-
-addomotd.so: addomotd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o addomotd.so addomotd.c
-
-wallops.so: wallops.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o wallops.so wallops.c
-
-admin.so: admin.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o admin.so admin.c
-		
-globops.so: globops.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o globops.so globops.c
-
-locops.so: locops.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o locops.so locops.c
-
-trace.so: trace.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o trace.so trace.c
-
-netinfo.so: netinfo.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o netinfo.so netinfo.c
-
-links.so: links.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o links.so links.c
-
-help.so: help.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o help.so help.c
-
-rules.so: rules.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o rules.so rules.c
-
-close.so: close.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o close.so close.c
-
-map.so: map.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o map.so map.c
-
-eos.so: eos.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o eos.so eos.c
-
-server.so: server.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o server.so server.c
-
-stats.so: stats.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o stats.so stats.c
-
-dccdeny.so: dccdeny.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o dccdeny.so dccdeny.c
-
-whowas.so: whowas.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o whowas.so whowas.c
-
-connect.so: connect.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o connect.so connect.c
-
-dccallow.so: dccallow.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o dccallow.so dccallow.c
-
-userip.so: userip.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o userip.so userip.c
-
-nick.so: nick.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o nick.so nick.c
-
-user.so: user.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o user.so user.c
-
-mode.so: mode.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o mode.so mode.c
-
-watch.so: watch.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o watch.so watch.c
-
-part.so: part.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o part.so part.c
-
-join.so: join.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o join.so join.c
-
-motd.so: motd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o motd.so motd.c
-
-opermotd.so: opermotd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o opermotd.so opermotd.c
-
-botmotd.so: botmotd.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o botmotd.so botmotd.c
-
-lusers.so: lusers.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o lusers.so lusers.c
-
-names.so: names.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-	       -o names.so names.c
-
-svsnolag.so: svsnolag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o svsnolag.so svsnolag.c
-
-starttls.so: starttls.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o starttls.so starttls.c
-
-webredir.so: webredir.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o webredir.so webredir.c
-
-cap.so: cap.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o cap.so cap.c
-
-sasl.so: sasl.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sasl.so sasl.c
-
-md.so: md.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o md.so md.c
-
-certfp.so: certfp.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o certfp.so certfp.c
-
-tls_antidos.so: tls_antidos.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o tls_antidos.so tls_antidos.c
-
-webirc.so: webirc.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o webirc.so webirc.c
-
-websocket.so: websocket.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o websocket.so websocket.c
-
-blacklist.so: blacklist.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o blacklist.so blacklist.c
-
-jointhrottle.so: jointhrottle.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o jointhrottle.so jointhrottle.c
-
-antirandom.so: antirandom.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o antirandom.so antirandom.c
-
-hideserver.so: hideserver.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o hideserver.so hideserver.c
-
-jumpserver.so: jumpserver.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o jumpserver.so jumpserver.c
-
-ircops.so: ircops.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o ircops.so ircops.c
-
-staff.so: staff.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o staff.so staff.c
-
-nocodes.so: nocodes.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nocodes.so nocodes.c
-
-charsys.so: charsys.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o charsys.so charsys.c
-
-antimixedutf8.so: antimixedutf8.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o antimixedutf8.so antimixedutf8.c
-
-authprompt.so: authprompt.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o authprompt.so authprompt.c
-
-sinfo.so: sinfo.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sinfo.so sinfo.c
-
-reputation.so: reputation.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o reputation.so reputation.c
-
-connthrottle.so: connthrottle.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o connthrottle.so connthrottle.c
-
-history_backend_mem.so: history_backend_mem.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o history_backend_mem.so history_backend_mem.c
-
-history_backend_null.so: history_backend_null.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o history_backend_null.so history_backend_null.c
-
-tkldb.so: tkldb.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o tkldb.so tkldb.c
-
-channeldb.so: channeldb.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o channeldb.so channeldb.c
-
-restrict-commands.so: restrict-commands.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o restrict-commands.so restrict-commands.c
-
-rmtkl.so: rmtkl.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o rmtkl.so rmtkl.c
-
-message-tags.so: message-tags.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o message-tags.so message-tags.c
-
-batch.so: batch.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o batch.so batch.c
-
-account-tag.so: account-tag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o account-tag.so account-tag.c
-
-labeled-response.so: labeled-response.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o labeled-response.so labeled-response.c
-
-link-security.so: link-security.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o link-security.so link-security.c
-
-message-ids.so: message-ids.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o message-ids.so message-ids.c
-
-plaintext-policy.so: plaintext-policy.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o plaintext-policy.so plaintext-policy.c
-
-server-time.so: server-time.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o server-time.so server-time.c
-
-sts.so: sts.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o sts.so sts.c
-
-echo-message.so: echo-message.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o echo-message.so echo-message.c
-
-userip-tag.so: userip-tag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o userip-tag.so userip-tag.c
-
-userhost-tag.so: userhost-tag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o userhost-tag.so userhost-tag.c
-
-bot-tag.so: bot-tag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o bot-tag.so bot-tag.c
-
-reply-tag.so: reply-tag.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o reply-tag.so reply-tag.c
-
-typing-indicator.so: typing-indicator.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o typing-indicator.so typing-indicator.c
-
-require-module.so: require-module.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o require-module.so require-module.c
-
-account-notify.so: account-notify.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o account-notify.so account-notify.c
-
-ident_lookup.so: ident_lookup.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o ident_lookup.so ident_lookup.c
-
-history.so: history.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o history.so history.c
-
-chathistory.so: chathistory.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o chathistory.so chathistory.c
-
-targetfloodprot.so: targetfloodprot.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o targetfloodprot.so targetfloodprot.c
-
-clienttagdeny.so: clienttagdeny.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o clienttagdeny.so clienttagdeny.c
-
-#############################################################################
-# capabilities
-#############################################################################
-
-#############################################################################
-#             and now the remaining modules...
-#############################################################################
-
-cloak.so: cloak.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o cloak.so cloak.c
-
 clean:
 	$(RM) -f *.o *.so *~ core
 	cd chanmodes; $(MAKE) clean
 	cd usermodes; $(MAKE) clean
-	cd snomasks; $(MAKE) clean
 	cd extbans; $(MAKE) clean
 	cd third; $(MAKE) clean
+
+# Generic *.so rule:
+%.so: %.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o $@ $<
+
+# geoip_classic requires extra library
+geoip_classic.so: geoip_classic.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) $(GEOIP_CLASSIC_CFLAGS) -DDYNAMIC_LINKING \
+		-o geoip_classic.so geoip_classic.c @LDFLAGS_PRIVATELIBS@ $(GEOIP_CLASSIC_LIBS)
+
+# geoip_maxmind requires another extra library
+geoip_maxmind.so: geoip_maxmind.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) $(LIBMAXMINDDB_CFLAGS) -DDYNAMIC_LINKING \
+		-o geoip_maxmind.so geoip_maxmind.c @LDFLAGS_PRIVATELIBS@ $(LIBMAXMINDDB_LIBS)
diff --git a/src/modules/account-notify.c b/src/modules/account-notify.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0", 			/* Version */
 	"account-notify CAP",	/* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
@@ -70,7 +70,7 @@ int account_notify_account_login(Client *client, MessageTag *recv_mtags)
 				     CAP_ACCOUNT_NOTIFY, mtags,
 				     ":%s ACCOUNT %s",
 				     client->name,
-				     !isdigit(*client->user->svid) ? client->user->svid : "*");
+				     IsLoggedIn(client) ? client->user->account : "*");
 	free_message_tags(mtags);
 	return 0;
 }
diff --git a/src/modules/account-tag.c b/src/modules/account-tag.c
@@ -28,14 +28,14 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"account-tag CAP",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
 long CAP_ACCOUNT_TAG = 0L;
 
-int account_tag_mtag_is_ok(Client *client, char *name, char *value);
-void mtag_add_account(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int account_tag_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_account(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -75,7 +75,7 @@ MOD_UNLOAD()
  * syntax.
  * We simply allow account-tag ONLY from servers and with any syntax.
  */
-int account_tag_mtag_is_ok(Client *client, char *name, char *value)
+int account_tag_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client))
 		return 1;
@@ -83,15 +83,15 @@ int account_tag_mtag_is_ok(Client *client, char *name, char *value)
 	return 0;
 }
 
-void mtag_add_account(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_account(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m;
 
-	if (client && client->user && (*client->user->svid != '*') && !isdigit(*client->user->svid))
+	if (IsLoggedIn(client))
 	{
 		m = safe_alloc(sizeof(MessageTag));
 		safe_strdup(m->name, "account");
-		safe_strdup(m->value, client->user->svid);
+		safe_strdup(m->value, client->user->account);
 
 		AddListItem(m, *mtag_list);
 	}
diff --git a/src/modules/addmotd.c b/src/modules/addmotd.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /addmotd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,7 +60,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_addmotd)
 {
 	FILE *conf;
-	char *text;
+	const char *text;
 
 	text = parc > 1 ? parv[1] : NULL;
 
diff --git a/src/modules/addomotd.c b/src/modules/addomotd.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /addomotd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,7 +60,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_addomotd)
 {
 	FILE *conf;
-	char *text;
+	const char *text;
 
 	text = parc > 1 ? parv[1] : NULL;
 
diff --git a/src/modules/admin.c b/src/modules/admin.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /admin", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -61,7 +61,7 @@ CMD_FUNC(cmd_admin)
 
 	if (IsUser(client))
 	{
-		if (hunt_server(client, recv_mtags, ":%s ADMIN :%s", 1, parc, parv) != HUNTED_ISME)
+		if (hunt_server(client, recv_mtags, "ADMIN", 1, parc, parv) != HUNTED_ISME)
 			return;
 	}
 
diff --git a/src/modules/antimixedutf8.c b/src/modules/antimixedutf8.c
@@ -48,7 +48,7 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"Mixed UTF8 character filter (look-alike character spam) - by Syzop",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 struct {
@@ -99,22 +99,22 @@ int detect_script(const char *t)
 	else if ((t[0] == 0xd3) && (t[1] >= 0x80) && (t[1] <= 0xbf))
 		return SCRIPT_CYRILLIC;
 
-	if((t[0] == 0xe4) && (t[1] >= 0xb8) && (t[1] <= 0xbf))
+	if ((t[0] == 0xe4) && (t[1] >= 0xb8) && (t[1] <= 0xbf))
 		return SCRIPT_CJK;
 	else if ((t[0] >= 0xe5) && (t[0] <= 0xe9) && (t[1] >= 0x80) && (t[1] <= 0xbf))
 		return SCRIPT_CJK;
 
-	if((t[0] == 0xea) && (t[1] >= 0xb0) && (t[1] <= 0xbf))
+	if ((t[0] == 0xea) && (t[1] >= 0xb0) && (t[1] <= 0xbf))
 		return SCRIPT_HANGUL;
 	else if ((t[0] >= 0xeb) && (t[0] <= 0xec) && (t[1] >= 0x80) && (t[1] <= 0xbf))
 		return SCRIPT_HANGUL;
 	else if ((t[0] == 0xed) && (t[1] >= 0x80) && (t[1] <= 0x9f))
 		return SCRIPT_HANGUL;
 
-	if((t[0] == 0xe1) && (t[1] >= 0x90) && (t[1] <= 0x99))
+	if ((t[0] == 0xe1) && (t[1] >= 0x90) && (t[1] <= 0x99))
 		return SCRIPT_CANADIAN;
 
-	if((t[0] == 0xe0) && (t[1] >= 0xb0) && (t[1] <= 0xb1))
+	if ((t[0] == 0xe0) && (t[1] >= 0xb0) && (t[1] <= 0xb1))
 		return SCRIPT_TELUGU;
 
 	if ((t[0] >= 'a') && (t[0] <= 'z'))
@@ -206,12 +206,9 @@ CMD_OVERRIDE_FUNC(override_msg)
 	score = lookalikespam_score(StripControlCodes(parv[2]));
 	if ((score >= cfg.score) && !find_tkl_exception(TKL_ANTIMIXEDUTF8, client))
 	{
-		if (cfg.ban_action == BAN_ACT_KILL)
-		{
-			sendto_realops("[antimixedutf8] Killed connection from %s (score %d)",
-				GetIP(client), score);
-		} /* no else here!! */
-
+		unreal_log(ULOG_INFO, "antimixedutf8", "ANTIMIXEDUTF8_HIT", client,
+		           "[antimixedutf8] Client $client.details hit score $score -- taking action",
+		           log_data_integer("score", score));
 		if ((cfg.ban_action == BAN_ACT_BLOCK) ||
 		    ((cfg.ban_action == BAN_ACT_SOFT_BLOCK) && !IsLoggedIn(client)))
 		{
@@ -246,10 +243,10 @@ MOD_INIT()
 
 MOD_LOAD()
 {
-	if (!CommandOverrideAdd(modinfo->handle, "PRIVMSG", override_msg))
+	if (!CommandOverrideAdd(modinfo->handle, "PRIVMSG", 0, override_msg))
 		return MOD_FAILED;
 
-	if (!CommandOverrideAdd(modinfo->handle, "NOTICE", override_msg))
+	if (!CommandOverrideAdd(modinfo->handle, "NOTICE", 0, override_msg))
 		return MOD_FAILED;
 
 	return MOD_SUCCESS;
@@ -286,45 +283,45 @@ int antimixedutf8_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *er
 		return 0;
 	
 	/* We are only interrested in set::antimixedutf8... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "antimixedutf8"))
+	if (!ce || !ce->name || strcmp(ce->name, "antimixedutf8"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
 			config_error("%s:%i: set::antimixedutf8::%s with no value",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		} else
-		if (!strcmp(cep->ce_varname, "score"))
+		if (!strcmp(cep->name, "score"))
 		{
-			int v = atoi(cep->ce_vardata);
+			int v = atoi(cep->value);
 			if ((v < 1) || (v > 99))
 			{
 				config_error("%s:%i: set::antimixedutf8::score: must be between 1 - 99 (got: %d)",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v);
+					cep->file->filename, cep->line_number, v);
 				errors++;
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "ban-action"))
+		if (!strcmp(cep->name, "ban-action"))
 		{
-			if (!banact_stringtoval(cep->ce_vardata))
+			if (!banact_stringtoval(cep->value))
 			{
 				config_error("%s:%i: set::antimixedutf8::ban-action: unknown action '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "ban-reason"))
+		if (!strcmp(cep->name, "ban-reason"))
 		{
 		} else
-		if (!strcmp(cep->ce_varname, "ban-time"))
+		if (!strcmp(cep->name, "ban-time"))
 		{
 		} else
 		{
 			config_error("%s:%i: unknown directive set::antimixedutf8::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
 	}
@@ -340,26 +337,26 @@ int antimixedutf8_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 	
 	/* We are only interrested in set::antimixedutf8... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "antimixedutf8"))
+	if (!ce || !ce->name || strcmp(ce->name, "antimixedutf8"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "score"))
+		if (!strcmp(cep->name, "score"))
 		{
-			cfg.score = atoi(cep->ce_vardata);
+			cfg.score = atoi(cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "ban-action"))
+		if (!strcmp(cep->name, "ban-action"))
 		{
-			cfg.ban_action = banact_stringtoval(cep->ce_vardata);
+			cfg.ban_action = banact_stringtoval(cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "ban-reason"))
+		if (!strcmp(cep->name, "ban-reason"))
 		{
-			safe_strdup(cfg.ban_reason, cep->ce_vardata);
+			safe_strdup(cfg.ban_reason, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "ban-time"))
+		if (!strcmp(cep->name, "ban-time"))
 		{
-			cfg.ban_time = config_checkval(cep->ce_vardata, CFG_TIME);
+			cfg.ban_time = config_checkval(cep->value, CFG_TIME);
 		}
 	}
 	return 1;
diff --git a/src/modules/antirandom.c b/src/modules/antirandom.c
@@ -22,24 +22,13 @@
 
 #include "unrealircd.h"
 
-/* You can change this '//#undef' into '#define' if you want to see quite
- * a flood for every user that connects (and on-load if cfg.fullstatus_on_load).
- * Obviously only recommended for testing, use with care!
- */
-#undef  DEBUGMODE
-
-/** Change this 'undef' to 'define' to get performance information.
- * This really only meant for debugging purposes.
- */
-#undef TIMING
-
 ModuleHeader MOD_HEADER
   = {
 	"antirandom",
 	"1.4",
 	"Detect and ban users with random names",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 #ifndef MAX
@@ -515,7 +504,6 @@ struct {
 	long ban_time;
 	int convert_to_lowercase;
 	int show_failedconnects;
-	int fullstatus_on_load;
 	ConfigItem_mask *except_hosts;
 	int except_webirc;
 } cfg;
@@ -553,19 +541,14 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, antirandom_config_run);
 
 	/* Some default values: */
-	cfg.fullstatus_on_load = 1;
 	cfg.convert_to_lowercase = 1;
 	cfg.except_webirc = 1;
 
 	return MOD_SUCCESS;
 }
 
-void check_all_users(void);
-
 MOD_LOAD()
 {
-	if (cfg.fullstatus_on_load)
-		check_all_users();
 	return MOD_SUCCESS;
 }
 
@@ -592,67 +575,64 @@ int antirandom_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 	
 	/* We are only interrested in set::antirandom... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "antirandom"))
+	if (!ce || !ce->name || strcmp(ce->name, "antirandom"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "except-hosts"))
+		if (!strcmp(cep->name, "except-hosts"))
 		{
 		} else
-		if (!strcmp(cep->ce_varname, "except-webirc"))
+		if (!strcmp(cep->name, "except-webirc"))
 		{
 			/* This should normally be UNDER the generic 'set::antirandom::%s with no value'
 			 * stuff but I put it here because people may think it's a hostlist and then
 			 * the error can be a tad confusing. -- Syzop
 			 */
-			if (!cep->ce_vardata)
+			if (!cep->value)
 			{
 				config_error("%s:%i: set::antirandom::except-webirc should be 'yes' or 'no'",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				             cep->file->filename, cep->line_number);
 				errors++;
 			}
 		} else
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
 			config_error("%s:%i: set::antirandom::%s with no value",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		} else
-		if (!strcmp(cep->ce_varname, "threshold"))
+		if (!strcmp(cep->name, "threshold"))
 		{
 			req.threshold = 1;
 		} else
-		if (!strcmp(cep->ce_varname, "ban-action"))
+		if (!strcmp(cep->name, "ban-action"))
 		{
-			if (!banact_stringtoval(cep->ce_vardata))
+			if (!banact_stringtoval(cep->value))
 			{
 				config_error("%s:%i: set::antirandom::ban-action: unknown action '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			} else
 				req.ban_action = 1;
 		} else
-		if (!strcmp(cep->ce_varname, "ban-reason"))
+		if (!strcmp(cep->name, "ban-reason"))
 		{
 			req.ban_reason = 1;
 		} else
-		if (!strcmp(cep->ce_varname, "ban-time"))
+		if (!strcmp(cep->name, "ban-time"))
 		{
 			req.ban_time = 1;
 		} else
-		if (!strcmp(cep->ce_varname, "convert-to-lowercase"))
-		{
-		} else
-		if (!strcmp(cep->ce_varname, "fullstatus-on-load"))
+		if (!strcmp(cep->name, "convert-to-lowercase"))
 		{
 		} else
-		if (!strcmp(cep->ce_varname, "show-failedconnects"))
+		if (!strcmp(cep->name, "show-failedconnects"))
 		{
 		} else
 		{
 			config_error("%s:%i: unknown directive set::antirandom::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
 	}
@@ -668,47 +648,43 @@ int antirandom_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 	
 	/* We are only interrested in set::antirandom... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "antirandom"))
+	if (!ce || !ce->name || strcmp(ce->name, "antirandom"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "except-hosts"))
+		if (!strcmp(cep->name, "except-hosts"))
 		{
-			for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
+			for (cep2 = cep->items; cep2; cep2 = cep2->next)
 				unreal_add_masks(&cfg.except_hosts, cep2);
 		} else
-		if (!strcmp(cep->ce_varname, "except-webirc"))
+		if (!strcmp(cep->name, "except-webirc"))
 		{
-			cfg.except_webirc = config_checkval(cep->ce_vardata, CFG_YESNO);
+			cfg.except_webirc = config_checkval(cep->value, CFG_YESNO);
 		} else
-		if (!strcmp(cep->ce_varname, "threshold"))
+		if (!strcmp(cep->name, "threshold"))
 		{
-			cfg.threshold = atoi(cep->ce_vardata);
+			cfg.threshold = atoi(cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "ban-action"))
+		if (!strcmp(cep->name, "ban-action"))
 		{
-			cfg.ban_action = banact_stringtoval(cep->ce_vardata);
+			cfg.ban_action = banact_stringtoval(cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "ban-reason"))
+		if (!strcmp(cep->name, "ban-reason"))
 		{
-			safe_strdup(cfg.ban_reason, cep->ce_vardata);
+			safe_strdup(cfg.ban_reason, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "ban-time"))
+		if (!strcmp(cep->name, "ban-time"))
 		{
-			cfg.ban_time = config_checkval(cep->ce_vardata, CFG_TIME);
+			cfg.ban_time = config_checkval(cep->value, CFG_TIME);
 		} else
-		if (!strcmp(cep->ce_varname, "convert-to-lowercase"))
+		if (!strcmp(cep->name, "convert-to-lowercase"))
 		{
-			cfg.convert_to_lowercase = config_checkval(cep->ce_vardata, CFG_YESNO);
+			cfg.convert_to_lowercase = config_checkval(cep->value, CFG_YESNO);
 		}
-		if (!strcmp(cep->ce_varname, "show-failedconnects"))
-		{
-			cfg.show_failedconnects = config_checkval(cep->ce_vardata, CFG_YESNO);
-		} else
-		if (!strcmp(cep->ce_varname, "fullstatus-on-load"))
+		if (!strcmp(cep->name, "show-failedconnects"))
 		{
-			cfg.fullstatus_on_load = config_checkval(cep->ce_vardata, CFG_YESNO);
+			cfg.show_failedconnects = config_checkval(cep->value, CFG_YESNO);
 		}
 	}
 	return 1;
@@ -820,24 +796,15 @@ static int internal_getscore(char *str)
 	
 	if (digits >= 5)
 	{
-		score += 5 + (digits - 5);
-#ifdef DEBUGMODE
-		sendto_ops_and_log("score@'%s': MATCH for digits check", str);
-#endif
+		score += digits;
 	}
 	if (vowels >= 4)
 	{
-		score += 4 + (vowels - 4);
-#ifdef DEBUGMODE
-		sendto_ops_and_log("score@'%s': MATCH for vowels check", str);
-#endif
+		score += vowels;
 	}
 	if (consonants >= 4)
 	{
-		score += 4 + (consonants - 4);
-#ifdef DEBUGMODE
-		sendto_ops_and_log("score@'%s': MATCH for consonants check", str);
-#endif
+		score += consonants;
 	}
 	
 	for (t=triples; t; t=t->next)
@@ -846,10 +813,6 @@ static int internal_getscore(char *str)
 			if ((t->two[0] == s[0]) && (t->two[1] == s[1]) && s[2] && strchr(t->rest, s[2]))
 			{
 				score++; /* OK */
-#ifdef DEBUGMODE
-				sendto_ops_and_log("score@'%s': MATCH for '%s[%s]' %c/%c/%c", str, t->two, t->rest,
-					s[0], s[1], s[2]);
-#endif
 			}
 	}
 
@@ -868,11 +831,6 @@ static int get_spam_score(Client *client)
 	char *gecos = client->info;
 	char nbuf[NICKLEN+1], ubuf[USERLEN+1], rbuf[REALLEN+1];
 	int nscore, uscore, gscore, score;
-#ifdef TIMING
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
 
 	if (cfg.convert_to_lowercase)
 	{
@@ -889,46 +847,9 @@ static int get_spam_score(Client *client)
 	gscore = internal_getscore(gecos);
 	score = nscore + uscore + gscore;
 
-#ifdef TIMING
-	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "AntiRandom Timing: %ld microseconds",
-		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
-#endif
-#ifdef DEBUGMODE
-	sendto_ops_and_log("got score: %d/%d/%d = %d",
-		nscore, uscore, gscore, score);
-#endif
-
 	return score;
 }
 
-void check_all_users(void)
-{
-	Client *client;
-	int matches=0, score;
-	
-	list_for_each_entry(client, &lclient_list, lclient_node)
-	{
-		if (IsUser(client))
-		{
-			if (is_exempt(client))
-				continue;
-
-			score = get_spam_score(client);
-			if (score > cfg.threshold)
-			{
-				if (!matches)
-					sendto_realops("[antirandom] Full status report follows:");
-				sendto_realops("%d points: %s!%s@%s:%s",
-					score, client->name, client->user->username, client->user->realhost, client->info);
-				matches++;
-			}
-		}
-	}
-	if (matches)
-		sendto_realops("[antirandom] %d match%s", matches, matches == 1 ? "" : "es");
-}
-
 int antirandom_preconnect(Client *client)
 {
 	int score;
@@ -941,13 +862,17 @@ int antirandom_preconnect(Client *client)
 	{
 		if (cfg.ban_action == BAN_ACT_WARN)
 		{
-			sendto_ops_and_log("[antirandom] would have denied access to user with score %d: %s!%s@%s:%s",
-				score, client->name, client->user->username, client->user->realhost, client->info);
+			unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client,
+			           "[antirandom] would have denied access to user with score $score: $client:$client.info",
+			           log_data_integer("score", score));
 			return HOOK_CONTINUE;
 		}
 		if (cfg.show_failedconnects)
-			sendto_ops_and_log("[antirandom] denied access to user with score %d: %s!%s@%s:%s",
-				score, client->name, client->user->username, client->user->realhost, client->info);
+		{
+			unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client,
+			           "[antirandom] denied access to user with score $score: $client:$client.info",
+			           log_data_integer("score", score));
+		}
 		place_host_ban(client, cfg.ban_action, cfg.ban_reason, cfg.ban_time);
 		return HOOK_DENY;
 	}
@@ -972,7 +897,7 @@ static int is_exempt(Client *client)
 	/* WEBIRC gateway and exempt? */
 	if (cfg.except_webirc)
 	{
-		char *val = moddata_client_get(client, "webirc");
+		const char *val = moddata_client_get(client, "webirc");
 		if (val && (atoi(val)>0))
 			return 1;
 	}
diff --git a/src/modules/authprompt.c b/src/modules/authprompt.c
@@ -25,7 +25,7 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"SASL authentication for clients that don't support SASL",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /** Configuration settings */
@@ -33,6 +33,7 @@ struct {
 	int enabled;
 	MultiLine *message;
 	MultiLine *fail_message;
+	MultiLine *unconfirmed_message;
 } cfg;
 
 /** User struct */
@@ -50,10 +51,9 @@ static void init_config(void);
 static void config_postdefaults(void);
 int authprompt_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int authprompt_config_run(ConfigFile *, ConfigEntry *, int);
-int authprompt_require_sasl(Client *client, char *reason);
-int authprompt_sasl_continuation(Client *client, char *buf);
+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, char *reason, long duration);
+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_connect(Client *client);
 CMD_FUNC(cmd_auth);
@@ -89,7 +89,6 @@ MOD_INIT()
 
 	init_config();
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, authprompt_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_REQUIRE_SASL, 0, authprompt_require_sasl);
 	HookAdd(modinfo->handle, HOOKTYPE_SASL_CONTINUATION, 0, authprompt_sasl_continuation);
 	HookAdd(modinfo->handle, HOOKTYPE_SASL_RESULT, 0, authprompt_sasl_result);
 	HookAdd(modinfo->handle, HOOKTYPE_PLACE_HOST_BAN, 0, authprompt_place_host_ban);
@@ -133,12 +132,18 @@ static void config_postdefaults(void)
 	{
 		addmultiline(&cfg.fail_message, "Authentication failed.");
 	}
+	if (!cfg.unconfirmed_message)
+	{
+		addmultiline(&cfg.unconfirmed_message, "You are trying to use an unconfirmed services account.");
+		addmultiline(&cfg.unconfirmed_message, "This services account can only be used after it has been activated/confirmed.");
+	}
 }
 
 static void free_config(void)
 {
 	freemultiline(cfg.message);
 	freemultiline(cfg.fail_message);
+	freemultiline(cfg.unconfirmed_message);
 	memset(&cfg, 0, sizeof(cfg)); /* needed! */
 }
 
@@ -151,29 +156,32 @@ int authprompt_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 
 	/* We are only interrested in set::authentication-prompt... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "authentication-prompt"))
+	if (!ce || !ce->name || strcmp(ce->name, "authentication-prompt"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
 			config_error("%s:%i: set::authentication-prompt::%s with no value",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		} else
-		if (!strcmp(cep->ce_varname, "enabled"))
+		if (!strcmp(cep->name, "enabled"))
 		{
 		} else
-		if (!strcmp(cep->ce_varname, "message"))
+		if (!strcmp(cep->name, "message"))
 		{
 		} else
-		if (!strcmp(cep->ce_varname, "fail-message"))
+		if (!strcmp(cep->name, "fail-message"))
+		{
+		} else
+		if (!strcmp(cep->name, "unconfirmed-message"))
 		{
 		} else
 		{
 			config_error("%s:%i: unknown directive set::authentication-prompt::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
 	}
@@ -189,22 +197,26 @@ int authprompt_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 
 	/* We are only interrested in set::authentication-prompt... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "authentication-prompt"))
+	if (!ce || !ce->name || strcmp(ce->name, "authentication-prompt"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "enabled"))
+		if (!strcmp(cep->name, "enabled"))
+		{
+			cfg.enabled = config_checkval(cep->value, CFG_YESNO);
+		} else
+		if (!strcmp(cep->name, "message"))
 		{
-			cfg.enabled = config_checkval(cep->ce_vardata, CFG_YESNO);
+			addmultiline(&cfg.message, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "message"))
+		if (!strcmp(cep->name, "fail-message"))
 		{
-			addmultiline(&cfg.message, cep->ce_vardata);
+			addmultiline(&cfg.fail_message, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "fail-message"))
+		if (!strcmp(cep->name, "unconfirmed-message"))
 		{
-			addmultiline(&cfg.fail_message, cep->ce_vardata);
+			addmultiline(&cfg.unconfirmed_message, cep->value);
 		}
 	}
 	return 1;
@@ -257,7 +269,7 @@ char *make_authbuf(const char *username, const char *password)
 	int size;
 
 	size = strlen(username) + 1 + strlen(username) + 1 + strlen(password);
-	if (size >= sizeof(inbuf))
+	if (size >= sizeof(inbuf)-1)
 		return NULL; /* too long */
 
 	/* Because size limits are already checked above, we can cut some corners here: */
@@ -281,7 +293,7 @@ void send_first_auth(Client *client)
 {
 	Client *sasl_server;
 	char *addr = BadPtr(client->ip) ? "0" : client->ip;
-	char *certfp = moddata_client_get(client, "certfp");
+	const char *certfp = moddata_client_get(client, "certfp");
 	sasl_server = find_client(SASL_SERVER, NULL);
 	if (!sasl_server)
 	{
@@ -365,25 +377,8 @@ void authprompt_send_auth_required_message(Client *client)
 	sendnotice_multiline(client, cfg.message);
 }
 
-int authprompt_require_sasl(Client *client, char *reason)
-{
-	/* If the client did SASL then we (authprompt) will not kick in */
-	if (HasCapability(client, "sasl"))
-		return 0;
-
-	authprompt_tag_as_auth_required(client);
-
-	/* Display the require authentication::reason */
-	if (reason && strcmp(reason, "-") && strcmp(reason, "*"))
-		sendnotice(client, "%s", reason);
-
-	authprompt_send_auth_required_message(client);
-
-	return 1;
-}
-
 /* Called upon "place a host ban on this user" (eg: spamfilter, blacklist, ..) */
-int authprompt_place_host_ban(Client *client, int action, char *reason, long duration)
+int authprompt_place_host_ban(Client *client, int action, const char *reason, long duration)
 {
 	/* 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.
@@ -437,7 +432,7 @@ int authprompt_pre_connect(Client *client)
 	return HOOK_CONTINUE; /* no action taken, proceed normally */
 }
 
-int authprompt_sasl_continuation(Client *client, char *buf)
+int authprompt_sasl_continuation(Client *client, const char *buf)
 {
 	/* If it's not for us (eg: user is doing real SASL) then return 0. */
 	if (!SEUSER(client) || !SEUSER(client)->authmsg)
@@ -468,10 +463,16 @@ int authprompt_sasl_result(Client *client, int success)
 		return 1;
 	}
 
+	if (client->user && !IsLoggedIn(client))
+	{
+		sendnotice_multiline(client, cfg.unconfirmed_message);
+		return 1;
+	}
+
 	/* Authentication was a success */
 	if (*client->name && client->user && *client->user->username && IsNotSpoof(client))
 	{
-		register_user(client, client->name, client->user->username, NULL, NULL, NULL);
+		register_user(client);
 		/* User MAY be killed now. But since we 'return 1' below, it's safe */
 	}
 
diff --git a/src/modules/away.c b/src/modules/away.c
@@ -25,6 +25,9 @@
 #include "unrealircd.h"
 
 CMD_FUNC(cmd_away);
+int away_join(Client *client, Channel *channel, MessageTag *mtags);
+
+long CAP_AWAY_NOTIFY = 0L;
 
 #define MSG_AWAY 	"AWAY"	
 
@@ -34,12 +37,19 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /away", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
 {
+	ClientCapabilityInfo c;
+	memset(&c, 0, sizeof(c));
+	c.name = "away-notify";
+	ClientCapabilityAdd(modinfo->handle, &c, &CAP_AWAY_NOTIFY);
 	CommandAdd(modinfo->handle, MSG_AWAY, cmd_away, 1, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, away_join);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_JOIN, 0, away_join);
+
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
 }
@@ -54,17 +64,44 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+int away_join(Client *client, Channel *channel, MessageTag *mtags)
+{
+	Member *lp;
+	Client *acptr;
+	int invisible = invisible_user_in_channel(client, channel);
+	for (lp = channel->members; lp; lp = lp->next)
+	{
+		acptr = lp->client;
+
+		if (!MyConnect(acptr))
+			continue; /* only locally connected clients */
+
+		if (invisible && !check_channel_access_member(lp, "hoaq") && (client != acptr))
+			continue; /* skip non-ops if requested to (used for mode +D), but always send to 'client' */
+
+		if (client->user->away && HasCapabilityFast(acptr, CAP_AWAY_NOTIFY))
+		{
+			MessageTag *mtags_away = NULL;
+			new_message(client, NULL, &mtags_away);
+			sendto_one(acptr, mtags_away, ":%s!%s@%s AWAY :%s",
+			           client->name, client->user->username, GetHost(client), client->user->away);
+			free_message_tags(mtags_away);
+		}
+	}
+	return 0;
+}
+
 /** Mark client as AWAY or mark them as back (in case of empty reason) */
 CMD_FUNC(cmd_away)
 {
-	char *new_reason = parv[1];
+	char reason[512];
 	int n, already_as_away = 0;
 	MessageTag *mtags = NULL;
 
 	if (IsServer(client))
 		return;
 
-	if (parc < 2 || !*new_reason)
+	if (parc < 2 || BadPtr(parv[1]))
 	{
 		/* Marking as not away */
 		if (client->user->away)
@@ -73,10 +110,9 @@ CMD_FUNC(cmd_away)
 
 			new_message(client, recv_mtags, &mtags);
 			sendto_server(client, 0, 0, mtags, ":%s AWAY", client->name);
-			hash_check_watch(client, RPL_NOTAWAY);
-			sendto_local_common_channels(client, client, ClientCapabilityBit("away-notify"), mtags,
+			sendto_local_common_channels(client, client, CAP_AWAY_NOTIFY, mtags,
 			                             ":%s AWAY", client->name);
-			RunHook3(HOOKTYPE_AWAY, client, mtags, NULL);
+			RunHook(HOOKTYPE_AWAY, client, mtags, NULL, 0);
 			free_message_tags(mtags);
 		}
 
@@ -85,8 +121,11 @@ CMD_FUNC(cmd_away)
 		return;
 	}
 
+	/* Obey set::away-length */
+	strlncpy(reason, parv[1], sizeof(reason), iConf.away_length);
+
 	/* Check spamfilters */
-	if (MyUser(client) && match_spamfilter(client, new_reason, SPAMF_AWAY, "AWAY", NULL, 0, NULL))
+	if (MyUser(client) && match_spamfilter(client, reason, SPAMF_AWAY, "AWAY", NULL, 0, NULL))
 		return;
 
 	/* Check away-flood */
@@ -98,21 +137,17 @@ CMD_FUNC(cmd_away)
 		return;
 	}
 
-	/* Obey set::away-length */
-	if (strlen(new_reason) > iConf.away_length)
-		new_reason[iConf.away_length] = '\0';
-
 	/* Check if the new away reason is the same as the current reason - if so then return (no change) */
-	if ((client->user->away) && !strcmp(client->user->away, new_reason))
+	if ((client->user->away) && !strcmp(client->user->away, reason))
 		return;
 
 	/* All tests passed. Now marking as away (or still away but changing the away reason) */
 
-	client->user->lastaway = TStime();
+	client->user->away_since = TStime();
 	
 	new_message(client, recv_mtags, &mtags);
 
-	sendto_server(client, 0, 0, mtags, ":%s AWAY :%s", client->id, new_reason);
+	sendto_server(client, 0, 0, mtags, ":%s AWAY :%s", client->id, reason);
 
 	if (client->user->away)
 	{
@@ -120,18 +155,16 @@ CMD_FUNC(cmd_away)
 		already_as_away = 1;
 	}
 	
-	safe_strdup(client->user->away, new_reason);
+	safe_strdup(client->user->away, reason);
 
 	if (MyConnect(client))
 		sendnumeric(client, RPL_NOWAWAY);
 
-	hash_check_watch(client, already_as_away ? RPL_REAWAY : RPL_GONEAWAY);
-
 	sendto_local_common_channels(client, client,
-	                             ClientCapabilityBit("away-notify"), mtags,
+	                             CAP_AWAY_NOTIFY, mtags,
 	                             ":%s AWAY :%s", client->name, client->user->away);
 
-	RunHook3(HOOKTYPE_AWAY, client, mtags, client->user->away);
+	RunHook(HOOKTYPE_AWAY, client, mtags, client->user->away, already_as_away);
 
 	free_message_tags(mtags);
 
diff --git a/src/modules/batch.c b/src/modules/batch.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Batch CAP", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Forward declarations */
@@ -37,7 +37,7 @@ CMD_FUNC(cmd_batch);
 /* Variables */
 long CAP_BATCH = 0L;
 
-int batch_mtag_is_ok(Client *client, char *name, char *value);
+int batch_mtag_is_ok(Client *client, const char *name, const char *value);
 
 MOD_INIT()
 {
@@ -111,7 +111,7 @@ CMD_FUNC(cmd_batch)
  * syntax.
  * We simply allow batch ONLY from servers and with any syntax.
  */
-int batch_mtag_is_ok(Client *client, char *name, char *value)
+int batch_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client))
 		return 1;
diff --git a/src/modules/blacklist.c b/src/modules/blacklist.c
@@ -26,7 +26,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Check connecting users against DNS Blacklists",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* In this module and the config syntax I tried to 'abstract' things
@@ -83,6 +83,9 @@ struct BLUser {
 	long save_tkltime;
 	char *save_opernotice;
 	char *save_reason;
+	char *save_blacklist;
+	char *save_blacklist_dns_name;
+	int save_blacklist_dns_reply;
 };
 
 /* Global variables */
@@ -96,7 +99,7 @@ void blacklist_free_conf(void);
 void delete_blacklist_block(Blacklist *e);
 void blacklist_md_free(ModData *md);
 int blacklist_handshake(Client *client);
-int blacklist_quit(Client *client, MessageTag *mtags, char *comment);
+int blacklist_quit(Client *client, MessageTag *mtags, const char *comment);
 int blacklist_preconnect(Client *client);
 void blacklist_resolver_callback(void *arg, int status, int timeouts, struct hostent *he);
 int blacklist_start_check(Client *client);
@@ -109,13 +112,11 @@ void blacklist_free_bluser_if_able(BLUser *bl);
 #define SetBLUser(x, y)	do { moddata_client(x, blacklist_md).ptr = y; } while(0)
 #define BLUSER(x)	((BLUser *)moddata_client(x, blacklist_md).ptr)
 
-long SNO_BLACKLIST = 0L;
-
 MOD_TEST()
 {
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, blacklist_config_test);
 
-	CallbackAddEx(modinfo->handle, CALLBACKTYPE_BLACKLIST_CHECK, blacklist_start_check);
+	CallbackAdd(modinfo->handle, CALLBACKTYPE_BLACKLIST_CHECK, blacklist_start_check);
 	return MOD_SUCCESS;
 }
 
@@ -150,8 +151,6 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, blacklist_rehash_complete);
 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, blacklist_quit);
 
-	SnomaskAdd(modinfo->handle, 'b', umode_allow_opers, &SNO_BLACKLIST);
-
 	return MOD_SUCCESS;
 }
 
@@ -251,192 +250,192 @@ int blacklist_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (!ce)
 		return 0;
 	
-	if (strcmp(ce->ce_varname, "blacklist"))
+	if (strcmp(ce->name, "blacklist"))
 		return 0; /* not interested in non-blacklist stuff.. */
 	
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: blacklist block without name (use: blacklist somename { })",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		*errs = 1;
 		return -1;
 	}
 
 	/* Now actually go parse the blacklist { } block */
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "dns"))
+		if (!strcmp(cep->name, "dns"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "reply"))
+				if (!strcmp(cepp->name, "reply"))
 				{
 					if (has_dns_reply)
 					{
 						/* this is an error (not a warning) */
 						config_error("%s:%i: blacklist block may contain only one blacklist::dns::reply item. "
 									 "You can specify multiple replies by using: reply { 1; 2; 4; };",
-									 cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+									 cepp->file->filename, cepp->line_number);
 						errors++;
 						continue;
 					}
-					if (!cepp->ce_vardata && !cepp->ce_entries)
+					if (!cepp->value && !cepp->items)
 					{
-						config_error_blank(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "blacklist::dns::reply");
+						config_error_blank(cepp->file->filename, cepp->line_number, "blacklist::dns::reply");
 						errors++;
 						continue;
 					}
 					has_dns_reply = 1; /* we have a reply. now whether it's actually valid is another story.. */
-					if (cepp->ce_vardata && cepp->ce_entries)
+					if (cepp->value && cepp->items)
 					{
 						config_error("%s:%i: blacklist::dns::reply must be either using format 'reply 1;' or "
 									 "'reply { 1; 2; 4; }; but not both formats at the same time.",
-									 cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+									 cepp->file->filename, cepp->line_number);
 						errors++;
 						continue;
 					}
-					if (cepp->ce_vardata)
+					if (cepp->value)
 					{
-						if (atoi(cepp->ce_vardata) <= 0)
+						if (atoi(cepp->value) <= 0)
 						{
 							config_error("%s:%i: blacklist::dns::reply must be >0",
-								cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+								cepp->file->filename, cepp->line_number);
 							errors++;
 							continue;
 						}
 					}
-					if (cepp->ce_entries)
+					if (cepp->items)
 					{
-						for (ceppp = cepp->ce_entries; ceppp; ceppp=ceppp->ce_next)
+						for (ceppp = cepp->items; ceppp; ceppp=ceppp->next)
 						{
-							if (atoi(ceppp->ce_varname) <= 0)
+							if (atoi(ceppp->name) <= 0)
 							{
 								config_error("%s:%i: all items in blacklist::dns::reply must be >0",
-									cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+									cepp->file->filename, cepp->line_number);
 								errors++;
 							}
 						}
 					}
 				} else
-				if (!cepp->ce_vardata)
+				if (!cepp->value)
 				{
-					config_error_empty(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-						"blacklist::dns", cepp->ce_varname);
+					config_error_empty(cepp->file->filename, cepp->line_number,
+						"blacklist::dns", cepp->name);
 					errors++;
 					continue;
 				} else
-				if (!strcmp(cepp->ce_varname, "name"))
+				if (!strcmp(cepp->name, "name"))
 				{
 					if (has_dns_name)
 					{
-						config_warn_duplicate(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum, "blacklist::dns::name");
+						config_warn_duplicate(cepp->file->filename,
+							cepp->line_number, "blacklist::dns::name");
 					}
 					has_dns_name = 1;
 				} else
-				if (!strcmp(cepp->ce_varname, "type"))
+				if (!strcmp(cepp->name, "type"))
 				{
 					if (has_dns_type)
 					{
-						config_warn_duplicate(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum, "blacklist::dns::type");
+						config_warn_duplicate(cepp->file->filename,
+							cepp->line_number, "blacklist::dns::type");
 					}
 					has_dns_type = 1;
-					if (!strcmp(cepp->ce_vardata, "record"))
+					if (!strcmp(cepp->value, "record"))
 						;
-					else if (!strcmp(cepp->ce_vardata, "bitmask"))
+					else if (!strcmp(cepp->value, "bitmask"))
 						;
 					else
 					{
 						config_error("%s:%i: unknown blacklist::dns::type '%s', must be either 'record' or 'bitmask'",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_vardata);
+							cepp->file->filename, cepp->line_number, cepp->value);
 						errors++;
 					}
 				}
 			}
 		} else
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"blacklist", cep->ce_varname);
+			config_error_empty(cep->file->filename, cep->line_number,
+				"blacklist", cep->name);
 			errors++;
 			continue;
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
 			if (has_action)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "blacklist::action");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "blacklist::action");
 				continue;
 			}
 			has_action = 1;
-			if (!banact_stringtoval(cep->ce_vardata))
+			if (!banact_stringtoval(cep->value))
 			{
 				config_error("%s:%i: blacklist::action has unknown action type '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ban-time"))
+		else if (!strcmp(cep->name, "ban-time"))
 		{
 			if (has_ban_time)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "blacklist::ban-time");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "blacklist::ban-time");
 				continue;
 			}
 			has_ban_time = 1;
 		} else
-		if (!strcmp(cep->ce_varname, "reason"))
+		if (!strcmp(cep->name, "reason"))
 		{
 			if (has_reason)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "blacklist::reason");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "blacklist::reason");
 				continue;
 			}
 			has_reason = 1;
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"blacklist", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"blacklist", cep->name);
 			errors++;
 		}
 	}
 
 	if (!has_action)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"blacklist::action");
 		errors++;
 	}
 
 	if (!has_reason)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"blacklist::reason");
 		errors++;
 	}
 
 	if (!has_dns_name)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"blacklist::dns::name");
 		errors++;
 	}
 
 	if (!has_dns_type)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"blacklist::dns::type");
 		errors++;
 	}
 
 	if (!has_dns_reply)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"blacklist::dns::reply");
 		errors++;
 	}
@@ -453,12 +452,12 @@ int blacklist_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_MAIN)
 		return 0;
 	
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "blacklist"))
+	if (!ce || !ce->name || strcmp(ce->name, "blacklist"))
 		return 0; /* not interested */
 
 	d = safe_alloc(sizeof(Blacklist));
-	safe_strdup(d->name, ce->ce_vardata);
-	/* set some defaults. TODO: use set::blacklist or something ? */
+	safe_strdup(d->name, ce->value);
+	/* set some defaults */
 	d->action = BAN_ACT_KILL;
 	safe_strdup(d->reason, "Your IP is on a DNS Blacklist");
 	d->ban_time = 3600;
@@ -468,28 +467,28 @@ int blacklist_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	d->backend = safe_alloc(sizeof(BlacklistBackend));
 	d->backend->dns = safe_alloc(sizeof(DNSBL));
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "dns"))
+		if (!strcmp(cep->name, "dns"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "reply"))
+				if (!strcmp(cepp->name, "reply"))
 				{
-					if (cepp->ce_vardata)
+					if (cepp->value)
 					{
 						/* single reply */
 						d->backend->dns->reply = safe_alloc(sizeof(int)*2);
-						d->backend->dns->reply[0] = atoi(cepp->ce_vardata);
+						d->backend->dns->reply[0] = atoi(cepp->value);
 						d->backend->dns->reply[1] = 0;
 					} else
-					if (cepp->ce_entries)
+					if (cepp->items)
 					{
 						/* (potentially) multiple reply values */
 						int cnt = 0;
-						for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+						for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 						{
-							if (ceppp->ce_varname)
+							if (ceppp->name)
 								cnt++;
 						}
 						
@@ -499,37 +498,37 @@ int blacklist_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 						d->backend->dns->reply = safe_alloc(sizeof(int)*(cnt+1));
 						
 						cnt = 0;
-						for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+						for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 						{
-							d->backend->dns->reply[cnt++] = atoi(ceppp->ce_varname);
+							d->backend->dns->reply[cnt++] = atoi(ceppp->name);
 						}
 						d->backend->dns->reply[cnt] = 0;
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "type"))
+				if (!strcmp(cepp->name, "type"))
 				{
-					if (!strcmp(cepp->ce_vardata, "record"))
+					if (!strcmp(cepp->value, "record"))
 						d->backend->dns->type = DNSBL_RECORD;
-					else if (!strcmp(cepp->ce_vardata, "bitmask"))
+					else if (!strcmp(cepp->value, "bitmask"))
 						d->backend->dns->type = DNSBL_BITMASK;
 				} else
-				if (!strcmp(cepp->ce_varname, "name"))
+				if (!strcmp(cepp->name, "name"))
 				{
-					safe_strdup(d->backend->dns->name, cepp->ce_vardata);
+					safe_strdup(d->backend->dns->name, cepp->value);
 				}
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
-			d->action = banact_stringtoval(cep->ce_vardata);
+			d->action = banact_stringtoval(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
-			safe_strdup(d->reason, cep->ce_vardata);
+			safe_strdup(d->reason, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "ban-time"))
+		else if (!strcmp(cep->name, "ban-time"))
 		{
-			d->ban_time = config_checkval(cep->ce_vardata, CFG_TIME);
+			d->ban_time = config_checkval(cep->value, CFG_TIME);
 		}
 	}
 
@@ -644,7 +643,7 @@ void blacklist_cancel(BLUser *bl)
 	bl->client = NULL;
 }
 
-int blacklist_quit(Client *client, MessageTag *mtags, char *comment)
+int blacklist_quit(Client *client, MessageTag *mtags, const char *comment)
 {
 	if (BLUSER(client))
 		blacklist_cancel(BLUSER(client));
@@ -725,10 +724,17 @@ int blacklist_parse_reply(struct hostent *he, int entry)
  * from blacklist_preconnect() for softbans that need to be delayed
  * as to give the user the opportunity to do SASL Authentication.
  */
-int blacklist_action(Client *client, char *opernotice, BanAction ban_action, char *ban_reason, long ban_time)
+int blacklist_action(Client *client, char *opernotice, BanAction ban_action, char *ban_reason, long ban_time,
+                     char *blacklist, char *blacklist_dns_name, int blacklist_dns_reply)
 {
-	sendto_snomask(SNO_BLACKLIST, "%s", opernotice);
-	ircd_log(LOG_KILL, "%s", opernotice);
+	unreal_log_raw(ULOG_INFO, "blacklist", "BLACKLIST_HIT", client,
+	               opernotice,
+	               log_data_string("blacklist_name", blacklist),
+	               log_data_string("blacklist_dns_name", blacklist_dns_name),
+	               log_data_integer("blacklist_dns_reply", blacklist_dns_reply),
+	               log_data_string("ban_action", banact_valtostring(ban_action)),
+	               log_data_string("ban_reason", ban_reason),
+	               log_data_integer("ban_time", ban_time));
 	if (ban_action == BAN_ACT_WARN)
 		return 0;
 	return place_host_ban(client, ban_action, ban_reason, ban_time);
@@ -766,9 +772,12 @@ void blacklist_hit(Client *client, Blacklist *bl, int reply)
 		blu->save_tkltime = bl->ban_time;
 		safe_strdup(blu->save_opernotice, opernotice);
 		safe_strdup(blu->save_reason, banbuf);
+		safe_strdup(blu->save_blacklist, bl->name);
+		safe_strdup(blu->save_blacklist_dns_name, bl->backend->dns->name);
+		blu->save_blacklist_dns_reply = reply;
 	} else {
 		/* Otherwise, execute the action immediately */
-		blacklist_action(client, opernotice, bl->action, banbuf, bl->ban_time);
+		blacklist_action(client, opernotice, bl->action, banbuf, bl->ban_time, bl->name, bl->backend->dns->name, reply);
 	}
 }
 
@@ -841,7 +850,10 @@ int blacklist_preconnect(Client *client)
 	if (IsLoggedIn(client))
 		return HOOK_CONTINUE; /* yup, so the softban does not apply. */
 
-	if (blacklist_action(client, blu->save_opernotice, blu->save_action, blu->save_reason, blu->save_tkltime))
+	if (blacklist_action(client, blu->save_opernotice, blu->save_action, blu->save_reason, blu->save_tkltime,
+	                     blu->save_blacklist, blu->save_blacklist_dns_name, blu->save_blacklist_dns_reply))
+	{
 		return HOOK_DENY;
+	}
 	return HOOK_CONTINUE; /* exempt */
 }
diff --git a/src/modules/bot-tag.c b/src/modules/bot-tag.c
@@ -33,11 +33,11 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"bot message tag",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
-int bottag_mtag_is_ok(Client *client, char *name, char *value);
-void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int bottag_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -68,7 +68,7 @@ MOD_UNLOAD()
 
 /** This function verifies if the client sending the mtag is permitted to do so.
  */
-int bottag_mtag_is_ok(Client *client, char *name, char *value)
+int bottag_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client) && (value == NULL))
 		return 1; /* OK */
@@ -76,7 +76,7 @@ int bottag_mtag_is_ok(Client *client, char *name, char *value)
 	return 0;
 }
 
-void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m;
 
diff --git a/src/modules/botmotd.c b/src/modules/botmotd.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /botmotd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,7 +60,7 @@ CMD_FUNC(cmd_botmotd)
 	MOTDLine *motdline;
 	ConfigItem_tld *tld;
 
-	if (hunt_server(client, recv_mtags, ":%s BOTMOTD :%s", 1, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "BOTMOTD", 1, parc, parv) != HUNTED_ISME)
 		return;
 
 	if (!IsUser(client))
diff --git a/src/modules/cap.c b/src/modules/cap.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /cap", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Forward declarations */
@@ -43,13 +43,7 @@ int cap_never_visible(Client *client);
 
 /* Variables */
 long CAP_IN_PROGRESS = 0L;
-long CAP_ACCOUNT_NOTIFY = 0L;
-long CAP_AWAY_NOTIFY = 0L;
-long CAP_MULTI_PREFIX = 0L;
-long CAP_USERHOST_IN_NAMES = 0L;
 long CAP_NOTIFY = 0L;
-long CAP_CHGHOST = 0L;
-long CAP_EXTENDED_JOIN = 0L;
 
 MOD_INIT()
 {
@@ -67,33 +61,9 @@ MOD_INIT()
 	ClientCapabilityAdd(modinfo->handle, &c, &CAP_IN_PROGRESS);
 
 	memset(&c, 0, sizeof(c));
-	c.name = "account-notify";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_ACCOUNT_NOTIFY);
-	
-	memset(&c, 0, sizeof(c));
-	c.name = "away-notify";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_AWAY_NOTIFY);
-
-	memset(&c, 0, sizeof(c));
-	c.name = "multi-prefix";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_MULTI_PREFIX);
-
-	memset(&c, 0, sizeof(c));
-	c.name = "userhost-in-names";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_USERHOST_IN_NAMES);
-
-	memset(&c, 0, sizeof(c));
 	c.name = "cap-notify";
 	ClientCapabilityAdd(modinfo->handle, &c, &CAP_NOTIFY);
 
-	memset(&c, 0, sizeof(c));
-	c.name = "chghost";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHGHOST);
-
-	memset(&c, 0, sizeof(c));
-	c.name = "extended-join";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_EXTENDED_JOIN);
-
 	HookAdd(modinfo->handle, HOOKTYPE_IS_HANDSHAKE_FINISHED, 0, cap_is_handshake_finished);
 
 	return MOD_SUCCESS;
@@ -139,17 +109,17 @@ static ClientCapability *clicap_find(Client *client, const char *data, int *nega
 		return NULL;
 	}
 
-	if(*p == '-')
+	if (*p == '-')
 	{
 		*negate = 1;
 		p++;
 
 		/* someone sent a '-' without a parameter.. */
-		if(*p == '\0')
+		if (*p == '\0')
 			return NULL;
 	}
 
-	if((s = strchr(p, ' ')))
+	if ((s = strchr(p, ' ')))
 		*s++ = '\0';
 
 	cap = ClientCapabilityFind(p, client);
@@ -190,7 +160,7 @@ static void clicap_generate(Client *client, const char *subcmd, int flags)
 	for (cap = clicaps; cap; cap = cap->next)
 	{
 		char name[256];
-		char *param;
+		const char *param;
 
 		if (cap->visible && !cap->visible(client))
 			continue; /* hidden */
@@ -240,7 +210,7 @@ static void cap_end(Client *client, const char *arg)
 	ClearCapabilityFast(client, CAP_IN_PROGRESS);
 
 	if (*client->name && client->user && *client->user->username && IsNotSpoof(client))
-		register_user(client, client->name, client->user->username, NULL, NULL, NULL);
+		register_user(client);
 }
 
 static void cap_list(Client *client, const char *arg)
@@ -386,8 +356,8 @@ CMD_FUNC(cmd_cap)
 	 * Only add a 1 second fake lag penalty if this is the XXth command.
 	 * This will speed up connections considerably.
 	 */
-	if (client->local->receiveM > 15)
-		client->local->since++;
+	if (client->local->traffic.messages_received > 15)
+		add_fake_lag(client, 1000);
 
 	if (DISABLE_CAP)
 	{
@@ -406,7 +376,7 @@ CMD_FUNC(cmd_cap)
 		return;
 	}
 
-	if(!(cmd = bsearch(parv[1], clicap_cmdtable,
+	if (!(cmd = bsearch(parv[1], clicap_cmdtable,
 			   sizeof(clicap_cmdtable) / sizeof(struct clicap_cmd),
 			   sizeof(struct clicap_cmd), (bqcmp) clicap_cmd_search)))
 	{
diff --git a/src/modules/certfp.c b/src/modules/certfp.c
@@ -1,6 +1,6 @@
 /*
  * Certificate Fingerprint Module
- * This grabs the SHA256 fingerprint of the SSL/TLS client certificate
+ * This grabs the SHA256 fingerprint of the TLS client certificate
  * the user is using, shares it with the other servers (and rest of
  * UnrealIRCd) and shows it in /WHOIS etc.
  *
@@ -17,24 +17,22 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Certificate fingerprint",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
 void certfp_free(ModData *m);
-char *certfp_serialize(ModData *m);
-void certfp_unserialize(char *str, ModData *m);
+const char *certfp_serialize(ModData *m);
+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);
+int certfp_whois(Client *client, Client *target, NameValuePrioList **list);
 
 ModDataInfo *certfp_md; /* Module Data structure which we acquire */
 
-#define WHOISCERTFP_STRING ":%s 276 %s %s :has client certificate fingerprint %s"
-
 MOD_INIT()
 {
-ModDataInfo mreq;
+	ModDataInfo mreq;
 
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	
@@ -43,7 +41,7 @@ ModDataInfo mreq;
 	mreq.free = certfp_free;
 	mreq.serialize = certfp_serialize;
 	mreq.unserialize = certfp_unserialize;
-	mreq.sync = 1;
+	mreq.sync = MODDATA_SYNC_EARLY;
 	mreq.type = MODDATATYPE_CLIENT;
 	certfp_md = ModDataAdd(modinfo->handle, mreq);
 	if (!certfp_md)
@@ -121,7 +119,7 @@ int certfp_connect(Client *client)
 {
 	if (IsSecure(client))
 	{
-		char *fp = moddata_client_get(client, "certfp");
+		const char *fp = moddata_client_get(client, "certfp");
 	
 		if (fp && !iConf.no_connect_tls_info)
 			sendnotice(client, "*** Your TLS certificate fingerprint is %s", fp);
@@ -130,12 +128,17 @@ int certfp_connect(Client *client)
 	return 0;
 }
 
-int certfp_whois(Client *client, Client *target)
+int certfp_whois(Client *client, Client *target, NameValuePrioList **list)
 {
-	char *fp = moddata_client_get(target, "certfp");
-	
-	if (fp)
-		sendnumeric(client, RPL_WHOISCERTFP, target->name, fp);
+	const char *fp = moddata_client_get(target, "certfp");
+	char buf[512];
+
+	if (!fp)
+		return 0;
+
+	if (whois_get_policy(client, target, "certfp") == WHOIS_CONFIG_DETAILS_FULL)
+		add_nvplist_numeric(list, 0, "certfp", client, RPL_WHOISCERTFP, target->name, fp);
+
 	return 0;
 }
 
@@ -144,14 +147,14 @@ void certfp_free(ModData *m)
 	safe_free(m->str);
 }
 
-char *certfp_serialize(ModData *m)
+const char *certfp_serialize(ModData *m)
 {
 	if (!m->str)
 		return NULL;
 	return m->str;
 }
 
-void certfp_unserialize(char *str, ModData *m)
+void certfp_unserialize(const char *str, ModData *m)
 {
 	safe_strdup(m->str, str);
 }
diff --git a/src/modules/chanmodes/Makefile.in b/src/modules/chanmodes/Makefile.in
@@ -25,14 +25,17 @@ INCLUDES = ../../include/channel.h \
 	../../include/ircsprintf.h \
 	../../include/license.h \
 	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
-	../../include/numeric.h ../../include/proto.h ../../include/dns.h \
+	../../include/numeric.h ../../include/dns.h \
 	../../include/resource.h ../../include/setup.h \
 	../../include/struct.h ../../include/sys.h \
-	../../include/types.h ../../include/url.h \
+	../../include/types.h \
 	../../include/version.h ../../include/whowas.h
 
 R_MODULES= \
-	nocolor.so stripcolor.so issecure.so permanent.so floodprot.so \
+	chanowner.so chanadmin.so chanop.so halfop.so voice.so \
+	key.so limit.so inviteonly.so secret.so private.so \
+	moderated.so noexternalmsgs.so topiclimit.so \
+	nocolor.so stripcolor.so isregistered.so issecure.so permanent.so floodprot.so \
 	noctcp.so link.so censor.so delayjoin.so noknock.so noinvite.so operonly.so \
 	nonotice.so regonly.so nonickchange.so nokick.so regonlyspeak.so \
 	secureonly.so history.so
@@ -41,6 +44,9 @@ MODULES=$(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
 RM=@RM@
 
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
 all: build
 
 build: $(MODULES)
@@ -48,82 +54,6 @@ build: $(MODULES)
 clean:
 	$(RM) -f *.o *.so *~ core
 
-#############################################################################
-#             .so's section
-#############################################################################
-
-issecure.so: issecure.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o issecure.so issecure.c
-
-nocolor.so: nocolor.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nocolor.so nocolor.c
-
-stripcolor.so: stripcolor.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o stripcolor.so stripcolor.c
-
-permanent.so: permanent.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o permanent.so permanent.c
-
-floodprot.so: floodprot.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o floodprot.so floodprot.c
-
-noctcp.so: noctcp.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o noctcp.so noctcp.c
-
-link.so: link.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o link.so link.c
-
-censor.so: censor.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o censor.so censor.c
-
-delayjoin.so: delayjoin.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o delayjoin.so delayjoin.c
-
-noknock.so: noknock.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o noknock.so noknock.c
-		
-noinvite.so: noinvite.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o noinvite.so noinvite.c
-		
-operonly.so: operonly.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o operonly.so operonly.c
-		
-nonotice.so: nonotice.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nonotice.so nonotice.c
-		
-regonly.so: regonly.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o regonly.so regonly.c
-		
-nonickchange.so: nonickchange.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nonickchange.so nonickchange.c
-		
-nokick.so: nokick.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nokick.so nokick.c
-		
-regonlyspeak.so: regonlyspeak.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o regonlyspeak.so regonlyspeak.c
-		
-secureonly.so: secureonly.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o secureonly.so secureonly.c
-
-history.so: history.c $(INCLUDES)
+%.so: %.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o history.so history.c
+		-o $@ $<
diff --git a/src/modules/chanmodes/censor.c b/src/modules/chanmodes/censor.c
@@ -12,18 +12,18 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +G",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 
 Cmode_t EXTMODE_CENSOR = 0L;
 
-#define IsCensored(x) ((x)->mode.extmode & EXTMODE_CENSOR)
+#define IsCensored(x) ((x)->mode.mode & EXTMODE_CENSOR)
 
-int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-char *censor_pre_local_part(Client *client, Channel *channel, char *text);
-char *censor_pre_local_quit(Client *client, char *text);
-int censor_stats_badwords_channel(Client *client, char *para);
+int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+const char *censor_pre_local_part(Client *client, Channel *channel, const char *text);
+const char *censor_pre_local_quit(Client *client, const char *text);
+int censor_stats_badwords_channel(Client *client, const char *para);
 int censor_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int censor_config_run(ConfigFile *, ConfigEntry *, int);
 
@@ -49,12 +49,12 @@ MOD_INIT()
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
 	req.is_ok = extcmode_default_requirechop;
-	req.flag = 'G';
+	req.letter = 'G';
 	CmodeAdd(modinfo->handle, req, &EXTMODE_CENSOR);
 
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, censor_can_send_to_channel);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, censor_pre_local_part);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, censor_pre_local_quit);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, censor_pre_local_part);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, censor_pre_local_quit);
 	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, censor_stats_badwords_channel);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, censor_config_run);
 	return MOD_SUCCESS;
@@ -88,99 +88,99 @@ int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_MAIN)
 		return 0;
 	
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "badword"))
+	if (!ce || !ce->name || strcmp(ce->name, "badword"))
 		return 0; /* not interested */
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: badword without type",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	else if (strcmp(ce->ce_vardata, "channel") && 
-	         strcmp(ce->ce_vardata, "quit") && strcmp(ce->ce_vardata, "all")) {
+	else if (strcmp(ce->value, "channel") && 
+	         strcmp(ce->value, "quit") && strcmp(ce->value, "all")) {
 /*			config_error("%s:%i: badword with unknown type",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum); -- can't do that.. */
+				ce->file->filename, ce->line_number); -- can't do that.. */
 		return 0; /* unhandled */
 	}
 	
-	if (!strcmp(ce->ce_vardata, "quit"))
+	if (!strcmp(ce->value, "quit"))
 	{
 		config_error("%s:%i: badword quit has been removed. We just use the bad words from "
 		             "badword channel { } instead.",
-		             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		             ce->file->filename, ce->line_number);
 		return 0; /* pretend unhandled.. ok not just pretend.. ;) */
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "badword"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "word"))
+		if (!strcmp(cep->name, "word"))
 		{
-			char *errbuf;
+			const char *errbuf;
 			if (has_word)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "badword::word");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::word");
 				continue;
 			}
 			has_word = 1;
-			if ((errbuf = badword_config_check_regex(cep->ce_vardata,1,1)))
+			if ((errbuf = badword_config_check_regex(cep->value,1,1)))
 			{
 				config_error("%s:%i: badword::%s contains an invalid regex: %s",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum,
-					cep->ce_varname, errbuf);
+					cep->file->filename,
+					cep->line_number,
+					cep->name, errbuf);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "replace"))
+		else if (!strcmp(cep->name, "replace"))
 		{
 			if (has_replace)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "badword::replace");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::replace");
 				continue;
 			}
 			has_replace = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
 			if (has_action)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "badword::action");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::action");
 				continue;
 			}
 			has_action = 1;
-			if (!strcmp(cep->ce_vardata, "replace"))
+			if (!strcmp(cep->value, "replace"))
 				action = 'r';
-			else if (!strcmp(cep->ce_vardata, "block"))
+			else if (!strcmp(cep->value, "block"))
 				action = 'b';
 			else
 			{
 				config_error("%s:%d: Unknown badword::action '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata);
+					cep->file->filename, cep->line_number,
+					cep->value);
 				errors++;
 			}
 				
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"badword", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"badword", cep->name);
 			errors++;
 		}
 	}
 
 	if (!has_word)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"badword::word");
 		errors++;
 	}
@@ -189,7 +189,7 @@ int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		if (has_replace && action == 'b')
 		{
 			config_error("%s:%i: badword::action is block but badword::replace exists",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				ce->file->filename, ce->line_number);
 			errors++;
 		}
 	}
@@ -207,39 +207,39 @@ int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_MAIN)
 		return 0;
 	
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "badword"))
+	if (!ce || !ce->name || strcmp(ce->name, "badword"))
 		return 0; /* not interested */
 
-	if (strcmp(ce->ce_vardata, "channel") && strcmp(ce->ce_vardata, "all"))
+	if (strcmp(ce->value, "channel") && strcmp(ce->value, "all"))
 	        return 0; /* not for us */
 
 	ca = safe_alloc(sizeof(ConfigItem_badword));
 	ca->action = BADWORD_REPLACE;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "action"))
+		if (!strcmp(cep->name, "action"))
 		{
-			if (!strcmp(cep->ce_vardata, "block"))
+			if (!strcmp(cep->value, "block"))
 			{
 				ca->action = BADWORD_BLOCK;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "replace"))
+		else if (!strcmp(cep->name, "replace"))
 		{
-			safe_strdup(ca->replace, cep->ce_vardata);
+			safe_strdup(ca->replace, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "word"))
+		if (!strcmp(cep->name, "word"))
 		{
 			word = cep;
 		}
 	}
 
-	badword_config_process(ca, word->ce_vardata);
+	badword_config_process(ca, word->value);
 
-	if (!strcmp(ce->ce_vardata, "channel"))
+	if (!strcmp(ce->value, "channel"))
 		AddListItem(ca, conf_badword_channel);
-	else if (!strcmp(ce->ce_vardata, "all"))
+	else if (!strcmp(ce->value, "all"))
 	{
 		AddListItem(ca, conf_badword_channel);
 		return 0; /* pretend we didn't see it, so other modules can handle 'all' as well */
@@ -248,12 +248,12 @@ int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	return 1;
 }
 
-char *stripbadwords_channel(char *str, int *blocked)
+const char *stripbadwords_channel(const char *str, int *blocked)
 {
 	return stripbadwords(str, conf_badword_channel, blocked);
 }
 
-int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	int blocked;
 	Hook *h;
@@ -281,7 +281,7 @@ int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp,
 	return HOOK_CONTINUE;
 }
 
-char *censor_pre_local_part(Client *client, Channel *channel, char *text)
+const char *censor_pre_local_part(Client *client, Channel *channel, const char *text)
 {
 	int blocked;
 
@@ -306,7 +306,7 @@ static int IsAnyChannelCensored(Client *client)
 	return 0;
 }
 
-char *censor_pre_local_quit(Client *client, char *text)
+const char *censor_pre_local_quit(Client *client, const char *text)
 {
 	int blocked = 0;
 
@@ -319,7 +319,7 @@ char *censor_pre_local_quit(Client *client, char *text)
 	return blocked ? NULL : text;
 }
 
-int censor_stats_badwords_channel(Client *client, char *para)
+int censor_stats_badwords_channel(Client *client, const char *para)
 {
 	ConfigItem_badword *words;
 
diff --git a/src/modules/chanmodes/chanadmin.c b/src/modules/chanmodes/chanadmin.c
@@ -0,0 +1,88 @@
+/*
+ * Channel Mode +a
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/chanadmin",
+	"6.0",
+	"Channel Mode +a",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+int cmode_chanadmin_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_chanadmin_is_ok;
+	creq.letter = 'a';
+	creq.prefix = '&';
+	creq.sjoin_prefix = '~'; /* yeah i know, totally not confusing! */
+	creq.rank = RANK_CHANADMIN;
+	creq.unset_with_param = 1;
+	creq.type = CMODE_MEMBER;
+	CmodeAdd(modinfo->handle, creq, NULL);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int cmode_chanadmin_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		Client *target = find_user(param, NULL);
+
+		if ((what == MODE_DEL) && (client == target))
+		{
+			/* User may always remove their own modes */
+			return EX_ALLOW;
+		}
+		if (check_channel_access(client, channel, "q"))
+		{
+			/* only +q people may do +a/-a */
+			return EX_ALLOW;
+		}
+		if (type == EXCHK_ACCESS_ERR)
+			sendnumeric(client, ERR_CHANOWNPRIVNEEDED, channel->name);
+		return EX_DENY;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
diff --git a/src/modules/chanmodes/chanop.c b/src/modules/chanmodes/chanop.c
@@ -0,0 +1,78 @@
+/*
+ * Channel Mode +o
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/chanop",
+	"6.0",
+	"Channel Mode +o",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+int cmode_chanop_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_chanop_is_ok;
+	creq.letter = 'o';
+	creq.prefix = '@';
+	creq.sjoin_prefix = '@';
+	creq.rank = RANK_CHANOP;
+	creq.unset_with_param = 1;
+	creq.type = CMODE_MEMBER;
+	CmodeAdd(modinfo->handle, creq, NULL);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int cmode_chanop_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		if (check_channel_access(client, channel, "oaq"))
+			return EX_ALLOW;
+		if (type == EXCHK_ACCESS_ERR)
+			sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return EX_DENY;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
diff --git a/src/modules/chanmodes/chanowner.c b/src/modules/chanmodes/chanowner.c
@@ -0,0 +1,88 @@
+/*
+ * Channel Mode +q
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/chanowner",
+	"6.0",
+	"Channel Mode +q",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+int cmode_chanowner_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_chanowner_is_ok;
+	creq.letter = 'q';
+	creq.prefix = '~';
+	creq.sjoin_prefix = '*';
+	creq.rank = RANK_CHANOWNER;
+	creq.unset_with_param = 1;
+	creq.type = CMODE_MEMBER;
+	CmodeAdd(modinfo->handle, creq, NULL);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int cmode_chanowner_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		Client *target = find_user(param, NULL);
+
+		if ((what == MODE_DEL) && (client == target))
+		{
+			/* User may always remove their own modes */
+			return EX_ALLOW;
+		}
+		if (check_channel_access(client, channel, "q"))
+		{
+			/* only +q people may do +q/-q */
+			return EX_ALLOW;
+		}
+		if (type == EXCHK_ACCESS_ERR)
+			sendnumeric(client, ERR_CHANOWNPRIVNEEDED, channel->name);
+		return EX_DENY;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
diff --git a/src/modules/chanmodes/delayjoin.c b/src/modules/chanmodes/delayjoin.c
@@ -11,7 +11,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"delayed join (+D,+d)", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 #define MOD_DATA_STR "delayjoin"
@@ -26,13 +26,14 @@ static Cmode_t EXTMODE_POST_DELAYED;
 int visible_in_channel(Client *client, Channel *channel);
 int moded_check_part(Client *client, Channel *channel);
 int moded_join(Client *client, Channel *channel);
-int moded_part(Client *client, Channel *channel, MessageTag *mtags, char *comment);
-int deny_all(Client *client, Channel *channel, char mode, char *para, int checkt, int what);
+int moded_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
+int moded_quit(Client *client, MessageTag *mtags, const char *comment);
+int delayjoin_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what);
 int moded_chanmode(Client *client, Channel *channel,
-                   MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
-int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype);
-char *moded_serialize(ModData *m);
-void moded_unserialize(char *str, ModData *m);
+                   MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode);
+int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, const char *text, SendType sendtype);
+const char *moded_serialize(ModData *m);
+void moded_unserialize(const char *str, ModData *m);
 
 MOD_INIT()
 {
@@ -45,13 +46,14 @@ MOD_INIT()
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
 	req.is_ok = extcmode_default_requirechop;
-	req.flag = 'D';
+	req.letter = 'D';
 	CmodeDelayed = CmodeAdd(modinfo->handle, req, &EXTMODE_DELAYED);
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.is_ok = deny_all;
-	req.flag = 'd';
+	req.is_ok = delayjoin_is_ok;
+	req.letter = 'd';
+	req.local = 1;
 	CmodePostDelayed = CmodeAdd(modinfo->handle, req, &EXTMODE_POST_DELAYED);
 
 	memset(&mreq, 0, sizeof(mreq));
@@ -76,6 +78,8 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_JOIN_DATA, 0, moded_join);
 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PART, 0, moded_part);
 	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_PART, 0, moded_part);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, moded_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_QUIT, 0, moded_quit);
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CHANMODE, 0, moded_chanmode);
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_REMOTE_CHANMODE, 0, moded_chanmode);
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_CHANMSG, 0, moded_prechanmsg);
@@ -97,10 +101,10 @@ void set_post_delayed(Channel *channel)
 {
 	MessageTag *mtags = NULL;
 
-	channel->mode.extmode |= EXTMODE_POST_DELAYED;
+	channel->mode.mode |= EXTMODE_POST_DELAYED;
 
 	new_message(&me, NULL, &mtags);
-	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +d", me.name, channel->chname);
+	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +d", me.name, channel->name);
 	free_message_tags(mtags);
 }
 
@@ -108,10 +112,10 @@ void clear_post_delayed(Channel *channel)
 {
 	MessageTag *mtags = NULL;
 
-	channel->mode.extmode &= ~EXTMODE_POST_DELAYED;
+	channel->mode.mode &= ~EXTMODE_POST_DELAYED;
 
 	new_message(&me, NULL, &mtags);
-	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s -d", me.name, channel->chname);
+	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s -d", me.name, channel->name);
 	free_message_tags(mtags);
 }
 
@@ -153,14 +157,14 @@ bool channel_has_invisible_users(Channel *channel)
 
 bool channel_is_post_delayed(Channel *channel)
 {
-	if (channel->mode.extmode & EXTMODE_POST_DELAYED)
+	if (channel->mode.mode & EXTMODE_POST_DELAYED)
 		return true;
 	return false;
 }
 
 bool channel_is_delayed(Channel *channel)
 {
-	if (channel->mode.extmode & EXTMODE_DELAYED)
+	if (channel->mode.mode & EXTMODE_DELAYED)
 		return true;
 	return false;
 }
@@ -196,7 +200,7 @@ void clear_user_invisible(Channel *channel, Client *client)
 		}
 	}
 
-	if (should_clear && (channel->mode.extmode & EXTMODE_POST_DELAYED))
+	if (should_clear && (channel->mode.mode & EXTMODE_POST_DELAYED))
 	{
 		clear_post_delayed(channel);
 	}
@@ -213,18 +217,18 @@ void clear_user_invisible_announce(Channel *channel, Client *client, MessageTag 
 	clear_user_invisible(channel, client);
 
 	ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s",
-				client->name, client->user->username, GetHost(client), channel->chname);
+				client->name, client->user->username, GetHost(client), channel->name);
 
 	ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s",
-		client->name, client->user->username, GetHost(client), channel->chname,
-		!isdigit(*client->user->svid) ? client->user->svid : "*",
+		client->name, client->user->username, GetHost(client), channel->name,
+		IsLoggedIn(client) ? client->user->account : "*",
 		client->info);
 
-	new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->chname);
+	new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->name);
 	for (i = channel->members; i; i = i->next)
 	{
 		Client *acptr = i->client;
-		if (!is_skochanop(acptr, channel) && acptr != client && MyConnect(acptr))
+		if (!check_channel_access(acptr, channel, "hoaq") && acptr != client && MyConnect(acptr))
 		{
 			if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN))
 				sendto_one(acptr, mtags, "%s", exjoinbuf);
@@ -252,7 +256,7 @@ void set_user_invisible(Channel *channel, Client *client)
 }
 
 
-int deny_all(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+int delayjoin_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
 	return EX_ALWAYS_DENY;
 }
@@ -272,7 +276,7 @@ int moded_join(Client *client, Channel *channel)
 	return 0;
 }
 
-int moded_part(Client *client, Channel *channel, MessageTag *mtags, char *comment)
+int moded_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment)
 {
 	if (channel_is_delayed(channel) || channel_is_post_delayed(channel))
 		clear_user_invisible(channel, client);
@@ -280,7 +284,23 @@ int moded_part(Client *client, Channel *channel, MessageTag *mtags, char *commen
 	return 0;
 }
 
-int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, char *modebuf, char *parabuf, time_t sendts, int samode)
+int moded_quit(Client *client, MessageTag *mtags, const char *comment)
+{
+	Membership *membership;
+	Channel *channel;
+
+	for (membership = client->user->channel; membership; membership=membership->next)
+	{
+		channel = membership->channel;
+		/* Identical to moded_part() */
+		if (channel_is_delayed(channel) || channel_is_post_delayed(channel))
+			clear_user_invisible(channel, client);
+	}
+
+	return 0;
+}
+
+int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode)
 {
 	long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
 
@@ -317,16 +337,16 @@ int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, cha
 					if (moded_user_invisible(i->client, channel))
 					{
 						MessageTag *mtags = NULL;
-						new_message_special(i->client, recv_mtags, &mtags, ":%s JOIN %s", i->client->name, channel->chname);
+						new_message_special(i->client, recv_mtags, &mtags, ":%s JOIN %s", i->client->name, channel->name);
 						if (HasCapabilityFast(user, CAP_EXTENDED_JOIN))
 						{
 							sendto_one(user, mtags, ":%s!%s@%s JOIN %s %s :%s",
 							           i->client->name, i->client->user->username, GetHost(i->client),
-							           channel->chname,
-							           !isdigit(*i->client->user->svid) ? i->client->user->svid : "*",
+							           channel->name,
+							           IsLoggedIn(i->client) ? i->client->user->account : "*",
 							           i->client->info);
 						} else {
-							sendto_one(user, mtags, ":%s!%s@%s JOIN :%s", i->client->name, i->client->user->username, GetHost(i->client), channel->chname);
+							sendto_one(user, mtags, ":%s!%s@%s JOIN :%s", i->client->name, i->client->user->username, GetHost(i->client), channel->name);
 						}
 						free_message_tags(mtags);
 					}
@@ -354,8 +374,8 @@ int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, cha
 					if (moded_user_invisible(i->client, channel))
 					{
 						MessageTag *mtags = NULL;
-						new_message_special(i->client, recv_mtags, &mtags, ":%s PART %s", i->client->name, channel->chname);
-						sendto_one(user, mtags, ":%s!%s@%s PART :%s", i->client->name, i->client->user->username, GetHost(i->client), channel->chname);
+						new_message_special(i->client, recv_mtags, &mtags, ":%s PART %s", i->client->name, channel->name);
+						sendto_one(user, mtags, ":%s!%s@%s PART :%s", i->client->name, i->client->user->username, GetHost(i->client), channel->name);
 						free_message_tags(mtags);
 					}
 				}
@@ -367,7 +387,7 @@ int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, cha
 	return 0;
 }
 
-int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype)
+int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, const char *text, SendType sendtype)
 {
 	if ((channel_is_delayed(channel) || channel_is_post_delayed(channel)) && (moded_user_invisible(client, channel)))
 		clear_user_invisible_announce(channel, client, mtags);
@@ -375,12 +395,12 @@ int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *
 	return 0;
 }
 
-char *moded_serialize(ModData *m)
+const char *moded_serialize(ModData *m)
 {
 	return m->i ? "1" : "0";
 }
 
-void moded_unserialize(char *str, ModData *m)
+void moded_unserialize(const char *str, ModData *m)
 {
 	m->i = atoi(str);
 }
diff --git a/src/modules/chanmodes/floodprot.c b/src/modules/chanmodes/floodprot.c
@@ -25,17 +25,18 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Channel Mode +f",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
-#define CHFLD_CTCP	0 /* c */
-#define CHFLD_JOIN	1 /* j */
-#define CHFLD_KNOCK	2 /* k */
-#define CHFLD_MSG		3 /* m */
-#define CHFLD_NICK	4 /* n */
-#define CHFLD_TEXT	5 /* t */
-#define CHFLD_REPEAT	6 /* r */
-
+typedef enum Flood {
+	CHFLD_CTCP	= 0,
+	CHFLD_JOIN	= 1,
+	CHFLD_KNOCK	= 2,
+	CHFLD_MSG	= 3,
+	CHFLD_NICK	= 4,
+	CHFLD_TEXT	= 5,
+	CHFLD_REPEAT	= 6,
+} Flood;
 #define NUMFLD	7 /* 7 flood types */
 
 /** Configuration settings */
@@ -47,7 +48,7 @@ struct {
 
 typedef struct FloodType {
 	char letter;
-	int index;
+	Flood index;
 	char *description;
 	char default_action;
 	char *actions;
@@ -61,7 +62,7 @@ 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, },
+	{ '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, },
@@ -113,7 +114,7 @@ static int timedban_available = 0; /**< Set to 1 if extbans/timedban module is l
 RemoveChannelModeTimer *removechannelmodetimer_list = NULL;
 char *floodprot_msghash_key = NULL;
 
-#define IsFloodLimit(x)	((x)->mode.extmode & EXTMODE_FLOODLIMIT)
+#define IsFloodLimit(x)	((x)->mode.mode & EXTMODE_FLOODLIMIT)
 
 /* Forward declarations */
 static void init_config(void);
@@ -128,24 +129,24 @@ 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);
-uint64_t gen_floodprot_msghash(char *text);
-int cmodef_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what);
-void *cmodef_put_param(void *r_in, char *param);
-char *cmodef_get_param(void *r_in);
-char *cmodef_conv_param(char *param_in, Client *client, Channel *channel);
+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);
 void *cmodef_dup_struct(void *r_in);
 int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx);
-int floodprot_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+int floodprot_join(Client *client, Channel *channel, MessageTag *mtags);
 EVENT(modef_event);
 int cmodef_channel_destroy(Channel *channel, int *should_destroy);
-int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
-int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment);
-int floodprot_nickchange(Client *client, MessageTag *mtags, char *oldnick);
+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);
+int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
+int floodprot_nickchange(Client *client, MessageTag *mtags, const char *oldnick);
 int floodprot_chanmode_del(Channel *channel, int m);
 void memberflood_free(ModData *md);
-int floodprot_stats(Client *client, char *flag);
+int floodprot_stats(Client *client, const char *flag);
 void floodprot_free_removechannelmodetimer_list(ModData *m);
 void floodprot_free_msghash_key(ModData *m);
 
@@ -165,7 +166,7 @@ MOD_INIT()
 	memset(&creq, 0, sizeof(creq));
 	creq.paracount = 1;
 	creq.is_ok = cmodef_is_ok;
-	creq.flag = 'f';
+	creq.letter = 'f';
 	creq.unset_with_param = 1; /* ah yeah, +f is special! */
 	creq.put_param = cmodef_put_param;
 	creq.get_param = cmodef_get_param;
@@ -244,53 +245,53 @@ int floodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!strcmp(ce->ce_varname, "modef-default-unsettime"))
+	if (!strcmp(ce->name, "modef-default-unsettime"))
 	{
-		if (!ce->ce_vardata)
+		if (!ce->value)
 		{
-			config_error_empty(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				"set", ce->ce_varname);
+			config_error_empty(ce->file->filename, ce->line_number,
+				"set", ce->name);
 			errors++;
 		} else {
-			int v = atoi(ce->ce_vardata);
+			int v = atoi(ce->value);
 			if ((v <= 0) || (v > 255))
 			{
 				config_error("%s:%i: set::modef-default-unsettime: value '%d' out of range (should be 1-255)",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum, v);
+					ce->file->filename, ce->line_number, v);
 				errors++;
 			}
 		}
 	} else
-	if (!strcmp(ce->ce_varname, "modef-max-unsettime"))
+	if (!strcmp(ce->name, "modef-max-unsettime"))
 	{
-		if (!ce->ce_vardata)
+		if (!ce->value)
 		{
-			config_error_empty(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				"set", ce->ce_varname);
+			config_error_empty(ce->file->filename, ce->line_number,
+				"set", ce->name);
 			errors++;
 		} else {
-			int v = atoi(ce->ce_vardata);
+			int v = atoi(ce->value);
 			if ((v <= 0) || (v > 255))
 			{
 				config_error("%s:%i: set::modef-max-unsettime: value '%d' out of range (should be 1-255)",
-					ce->ce_fileptr->cf_filename, ce->ce_varlinenum, v);
+					ce->file->filename, ce->line_number, v);
 				errors++;
 			}
 		}
 	} else
-	if (!strcmp(ce->ce_varname, "modef-boot-delay"))
+	if (!strcmp(ce->name, "modef-boot-delay"))
 	{
-		if (!ce->ce_vardata)
+		if (!ce->value)
 		{
-			config_error_empty(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				"set", ce->ce_varname);
+			config_error_empty(ce->file->filename, ce->line_number,
+				"set", ce->name);
 			errors++;
 		} else {
-			long v = config_checkval(ce->ce_vardata, CFG_TIME);
+			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->ce_fileptr->cf_filename, ce->ce_varlinenum, v);
+					ce->file->filename, ce->line_number, v);
 				errors++;
 			}
 		}
@@ -309,12 +310,12 @@ int floodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!strcmp(ce->ce_varname, "modef-default-unsettime"))
-		cfg.modef_default_unsettime = (unsigned char)atoi(ce->ce_vardata);
-	else if (!strcmp(ce->ce_varname, "modef-max-unsettime"))
-		cfg.modef_max_unsettime = (unsigned char)atoi(ce->ce_vardata);
-	else if (!strcmp(ce->ce_varname, "modef-boot-delay"))
-		cfg.modef_boot_delay = config_checkval(ce->ce_vardata, CFG_TIME);
+	if (!strcmp(ce->name, "modef-default-unsettime"))
+		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 */
 
@@ -331,7 +332,7 @@ FloodType *find_floodprot_by_letter(char c)
 	return NULL;
 }
 
-FloodType *find_floodprot_by_index(int index)
+FloodType *find_floodprot_by_index(Flood index)
 {
 	int i;
 	for (i=0; i < ARRAY_SIZEOF(floodtypes); i++)
@@ -341,11 +342,11 @@ FloodType *find_floodprot_by_index(int index)
 	return NULL;
 }
 
-int cmodef_is_ok(Client *client, Channel *channel, char mode, char *param, int type, int what)
+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) && is_chan_op(client, channel))
+		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');
@@ -359,7 +360,7 @@ int cmodef_is_ok(Client *client, Channel *channel, char mode, char *param, int t
 		unsigned short warnings = 0, breakit;
 		unsigned char r;
 		FloodType *floodtype;
-		int index;
+		Flood index;
 
 		memset(&newf, 0, sizeof(newf));
 
@@ -467,7 +468,7 @@ invalidsyntax:
 	return EX_DENY;
 }
 
-void *cmodef_put_param(void *fld_in, char *param)
+void *cmodef_put_param(void *fld_in, const char *param)
 {
 	ChannelFloodProtection *fld = (ChannelFloodProtection *)fld_in;
 	char xbuf[256], c, a, *p, *p2, *x = xbuf+1;
@@ -475,7 +476,7 @@ void *cmodef_put_param(void *fld_in, char *param)
 	unsigned short breakit;
 	unsigned char r;
 	FloodType *floodtype;
-	int index;
+	Flood index;
 
 	strlcpy(xbuf, param, sizeof(xbuf));
 
@@ -581,7 +582,7 @@ fail_cmodef_put_param:
 	return fld; /* FAIL */
 }
 
-char *cmodef_get_param(void *r_in)
+const char *cmodef_get_param(void *r_in)
 {
 	ChannelFloodProtection *r = (ChannelFloodProtection *)r_in;
 	static char retbuf[512];
@@ -596,7 +597,7 @@ char *cmodef_get_param(void *r_in)
 /** Convert parameter to something proper.
  * NOTE: client may be NULL if called for e.g. set::modes-on-join
  */
-char *cmodef_conv_param(char *param_in, Client *client, Channel *channel)
+const char *cmodef_conv_param(const char *param_in, Client *client, Channel *channel)
 {
 	static char retbuf[256];
 	char param[256];
@@ -607,7 +608,7 @@ char *cmodef_conv_param(char *param_in, Client *client, Channel *channel)
 	unsigned short breakit;
 	unsigned char r;
 	FloodType *floodtype;
-	int index;
+	Flood index;
 
 	memset(&newf, 0, sizeof(newf));
 
@@ -736,7 +737,7 @@ int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx)
 	return EXSJ_MERGE;
 }
 
-int floodprot_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[])
+int floodprot_join(Client *client, Channel *channel, MessageTag *mtags)
 {
 	/* I'll explain this only once:
 	 * 1. if channel is +f
@@ -749,8 +750,8 @@ int floodprot_join(Client *client, Channel *channel, MessageTag *mtags, char *pa
 	 * from all servers.
 	 */
 	if (IsFloodLimit(channel) &&
-	    (MyUser(client) || client->srvptr->serv->flags.synced) &&
-	    (client->srvptr->serv->boottime && (TStime() - client->srvptr->serv->boottime >= MODEF_BOOT_DELAY)) &&
+	    (MyUser(client) || client->uplink->server->flags.synced) &&
+	    (client->uplink->server->boottime && (TStime() - client->uplink->server->boottime >= MODEF_BOOT_DELAY)) &&
 	    !IsULine(client))
 	{
 	    do_floodprot(channel, client, CHFLD_JOIN);
@@ -818,7 +819,7 @@ char *channel_modef_string(ChannelFloodProtection *x, char *retbuf)
 	return retbuf;
 }
 
-int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Membership *mb;
 	ChannelFloodProtection *chp;
@@ -834,7 +835,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 	if (sendtype == SEND_TYPE_TAGMSG)
 		return 0; // TODO: some TAGMSG specific limit? (1 of 2)
 
-	if (ValidatePermissionsForPath("channel:override:flood",client,NULL,channel,NULL) || !IsFloodLimit(channel) || is_skochanop(client, channel))
+	if (ValidatePermissionsForPath("channel:override:flood",client,NULL,channel,NULL) || !IsFloodLimit(channel) || check_channel_access(client, channel, "hoaq"))
 		return HOOK_CONTINUE;
 
 	if (!(mb = find_membership_link(client->user->channel, channel)))
@@ -921,16 +922,21 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 		{
 			/* Ban the user */
 			if (timedban_available && (chp->remove_after[flood_type] > 0))
-				snprintf(mask, sizeof(mask), "~t:%d:*!*@%s", chp->remove_after[flood_type], GetHost(client));
-			else
+			{
+				if (iConf.named_extended_bans)
+					snprintf(mask, sizeof(mask), "~time:%d:*!*@%s", chp->remove_after[flood_type], GetHost(client));
+				else
+					snprintf(mask, sizeof(mask), "~t:%d:*!*@%s", chp->remove_after[flood_type], GetHost(client));
+			} else {
 				snprintf(mask, sizeof(mask), "*!*@%s", GetHost(client));
+			}
 			if (add_listmode(&channel->banlist, &me, channel, mask) == 0)
 			{
 				mtags = NULL;
 				new_message(&me, NULL, &mtags);
-				sendto_server(NULL, 0, 0, mtags, ":%s MODE %s +b %s 0", me.id, channel->chname, mask);
+				sendto_server(NULL, 0, 0, mtags, ":%s MODE %s +b %s 0", me.id, channel->name, mask);
 				sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
-				    ":%s MODE %s +b %s", me.name, channel->chname, mask);
+				    ":%s MODE %s +b %s", me.name, channel->name, mask);
 				free_message_tags(mtags);
 			} /* else.. ban list is full */
 		}
@@ -942,9 +948,9 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 	return HOOK_CONTINUE;
 }
 
-int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, 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)
 {
-	if (!IsFloodLimit(channel) || is_skochanop(client, channel) || IsULine(client))
+	if (!IsFloodLimit(channel) || check_channel_access(client, channel, "hoaq") || IsULine(client))
 		return 0;
 
 	if (sendtype == SEND_TYPE_TAGMSG)
@@ -960,14 +966,14 @@ int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int 
 	return 0;
 }
 
-int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment)
+int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, const char *comment)
 {
 	if (IsFloodLimit(channel) && !IsULine(client))
 		do_floodprot(channel, client, CHFLD_KNOCK);
 	return 0;
 }
 
-int floodprot_nickchange(Client *client, MessageTag *mtags, char *oldnick)
+int floodprot_nickchange(Client *client, MessageTag *mtags, const char *oldnick)
 {
 	Membership *mp;
 
@@ -977,8 +983,7 @@ int floodprot_nickchange(Client *client, MessageTag *mtags, char *oldnick)
 	for (mp = client->user->channel; mp; mp = mp->next)
 	{
 		Channel *channel = mp->channel;
-		if (channel && IsFloodLimit(channel) &&
-		    !(mp->flags & (CHFL_CHANOP|CHFL_VOICE|CHFL_CHANOWNER|CHFL_HALFOP|CHFL_CHANADMIN)))
+		if (channel && IsFloodLimit(channel) && !check_channel_access_membership(mp, "vhoaq"))
 		{
 			do_floodprot(channel, client, CHFLD_NICK);
 		}
@@ -1080,8 +1085,10 @@ void floodprottimer_add(Channel *channel, char mflag, time_t when)
 	{
 		if (strlen(chp->timers_running)+1 >= sizeof(chp->timers_running))
 		{
-			sendto_realops_and_log("floodprottimer_add: too many timers running for %s (%s)!!!",
-				channel->chname, chp->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));
 			return;
 		}
 		strccat(chp->timers_running, mflag); /* bounds already checked ^^ */
@@ -1137,39 +1144,24 @@ EVENT(modef_event)
 		if (e->when <= now)
 		{
 			/* Remove chanmode... */
-			long mode = 0;
-			Cmode_t extmode = 0;
-#ifdef NEWFLDDBG
-			sendto_realops("modef_event: chan %s mode -%c EXPIRED", e->channel->chname, e->m);
-#endif
-			mode = get_mode_bitbychar(e->m);
-			if (mode == 0)
-			        extmode = get_extmode_bitbychar(e->m);
-
-			if ((mode && (e->channel->mode.mode & mode)) ||
-			    (extmode && (e->channel->mode.extmode & extmode)))
+			Cmode_t extmode = get_extmode_bitbychar(e->m);
+
+			if (extmode && (e->channel->mode.mode & extmode))
 			{
 				MessageTag *mtags = NULL;
 
 				new_message(&me, NULL, &mtags);
-				sendto_server(NULL, 0, 0, mtags, ":%s MODE %s -%c 0", me.id, e->channel->chname, e->m);
+				sendto_server(NULL, 0, 0, mtags, ":%s MODE %s -%c 0", me.id, e->channel->name, e->m);
 				sendto_channel(e->channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
 				               ":%s MODE %s -%c",
-				               me.name, e->channel->chname, e->m);
+				               me.name, e->channel->name, e->m);
 				free_message_tags(mtags);
-
-				e->channel->mode.mode &= ~mode;
-				e->channel->mode.extmode &= ~extmode;
+				e->channel->mode.mode &= ~extmode;
 			}
 
 			/* And delete... */
 			DelListItem(e, removechannelmodetimer_list);
 			safe_free(e);
-		} else {
-#ifdef NEWFLDDBG
-			sendto_realops("modef_event: chan %s mode -%c about %d seconds",
-				e->channel->chname, e->m, e->when - now);
-#endif
 		}
 	}
 }
@@ -1220,7 +1212,6 @@ int do_floodprot(Channel *channel, Client *client, int what)
 void do_floodprot_action(Channel *channel, int what)
 {
 	char m;
-	int mode = 0;
 	Cmode_t extmode = 0;
 	ChannelFloodProtection *chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
 	FloodType *floodtype = find_floodprot_by_index(what);
@@ -1240,15 +1231,11 @@ void do_floodprot_action(Channel *channel, int what)
 	if (chp->action[what] == 'd')
 		return;
 
-	mode = get_mode_bitbychar(m);
-	if (mode == 0)
-		extmode = get_extmode_bitbychar(m);
-
-	if (!mode && !extmode)
+	extmode = get_extmode_bitbychar(m);
+	if (!extmode)
 		return;
 
-	if (!(mode && (channel->mode.mode & mode)) &&
-		!(extmode && (channel->mode.extmode & extmode)))
+	if (!(extmode && (channel->mode.mode & extmode)))
 	{
 		char comment[512], target[CHANNELLEN + 8];
 		MessageTag *mtags;
@@ -1258,8 +1245,8 @@ void do_floodprot_action(Channel *channel, int what)
 		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->chname);
-		sendto_channel(channel, &me, NULL, PREFIX_HALFOP|PREFIX_OP|PREFIX_ADMIN|PREFIX_OWNER,
+		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);
@@ -1267,13 +1254,12 @@ void do_floodprot_action(Channel *channel, int what)
 		/* 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->chname, m);
-		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +%c", me.name, channel->chname, m);
+		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 |= mode;
-		channel->mode.extmode |= extmode;
+		channel->mode.mode |= extmode;
 
 		/* Add remove-chanmode timer */
 		if (chp->remove_after[what])
@@ -1286,7 +1272,7 @@ void do_floodprot_action(Channel *channel, int what)
 	}
 }
 
-uint64_t gen_floodprot_msghash(char *text)
+uint64_t gen_floodprot_msghash(const char *text)
 {
 	int i;
 	int is_ctcp, is_action;
@@ -1312,10 +1298,10 @@ uint64_t gen_floodprot_msghash(char *text)
 	if (is_ctcp || is_action)
 	{
 		// Remove the \001 chars around the message
-		if((len = strlen(plaintext)) && plaintext[len - 1] == '\001')
+		if ((len = strlen(plaintext)) && plaintext[len - 1] == '\001')
 			plaintext[len - 1] = '\0';
 		plaintext++;
-		if(is_action)
+		if (is_action)
 			plaintext += 7;
 	}
 
@@ -1345,7 +1331,7 @@ void memberflood_free(ModData *md)
 	safe_free(md->ptr);
 }
 
-int floodprot_stats(Client *client, char *flag)
+int floodprot_stats(Client *client, const char *flag)
 {
 	if (*flag != 'S')
 		return 0;
diff --git a/src/modules/chanmodes/halfop.c b/src/modules/chanmodes/halfop.c
@@ -0,0 +1,88 @@
+/*
+ * Channel Mode +h
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/halfop",
+	"6.0",
+	"Channel Mode +h",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+int cmode_halfop_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_halfop_is_ok;
+	creq.letter = 'h';
+	creq.prefix = '%';
+	creq.sjoin_prefix = '%';
+	creq.rank = RANK_HALFOP;
+	creq.unset_with_param = 1;
+	creq.type = CMODE_MEMBER;
+	CmodeAdd(modinfo->handle, creq, NULL);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int cmode_halfop_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		Client *target = find_user(param, NULL);
+
+		if ((what == MODE_DEL) && (target == client))
+		{
+			/* User may always remove their own modes */
+			return EX_ALLOW;
+		}
+		if ((what == MODE_ADD) && check_channel_access(client, channel, "hoaq"))
+		{
+			/* Permitted for +hoaq */
+			return EX_ALLOW;
+		}
+		if (type == EXCHK_ACCESS_ERR)
+			sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return EX_DENY;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
diff --git a/src/modules/chanmodes/history.c b/src/modules/chanmodes/history.c
@@ -11,7 +11,7 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"Channel Mode +H",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 typedef struct ConfigHistoryExt ConfigHistoryExt;
@@ -37,25 +37,25 @@ Cmode_t EXTMODE_HISTORY = 0L;
 static cfgstruct cfg;
 static cfgstruct test;
 
-#define HistoryEnabled(channel)    (channel->mode.extmode & EXTMODE_HISTORY)
+#define HistoryEnabled(channel)    (channel->mode.mode & EXTMODE_HISTORY)
 
 /* Forward declarations */
 static void init_config(cfgstruct *cfg);
 int history_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int history_config_posttest(int *);
 int history_config_run(ConfigFile *, ConfigEntry *, int);
-int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel);
 static int compare_history_modes(HistoryChanMode *a, HistoryChanMode *b);
-int history_chanmode_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what);
-void *history_chanmode_put_param(void *r_in, char *param);
-char *history_chanmode_get_param(void *r_in);
-char *history_chanmode_conv_param(char *param, Client *client, Channel *channel);
+int history_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+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);
 void *history_chanmode_dup_struct(void *r_in);
 int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int history_channel_destroy(Channel *channel, int *should_destroy);
-int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
-int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+int history_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype);
+int history_join(Client *client, Channel *channel, MessageTag *mtags);
 CMD_OVERRIDE_FUNC(override_mode);
 
 MOD_TEST()
@@ -77,7 +77,7 @@ MOD_INIT()
 	memset(&creq, 0, sizeof(creq));
 	creq.paracount = 1;
 	creq.is_ok = history_chanmode_is_ok;
-	creq.flag = 'H';
+	creq.letter = 'H';
 	creq.put_param = history_chanmode_put_param;
 	creq.get_param = history_chanmode_get_param;
 	creq.conv_param = history_chanmode_conv_param;
@@ -99,11 +99,11 @@ MOD_INIT()
 
 MOD_LOAD()
 {
-	CommandOverrideAdd(modinfo->handle, "MODE", override_mode);
-	CommandOverrideAdd(modinfo->handle, "SVSMODE", override_mode);
-	CommandOverrideAdd(modinfo->handle, "SVS2MODE", override_mode);
-	CommandOverrideAdd(modinfo->handle, "SAMODE", override_mode);
-	CommandOverrideAdd(modinfo->handle, "SJOIN", override_mode);
+	CommandOverrideAdd(modinfo->handle, "MODE", 0, override_mode);
+	CommandOverrideAdd(modinfo->handle, "SVSMODE", 0, override_mode);
+	CommandOverrideAdd(modinfo->handle, "SVS2MODE", 0, override_mode);
+	CommandOverrideAdd(modinfo->handle, "SAMODE", 0, override_mode);
+	CommandOverrideAdd(modinfo->handle, "SJOIN", 0, override_mode);
 	return MOD_SUCCESS;
 }
 
@@ -124,8 +124,6 @@ static void init_config(cfgstruct *cfg)
 	cfg->max_storage_per_channel_registered.time = 86400*31;
 }
 
-#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
-
 int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
@@ -134,140 +132,140 @@ int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	long on_join_time=0L, maximum_storage_time_registered=0L, maximum_storage_time_unregistered=0L;
 
 	/* We only care about set::history */
-	if ((type != CONFIG_SET) || strcmp(ce->ce_varname, "history"))
+	if ((type != CONFIG_SET) || strcmp(ce->name, "history"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "channel"))
+		if (!strcmp(cep->name, "channel"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "playback-on-join"))
+				if (!strcmp(cepp->name, "playback-on-join"))
 				{
-					for (cep4 = cepp->ce_entries; cep4; cep4 = cep4->ce_next)
+					for (cep4 = cepp->items; cep4; cep4 = cep4->next)
 					{
-						if (!strcmp(cep4->ce_varname, "lines"))
+						if (!strcmp(cep4->name, "lines"))
 						{
 							int v;
 							CheckNull(cep4);
-							v = atoi(cep4->ce_vardata);
+							v = atoi(cep4->value);
 							if ((v < 0) || (v > 1000))
 							{
 								config_error("%s:%i: set::history::channel::playback-on-join::lines must be between 0 and 1000. "
 								             "Recommended values are 10-50. Got: %d.",
-								             cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum, v);
+								             cep4->file->filename, cep4->line_number, v);
 								errors++;
 								continue;
 							}
 							test.playback_on_join.lines = v;
 						} else
-						if (!strcmp(cep4->ce_varname, "time"))
+						if (!strcmp(cep4->name, "time"))
 						{
 							long v;
 							CheckNull(cep4);
-							v = config_checkval(cep4->ce_vardata, CFG_TIME);
+							v = config_checkval(cep4->value, CFG_TIME);
 							if (v < 0)
 							{
 								config_error("%s:%i: set::history::channel::playback-on-join::time must be zero or more.",
-								             cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
+								             cep4->file->filename, cep4->line_number);
 								errors++;
 								continue;
 							}
 							test.playback_on_join.time = v;
 						} else
 						{
-							config_error_unknown(cep4->ce_fileptr->cf_filename,
-								cep4->ce_varlinenum, "set::history::channel::playback-on-join", cep4->ce_varname);
+							config_error_unknown(cep4->file->filename,
+								cep4->line_number, "set::history::channel::playback-on-join", cep4->name);
 							errors++;
 						}
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "max-storage-per-channel"))
+				if (!strcmp(cepp->name, "max-storage-per-channel"))
 				{
-					for (cep4 = cepp->ce_entries; cep4; cep4 = cep4->ce_next)
+					for (cep4 = cepp->items; cep4; cep4 = cep4->next)
 					{
-						if (!strcmp(cep4->ce_varname, "registered"))
+						if (!strcmp(cep4->name, "registered"))
 						{
-							for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
+							for (cep5 = cep4->items; cep5; cep5 = cep5->next)
 							{
-								if (!strcmp(cep5->ce_varname, "lines"))
+								if (!strcmp(cep5->name, "lines"))
 								{
 									int v;
 									CheckNull(cep5);
-									v = atoi(cep5->ce_vardata);
+									v = atoi(cep5->value);
 									if (v < 1)
 									{
 										config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::lines must be a positive number.",
-											     cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+											     cep5->file->filename, cep5->line_number);
 										errors++;
 										continue;
 									}
 									test.max_storage_per_channel_registered.lines = v;
 								} else
-								if (!strcmp(cep5->ce_varname, "time"))
+								if (!strcmp(cep5->name, "time"))
 								{
 									long v;
 									CheckNull(cep5);
-									v = config_checkval(cep5->ce_vardata, CFG_TIME);
+									v = config_checkval(cep5->value, CFG_TIME);
 									if (v < 1)
 									{
 										config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::time must be a positive number.",
-											     cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+											     cep5->file->filename, cep5->line_number);
 										errors++;
 										continue;
 									}
 									test.max_storage_per_channel_registered.time = v;
 								} else
 								{
-									config_error_unknown(cep5->ce_fileptr->cf_filename,
-										cep5->ce_varlinenum, "set::history::channel::max-storage-per-channel::registered", cep5->ce_varname);
+									config_error_unknown(cep5->file->filename,
+										cep5->line_number, "set::history::channel::max-storage-per-channel::registered", cep5->name);
 									errors++;
 								}
 							}
 						} else
-						if (!strcmp(cep4->ce_varname, "unregistered"))
+						if (!strcmp(cep4->name, "unregistered"))
 						{
-							for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
+							for (cep5 = cep4->items; cep5; cep5 = cep5->next)
 							{
-								if (!strcmp(cep5->ce_varname, "lines"))
+								if (!strcmp(cep5->name, "lines"))
 								{
 									int v;
 									CheckNull(cep5);
-									v = atoi(cep5->ce_vardata);
+									v = atoi(cep5->value);
 									if (v < 1)
 									{
 										config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::lines must be a positive number.",
-											     cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+											     cep5->file->filename, cep5->line_number);
 										errors++;
 										continue;
 									}
 									test.max_storage_per_channel_unregistered.lines = v;
 								} else
-								if (!strcmp(cep5->ce_varname, "time"))
+								if (!strcmp(cep5->name, "time"))
 								{
 									long v;
 									CheckNull(cep5);
-									v = config_checkval(cep5->ce_vardata, CFG_TIME);
+									v = config_checkval(cep5->value, CFG_TIME);
 									if (v < 1)
 									{
 										config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::time must be a positive number.",
-											     cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+											     cep5->file->filename, cep5->line_number);
 										errors++;
 										continue;
 									}
 									test.max_storage_per_channel_unregistered.time = v;
 								} else
 								{
-									config_error_unknown(cep5->ce_fileptr->cf_filename,
-										cep5->ce_varlinenum, "set::history::channel::max-storage-per-channel::unregistered", cep5->ce_varname);
+									config_error_unknown(cep5->file->filename,
+										cep5->line_number, "set::history::channel::max-storage-per-channel::unregistered", cep5->name);
 									errors++;
 								}
 							}
 						} else
 						{
-							config_error_unknown(cep->ce_fileptr->cf_filename,
-								cep->ce_varlinenum, "set::history::max-storage-per-channel", cep->ce_varname);
+							config_error_unknown(cep->file->filename,
+								cep->line_number, "set::history::max-storage-per-channel", cep->name);
 							errors++;
 						}
 					}
@@ -304,15 +302,15 @@ int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 					}
 					if (!used)
 					{
-						config_error_unknown(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum, "set::history::channel", cepp->ce_varname);
+						config_error_unknown(cepp->file->filename,
+							cepp->line_number, "set::history::channel", cepp->name);
 						errors++;
 					}
 				}
 			}
 		} else {
-			config_error_unknown(cep->ce_fileptr->cf_filename,
-				cep->ce_varlinenum, "set::history", cep->ce_varname);
+			config_error_unknown(cep->file->filename,
+				cep->line_number, "set::history", cep->name);
 			errors++;
 		}
 	}
@@ -337,58 +335,58 @@ int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 {
 	ConfigEntry *cep, *cepp, *cep4, *cep5;
 
-	if ((type != CONFIG_SET) || strcmp(ce->ce_varname, "history"))
+	if ((type != CONFIG_SET) || strcmp(ce->name, "history"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "channel"))
+		if (!strcmp(cep->name, "channel"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "playback-on-join"))
+				if (!strcmp(cepp->name, "playback-on-join"))
 				{
-					for (cep4 = cepp->ce_entries; cep4; cep4 = cep4->ce_next)
+					for (cep4 = cepp->items; cep4; cep4 = cep4->next)
 					{
-						if (!strcmp(cep4->ce_varname, "lines"))
+						if (!strcmp(cep4->name, "lines"))
 						{
-							cfg.playback_on_join.lines = atoi(cep4->ce_vardata);
+							cfg.playback_on_join.lines = atoi(cep4->value);
 						} else
-						if (!strcmp(cep4->ce_varname, "time"))
+						if (!strcmp(cep4->name, "time"))
 						{
-							cfg.playback_on_join.time = config_checkval(cep4->ce_vardata, CFG_TIME);
+							cfg.playback_on_join.time = config_checkval(cep4->value, CFG_TIME);
 						}
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "max-storage-per-channel"))
+				if (!strcmp(cepp->name, "max-storage-per-channel"))
 				{
-					for (cep4 = cepp->ce_entries; cep4; cep4 = cep4->ce_next)
+					for (cep4 = cepp->items; cep4; cep4 = cep4->next)
 					{
-						if (!strcmp(cep4->ce_varname, "registered"))
+						if (!strcmp(cep4->name, "registered"))
 						{
-							for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
+							for (cep5 = cep4->items; cep5; cep5 = cep5->next)
 							{
-								if (!strcmp(cep5->ce_varname, "lines"))
+								if (!strcmp(cep5->name, "lines"))
 								{
-									cfg.max_storage_per_channel_registered.lines = atoi(cep5->ce_vardata);
+									cfg.max_storage_per_channel_registered.lines = atoi(cep5->value);
 								} else
-								if (!strcmp(cep5->ce_varname, "time"))
+								if (!strcmp(cep5->name, "time"))
 								{
-									cfg.max_storage_per_channel_registered.time = config_checkval(cep5->ce_vardata, CFG_TIME);
+									cfg.max_storage_per_channel_registered.time = config_checkval(cep5->value, CFG_TIME);
 								}
 							}
 						} else
-						if (!strcmp(cep4->ce_varname, "unregistered"))
+						if (!strcmp(cep4->name, "unregistered"))
 						{
-							for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
+							for (cep5 = cep4->items; cep5; cep5 = cep5->next)
 							{
-								if (!strcmp(cep5->ce_varname, "lines"))
+								if (!strcmp(cep5->name, "lines"))
 								{
-									cfg.max_storage_per_channel_unregistered.lines = atoi(cep5->ce_vardata);
+									cfg.max_storage_per_channel_unregistered.lines = atoi(cep5->value);
 								} else
-								if (!strcmp(cep5->ce_varname, "time"))
+								if (!strcmp(cep5->name, "time"))
 								{
-									cfg.max_storage_per_channel_unregistered.time = config_checkval(cep5->ce_vardata, CFG_TIME);
+									cfg.max_storage_per_channel_unregistered.time = config_checkval(cep5->value, CFG_TIME);
 								}
 							}
 						}
@@ -415,7 +413,7 @@ int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
  * @param lines: The number of lines (the X in +H X:Y)
  * @param t:     The time value (the Y in +H X:Y)
   */
-int history_parse_chanmode(Channel *channel, char *param, int *lines, long *t)
+int history_parse_chanmode(Channel *channel, const char *param, int *lines, long *t)
 {
 	char buf[64], *p, *q;
 	char contains_non_digit = 0;
@@ -479,11 +477,11 @@ int history_parse_chanmode(Channel *channel, char *param, int *lines, long *t)
  * Does the user have rights to add/remove this channel mode?
  * Is the supplied mode parameter ok?
  */
-int history_chanmode_is_ok(Client *client, Channel *channel, char mode, char *param, int type, int what)
+int history_chanmode_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) && is_chan_op(client, channel))
+		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, 'H');
@@ -529,7 +527,7 @@ static void history_chanmode_helper(char *buf, size_t bufsize, int lines, long t
 /** Convert channel parameter to something proper.
  * NOTE: client may be NULL if called for e.g. set::modes-playback-on-join
  */
-char *history_chanmode_conv_param(char *param, Client *client, Channel *channel)
+const char *history_chanmode_conv_param(const char *param, Client *client, Channel *channel)
 {
 	static char buf[64];
 	int lines = 0;
@@ -543,7 +541,7 @@ char *history_chanmode_conv_param(char *param, Client *client, Channel *channel)
 }
 
 /** Store the +H x:y channel mode */
-void *history_chanmode_put_param(void *mode_in, char *param)
+void *history_chanmode_put_param(void *mode_in, const char *param)
 {
 	HistoryChanMode *h = (HistoryChanMode *)mode_in;
 	int lines = 0;
@@ -565,7 +563,7 @@ void *history_chanmode_put_param(void *mode_in, char *param)
 }
 
 /** Retrieve the +H settings (the X:Y string) */
-char *history_chanmode_get_param(void *h_in)
+const char *history_chanmode_get_param(void *h_in)
 {
 	HistoryChanMode *h = (HistoryChanMode *)h_in;
 	static char buf[64];
@@ -612,7 +610,7 @@ int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx)
 }
 
 /** On channel mode change, communicate the +H limits to the history backend layer */
-int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode)
+int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel)
 {
 	HistoryChanMode *settings;
 
@@ -623,9 +621,9 @@ int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags,
 	/* If so, grab the settings, and communicate them */
 	settings = (HistoryChanMode *)GETPARASTRUCT(channel, 'H');
 	if (settings)
-		history_set_limit(channel->chname, settings->max_lines, settings->max_time);
+		history_set_limit(channel->name, settings->max_lines, settings->max_time);
 	else
-		history_destroy(channel->chname);
+		history_destroy(channel->name);
 
 	return 0;
 }
@@ -636,12 +634,12 @@ int history_channel_destroy(Channel *channel, int *should_destroy)
 	if (*should_destroy == 0)
 		return 0; /* channel will not be destroyed */
 
-	history_destroy(channel->chname);
+	history_destroy(channel->name);
 
 	return 0;
 }
 
-int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype)
+int history_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype)
 {
 	char buf[512];
 	char source[64];
@@ -672,15 +670,15 @@ int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix,
 	snprintf(buf, sizeof(buf), ":%s %s %s :%s",
 		source,
 		sendtype_to_cmd(sendtype),
-		channel->chname,
+		channel->name,
 		text);
 
-	history_add(channel->chname, mtags, buf);
+	history_add(channel->name, mtags, buf);
 
 	return 0;
 }
 
-int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[])
+int history_join(Client *client, Channel *channel, MessageTag *mtags)
 {
 	/* Only for +H channels */
 	if (!HistoryEnabled(channel) || !cfg.playback_on_join.lines || !cfg.playback_on_join.time)
@@ -689,7 +687,7 @@ int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv
 	/* No history-on-join for clients that implement CHATHISTORY,
 	 * they will pull history themselves if they need it.
 	 */
-	if (HasCapability(client, "draft/chathistory") || HasCapability(client, "chathistory"))
+	if (HasCapability(client, "draft/chathistory") /*|| HasCapability(client, "chathistory")*/)
 		return 0;
 
 	if (MyUser(client) && can_receive_history(client))
@@ -700,7 +698,7 @@ int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv
 		filter.cmd = HFC_SIMPLE;
 		filter.last_lines = cfg.playback_on_join.lines;
 		filter.last_seconds = cfg.playback_on_join.time;
-		r = history_request(channel->chname, &filter);
+		r = history_request(channel->name, &filter);
 		if (r)
 		{
 			history_send_result(client, r);
@@ -724,10 +722,10 @@ CMD_OVERRIDE_FUNC(override_mode)
 	 * means: we are the server that services are linked to.
 	 */
 	if ((IsServer(client) && client->local) ||
-	    (IsUser(client) && client->srvptr && client->srvptr->local))
+	    (IsUser(client) && client->uplink && client->uplink->local))
 	{
 		/* Now check if the channel is currently +r */
-		if ((parc >= 2) && !BadPtr(parv[1]) && ((channel = find_channel(parv[1], NULL))) &&
+		if ((parc >= 2) && !BadPtr(parv[1]) && ((channel = find_channel(parv[1]))) &&
 		    has_channel_mode(channel, 'r'))
 		{
 			had_r = 1;
@@ -744,7 +742,7 @@ CMD_OVERRIDE_FUNC(override_mode)
 	 * then...
 	 */
 	if (had_r &&
-	    ((channel = find_channel(parv[1], NULL))) &&
+	    ((channel = find_channel(parv[1]))) &&
 	    !has_channel_mode(channel, 'r') &&
 	    HistoryEnabled(channel))
 	{
@@ -770,7 +768,9 @@ CMD_OVERRIDE_FUNC(override_mode)
 		if (changed)
 		{
 			MessageTag *mtags = NULL;
-			char *params = history_chanmode_get_param(settings);
+			const char *params = history_chanmode_get_param(settings);
+			char modebuf[BUFSIZE], parabuf[BUFSIZE];
+			int destroy_channel = 0;
 
 			if (!params)
 				return; /* Weird */
@@ -782,13 +782,13 @@ CMD_OVERRIDE_FUNC(override_mode)
 
 			sendto_channel(channel, &me, &me, 0, 0, SEND_LOCAL, mtags,
 				       ":%s MODE %s %s %s",
-				       me.name, channel->chname, modebuf, parabuf);
+				       me.name, channel->name, modebuf, parabuf);
 			sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s %lld",
-				me.id, channel->chname, modebuf, parabuf,
+				me.id, channel->name, modebuf, parabuf,
 				(long long)channel->creationtime);
 
 			/* Activate this hook just like cmd_mode.c */
-			RunHook7(HOOKTYPE_REMOTE_CHANMODE, &me, channel, mtags, modebuf, parabuf, 0, 0);
+			RunHook(HOOKTYPE_REMOTE_CHANMODE, &me, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel);
 
 			free_message_tags(mtags);
 
diff --git a/src/modules/chanmodes/inviteonly.c b/src/modules/chanmodes/inviteonly.c
@@ -0,0 +1,77 @@
+/*
+ * Channel Mode +i
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/inviteonly",
+	"6.0",
+	"Channel Mode +i",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_INVITE_ONLY;
+
+#define IsInviteOnly(channel)    (channel->mode.mode & EXTCMODE_INVITE_ONLY)
+
+int inviteonly_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 'i';
+	req.is_ok = extcmode_default_requirehalfop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_INVITE_ONLY);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, inviteonly_can_join);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int inviteonly_can_join (Client *client, Channel *channel, const char *key, char **errmsg)
+{
+	if (IsInviteOnly(channel))
+	{
+		if (is_invited(client, channel))
+			return 0;
+		if (find_invex(channel, client))
+			return 0;
+		*errmsg = STR_ERR_INVITEONLYCHAN;
+		return ERR_INVITEONLYCHAN;
+	}
+	return 0;
+}
diff --git a/src/modules/chanmodes/isregistered.c b/src/modules/chanmodes/isregistered.c
@@ -0,0 +1,72 @@
+/*
+ * Channel Mode +r
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/isregistered",
+	"6.0",
+	"Channel Mode +r",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_REGISTERED;
+
+#define IsRegisteredChannel(channel)    (channel->mode.mode & EXTCMODE_REGISTERED)
+
+int isregistered_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what);
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 'r';
+	req.is_ok = isregistered_chanmode_is_ok;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_REGISTERED);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int isregistered_chanmode_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if (!IsServer(client) && !IsULine(client))
+	{
+		if (type == EXCHK_ACCESS_ERR)
+			sendnumeric(client, ERR_ONLYSERVERSCANCHANGE, channel->name);
+		return EX_ALWAYS_DENY;
+	}
+	return EX_ALLOW;
+}
diff --git a/src/modules/chanmodes/issecure.c b/src/modules/chanmodes/issecure.c
@@ -3,7 +3,7 @@
  * (C) Copyright 2010-.. Bram Matthys (Syzop) and the UnrealIRCd team
  *
  * This module will indicate if a channel is secure, and if so will set +Z.
- * Secure is defined as: all users on the channel are connected through SSL/TLS
+ * Secure is defined as: all users on the channel are connected through TLS
  * Additionally, the channel has to be +z (only allow secure users to join).
  * Suggested on http://bugs.unrealircd.org/view.php?id=3720
  * Thanks go to fez for pushing us for some kind of method to indicate
@@ -34,21 +34,21 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +Z", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_ISSECURE;
 
-#define IsSecureChanIndicated(channel)	(channel->mode.extmode & EXTCMODE_ISSECURE)
+#define IsSecureChanIndicated(channel)	(channel->mode.mode & EXTCMODE_ISSECURE)
 
 int IsSecureJoin(Channel *channel);
-int modeZ_is_ok(Client *client, Channel *channel, char mode, char *para, int checkt, int what);
-int issecure_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
-int issecure_part(Client *client, Channel *channel, MessageTag *mtags, char *comment);
-int issecure_quit(Client *client, MessageTag *mtags, char *comment);
-int issecure_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
+int modeZ_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what);
+int issecure_join(Client *client, Channel *channel, MessageTag *mtags);
+int issecure_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment);
+int issecure_quit(Client *client, MessageTag *mtags, const char *comment);
+int issecure_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, const char *comment);
 int issecure_chanmode(Client *client, Channel *channel, MessageTag *mtags,
-                             char *modebuf, char *parabuf, time_t sendts, int samode);
+                             const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel);
                              
 
 MOD_TEST()
@@ -64,7 +64,7 @@ CmodeInfo req;
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
 	req.is_ok = modeZ_is_ok;
-	req.flag = 'Z';
+	req.letter = 'Z';
 	req.local = 1; /* local channel mode */
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_ISSECURE);
 	
@@ -108,7 +108,7 @@ int IsSecureJoin(Channel *channel)
 	return i;
 }
 
-int modeZ_is_ok(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+int modeZ_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
 	/* Reject any attempt to set or unset our mode. Even to IRCOps */
 	return EX_ALWAYS_DENY;
@@ -143,17 +143,17 @@ void issecure_unset(Channel *channel, Client *client, MessageTag *recv_mtags, in
 	if (notice)
 	{
 		mtags = NULL;
-		new_message_special(&me, recv_mtags, &mtags, "NOTICE %s :setting -Z", channel->chname);
+		new_message_special(&me, recv_mtags, &mtags, "NOTICE %s :setting -Z", channel->name);
 		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
-		               ":%s NOTICE %s :User '%s' joined and is not connected through SSL/TLS, setting channel -Z (insecure)",
-		               me.id, channel->chname, client->name);
+		               ":%s NOTICE %s :User '%s' joined and is not connected through TLS, setting channel -Z (insecure)",
+		               me.id, channel->name, client->name);
 		free_message_tags(mtags);
 	}
 		
-	channel->mode.extmode &= ~EXTCMODE_ISSECURE;
+	channel->mode.mode &= ~EXTCMODE_ISSECURE;
 	mtags = NULL;
-	new_message_special(&me, recv_mtags, &mtags, "MODE %s -Z", channel->chname);
-	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s -Z", me.name, channel->chname);
+	new_message_special(&me, recv_mtags, &mtags, "MODE %s -Z", channel->name);
+	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s -Z", me.name, channel->name);
 	free_message_tags(mtags);
 }
 
@@ -168,31 +168,31 @@ void issecure_set(Channel *channel, Client *client, MessageTag *recv_mtags, int 
 	MessageTag *mtags;
 
 	mtags = NULL;
-	new_message_special(&me, recv_mtags, &mtags, "NOTICE %s :setting +Z", channel->chname);
+	new_message_special(&me, recv_mtags, &mtags, "NOTICE %s :setting +Z", channel->name);
 	if (notice && client)
 	{
 		/* note that we have to skip 'client', since when this call is being made
 		 * he is still considered a member of this channel.
 		 */
 		sendto_channel(channel, &me, client, 0, 0, SEND_LOCAL, NULL,
-		               ":%s NOTICE %s :Now all users in the channel are connected through SSL/TLS, setting channel +Z (secure)",
-		               me.name, channel->chname);
+		               ":%s NOTICE %s :Now all users in the channel are connected through TLS, setting channel +Z (secure)",
+		               me.name, channel->name);
 	} else if (notice)
 	{
 		/* note the missing word 'now' in next line */
 		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, NULL,
-		               ":%s NOTICE %s :All users in the channel are connected through SSL/TLS, setting channel +Z (secure)",
-		               me.name, channel->chname);
+		               ":%s NOTICE %s :All users in the channel are connected through TLS, setting channel +Z (secure)",
+		               me.name, channel->name);
 	}
 	free_message_tags(mtags);
 
-	channel->mode.extmode |= EXTCMODE_ISSECURE;
+	channel->mode.mode |= EXTCMODE_ISSECURE;
 
 	mtags = NULL;
-	new_message_special(&me, recv_mtags, &mtags, "MODE %s +Z", channel->chname);
+	new_message_special(&me, recv_mtags, &mtags, "MODE %s +Z", channel->name);
 	sendto_channel(channel, &me, client, 0, 0, SEND_LOCAL, mtags,
 	               ":%s MODE %s +Z",
-	               me.name, channel->chname);
+	               me.name, channel->name);
 	free_message_tags(mtags);
 }
 
@@ -200,7 +200,7 @@ void issecure_set(Channel *channel, Client *client, MessageTag *recv_mtags, int 
  *       so while they can be written shorter, they would only take longer to execute!
  */
 
-int issecure_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[])
+int issecure_join(Client *client, Channel *channel, MessageTag *mtags)
 {
 	/* Check only if chan already +zZ and the user joining is insecure (no need to count) */
 	if (IsSecureJoin(channel) && IsSecureChanIndicated(channel) && !IsSecureConnect(client) && !IsULine(client))
@@ -213,7 +213,7 @@ int issecure_join(Client *client, Channel *channel, MessageTag *mtags, char *par
 	return 0;
 }
 
-int issecure_part(Client *client, Channel *channel, MessageTag *mtags, char *comment)
+int issecure_part(Client *client, Channel *channel, MessageTag *mtags, const char *comment)
 {
 	/* Only care if chan is +z-Z and the user leaving is insecure, then count */
 	if (IsSecureJoin(channel) && !IsSecureChanIndicated(channel) && !IsSecureConnect(client) &&
@@ -222,7 +222,7 @@ int issecure_part(Client *client, Channel *channel, MessageTag *mtags, char *com
 	return 0;
 }
 
-int issecure_quit(Client *client, MessageTag *mtags, char *comment)
+int issecure_quit(Client *client, MessageTag *mtags, const char *comment)
 {
 Membership *membership;
 Channel *channel;
@@ -238,7 +238,7 @@ Channel *channel;
 	return 0;
 }
 
-int issecure_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment)
+int issecure_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, const char *comment)
 {
 	/* Identical to part&quit, except we care about 'victim' and not 'client' */
 	if (IsSecureJoin(channel) && !IsSecureChanIndicated(channel) &&
@@ -248,7 +248,7 @@ int issecure_kick(Client *client, Client *victim, Channel *channel, MessageTag *
 }
 
 int issecure_chanmode(Client *client, Channel *channel, MessageTag *mtags,
-                             char *modebuf, char *parabuf, time_t sendts, int samode)
+                             const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel)
 {
 	if (!strchr(modebuf, 'z'))
 		return 0; /* don't care */
diff --git a/src/modules/chanmodes/key.c b/src/modules/chanmodes/key.c
@@ -0,0 +1,232 @@
+/*
+ * Channel Mode +k
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/key",
+	"6.0",
+	"Channel Mode +k",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+typedef struct ChannelKey ChannelKey;
+struct ChannelKey {
+	char key[KEYLEN+1];
+};
+
+/* Global variables */
+ModDataInfo *mdkey = NULL;
+Cmode_t EXTMODE_KEY = 0L;
+
+#define IsKey(x)	((x)->mode.mode & EXTMODE_KEY)
+
+/* Forward declarations */
+int key_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
+int cmode_key_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+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);
+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);
+void transform_channel_key(const char *i, char *o, int n);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_key_is_ok;
+	creq.letter = 'k';
+	creq.unset_with_param = 1; /* yeah... +k is like this */
+	creq.put_param = cmode_key_put_param;
+	creq.get_param = cmode_key_get_param;
+	creq.conv_param = cmode_key_conv_param;
+	creq.free_param = cmode_key_free_param;
+	creq.dup_struct = cmode_key_dup_struct;
+	creq.sjoin_check = cmode_key_sjoin_check;
+	CmodeAdd(modinfo->handle, creq, &EXTMODE_KEY);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, key_can_join);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Can the user join the channel? */
+int key_can_join(Client *client, Channel *channel, const char *key, char **errmsg)
+{
+	ChannelKey *r = (ChannelKey *)GETPARASTRUCT(channel, 'k');
+
+	/* Is the channel +k? */
+	if (r && *r->key)
+	{
+		if (key && !strcmp(r->key, key))
+			return 0;
+		*errmsg = STR_ERR_BADCHANNELKEY;
+		return ERR_BADCHANNELKEY;
+	}
+
+	return 0;
+}
+
+int cmode_key_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		/* Permitted for +hoaq */
+		if (IsUser(client) && check_channel_access(client, channel, "hoaq"))
+			return EX_ALLOW;
+		return EX_DENY;
+	} else
+	if (type == EXCHK_PARAM)
+	{
+		if (!is_valid_key(param))
+		{
+			sendnumeric(client, ERR_INVALIDMODEPARAM,
+				channel->name, 'k', "*", "Channel key contains forbidden characters or is too long");
+			return EX_DENY;
+		}
+		return EX_ALLOW;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
+
+void *cmode_key_put_param(void *k_in, const char *param)
+{
+	ChannelKey *fld = (ChannelKey *)k_in;
+
+	if (!fld)
+		fld = safe_alloc(sizeof(ChannelKey));
+
+	transform_channel_key(param, fld->key, sizeof(fld->key));
+
+	return fld;
+}
+
+const char *cmode_key_get_param(void *r_in)
+{
+	ChannelKey *r = (ChannelKey *)r_in;
+	static char retbuf[KEYLEN+1];
+
+	if (!r)
+		return NULL;
+
+	strlcpy(retbuf, r->key, sizeof(retbuf));
+	return retbuf;
+}
+
+const char *cmode_key_conv_param(const char *param, Client *client, Channel *channel)
+{
+	static char retbuf[KEYLEN+1];
+
+	transform_channel_key(param, retbuf, sizeof(retbuf));
+
+	if (!*retbuf)
+		return NULL; /* entire key was invalid */
+
+	return retbuf;
+}
+
+void cmode_key_free_param(void *r)
+{
+	safe_free(r);
+}
+
+void *cmode_key_dup_struct(void *r_in)
+{
+	ChannelKey *r = (ChannelKey *)r_in;
+	ChannelKey *w = safe_alloc(sizeof(ChannelKey));
+
+	memcpy(w, r, sizeof(ChannelKey));
+
+	return (void *)w;
+}
+
+int cmode_key_sjoin_check(Channel *channel, void *ourx, void *theirx)
+{
+	ChannelKey *our = (ChannelKey *)ourx;
+	ChannelKey *their = (ChannelKey *)theirx;
+	int i;
+	int r;
+
+	r = strcmp(our->key, their->key);
+	if (r == 0)
+		return EXSJ_SAME;
+	else if (r > 0)
+		return EXSJ_WEWON;
+	else
+		return EXSJ_THEYWON;
+}
+
+int valid_key_char(char c)
+{
+	if (strchr(" :,", c))
+		return 0;
+	if (c <= 32)
+		return 0;
+	return 1;
+}
+
+#define BADKEYCHARS " :,"
+int is_valid_key(const char *key)
+{
+	const char *p;
+
+	if (strlen(key) > KEYLEN)
+		return 0;
+	for (p = key; *p; p++)
+		if (!valid_key_char(*p))
+			return 0;
+	return 1;
+}
+
+void transform_channel_key(const char *i, char *o, int n)
+{
+	n--; /* reserve one for final nul byte */
+
+	for (; *i; i++)
+	{
+		if (!valid_key_char(*i))
+			break;
+		if (n <= 0)
+			break;
+		*o++ = *i;
+		n--;
+	}
+	*o = '\0';
+}
diff --git a/src/modules/chanmodes/limit.c b/src/modules/chanmodes/limit.c
@@ -0,0 +1,198 @@
+/*
+ * Channel Mode +l
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/limit",
+	"6.0",
+	"Channel Mode +l",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+typedef struct ChannelLimit ChannelLimit;
+struct ChannelLimit {
+	int limit;
+};
+
+/* Global variables */
+ModDataInfo *mdlimit = NULL;
+Cmode_t EXTMODE_LIMIT = 0L;
+
+#define IsLimit(x)	((x)->mode.mode & EXTMODE_LIMIT)
+
+/* Just for buffers, nothing else */
+#define LIMITLEN	32
+
+/* Forward declarations */
+int limit_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
+int cmode_limit_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+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);
+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);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_limit_is_ok;
+	creq.letter = 'l';
+	creq.put_param = cmode_limit_put_param;
+	creq.get_param = cmode_limit_get_param;
+	creq.conv_param = cmode_limit_conv_param;
+	creq.free_param = cmode_limit_free_param;
+	creq.dup_struct = cmode_limit_dup_struct;
+	creq.sjoin_check = cmode_limit_sjoin_check;
+	CmodeAdd(modinfo->handle, creq, &EXTMODE_LIMIT);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, limit_can_join);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Can the user join the channel? */
+int limit_can_join(Client *client, Channel *channel, const char *key, char **errmsg)
+{
+	ChannelLimit *r = (ChannelLimit *)GETPARASTRUCT(channel, 'l');
+
+	/* Is the channel +l? */
+	if (r && r->limit && (channel->users >= r->limit))
+	{
+		Hook *h;
+		for (h = Hooks[HOOKTYPE_CAN_JOIN_LIMITEXCEEDED]; h; h = h->next) 
+		{
+			int i = (*(h->func.intfunc))(client,channel,key,errmsg);
+			if (i != 0)
+				return i;
+		}
+		*errmsg = STR_ERR_CHANNELISFULL;
+		return ERR_CHANNELISFULL;
+	}
+
+	return 0;
+}
+
+int cmode_limit_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		/* Permitted for +hoaq */
+		if (IsUser(client) && check_channel_access(client, channel, "hoaq"))
+			return EX_ALLOW;
+		return EX_DENY;
+	} else
+	if (type == EXCHK_PARAM)
+	{
+		/* Actually any value is valid, we just morph it */
+		return EX_ALLOW;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
+
+void *cmode_limit_put_param(void *k_in, const char *param)
+{
+	ChannelLimit *fld = (ChannelLimit *)k_in;
+
+	if (!fld)
+		fld = safe_alloc(sizeof(ChannelLimit));
+
+	fld->limit = transform_channel_limit(param);
+
+	return fld;
+}
+
+const char *cmode_limit_get_param(void *r_in)
+{
+	ChannelLimit *r = (ChannelLimit *)r_in;
+	static char retbuf[32];
+
+	if (!r)
+		return NULL;
+
+	snprintf(retbuf, sizeof(retbuf), "%d", r->limit);
+	return retbuf;
+}
+
+const char *cmode_limit_conv_param(const char *param, Client *client, Channel *channel)
+{
+	static char retbuf[32];
+	int v = transform_channel_limit(param);
+	snprintf(retbuf, sizeof(retbuf), "%d", v);
+	return retbuf;
+}
+
+void cmode_limit_free_param(void *r)
+{
+	safe_free(r);
+}
+
+void *cmode_limit_dup_struct(void *r_in)
+{
+	ChannelLimit *r = (ChannelLimit *)r_in;
+	ChannelLimit *w = safe_alloc(sizeof(ChannelLimit));
+
+	memcpy(w, r, sizeof(ChannelLimit));
+
+	return (void *)w;
+}
+
+int cmode_limit_sjoin_check(Channel *channel, void *ourx, void *theirx)
+{
+	ChannelLimit *our = (ChannelLimit *)ourx;
+	ChannelLimit *their = (ChannelLimit *)theirx;
+
+	if (our->limit == their->limit)
+		return EXSJ_SAME;
+	else if (our->limit > their->limit)
+		return EXSJ_WEWON;
+	else
+		return EXSJ_THEYWON;
+}
+
+int transform_channel_limit(const char *param)
+{
+	int v = atoi(param);
+	if (v <= 0)
+		v = 1; /* setting +l with a negative number makes no sense */
+	if (v > 1000000)
+		v = 1000000; /* some kind of limit, 1 million (mrah...) */
+	return v;
+}
diff --git a/src/modules/chanmodes/link.c b/src/modules/chanmodes/link.c
@@ -26,7 +26,7 @@ ModuleHeader MOD_HEADER = {
 	"5.0",
 	"Channel Mode +L",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 Cmode_t EXTMODE_LINK = 0L;
@@ -39,26 +39,25 @@ typedef enum {
 	LINKTYPE_BAN = 1, // +b
 	LINKTYPE_INVITE = 2, // +i
 	LINKTYPE_OPER = 3, // +O
-	LINKTYPE_SSL = 4, // +z
+	LINKTYPE_SECURE = 4, // +z
 	LINKTYPE_REG = 5, // +R
 	LINKTYPE_LIMIT = 6, // +l
 	LINKTYPE_BADKEY = 7, // +k
 } linkType;
 
-int cmodeL_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what);
-void *cmodeL_put_param(void *r_in, char *param);
-char *cmodeL_get_param(void *r_in);
-char *cmodeL_conv_param(char *param_in, Client *client, Channel *channel);
+int cmodeL_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+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);
 void *cmodeL_dup_struct(void *r_in);
 int cmodeL_sjoin_check(Channel *channel, void *ourx, void *theirx);
 
-int extban_link_syntax(Client *client, int checkt, char *reason);
-int extban_link_is_ok(Client *client, Channel *channel, char *param, int checkt, int what, int what2);
-char *extban_link_conv_param(char *param);
-int extban_link_is_banned(Client *client, Channel *channel, char *ban, int type, char **msg, char **errmsg);
-int link_doforward(Client *client, Channel *channel, char *linked, linkType linktype);
-int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[]);
+int extban_link_syntax(Client *client, int checkt, const char *reason);
+int extban_link_is_ok(BanContext *b);
+const char *extban_link_conv_param(BanContext *b, Extban *extban);
+int link_doforward(Client *client, Channel *channel, const char *linked, linkType linktype);
+int link_pre_localjoin_cb(Client *client, Channel *channel, const char *key);
 
 MOD_INIT()
 {
@@ -70,7 +69,7 @@ MOD_INIT()
 	memset(&req, 0, sizeof(req));
 	req.paracount = 1;
 	req.is_ok = cmodeL_is_ok;
-	req.flag = 'L';
+	req.letter = 'L';
 	req.unset_with_param = 1; /* Oh yeah, we are special! */
 	req.put_param = cmodeL_put_param;
 	req.get_param = cmodeL_get_param;
@@ -81,10 +80,10 @@ MOD_INIT()
 	CmodeAdd(modinfo->handle, req, &EXTMODE_LINK);
 
 	memset(&req_extban, 0, sizeof(ExtbanInfo));
-	req_extban.flag = 'f';
+	req_extban.letter = 'f';
+	req_extban.name = "forward";
 	req_extban.is_ok = extban_link_is_ok;
 	req_extban.conv_param = extban_link_conv_param;
-	req_extban.is_banned = extban_link_is_banned;
 	req_extban.options = EXTBOPT_ACTMODIFIER;
 	if (!ExtbanAdd(modinfo->handle, req_extban))
 	{
@@ -107,14 +106,14 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int cmodeL_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what)
+int cmodeL_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what)
 {
 	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
 	{
-		if (IsUser(client) && is_chan_op(client, channel))
+		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, 'H');
+			sendnumeric(client, ERR_NOTFORHALFOPS, 'L');
 		return EX_DENY;
 	} else
 	if (type == EXCHK_PARAM)
@@ -129,7 +128,7 @@ int cmodeL_is_ok(Client *client, Channel *channel, char mode, char *para, int ty
 			return EX_DENY;
 		}
 
-		if (find_channel(para, NULL) == channel)
+		if (find_channel(para) == channel)
 		{
 			if (MyUser(client))
 				sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'L',
@@ -143,7 +142,7 @@ int cmodeL_is_ok(Client *client, Channel *channel, char mode, char *para, int ty
 	return EX_DENY;
 }
 
-void *cmodeL_put_param(void *r_in, char *param)
+void *cmodeL_put_param(void *r_in, const char *param)
 {
 	aModeLEntry *r = (aModeLEntry *)r_in;
 
@@ -156,7 +155,7 @@ void *cmodeL_put_param(void *r_in, char *param)
 	return (void *)r;
 }
 
-char *cmodeL_get_param(void *r_in)
+const char *cmodeL_get_param(void *r_in)
 {
 	aModeLEntry *r = (aModeLEntry *)r_in;
 	static char retbuf[CHANNELLEN+1];
@@ -171,10 +170,8 @@ char *cmodeL_get_param(void *r_in)
 /** Convert parameter to something proper.
  * NOTE: client may be NULL
  */
-char *cmodeL_conv_param(char *param, Client *client, Channel *channel)
+const char *cmodeL_conv_param(const char *param, Client *client, Channel *channel)
 {
-	char *p;
-
 	if (!valid_channelname(param))
 		return NULL;
 
@@ -207,7 +204,7 @@ int cmodeL_sjoin_check(Channel *channel, void *ourx, void *theirx)
 	return EXSJ_THEYWON;
 }
 
-int extban_link_syntax(Client *client, int checkt, char *reason)
+int extban_link_syntax(Client *client, int checkt, const char *reason)
 {
 	if (MyUser(client) && (checkt == EXBCHK_PARAM))
 	{
@@ -222,52 +219,50 @@ int extban_link_syntax(Client *client, int checkt, char *reason)
 	return 0; // Reject ban
 }
 
-int extban_link_is_ok(Client *client, Channel *channel, char *param, int checkt, int what, int what2)
+int extban_link_is_ok(BanContext *b)
 {
-	char paramtmp[MAX_EB_LEN + 1];
-	char tmpmask[MAX_EB_LEN + 1];
+	static char paramtmp[MAX_EB_LEN + 1];
 	char *matchby; // Matching method, such as 'n!u@h'
 	char *chan;
 
 	// Always permit deletion
-	if (what == MODE_DEL)
+	if (b->what == MODE_DEL)
 		return 1;
 
-	if (what2 != EXBTYPE_BAN)
+	if (b->ban_type != EXBTYPE_BAN)
 	{
-		if (checkt == EXBCHK_PARAM)
-			sendnotice(client, "Ban type ~f only works with bans (+b) and not with exceptions or invex (+e/+I)");
+		if (b->is_ok_check == EXBCHK_PARAM)
+			sendnotice(b->client, "Ban type ~f only works with bans (+b) and not with exceptions or invex (+e/+I)");
 		return 0; // Reject
 	}
 
-	strlcpy(paramtmp, param + 3, sizeof(paramtmp)); // Work on a size-truncated copy
+	strlcpy(paramtmp, b->banstr, sizeof(paramtmp)); // Work on a size-truncated copy
 	chan = paramtmp;
 	matchby = strchr(paramtmp, ':');
 	if (!matchby || !matchby[1])
-		return extban_link_syntax(client, checkt, "Invalid syntax");
+		return extban_link_syntax(b->client, b->is_ok_check, "Invalid syntax");
 	*matchby++ = '\0';
 
-	if (*chan != '#' || strchr(param, ','))
-		return extban_link_syntax(client, checkt, "Invalid channel");
+	if (*chan != '#' || strchr(b->banstr, ','))
+		return extban_link_syntax(b->client, b->is_ok_check, "Invalid channel");
 
-	// Possibly stack multiple extbans, this is a little convoluted due to extban API limitations
-	snprintf(tmpmask, sizeof(tmpmask), "~?:%s", matchby);
-	if (extban_is_ok_nuh_extban(client, channel, tmpmask, checkt, what, what2) == 0)
-		return extban_link_syntax(client, checkt, "Invalid matcher");
+	b->banstr = matchby;
+	if (extban_is_ok_nuh_extban(b) == 0)
+		return extban_link_syntax(b->client, b->is_ok_check, "Invalid matcher");
 
 	return 1; // Is ok
 }
 
-char *extban_link_conv_param(char *param)
+const char *extban_link_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[MAX_EB_LEN + 1];
 	char paramtmp[MAX_EB_LEN + 1];
 	char tmpmask[MAX_EB_LEN + 1];
 	char *matchby; // Matching method, such as 'n!u@h'
-	char *newmask; // Cleaned matching method, such as 'n!u@h'
+	const char *newmask; // Cleaned matching method, such as 'n!u@h'
 	char *chan;
 
-	strlcpy(paramtmp, param + 3, sizeof(paramtmp)); // Work on a size-truncated copy
+	strlcpy(paramtmp, b->banstr, sizeof(paramtmp)); // Work on a size-truncated copy
 	chan = paramtmp;
 	matchby = strchr(paramtmp, ':');
 	if (!matchby || !matchby[1])
@@ -277,26 +272,20 @@ char *extban_link_conv_param(char *param)
 	if (!valid_channelname(chan))
 		return NULL;
 
-	// Possibly stack multiple extbans, this is a little convoluted due to extban API limitations
-	snprintf(tmpmask, sizeof(tmpmask), "~?:%s", matchby);
-	newmask = extban_conv_param_nuh_or_extban(tmpmask);
-	if (!newmask || (strlen(newmask) <= 3))
+	b->banstr = matchby;
+	newmask = extban_conv_param_nuh_or_extban(b, extban);
+	if (BadPtr(newmask))
 		return NULL;
 
-	snprintf(retbuf, sizeof(retbuf), "~f:%s:%s", chan, newmask + 3);
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", chan, newmask);
 	return retbuf;
 }
 
-int extban_link_is_banned(Client *client, Channel *channel, char *ban, int type, char **msg, char **errmsg)
-{
-	// We don't actually ban here because we have to extract the channel name in PRE_LOCAL_JOIN anyways
-	return 0;
-}
-
-int link_doforward(Client *client, Channel *channel, char *linked, linkType type)
+int link_doforward(Client *client, Channel *channel, const char *linked, linkType type)
 {
+	char linked_channel_buffer[CHANNELLEN+1];
 	char desc[64];
-	char *parv[3];
+	const char *parv[3];
 
 	switch (type)
 	{
@@ -312,8 +301,8 @@ int link_doforward(Client *client, Channel *channel, char *linked, linkType type
 			strncpy(desc, "channel is oper only", sizeof(desc));
 			break;
 
-		case LINKTYPE_SSL:
-			strncpy(desc, "channel requires SSL", sizeof(desc));
+		case LINKTYPE_SECURE:
+			strncpy(desc, "channel requires a secure connection", sizeof(desc));
 			break;
 
 		case LINKTYPE_REG:
@@ -335,23 +324,24 @@ int link_doforward(Client *client, Channel *channel, char *linked, linkType type
 
 	sendto_one(client, NULL,
 	           ":%s %d %s %s %s :[Link] Cannot join channel %s (%s) -- transferring you to %s",
-	           me.name, ERR_LINKCHANNEL, client->name, channel->chname, linked,
-	           channel->chname, desc, linked);
+	           me.name, ERR_LINKCHANNEL, client->name, channel->name, linked,
+	           channel->name, desc, linked);
+
+	strlcpy(linked_channel_buffer, linked, sizeof(linked_channel_buffer));
 	parv[0] = client->name;
-	parv[1] = linked;
+	parv[1] = linked_channel_buffer;
 	parv[2] = NULL;
+
 	do_join(client, 2, parv);
+
 	return HOOK_DENY; // Original channel join = ignored
 }
 
-int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[])
+int link_pre_localjoin_cb(Client *client, Channel *channel, const char *key)
 {
-	char *linked;
+	const char *linked;
 	int canjoin;
-	Ban *ban;
-	char bantmp[MAX_EB_LEN + 1];
-	char *banchan;
-	char *banmask;
+	char *error = NULL;
 
 	// User might already be on this channel, let's also exclude any possible services bots early
 	if (IsULine(client) || find_membership_link(client->user->channel, channel))
@@ -361,12 +351,26 @@ int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[])
 	// only /INVITE from chanop bypasses:
 	if (!is_invited(client, channel))
 	{
-		for(ban = channel->banlist; ban; ban = ban->next)
+		Ban *ban;
+		BanContext *b = safe_alloc(sizeof(BanContext));
+		char bantmp[MAX_EB_LEN + 1];
+		char *banchan;
+		char *banmask;
+
+		b->client = client;
+		b->channel = channel;
+		b->ban_check_types = BANCHK_JOIN;
+
+		for (ban = channel->banlist; ban; ban = ban->next)
 		{
 			if (!strncmp(ban->banstr, "~f:", 3))
 			{
 				strlcpy(bantmp, ban->banstr + 3, sizeof(bantmp));
 			} else
+			if (!strncmp(ban->banstr, "~forward:", 9))
+			{
+				strlcpy(bantmp, ban->banstr + 9, sizeof(bantmp));
+			} else
 			if (!strncmp(ban->banstr, "~t:", 3))
 			{
 				/* A timed ban, but is it for us? Need to parse a little:
@@ -376,6 +380,28 @@ int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[])
 				if (p && !strncmp(p, ":~f:", 4))
 				{
 					strlcpy(bantmp, p + 4, sizeof(bantmp));
+				} else
+				if (p && !strncmp(p, ":~forward:", 10))
+				{
+					strlcpy(bantmp, p + 10, sizeof(bantmp));
+				} else {
+					/* Not for us - some other ~t ban */
+					continue;
+				}
+			} else
+			if (!strncmp(ban->banstr, "~time:", 6))
+			{
+				/* A timed ban, but is it for us? Need to parse a little:
+				 * ~t:dddd:~f:...
+				 */
+				char *p = strchr(ban->banstr + 6, ':');
+				if (p && !strncmp(p, ":~f:", 4))
+				{
+					strlcpy(bantmp, p + 4, sizeof(bantmp));
+				} else
+				if (p && !strncmp(p, ":~forward:", 10))
+				{
+					strlcpy(bantmp, p + 10, sizeof(bantmp));
 				} else {
 					/* Not for us - some other ~t ban */
 					continue;
@@ -391,26 +417,32 @@ int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[])
 				continue;
 			*banmask++ = '\0';
 
-			if (ban_check_mask(client, channel, banmask, BANCHK_JOIN, NULL, NULL, 0))
+			b->banstr = banmask;
+			if (ban_check_mask(b))
+			{
+				safe_free(b);
 				return link_doforward(client, channel, banchan, LINKTYPE_BAN);
+			}
 		}
+
+		safe_free(b);
 	}
 
 	// Either +L is not set, or it is set but the parameter isn't stored somehow
-	if (!(channel->mode.extmode & EXTMODE_LINK) || !(linked = cm_getparameter(channel, 'L')))
+	if (!(channel->mode.mode & EXTMODE_LINK) || !(linked = cm_getparameter(channel, 'L')))
 		return HOOK_CONTINUE;
 
 	// can_join() actually returns 0 if we *can* join a channel, so we don't need to bother checking any further conditions
-	if (!(canjoin = can_join(client, channel, parv[2], parv)))
+	if (!(canjoin = can_join(client, channel, key, &error)))
 		return HOOK_CONTINUE;
 
 	// Oper only channel
 	if (has_channel_mode(channel, 'O') && !IsOper(client))
 		return link_doforward(client, channel, linked, LINKTYPE_OPER);
 
-	// SSL/TLS connected users only
+	// TLS connected users only
 	if (has_channel_mode(channel, 'z') && !IsSecureConnect(client))
-		return link_doforward(client, channel, linked, LINKTYPE_SSL);
+		return link_doforward(client, channel, linked, LINKTYPE_SECURE);
 
 	// Registered/identified users only
 	if (has_channel_mode(channel, 'R') && !IsRegNick(client))
diff --git a/src/modules/chanmodes/moderated.c b/src/modules/chanmodes/moderated.c
@@ -0,0 +1,117 @@
+/*
+ * Channel Mode +m
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/moderated",
+	"6.0",
+	"Channel Mode +m",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+Cmode_t EXTCMODE_MODERATED;
+
+/* Forward declarations */
+int moderated_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+const char *moderated_pre_local_part(Client *client, Channel *channel, const char *text);
+int moderated_can_set_topic(Client *client, Channel *channel, const char *topic, const char **errmsg);
+
+/* Macros */
+#define IsModerated(channel)    (channel->mode.mode & EXTCMODE_MODERATED)
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 'm';
+	req.is_ok = extcmode_default_requirehalfop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_MODERATED);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, moderated_can_send_to_channel);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, moderated_pre_local_part);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SET_TOPIC, 0, moderated_can_set_topic);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int moderated_can_send_to_channel(Client *client, Channel *channel, Membership *m, const char **msg, const char **errmsg, SendType sendtype)
+{
+	if (IsModerated(channel) && (!m || !check_channel_access_membership(m, "vhoaq")) &&
+	    !op_can_override("channel:override:message:moderated",client,channel,NULL))
+	{
+		Hook *h;
+		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
+		{
+			int i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_MODERATED);
+			if (i == HOOK_ALLOW)
+				return HOOK_CONTINUE; /* bypass +m restriction */
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		*errmsg = "You need voice (+v)";
+		return HOOK_DENY; /* BLOCK message */
+	}
+
+	return HOOK_CONTINUE;
+}
+
+/** Remove PART reason too if the channel is +m, -t, and user not +vhoaq */
+const char *moderated_pre_local_part(Client *client, Channel *channel, const char *text)
+{
+	if (IsModerated(channel) && !check_channel_access(client, channel, "v") && !check_channel_access(client, channel, "h"))
+		return NULL;
+	return text;
+}
+
+int moderated_can_set_topic(Client *client, Channel *channel, const char *topic, const char **errmsg)
+{
+	static char errmsg_buf[NICKLEN+256];
+
+	/* Channel is +m but user is not +vhoaq: reject the topic change */
+	if (has_channel_mode(channel, 'm') && !check_channel_access(client, channel, "vhoaq"))
+	{
+		char buf[512];
+		snprintf(buf, sizeof(buf), "Voice (+v) or higher is required in order to change the topic on %s (channel is +m)", channel->name);
+		buildnumeric(errmsg_buf, sizeof(errmsg_buf), client, ERR_CANNOTDOCOMMAND, "TOPIC", buf);
+		*errmsg = errmsg_buf;
+		return EX_DENY;
+	}
+
+	return EX_ALLOW;
+}
diff --git a/src/modules/chanmodes/nocolor.c b/src/modules/chanmodes/nocolor.c
@@ -27,16 +27,16 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +c",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NOCOLOR;
 
-#define IsNoColor(channel)    (channel->mode.extmode & EXTCMODE_NOCOLOR)
+#define IsNoColor(channel)    (channel->mode.mode & EXTCMODE_NOCOLOR)
 
-int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-char *nocolor_prelocalpart(Client *client, Channel *channel, char *comment);
-char *nocolor_prelocalquit(Client *client, char *comment);
+int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+const char *nocolor_prelocalpart(Client *client, Channel *channel, const char *comment);
+const char *nocolor_prelocalquit(Client *client, const char *comment);
 
 MOD_TEST()
 {
@@ -50,14 +50,14 @@ CmodeInfo req;
 	/* Channel mode */
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'c';
+	req.letter = 'c';
 	req.is_ok = extcmode_default_requirechop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NOCOLOR);
 	
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, nocolor_can_send_to_channel);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, nocolor_prelocalpart);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT_CHAN, 0, nocolor_prelocalpart);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, nocolor_prelocalquit);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, nocolor_prelocalpart);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT_CHAN, 0, nocolor_prelocalpart);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, nocolor_prelocalquit);
 	
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
@@ -73,7 +73,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static int IsUsingColor(char *s)
+static int IsUsingColor(const char *s)
 {
         if (!s)
                 return 0;
@@ -85,7 +85,7 @@ static int IsUsingColor(char *s)
         return 0;
 }
 
-int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
@@ -108,7 +108,7 @@ int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp
 	return HOOK_CONTINUE;
 }
 
-char *nocolor_prelocalpart(Client *client, Channel *channel, char *comment)
+const char *nocolor_prelocalpart(Client *client, Channel *channel, const char *comment)
 {
 	if (!comment)
 		return NULL;
@@ -130,7 +130,7 @@ static int IsAnyChannelNoColor(Client *client)
 	return 0;
 }
 
-char *nocolor_prelocalquit(Client *client, char *comment)
+const char *nocolor_prelocalquit(Client *client, const char *comment)
 {
 	if (!comment)
 		return NULL;
diff --git a/src/modules/chanmodes/noctcp.c b/src/modules/chanmodes/noctcp.c
@@ -27,14 +27,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +C",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NOCTCP;
 
-#define IsNoCTCP(channel)    (channel->mode.extmode & EXTCMODE_NOCTCP)
+#define IsNoCTCP(channel)    (channel->mode.mode & EXTCMODE_NOCTCP)
 
-int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
 
 MOD_TEST()
 {
@@ -47,7 +47,7 @@ CmodeInfo req;
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'C';
+	req.letter = 'C';
 	req.is_ok = extcmode_default_requirehalfop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NOCTCP);
 	
@@ -67,7 +67,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static int IsACTCP(char *s)
+static int IsACTCP(const char *s)
 {
 	if (!s)
 		return 0;
@@ -78,7 +78,7 @@ static int IsACTCP(char *s)
 	return 0;
 }
 
-int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	if (IsNoCTCP(channel) && IsACTCP(*msg))
 	{
diff --git a/src/modules/chanmodes/noexternalmsgs.c b/src/modules/chanmodes/noexternalmsgs.c
@@ -0,0 +1,89 @@
+/*
+ * Channel Mode +n
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/noexternalmsgs",
+	"6.0",
+	"Channel Mode +n",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_NO_EXTERNAL_MESSAGES;
+
+#define IsNoExternalMessages(channel)    (channel->mode.mode & EXTCMODE_NO_EXTERNAL_MESSAGES)
+
+int noexternalmsgs_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 'n';
+	req.is_ok = extcmode_default_requirehalfop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_NO_EXTERNAL_MESSAGES);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, noexternalmsgs_can_send_to_channel);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int noexternalmsgs_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
+{
+	if (IsNoExternalMessages(channel) && !IsMember(client,channel))
+	{
+		/* Channel does not accept external messages (+n).
+		 * Reject, unless HOOKTYPE_CAN_BYPASS_NO_EXTERNAL_MSGS tells otherwise.
+		 */
+		Hook *h;
+		int i;
+
+		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_EXTERNAL);
+			if (i == HOOK_ALLOW)
+				return HOOK_CONTINUE; /* bypass +n restriction */
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		*errmsg = "No external channel messages";
+		return HOOK_DENY; /* BLOCK message */
+	}
+
+	return HOOK_CONTINUE;
+}
diff --git a/src/modules/chanmodes/noinvite.c b/src/modules/chanmodes/noinvite.c
@@ -27,14 +27,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +V",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NOINVITE;
 
-#define IsNoInvite(channel)    (channel->mode.extmode & EXTCMODE_NOINVITE)
+#define IsNoInvite(channel)    (channel->mode.mode & EXTCMODE_NOINVITE)
 
-int noinvite_pre_knock(Client *client, Channel *channel);
+int noinvite_pre_knock(Client *client, Channel *channel, const char **reason);
 int noinvite_pre_invite(Client *client, Client *target, Channel *channel, int *override);
 
 MOD_TEST()
@@ -48,7 +48,7 @@ MOD_INIT()
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'V';
+	req.letter = 'V';
 	req.is_ok = extcmode_default_requirechop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NOINVITE);
 	
@@ -70,12 +70,12 @@ MOD_UNLOAD()
 }
 
 
-int noinvite_pre_knock(Client *client, Channel *channel)
+int noinvite_pre_knock(Client *client, Channel *channel, const char **reason)
 {
 	if (MyUser(client) && IsNoInvite(channel))
 	{
-		sendnumeric(client, ERR_CANNOTKNOCK,
-				    channel->chname, "The channel does not allow invites (+V)");
+		sendnumeric(client, ERR_CANNOTKNOCK, channel->name,
+		            "The channel does not allow invites (+V)");
 		return HOOK_DENY;
 	}
 
@@ -90,7 +90,7 @@ int noinvite_pre_invite(Client *client, Client *target, Channel *channel, int *o
 		{
 			*override = 1;
 		} else {
-			sendnumeric(client, ERR_NOINVITE, channel->chname);
+			sendnumeric(client, ERR_NOINVITE, channel->name);
 			return HOOK_DENY;
 		}
 	}
diff --git a/src/modules/chanmodes/nokick.c b/src/modules/chanmodes/nokick.c
@@ -26,14 +26,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +Q",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NOKICK;
 
-#define IsNoKick(channel)    (channel->mode.extmode & EXTCMODE_NOKICK)
+#define IsNoKick(channel)    (channel->mode.mode & EXTCMODE_NOKICK)
 
-int nokick_check (Client *client, Client *who, Channel *channel, char *comment, long client_flags, long who_flags, char **reject_reason);
+int nokick_check (Client *client, Client *target, Channel *channel, const char *comment, const char *client_member_modes, const char *target_member_modes, const char **reject_reason);
 
 MOD_TEST()
 {
@@ -46,7 +46,7 @@ MOD_INIT()
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'Q';
+	req.letter = 'Q';
 	req.is_ok = extcmode_default_requirechop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NOKICK);
 	
@@ -67,7 +67,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int nokick_check (Client *client, Client *who, Channel *channel, char *comment, long client_flags, long who_flags, char **reject_reason)
+int nokick_check (Client *client, Client *target, Channel *channel, const char *comment, const char *client_member_modes, const char *target_member_modes, const char **reject_reason)
 {
 	static char errmsg[256];
 
diff --git a/src/modules/chanmodes/noknock.c b/src/modules/chanmodes/noknock.c
@@ -25,15 +25,15 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +K",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NOKNOCK;
 
-#define IsNoKnock(channel)    (channel->mode.extmode & EXTCMODE_NOKNOCK)
+#define IsNoKnock(channel)    (channel->mode.mode & EXTCMODE_NOKNOCK)
 
-int noknock_check (Client *client, Channel *channel);
-int noknock_mode_allow(Client *client, Channel *channel, char mode, char *para, int checkt, int what);
+int noknock_check_knock(Client *client, Channel *channel, const char **reason);
+int noknock_mode_allow(Client *client, Channel *channel, char mode, const char *para, int checkt, int what);
 int noknock_mode_del (Channel *channel, int modeChar);
 
 MOD_TEST()
@@ -47,11 +47,11 @@ CmodeInfo req;
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'K';
+	req.letter = 'K';
 	req.is_ok = noknock_mode_allow;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NOKNOCK);
 	
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_KNOCK, 0, noknock_check);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_KNOCK, 0, noknock_check_knock);
 	HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_DEL, 0, noknock_mode_del);
 
 	
@@ -70,11 +70,11 @@ MOD_UNLOAD()
 }
 
 
-int noknock_check (Client *client, Channel *channel)
+int noknock_check_knock (Client *client, Channel *channel, const char **reason)
 {
 	if (MyUser(client) && IsNoKnock(channel))
 	{
-		sendnumeric(client, ERR_CANNOTKNOCK, channel->chname, "No knocks are allowed! (+K)");
+		sendnumeric(client, ERR_CANNOTKNOCK, channel->name, "No knocks are allowed! (+K)");
 		return HOOK_DENY;
 	}
 
@@ -85,14 +85,14 @@ int noknock_mode_del (Channel *channel, int modeChar)
 {
 	// Remove noknock when we're removing invite only
 	if (modeChar == 'i')
-		channel->mode.extmode &= ~EXTCMODE_NOKNOCK;
+		channel->mode.mode &= ~EXTCMODE_NOKNOCK;
 
 	return 0;
 }
 
-int noknock_mode_allow(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+int noknock_mode_allow(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
-	if (!(channel->mode.mode & MODE_INVITEONLY))
+	if (!has_channel_mode(channel, 'i'))
 	{
 		if (checkt == EXCHK_ACCESS_ERR)
 		{
diff --git a/src/modules/chanmodes/nonickchange.c b/src/modules/chanmodes/nonickchange.c
@@ -26,12 +26,12 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +N",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NONICKCHANGE;
 
-#define IsNoNickChange(channel)    (channel->mode.extmode & EXTCMODE_NONICKCHANGE)
+#define IsNoNickChange(channel)    (channel->mode.mode & EXTCMODE_NONICKCHANGE)
 
 int nonickchange_check (Client *client, Channel *channel);
 
@@ -46,7 +46,7 @@ CmodeInfo req;
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'N';
+	req.letter = 'N';
 	req.is_ok = extcmode_default_requirehalfop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NONICKCHANGE);
 	
@@ -71,7 +71,7 @@ int nonickchange_check (Client *client, Channel *channel)
 {
 	if (!IsOper(client) && !IsULine(client)
 		&& IsNoNickChange(channel)
-		&& !is_chan_op(client, channel))
+		&& !check_channel_access(client, channel, "oaq"))
 	{
 		return HOOK_DENY;
 	}
diff --git a/src/modules/chanmodes/nonotice.c b/src/modules/chanmodes/nonotice.c
@@ -25,14 +25,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +T",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_NONOTICE;
 
-#define IsNoNotice(channel)    (channel->mode.extmode & EXTCMODE_NONOTICE)
+#define IsNoNotice(channel)    (channel->mode.mode & EXTCMODE_NONOTICE)
 
-int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
 
 MOD_TEST()
 {
@@ -45,7 +45,7 @@ MOD_INIT()
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'T';
+	req.letter = 'T';
 	req.is_ok = extcmode_default_requirechop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_NONOTICE);
 	
@@ -65,14 +65,14 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
 
 	if ((sendtype == SEND_TYPE_NOTICE) &&
 	    IsNoNotice(channel) &&
-	    (!lp || !(lp->flags & (CHFL_CHANOP | CHFL_CHANOWNER | CHFL_CHANADMIN))))
+	    !check_channel_access_membership(lp, "oaq"))
 	{
 		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
 		{
diff --git a/src/modules/chanmodes/operonly.c b/src/modules/chanmodes/operonly.c
@@ -27,15 +27,15 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +O",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_OPERONLY;
 
-int operonly_require_oper(Client *client, Channel *channel, char mode, char *para, int checkt, int what);
-int operonly_check (Client *client, Channel *channel, char *key, char *parv[]);
-int operonly_topic_allow (Client *client, Channel *channel);
-int operonly_check_ban(Client *client, Channel *channel);
+int operonly_require_oper(Client *client, Channel *channel, char mode, const char *para, int checkt, int what);
+int operonly_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
+int operonly_view_topic_outside_channel(Client *client, Channel *channel);
+int operonly_oper_invite_ban(Client *client, Channel *channel);
 
 MOD_TEST()
 {
@@ -48,13 +48,13 @@ CmodeInfo req;
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'O';
+	req.letter = 'O';
 	req.is_ok = operonly_require_oper;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_OPERONLY);
 	
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, operonly_check);
-	HookAdd(modinfo->handle, HOOKTYPE_OPER_INVITE_BAN, 0, operonly_check_ban);
-	HookAdd(modinfo->handle, HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL, 0, operonly_topic_allow);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, operonly_can_join);
+	HookAdd(modinfo->handle, HOOKTYPE_OPER_INVITE_BAN, 0, operonly_oper_invite_ban);
+	HookAdd(modinfo->handle, HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL, 0, operonly_view_topic_outside_channel);
 
 	
 	MARK_AS_OFFICIAL_MODULE(modinfo);
@@ -71,31 +71,34 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int operonly_check (Client *client, Channel *channel, char *key, char *parv[])
+int operonly_can_join(Client *client, Channel *channel, const char *key, char **errmsg)
 {
-	if ((channel->mode.extmode & EXTCMODE_OPERONLY) && !ValidatePermissionsForPath("channel:operonly:join",client,NULL,channel,NULL))
+	if ((channel->mode.mode & EXTCMODE_OPERONLY) && !ValidatePermissionsForPath("channel:operonly:join",client,NULL,channel,NULL))
+	{
+		*errmsg = STR_ERR_OPERONLY;
 		return ERR_OPERONLY;
+	}
 	return 0;
 }
 
-int operonly_check_ban(Client *client, Channel *channel)
+int operonly_oper_invite_ban(Client *client, Channel *channel)
 {
-	 if ((channel->mode.extmode & EXTCMODE_OPERONLY) &&
+	 if ((channel->mode.mode & EXTCMODE_OPERONLY) &&
 		    !ValidatePermissionsForPath("channel:operonly:ban",client,NULL,NULL,NULL))
 		 return HOOK_DENY;
 
 	 return HOOK_CONTINUE;
 }
 
-int operonly_topic_allow (Client *client, Channel *channel)
+int operonly_view_topic_outside_channel(Client *client, Channel *channel)
 {
-	if (channel->mode.extmode & EXTCMODE_OPERONLY && !ValidatePermissionsForPath("channel:operonly:topic",client,NULL,channel,NULL))
+	if (channel->mode.mode & EXTCMODE_OPERONLY && !ValidatePermissionsForPath("channel:operonly:topic",client,NULL,channel,NULL))
 		return HOOK_DENY;
 
 	return HOOK_CONTINUE;
 }
 
-int operonly_require_oper(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+int operonly_require_oper(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
 	if (!MyUser(client) || ValidatePermissionsForPath("channel:operonly:set",client,NULL,channel,NULL))
 		return EX_ALLOW;
diff --git a/src/modules/chanmodes/permanent.c b/src/modules/chanmodes/permanent.c
@@ -25,20 +25,20 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Permanent channel mode (+P)", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 static Cmode_t EXTMODE_PERMANENT = 0L;
 
 static int permanent_channel_destroy(Channel *channel, int *should_destroy)
 {
-	if (channel->mode.extmode & EXTMODE_PERMANENT)
+	if (channel->mode.mode & EXTMODE_PERMANENT)
 		*should_destroy = 0;
 	
 	return 0;
 }
 
-static int permanent_is_ok(Client *client, Channel *channel, char mode, char *para, int checkt, int what)
+static int permanent_is_ok(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
 {
 	if (!IsOper(client))
 	{
@@ -51,14 +51,17 @@ static int permanent_is_ok(Client *client, Channel *channel, char mode, char *pa
 	return EX_ALLOW;
 }
 
-int permanent_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode)
+int permanent_chanmode(Client *client, Channel *channel, MessageTag *mtags, const char *modebuf, const char *parabuf, time_t sendts, int samode, int *destroy_channel)
 {
 	if (samode == -1)
 		return 0; /* SJOIN server-sync, already has its own way of destroying the channel */
 
 	/* Destroy the channel if it was set '(SA)MODE #chan -P' with nobody in it (#4442) */
-	if (!(channel->mode.extmode & EXTMODE_PERMANENT) && (channel->users <= 0))
+	if (!(channel->mode.mode & EXTMODE_PERMANENT) && (channel->users <= 0))
+	{
 		sub1_from_channel(channel);
+		*destroy_channel = 1;
+	}
 	
 	return 0;
 }
@@ -72,7 +75,7 @@ CmodeInfo req;
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'P';
+	req.letter = 'P';
 	req.is_ok = permanent_is_ok;
 	CmodeAdd(modinfo->handle, req, &EXTMODE_PERMANENT);
 
diff --git a/src/modules/chanmodes/private.c b/src/modules/chanmodes/private.c
@@ -0,0 +1,73 @@
+/*
+ * Channel Mode +p
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/private",
+	"6.0",
+	"Channel Mode +p",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_PRIVATE;
+
+#define IsPrivate(channel)    (channel->mode.mode & EXTCMODE_PRIVATE)
+
+int private_modechar_add(Channel *channel, int modechar);
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 'p';
+	req.is_ok = extcmode_default_requirehalfop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_PRIVATE);
+
+	HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_ADD, 0, private_modechar_add);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This clears channel mode +p when +s gets set */
+int private_modechar_add(Channel *channel, int modechar)
+{
+	if (modechar == 's')
+	{
+		channel->mode.mode &= ~EXTCMODE_PRIVATE;
+	}
+	return 0;
+}
diff --git a/src/modules/chanmodes/regonly.c b/src/modules/chanmodes/regonly.c
@@ -26,14 +26,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +R",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_REGONLY;
 
-#define IsRegOnly(channel)    (channel->mode.extmode & EXTCMODE_REGONLY)
+#define IsRegOnly(channel)    (channel->mode.mode & EXTCMODE_REGONLY)
 
-int regonly_check (Client *client, Channel *channel, char *key, char *parv[]);
+int regonly_check(Client *client, Channel *channel, const char *key, char **errmsg);
 
 
 MOD_TEST()
@@ -47,7 +47,7 @@ CmodeInfo req;
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'R';
+	req.letter = 'R';
 	req.is_ok = extcmode_default_requirehalfop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_REGONLY);
 	
@@ -68,10 +68,13 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int regonly_check (Client *client, Channel *channel, char *key, char *parv[])
+int regonly_check (Client *client, Channel *channel, const char *key, char **errmsg)
 {
 	if (IsRegOnly(channel) && !IsLoggedIn(client))
+	{
+		*errmsg = STR_ERR_NEEDREGGEDNICK;
 		return ERR_NEEDREGGEDNICK;
+	}
 	return 0;
 }
 
diff --git a/src/modules/chanmodes/regonlyspeak.c b/src/modules/chanmodes/regonlyspeak.c
@@ -26,16 +26,16 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +M",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_REGONLYSPEAK;
 static char errMsg[2048];
 
-#define IsRegOnlySpeak(channel)    (channel->mode.extmode & EXTCMODE_REGONLYSPEAK)
+#define IsRegOnlySpeak(channel)    (channel->mode.mode & EXTCMODE_REGONLYSPEAK)
 
-int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-char *regonlyspeak_part_message (Client *client, Channel *channel, char *comment);
+int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+const char *regonlyspeak_part_message (Client *client, Channel *channel, const char *comment);
 
 MOD_TEST()
 {
@@ -48,12 +48,12 @@ MOD_INIT()
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'M';
+	req.letter = 'M';
 	req.is_ok = extcmode_default_requirehalfop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_REGONLYSPEAK);
 	
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, regonlyspeak_can_send_to_channel);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, regonlyspeak_part_message);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, regonlyspeak_part_message);
 
 	
 	MARK_AS_OFFICIAL_MODULE(modinfo);
@@ -70,7 +70,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-char *regonlyspeak_part_message (Client *client, Channel *channel, char *comment)
+const char *regonlyspeak_part_message (Client *client, Channel *channel, const char *comment)
 {
 	if (!comment)
 		return NULL;
@@ -81,15 +81,15 @@ char *regonlyspeak_part_message (Client *client, Channel *channel, char *comment
 	return comment;
 }
 
-int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
 
-	if (IsRegOnlySpeak(channel) && !op_can_override("channel:override:message:regonlyspeak",client,channel,NULL) && !IsLoggedIn(client) &&
-		    (!lp
-		    || !(lp->flags & (CHFL_CHANOP | CHFL_VOICE | CHFL_CHANOWNER |
-		    CHFL_HALFOP | CHFL_CHANADMIN))))
+	if (IsRegOnlySpeak(channel) &&
+	    !op_can_override("channel:override:message:regonlyspeak",client,channel,NULL) &&
+	    !IsLoggedIn(client) &&
+	    !check_channel_access_membership(lp, "vhoaq"))
 	{
 		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
 		{
diff --git a/src/modules/chanmodes/secret.c b/src/modules/chanmodes/secret.c
@@ -0,0 +1,73 @@
+/*
+ * Channel Mode +s
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/secret",
+	"6.0",
+	"Channel Mode +s",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_SECRET;
+
+#define IsSecret(channel)    (channel->mode.mode & EXTCMODE_SECRET)
+
+int secret_modechar_add(Channel *channel, int modechar);
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 's';
+	req.is_ok = extcmode_default_requirehalfop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_SECRET);
+
+	HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_ADD, 0, secret_modechar_add);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This clears channel mode +s when +p gets set */
+int secret_modechar_add(Channel *channel, int modechar)
+{
+	if (modechar == 'p')
+	{
+		channel->mode.mode &= ~EXTCMODE_SECRET;
+	}
+	return 0;
+}
diff --git a/src/modules/chanmodes/secureonly.c b/src/modules/chanmodes/secureonly.c
@@ -25,18 +25,18 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +z",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_SECUREONLY;
 
-#define IsSecureOnly(channel)    (channel->mode.extmode & EXTCMODE_SECUREONLY)
+#define IsSecureOnly(channel)    (channel->mode.mode & EXTCMODE_SECUREONLY)
 
-int secureonly_check_join(Client *client, Channel *channel, char *key, char *parv[]);
+int secureonly_check_join(Client *client, Channel *channel, const char *key, char **errmsg);
 int secureonly_channel_sync (Channel *channel, int merge, int removetheirs, int nomode);
 int secureonly_check_secure(Channel *channel);
 int secureonly_check_sajoin(Client *target, Channel *channel, Client *requester);
-int secureonly_specialcheck(Client *client, Channel *channel, char *parv[]);
+int secureonly_pre_local_join(Client *client, Channel *channel, const char *key);
 
 MOD_TEST()
 {
@@ -49,11 +49,11 @@ MOD_INIT()
 
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'z';
+	req.letter = 'z';
 	req.is_ok = extcmode_default_requirechop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_SECUREONLY);
 
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_JOIN, 0, secureonly_specialcheck);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_JOIN, 0, secureonly_pre_local_join);
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, secureonly_check_join);
 	HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_SYNCED, 0, secureonly_channel_sync);
 	HookAdd(modinfo->handle, HOOKTYPE_IS_CHANNEL_SECURE, 0, secureonly_check_secure);
@@ -95,39 +95,39 @@ static int secureonly_kick_insecure_users(Channel *channel)
 		client = member->client;
 		if (MyUser(client) && !IsSecureConnect(client) && !IsULine(client))
 		{
-			int prefix = 0;
+			char *prefix = NULL;
 			MessageTag *mtags = NULL;
 
 			if (invisible_user_in_channel(client, channel))
 			{
 				/* Send only to chanops */
-				prefix = CHFL_HALFOP|CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN;
+				prefix = "ho";
 			}
 
 			new_message(&me, NULL, &mtags);
 
-			RunHook6(HOOKTYPE_LOCAL_KICK, &me, &me, client, channel, mtags, comment);
+			RunHook(HOOKTYPE_LOCAL_KICK, &me, &me, client, channel, mtags, comment);
 
 			sendto_channel(channel, &me, client,
 				       prefix, 0,
 				       SEND_LOCAL, mtags,
 				       ":%s KICK %s %s :%s",
-				       me.name, channel->chname, client->name, comment);
+				       me.name, channel->name, client->name, comment);
 
-			sendto_prefix_one(client, &me, mtags, ":%s KICK %s %s :%s", me.name, channel->chname, client->name, comment);
+			sendto_prefix_one(client, &me, mtags, ":%s KICK %s %s :%s", me.name, channel->name, client->name, comment);
 
-			sendto_server(NULL, 0, 0, mtags, ":%s KICK %s %s :%s", me.id, channel->chname, client->id, comment);
+			sendto_server(NULL, 0, 0, mtags, ":%s KICK %s %s :%s", me.id, channel->name, client->id, comment);
 
 			free_message_tags(mtags);
 
-			if (remove_user_from_channel(client, channel) == 1)
+			if (remove_user_from_channel(client, channel, 0) == 1)
 				return 1; /* channel was destroyed */
 		}
 	}
 	return 0;
 }
 
-int secureonly_check_join(Client *client, Channel *channel, char *key, char *parv[])
+int secureonly_check_join(Client *client, Channel *channel, const char *key, char **errmsg)
 {
 	Link *lp;
 
@@ -141,6 +141,7 @@ int secureonly_check_join(Client *client, Channel *channel, char *key, char *par
 			if (is_invited(client, channel))
 				return HOOK_CONTINUE;
 		}
+		*errmsg = STR_ERR_SECUREONLYCHAN;
 		return ERR_SECUREONLYCHAN;
 	}
 	return 0;
@@ -167,8 +168,8 @@ int secureonly_check_sajoin(Client *target, Channel *channel, Client *requester)
 {
 	if (IsSecureOnly(channel) && !IsSecure(target))
 	{
-		sendnotice(requester, "You cannot SAJOIN %s to %s because the channel is +z and the user is not connected via SSL/TLS",
-			target->name, channel->chname);
+		sendnotice(requester, "You cannot SAJOIN %s to %s because the channel is +z and the user is not connected via TLS",
+			target->name, channel->name);
 		return HOOK_DENY;
 	}
 
@@ -178,11 +179,11 @@ int secureonly_check_sajoin(Client *target, Channel *channel, Client *requester)
 /* Special check for +z in set::modes-on-join. Needs to be done early.
  * Perhaps one day this will be properly handled in the core so this can be removed.
  */
-int secureonly_specialcheck(Client *client, Channel *channel, char *parv[])
+int secureonly_pre_local_join(Client *client, Channel *channel, const char *key)
 {
-	if ((channel->users == 0) && (iConf.modes_on_join.extmodes & EXTCMODE_SECUREONLY) && !IsSecure(client) && !IsOper(client))
+	if ((channel->users == 0) && (MODES_ON_JOIN & EXTCMODE_SECUREONLY) && !IsSecure(client) && !IsOper(client))
 	{
-		sendnumeric(client, ERR_SECUREONLYCHAN, channel->chname);
+		sendnumeric(client, ERR_SECUREONLYCHAN, channel->name);
 		return HOOK_DENY;
 	}
 	return HOOK_CONTINUE;
diff --git a/src/modules/chanmodes/stripcolor.c b/src/modules/chanmodes/stripcolor.c
@@ -27,16 +27,16 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Channel Mode +S",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 Cmode_t EXTCMODE_STRIPCOLOR;
 
-#define IsStripColor(channel)    (channel->mode.extmode & EXTCMODE_STRIPCOLOR)
+#define IsStripColor(channel)    (channel->mode.mode & EXTCMODE_STRIPCOLOR)
 
-int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-char *stripcolor_prelocalpart(Client *client, Channel *channel, char *comment);
-char *stripcolor_prelocalquit(Client *client, char *comment);
+int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+const char *stripcolor_prelocalpart(Client *client, Channel *channel, const char *comment);
+const char *stripcolor_prelocalquit(Client *client, const char *comment);
 
 MOD_TEST()
 {
@@ -50,14 +50,14 @@ CmodeInfo req;
 	/* Channel mode */
 	memset(&req, 0, sizeof(req));
 	req.paracount = 0;
-	req.flag = 'S';
+	req.letter = 'S';
 	req.is_ok = extcmode_default_requirechop;
 	CmodeAdd(modinfo->handle, req, &EXTCMODE_STRIPCOLOR);
 	
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, stripcolor_can_send_to_channel);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, stripcolor_prelocalpart);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT_CHAN, 0, stripcolor_prelocalpart);
-	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, stripcolor_prelocalquit);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, stripcolor_prelocalpart);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT_CHAN, 0, stripcolor_prelocalpart);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, stripcolor_prelocalquit);
 	
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
@@ -73,7 +73,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
@@ -95,7 +95,7 @@ int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership 
 	return HOOK_CONTINUE;
 }
 
-char *stripcolor_prelocalpart(Client *client, Channel *channel, char *comment)
+const char *stripcolor_prelocalpart(Client *client, Channel *channel, const char *comment)
 {
 	if (!comment)
 		return NULL;
@@ -118,7 +118,7 @@ static int IsAnyChannelStripColor(Client *client)
 }
 
 
-char *stripcolor_prelocalquit(Client *client, char *comment)
+const char *stripcolor_prelocalquit(Client *client, const char *comment)
 {
 	if (!comment)
 		return NULL;
diff --git a/src/modules/chanmodes/topiclimit.c b/src/modules/chanmodes/topiclimit.c
@@ -0,0 +1,82 @@
+/*
+ * Channel Mode +t
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/topiclimit",
+	"6.0",
+	"Channel Mode +t",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+Cmode_t EXTCMODE_TOPIC_LIMIT;
+
+/* Forward declarations */
+int topiclimit_can_set_topic(Client *client, Channel *channel, const char *topic, const char **errmsg);
+
+/* Macros */
+#define IsTopicLimit(channel)    (channel->mode.mode & EXTCMODE_TOPIC_LIMIT)
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 't';
+	req.is_ok = extcmode_default_requirehalfop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_TOPIC_LIMIT);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SET_TOPIC, 0, topiclimit_can_set_topic);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int topiclimit_can_set_topic(Client *client, Channel *channel, const char *topic, const char **errmsg)
+{
+	static char errmsg_buf[NICKLEN+256];
+
+	if (has_channel_mode(channel, 't') &&
+	    !check_channel_access(client, channel, "hoaq") &&
+	    !IsULine(client) &&
+	    !IsServer(client))
+	{
+		buildnumeric(errmsg_buf, sizeof(errmsg_buf), client, ERR_CHANOPRIVSNEEDED, channel->name);
+		*errmsg = errmsg_buf;
+		return EX_DENY;
+	}
+
+	return EX_ALLOW;
+}
diff --git a/src/modules/chanmodes/voice.c b/src/modules/chanmodes/voice.c
@@ -0,0 +1,88 @@
+/*
+ * Channel Mode +v
+ * (C) Copyright 2021 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
+  = {
+	"chanmodes/voice",
+	"6.0",
+	"Channel Mode +v",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+int cmode_voice_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
+
+MOD_INIT()
+{
+	CmodeInfo creq;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmode_voice_is_ok;
+	creq.letter = 'v';
+	creq.prefix = '+';
+	creq.sjoin_prefix = '+';
+	creq.rank = RANK_VOICE;
+	creq.unset_with_param = 1;
+	creq.type = CMODE_MEMBER;
+	CmodeAdd(modinfo->handle, creq, NULL);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int cmode_voice_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		Client *target = find_user(param, NULL);
+
+		if ((what == MODE_DEL) && (target == client))
+		{
+			/* User may always remove their own modes */
+			return EX_ALLOW;
+		}
+		if (check_channel_access(client, channel, "hoaq"))
+		{
+			/* Permitted for +hoaq */
+			return EX_ALLOW;
+		}
+		if (type == EXCHK_ACCESS_ERR)
+			sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return EX_DENY;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
@@ -11,7 +11,7 @@ ModuleHeader MOD_HEADER = {
 	"1.0",
 	"Stores and retrieves channel settings for persistent (+P) channels",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Database version */
@@ -27,15 +27,14 @@ ModuleHeader MOD_HEADER = {
 #define MAGIC_CHANNEL_START	0x11111111
 #define MAGIC_CHANNEL_END	0x22222222
 
-#ifdef DEBUGMODE
- #define BENCHMARK
-#endif
+// #undef BENCHMARK
 
 #define WARN_WRITE_ERROR(fname) \
 	do { \
-		sendto_realops_and_log("[channeldb] Error writing to temporary database file " \
-		                       "'%s': %s (DATABASE NOT SAVED)", \
-		                       fname, unrealdb_get_error_string()); \
+		unreal_log(ULOG_ERROR, "channeldb", "CHANNELDB_FILE_WRITE_ERROR", NULL, \
+			   "[channeldb] 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) \
@@ -72,7 +71,6 @@ EVENT(write_channeldb_evt);
 int write_channeldb(void);
 int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel);
 int read_channeldb(void);
-static void set_channel_mode(Channel *channel, char *modes, char *parameters);
 
 /* Global variables */
 static uint32_t channeldb_version = CHANNELDB_VERSION;
@@ -132,7 +130,7 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
-	if (loop.ircd_terminating)
+	if (loop.terminating)
 		write_channeldb();
 	freecfg(&test);
 	freecfg(&cfg);
@@ -168,34 +166,34 @@ int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!ce || strcmp(ce->ce_varname, "channeldb"))
+	if (!ce || strcmp(ce->name, "channeldb"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
-			config_error("%s:%i: blank set::channeldb::%s without value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%i: blank set::channeldb::%s without value", cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		} else
-		if (!strcmp(cep->ce_varname, "database"))
+		if (!strcmp(cep->name, "database"))
 		{
-			convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
-			safe_strdup(test.database, cep->ce_vardata);
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "db-secret"))
+		if (!strcmp(cep->name, "db-secret"))
 		{
-			char *err;
-			if ((err = unrealdb_test_secret(cep->ce_vardata)))
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
 			{
-				config_error("%s:%i: set::channeldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+				config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
 				errors++;
 				continue;
 			}
-			safe_strdup(test.db_secret, cep->ce_vardata);
+			safe_strdup(test.db_secret, cep->value);
 		} else
 		{
-			config_error("%s:%i: unknown directive set::channeldb::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%i: unknown directive set::channeldb::%s", cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
 	}
@@ -227,15 +225,15 @@ int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!ce || strcmp(ce->ce_varname, "channeldb"))
+	if (!ce || strcmp(ce->name, "channeldb"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "database"))
-			safe_strdup(cfg.database, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "db-secret"))
-			safe_strdup(cfg.db_secret, cep->ce_vardata);
+		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;
 }
@@ -300,7 +298,7 @@ int write_channeldb(void)
 #endif
 	if (rename(tmpfname, cfg.database) < 0)
 	{
-		sendto_realops_and_log("[channeldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
+		config_error("[channeldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
 		return 0;
 	}
 #ifdef BENCHMARK
@@ -333,9 +331,11 @@ int write_listmode(UnrealDB *db, const char *tmpfname, Ban *lst)
 
 int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel)
 {
+	char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
 	W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_START));
 	/* Channel name */
-	W_SAFE(unrealdb_write_str(db, channel->chname));
+	W_SAFE(unrealdb_write_str(db, channel->name));
 	/* Channel creation time */
 	W_SAFE(unrealdb_write_int64(db, channel->creationtime));
 	/* Topic (topic, setby, seton) */
@@ -343,7 +343,7 @@ int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel)
 	W_SAFE(unrealdb_write_str(db, channel->topic_nick));
 	W_SAFE(unrealdb_write_int64(db, channel->topic_time));
 	/* Basic channel modes (eg: +sntkl key 55) */
-	channel_modes(&me, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel);
+	channel_modes(&me, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
 	W_SAFE(unrealdb_write_str(db, modebuf));
 	W_SAFE(unrealdb_write_str(db, parabuf));
 	/* Mode lock */
@@ -506,8 +506,11 @@ int read_channeldb(void)
 		R_SAFE(unrealdb_read_str(db, &modes2));
 		R_SAFE(unrealdb_read_str(db, &mode_lock));
 		/* If we got this far, we can create/initialize the channel with the above */
-		channel = get_channel(&me, chname, CREATE);
-		channel->creationtime = creationtime;
+		channel = make_channel(chname);
+		if (IsInvalidChannelTS(creationtime))
+			channel->creationtime = TStime();
+		else
+			channel->creationtime = creationtime;
 		safe_strdup(channel->topic, topic);
 		safe_strdup(channel->topic_nick, topic_nick);
 		channel->topic_time = topic_time;
@@ -529,37 +532,14 @@ int read_channeldb(void)
 	unrealdb_close(db);
 
 	if (added)
-		sendto_realops_and_log("[channeldb] Added %d persistent channels (+P)", added);
+		config_status("[channeldb] Added %d persistent channels (+P)", added);
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "[channeldb] Benchmark: LOAD DB: %ld microseconds",
-		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
+	unreal_log(ULOG_DEBUG, "channeldb", "CHANNELDB_BENCHMARK", NULL,
+	           "[channeldb] 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 FreeChannelEntry
 #undef R_SAFE
-
-static void set_channel_mode(Channel *channel, char *modes, char *parameters)
-{
-	char buf[512];
-	char *p, *param;
-	int myparc = 1, i;
-	char *myparv[64];
-
-	memset(&myparv, 0, sizeof(myparv));
-	myparv[0] = raw_strdup(modes);
-
-	strlcpy(buf, parameters, sizeof(buf));
-	for (param = strtoken(&p, buf, " "); param; param = strtoken(&p, NULL, " "))
-		myparv[myparc++] = raw_strdup(param);
-	myparv[myparc] = NULL;
-
-	SetULine(&me); // hack for crash.. set ulined so no access checks.
-	do_mode(channel, &me, NULL, myparc, myparv, 0, 0);
-	ClearULine(&me); // and clear it again..
-
-	for (i = 0; i < myparc; i++)
-		safe_free(myparv[i]);
-}
-// FIXME: move above function to m_mode and make efunc, available for all modules anyway
diff --git a/src/modules/charsys.c b/src/modules/charsys.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"Character System (set::allowed-nickchars)", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* NOTE: it is guaranteed that char is unsigned by compiling options
@@ -73,6 +73,7 @@ char langsinuse[4096];
 #define LANGAV_CYRILLIC_UTF8		0x008000 /* UTF8: cyrillic script */
 #define LANGAV_GREEK_UTF8		0x010000 /* UTF8: greek script */
 #define LANGAV_HEBREW_UTF8		0x020000 /* UTF8: hebrew script */
+#define LANGAV_ARABIC_UTF8		0x040000 /* UTF8: arabic script */
 typedef struct LangList LangList;
 struct LangList
 {
@@ -83,7 +84,7 @@ struct LangList
 
 /* MUST be alphabetized (first column) */
 static LangList langlist[] = {
-/*	{ "arabic",       "ara", LANGAV_ASCII|LANGAV_ISO8859_6 }, -- TODO: check if this has issues first! */
+	{ "arabic-utf8", "ara-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_ARABIC_UTF8 },
 	{ "belarussian-utf8", "blr-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
 	{ "belarussian-w1251", "blr", LANGAV_ASCII|LANGAV_W1251 },
 	{ "catalan",      "cat", LANGAV_ASCII|LANGAV_LATIN1 },
@@ -189,7 +190,7 @@ MOD_TEST()
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	EfunctionAdd(modinfo->handle, EFUNC_DO_NICK_NAME, _do_nick_name);
 	EfunctionAdd(modinfo->handle, EFUNC_DO_REMOTE_NICK_NAME, _do_remote_nick_name);
-	EfunctionAddPChar(modinfo->handle, EFUNC_CHARSYS_GET_CURRENT_LANGUAGES, _charsys_get_current_languages);
+	EfunctionAddString(modinfo->handle, EFUNC_CHARSYS_GET_CURRENT_LANGUAGES, _charsys_get_current_languages);
 	charsys_reset();
 	charsys_reset_pretest();
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, charsys_config_test);
@@ -226,26 +227,26 @@ int charsys_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 
 	/* We are only interrested in set::allowed-nickchars... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "allowed-nickchars"))
+	if (!ce || !ce->name || strcmp(ce->name, "allowed-nickchars"))
 		return 0;
 
-	if (ce->ce_vardata)
+	if (ce->value)
 	{
 		config_error("%s:%i: set::allowed-nickchars: please use 'allowed-nickchars { name; };' "
 					 "and not 'allowed-nickchars name;'",
-					 ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+					 ce->file->filename, ce->line_number);
 		/* Give up immediately. Don't bother the user with any other errors. */
 		errors++;
 		*errs = errors;
 		return -1;
 	}
 
-	for (cep = ce->ce_entries; cep; cep=cep->ce_next)
+	for (cep = ce->items; cep; cep=cep->next)
 	{
-		if (!charsys_test_language(cep->ce_varname))
+		if (!charsys_test_language(cep->name))
 		{
 			config_error("%s:%i: set::allowed-nickchars: Unknown (sub)language '%s'",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum, cep->ce_varname);
+				ce->file->filename, ce->line_number, cep->name);
 			errors++;
 		}
 	}
@@ -262,11 +263,11 @@ int charsys_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 
 	/* We are only interrested in set::allowed-nickchars... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "allowed-nickchars"))
+	if (!ce || !ce->name || strcmp(ce->name, "allowed-nickchars"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-		charsys_add_language(cep->ce_varname);
+	for (cep = ce->items; cep; cep = cep->next)
+		charsys_add_language(cep->name);
 
 	return 1;
 }
@@ -313,6 +314,8 @@ int charsys_config_posttest(int *errs)
 	    x++;
 	if (x > 1)
 	{
+#if 0
+// I don't think this should be hard error, right? Some combinations may be problematic, but not all.
 		if (langav & LANGAV_LATIN_UTF8)
 		{
 			config_error("ERROR: set::allowed-nickchars: you cannot combine 'latin-utf8' with any other character set");
@@ -333,8 +336,13 @@ int charsys_config_posttest(int *errs)
 			config_error("ERROR: set::allowed-nickchars: you cannot combine 'hebrew-utf8' with any other character set");
 			errors++;
 		}
-		config_status("WARNING: set::allowed-nickchars: "
-		            "Mixing of charsets (eg: latin1+latin2) can cause display problems");
+		if (langav & LANGAV_ARABIC_UTF8)
+		{
+			config_error("ERROR: set::allowed-nickchars: you cannot combine 'arabic-utf8' with any other character set");
+			errors++;
+		}
+#endif
+		config_status("WARNING: set::allowed-nickchars: Mixing of charsets (eg: latin1+latin2) may cause display problems");
 	}
 
 	*errs = errors;
@@ -877,13 +885,6 @@ void charsys_add_language(char *name)
 		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
 		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbe);
 	}
-/*	if (latin1 || !strcmp(name, "arabic")) -- Since when is arabic considered latin(1)??? oh man...
-	{
-		char bytes[] = { 0xa0, 0xa4, 0xac, 0xad, 0xbb, 0xbf, 0x00 };
-		charsys_addallowed(bytes);
-		charsys_addallowed_range(0xc1, 0xda);
-		charsys_addallowed_range(0xe0, 0xf2);
-	} */
 
 	/* [LATIN2] and rest of [LATIN-UTF8] */
 	/* actually hungarian is a special case, include it in both w1250 and latin2 ;p */
@@ -1181,6 +1182,19 @@ void charsys_add_language(char *name)
 		charsys_addmultibyterange(0xc5, 0xc5, 0xaa, 0xab);
 		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
 	}
+
+	/* [ARABIC] */
+	if (latin_utf8 || !strcmp(name, "arabic-utf8"))
+	{
+		/* Supplied by Sensiva */
+		/*charsys_addallowed("اأإآءبتثجحخدذرزسشصضطظعغفقكلمنهؤةويىئ");*/
+		/*- From U+0621 to U+063A (Regex: [\u0621-\u063A])*/
+		/* 0xd8a1 - 0xd8ba */
+		charsys_addmultibyterange(0xd8, 0xd8, 0xa1, 0xba);
+		/*- From U+0641 to U+064A (Regex: [\u0641-\u064A])*/
+		/* 0xd981 - 0xd98a */
+		charsys_addmultibyterange(0xd9, 0xd9, 0x81, 0x8a);
+	}
 }
 
 /** This displays all the nick characters that are permitted */
@@ -1250,6 +1264,8 @@ char *charsys_group(int v)
 		return "Greek script";
 	if (v & LANGAV_HEBREW_UTF8)
 		return "Hebrew script";
+	if (v & LANGAV_ARABIC_UTF8)
+		return "Arabic script";
 
 	return "Other";
 }
diff --git a/src/modules/chathistory.c b/src/modules/chathistory.c
@@ -13,7 +13,15 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"IRCv3 CHATHISTORY command",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
+};
+
+/* Structs */
+typedef struct ChatHistoryTarget ChatHistoryTarget;
+struct ChatHistoryTarget {
+	ChatHistoryTarget *prev, *next;
+	char *datetime;
+	char *object;
 };
 
 /* Forward declarations */
@@ -22,7 +30,6 @@ CMD_FUNC(cmd_chathistory);
 /* Global variables */
 long CAP_CHATHISTORY = 0L;
 
-/* TODO: consider moving to config file */
 #define CHATHISTORY_LIMIT 50
 
 MOD_INIT()
@@ -49,13 +56,18 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int chathistory_token(char *str, char *token, char **store)
+int chathistory_token(const char *str, char *token, char **store)
 {
-	char *p = strchr(str, '=');
+	char request[BUFSIZE];
+	char *p;
+
+	strlcpy(request, str, sizeof(request));
+
+	p = strchr(request, '=');
 	if (!p)
 		return 0;
 	*p = '\0'; // frag
-	if (!strcmp(str, token))
+	if (!strcmp(request, token))
 	{
 		*p = '='; // restore
 		*store = strdup(p + 1); // can be \0
@@ -65,15 +77,68 @@ int chathistory_token(char *str, char *token, char **store)
 	return 0;
 }
 
-static int chathistory_targets_send_line(Client *client, HistoryResult *r, char *batchid)
+static void add_chathistory_target_list(ChatHistoryTarget *new, ChatHistoryTarget **list)
+{
+	ChatHistoryTarget *x, *last = NULL;
+
+	if (!*list)
+	{
+		/* We are the only item. Easy. */
+		*list = new;
+		return;
+	}
+
+	for (x = *list; x; x = x->next)
+	{
+		last = x;
+		if (strcmp(new->datetime, x->datetime) >= 0)
+			break;
+	}
+
+	if (x)
+	{
+		if (x->prev)
+		{
+			/* We will insert ourselves just before this item */
+			new->prev = x->prev;
+			new->next = x;
+			x->prev->next = new;
+			x->prev = new;
+		} else {
+			/* We are the new head */
+			*list = new;
+			new->next = x;
+			x->prev = new;
+		}
+	} else
+	{
+		/* We are the last item */
+		last->next = new;
+		new->prev = last;
+	}
+}
+
+static void add_chathistory_target(ChatHistoryTarget **list, HistoryResult *r)
 {
-	MessageTag *mtags = NULL;
 	MessageTag *m;
-	char *ts;
+	time_t ts;
+	char *datetime;
+	ChatHistoryTarget *e;
 
 	if (!r->log || !((m = find_mtag(r->log->mtags, "time"))) || !m->value)
-		return 0;
-	ts = m->value;
+		return;
+	datetime = m->value;
+
+	e = safe_alloc(sizeof(ChatHistoryTarget));
+	safe_strdup(e->datetime, datetime);
+	safe_strdup(e->object, r->object);
+	add_chathistory_target_list(e, list);
+}
+
+static void chathistory_targets_send_line(Client *client, ChatHistoryTarget *r, char *batchid)
+{
+	MessageTag *mtags = NULL;
+	MessageTag *m;
 
 	if (!BadPtr(batchid))
 	{
@@ -83,12 +148,10 @@ static int chathistory_targets_send_line(Client *client, HistoryResult *r, char 
 	}
 
 	sendto_one(client, mtags, ":%s CHATHISTORY TARGETS %s %s",
-		me.name, r->object, ts);
+		me.name, r->object, r->datetime);
 
 	if (mtags)
 		free_message_tags(mtags);
-
-	return 1;
 }
 
 void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
@@ -97,14 +160,9 @@ void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
 	HistoryResult *r;
 	char batch[BATCHLEN+1];
 	int sent = 0;
+	ChatHistoryTarget *targets = NULL, *targets_next;
 
-	batch[0] = '\0';
-	if (HasCapability(client, "batch"))
-	{
-		/* Start a new batch */
-		generate_batch_id(batch);
-		sendto_one(client, NULL, ":%s BATCH +%s draft/chathistory-targets", me.name, batch);
-	}
+	/* 1. Grab all information we need */
 
 	filter->cmd = HFC_BEFORE;
 	if (strcmp(filter->timestamp_a, filter->timestamp_b) < 0)
@@ -119,14 +177,32 @@ void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
 	for (mp = client->user->channel; mp; mp = mp->next)
 	{
 		Channel *channel = mp->channel;
-		r = history_request(channel->chname, filter);
-		if (r->log && chathistory_targets_send_line(client, r, batch))
+		r = history_request(channel->name, filter);
+		if (r)
 		{
-			if (++sent >= limit)
-				break; /* We are done */
+			add_chathistory_target(&targets, r);
+			free_history_result(r);
 		}
-		free_history_result(r);
-		r = NULL;
+	}
+
+	/* 2. Now send it to the client */
+
+	batch[0] = '\0';
+	if (HasCapability(client, "batch"))
+	{
+		/* Start a new batch */
+		generate_batch_id(batch);
+		sendto_one(client, NULL, ":%s BATCH +%s draft/chathistory-targets", me.name, batch);
+	}
+
+	for (; targets; targets = targets_next)
+	{
+		targets_next = targets->next;
+		if (++sent < limit)
+			chathistory_targets_send_line(client, targets, batch);
+		safe_free(targets->datetime);
+		safe_free(targets->object);
+		safe_free(targets);
 	}
 
 	/* End of batch */
@@ -160,7 +236,7 @@ CMD_FUNC(cmd_chathistory)
 		return;
 	}
 
-	if (!strcmp(parv[1], "TARGETS"))
+	if (!strcasecmp(parv[1], "TARGETS"))
 	{
 		Membership *mp;
 		int limit;
@@ -185,18 +261,39 @@ CMD_FUNC(cmd_chathistory)
 		goto end;
 	}
 
-	channel = find_channel(parv[2], NULL);
-	if (!channel || !IsMember(client, channel) || !has_channel_mode(channel, 'H'))
+	channel = find_channel(parv[2]);
+	if (!channel)
+	{
+		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_TARGET %s %s :Messages could not be retrieved, not an existing channel",
+			me.name, parv[1], parv[2]);
+		return;
+	}
+
+	if (!IsMember(client, channel))
 	{
-		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_TARGET %s %s :Messages could not be retrieved",
+		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_TARGET %s %s :Messages could not be retrieved, you are not a member",
 			me.name, parv[1], parv[2]);
 		return;
 	}
 
+	/* 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);
+		}
+		return;
+	}
+
 	filter = safe_alloc(sizeof(HistoryFilter));
 	/* Below this point, instead of 'return', use 'goto end', which takes care of the freeing of 'filter' and 'history' */
 
-	if (!strcmp(parv[1], "BEFORE"))
+	if (!strcasecmp(parv[1], "BEFORE"))
 	{
 		filter->cmd = HFC_BEFORE;
 		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
@@ -208,7 +305,7 @@ CMD_FUNC(cmd_chathistory)
 		}
 		filter->limit = atoi(parv[4]);
 	} else
-	if (!strcmp(parv[1], "AFTER"))
+	if (!strcasecmp(parv[1], "AFTER"))
 	{
 		filter->cmd = HFC_AFTER;
 		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
@@ -220,7 +317,7 @@ CMD_FUNC(cmd_chathistory)
 		}
 		filter->limit = atoi(parv[4]);
 	} else
-	if (!strcmp(parv[1], "LATEST"))
+	if (!strcasecmp(parv[1], "LATEST"))
 	{
 		filter->cmd = HFC_LATEST;
 		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
@@ -233,7 +330,7 @@ CMD_FUNC(cmd_chathistory)
 		}
 		filter->limit = atoi(parv[4]);
 	} else
-	if (!strcmp(parv[1], "AROUND"))
+	if (!strcasecmp(parv[1], "AROUND"))
 	{
 		filter->cmd = HFC_AROUND;
 		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
@@ -245,7 +342,7 @@ CMD_FUNC(cmd_chathistory)
 		}
 		filter->limit = atoi(parv[4]);
 	} else
-	if (!strcmp(parv[1], "BETWEEN"))
+	if (!strcasecmp(parv[1], "BETWEEN"))
 	{
 		filter->cmd = HFC_BETWEEN;
 		if (BadPtr(parv[5]))
@@ -283,7 +380,7 @@ CMD_FUNC(cmd_chathistory)
 	if (filter->limit > CHATHISTORY_LIMIT)
 		filter->limit = CHATHISTORY_LIMIT;
 
-	if ((r = history_request(channel->chname, filter)))
+	if ((r = history_request(channel->name, filter)))
 		history_send_result(client, r);
 
 end:
diff --git a/src/modules/chghost.c b/src/modules/chghost.c
@@ -25,6 +25,10 @@
 #define MSG_CHGHOST 	"CHGHOST"
 
 CMD_FUNC(cmd_chghost);
+void _userhost_save_current(Client *client);
+void _userhost_changed(Client *client);
+
+long CAP_CHGHOST = 0L;
 
 ModuleHeader MOD_HEADER
   = {
@@ -32,13 +36,28 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"/chghost", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_SAVE_CURRENT, _userhost_save_current);
+	EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_CHANGED, _userhost_changed);
+	return MOD_SUCCESS;
+}
+
 MOD_INIT()
 {
+	ClientCapabilityInfo c;
+
 	CommandAdd(modinfo->handle, MSG_CHGHOST, cmd_chghost, MAXPARA, CMD_USER|CMD_SERVER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&c, 0, sizeof(c));
+	c.name = "chghost";
+	ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHGHOST);
+
 	return MOD_SUCCESS;
 }
 
@@ -53,6 +72,177 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;	
 }
 
+
+static char remember_nick[NICKLEN+1];
+static char remember_user[USERLEN+1];
+static char remember_host[HOSTLEN+1];
+
+/** Save current nick/user/host. Used later by userhost_changed(). */
+void _userhost_save_current(Client *client)
+{
+	strlcpy(remember_nick, client->name, sizeof(remember_nick));
+	strlcpy(remember_user, client->user->username, sizeof(remember_user));
+	strlcpy(remember_host, GetHost(client), sizeof(remember_host));
+}
+
+/** User/Host changed for user.
+ * Note that userhost_save_current() needs to be called before this
+ * to save the old username/hostname.
+ * This userhost_changed() function deals with notifying local clients
+ * about the user/host change by sending PART+JOIN+MODE if
+ * set::allow-userhost-change force-rejoin is in use,
+ * and it wills end "CAP chghost" to such capable clients.
+ * It will also deal with bumping fakelag for the user since a user/host
+ * change is costly, doesn't matter if it was self-induced or not.
+ *
+ * Please call this function for any user/host change by doing:
+ * userhost_save_current(client);
+ * << change username or hostname here >>
+ * userhost_changed(client);
+ */
+void _userhost_changed(Client *client)
+{
+	Membership *channels;
+	Member *lp;
+	Client *acptr;
+	int impact = 0;
+	char buf[512];
+	long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
+
+	if (strcmp(remember_nick, client->name))
+	{
+		unreal_log(ULOG_ERROR, "main", "BUG_USERHOST_CHANGED", client,
+		           "[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: $client\n"
+		           "Please report above bug on https://bugs.unrealircd.org/");
+		return; /* We cannot safely process this request anymore */
+	}
+
+	/* It's perfectly acceptable to call us even if the userhost didn't change. */
+	if (!strcmp(remember_user, client->user->username) && !strcmp(remember_host, GetHost(client)))
+		return; /* Nothing to do */
+
+	/* Most of the work is only necessary for set::allow-userhost-change force-rejoin */
+	if (UHOST_ALLOWED == UHALLOW_REJOIN)
+	{
+		/* Walk through all channels of this user.. */
+		for (channels = client->user->channel; channels; channels = channels->next)
+		{
+			Channel *channel = channels->channel;
+			char *modes;
+			char partbuf[512]; /* PART */
+			char joinbuf[512]; /* JOIN */
+			char exjoinbuf[512]; /* JOIN (for CAP extended-join) */
+			char modebuf[512]; /* MODE (if any) */
+			int chanops_only = invisible_user_in_channel(client, channel);
+
+			modebuf[0] = '\0';
+
+			/* If the user is banned, don't send any rejoins, it would only be annoying */
+			if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
+				continue;
+
+			/* Prepare buffers for PART, JOIN, MODE */
+			ircsnprintf(partbuf, sizeof(partbuf), ":%s!%s@%s PART %s :%s",
+						remember_nick, remember_user, remember_host,
+						channel->name,
+						"Changing host");
+
+			ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s",
+						client->name, client->user->username, GetHost(client), channel->name);
+
+			ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s",
+				client->name, client->user->username, GetHost(client), channel->name,
+				IsLoggedIn(client) ? client->user->account : "*",
+				client->info);
+
+			modes = get_chmodes_for_user(client, channels->member_modes);
+			if (!BadPtr(modes))
+				ircsnprintf(modebuf, sizeof(modebuf), ":%s MODE %s %s", me.name, channel->name, modes);
+
+			for (lp = channel->members; lp; lp = lp->next)
+			{
+				acptr = lp->client;
+
+				if (acptr == client)
+					continue; /* skip self */
+
+				if (!MyConnect(acptr))
+					continue; /* only locally connected clients */
+
+				if (chanops_only && !check_channel_access_member(lp, "hoaq"))
+					continue; /* skip non-ops if requested to (used for mode +D) */
+
+				if (HasCapabilityFast(acptr, CAP_CHGHOST))
+					continue; /* we notify 'CAP chghost' users in a different way, so don't send it here. */
+
+				impact++;
+
+				/* FIXME: if a client does not have the "chghost" cap then
+				 * here we will not generate a proper new message, probably
+				 * needs to be fixed... I skipped doing it for now.
+				 */
+				sendto_one(acptr, NULL, "%s", partbuf);
+
+				if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN))
+					sendto_one(acptr, NULL, "%s", exjoinbuf);
+				else
+					sendto_one(acptr, NULL, "%s", joinbuf);
+
+				if (*modebuf)
+					sendto_one(acptr, NULL, "%s", modebuf);
+			}
+		}
+	}
+
+	/* Now deal with "CAP chghost" clients.
+	 * This only needs to be sent one per "common channel".
+	 * This would normally call sendto_common_channels_local_butone() but the user already
+	 * has the new user/host.. so we do it here..
+	 */
+	ircsnprintf(buf, sizeof(buf), ":%s!%s@%s CHGHOST %s %s",
+	            remember_nick, remember_user, remember_host,
+	            client->user->username,
+	            GetHost(client));
+	current_serial++;
+	for (channels = client->user->channel; channels; channels = channels->next)
+	{
+		for (lp = channels->channel->members; lp; lp = lp->next)
+		{
+			acptr = lp->client;
+			if (MyUser(acptr) && HasCapabilityFast(acptr, CAP_CHGHOST) &&
+			    (acptr->local->serial != current_serial) && (client != acptr))
+			{
+				/* FIXME: send mtag */
+				sendto_one(acptr, NULL, "%s", buf);
+				acptr->local->serial = current_serial;
+			}
+		}
+	}
+	
+	RunHook(HOOKTYPE_USERHOST_CHANGED, client, remember_user, remember_host);
+
+	if (MyUser(client))
+	{
+		/* We take the liberty of sending the CHGHOST to the impacted user as
+		 * well. This makes things easy for client coders.
+		 * (Note that this cannot be merged with the for loop from 15 lines up
+		 *  since the user may not be in any channels)
+		 */
+		if (HasCapabilityFast(client, CAP_CHGHOST))
+			sendto_one(client, NULL, "%s", buf);
+
+		/* A userhost change always generates the following network traffic:
+		 * server to server traffic, CAP "chghost" notifications, and
+		 * possibly PART+JOIN+MODE if force-rejoin had work to do.
+		 * We give the user a penalty so they don't flood...
+		 */
+		if (impact)
+			add_fake_lag(client, 7000); /* Resulted in rejoins and such. */
+		else
+			add_fake_lag(client, 4000); /* No rejoins */
+	}
+}
+
 /* 
  * cmd_chghost - 12/07/1999 (two months after I made SETIDENT) - Stskeeps
  * :prefix CHGHOST <nick> <new hostname>
@@ -83,7 +273,7 @@ CMD_FUNC(cmd_chghost)
 		return;
 	}
 
-	if (!valid_host(parv[2]))
+	if (!valid_host(parv[2], 0))
 	{
 		sendnotice(client, "*** /ChgHost Error: A hostname may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them");
 		return;
@@ -95,7 +285,7 @@ CMD_FUNC(cmd_chghost)
 		return;
 	}
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
@@ -135,14 +325,11 @@ CMD_FUNC(cmd_chghost)
 
 	if (!IsULine(client))
 	{
-		sendto_snomask(SNO_EYES,
-		    "%s changed the virtual hostname of %s (%s@%s) to be %s",
-		    client->name, target->name, target->user->username,
-		    target->user->realhost, parv[2]);
-		/* Logging added by XeRXeS */
-		ircd_log(LOG_CHGCMDS,                                         
-			"CHGHOST: %s changed the virtual hostname of %s (%s@%s) to be %s",
-			client->name, target->name, target->user->username, target->user->realhost, parv[2]); 
+		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;
diff --git a/src/modules/chgident.c b/src/modules/chgident.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"/chgident", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -63,7 +63,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_chgident)
 {
 	Client *target;
-	char *s;
+	const char *s;
 	int legalident = 1;
 
 	if (!ValidatePermissionsForPath("client:set:ident",client,NULL,NULL,NULL))
@@ -102,7 +102,7 @@ CMD_FUNC(cmd_chgident)
 		return;
 	}
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
@@ -134,15 +134,11 @@ CMD_FUNC(cmd_chgident)
 	}
 	if (!IsULine(client))
 	{
-		sendto_snomask(SNO_EYES,
-		    "%s changed the virtual ident of %s (%s@%s) to be %s",
-		    client->name, target->name, target->user->username,
-		    GetHost(target), parv[2]);
-		/* Logging ability added by XeRXeS */
-		ircd_log(LOG_CHGCMDS,
-			"CHGIDENT: %s changed the virtual ident of %s (%s@%s) to be %s",
-			client->name, target->name, target->user->username,    
-			GetHost(target), parv[2]);
+		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",
diff --git a/src/modules/chgname.c b/src/modules/chgname.c
@@ -31,7 +31,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /chgname", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 
@@ -88,7 +88,7 @@ CMD_FUNC(cmd_chgname)
 		return;
 	}
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
@@ -97,15 +97,11 @@ CMD_FUNC(cmd_chgname)
 	/* Let's log this first */
 	if (!IsULine(client))
 	{
-		sendto_snomask(SNO_EYES,
-		    "%s changed the GECOS of %s (%s@%s) to be %s",
-		    client->name, target->name, target->user->username,
-		    GetHost(target), parv[2]);
-		/* Logging ability added by XeRXeS */
-		ircd_log(LOG_CHGCMDS,
-			"CHGNAME: %s changed the GECOS of %s (%s@%s) to be %s",
-			client->name, target->name, target->user->username,
-			GetHost(target), parv[2]);
+		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 */
diff --git a/src/modules/clienttagdeny.c b/src/modules/clienttagdeny.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER = {
 	"5.0",
 	"Informs clients about supported client tags",
 	"k4be",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 MOD_INIT(){
@@ -62,7 +62,7 @@ char *ct_isupport_param(void){
 	strlcpy(buf, "*", sizeof(buf));
 
 	for (m = mtaghandlers; m; m = m->next) {
-		if(!m->unloaded && m->name[0] == '+'){
+		if (!m->unloaded && m->name[0] == '+'){
 			strlcat(buf, ",-", sizeof(buf));
 			strlcat(buf, m->name+1, sizeof(buf));
 		}
diff --git a/src/modules/cloak.c b/src/modules/cloak.c
@@ -1,425 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/cloak.c
- *   (C) 2004 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"
-
-static char *cloak_key1 = NULL, *cloak_key2 = NULL, *cloak_key3 = NULL;
-static char cloak_checksum[64];
-static int nokeys = 1;
-
-int CLOAK_IP_ONLY = 0;
-
-#undef KEY1
-#undef KEY2
-#undef KEY3
-#define KEY1 cloak_key1
-#define KEY2 cloak_key2
-#define KEY3 cloak_key3
-
-char *hidehost(Client *client, char *host);
-char *cloakcsum();
-int cloak_config_test(ConfigFile *, ConfigEntry *, int, int *);
-int cloak_config_run(ConfigFile *, ConfigEntry *, int);
-int cloak_config_posttest(int *);
-
-static char *hidehost_ipv4(char *host);
-static char *hidehost_ipv6(char *host);
-static char *hidehost_normalhost(char *host);
-static inline unsigned int downsample(char *i);
-
-Callback *cloak = NULL, *cloak_csum = NULL;
-
-ModuleHeader MOD_HEADER = {
-	"cloak",
-	"1.0",
-	"Official cloaking module (md5)",
-	"UnrealIRCd Team",
-	"unrealircd-5",
-};
-
-MOD_TEST()
-{
-	cloak = CallbackAddPCharEx(modinfo->handle, CALLBACKTYPE_CLOAK_EX, hidehost);
-	if (!cloak)
-	{
-		config_error("cloak: Error while trying to install cloaking callback!");
-		return MOD_FAILED;
-	}
-	cloak_csum = CallbackAddPCharEx(modinfo->handle, CALLBACKTYPE_CLOAKKEYCSUM, cloakcsum);
-	if (!cloak_csum)
-	{
-		config_error("cloak: Error while trying to install cloaking checksum callback!");
-		return MOD_FAILED;
-	}
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cloak_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, cloak_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cloak_config_run);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	if (cloak_key1)
-	{
-		safe_free(cloak_key1);
-		safe_free(cloak_key2);
-		safe_free(cloak_key3);
-	}
-	return MOD_SUCCESS;
-}
-
-static int check_badrandomness(char *key)
-{
-char gotlowcase=0, gotupcase=0, gotdigit=0;
-char *p;
-	for (p=key; *p; p++)
-		if (islower(*p))
-			gotlowcase = 1;
-		else if (isupper(*p))
-			gotupcase = 1;
-		else if (isdigit(*p))
-			gotdigit = 1;
-
-	if (gotlowcase && gotupcase && gotdigit)
-		return 0;
-	return 1;
-}
-
-
-int cloak_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep;
-	int keycnt = 0, errors = 0;
-	char *keys[3];
-
-	if (type == CONFIG_SET)
-	{
-		/* set::cloak-method */
-		if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "cloak-method"))
-			return 0;
-
-		if (!ce->ce_vardata)
-		{
-			config_error("%s:%i: set::cloak-method: no method specified. The only supported methods are: 'ip' and 'host'",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-			errors++;
-		} else
-		if (strcmp(ce->ce_vardata, "ip") && strcmp(ce->ce_vardata, "host"))
-		{
-			config_error("%s:%i: set::cloak-method: unknown method '%s'. The only supported methods are: 'ip' and 'host'",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
-			errors++;
-		}
-
-		*errs = errors;
-		return errors ? -1 : 1;
-	}
-
-	if (type != CONFIG_CLOAKKEYS)
-		return 0;
-
-	nokeys = 0;
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		keycnt++;
-		/* TODO: check randomness */
-		if (check_badrandomness(cep->ce_varname))
-		{
-			config_error("%s:%i: set::cloak-keys: (key %d) Keys should be mixed a-zA-Z0-9, "
-			             "like \"a2JO6fh3Q6w4oN3s7\"", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, keycnt);
-			errors++;
-		}
-		if (strlen(cep->ce_varname) < 5)
-		{
-			config_error("%s:%i: set::cloak-keys: (key %d) Each key should be at least 5 characters",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, keycnt);
-			errors++;
-		}
-		if (strlen(cep->ce_varname) > 100)
-		{
-			config_error("%s:%i: set::cloak-keys: (key %d) Each key should be less than 100 characters",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, keycnt);
-			errors++;
-		}
-		if (keycnt < 4)
-			keys[keycnt-1] = cep->ce_varname;
-	}
-	if (keycnt != 3)
-	{
-		config_error("%s:%i: set::cloak-keys: we want 3 values, not %i!",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum, keycnt);
-		errors++;
-	}
-	if ((keycnt == 3) && (!strcmp(keys[0], keys[1]) || !strcmp(keys[1], keys[2])))
-	{
-		config_error("%s:%i: set::cloak-keys: All your 3 keys should be RANDOM, they should not be equal",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		errors++;
-	}
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int cloak_config_posttest(int *errs)
-{
-int errors = 0;
-
-	if (nokeys)
-	{
-		config_error("set::cloak-keys missing!");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int cloak_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-ConfigEntry *cep;
-char buf[512], result[16];
-
-	if (type == CONFIG_SET)
-	{
-		/* set::cloak-method */
-		if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "cloak-method"))
-			return 0;
-
-		if (!strcmp(ce->ce_vardata, "ip"))
-			CLOAK_IP_ONLY = 1;
-
-		return 0;
-	}
-
-	if (type != CONFIG_CLOAKKEYS)
-		return 0;
-
-	/* config test should ensure this goes fine... */
-	cep = ce->ce_entries;
-	safe_strdup(cloak_key1, cep->ce_varname);
-	cep = cep->ce_next;
-	safe_strdup(cloak_key2, cep->ce_varname);
-	cep = cep->ce_next;
-	safe_strdup(cloak_key3, cep->ce_varname);
-
-	/* Calculate checksum */
-	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY1, KEY2, KEY3);
-	DoMD5(result, buf, strlen(buf));
-	ircsnprintf(cloak_checksum, sizeof(cloak_checksum),
-		"MD5:%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",
-		(u_int)(result[0] & 0xf), (u_int)(result[0] >> 4),
-		(u_int)(result[1] & 0xf), (u_int)(result[1] >> 4),
-		(u_int)(result[2] & 0xf), (u_int)(result[2] >> 4),
-		(u_int)(result[3] & 0xf), (u_int)(result[3] >> 4),
-		(u_int)(result[4] & 0xf), (u_int)(result[4] >> 4),
-		(u_int)(result[5] & 0xf), (u_int)(result[5] >> 4),
-		(u_int)(result[6] & 0xf), (u_int)(result[6] >> 4),
-		(u_int)(result[7] & 0xf), (u_int)(result[7] >> 4),
-		(u_int)(result[8] & 0xf), (u_int)(result[8] >> 4),
-		(u_int)(result[9] & 0xf), (u_int)(result[9] >> 4),
-		(u_int)(result[10] & 0xf), (u_int)(result[10] >> 4),
-		(u_int)(result[11] & 0xf), (u_int)(result[11] >> 4),
-		(u_int)(result[12] & 0xf), (u_int)(result[12] >> 4),
-		(u_int)(result[13] & 0xf), (u_int)(result[13] >> 4),
-		(u_int)(result[14] & 0xf), (u_int)(result[14] >> 4),
-		(u_int)(result[15] & 0xf), (u_int)(result[15] >> 4));
-	return 1;
-}
-
-char *hidehost(Client *client, char *host)
-{
-	char *p;
-	int host_type;
-
-	if (CLOAK_IP_ONLY)
-		host = GetIP(client);
-
-	host_type = is_valid_ip(host);
-
-	if (host_type == 4)
-		return hidehost_ipv4(host);
-	else if (host_type == 6)
-		return hidehost_ipv6(host);
-	else
-		return hidehost_normalhost(host);
-}
-
-char *cloakcsum()
-{
-	return cloak_checksum;
-}
-
-/** Downsamples a 128bit result to 32bits (md5 -> unsigned int) */
-static inline unsigned int downsample(char *i)
-{
-char r[4];
-
-	r[0] = i[0] ^ i[1] ^ i[2] ^ i[3];
-	r[1] = i[4] ^ i[5] ^ i[6] ^ i[7];
-	r[2] = i[8] ^ i[9] ^ i[10] ^ i[11];
-	r[3] = i[12] ^ i[13] ^ i[14] ^ i[15];
-	
-	return ( ((unsigned int)r[0] << 24) +
-	         ((unsigned int)r[1] << 16) +
-	         ((unsigned int)r[2] << 8) +
-	         (unsigned int)r[3]);
-}
-
-static char *hidehost_ipv4(char *host)
-{
-unsigned int a, b, c, d;
-static char buf[512], res[512], res2[512], result[128];
-unsigned long n;
-unsigned int alpha, beta, gamma;
-
-	/* 
-	 * Output: ALPHA.BETA.GAMMA.IP
-	 * ALPHA is unique for a.b.c.d
-	 * BETA  is unique for a.b.c.*
-	 * GAMMA is unique for a.b.*
-	 * We cloak like this:
-	 * ALPHA = downsample(md5(md5("KEY2:A.B.C.D:KEY3")+"KEY1"));
-	 * BETA  = downsample(md5(md5("KEY3:A.B.C:KEY1")+"KEY2"));
-	 * GAMMA = downsample(md5(md5("KEY1:A.B:KEY2")+"KEY3"));
-	 */
-	sscanf(host, "%u.%u.%u.%u", &a, &b, &c, &d);
-
-	/* ALPHA... */
-	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY2, host, KEY3);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY1, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	alpha = downsample(res2);
-
-	/* BETA... */
-	ircsnprintf(buf, sizeof(buf), "%s:%d.%d.%d:%s", KEY3, a, b, c, KEY1);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY2, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	beta = downsample(res2);
-
-	/* GAMMA... */
-	ircsnprintf(buf, sizeof(buf), "%s:%d.%d:%s", KEY1, a, b, KEY2);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY3, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	gamma = downsample(res2);
-
-	ircsnprintf(result, sizeof(result), "%X.%X.%X.IP", alpha, beta, gamma);
-	return result;
-}
-
-static char *hidehost_ipv6(char *host)
-{
-unsigned int a, b, c, d, e, f, g, h;
-static char buf[512], res[512], res2[512], result[128];
-unsigned long n;
-unsigned int alpha, beta, gamma;
-
-	/* 
-	 * Output: ALPHA:BETA:GAMMA:IP
-	 * ALPHA is unique for a:b:c:d:e:f:g:h
-	 * BETA  is unique for a:b:c:d:e:f:g
-	 * GAMMA is unique for a:b:c:d
-	 * We cloak like this:
-	 * ALPHA = downsample(md5(md5("KEY2:a:b:c:d:e:f:g:h:KEY3")+"KEY1"));
-	 * BETA  = downsample(md5(md5("KEY3:a:b:c:d:e:f:g:KEY1")+"KEY2"));
-	 * GAMMA = downsample(md5(md5("KEY1:a:b:c:d:KEY2")+"KEY3"));
-	 */
-	sscanf(host, "%x:%x:%x:%x:%x:%x:%x:%x",
-		&a, &b, &c, &d, &e, &f, &g, &h);
-
-	/* ALPHA... */
-	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY2, host, KEY3);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY1, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	alpha = downsample(res2);
-
-	/* BETA... */
-	ircsnprintf(buf, sizeof(buf), "%s:%x:%x:%x:%x:%x:%x:%x:%s", KEY3, a, b, c, d, e, f, g, KEY1);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY2, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	beta = downsample(res2);
-
-	/* GAMMA... */
-	ircsnprintf(buf, sizeof(buf), "%s:%x:%x:%x:%x:%s", KEY1, a, b, c, d, KEY2);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY3, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	gamma = downsample(res2);
-
-	ircsnprintf(result, sizeof(result), "%X:%X:%X:IP", alpha, beta, gamma);
-	return result;
-}
-
-static char *hidehost_normalhost(char *host)
-{
-char *p;
-static char buf[512], res[512], res2[512], result[HOSTLEN+1];
-unsigned int alpha, n;
-
-	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY1, host, KEY2);
-	DoMD5(res, buf, strlen(buf));
-	strlcpy(res+16, KEY3, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
-	n = strlen(res+16) + 16;
-	DoMD5(res2, res, n);
-	alpha = downsample(res2);
-
-	for (p = host; *p; p++)
-		if (*p == '.')
-			if (isalpha(*(p + 1)))
-				break;
-
-	if (*p)
-	{
-		unsigned int len;
-		p++;
-		ircsnprintf(result, sizeof(result), "%s-%X.", hidden_host, alpha);
-		len = strlen(result) + strlen(p);
-		if (len <= HOSTLEN)
-			strlcat(result, p, sizeof(result));
-		else
-			strlcat(result, p + (len - HOSTLEN), sizeof(result));
-	} else
-		ircsnprintf(result, sizeof(result),  "%s-%X", hidden_host, alpha);
-
-	return result;
-}
diff --git a/src/modules/cloak_md5.c b/src/modules/cloak_md5.c
@@ -0,0 +1,422 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/cloak_md5.c
+ *   (C) 2004-2017 Bram Matthys and The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+static char *cloak_key1 = NULL, *cloak_key2 = NULL, *cloak_key3 = NULL;
+static char cloak_checksum[64];
+static int nokeys = 1;
+
+int CLOAK_IP_ONLY = 0;
+
+#undef KEY1
+#undef KEY2
+#undef KEY3
+#define KEY1 cloak_key1
+#define KEY2 cloak_key2
+#define KEY3 cloak_key3
+
+char *hidehost(Client *client, char *host);
+char *cloakcsum();
+int cloak_config_test(ConfigFile *, ConfigEntry *, int, int *);
+int cloak_config_run(ConfigFile *, ConfigEntry *, int);
+int cloak_config_posttest(int *);
+
+static char *hidehost_ipv4(char *host);
+static char *hidehost_ipv6(char *host);
+static char *hidehost_normalhost(char *host);
+static inline unsigned int downsample(char *i);
+
+ModuleHeader MOD_HEADER = {
+	"cloak_md5",
+	"1.0",
+	"Old cloaking module (MD5)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+MOD_TEST()
+{
+	if (!CallbackAddString(modinfo->handle, CALLBACKTYPE_CLOAK_KEY_CHECKSUM, cloakcsum))
+	{
+		unreal_log(ULOG_ERROR, "config", "CLOAK_MODULE_DUPLICATE", NULL,
+		           "cloak_md5: Error while trying to install callback.\n"
+		           "Maybe you have multiple cloaking modules loaded? You can only load one!");
+		return MOD_FAILED;
+	}
+	if (!CallbackAddString(modinfo->handle, CALLBACKTYPE_CLOAK_EX, hidehost))
+	{
+		config_error("cloak_md5: Error while trying to install cloaking callback!");
+		return MOD_FAILED;
+	}
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cloak_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, cloak_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cloak_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (cloak_key1)
+	{
+		safe_free(cloak_key1);
+		safe_free(cloak_key2);
+		safe_free(cloak_key3);
+	}
+	return MOD_SUCCESS;
+}
+
+static int check_badrandomness(char *key)
+{
+char gotlowcase=0, gotupcase=0, gotdigit=0;
+char *p;
+	for (p=key; *p; p++)
+		if (islower(*p))
+			gotlowcase = 1;
+		else if (isupper(*p))
+			gotupcase = 1;
+		else if (isdigit(*p))
+			gotdigit = 1;
+
+	if (gotlowcase && gotupcase && gotdigit)
+		return 0;
+	return 1;
+}
+
+
+int cloak_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int keycnt = 0, errors = 0;
+	char *keys[3];
+
+	if (type == CONFIG_SET)
+	{
+		/* set::cloak-method */
+		if (!ce || !ce->name || strcmp(ce->name, "cloak-method"))
+			return 0;
+
+		if (!ce->value)
+		{
+			config_error("%s:%i: set::cloak-method: no method specified. The only supported methods are: 'ip' and 'host'",
+				ce->file->filename, ce->line_number);
+			errors++;
+		} else
+		if (strcmp(ce->value, "ip") && strcmp(ce->value, "host"))
+		{
+			config_error("%s:%i: set::cloak-method: unknown method '%s'. The only supported methods are: 'ip' and 'host'",
+				ce->file->filename, ce->line_number, ce->value);
+			errors++;
+		}
+
+		*errs = errors;
+		return errors ? -1 : 1;
+	}
+
+	if (type != CONFIG_CLOAKKEYS)
+		return 0;
+
+	nokeys = 0;
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		keycnt++;
+		if (check_badrandomness(cep->name))
+		{
+			config_error("%s:%i: set::cloak-keys: (key %d) Keys should be mixed a-zA-Z0-9, "
+			             "like \"a2JO6fh3Q6w4oN3s7\"", cep->file->filename, cep->line_number, keycnt);
+			errors++;
+		}
+		if (strlen(cep->name) < 5)
+		{
+			config_error("%s:%i: set::cloak-keys: (key %d) Each key should be at least 5 characters",
+				cep->file->filename, cep->line_number, keycnt);
+			errors++;
+		}
+		if (strlen(cep->name) > 100)
+		{
+			config_error("%s:%i: set::cloak-keys: (key %d) Each key should be less than 100 characters",
+				cep->file->filename, cep->line_number, keycnt);
+			errors++;
+		}
+		if (keycnt < 4)
+			keys[keycnt-1] = cep->name;
+	}
+	if (keycnt != 3)
+	{
+		config_error("%s:%i: set::cloak-keys: we want 3 values, not %i!",
+			ce->file->filename, ce->line_number, keycnt);
+		errors++;
+	}
+	if ((keycnt == 3) && (!strcmp(keys[0], keys[1]) || !strcmp(keys[1], keys[2])))
+	{
+		config_error("%s:%i: set::cloak-keys: All your 3 keys should be RANDOM, they should not be equal",
+			ce->file->filename, ce->line_number);
+		errors++;
+	}
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int cloak_config_posttest(int *errs)
+{
+int errors = 0;
+
+	if (nokeys)
+	{
+		config_error("set::cloak-keys missing!");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int cloak_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+ConfigEntry *cep;
+char buf[512], result[16];
+
+	if (type == CONFIG_SET)
+	{
+		/* set::cloak-method */
+		if (!ce || !ce->name || strcmp(ce->name, "cloak-method"))
+			return 0;
+
+		if (!strcmp(ce->value, "ip"))
+			CLOAK_IP_ONLY = 1;
+
+		return 0;
+	}
+
+	if (type != CONFIG_CLOAKKEYS)
+		return 0;
+
+	/* config test should ensure this goes fine... */
+	cep = ce->items;
+	safe_strdup(cloak_key1, cep->name);
+	cep = cep->next;
+	safe_strdup(cloak_key2, cep->name);
+	cep = cep->next;
+	safe_strdup(cloak_key3, cep->name);
+
+	/* Calculate checksum */
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY1, KEY2, KEY3);
+	DoMD5(result, buf, strlen(buf));
+	ircsnprintf(cloak_checksum, sizeof(cloak_checksum),
+		"MD5:%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",
+		(u_int)(result[0] & 0xf), (u_int)(result[0] >> 4),
+		(u_int)(result[1] & 0xf), (u_int)(result[1] >> 4),
+		(u_int)(result[2] & 0xf), (u_int)(result[2] >> 4),
+		(u_int)(result[3] & 0xf), (u_int)(result[3] >> 4),
+		(u_int)(result[4] & 0xf), (u_int)(result[4] >> 4),
+		(u_int)(result[5] & 0xf), (u_int)(result[5] >> 4),
+		(u_int)(result[6] & 0xf), (u_int)(result[6] >> 4),
+		(u_int)(result[7] & 0xf), (u_int)(result[7] >> 4),
+		(u_int)(result[8] & 0xf), (u_int)(result[8] >> 4),
+		(u_int)(result[9] & 0xf), (u_int)(result[9] >> 4),
+		(u_int)(result[10] & 0xf), (u_int)(result[10] >> 4),
+		(u_int)(result[11] & 0xf), (u_int)(result[11] >> 4),
+		(u_int)(result[12] & 0xf), (u_int)(result[12] >> 4),
+		(u_int)(result[13] & 0xf), (u_int)(result[13] >> 4),
+		(u_int)(result[14] & 0xf), (u_int)(result[14] >> 4),
+		(u_int)(result[15] & 0xf), (u_int)(result[15] >> 4));
+	return 1;
+}
+
+char *hidehost(Client *client, char *host)
+{
+	char *p;
+	int host_type;
+
+	if (CLOAK_IP_ONLY)
+		host = GetIP(client);
+
+	host_type = is_valid_ip(host);
+
+	if (host_type == 4)
+		return hidehost_ipv4(host);
+	else if (host_type == 6)
+		return hidehost_ipv6(host);
+	else
+		return hidehost_normalhost(host);
+}
+
+char *cloakcsum()
+{
+	return cloak_checksum;
+}
+
+/** Downsamples a 128bit result to 32bits (md5 -> unsigned int) */
+static inline unsigned int downsample(char *i)
+{
+char r[4];
+
+	r[0] = i[0] ^ i[1] ^ i[2] ^ i[3];
+	r[1] = i[4] ^ i[5] ^ i[6] ^ i[7];
+	r[2] = i[8] ^ i[9] ^ i[10] ^ i[11];
+	r[3] = i[12] ^ i[13] ^ i[14] ^ i[15];
+	
+	return ( ((unsigned int)r[0] << 24) +
+	         ((unsigned int)r[1] << 16) +
+	         ((unsigned int)r[2] << 8) +
+	         (unsigned int)r[3]);
+}
+
+static char *hidehost_ipv4(char *host)
+{
+unsigned int a, b, c, d;
+static char buf[512], res[512], res2[512], result[128];
+unsigned long n;
+unsigned int alpha, beta, gamma;
+
+	/* 
+	 * Output: ALPHA.BETA.GAMMA.IP
+	 * ALPHA is unique for a.b.c.d
+	 * BETA  is unique for a.b.c.*
+	 * GAMMA is unique for a.b.*
+	 * We cloak like this:
+	 * ALPHA = downsample(md5(md5("KEY2:A.B.C.D:KEY3")+"KEY1"));
+	 * BETA  = downsample(md5(md5("KEY3:A.B.C:KEY1")+"KEY2"));
+	 * GAMMA = downsample(md5(md5("KEY1:A.B:KEY2")+"KEY3"));
+	 */
+	sscanf(host, "%u.%u.%u.%u", &a, &b, &c, &d);
+
+	/* ALPHA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY2, host, KEY3);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY1, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	alpha = downsample(res2);
+
+	/* BETA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%d.%d.%d:%s", KEY3, a, b, c, KEY1);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY2, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	beta = downsample(res2);
+
+	/* GAMMA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%d.%d:%s", KEY1, a, b, KEY2);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY3, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	gamma = downsample(res2);
+
+	ircsnprintf(result, sizeof(result), "%X.%X.%X.IP", alpha, beta, gamma);
+	return result;
+}
+
+static char *hidehost_ipv6(char *host)
+{
+unsigned int a, b, c, d, e, f, g, h;
+static char buf[512], res[512], res2[512], result[128];
+unsigned long n;
+unsigned int alpha, beta, gamma;
+
+	/* 
+	 * Output: ALPHA:BETA:GAMMA:IP
+	 * ALPHA is unique for a:b:c:d:e:f:g:h
+	 * BETA  is unique for a:b:c:d:e:f:g
+	 * GAMMA is unique for a:b:c:d
+	 * We cloak like this:
+	 * ALPHA = downsample(md5(md5("KEY2:a:b:c:d:e:f:g:h:KEY3")+"KEY1"));
+	 * BETA  = downsample(md5(md5("KEY3:a:b:c:d:e:f:g:KEY1")+"KEY2"));
+	 * GAMMA = downsample(md5(md5("KEY1:a:b:c:d:KEY2")+"KEY3"));
+	 */
+	sscanf(host, "%x:%x:%x:%x:%x:%x:%x:%x",
+		&a, &b, &c, &d, &e, &f, &g, &h);
+
+	/* ALPHA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY2, host, KEY3);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY1, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	alpha = downsample(res2);
+
+	/* BETA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%x:%x:%x:%x:%x:%x:%x:%s", KEY3, a, b, c, d, e, f, g, KEY1);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY2, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	beta = downsample(res2);
+
+	/* GAMMA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%x:%x:%x:%x:%s", KEY1, a, b, c, d, KEY2);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY3, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	gamma = downsample(res2);
+
+	ircsnprintf(result, sizeof(result), "%X:%X:%X:IP", alpha, beta, gamma);
+	return result;
+}
+
+static char *hidehost_normalhost(char *host)
+{
+char *p;
+static char buf[512], res[512], res2[512], result[HOSTLEN+1];
+unsigned int alpha, n;
+
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY1, host, KEY2);
+	DoMD5(res, buf, strlen(buf));
+	strlcpy(res+16, KEY3, sizeof(res)-16); /* first 16 bytes are filled, append our key.. */
+	n = strlen(res+16) + 16;
+	DoMD5(res2, res, n);
+	alpha = downsample(res2);
+
+	for (p = host; *p; p++)
+		if (*p == '.')
+			if (isalpha(*(p + 1)))
+				break;
+
+	if (*p)
+	{
+		unsigned int len;
+		p++;
+		ircsnprintf(result, sizeof(result), "%s-%X.", CLOAK_PREFIX, alpha);
+		len = strlen(result) + strlen(p);
+		if (len <= HOSTLEN)
+			strlcat(result, p, sizeof(result));
+		else
+			strlcat(result, p + (len - HOSTLEN), sizeof(result));
+	} else
+		ircsnprintf(result, sizeof(result),  "%s-%X", CLOAK_PREFIX, alpha);
+
+	return result;
+}
diff --git a/src/modules/cloak_none.c b/src/modules/cloak_none.c
@@ -0,0 +1,87 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/cloak_none.c
+ *   (C) 2021 Bram Matthys and The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+char *cloakcsum();
+int cloak_config_test(ConfigFile *, ConfigEntry *, int, int *);
+
+ModuleHeader MOD_HEADER = {
+	"cloak_none",
+	"1.0",
+	"Cloaking module that does nothing",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+MOD_TEST()
+{
+	if (!CallbackAddString(modinfo->handle, CALLBACKTYPE_CLOAK_KEY_CHECKSUM, cloakcsum))
+	{
+		unreal_log(ULOG_ERROR, "config", "CLOAK_MODULE_DUPLICATE", NULL,
+		           "cloak_none: Error while trying to install callback.\n"
+		           "Maybe you have multiple cloaking modules loaded? You can only load one!");
+		return MOD_FAILED;
+	}
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cloak_config_test);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int cloak_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+
+	if (type != CONFIG_CLOAKKEYS)
+		return 0;
+
+	if (ce->items)
+	{
+		config_error("%s:%i: The cloaking module 'cloak_none' is loaded (no cloaking) but "
+		             "you also have set::cloak-keys set. Either delete your cloak keys, "
+		             "or switch to a real cloaking module.",
+		             ce->file->filename, ce->line_number);
+		errors++;
+	}
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+char *cloakcsum()
+{
+	return "NONE";
+}
diff --git a/src/modules/cloak_sha256.c b/src/modules/cloak_sha256.c
@@ -0,0 +1,411 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/cloak_sha256.c
+ *   (C) 2004-2021 Bram Matthys and The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+static char *cloak_key1 = NULL, *cloak_key2 = NULL, *cloak_key3 = NULL;
+static char cloak_checksum[64];
+static int nokeys = 1;
+
+int CLOAK_IP_ONLY = 0;
+
+#undef KEY1
+#undef KEY2
+#undef KEY3
+#define KEY1 cloak_key1
+#define KEY2 cloak_key2
+#define KEY3 cloak_key3
+
+#define SHA256_HASH_SIZE	(256/8)
+
+char *hidehost(Client *client, char *host);
+char *cloakcsum();
+int cloak_config_test(ConfigFile *, ConfigEntry *, int, int *);
+int cloak_config_run(ConfigFile *, ConfigEntry *, int);
+int cloak_config_posttest(int *);
+
+static char *hidehost_ipv4(char *host);
+static char *hidehost_ipv6(char *host);
+static char *hidehost_normalhost(char *host);
+static inline unsigned int downsample(char *i);
+
+ModuleHeader MOD_HEADER = {
+	"cloak_sha256",
+	"1.0",
+	"Cloaking module (SHA256)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+MOD_TEST()
+{
+	if (!CallbackAddString(modinfo->handle, CALLBACKTYPE_CLOAK_KEY_CHECKSUM, cloakcsum))
+	{
+		unreal_log(ULOG_ERROR, "config", "CLOAK_MODULE_DUPLICATE", NULL,
+		           "cloak_sha256: Error while trying to install callback.\n"
+		           "Maybe you have multiple cloaking modules loaded? You can only load one!");
+		return MOD_FAILED;
+	}
+	if (!CallbackAddString(modinfo->handle, CALLBACKTYPE_CLOAK_EX, hidehost))
+	{
+		config_error("cloak_sha256: Error while trying to install cloaking callback!");
+		return MOD_FAILED;
+	}
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cloak_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, cloak_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cloak_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (cloak_key1)
+	{
+		safe_free(cloak_key1);
+		safe_free(cloak_key2);
+		safe_free(cloak_key3);
+	}
+	return MOD_SUCCESS;
+}
+
+static int check_badrandomness(char *key)
+{
+	char gotlowcase=0, gotupcase=0, gotdigit=0;
+	char *p;
+
+	for (p=key; *p; p++)
+	{
+		if (islower(*p))
+			gotlowcase = 1;
+		else if (isupper(*p))
+			gotupcase = 1;
+		else if (isdigit(*p))
+			gotdigit = 1;
+	}
+
+	if (gotlowcase && gotupcase && gotdigit)
+		return 0;
+
+	return 1;
+}
+
+
+int cloak_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int keycnt = 0, errors = 0;
+	char *keys[3];
+
+	if (type == CONFIG_SET)
+	{
+		/* set::cloak-method */
+		if (!ce || !ce->name || strcmp(ce->name, "cloak-method"))
+			return 0;
+
+		if (!ce->value)
+		{
+			config_error("%s:%i: set::cloak-method: no method specified. The only supported methods are: 'ip' and 'host'",
+				ce->file->filename, ce->line_number);
+			errors++;
+		} else
+		if (strcmp(ce->value, "ip") && strcmp(ce->value, "host"))
+		{
+			config_error("%s:%i: set::cloak-method: unknown method '%s'. The only supported methods are: 'ip' and 'host'",
+				ce->file->filename, ce->line_number, ce->value);
+			errors++;
+		}
+
+		*errs = errors;
+		return errors ? -1 : 1;
+	}
+
+	if (type != CONFIG_CLOAKKEYS)
+		return 0;
+
+	nokeys = 0;
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		keycnt++;
+		if (check_badrandomness(cep->name))
+		{
+			config_error("%s:%i: set::cloak-keys: (key %d) Keys should be mixed a-zA-Z0-9, "
+			             "like \"a2JO6fh3Q6w4oN3s7\"", cep->file->filename, cep->line_number, keycnt);
+			errors++;
+		}
+		if (strlen(cep->name) < 80)
+		{
+			config_error("%s:%i: set::cloak-keys: (key %d) Each key should be at least 80 characters",
+				cep->file->filename, cep->line_number, keycnt);
+			errors++;
+		}
+		if (strlen(cep->name) > 1000)
+		{
+			config_error("%s:%i: set::cloak-keys: (key %d) Each key should be less than 1000 characters",
+				cep->file->filename, cep->line_number, keycnt);
+			errors++;
+		}
+		if (keycnt < 4)
+			keys[keycnt-1] = cep->name;
+	}
+	if (keycnt != 3)
+	{
+		config_error("%s:%i: set::cloak-keys: we want 3 values, not %i!",
+			ce->file->filename, ce->line_number, keycnt);
+		errors++;
+	}
+	if ((keycnt == 3) && (!strcmp(keys[0], keys[1]) || !strcmp(keys[1], keys[2])))
+	{
+		config_error("%s:%i: set::cloak-keys: All your 3 keys should be RANDOM, they should not be equal",
+			ce->file->filename, ce->line_number);
+		errors++;
+	}
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int cloak_config_posttest(int *errs)
+{
+	int errors = 0;
+
+	if (nokeys)
+	{
+		config_error("set::cloak-keys missing!");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int cloak_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	char buf[4096];
+	char result[128];
+
+	if (type == CONFIG_SET)
+	{
+		/* set::cloak-method */
+		if (!ce || !ce->name || strcmp(ce->name, "cloak-method"))
+			return 0;
+
+		if (!strcmp(ce->value, "ip"))
+			CLOAK_IP_ONLY = 1;
+
+		return 0;
+	}
+
+	if (type != CONFIG_CLOAKKEYS)
+		return 0;
+
+	/* config test should ensure this goes fine... */
+	cep = ce->items;
+	safe_strdup(cloak_key1, cep->name);
+	cep = cep->next;
+	safe_strdup(cloak_key2, cep->name);
+	cep = cep->next;
+	safe_strdup(cloak_key3, cep->name);
+
+	/* Calculate checksum */
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY1, KEY2, KEY3);
+	ircsnprintf(cloak_checksum, sizeof(cloak_checksum), "SHA256:%s", sha256hash(result, buf, strlen(buf)));
+	return 1;
+}
+
+char *hidehost(Client *client, char *host)
+{
+	char *p;
+	int host_type;
+
+	if (CLOAK_IP_ONLY)
+		host = GetIP(client);
+
+	host_type = is_valid_ip(host);
+
+	if (host_type == 4)
+		return hidehost_ipv4(host);
+	else if (host_type == 6)
+		return hidehost_ipv6(host);
+	else
+		return hidehost_normalhost(host);
+}
+
+char *cloakcsum()
+{
+	return cloak_checksum;
+}
+
+/** Downsamples a 256 bit result to 32 bits (SHA256 -> unsigned int) */
+static inline unsigned int downsample(char *i)
+{
+	char r[4];
+
+	r[0] = i[0] ^ i[1] ^ i[2] ^ i[3] ^ i[4] ^ i[5] ^ i[6] ^ i[7];
+	r[1] = i[8] ^ i[9] ^ i[10] ^ i[11] ^ i[12] ^ i[13] ^ i[14] ^ i[15];
+	r[2] = i[16] ^ i[17] ^ i[18] ^ i[19] ^ i[20] ^ i[21] ^ i[22] ^ i[23];
+	r[3] = i[24] ^ i[25] ^ i[26] ^ i[27] ^ i[28] ^ i[29] ^ i[30] ^ i[31];
+	
+	return ( ((unsigned int)r[0] << 24) +
+	         ((unsigned int)r[1] << 16) +
+	         ((unsigned int)r[2] << 8) +
+	         (unsigned int)r[3]);
+}
+
+static char *hidehost_ipv4(char *host)
+{
+	unsigned int a, b, c, d;
+	static char buf[512], res[512], res2[512], result[128];
+	unsigned long n;
+	unsigned int alpha, beta, gamma;
+
+	/* 
+	 * Output: ALPHA.BETA.GAMMA.IP
+	 * ALPHA is unique for a.b.c.d
+	 * BETA  is unique for a.b.c.*
+	 * GAMMA is unique for a.b.*
+	 * We cloak like this:
+	 * ALPHA = downsample(sha256(sha256("KEY2:A.B.C.D:KEY3")+"KEY1"));
+	 * BETA  = downsample(sha256(sha256("KEY3:A.B.C:KEY1")+"KEY2"));
+	 * GAMMA = downsample(sha256(sha256("KEY1:A.B:KEY2")+"KEY3"));
+	 */
+	sscanf(host, "%u.%u.%u.%u", &a, &b, &c, &d);
+
+	/* ALPHA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY2, host, KEY3);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY1, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	alpha = downsample(res2);
+
+	/* BETA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%d.%d.%d:%s", KEY3, a, b, c, KEY1);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY2, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	beta = downsample(res2);
+
+	/* GAMMA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%d.%d:%s", KEY1, a, b, KEY2);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY3, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	gamma = downsample(res2);
+
+	ircsnprintf(result, sizeof(result), "%X.%X.%X.IP", alpha, beta, gamma);
+	return result;
+}
+
+static char *hidehost_ipv6(char *host)
+{
+	unsigned int a, b, c, d, e, f, g, h;
+	static char buf[512], res[512], res2[512], result[128];
+	unsigned long n;
+	unsigned int alpha, beta, gamma;
+
+	/* 
+	 * Output: ALPHA:BETA:GAMMA:IP
+	 * ALPHA is unique for a:b:c:d:e:f:g:h
+	 * BETA  is unique for a:b:c:d:e:f:g
+	 * GAMMA is unique for a:b:c:d
+	 * We cloak like this:
+	 * ALPHA = downsample(sha256(sha256("KEY2:a:b:c:d:e:f:g:h:KEY3")+"KEY1"));
+	 * BETA  = downsample(sha256(sha256("KEY3:a:b:c:d:e:f:g:KEY1")+"KEY2"));
+	 * GAMMA = downsample(sha256(sha256("KEY1:a:b:c:d:KEY2")+"KEY3"));
+	 */
+	sscanf(host, "%x:%x:%x:%x:%x:%x:%x:%x",
+		&a, &b, &c, &d, &e, &f, &g, &h);
+
+	/* ALPHA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY2, host, KEY3);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY1, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	alpha = downsample(res2);
+
+	/* BETA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%x:%x:%x:%x:%x:%x:%x:%s", KEY3, a, b, c, d, e, f, g, KEY1);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY2, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	beta = downsample(res2);
+
+	/* GAMMA... */
+	ircsnprintf(buf, sizeof(buf), "%s:%x:%x:%x:%x:%s", KEY1, a, b, c, d, KEY2);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY3, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	gamma = downsample(res2);
+
+	ircsnprintf(result, sizeof(result), "%X:%X:%X:IP", alpha, beta, gamma);
+	return result;
+}
+
+static char *hidehost_normalhost(char *host)
+{
+	char *p;
+	static char buf[512], res[512], res2[512], result[HOSTLEN+1];
+	unsigned int alpha, n;
+
+	ircsnprintf(buf, sizeof(buf), "%s:%s:%s", KEY1, host, KEY2);
+	sha256hash_binary(res, buf, strlen(buf));
+	strlcpy(res+SHA256_HASH_SIZE, KEY3, sizeof(res)-SHA256_HASH_SIZE); /* first bytes are filled, append our key.. */
+	n = strlen(res+SHA256_HASH_SIZE) + SHA256_HASH_SIZE;
+	sha256hash_binary(res2, res, n);
+	alpha = downsample(res2);
+
+	for (p = host; *p; p++)
+		if (*p == '.')
+			if (isalpha(*(p + 1)))
+				break;
+
+	if (*p)
+	{
+		unsigned int len;
+		p++;
+		ircsnprintf(result, sizeof(result), "%s-%X.", CLOAK_PREFIX, alpha);
+		len = strlen(result) + strlen(p);
+		if (len <= HOSTLEN)
+			strlcat(result, p, sizeof(result));
+		else
+			strlcat(result, p + (len - HOSTLEN), sizeof(result));
+	} else
+		ircsnprintf(result, sizeof(result),  "%s-%X", CLOAK_PREFIX, alpha);
+
+	return result;
+}
diff --git a/src/modules/close.c b/src/modules/close.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /close", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -75,7 +75,8 @@ CMD_FUNC(cmd_close)
 	}
 
 	sendnumeric(client, RPL_CLOSEEND, closed);
-	sendto_realops("%s!%s@%s closed %d unknown connections", client->name,
-	    client->user->username, GetHost(client), closed);
+	unreal_log(ULOG_INFO, "close", "CLOSED_CONNECTIONS", client,
+	           "$client.details closed $num_closed unknown connections",
+	           log_data_integer("num_closed", closed));
 	irccounts.unknown = 0;
 }
diff --git a/src/modules/connect.c b/src/modules/connect.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /connect", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -76,7 +76,7 @@ CMD_FUNC(cmd_connect)
 		sendnumeric(client, ERR_NOPRIVILEGES);
 		return;
 	}
-	if (hunt_server(client, recv_mtags, ":%s CONNECT %s %s :%s", 3, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "CONNECT", 3, parc, parv) != HUNTED_ISME)
 		return;
 
 	if (parc < 2 || *parv[1] == '\0')
@@ -117,7 +117,7 @@ CMD_FUNC(cmd_connect)
 	/* Evaluate deny link */
 	for (deny = conf_deny_link; deny; deny = deny->next)
 	{
-		if (deny->flag.type == CRULE_ALL && match_simple(deny->mask, aconf->servername)
+		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");
@@ -134,22 +134,5 @@ CMD_FUNC(cmd_connect)
 		    get_client_name(client, FALSE));
 	}
 
-	switch (retval = connect_server(aconf, client, NULL))
-	{
-	  case 0:
-		  sendnotice(client, "*** Trying to activate link with server %s[%s]...",
-		      aconf->servername, aconf->outgoing.hostname);
-		  break;
-	  case -1:
-		  sendnotice(client, "*** Couldn't connect to %s[%s]",
-		  	aconf->servername, aconf->outgoing.hostname);
-		  break;
-	  case -2:
-		  sendnotice(client, "*** Resolving hostname '%s'...",
-		  	aconf->outgoing.hostname);
-		  break;
-	  default:
-		  sendnotice(client, "*** Connection to %s[%s] failed: %s",
-		  	aconf->servername, aconf->outgoing.hostname, STRERROR(retval));
-	}
+	connect_server(aconf, client, NULL);
 }
diff --git a/src/modules/connthrottle.c b/src/modules/connthrottle.c
@@ -19,7 +19,7 @@ ModuleHeader MOD_HEADER
 	CONNTHROTTLE_VERSION,
 	"Connection throttler - by Syzop",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 typedef struct {
@@ -146,9 +146,6 @@ int ct_config_posttest(int *errs)
 	return errors ? -1 : 1;
 }
 
-#ifndef CheckNull
- #define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
-#endif
 /** Test the set::connthrottle configuration */
 int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
@@ -159,113 +156,113 @@ int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 	
 	/* We are only interrested in set::connthrottle.. */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "connthrottle"))
+	if (!ce || !ce->name || strcmp(ce->name, "connthrottle"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "known-users"))
+		if (!strcmp(cep->name, "known-users"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "minimum-reputation-score"))
+				if (!strcmp(cepp->name, "minimum-reputation-score"))
 				{
-					int cnt = atoi(cepp->ce_vardata);
+					int cnt = atoi(cepp->value);
 					if (cnt < 1)
 					{
 						config_error("%s:%i: set::connthrottle::known-users::minimum-reputation-score should be at least 1",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+							cepp->file->filename, cepp->line_number);
 						errors++;
 						continue;
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "sasl-bypass"))
+				if (!strcmp(cepp->name, "sasl-bypass"))
 				{
 				} else
-				if (!strcmp(cepp->ce_varname, "webirc-bypass"))
+				if (!strcmp(cepp->name, "webirc-bypass"))
 				{
 				} else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-					                     "set::connthrottle::known-users", cepp->ce_varname);
+					config_error_unknown(cepp->file->filename, cepp->line_number,
+					                     "set::connthrottle::known-users", cepp->name);
 					errors++;
 				}
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "new-users"))
+		if (!strcmp(cep->name, "new-users"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "local-throttle"))
+				if (!strcmp(cepp->name, "local-throttle"))
 				{
 					int cnt, period;
-					if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
+					if (!config_parse_flood(cepp->value, &cnt, &period) ||
 					    (cnt < 1) || (cnt > 2000000000) || (period > 2000000000))
 					{
 						config_error("%s:%i: set::connthrottle::new-users::local-throttle error. "
 							     "Syntax is <count>:<period> (eg 6:60), "
 							     "and count and period should be non-zero.",
-							     cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+							     cepp->file->filename, cepp->line_number);
 						errors++;
 						continue;
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "global-throttle"))
+				if (!strcmp(cepp->name, "global-throttle"))
 				{
 					int cnt, period;
-					if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
+					if (!config_parse_flood(cepp->value, &cnt, &period) ||
 					    (cnt < 1) || (cnt > 2000000000) || (period > 2000000000))
 					{
 						config_error("%s:%i: set::connthrottle::new-users::global-throttle error. "
 							     "Syntax is <count>:<period> (eg 6:60), "
 							     "and count and period should be non-zero.",
-							     cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+							     cepp->file->filename, cepp->line_number);
 						errors++;
 						continue;
 					}
 				} else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-					                     "set::connthrottle::new-users", cepp->ce_varname);
+					config_error_unknown(cepp->file->filename, cepp->line_number,
+					                     "set::connthrottle::new-users", cepp->name);
 					errors++;
 				}
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "disabled-when"))
+		if (!strcmp(cep->name, "disabled-when"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
 				CheckNull(cepp);
-				if (!strcmp(cepp->ce_varname, "start-delay"))
+				if (!strcmp(cepp->name, "start-delay"))
 				{
-					int cnt = config_checkval(cepp->ce_vardata, CFG_TIME);
+					int cnt = config_checkval(cepp->value, CFG_TIME);
 					if ((cnt < 0) || (cnt > 3600))
 					{
 						config_error("%s:%i: set::connthrottle::disabled-when::start-delay should be in range 0-3600",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+							cepp->file->filename, cepp->line_number);
 						errors++;
 						continue;
 					}
 				} else
-				if (!strcmp(cepp->ce_varname, "reputation-gathering"))
+				if (!strcmp(cepp->name, "reputation-gathering"))
 				{
 				} else
 				{
-					config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
-					                     "set::connthrottle::disabled-when", cepp->ce_varname);
+					config_error_unknown(cepp->file->filename, cepp->line_number,
+					                     "set::connthrottle::disabled-when", cepp->name);
 					errors++;
 				}
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "reason"))
+		if (!strcmp(cep->name, "reason"))
 		{
 			CheckNull(cep);
 		} else
 		{
 			config_error("%s:%i: unknown directive set::connthrottle::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
@@ -284,48 +281,48 @@ int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 	
 	/* We are only interrested in set::connthrottle.. */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "connthrottle"))
+	if (!ce || !ce->name || strcmp(ce->name, "connthrottle"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "known-users"))
+		if (!strcmp(cep->name, "known-users"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "minimum-reputation-score"))
-					cfg.minimum_reputation_score = atoi(cepp->ce_vardata);
-				else if (!strcmp(cepp->ce_varname, "sasl-bypass"))
-					cfg.sasl_bypass = config_checkval(cepp->ce_vardata, CFG_YESNO);
-				else if (!strcmp(cepp->ce_varname, "webirc-bypass"))
-					cfg.webirc_bypass = config_checkval(cepp->ce_vardata, CFG_YESNO);
+				if (!strcmp(cepp->name, "minimum-reputation-score"))
+					cfg.minimum_reputation_score = atoi(cepp->value);
+				else if (!strcmp(cepp->name, "sasl-bypass"))
+					cfg.sasl_bypass = config_checkval(cepp->value, CFG_YESNO);
+				else if (!strcmp(cepp->name, "webirc-bypass"))
+					cfg.webirc_bypass = config_checkval(cepp->value, CFG_YESNO);
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "new-users"))
+		if (!strcmp(cep->name, "new-users"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "local-throttle"))
-					config_parse_flood(cepp->ce_vardata, &cfg.local.count, &cfg.local.period);
-				else if (!strcmp(cepp->ce_varname, "global-throttle"))
-					config_parse_flood(cepp->ce_vardata, &cfg.global.count, &cfg.global.period);
+				if (!strcmp(cepp->name, "local-throttle"))
+					config_parse_flood(cepp->value, &cfg.local.count, &cfg.local.period);
+				else if (!strcmp(cepp->name, "global-throttle"))
+					config_parse_flood(cepp->value, &cfg.global.count, &cfg.global.period);
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "disabled-when"))
+		if (!strcmp(cep->name, "disabled-when"))
 		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			for (cepp = cep->items; cepp; cepp = cepp->next)
 			{
-				if (!strcmp(cepp->ce_varname, "start-delay"))
-					cfg.start_delay = config_checkval(cepp->ce_vardata, CFG_TIME);
-				else if (!strcmp(cepp->ce_varname, "reputation-gathering"))
-					cfg.reputation_gathering = config_checkval(cepp->ce_vardata, CFG_TIME);
+				if (!strcmp(cepp->name, "start-delay"))
+					cfg.start_delay = config_checkval(cepp->value, CFG_TIME);
+				else if (!strcmp(cepp->name, "reputation-gathering"))
+					cfg.reputation_gathering = config_checkval(cepp->value, CFG_TIME);
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "reason"))
+		if (!strcmp(cep->name, "reason"))
 		{
 			safe_free(cfg.reason);
-			cfg.reason = safe_alloc(strlen(cep->ce_vardata)+16);
-			sprintf(cfg.reason, "Throttled: %s", cep->ce_vardata);
+			cfg.reason = safe_alloc(strlen(cep->value)+16);
+			sprintf(cfg.reason, "Throttled: %s", cep->value);
 		}
 	}
 	return 1;
@@ -360,16 +357,18 @@ EVENT(connthrottle_evt)
 
 	if (ucounter->rejected_clients)
 	{
-		snprintf(buf, sizeof(buf),
-		         "[ConnThrottle] Stats for this server past 60 secs: Connections rejected: %d. Accepted: %d known user(s), %d SASL, %d WEBIRC and %d new user(s).",
-		         ucounter->rejected_clients,
-		         ucounter->allowed_score,
-		         ucounter->allowed_sasl,
-			 ucounter->allowed_webirc,
-		         ucounter->allowed_other);
-
-		sendto_realops("%s", buf);
-		ircd_log(LOG_ERROR, "%s", buf);
+		unreal_log(ULOG_INFO, "connthrottle", "CONNTHROTLE_REPORT", NULL,
+		           "ConnThrottle] Stats for this server past 60 secs: "
+		           "Connections rejected: $num_rejected. "
+		           "Accepted: $num_accepted_known_users known user(s), "
+		           "$num_accepted_sasl SASL, "
+		           "$num_accepted_webirc WEBIRC and "
+		           "$num_accepted_unknown_users new user(s).",
+		           log_data_integer("num_rejected", ucounter->rejected_clients),
+		           log_data_integer("num_accepted_known_users", ucounter->allowed_score),
+		           log_data_integer("num_accepted_sasl", ucounter->allowed_sasl),
+		           log_data_integer("num_accepted_webirc", ucounter->allowed_webirc),
+		           log_data_integer("num_accepted_unknown_users", ucounter->allowed_other));
 	}
 
 	/* Reset stats for next message */
@@ -391,7 +390,7 @@ int ct_pre_lconnect(Client *client)
 	int throttle=0;
 	int score;
 
-	if (me.local->firsttime + cfg.start_delay > TStime())
+	if (me.local->creationtime + cfg.start_delay > TStime())
 		return HOOK_CONTINUE; /* no throttle: start delay */
 
 	if (ucounter->disabled)
@@ -436,10 +435,10 @@ int ct_pre_lconnect(Client *client)
 		/* We send the LARGE banner if throttling was activated */
 		if (!ucounter->throttling_previous_minute && !ucounter->throttling_banner_displayed)
 		{
-			ircd_log(LOG_ERROR, "[ConnThrottle] Connection throttling has been ACTIVATED due to a HIGH CONNECTION RATE.");
-			sendto_realops("[ConnThrottle] Connection throttling has been ACTIVATED due to a HIGH CONNECTION RATE.");
-			sendto_realops("[ConnThrottle] Users with IP addresses that have not been seen before will be rejected above the set connection rate. Known users can still get in.");
-			sendto_realops("[ConnThrottle] For more information see https://www.unrealircd.org/docs/ConnThrottle");
+			unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_ACTIVATED", NULL,
+			           "[ConnThrottle] Connection throttling has been ACTIVATED due to a HIGH CONNECTION RATE.\n"
+			           "Users with IP addresses that have not been seen before will be rejected above the set connection rate. Known users can still get in.\n"
+			           "or more information see https://www.unrealircd.org/docs/ConnThrottle");
 			ucounter->throttling_banner_displayed = 1;
 		}
 		exit_client(client, NULL, cfg.reason);
@@ -478,7 +477,7 @@ int ct_lconnect(Client *client)
 {
 	int score;
 
-	if (me.local->firsttime + cfg.start_delay > TStime())
+	if (me.local->creationtime + cfg.start_delay > TStime())
 		return 0; /* no throttle: start delay */
 
 	if (ucounter->disabled)
@@ -521,7 +520,7 @@ int ct_rconnect(Client *client)
 {
 	int score;
 
-	if (client->srvptr && !IsSynched(client->srvptr))
+	if (client->uplink && !IsSynched(client->uplink))
 		return 0; /* Netmerge: skip */
 
 	if (IsULine(client))
@@ -533,8 +532,8 @@ int ct_rconnect(Client *client)
 	 * set::disabled-when::start-delay restriction on remote
 	 * servers as well.
 	 */
-	if (client->srvptr && client->srvptr->serv && client->srvptr->serv->boottime &&
-	    (TStime() - client->srvptr->serv->boottime < cfg.start_delay))
+	if (client->uplink && client->uplink->server && client->uplink->server->boottime &&
+	    (TStime() - client->uplink->server->boottime < cfg.start_delay))
 	{
 		return 0;
 	}
@@ -584,10 +583,10 @@ CMD_FUNC(ct_throttle)
 			{
 				sendnotice(client, "Module DISABLED because the 'reputation' module has not gathered enough data yet (set::connthrottle::disabled-when::reputation-gathering).");
 			} else
-			if (me.local->firsttime + cfg.start_delay > TStime())
+			if (me.local->creationtime + cfg.start_delay > TStime())
 			{
 				sendnotice(client, "Module DISABLED due to start-delay (set::connthrottle::disabled-when::start-delay), will be enabled in %lld second(s).",
-					(long long)((me.local->firsttime + cfg.start_delay) - TStime()));
+					(long long)((me.local->creationtime + cfg.start_delay) - TStime()));
 			} else
 			{
 				sendnotice(client, "Module ENABLED");
@@ -602,8 +601,8 @@ CMD_FUNC(ct_throttle)
 			return;
 		}
 		ucounter->disabled = 1;
-		sendto_realops("[connthrottle] %s (%s@%s) DISABLED the connthrottle module.",
-			client->name, client->user->username, client->user->realhost);
+		unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_MODULE_DISABLED", client,
+			   "[ConnThrottle] $client.details DISABLED the connthrottle module.");
 	} else
 	if (!strcasecmp(parv[1], "ON"))
 	{
@@ -612,15 +611,15 @@ CMD_FUNC(ct_throttle)
 			sendnotice(client, "Already ON");
 			return;
 		}
-		sendto_realops("[connthrottle] %s (%s@%s) ENABLED the connthrottle module.",
-			client->name, client->user->username, client->user->realhost);
+		unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_MODULE_ENABLED", client,
+			   "[ConnThrottle] $client.details ENABLED the connthrottle module.");
 		ucounter->disabled = 0;
 	} else
 	if (!strcasecmp(parv[1], "RESET"))
 	{
 		memset(ucounter, 0, sizeof(UCounter));
-		sendto_realops("[connthrottle] %s (%s@%s) did a RESET on the stats/counters!!",
-			client->name, client->user->username, client->user->realhost);
+		unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_RESET", client,
+			   "[ConnThrottle] $client.details did a RESET on the statistics/counters.");
 	} else
 	{
 		sendnotice(client, "Unknown option '%s'", parv[1]);
diff --git a/src/modules/cycle.c b/src/modules/cycle.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /cycle", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -62,6 +62,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_cycle)
 {
 	char channels[BUFSIZE];
+	const char *parx[3];
 	int n;
 	
 	if (parc < 2)
@@ -75,7 +76,8 @@ CMD_FUNC(cmd_cycle)
 		return;
 
 	/* Then JOIN the user again... */
-	parv[1] = channels;
-	parv[2] = NULL;
-	do_cmd(client, recv_mtags, "JOIN", 2, parv);
+	parx[0] = NULL;
+	parx[1] = channels;
+	parx[2] = NULL;
+	do_cmd(client, recv_mtags, "JOIN", 2, parx);
 }
diff --git a/src/modules/dccallow.c b/src/modules/dccallow.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /dccallow", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -59,6 +59,7 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_dccallow)
 {
+	char request[BUFSIZE];
 	Link *lp;
 	char *p, *s;
 	Client *friend;
@@ -92,7 +93,8 @@ CMD_FUNC(cmd_dccallow)
 		return;
 	}
 
-	for (p = NULL, s = strtoken(&p, parv[1], ", "); s; s = strtoken(&p, NULL, ", "))
+	strlcpy(request, parv[1], sizeof(request));
+	for (p = NULL, s = strtoken(&p, request, ", "); s; s = strtoken(&p, NULL, ", "))
 	{
 		if (MyUser(client) && (++ntargets > maxtargets))
 		{
@@ -105,7 +107,7 @@ CMD_FUNC(cmd_dccallow)
 			if (!*++s)
 				continue;
 			
-			friend = find_person(s, NULL);
+			friend = find_user(s, NULL);
 			
 			if (friend == client)
 				continue;
@@ -123,7 +125,7 @@ CMD_FUNC(cmd_dccallow)
 			if (!*++s)
 				continue;
 			
-			friend = find_person(s, NULL);
+			friend = find_user(s, NULL);
 			if (friend == client)
 				continue;
 			if (!friend)
@@ -243,8 +245,11 @@ int del_dccallow(Client *client, Client *optr)
 		}
 	}
 	if (!found)
-		sendto_realops("[BUG!] %s was in dccallowme list of %s but not in dccallowrem list!",
-			optr->name, client->name);
+	{
+		unreal_log(ULOG_WARNING, "dccallow", "BUG_DCCALLOW", client,
+		           "[BUG] DCCALLOW list for $client did not contain $target",
+		           log_data_client("target", optr));
+	}
 
 	sendnumeric(client, RPL_DCCSTATUS, optr->name, "removed from");
 
diff --git a/src/modules/dccdeny.c b/src/modules/dccdeny.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /dccdeny", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Variables */
@@ -40,24 +40,26 @@ int dccdeny_configtest_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *
 int dccdeny_configtest_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int dccdeny_configrun_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type);
 int dccdeny_configrun_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type);
-int dccdeny_stats(Client *client, char *para);
+int dccdeny_stats(Client *client, const char *para);
+int dccdeny_dcc_denied(Client *client, const char *target, const char *realfile, const char *displayfile, ConfigItem_deny_dcc *dccdeny);
 CMD_FUNC(cmd_dccdeny);
 CMD_FUNC(cmd_undccdeny);
 CMD_FUNC(cmd_svsfline);
-int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
-int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int dccdeny_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
+int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
 int dccdeny_server_sync(Client *client);
-static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, char *filename);
-static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, char *filename);
-static void DCCdeny_add(char *filename, char *reason, int type, int type2);
+static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, const char *filename);
+static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, const char *filename);
+static void DCCdeny_add(const char *filename, const char *reason, int type, int type2);
 static void DCCdeny_del(ConfigItem_deny_dcc *deny);
 static void dcc_wipe_services(void);
-static char *get_dcc_filename(const char *text);
-static int can_dcc(Client *client, char *target, Client *targetcli, char *filename, char **errmsg);
-static int can_dcc_soft(Client *from, Client *to, char *filename, char **errmsg);
+static const char *get_dcc_filename(const char *text);
+static int can_dcc(Client *client, const char *target, Client *targetcli, const char *filename, const char **errmsg);
+static int can_dcc_soft(Client *from, Client *to, const char *filename, const char **errmsg);
 static void free_dcc_config_blocks(void);
 void dccdeny_unload_free_all_conf_deny_dcc(ModData *m);
 void dccdeny_unload_free_all_conf_allow_dcc(ModData *m);
+ConfigItem_deny_dcc *find_deny_dcc(const char *name);
 
 MOD_TEST()
 {
@@ -80,6 +82,7 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, dccdeny_can_send_to_user);
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, dccdeny_can_send_to_channel);
 	HookAdd(modinfo->handle, HOOKTYPE_SERVER_SYNC, 0, dccdeny_server_sync);
+	HookAdd(modinfo->handle, HOOKTYPE_DCC_DENIED, 0, dccdeny_dcc_denied);
 	return MOD_SUCCESS;
 }
 
@@ -104,62 +107,62 @@ int dccdeny_configtest_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *
 	char has_filename = 0, has_reason = 0, has_soft = 0;
 
 	/* We are only interested in deny dcc { } */
-	if ((type != CONFIG_DENY) || strcmp(ce->ce_vardata, "dcc"))
+	if ((type != CONFIG_DENY) || strcmp(ce->value, "dcc"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "deny dcc"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "filename"))
+		if (!strcmp(cep->name, "filename"))
 		{
 			if (has_filename)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "deny dcc::filename");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "deny dcc::filename");
 				continue;
 			}
 			has_filename = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
 			if (has_reason)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "deny dcc::reason");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "deny dcc::reason");
 				continue;
 			}
 			has_reason = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "soft"))
+		else if (!strcmp(cep->name, "soft"))
 		{
 			if (has_soft)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "deny dcc::soft");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "deny dcc::soft");
 				continue;
 			}
 			has_soft = 1;
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename,
-				cep->ce_varlinenum, "deny dcc", cep->ce_varname);
+			config_error_unknown(cep->file->filename,
+				cep->line_number, "deny dcc", cep->name);
 			errors++;
 		}
 	}
 	if (!has_filename)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"deny dcc::filename");
 		errors++;
 	}
 	if (!has_reason)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"deny dcc::reason");
 		errors++;
 	}
@@ -174,46 +177,46 @@ int dccdeny_configtest_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int 
 	int errors = 0, has_filename = 0, has_soft = 0;
 
 	/* We are only interested in allow dcc { } */
-	if ((type != CONFIG_ALLOW) || strcmp(ce->ce_vardata, "dcc"))
+	if ((type != CONFIG_ALLOW) || strcmp(ce->value, "dcc"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "allow dcc"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "filename"))
+		if (!strcmp(cep->name, "filename"))
 		{
 			if (has_filename)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow dcc::filename");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow dcc::filename");
 				continue;
 			}
 			has_filename = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "soft"))
+		else if (!strcmp(cep->name, "soft"))
 		{
 			if (has_soft)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "allow dcc::soft");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow dcc::soft");
 				continue;
 			}
 			has_soft = 1;
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"allow dcc", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"allow dcc", cep->name);
 			errors++;
 		}
 	}
 	if (!has_filename)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"allow dcc::filename");
 		errors++;
 	}
@@ -228,23 +231,23 @@ int dccdeny_configrun_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type)
 	ConfigEntry 	    	*cep;
 
 	/* We are only interested in deny dcc { } */
-	if ((type != CONFIG_DENY) || strcmp(ce->ce_vardata, "dcc"))
+	if ((type != CONFIG_DENY) || strcmp(ce->value, "dcc"))
 		return 0;
 
 	deny = safe_alloc(sizeof(ConfigItem_deny_dcc));
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "filename"))
+		if (!strcmp(cep->name, "filename"))
 		{
-			safe_strdup(deny->filename, cep->ce_vardata);
+			safe_strdup(deny->filename, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
-			safe_strdup(deny->reason, cep->ce_vardata);
+			safe_strdup(deny->reason, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "soft"))
+		else if (!strcmp(cep->name, "soft"))
 		{
-			int x = config_checkval(cep->ce_vardata,CFG_YESNO);
+			int x = config_checkval(cep->value,CFG_YESNO);
 			if (x == 1)
 				deny->flag.type = DCCDENY_SOFT;
 		}
@@ -266,18 +269,18 @@ int dccdeny_configrun_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type)
 	ConfigEntry *cep;
 
 	/* We are only interested in allow dcc { } */
-	if ((type != CONFIG_ALLOW) || strcmp(ce->ce_vardata, "dcc"))
+	if ((type != CONFIG_ALLOW) || strcmp(ce->value, "dcc"))
 		return 0;
 
 	allow = safe_alloc(sizeof(ConfigItem_allow_dcc));
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "filename"))
-			safe_strdup(allow->filename, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "soft"))
+		if (!strcmp(cep->name, "filename"))
+			safe_strdup(allow->filename, cep->value);
+		else if (!strcmp(cep->name, "soft"))
 		{
-			int x = config_checkval(cep->ce_vardata,CFG_YESNO);
+			int x = config_checkval(cep->value,CFG_YESNO);
 			if (x)
 				allow->flag.type = DCCDENY_SOFT;
 		}
@@ -371,8 +374,10 @@ CMD_FUNC(cmd_dccdeny)
 
 	if (!find_deny_dcc(parv[1]))
 	{
-		sendto_ops("%s added a temp dccdeny for %s (%s)", client->name,
-		    parv[1], parv[2]);
+		unreal_log(ULOG_INFO, "dccdeny", "DCCDENY_ADD", client,
+		           "[dccdeny] $client added a temporary DCCDENY for $file ($reason)",
+		           log_data_string("file", parv[1]),
+		           log_data_string("reason", parv[2]));
 		DCCdeny_add(parv[1], parv[2], DCCDENY_HARD, CONF_BAN_TYPE_TEMPORARY);
 		return;
 	} else
@@ -405,7 +410,10 @@ CMD_FUNC(cmd_undccdeny)
 
 	if ((d = find_deny_dcc(parv[1])) && d->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
 	{
-		sendto_ops("%s removed a temp dccdeny for %s", client->name, parv[1]);
+		unreal_log(ULOG_INFO, "dccdeny", "DCCDENY_DEL", client,
+		           "[dccdeny] $client removed a temporary DCCDENY for $file ($reason)",
+		           log_data_string("file", d->filename),
+		           log_data_string("reason", d->reason));
 		DCCdeny_del(d);
 		return;
 	} else
@@ -489,11 +497,11 @@ int dccdeny_server_sync(Client *client)
 }
 
 /** Check if a DCC should be blocked (user-to-user) */
-int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int dccdeny_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	if (**text == '\001')
 	{
-		char *filename = get_dcc_filename(*text);
+		const char *filename = get_dcc_filename(*text);
 		if (filename)
 		{
 			if (MyUser(client) && !can_dcc(client, target->name, target, filename, errmsg))
@@ -507,15 +515,15 @@ int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char *
 }
 
 /** Check if a DCC should be blocked (user-to-channel, unusual) */
-int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	static char errbuf[512];
 
 	if (MyUser(client) && (**msg == '\001'))
 	{
-		char *err = NULL;
-		char *filename = get_dcc_filename(*msg);
-		if (filename && !can_dcc(client, channel->chname, NULL, filename, &err))
+		const char *err = NULL;
+		const char *filename = get_dcc_filename(*msg);
+		if (filename && !can_dcc(client, channel->name, NULL, filename, &err))
 		{
 			if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE))
 			{
@@ -541,10 +549,11 @@ int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp
  * This is to protect a bit against tricks like 'flood-it-off-the-buffer'
  * and color 1,1 etc...
  */
-static char *dcc_displayfile(char *f)
+static const char *dcc_displayfile(const char *f)
 {
 	static char buf[512];
-	char *i, *o = buf;
+	const char *i;
+	char *o = buf;
 	size_t n = strlen(f);
 
 	if (n < 300)
@@ -575,7 +584,7 @@ static char *dcc_displayfile(char *f)
 	return buf;
 }
 
-static char *get_dcc_filename(const char *text)
+static const char *get_dcc_filename(const char *text)
 {
 	static char filename[BUFSIZE+1];
 	char *end;
@@ -617,7 +626,7 @@ static char *get_dcc_filename(const char *text)
  * @param text        The entire message
  * @returns 1 if DCC SEND allowed, 0 if rejected
  */
-static int can_dcc(Client *client, char *target, Client *targetcli, char *filename, char **errmsg)
+static int can_dcc(Client *client, const char *target, Client *targetcli, const char *filename, const char **errmsg)
 {
 	ConfigItem_deny_dcc *fl;
 	static char errbuf[256];
@@ -644,9 +653,9 @@ static int can_dcc(Client *client, char *target, Client *targetcli, char *filena
 
 	if ((fl = dcc_isforbidden(client, filename)))
 	{
-		char *displayfile = dcc_displayfile(filename);
+		const char *displayfile = dcc_displayfile(filename);
 
-		RunHook5(HOOKTYPE_DCC_DENIED, client, target, filename, displayfile, fl);
+		RunHook(HOOKTYPE_DCC_DENIED, client, target, filename, displayfile, fl);
 
 		ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
 		*errmsg = errbuf;
@@ -675,10 +684,10 @@ static int can_dcc(Client *client, char *target, Client *targetcli, char *filena
  * 1:			allowed
  * 0:			block
  */
-static int can_dcc_soft(Client *from, Client *to, char *filename, char **errmsg)
+static int can_dcc_soft(Client *from, Client *to, const char *filename, const char **errmsg)
 {
 	ConfigItem_deny_dcc *fl;
-	char *displayfile;
+	const char *displayfile;
 	static char errbuf[256];
 
 	/* User (IRCOp) may bypass send restrictions */
@@ -718,7 +727,7 @@ static int can_dcc_soft(Client *from, Client *to, char *filename, char **errmsg)
 }
 
 /** Checks if the dcc is blacklisted. */
-static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, char *filename)
+static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, const char *filename)
 {
 	ConfigItem_deny_dcc *d;
 	ConfigItem_allow_dcc *a;
@@ -743,7 +752,7 @@ static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, char *filename)
 }
 
 /** checks if the dcc is discouraged ('soft bans'). */
-static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, char *filename)
+static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, const char *filename)
 {
 	ConfigItem_deny_dcc *d;
 	ConfigItem_allow_dcc *a;
@@ -767,7 +776,7 @@ static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, char *filename)
 	return NULL;
 }
 
-static void DCCdeny_add(char *filename, char *reason, int type, int type2)
+static void DCCdeny_add(const char *filename, const char *reason, int type, int type2)
 {
 	ConfigItem_deny_dcc *deny = NULL;
 
@@ -787,7 +796,7 @@ static void DCCdeny_del(ConfigItem_deny_dcc *deny)
 	safe_free(deny);
 }
 
-ConfigItem_deny_dcc *find_deny_dcc(char *name)
+ConfigItem_deny_dcc *find_deny_dcc(const char *name)
 {
 	ConfigItem_deny_dcc	*e;
 
@@ -820,7 +829,7 @@ static void dcc_wipe_services(void)
 
 }
 
-int dccdeny_stats(Client *client, char *para)
+int dccdeny_stats(Client *client, const char *para)
 {
 	ConfigItem_deny_dcc *denytmp;
 	ConfigItem_allow_dcc *allowtmp;
@@ -860,3 +869,13 @@ int dccdeny_stats(Client *client, char *para)
 	}
 	return 1;
 }
+
+int dccdeny_dcc_denied(Client *client, const char *target, const char *realfile, const char *displayfile, ConfigItem_deny_dcc *dccdeny)
+{
+	unreal_log(ULOG_INFO, "dcc", "DCC_REJECTED", client,
+	           "$client.details tried to send forbidden file $filename ($ban_reason) to $target (is blocked now)",
+	           log_data_string("filename", displayfile),
+	           log_data_string("ban_reason", dccdeny->reason),
+	           log_data_string("target", target));
+	return 0;
+}
diff --git a/src/modules/echo-message.c b/src/modules/echo-message.c
@@ -28,15 +28,15 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Batch CAP", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
 long CAP_ECHO_MESSAGE = 0L;
 
 /* Forward declarations */
-int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
-int em_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype);
+int em_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype);
+int em_usermsg(Client *client, Client *to, MessageTag *mtags, const char *text, SendType sendtype);
 
 MOD_INIT()
 {
@@ -64,7 +64,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype)
+int em_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype)
 {
 	if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
 	{
@@ -85,7 +85,7 @@ int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char
 	return 0;
 }
 
-int em_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype)
+int em_usermsg(Client *client, Client *to, MessageTag *mtags, const char *text, SendType sendtype)
 {
 	if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
 	{
diff --git a/src/modules/eos.c b/src/modules/eos.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /eos", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -63,12 +63,8 @@ CMD_FUNC(cmd_eos)
 {
 	if (!IsServer(client))
 		return;
-	client->serv->flags.synced = 1;
+	client->server->flags.synced = 1;
 	/* pass it on ^_- */
-#ifdef DEBUGMODE
-	ircd_log(LOG_ERROR, "[EOSDBG] cmd_eos: got sync from %s (path:%s)", client->name, client->direction->name);
-	ircd_log(LOG_ERROR, "[EOSDBG] cmd_eos: broadcasting it back to everyone except route from %s", client->direction->name);
-#endif
 	sendto_server(client, 0, 0, NULL, ":%s EOS", client->id);
 
 	RunHook(HOOKTYPE_SERVER_SYNCED, client);
diff --git a/src/modules/extbans/Makefile.in b/src/modules/extbans/Makefile.in
@@ -25,21 +25,25 @@ INCLUDES = ../../include/channel.h \
 	../../include/ircsprintf.h \
 	../../include/license.h \
 	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
-	../../include/numeric.h ../../include/proto.h ../../include/dns.h \
+	../../include/numeric.h ../../include/dns.h \
 	../../include/resource.h ../../include/setup.h \
 	../../include/struct.h ../../include/sys.h \
-	../../include/types.h ../../include/url.h \
+	../../include/types.h \
 	../../include/version.h ../../include/whowas.h
 
 R_MODULES= \
 	join.so quiet.so nickchange.so inchannel.so realname.so \
 	account.so operclass.so certfp.so textban.so msgbypass.so \
-	timedban.so partmsg.so securitygroup.so
+	timedban.so partmsg.so securitygroup.so \
+	country.so
 
 MODULES=$(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
 RM=@RM@
 
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
 all: build
 
 build: $(MODULES)
@@ -47,62 +51,6 @@ build: $(MODULES)
 clean:
 	$(RM) -f *.o *.so *~ core
 
-#############################################################################
-#             .so's section
-#############################################################################
-
-skel.so: skel.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o skel.so skel.c
-
-join.so: join.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o join.so join.c
-
-quiet.so: quiet.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o quiet.so quiet.c
-
-nickchange.so: nickchange.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nickchange.so nickchange.c
-
-inchannel.so: inchannel.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o inchannel.so inchannel.c
-
-realname.so: realname.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o realname.so realname.c
-
-account.so: account.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o account.so account.c
-
-operclass.so: operclass.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o operclass.so operclass.c
-
-certfp.so: certfp.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o certfp.so certfp.c
-
-textban.so: textban.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o textban.so textban.c
-
-msgbypass.so: msgbypass.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o msgbypass.so msgbypass.c
-
-timedban.so: timedban.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o timedban.so timedban.c
-
-partmsg.so: partmsg.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o partmsg.so partmsg.c
-
-securitygroup.so: securitygroup.c $(INCLUDES)
+%.so: %.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o securitygroup.so securitygroup.c
+		-o $@ $<
diff --git a/src/modules/extbans/account.c b/src/modules/extbans/account.c
@@ -24,22 +24,25 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~a - Ban/exempt by services account name",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-char *extban_account_conv_param(char *para);
-int extban_account_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+const char *extban_account_conv_param(BanContext *b, Extban *extban);
+int extban_account_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'a';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'a';
+	req.name = "account";
 	req.is_ok = NULL;
 	req.conv_param = extban_account_conv_param;
 	req.is_banned = extban_account_is_banned;
+	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
 	req.options = EXTBOPT_INVEX|EXTBOPT_TKL;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -65,27 +68,34 @@ MOD_UNLOAD()
 }
 
 /** Account bans */
-char *extban_account_conv_param(char *para)
+const char *extban_account_conv_param(BanContext *b, Extban *extban)
 {
 	char *mask, *acc;
 	static char retbuf[NICKLEN + 4];
 
-	strlcpy(retbuf, para, sizeof(retbuf)); /* truncate */
+	strlcpy(retbuf, b->banstr, sizeof(retbuf)); /* truncate */
 
-	acc = retbuf+3;
+	acc = retbuf;
 	if (!*acc)
 		return NULL; /* don't allow "~a:" */
-	if (!strcmp(acc, "0"))
-		return NULL; /* ~a:0 would mean ban all non-regged, but we already have +R for that. */
 
 	return retbuf;
 }
 
-int extban_account_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_account_is_banned(BanContext *b)
 {
-	char *ban = banin+3;
+	/* ~a:0 is special and matches all unauthenticated users */
+	if (!strcmp(b->banstr, "0") && !IsLoggedIn(b->client))
+		return 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 (!strcasecmp(ban, client->user->svid))
+	if (match_simple(b->banstr, b->client->user->account))
 		return 1;
 
 	return 0;
diff --git a/src/modules/extbans/certfp.c b/src/modules/extbans/certfp.c
@@ -24,23 +24,26 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~S - Ban/exempt by SHA256 TLS certificate fingerprint",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int extban_certfp_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2);
-char *extban_certfp_conv_param(char *para);
-int extban_certfp_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+int extban_certfp_is_ok(BanContext *b);
+const char *extban_certfp_conv_param(BanContext *b, Extban *extban);
+int extban_certfp_is_banned(BanContext *b);
 
 /* Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'S';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'S';
+	req.name = "certfp";
 	req.is_ok = extban_certfp_is_ok;
 	req.conv_param = extban_certfp_conv_param;
 	req.is_banned = extban_certfp_is_banned;
+	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
 	req.options = EXTBOPT_INVEX|EXTBOPT_TKL;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -74,18 +77,18 @@ int extban_certfp_usage(Client *client)
 	return EX_DENY;
 }
 
-int extban_certfp_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2)
+int extban_certfp_is_ok(BanContext *b)
 {
-	if (checkt == EXCHK_PARAM)
+	if (b->is_ok_check == EXCHK_PARAM)
 	{
-		char *p;
+		const char *p;
 		
-		if (strlen(para) != 3 + CERT_FP_LEN)
-			return extban_certfp_usage(client);
+		if (strlen(b->banstr) != CERT_FP_LEN)
+			return extban_certfp_usage(b->client);
 		
-		for (p = para + 3; *p; p++)
+		for (p = b->banstr; *p; p++)
 			if (!isxdigit(*p))
-				return extban_certfp_usage(client);
+				return extban_certfp_usage(b->client);
 
 		return EX_ALLOW;
 	}
@@ -93,14 +96,14 @@ int extban_certfp_is_ok(Client *client, Channel *channel, char *para, int checkt
 }
 
 /* Obtain targeted certfp from the ban string */
-char *extban_certfp_conv_param(char *para)
+const char *extban_certfp_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[EVP_MAX_MD_SIZE * 2 + 1];
 	char *p;
 	
-	strlcpy(retbuf, para, sizeof(retbuf));
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
 	
-	for (p = retbuf+3; *p; p++)
+	for (p = retbuf; *p; p++)
 	{
 		*p = tolower(*p);
 	}
@@ -108,17 +111,14 @@ char *extban_certfp_conv_param(char *para)
 	return retbuf;
 }
 
-int extban_certfp_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_certfp_is_banned(BanContext *b)
 {
-	char *ban = banin+3;
-	char *fp;
-
-	fp = moddata_client_get(client, "certfp");
+	const char *fp = moddata_client_get(b->client, "certfp");
 
 	if (!fp)
 		return 0; /* not using TLS */
 
-	if (!strcmp(ban, fp))
+	if (!strcmp(b->banstr, fp))
 		return 1;
 
 	return 0;
diff --git a/src/modules/extbans/country.c b/src/modules/extbans/country.c
@@ -0,0 +1,124 @@
+/*
+ * Extended ban to ban/exempt by country/geoip info (+b ~country:UK)
+ * (C) Copyright 2021 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/country",
+	"6.0",
+	"ExtBan ~country - Ban/exempt by country (geoip)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int extban_country_is_ok(BanContext *b);
+const char *extban_country_conv_param(BanContext *b, Extban *extban);
+int extban_country_is_banned(BanContext *b);
+
+/* Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+
+	memset(&req, 0, sizeof(req));
+	req.letter = 'C';
+	req.name = "country";
+	req.is_ok = extban_country_is_ok;
+	req.conv_param = extban_country_conv_param;
+	req.is_banned = extban_country_is_banned;
+	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
+	req.options = EXTBOPT_INVEX|EXTBOPT_TKL;
+	if (!ExtbanAdd(modinfo->handle, req))
+	{
+		config_error("could not register extended ban type");
+		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;
+}
+
+int extban_country_usage(Client *client)
+{
+	sendnotice(client, "ERROR: ExtBan ~country expects a two letter country code, or * to ban unknown countries. "
+					 "For example: +b ~country:UK");
+	return EX_DENY;
+}
+
+int extban_country_is_ok(BanContext *b)
+{
+	if (b->is_ok_check == EXCHK_PARAM)
+	{
+		const char *p;
+
+		if (!strcmp(b->banstr, "*"))
+			return EX_ALLOW;
+
+		if ((strlen(b->banstr) != 2))
+			return extban_country_usage(b->client);
+
+		for (p = b->banstr; *p; p++)
+			if (!isalpha(*p))
+				return extban_country_usage(b->client);
+
+		return EX_ALLOW;
+	}
+	return EX_ALLOW;
+}
+
+/* Obtain targeted country from the ban string */
+const char *extban_country_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[EVP_MAX_MD_SIZE * 2 + 1];
+	char *p;
+
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
+
+	for (p = retbuf; *p; p++)
+		*p = toupper(*p);
+
+	return retbuf;
+}
+
+int extban_country_is_banned(BanContext *b)
+{
+	GeoIPResult *geo = geoip_client(b->client);
+	char *country;
+
+	country = geo ? geo->country_code : "*";
+
+	if (!strcmp(b->banstr, country))
+		return 1;
+
+	return 0;
+}
diff --git a/src/modules/extbans/inchannel.c b/src/modules/extbans/inchannel.c
@@ -24,23 +24,26 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~c - banned when in specified channel",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int extban_inchannel_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2);
-char *extban_inchannel_conv_param(char *para);
-int extban_inchannel_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+int extban_inchannel_is_ok(BanContext *b);
+const char *extban_inchannel_conv_param(BanContext *b, Extban *extban);
+int extban_inchannel_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'c';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'c';
+	req.name = "channel";
 	req.is_ok = extban_inchannel_is_ok;
 	req.conv_param = extban_inchannel_conv_param;
 	req.is_banned = extban_inchannel_is_banned;
+	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
 	req.options = EXTBOPT_INVEX; /* for +I too */
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -65,13 +68,13 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-char *extban_inchannel_conv_param(char *para)
+const char *extban_inchannel_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[CHANNELLEN+6];
 	char *chan, *p, symbol='\0';
 
-	strlcpy(retbuf, para, sizeof(retbuf));
-	chan = retbuf+3;
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
+	chan = retbuf;
 
 	if ((*chan == '+') || (*chan == '%') || (*chan == '%') ||
 	    (*chan == '@') || (*chan == '&') || (*chan == '~'))
@@ -92,51 +95,53 @@ char *extban_inchannel_conv_param(char *para)
 }
 
 /* The only purpose of this function is a temporary workaround to prevent a desync.. pfff */
-int extban_inchannel_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2)
+int extban_inchannel_is_ok(BanContext *b)
 {
-	char *p;
+	const char *p = b->banstr;
 
-	if ((checkt == EXBCHK_PARAM) && MyUser(client) && (what == MODE_ADD) && (strlen(para) > 3))
+	if ((b->is_ok_check == EXBCHK_PARAM) && MyUser(b->client) && (b->what == MODE_ADD) && (strlen(b->banstr) > 3))
 	{
-		p = para + 3;
 		if ((*p == '+') || (*p == '%') || (*p == '%') ||
 		    (*p == '@') || (*p == '&') || (*p == '~'))
 		    p++;
 
 		if (*p != '#')
 		{
-			sendnotice(client, "Please use a # in the channelname (eg: ~c:#*blah*)");
+			sendnotice(b->client, "Please use a # in the channelname (eg: ~c:#*blah*)");
 			return 0;
 		}
 	}
 	return 1;
 }
 
-static int extban_inchannel_compareflags(char symbol, int flags)
+static int extban_inchannel_compareflags(char symbol, const char *member_modes)
 {
-	int require=0;
+	const char *required_modes = NULL;
 
 	if (symbol == '+')
-		require = CHFL_VOICE|CHFL_HALFOP|CHFL_CHANOP|CHFL_CHANADMIN|CHFL_CHANOWNER;
+		required_modes = "vhoaq";
 	else if (symbol == '%')
-		require = CHFL_HALFOP|CHFL_CHANOP|CHFL_CHANADMIN|CHFL_CHANOWNER;
+		required_modes = "hoaq";
 	else if (symbol == '@')
-		require = CHFL_CHANOP|CHFL_CHANADMIN|CHFL_CHANOWNER;
+		required_modes = "oaq";
 	else if (symbol == '&')
-		require = CHFL_CHANADMIN|CHFL_CHANOWNER;
+		required_modes = "aq";
 	else if (symbol == '~')
-		require = CHFL_CHANOWNER;
+		required_modes = "q";
+	else
+		return 0; /* unknown prefix character */
 
-	if (flags & require)
+	if (check_channel_access_string(member_modes, required_modes))
 		return 1;
 
 	return 0;
 }
 
-int extban_inchannel_is_banned(Client *client, Channel *channel, char *ban, int type, char **msg, char **errmsg)
+int extban_inchannel_is_banned(BanContext *b)
 {
 	Membership *lp;
-	char *p = ban+3, symbol = '\0';
+	const char *p = b->banstr;
+	char symbol = '\0';
 
 	if (*p != '#')
 	{
@@ -144,14 +149,14 @@ int extban_inchannel_is_banned(Client *client, Channel *channel, char *ban, int 
 		p++;
 	}
 
-	for (lp = client->user->channel; lp; lp = lp->next)
+	for (lp = b->client->user->channel; lp; lp = lp->next)
 	{
-		if (match_esc(p, lp->channel->chname))
+		if (match_esc(p, lp->channel->name))
 		{
 			/* Channel matched, check symbol if needed (+/%/@/etc) */
 			if (symbol)
 			{
-				if (extban_inchannel_compareflags(symbol, lp->flags))
+				if (extban_inchannel_compareflags(symbol, lp->member_modes))
 					return 1;
 			} else
 				return 1;
diff --git a/src/modules/extbans/join.c b/src/modules/extbans/join.c
@@ -24,21 +24,24 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"Extban ~j - prevent join only",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int extban_modej_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+int extban_modej_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'j';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'j';
+	req.name = "join";
 	req.is_ok = extban_is_ok_nuh_extban;
 	req.conv_param = extban_conv_param_nuh_or_extban;
 	req.is_banned = extban_modej_is_banned;
+	req.is_banned_events = BANCHK_JOIN;
 	req.options = EXTBOPT_ACTMODIFIER;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -64,15 +67,7 @@ MOD_UNLOAD()
 }
 
 /** This ban that affects JOINs only */
-int extban_modej_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_modej_is_banned(BanContext *b)
 {
-	char *sub_ban;
-
-	if (type != BANCHK_JOIN)
-		return 0;
-
-	sub_ban = banin + 3;
-
-	return ban_check_mask(client, channel, sub_ban, type, msg, errmsg, 0);
+	return ban_check_mask(b);
 }
-
diff --git a/src/modules/extbans/msgbypass.c b/src/modules/extbans/msgbypass.c
@@ -24,24 +24,24 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~m - bypass +m/+n/+c/+S/+T (msgbypass)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int extban_msgbypass_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
 int msgbypass_can_bypass(Client *client, Channel *channel, BypassChannelMessageRestrictionType bypass_type);
-int msgbypass_extban_is_ok(Client *client, Channel* channel, char *para, int checkt, int what, int what2);
-char *msgbypass_extban_conv_param(char *para);
+int msgbypass_extban_is_ok(BanContext *b);
+const char *msgbypass_extban_conv_param(BanContext *b, Extban *extban);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'm';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'm';
+	req.name = "msgbypass";
 	req.is_ok = msgbypass_extban_is_ok;
 	req.conv_param = msgbypass_extban_conv_param;
-	req.is_banned = extban_msgbypass_is_banned;
 	req.options = EXTBOPT_ACTMODIFIER;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -67,42 +67,50 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-/** Is the user banned? No, never by us anyway. */
-int extban_msgbypass_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
-{
-	return 0; /* not banned by us */
-}
-
 /** Can the user bypass restrictions? */
 int msgbypass_can_bypass(Client *client, Channel *channel, BypassChannelMessageRestrictionType bypass_type)
 {
 	Ban *ban;
 	char *p;
-	
+	BanContext *b = safe_alloc(sizeof(BanContext));
+
+	b->client = client;
+	b->channel = channel;
+	b->ban_check_types = BANCHK_MSG;
+
 	for (ban = channel->exlist; ban; ban=ban->next)
 	{
+		char *type;
+		char *matchby;
+
 		if (!strncmp(ban->banstr, "~m:", 3))
+			type = ban->banstr + 3;
+		else if (!strncmp(ban->banstr, "~msgbypass:", 11))
+			type = ban->banstr + 11;
+		else
+			continue;
+
+		if (((bypass_type == BYPASS_CHANMSG_EXTERNAL) && !strncmp(type, "external:", 9)) ||
+		    ((bypass_type == BYPASS_CHANMSG_MODERATED) && !strncmp(type, "moderated:", 10)) ||
+		    ((bypass_type == BYPASS_CHANMSG_COLOR) && !strncmp(type, "color:", 6)) ||
+		    ((bypass_type == BYPASS_CHANMSG_CENSOR) && !strncmp(type, "censor:", 7)) ||
+		    ((bypass_type == BYPASS_CHANMSG_NOTICE) && !strncmp(type, "notice:", 7)))
 		{
-			char *type = ban->banstr + 3;
-			char *matchby;
+			matchby = strchr(type, ':');
+			if (!matchby)
+				continue;
+			matchby++;
 			
-			if (((bypass_type == BYPASS_CHANMSG_EXTERNAL) && !strncmp(type, "external:", 9)) ||
-			    ((bypass_type == BYPASS_CHANMSG_MODERATED) && !strncmp(type, "moderated:", 10)) ||
-			    ((bypass_type == BYPASS_CHANMSG_COLOR) && !strncmp(type, "color:", 6)) || 
-			    ((bypass_type == BYPASS_CHANMSG_CENSOR) && !strncmp(type, "censor:", 7)) ||
-			    ((bypass_type == BYPASS_CHANMSG_NOTICE) && !strncmp(type, "notice:", 7)))
+			b->banstr = matchby;
+			if (ban_check_mask(b))
 			{
-				matchby = strchr(type, ':');
-				if (!matchby)
-					continue;
-				matchby++;
-				
-				if (ban_check_mask(client, channel, matchby, BANCHK_MSG, NULL, NULL, 0))
-					return HOOK_ALLOW; /* Yes, user may bypass */
+				safe_free(b);
+				return HOOK_ALLOW; /* Yes, user may bypass */
 			}
 		}
 	}
 
+	safe_free(b);
 	return HOOK_CONTINUE; /* No, may NOT bypass. */
 }
 
@@ -121,16 +129,16 @@ int msgbypass_extban_type_ok(char *type)
 }
 
 #define MAX_LENGTH 128
-char *msgbypass_extban_conv_param(char *para_in)
+const char *msgbypass_extban_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[MAX_LENGTH+1];
 	char para[MAX_LENGTH+1];
 	char tmpmask[MAX_LENGTH+1];
 	char *type; /**< Type, such as 'external' */
 	char *matchby; /**< Matching method, such as 'n!u@h' */
-	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
 
-	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
 	
 	/* ~m:type:n!u@h   for direct matching
 	 * ~m:type:~x:.... when calling another bantype
@@ -145,16 +153,13 @@ char *msgbypass_extban_conv_param(char *para_in)
 	if (!msgbypass_extban_type_ok(type))
 		return NULL;
 
-	/* This is quite silly, we have to create a fake extban here due to
-	 * the current API of extban_conv_param_nuh and extban_conv_param_nuh_or_extban
-	 * expecting the full banmask rather than the portion that actually matters.
-	 */
-	snprintf(tmpmask, sizeof(tmpmask), "~?:%s", matchby);
-	newmask = extban_conv_param_nuh_or_extban(tmpmask);
-	if (!newmask || (strlen(newmask) <= 3))
+	b->banstr = matchby;
+	newmask = extban_conv_param_nuh_or_extban(b, extban);
+	if (BadPtr(newmask))
 		return NULL;
 
-	snprintf(retbuf, sizeof(retbuf), "~m:%s:%s", type, newmask+3);
+	//snprintf(retbuf, sizeof(retbuf), "~m:%s:%s", type, newmask);
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", type, newmask);
 	return retbuf;
 }
 
@@ -171,26 +176,25 @@ int msgbypass_extban_syntax(Client *client, int checkt, char *reason)
 	return 0; /* FAIL: ban rejected */
 }
 
-int msgbypass_extban_is_ok(Client *client, Channel* channel, char *para_in, int checkt, int what, int what2)
+int msgbypass_extban_is_ok(BanContext *b)
 {
-	char para[MAX_LENGTH+1];
-	char tmpmask[MAX_LENGTH+1];
+	static char para[MAX_LENGTH+1];
 	char *type; /**< Type, such as 'external' */
 	char *matchby; /**< Matching method, such as 'n!u@h' */
 	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
 
 	/* Always permit deletion */
-	if (what == MODE_DEL)
+	if (b->what == MODE_DEL)
 		return 1;
 	
-	if (what2 != EXBTYPE_EXCEPT)
+	if (b->ban_type != EXBTYPE_EXCEPT)
 	{
-		if (checkt == EXBCHK_PARAM)
-			sendnotice(client, "Ban type ~m only works with exceptions (+e) and not with bans or invex (+b/+I)");
+		if (b->is_ok_check == EXBCHK_PARAM)
+			sendnotice(b->client, "Ban type ~m only works with exceptions (+e) and not with bans or invex (+b/+I)");
 		return 0; /* reject */
 	}
 
-	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
 	
 	/* ~m:type:n!u@h   for direct matching
 	 * ~m:type:~x:.... when calling another bantype
@@ -199,24 +203,20 @@ int msgbypass_extban_is_ok(Client *client, Channel* channel, char *para_in, int 
 	type = para;
 	matchby = strchr(para, ':');
 	if (!matchby || !matchby[1])
-		return msgbypass_extban_syntax(client, checkt, "Invalid syntax");
+		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
 	*matchby++ = '\0';
 
 	if (!msgbypass_extban_type_ok(type))
-		return msgbypass_extban_syntax(client, checkt, "Unknown type");
+		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Unknown type");
 
-	/* This is quite silly, we have to create a fake extban here due to
-	 * the current API of extban_conv_param_nuh and extban_conv_param_nuh_or_extban
-	 * expecting the full banmask rather than the portion that actually matters.
-	 */
-	snprintf(tmpmask, sizeof(tmpmask), "~?:%s", matchby);
-	if (extban_is_ok_nuh_extban(client, channel, tmpmask, checkt, what, what2) == 0)
+	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 msgbypass_extban_syntax(client, checkt, "Invalid matcher");
+		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
 	}
 
 	return 1; /* OK */
diff --git a/src/modules/extbans/nickchange.c b/src/modules/extbans/nickchange.c
@@ -24,21 +24,24 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~n - prevent nick-changes only",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int extban_nickchange_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+int extban_nickchange_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'n';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'n';
+	req.name = "nickchange";
 	req.is_ok = extban_is_ok_nuh_extban;
 	req.conv_param = extban_conv_param_nuh_or_extban;
 	req.is_banned = extban_nickchange_is_banned;
+	req.is_banned_events = BANCHK_NICK;
 	req.options = EXTBOPT_ACTMODIFIER;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -64,18 +67,10 @@ MOD_UNLOAD()
 }
 
 /** This ban that affects nick-changes only */
-int extban_nickchange_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_nickchange_is_banned(BanContext *b)
 {
-	char *sub_ban;
-
-	if (type != BANCHK_NICK)
-		return 0;
-
-	if (has_voice(client, channel))
+	if (check_channel_access(b->client, b->channel, "v"))
 		return 0;
 
-	sub_ban = banin + 3;
-
-	return ban_check_mask(client, channel, sub_ban, type, msg, errmsg, 0);
+	return ban_check_mask(b);
 }
-
diff --git a/src/modules/extbans/operclass.c b/src/modules/extbans/operclass.c
@@ -24,22 +24,25 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~O - Ban/exempt operclass",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-char *extban_operclass_conv_param(char *para);
-int extban_operclass_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+const char *extban_operclass_conv_param(BanContext *b, Extban *extban);
+int extban_operclass_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'O';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'O';
+	req.name = "operclass";
 	req.is_ok = NULL;
 	req.conv_param = extban_operclass_conv_param;
 	req.is_banned = extban_operclass_is_banned;
+	req.is_banned_events = BANCHK_ALL;
 	req.options = EXTBOPT_INVEX;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -67,15 +70,15 @@ MOD_UNLOAD()
 
 #define OPERCLASSLEN 64
 
-char *extban_operclass_conv_param(char *para)
+const char *extban_operclass_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[OPERCLASSLEN + 4];
 	char *p;
 
-	strlcpy(retbuf, para, sizeof(retbuf));
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
 
 	/* allow alpha, numeric, -, _, * and ? wildcards */
-	for (p = retbuf+3; *p; p++)
+	for (p = retbuf; *p; p++)
 		if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_?*", *p))
 			*p = '\0';
 
@@ -85,18 +88,12 @@ char *extban_operclass_conv_param(char *para)
 	return retbuf;
 }
 
-int extban_operclass_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_operclass_is_banned(BanContext *b)
 {
-	char *ban = banin+3;
-
-	if (MyUser(client) && IsOper(client))
+	if (MyUser(b->client) && IsOper(b->client))
 	{
-		char *operclass = NULL;
-		ConfigItem_oper *oper = find_oper(client->user->operlogin);
-		if (oper && oper->operclass)
-			operclass = oper->operclass;
-		
-		if (operclass && match_simple(ban, operclass))
+		const char *operclass = get_operclass(b->client);
+		if (operclass && match_simple(b->banstr, operclass))
 			return 1;
 	}
 
diff --git a/src/modules/extbans/partmsg.c b/src/modules/extbans/partmsg.c
@@ -25,20 +25,23 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~p - Ban/exempt Part/Quit message",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
-int extban_partmsg_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+int extban_partmsg_is_banned(BanContext *b);
 
 MOD_INIT()
 {
 	ExtbanInfo req;
 
-	req.flag = 'p';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'p';
+	req.name = "partmsg";
 	req.is_ok = extban_is_ok_nuh_extban;
 	req.conv_param = extban_conv_param_nuh_or_extban;
 	req.options = EXTBOPT_ACTMODIFIER;
 	req.is_banned = extban_partmsg_is_banned;
+	req.is_banned_events = BANCHK_LEAVE_MSG;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
 		config_error("could not register extended ban type");
@@ -62,10 +65,10 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int extban_partmsg_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_partmsg_is_banned(BanContext *b)
 {
-	if (type == BANCHK_LEAVE_MSG)
-		*msg = NULL;
+	b->msg = NULL;
+	// Uh.. there is no attempt to match.... anything.......?
 
 	return 0;
 }
diff --git a/src/modules/extbans/quiet.c b/src/modules/extbans/quiet.c
@@ -24,21 +24,24 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~q - prevent messages only (quiet)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int extban_quiet_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+int extban_quiet_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'q';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'q';
+	req.name = "quiet";
 	req.is_ok = extban_is_ok_nuh_extban;
 	req.conv_param = extban_conv_param_nuh_or_extban;
 	req.is_banned = extban_quiet_is_banned;
+	req.is_banned_events = BANCHK_MSG;
 	req.options = EXTBOPT_ACTMODIFIER;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -64,14 +67,7 @@ MOD_UNLOAD()
 }
 
 /** This ban that affects messages/notices only */
-int extban_quiet_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_quiet_is_banned(BanContext *b)
 {
-	char *sub_ban;
-
-	if (type != BANCHK_MSG)
-		return 0;
-
-	sub_ban = banin + 3;
-
-	return ban_check_mask(client, channel, sub_ban, type, msg, errmsg, 0);
+	return ban_check_mask(b);
 }
diff --git a/src/modules/extbans/realname.c b/src/modules/extbans/realname.c
@@ -24,22 +24,25 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~r - Ban based on realname/gecos field",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-char *extban_realname_conv_param(char *para);
-int extban_realname_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+const char *extban_realname_conv_param(BanContext *b, Extban *extban);
+int extban_realname_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
 	
-	req.flag = 'r';
+	memset(&req, 0, sizeof(req));
+	req.letter = 'r';
+	req.name = "realname";
 	req.is_ok = NULL;
 	req.conv_param = extban_realname_conv_param;
 	req.is_banned = extban_realname_is_banned;
+	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
 	req.options = EXTBOPT_CHSVSMODE|EXTBOPT_INVEX|EXTBOPT_TKL;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -65,32 +68,31 @@ MOD_UNLOAD()
 }
 
 /** Realname bans - conv_param */
-char *extban_realname_conv_param(char *para)
+const char *extban_realname_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[REALLEN + 8];
 	char *mask;
 
-	strlcpy(retbuf, para, sizeof(retbuf));
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
 
-	mask = retbuf+3;
+	mask = retbuf;
 
 	if (!*mask)
 		return NULL; /* don't allow "~r:" */
 
-	if (strlen(mask) > REALLEN + 3)
-		mask[REALLEN + 3] = '\0';
+	if (strlen(mask) > REALLEN)
+		mask[REALLEN] = '\0';
 
+	/* Prevent otherwise confusing extban relationship */
 	if (*mask == '~')
-		*mask = '?'; /* Is this good? No ;) */
+		*mask = '?';
 
 	return retbuf;
 }
 
-int extban_realname_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_realname_is_banned(BanContext *b)
 {
-	char *ban = banin+3;
-
-	if (match_esc(ban, client->info))
+	if (match_esc(b->banstr, b->client->info))
 		return 1;
 
 	return 0;
diff --git a/src/modules/extbans/securitygroup.c b/src/modules/extbans/securitygroup.c
@@ -24,23 +24,26 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"ExtBan ~G - Ban based on security-group",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-char *extban_securitygroup_conv_param(char *para);
-int extban_securitygroup_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2);
-int extban_securitygroup_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban);
+int extban_securitygroup_is_ok(BanContext *b);
+int extban_securitygroup_is_banned(BanContext *b);
 
 /** Called upon module init */
 MOD_INIT()
 {
 	ExtbanInfo req;
-	
-	req.flag = 'G';
+
+	memset(&req, 0, sizeof(req));
+	req.letter = 'G';
+	req.name = "security-group";
 	req.conv_param = extban_securitygroup_conv_param;
 	req.is_ok = extban_securitygroup_is_ok;
 	req.is_banned = extban_securitygroup_is_banned;
+	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
 	req.options = EXTBOPT_INVEX|EXTBOPT_TKL;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
@@ -68,12 +71,8 @@ MOD_UNLOAD()
 /* Helper function for extban_securitygroup_is_ok() and extban_securitygroup_conv_param()
  * to do ban validation.
  */
-int extban_securitygroup_generic(char *para, int strict)
+int extban_securitygroup_generic(char *mask, int strict)
 {
-	char *mask;
-
-	mask = para+3;
-
 	/* ! at the start means negative match */
 	if (*mask == '!')
 		mask++;
@@ -91,27 +90,24 @@ int extban_securitygroup_generic(char *para, int strict)
 	if (!*mask)
 		return 0; /* don't allow "~G:" nor "~G:!" */
 
-	if (strlen(mask) > SECURITYGROUPLEN + 3)
-		mask[SECURITYGROUPLEN + 3] = '\0';
-
 	return 1;
 }
 
-int extban_securitygroup_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2)
+int extban_securitygroup_is_ok(BanContext *b)
 {
-	if (MyUser(client) && (what == MODE_ADD) && (checkt == EXBCHK_PARAM))
+	if (MyUser(b->client) && (b->what == MODE_ADD) && (b->is_ok_check == EXBCHK_PARAM))
 	{
 		char banbuf[SECURITYGROUPLEN+8];
-		strlcpy(banbuf, para, sizeof(banbuf));
+		strlcpy(banbuf, b->banstr, sizeof(banbuf));
 		if (!extban_securitygroup_generic(banbuf, 1))
 		{
 			SecurityGroup *s;
-			sendnotice(client, "ERROR: Unknown security-group '%s'. Syntax: +b ~G:securitygroup or +b ~G:!securitygroup", para+3);
-			sendnotice(client, "Available security groups:");
+			sendnotice(b->client, "ERROR: Unknown security-group '%s'. Syntax: +b ~G:securitygroup or +b ~G:!securitygroup", b->banstr);
+			sendnotice(b->client, "Available security groups:");
 			for (s = securitygroups; s; s = s->next)
-				sendnotice(client, "%s", s->name);
-			sendnotice(client, "unknown-users");
-			sendnotice(client, "End of security group list.");
+				sendnotice(b->client, "%s", s->name);
+			sendnotice(b->client, "unknown-users");
+			sendnotice(b->client, "End of security group list.");
 			return 0;
 		}
 	}
@@ -119,11 +115,11 @@ int extban_securitygroup_is_ok(Client *client, Channel *channel, char *para, int
 }
 
 /** Security group extban - conv_param */
-char *extban_securitygroup_conv_param(char *para)
+const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[SECURITYGROUPLEN + 8];
 
-	strlcpy(retbuf, para, sizeof(retbuf));
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
 	if (!extban_securitygroup_generic(retbuf, 0))
 		return NULL;
 
@@ -131,11 +127,9 @@ char *extban_securitygroup_conv_param(char *para)
 }
 
 /** Is the user banned by ~G:something ? */
-int extban_securitygroup_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+int extban_securitygroup_is_banned(BanContext *b)
 {
-	char *ban = banin+3;
-
-	if (*ban == '!')
-		return !user_allowed_by_security_group_name(client, ban+1);
-	return user_allowed_by_security_group_name(client, ban);
+	if (*b->banstr == '!')
+		return !user_allowed_by_security_group_name(b->client, b->banstr+1);
+	return user_allowed_by_security_group_name(b->client, b->banstr);
 }
diff --git a/src/modules/extbans/textban.c b/src/modules/extbans/textban.c
@@ -61,30 +61,20 @@
 /** Which censor replace word to use when CENSORFEATURE is enabled. */
 #define CENSORWORD "<censored>"
 
-/** Benchmark mode.
- * Should never be used on production servers.
- * Mainly meant for debugging/profiling purposes for myself, but if you
- * have a test server and are curious about the speed of this module,
- * then you can enable it of course ;).
- */
-#undef BENCHMARK
-
-
 ModuleHeader MOD_HEADER
   = {
 	"extbans/textban",
 	"2.2",
 	"ExtBan ~T (textban) by Syzop",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
-char *extban_modeT_conv_param(char *para_in);
-int textban_check_ban(Client *client, Channel *channel, char *ban, char **msg, char **errmsg);
-int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-int extban_modeT_is_banned(Client *client, Channel *channel, char *ban, int type, char **msg, char **errmsg);
-int extban_modeT_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2);
+const char *extban_modeT_conv_param(BanContext *b, Extban *extban);
+int textban_check_ban(Client *client, Channel *channel, const char *ban, const char **msg, const char **errmsg);
+int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+int extban_modeT_is_ok(BanContext *b);
 void parse_word(const char *s, char **word, int *type);
 
 MOD_INIT()
@@ -94,10 +84,10 @@ MOD_INIT()
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
 	memset(&req, 0, sizeof(ExtbanInfo));
-	req.flag = 'T';
+	req.letter = 'T';
+	req.name = "text";
 	req.options = EXTBOPT_NOSTACKCHILD; /* disallow things like ~n:~T, as we only affect text. */
 	req.conv_param = extban_modeT_conv_param;
-	req.is_banned = extban_modeT_is_banned;
 	req.is_ok = extban_modeT_is_ok;
 
 	if (!ExtbanAdd(modinfo->handle, req))
@@ -262,21 +252,21 @@ unsigned int counttextbans(Channel *channel)
 }
 
 
-int extban_modeT_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2)
+int extban_modeT_is_ok(BanContext *b)
 {
 	int n;
 
-	if ((what == MODE_ADD) && (what2 == EXBTYPE_EXCEPT) && MyUser(client))
+	if ((b->what == MODE_ADD) && (b->ban_type == EXBTYPE_EXCEPT) && MyUser(b->client))
 		return 0; /* except is not supported */
 
 	/* We check the # of bans in the channel, may not exceed MAX_EXTBANT_PER_CHAN */
-	if ((what == MODE_ADD) && (checkt == EXBCHK_PARAM) &&
-	     MyUser(client) && !IsOper(client) &&
-	    ((n = counttextbans(channel)) >= MAX_EXTBANT_PER_CHAN))
+	if ((b->what == MODE_ADD) && (b->is_ok_check == EXBCHK_PARAM) &&
+	     MyUser(b->client) && !IsOper(b->client) &&
+	    ((n = counttextbans(b->channel)) >= MAX_EXTBANT_PER_CHAN))
 	{
 		/* We check the # of bans in the channel, may not exceed MAX_EXTBANT_PER_CHAN */
-		sendnumeric(client, ERR_BANLISTFULL, channel->chname, para);
-		sendnotice(client, "Too many textbans for this channel");
+		sendnumeric(b->client, ERR_BANLISTFULL, b->channel->name, b->banstr); // FIXME: wants b->full_banstr here
+		sendnotice(b->client, "Too many textbans for this channel");
 		return 0;
 	}
 	return 1;
@@ -298,7 +288,7 @@ char *conv_pattern_asterisks(const char *pattern)
 }
 
 /** Ban callbacks */
-char *extban_modeT_conv_param(char *para_in)
+const char *extban_modeT_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[MAX_LENGTH+1];
 	char para[MAX_LENGTH+1], *action, *text, *p;
@@ -307,7 +297,7 @@ char *extban_modeT_conv_param(char *para_in)
 	int ap = 0;
 #endif
 
-	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
 
 	/* ~T:<action>:<text>
 	 * ~T:user@host:<action>:<text> if UHOSTFEATURE is enabled
@@ -380,26 +370,20 @@ char *extban_modeT_conv_param(char *para_in)
 
 	/* Rebuild the string.. can be cut off if too long. */
 #ifdef UHOSTFEATURE
-	snprintf(retbuf, sizeof(retbuf), "~T:%s:%s:%s", uhost, action, text);
+	snprintf(retbuf, sizeof(retbuf), "%s:%s:%s", uhost, action, text);
 #else
-	snprintf(retbuf, sizeof(retbuf), "~T:%s:%s", action, text);
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", action, text);
 #endif
 	return retbuf;
 }
 
-/** This is the regular "is banned?" routine. We can't use this as we need to be called for voiced users as well */
-int extban_modeT_is_banned(Client *client, Channel *channel, char *ban, int checktype, char **msg, char **errmsg)
-{
-	return 0;
-}
-
 /** Check for text bans (censor and block) */
-int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Ban *ban;
 
 	/* +h/+o/+a/+q users bypass textbans */
-	if (is_skochanop(client, channel))
+	if (check_channel_access(client, channel, "hoaq"))
 		return HOOK_CONTINUE;
 
 	/* IRCOps with these privileges bypass textbans too */
@@ -409,21 +393,29 @@ int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp
 	/* Now we have to manually walk the banlist and check if things match */
 	for (ban = channel->banlist; ban; ban=ban->next)
 	{
-		if (!strncmp(ban->banstr, "~T:", 3))
+		char *banstr = ban->banstr;
+
+		/* Pretend time does not exist... */
+		if (!strncmp(banstr, "~t:", 3))
 		{
-			/* ~T ban */
-			if (textban_check_ban(client, channel, ban->banstr, msg, errmsg))
-				return HOOK_DENY;
-		} else
-		if (!strncmp(ban->banstr, "~t:", 3))
+			banstr = strchr(banstr+3, ':');
+			if (!banstr)
+				continue;
+			banstr++;
+		}
+		else if (!strncmp(banstr, "~time:", 6))
 		{
-			/* Stacked ~t:xx:~T ban (timed text ban) */
-			char *p = strchr(ban->banstr+3, ':');
-			if (p && !strncmp(p+1, "~T:", 3))
-			{
-				if (textban_check_ban(client, channel, p+1, msg, errmsg))
-					return HOOK_DENY;
-			}
+			banstr = strchr(banstr+6, ':');
+			if (!banstr)
+				continue;
+			banstr++;
+		}
+
+		if (!strncmp(banstr, "~T:", 3) || !strncmp(banstr, "~text:", 6))
+		{
+			/* text ban */
+			if (textban_check_ban(client, channel, banstr, msg, errmsg))
+				return HOOK_DENY;
 		}
 	}
 
@@ -431,23 +423,18 @@ int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp
 }
 
 
-int textban_check_ban(Client *client, Channel *channel, char *ban, char **msg, char **errmsg)
+int textban_check_ban(Client *client, Channel *channel, const char *ban, const char **msg, const char **errmsg)
 {
 	static char retbuf[512];
 	char filtered[512]; /* temp input buffer */
 	long fl;
 	int cleaned=0;
-	char *p;
+	const char *p;
 #ifdef UHOSTFEATURE
 	char buf[512], uhost[USERLEN + HOSTLEN + 16];
 #endif
 	char tmp[1024], *word;
 	int type;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
 
 	/* We can only filter on non-NULL text of course */
 	if ((msg == NULL) || (*msg == NULL))
@@ -460,7 +447,10 @@ int textban_check_ban(Client *client, Channel *channel, char *ban, char **msg, c
 #endif
 	strlcpy(filtered, StripControlCodes(*msg), sizeof(filtered));
 
-	p = ban + 3;
+	p = strchr(ban, ':');
+	if (!p)
+		return 0; /* "impossible" */
+	p++;
 #ifdef UHOSTFEATURE
 	/* First.. deal with userhost... */
 	strcpy(buf, p);
@@ -496,13 +486,6 @@ int textban_check_ban(Client *client, Channel *channel, char *ban, char **msg, c
 #endif
 	}
 
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "TextBan Timing: %ld microseconds (%s / %s / %d)",
-		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec),
-		client->name, channel->chname, strlen(*msg));
-#endif
-
 	if (cleaned)
 	{
 		/* check for null string */
diff --git a/src/modules/extbans/timedban.c b/src/modules/extbans/timedban.c
@@ -51,13 +51,13 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"ExtBan ~t: automatically removed timed bans",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
-char *timedban_extban_conv_param(char *para_in);
-int timedban_extban_is_ok(Client *client, Channel* channel, char *para_in, int checkt, int what, int what2);
-int timedban_is_banned(Client *client, Channel *channel, char *ban, int chktype, char **msg, char **errmsg);
+const char *timedban_extban_conv_param(BanContext *b, Extban *extban);
+int timedban_extban_is_ok(BanContext *b);
+int timedban_is_banned(BanContext *b);
 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param);
 char *timedban_chanmsg(Client *, Client *, Channel *, char *, int);
 
@@ -70,18 +70,20 @@ MOD_TEST()
 
 MOD_INIT()
 {
-ExtbanInfo extban;
+	ExtbanInfo extban;
 
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
 	memset(&extban, 0, sizeof(ExtbanInfo));
-	extban.flag = 't';
+	extban.letter = 't';
+	extban.name = "time";
 	extban.options |= EXTBOPT_ACTMODIFIER; /* not really, but ours shouldn't be stacked from group 1 */
 	extban.options |= EXTBOPT_CHSVSMODE; /* so "SVSMODE -nick" will unset affected ~t extbans */
 	extban.options |= EXTBOPT_INVEX; /* also permit timed invite-only exceptions (+I) */
 	extban.conv_param = timedban_extban_conv_param;
 	extban.is_ok = timedban_extban_is_ok;
 	extban.is_banned = timedban_is_banned;
+	extban.is_banned_events = BANCHK_ALL;
 
 	if (!ExtbanAdd(modinfo->handle, extban))
 	{
@@ -106,17 +108,18 @@ MOD_UNLOAD()
 
 /** Generic helper for our conv_param extban function.
  * Mostly copied from clean_ban_mask()
+ * FIXME: Figure out why we have this one at all and not use conv_param? ;)
  */
-char *generic_clean_ban_mask(char *mask)
+const char *generic_clean_ban_mask(BanContext *b, Extban *extban)
 {
 	char *cp, *x;
 	char *user;
 	char *host;
-	Extban *p;
 	static char maskbuf[512];
+	char *mask;
 
 	/* Work on a copy */
-	strlcpy(maskbuf, mask, sizeof(maskbuf));
+	strlcpy(maskbuf, b->banstr, sizeof(maskbuf));
 	mask = maskbuf;
 
 	cp = strchr(mask, ' ');
@@ -136,11 +139,22 @@ char *generic_clean_ban_mask(char *mask)
 	/* Extended ban? */
 	if (is_extended_ban(mask))
 	{
-		p = findmod_by_bantype(mask[1]);
-		if (!p)
+		const char *nextbanstr;
+		Extban *extban = findmod_by_bantype(mask, &nextbanstr);
+		if (!extban)
 			return NULL; /* reject unknown extban */
-		if (p->conv_param)
-			return p->conv_param(mask);
+		if (extban->conv_param)
+		{
+			const char *ret;
+			static char retbuf[512];
+			BanContext *newb = safe_alloc(sizeof(BanContext));
+			newb->banstr = nextbanstr;
+			newb->conv_options = b->conv_options;
+			ret = extban->conv_param(newb, extban);
+			ret = prefix_with_extban(ret, newb, extban, retbuf, sizeof(retbuf));
+			safe_free(newb);
+			return ret;
+		}
 		/* else, do some basic sanity checks and cut it off at 80 bytes */
 		if ((mask[1] != ':') || (mask[2] == '\0'))
 		    return NULL; /* require a ":<char>" after extban type */
@@ -169,7 +183,7 @@ char *generic_clean_ban_mask(char *mask)
 }
 
 /** Convert ban to an acceptable format (or return NULL to fully reject it) */
-char *timedban_extban_conv_param(char *para_in)
+const char *timedban_extban_conv_param(BanContext *b, Extban *extban)
 {
 	static char retbuf[MAX_LENGTH+1];
 	char para[MAX_LENGTH+1];
@@ -177,13 +191,13 @@ char *timedban_extban_conv_param(char *para_in)
 	char *durationstr; /**< Duration, such as '5' */
 	int duration;
 	char *matchby; /**< Matching method, such as 'n!u@h' */
-	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
 	static int timedban_extban_conv_param_recursion = 0;
 	
 	if (timedban_extban_conv_param_recursion)
 		return NULL; /* reject: recursion detected! */
 
-	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
 	
 	/* ~t:duration:n!u@h   for direct matching
 	 * ~t:duration:~x:.... when calling another bantype
@@ -203,12 +217,14 @@ char *timedban_extban_conv_param(char *para_in)
 	strlcpy(tmpmask, matchby, sizeof(tmpmask));
 	timedban_extban_conv_param_recursion++;
 	//newmask = extban_conv_param_nuh_or_extban(tmpmask);
-	newmask = generic_clean_ban_mask(tmpmask);
+	b->banstr = matchby; // this was previously 'tmpmask' but then it's a copy-copy-copy.. :D
+	newmask = generic_clean_ban_mask(b, extban);
 	timedban_extban_conv_param_recursion--;
 	if (!newmask || (strlen(newmask) <= 1))
 		return NULL;
 
-	snprintf(retbuf, sizeof(retbuf), "~t:%d:%s", duration, newmask);
+	//snprintf(retbuf, sizeof(retbuf), "~t:%d:%s", duration, newmask);
+	snprintf(retbuf, sizeof(retbuf), "%d:%s", duration, newmask);
 	return retbuf;
 }
 
@@ -226,54 +242,50 @@ int timedban_extban_syntax(Client *client, int checkt, char *reason)
 }
 
 /** Generic helper for sub-bans, used by our "is this ban ok?" function */
-int generic_ban_is_ok(Client *client, Channel *channel, char *mask, int checkt, int what, int what2)
+int generic_ban_is_ok(BanContext *b)
 {
-	if ((mask[0] == '~') && MyUser(client))
+	if ((b->banstr[0] == '~') && MyUser(b->client))
 	{
-		Extban *p;
+		Extban *extban;
+		const char *nextbanstr;
 
 		/* This portion is copied from clean_ban_mask() */
-		if (is_extended_ban(mask) && MyUser(client))
+		if (is_extended_ban(b->banstr) && MyUser(b->client))
 		{
-			if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",client,NULL,NULL,NULL))
+			if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL))
 			{
 				if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
 				{
-					if (checkt == EXBCHK_ACCESS_ERR)
-						sendnotice(client, "Setting/removing of extended bans has been disabled");
+					if (b->is_ok_check == EXBCHK_ACCESS_ERR)
+						sendnotice(b->client, "Setting/removing of extended bans has been disabled");
 					return 0; /* REJECT */
 				}
-				if (strchr(RESTRICT_EXTENDEDBANS, mask[1]))
+				if (strchr(RESTRICT_EXTENDEDBANS, b->banstr[1]))
 				{
-					if (checkt == EXBCHK_ACCESS_ERR)
-						sendnotice(client, "Setting/removing of extended bantypes '%s' has been disabled", RESTRICT_EXTENDEDBANS);
+					if (b->is_ok_check == EXBCHK_ACCESS_ERR)
+						sendnotice(b->client, "Setting/removing of extended bantypes '%s' has been disabled", RESTRICT_EXTENDEDBANS);
 					return 0; /* REJECT */
 				}
 			}
 			/* And next is inspired by cmd_mode */
-			p = findmod_by_bantype(mask[1]);
-			if (checkt == EXBCHK_ACCESS)
-			{
-				if (p && p->is_ok && !p->is_ok(client, channel, mask, EXBCHK_ACCESS, what, what2) &&
-				    !ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
-				{
-					return 0; /* REJECT */
-				}
-			} else
-			if (checkt == EXBCHK_ACCESS_ERR)
+			extban = findmod_by_bantype(b->banstr, &nextbanstr);
+			if (extban && extban->is_ok)
 			{
-				if (p && p->is_ok && !p->is_ok(client, channel, mask, EXBCHK_ACCESS, what, what2) &&
-				    !ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
+				b->banstr = nextbanstr;
+				if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR))
 				{
-					p->is_ok(client, channel, mask, EXBCHK_ACCESS_ERR, what, what2);
-					return 0; /* REJECT */
-				}
-			} else
-			if (checkt == EXBCHK_PARAM)
-			{
-				if (p && p->is_ok && !p->is_ok(client, channel, mask, EXBCHK_PARAM, what, what2))
+					if (!extban->is_ok(b) &&
+					    !ValidatePermissionsForPath("channel:override:mode:extban",b->client,NULL,b->channel,NULL))
+					{
+						return 0; /* REJECT */
+					}
+				} else
+				if (b->is_ok_check == EXBCHK_PARAM)
 				{
-					return 0; /* REJECT */
+					if (!extban->is_ok(b))
+					{
+						return 0; /* REJECT */
+					}
 				}
 			}
 		}
@@ -288,7 +300,7 @@ int generic_ban_is_ok(Client *client, Channel *channel, char *mask, int checkt, 
 }
 
 /** Validate ban ("is this ban ok?") */
-int timedban_extban_is_ok(Client *client, Channel* channel, char *para_in, int checkt, int what, int what2)
+int timedban_extban_is_ok(BanContext *b)
 {
 	char para[MAX_LENGTH+1];
 	char tmpmask[MAX_LENGTH+1];
@@ -300,13 +312,13 @@ int timedban_extban_is_ok(Client *client, Channel* channel, char *para_in, int c
 	int res;
 
 	/* Always permit deletion */
-	if (what == MODE_DEL)
+	if (b->what == MODE_DEL)
 		return 1;
 
 	if (timedban_extban_is_ok_recursion)
 		return 0; /* Recursion detected (~t:1:~t:....) */
 
-	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
 	
 	/* ~t:duration:n!u@h   for direct matching
 	 * ~t:duration:~x:.... when calling another bantype
@@ -315,18 +327,19 @@ int timedban_extban_is_ok(Client *client, Channel* channel, char *para_in, int c
 	durationstr = para;
 	matchby = strchr(para, ':');
 	if (!matchby || !matchby[1])
-		return timedban_extban_syntax(client, checkt, "Invalid syntax");
+		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
 	*matchby++ = '\0';
 
 	duration = atoi(durationstr);
 
 	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
-		return timedban_extban_syntax(client, checkt, "Invalid duration time");
+		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid duration time");
 
 	strlcpy(tmpmask, matchby, sizeof(tmpmask));
 	timedban_extban_is_ok_recursion++;
-	//res = extban_is_ok_nuh_extban(client, channel, tmpmask, checkt, what, what2);
-	res = generic_ban_is_ok(client, channel, tmpmask, checkt, what, what2);
+	//res = extban_is_ok_nuh_extban(b->client, b->channel, tmpmask, b->is_ok_check, b->what, b->ban_type);
+	b->banstr = tmpmask;
+	res = generic_ban_is_ok(b);
 	timedban_extban_is_ok_recursion--;
 	if (res == 0)
 	{
@@ -334,41 +347,48 @@ int timedban_extban_is_ok(Client *client, Channel* channel, char *para_in, int c
 		 * invalid n!u@h syntax, unknown (sub)extbantype,
 		 * disabled extban type in conf, too much recursion, etc.
 		 */
-		return timedban_extban_syntax(client, checkt, "Invalid matcher");
+		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
 	}
 
 	return 1; /* OK */
 }
 
 /** Check if the user is currently banned */
-int timedban_is_banned(Client *client, Channel *channel, char *ban, int chktype, char **msg, char **errmsg)
+int timedban_is_banned(BanContext *b)
 {
-	if (strncmp(ban, "~t:", 3))
-		return 0; /* not for us */
-	ban = strchr(ban+3, ':'); /* skip time argument */
-	if (!ban)
+	b->banstr = strchr(b->banstr, ':'); /* skip time argument */
+	if (!b->banstr)
 		return 0; /* invalid fmt */
-	ban++;
+	b->banstr++; /* skip over final semicolon */
 
-	return ban_check_mask(client, channel, ban, chktype, msg, errmsg, 0);
+	return ban_check_mask(b);
 }
 
-/** Helper to check if the ban has been expired */
+/** Helper to check if the ban has been expired.
+ */
 int timedban_has_ban_expired(Ban *ban)
 {
 	char *banstr = ban->banstr;
-	char *p;
+	char *p1, *p2;
 	int t;
 	time_t expire_on;
 
-	if (strncmp(banstr, "~t:", 3))
+	/* The caller has only performed a very light check (string starting
+	 * with ~t, in the interest of performance), so we don't know yet if
+	 * it REALLY is a timed ban. We check that first here...
+	 */
+	if (!strncmp(banstr, "~t:", 3))
+		p1 = banstr + 3;
+	else if (!strncmp(banstr, "~time:", 6))
+		p1 = banstr + 6;
+	else
 		return 0; /* not for us */
-	p = strchr(banstr+3, ':'); /* skip time argument */
-	if (!p)
+	p2 = strchr(p1+1, ':'); /* skip time argument */
+	if (!p2)
 		return 0; /* invalid fmt */
-	*p = '\0'; /* danger.. must restore!! */
-	t = atoi(banstr+3);
-	*p = ':'; /* restored.. */
+	*p2 = '\0'; /* danger.. must restore!! */
+	t = atoi(p1);
+	*p2 = ':'; /* restored.. */
 	
 	expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA;
 	
@@ -398,14 +418,14 @@ EVENT(timedban_timeout)
 		 * is too costly. So we stick with this. It should be
 		 * good enough. Alternative would be some channel->id value.
 		 */
-		if (((unsigned int)channel->chname[1] % TIMEDBAN_TIMER_ITERATION_SPLIT) != current_iteration)
+		if (((unsigned int)channel->name[1] % TIMEDBAN_TIMER_ITERATION_SPLIT) != current_iteration)
 			continue; /* not this time, maybe next */
 
 		*mbuf = *pbuf = '\0';
 		for (ban = channel->banlist; ban; ban=nextban)
 		{
 			nextban = ban->next;
-			if (!strncmp(ban->banstr, "~t:", 3) && timedban_has_ban_expired(ban))
+			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
 			{
 				add_send_mode_param(channel, &me, '-',  'b', ban->banstr);
 				del_listmode(&channel->banlist, channel, ban->banstr);
@@ -414,7 +434,7 @@ EVENT(timedban_timeout)
 		for (ban = channel->exlist; ban; ban=nextban)
 		{
 			nextban = ban->next;
-			if (!strncmp(ban->banstr, "~t:", 3) && timedban_has_ban_expired(ban))
+			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
 			{
 				add_send_mode_param(channel, &me, '-',  'e', ban->banstr);
 				del_listmode(&channel->exlist, channel, ban->banstr);
@@ -423,7 +443,7 @@ EVENT(timedban_timeout)
 		for (ban = channel->invexlist; ban; ban=nextban)
 		{
 			nextban = ban->next;
-			if (!strncmp(ban->banstr, "~t:", 3) && timedban_has_ban_expired(ban))
+			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
 			{
 				add_send_mode_param(channel, &me, '-',  'I', ban->banstr);
 				del_listmode(&channel->invexlist, channel, ban->banstr);
@@ -433,8 +453,8 @@ EVENT(timedban_timeout)
 		{
 			MessageTag *mtags = NULL;
 			new_message(&me, NULL, &mtags);
-			sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->chname, mbuf, pbuf);
-			sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->chname, mbuf, pbuf);
+			sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf);
+			sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf);
 			free_message_tags(mtags);
 			*pbuf = 0;
 		}
@@ -484,8 +504,8 @@ void add_send_mode_param(Channel *channel, Client *from, char what, char mode, c
 		MessageTag *mtags = NULL;
 
 		new_message(&me, NULL, &mtags);
-		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->chname, mbuf, pbuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->chname, mbuf, pbuf);
+		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf);
 		free_message_tags(mtags);
 		send = 0;
 		*pbuf = 0;
diff --git a/src/modules/extended-monitor.c b/src/modules/extended-monitor.c
@@ -0,0 +1,153 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/extended-monitor.c
+ *   (C) 2021 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"
+
+long CAP_EXTENDED_MONITOR = 0L;
+
+int extended_monitor_away(Client *client, MessageTag *mtags, const char *reason, int already_as_away);
+int extended_monitor_account_login(Client *client, MessageTag *mtags);
+int extended_monitor_userhost_changed(Client *client, const char *olduser, const char *oldhost);
+int extended_monitor_realname_changed(Client *client, const char *oldinfo);
+int extended_monitor_notification(Client *client, Watch *watch, Link *lp, int event);
+
+ModuleHeader MOD_HEADER
+  = {
+	"extended-monitor",
+	"5.0",
+	"extended functionality for /monitor", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+	ClientCapability *c;
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	ModDataInfo mreq;
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "draft/extended-monitor";
+	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_EXTENDED_MONITOR);
+	if (!c)
+	{
+		config_error("[%s] Failed to request extended-monitor cap: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_AWAY, 0, extended_monitor_away);
+	HookAdd(modinfo->handle, HOOKTYPE_ACCOUNT_LOGIN, 0, extended_monitor_account_login);
+	HookAdd(modinfo->handle, HOOKTYPE_USERHOST_CHANGED, 0, extended_monitor_userhost_changed);
+	HookAdd(modinfo->handle, HOOKTYPE_REALNAME_CHANGED, 0, extended_monitor_realname_changed);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int extended_monitor_away(Client *client, MessageTag *mtags, const char *reason, int already_as_away)
+{
+	if (reason)
+		watch_check(client, WATCH_EVENT_AWAY, extended_monitor_notification);
+	else
+		watch_check(client, WATCH_EVENT_NOTAWAY, extended_monitor_notification);
+
+	return 0;
+}
+
+int extended_monitor_account_login(Client *client, MessageTag *mtags)
+{
+	if (IsLoggedIn(client))
+		watch_check(client, WATCH_EVENT_LOGGEDIN, extended_monitor_notification);
+	else
+		watch_check(client, WATCH_EVENT_LOGGEDOUT, extended_monitor_notification);
+
+	return 0;
+}
+
+int extended_monitor_userhost_changed(Client *client, const char *olduser, const char *oldhost)
+{
+	watch_check(client, WATCH_EVENT_USERHOST, extended_monitor_notification);
+	return 0;
+}
+
+int extended_monitor_realname_changed(Client *client, const char *oldinfo)
+{
+	watch_check(client, WATCH_EVENT_REALNAME, extended_monitor_notification);
+	return 0;
+}
+
+int extended_monitor_notification(Client *client, Watch *watch, Link *lp, int event)
+{
+	if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR))
+		return 0;
+
+	if (!HasCapabilityFast(lp->value.client, CAP_EXTENDED_MONITOR))
+		return 0; /* this client does not support our notifications */
+
+	if (has_common_channels(client, lp->value.client))
+		return 0; /* will be notified anyway */
+
+	switch (event)
+	{
+		case WATCH_EVENT_AWAY:
+			if (HasCapability(lp->value.client, "away-notify"))
+				sendto_prefix_one(lp->value.client, client, NULL, ":%s AWAY :%s", client->name, client->user->away);
+			break;
+		case WATCH_EVENT_NOTAWAY:
+			if (HasCapability(lp->value.client, "away-notify"))
+				sendto_prefix_one(lp->value.client, client, NULL, ":%s AWAY", client->name);
+			break;
+		case WATCH_EVENT_LOGGEDIN:
+			if (HasCapability(lp->value.client, "account-notify"))
+				sendto_prefix_one(lp->value.client, client, NULL, ":%s ACCOUNT :%s", client->name, client->user->account);
+			break;
+		case WATCH_EVENT_LOGGEDOUT:
+			if (HasCapability(lp->value.client, "account-notify"))
+				sendto_prefix_one(lp->value.client, client, NULL, ":%s ACCOUNT :*", client->name);
+			break;
+		case WATCH_EVENT_USERHOST:
+			if (HasCapability(lp->value.client, "chghost"))
+				sendto_prefix_one(lp->value.client, client, NULL, ":%s CHGHOST %s %s", client->name, client->user->username, GetHost(client));
+			break;
+		case WATCH_EVENT_REALNAME:
+			if (HasCapability(lp->value.client, "setname"))
+				sendto_prefix_one(lp->value.client, client, NULL, ":%s SETNAME :%s", client->name, client->info);
+			break;
+		default:
+			break;
+	}
+	
+	return 0;
+}
+
diff --git a/src/modules/extjwt.c b/src/modules/extjwt.c
@@ -0,0 +1,1151 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/extjwt.c
+ *   (C) 2021 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"
+
+#if defined(__GNUC__)
+/* Temporarily ignore these for this entire file. FIXME later when updating the code for OpenSSL 3: */
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+/* internal definitions */
+
+#define MSG_EXTJWT	"EXTJWT"
+#define MYCONF "extjwt"
+
+#undef NEW_ISUPPORT /* enable this for https://github.com/ircv3/ircv3-specifications/pull/341#issuecomment-617038799 */
+
+#define EXTJWT_METHOD_NOT_SET 0
+#define EXTJWT_METHOD_HS256 1
+#define EXTJWT_METHOD_HS384 2
+#define EXTJWT_METHOD_HS512 3
+#define EXTJWT_METHOD_RS256 4
+#define EXTJWT_METHOD_RS384 5
+#define EXTJWT_METHOD_RS512 6
+#define EXTJWT_METHOD_ES256 7
+#define EXTJWT_METHOD_ES384 8
+#define EXTJWT_METHOD_ES512 9
+#define EXTJWT_METHOD_NONE 10
+
+#define NEEDS_KEY(x) (x>=EXTJWT_METHOD_RS256 && x<=EXTJWT_METHOD_ES512)
+
+#define URL_LENGTH 4096
+#define MODES_SIZE 41 /* about 10 mode chars */
+#define TS_LENGTH 19 /* 64-bit integer */
+#define MAX_TOKEN_CHUNK (510-sizeof(extjwt_message_pattern)-HOSTLEN-CHANNELLEN)
+
+/* OpenSSL 1.0.x compatibility */
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
+{
+	if (pr != NULL)
+		*pr = sig->r;
+	if (ps != NULL)
+		*ps = sig->s;
+}
+#endif
+
+/* struct definitions */
+
+struct extjwt_config {
+	time_t exp_delay;
+	char *secret;
+	int method;
+	char *vfy;
+};
+
+struct jwt_service {
+	char *name;
+	struct extjwt_config *cfg;
+	struct jwt_service *next;
+};
+
+/* function declarations */
+
+CMD_FUNC(cmd_extjwt);
+char *extjwt_make_payload(Client *client, Channel *channel, struct extjwt_config *config);
+char *extjwt_generate_token(const char *payload, struct extjwt_config *config);
+void b64url(char *b64);
+unsigned char *extjwt_hmac_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen);
+unsigned char *extjwt_sha_pem_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen);
+unsigned char *extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen);
+char *extjwt_gen_header(int method);
+int extjwt_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int extjwt_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+int extjwt_configposttest(int *errs);
+void extjwt_free_services(struct jwt_service **services);
+struct jwt_service *find_jwt_service(struct jwt_service *services, const char *name);
+int extjwt_valid_integer_string(const char *in, int min, int max);
+char *extjwt_test_key(const char *file, int method);
+char *extjwt_read_file_contents(const char *file, int absolute, int *size);
+int EXTJWT_METHOD_from_string(const char *in);
+#ifdef NEW_ISUPPORT
+char *extjwt_isupport_param(void);
+#endif
+
+/* string constants */
+
+const char extjwt_message_pattern[] = ":%s EXTJWT %s %s %s%s";
+
+/* global structs */
+
+ModuleHeader MOD_HEADER = {
+	"extjwt",
+	"6.0",
+	"Command /EXTJWT (web service authorization)", 
+	"UnrealIRCd Team",
+	"unrealircd-6"
+};
+
+struct {
+	int have_secret;
+	int have_key;
+	int have_method;
+	int have_expire;
+	int have_vfy;
+	char *key_filename;
+} cfg_state;
+
+struct extjwt_config cfg;
+struct jwt_service *jwt_services;
+
+MOD_TEST()
+{
+	memset(&cfg_state, 0, sizeof(cfg_state));
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, extjwt_configtest);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, extjwt_configposttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_EXTJWT, cmd_extjwt, 2, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, extjwt_configrun);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	struct jwt_service *service = jwt_services;
+#ifdef NEW_ISUPPORT
+	ISupportAdd(modinfo->handle, "EXTJWT", extjwt_isupport_param());
+#else
+	ISupportAdd(modinfo->handle, "EXTJWT", "1");
+#endif
+	while (service)
+	{ /* copy default exp to all services not having one specified */
+		if (service->cfg->exp_delay == 0)
+			service->cfg->exp_delay = cfg.exp_delay;
+		service = service->next;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	extjwt_free_services(&jwt_services);
+	return MOD_SUCCESS;
+}
+
+#ifdef NEW_ISUPPORT
+char *extjwt_isupport_param(void)
+{
+	struct jwt_service *services = jwt_services;
+	int count = 0;
+	static char buf[500];
+	strlcpy(buf, "V:1", sizeof(buf));
+	while (services)
+	{
+		strlcat(buf, count?",":"&S:", sizeof(buf));
+		strlcat(buf, services->name, sizeof(buf));
+		count++;
+		services = services->next;
+	}
+	return buf;
+}
+#endif
+
+void extjwt_free_services(struct jwt_service **services){
+	struct jwt_service *ss, *next;
+	ss = *services;
+	while (ss)
+	{
+		next = ss->next;
+		safe_free(ss->name);
+		if (ss->cfg)
+			safe_free(ss->cfg->secret);
+		safe_free(ss->cfg);
+		safe_free(ss);
+		ss = next;
+	}
+	*services = NULL;
+}
+
+struct jwt_service *find_jwt_service(struct jwt_service *services, const char *name)
+{
+	if (!name)
+		return NULL;
+	while (services)
+	{
+		if (services->name && !strcmp(services->name, name))
+			return services;
+		services = services->next;
+	}
+	return NULL;
+}
+
+int extjwt_valid_integer_string(const char *in, int min, int max)
+{
+	int i, val;
+	if (BadPtr(in))
+		return 0;
+	for (i=0; in[i]; i++){
+		if (!isdigit(in[i]))
+			return 0;
+	}
+	val = atoi(in);
+	if (val < min || val > max)
+		return 0;
+	return 1;
+}
+
+int vfy_url_is_valid(const char *string)
+{
+	if (strstr(string, "http://") == string || strstr(string, "https://") == string)
+	{
+		if (strstr(string, "%s"))
+			return 1;
+	}
+	return 0;
+}
+
+char *extjwt_test_key(const char *file, int method)
+{ /* returns NULL when valid */
+	int fsize;
+	char *fcontent = NULL;
+	char *retval = NULL;
+	BIO *bufkey = NULL;
+	EVP_PKEY *pkey = NULL;
+	int type, pkey_type;
+	do {
+		switch (method)
+		{
+			case EXTJWT_METHOD_RS256: case EXTJWT_METHOD_RS384: case EXTJWT_METHOD_RS512:
+				type = EVP_PKEY_RSA;
+				break;
+			case EXTJWT_METHOD_ES256: case EXTJWT_METHOD_ES384: case EXTJWT_METHOD_ES512:
+				type = EVP_PKEY_EC;
+				break;
+			default:
+				retval = "Internal error (invalid type)";
+				return retval;
+		}
+		fcontent = extjwt_read_file_contents(file, 0, &fsize);
+		if (!fcontent)
+		{
+			retval = "Cannot open file";
+			break;
+		}
+		if (fsize == 0)
+		{
+			retval = "File is empty";
+			break;
+		}
+		if (!(bufkey = BIO_new_mem_buf(fcontent, fsize)))
+		{
+			retval = "Unknown error";
+			break;
+		}
+		if (!(pkey = PEM_read_bio_PrivateKey(bufkey, NULL, NULL, NULL)))
+		{
+			retval = "Key is invalid";
+			break;
+		}
+		pkey_type = EVP_PKEY_id(pkey);
+		if (type != pkey_type)
+		{
+			retval = "Key does not match method";
+			break;
+		}
+	} while (0);
+	safe_free(fcontent);
+	if (bufkey)
+		BIO_free(bufkey);
+	if (pkey)
+		EVP_PKEY_free(pkey);
+	return retval;
+}
+
+int EXTJWT_METHOD_from_string(const char *in)
+{
+	if (!strcmp(in, "HS256"))
+		return EXTJWT_METHOD_HS256;
+	if (!strcmp(in, "HS384"))
+		return EXTJWT_METHOD_HS384;
+	if (!strcmp(in, "HS512"))
+		return EXTJWT_METHOD_HS512;
+	if (!strcmp(in, "RS256"))
+		return EXTJWT_METHOD_RS256;
+	if (!strcmp(in, "RS384"))
+		return EXTJWT_METHOD_RS384;
+	if (!strcmp(in, "RS512"))
+		return EXTJWT_METHOD_RS512;
+	if (!strcmp(in, "ES256"))
+		return EXTJWT_METHOD_ES256;
+	if (!strcmp(in, "ES384"))
+		return EXTJWT_METHOD_ES384;
+	if (!strcmp(in, "ES512"))
+		return EXTJWT_METHOD_ES512;
+	if (!strcmp(in, "NONE"))
+		return EXTJWT_METHOD_NONE;
+	return EXTJWT_METHOD_NOT_SET;
+}
+
+/* Configuration is described in conf/modules.optional.conf */
+
+int extjwt_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep, *cep2;
+	int i;
+	struct jwt_service *services = NULL;
+	struct jwt_service **ss = &services; /* list for checking whether service names repeat */
+	int have_ssecret, have_smethod, have_svfy, have_scert;
+	unsigned int sfilename_line_number = 0;
+	char *sfilename = NULL;
+
+	if (type != CONFIG_MAIN)
+		return 0;
+
+	if (!ce || strcmp(ce->name, MYCONF))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: blank %s::%s without value", cep->file->filename, cep->line_number, MYCONF, cep->name);
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "method"))
+		{
+			if (cfg_state.have_method)
+			{
+				config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+				continue;
+			}
+			cfg_state.have_method = EXTJWT_METHOD_from_string(cep->value);
+			if (cfg_state.have_method == EXTJWT_METHOD_NOT_SET)
+			{
+				config_error("%s:%i: invalid value %s::%s \"%s\" (check docs for allowed options)", cep->file->filename, cep->line_number, MYCONF, cep->name, cep->value);
+				errors++;
+			}
+			continue;
+		}
+		if (!strcmp(cep->name, "expire-after"))
+		{
+			if (!extjwt_valid_integer_string(cep->value, 1, 9999))
+			{
+				config_error("%s:%i: %s::%s must be an integer between 1 and 9999 (seconds)", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+			}
+			continue;
+		}
+		if (!strcmp(cep->name, "secret"))
+		{
+			if (cfg_state.have_secret)
+			{
+				config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+				continue;
+			}
+			cfg_state.have_secret = 1;
+			if (strlen(cep->value) < 4)
+			{
+				config_error("%s:%i: Secret specified in %s::%s is too short!", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+			}
+			continue;
+		}
+		if (!strcmp(cep->name, "key"))
+		{
+			if (cfg_state.have_key)
+			{
+				config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+				continue;
+			}
+			if (!is_file_readable(cep->value, CONFDIR))
+			{
+				config_error("%s:%i: Cannot open file \"%s\" specified in %s::%s for reading", cep->file->filename, cep->line_number, cep->value, MYCONF, cep->name);
+				errors++;
+			}
+			safe_strdup(cfg_state.key_filename, cep->value);
+			cfg_state.have_key = 1;
+			continue;
+		}
+		if (!strcmp(cep->name, "verify-url"))
+		{
+			if (cfg_state.have_vfy)
+			{
+				config_error("%s:%i: duplicate %s:%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+				continue;
+			}
+			cfg_state.have_vfy = 1;
+			if (!vfy_url_is_valid(cep->value))
+			{
+				config_error("%s:%i: Optional URL specified in %s::%s is invalid!", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+				continue;
+			}
+			if (strlen(cep->value) > URL_LENGTH)
+			{
+				config_error("%s:%i: Optional URL specified in %s::%s is too long!", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+			}
+			continue;
+		}
+		if (!strcmp(cep->name, "service"))
+		{
+			have_ssecret = 0;
+			have_smethod = 0;
+			have_svfy = 0;
+			have_scert = 0;
+			if (strchr(cep->value, ' ') || strchr(cep->value, ','))
+			{
+				config_error("%s:%i: Invalid %s::%s name (contains spaces or commas)", cep->file->filename, cep->line_number, MYCONF, cep->name);
+				errors++;
+				continue;
+			}
+			if (find_jwt_service(services, cep->value))
+			{
+				config_error("%s:%i: Duplicate %s::%s name \"%s\"", cep->file->filename, cep->line_number, MYCONF, cep->name, cep->value);
+				errors++;
+				continue;
+			}
+			*ss = safe_alloc(sizeof(struct jwt_service)); /* store the new name for further checking */
+			safe_strdup((*ss)->name, cep->value);
+			ss = &(*ss)->next;
+			for (cep2 = cep->items; cep2; cep2 = cep2->next)
+			{
+				if (!cep2->name || !cep2->value || !cep2->value[0])
+				{
+					config_error("%s:%i: blank/incomplete %s::service entry", cep2->file->filename, cep2->line_number, MYCONF);
+					errors++;
+					continue;
+				}
+
+				if (!strcmp(cep2->name, "method"))
+				{
+					if (have_smethod)
+					{
+						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+						continue;
+					}
+					have_smethod = EXTJWT_METHOD_from_string(cep2->value);
+					if (have_smethod == EXTJWT_METHOD_NOT_SET || have_smethod == EXTJWT_METHOD_NONE)
+					{
+						config_error("%s:%i: invalid value of optional %s::service::%s \"%s\" (check docs for allowed options)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name, cep2->value);
+						errors++;
+					}
+					continue;
+				}
+
+				if (!strcmp(cep2->name, "secret"))
+				{
+					if (have_ssecret)
+					{
+						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+						continue;
+					}
+					have_ssecret = 1;
+					if (strlen(cep2->value) < 4) /* TODO maybe a better check? */
+					{
+						config_error("%s:%i: Secret specified in %s::service::%s is too short!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+					}
+					continue;
+				}
+
+				if (!strcmp(cep2->name, "key"))
+				{
+					if (have_scert)
+					{
+						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+						continue;
+					}
+					if (!is_file_readable(cep2->value, CONFDIR))
+					{
+						config_error("%s:%i: Cannot open file \"%s\" specified in %s::service::%s for reading", cep2->file->filename, cep2->line_number, cep2->value, MYCONF, cep2->name);
+						errors++;
+					}
+					have_scert = 1;
+					safe_strdup(sfilename, cep2->value);
+					sfilename_line_number = cep2->line_number;
+					continue;
+				}
+
+				if (!strcmp(cep2->name, "expire-after"))
+				{
+					if (!extjwt_valid_integer_string(cep2->value, 1, 9999))
+					{
+						config_error("%s:%i: %s::%s must be an integer between 1 and 9999 (seconds)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+					}
+					continue;
+				}
+
+				if (!strcmp(cep2->name, "verify-url"))
+				{
+					if (have_svfy)
+					{
+						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+						continue;
+					}
+					have_svfy = 1;
+					if (!vfy_url_is_valid(cep2->value))
+					{
+						config_error("%s:%i: Optional URL specified in %s::service::%s is invalid!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+						continue;
+					}
+					if (strlen(cep2->value) > URL_LENGTH)
+					{
+						config_error("%s:%i: Optional URL specified in %s::service::%s is too long!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+						errors++;
+					}
+					continue;
+				}
+
+				config_error("%s:%i: invalid %s::service attribute %s (must be one of: name, secret, expire-after)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
+				errors++;
+			}
+			if (!have_smethod)
+			{
+				config_error("%s:%i: invalid %s::service entry (no %s::service::method specfied)", cep->file->filename, cep->line_number, MYCONF, MYCONF);
+				errors++;
+				continue;
+			}
+			if (have_ssecret && NEEDS_KEY(have_smethod))
+			{
+				config_error("%s:%i: invalid %s::service entry (this method needs %s::service::key and not %s::service::secret option)", cep->file->filename, cep->line_number, MYCONF, MYCONF, MYCONF);
+				errors++;
+				continue;
+			}
+			if (have_scert && !NEEDS_KEY(have_smethod))
+			{
+				config_error("%s:%i: invalid %s::service entry (this method needs %s::service::secret and not %s::service::key option)", cep->file->filename, cep->line_number, MYCONF, MYCONF, MYCONF);
+				errors++;
+				continue;
+			}
+			if (!have_ssecret && !NEEDS_KEY(have_smethod))
+			{
+				config_error("%s:%i: invalid %s::service entry (must contain %s::service::secret option)", cep->file->filename, cep->line_number, MYCONF, MYCONF);
+				errors++;
+				continue;
+			}
+			if (!have_scert && NEEDS_KEY(have_smethod)) {
+				config_error("%s:%i: invalid %s::service entry (must contain %s::service::key option)", cep->file->filename, cep->line_number, MYCONF, MYCONF);
+				errors++;
+				continue;
+			}
+			if (NEEDS_KEY(have_smethod) && have_scert)
+			{
+				char *keyerr;
+				keyerr = extjwt_test_key(sfilename, have_smethod);
+				if (keyerr)
+				{
+					config_error("%s:%i: Invalid key file specified for %s::key: %s", cep->file->filename, sfilename_line_number, MYCONF, keyerr);
+					errors++;
+				}
+			}
+			continue;
+		}
+		config_error("%s:%i: unknown directive %s::%s", cep->file->filename, cep->line_number, MYCONF, cep->name);
+		errors++;
+	}
+	*errs = errors;
+	extjwt_free_services(&services);
+	if (errors)
+		safe_free(cfg_state.key_filename);
+	safe_free(sfilename);
+	return errors ? -1 : 1;
+}
+
+int extjwt_configposttest(int *errs)
+{
+	int errors = 0;
+	if (cfg_state.have_method == EXTJWT_METHOD_NOT_SET)
+	{
+		config_error("No %s::method specfied!", MYCONF);
+		errors++;
+	} else
+	{
+		if (cfg_state.have_method != EXTJWT_METHOD_NONE && !NEEDS_KEY(cfg_state.have_method) && !cfg_state.have_secret)
+		{
+			config_error("No %s::secret specfied as required by requested method!", MYCONF);
+			errors++;
+		}
+		if ((cfg_state.have_method == EXTJWT_METHOD_NONE || NEEDS_KEY(cfg_state.have_method)) && cfg_state.have_secret)
+		{
+			config_error("A %s::secret specfied but it should not be when using requested method!", MYCONF);
+			errors++;
+		}
+		if (NEEDS_KEY(cfg_state.have_method) && !cfg_state.have_key)
+		{
+			config_error("No %s::key specfied as required by requested method!", MYCONF);
+			errors++;
+		}
+		if (!NEEDS_KEY(cfg_state.have_method) && cfg_state.have_key)
+		{
+			config_error("A %s::key specfied but it should not be when using requested method!", MYCONF);
+			errors++;
+		}
+		if (NEEDS_KEY(cfg_state.have_method) && cfg_state.have_key && cfg_state.key_filename)
+		{
+			char *keyerr;
+			
+			keyerr = extjwt_test_key(cfg_state.key_filename, cfg_state.have_method);
+			if (keyerr)
+			{
+				config_error("Invalid key file specified for %s::key: %s", MYCONF, keyerr);
+				errors++;
+			}
+		}
+	}
+	safe_free(cfg_state.key_filename);
+	if (errors)
+	{
+		*errs = errors;
+		return -1;
+	}
+	/* setting defaults, FIXME this may behave incorrectly if there's another module failing POSTTEST */
+	if (!cfg_state.have_expire)
+		cfg.exp_delay = 30;
+	/* prepare service list to load new data */
+	extjwt_free_services(&jwt_services);
+	return 1;
+}
+
+int extjwt_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{ /* actually use the new configuration data */
+	ConfigEntry *cep, *cep2;
+	struct jwt_service **ss = &jwt_services;
+	if (*ss)
+		ss = &((*ss)->next);
+
+	if (type != CONFIG_MAIN)
+		return 0;
+
+	if (!ce || strcmp(ce->name, MYCONF))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "method"))
+		{
+			cfg.method = EXTJWT_METHOD_from_string(cep->value);
+			continue;
+		}
+		if (!strcmp(cep->name, "expire-after"))
+		{
+			cfg.exp_delay = atoi(cep->value);
+			continue;
+		}
+		if (!strcmp(cep->name, "secret"))
+		{
+			cfg.secret = strdup(cep->value);
+			continue;
+		}
+		if (!strcmp(cep->name, "key"))
+		{
+			cfg.secret = extjwt_read_file_contents(cep->value, 0, NULL);
+			continue;
+		}
+		if (!strcmp(cep->name, "verify-url"))
+		{
+			cfg.vfy = strdup(cep->value);
+			continue;
+		}
+		if (!strcmp(cep->name, "service"))
+		{ /* nested block */
+			*ss = safe_alloc(sizeof(struct jwt_service));
+			(*ss)->cfg = safe_alloc(sizeof(struct extjwt_config));
+			safe_strdup((*ss)->name, cep->value); /* copy the service name */
+			for (cep2 = cep->items; cep2; cep2 = cep2->next)
+			{
+				if (!strcmp(cep2->name, "method"))
+				{
+					(*ss)->cfg->method = EXTJWT_METHOD_from_string(cep2->value);
+					continue;
+				}
+				if (!strcmp(cep2->name, "expire-after"))
+				{
+					(*ss)->cfg->exp_delay = atoi(cep2->value);
+					continue;
+				}
+				if (!strcmp(cep2->name, "secret"))
+				{
+					(*ss)->cfg->secret = strdup(cep2->value);
+					continue;
+				}
+				if (!strcmp(cep2->name, "key"))
+				{
+					(*ss)->cfg->secret = extjwt_read_file_contents(cep2->value, 0, NULL);
+					continue;
+				}
+				if (!strcmp(cep2->name, "verify-url"))
+				{
+					(*ss)->cfg->vfy = strdup(cep2->value);
+					continue;
+				}
+			}
+			ss = &((*ss)->next);
+		}
+	}
+	return 1;
+}
+
+char *extjwt_read_file_contents(const char *file, int absolute, int *size)
+{
+	FILE *f = NULL;
+	int fsize;
+	char *filename = NULL;
+	char *buf = NULL;
+	do
+	{
+		safe_strdup(filename, file);
+		if (!absolute)
+			convert_to_absolute_path(&filename, CONFDIR);
+		f = fopen(filename, "rb");
+		if (!f)
+			break;
+		fseek(f, 0, SEEK_END);
+		fsize = ftell(f);
+		fseek(f, 0, SEEK_SET);
+		buf = safe_alloc(fsize + 1);
+		fsize = fread(buf, 1, fsize, f);
+		buf[fsize] = '\0';
+		if (size)
+			*size = fsize;
+		fclose(f);
+	} while (0);
+	safe_free(filename);
+	if (!buf && size)
+		*size = 0;
+	return buf;
+}
+
+CMD_FUNC(cmd_extjwt)
+{
+	Channel *channel;
+	char *payload;
+	char *token, *full_token;
+	struct jwt_service *service = NULL;
+	struct extjwt_config *config;
+	int last = 0;
+	char message[MAX_TOKEN_CHUNK+1];
+	if (parc < 2 || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, MSG_EXTJWT);
+		return;
+	}
+	if (parv[1][0] == '*' && parv[1][1] == '\0')
+	{
+		channel = NULL; /* not linked to a channel */
+	} else
+	{
+		channel = find_channel(parv[1]);
+		if (!channel)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+			return;
+		}
+	}
+	if (parc > 2 && !BadPtr(parv[2]))
+	{
+		service = find_jwt_service(jwt_services, parv[2]);
+		if (!service)
+		{
+			sendto_one(client, NULL, ":%s FAIL %s NO_SUCH_SERVICE :No such service", me.name, MSG_EXTJWT);
+			return;
+		}
+	}
+	if (service){
+		config = service->cfg; /* service config */
+	} else {
+		config = &cfg; /* default config */
+	}
+	if (!(payload = extjwt_make_payload(client, channel, config)) || !(full_token = extjwt_generate_token(payload, config)))
+	{
+		sendto_one(client, NULL, ":%s FAIL %s UNKNOWN_ERROR :Failed to generate token", me.name, MSG_EXTJWT);
+		return;
+	}
+	safe_free(payload);
+	token = full_token;
+	do
+	{
+		if (strlen(token) <= MAX_TOKEN_CHUNK)
+		{ /* the remaining data (or whole token) will fit a single irc message */
+			last = 1;
+			strcpy(message, token);
+		} else
+		{ /* send a chunk and shift buffer */
+			strlcpy(message, token, MAX_TOKEN_CHUNK+1);
+			token += MAX_TOKEN_CHUNK;
+		}
+		sendto_one(client, NULL, extjwt_message_pattern, me.name, parv[1], "*", last?"":"* ", message);
+	} while (!last);
+	safe_free(full_token);
+}
+
+char *extjwt_make_payload(Client *client, Channel *channel, struct extjwt_config *config)
+{
+	Membership *lp;
+	json_t *payload = NULL;
+	json_t *modes = NULL;
+	json_t *umodes = NULL;
+	char *modestring;
+	char singlemode[2] = { '\0' };
+	char *result;
+
+	if (!IsUser(client))
+		return NULL;
+
+	payload = json_object();
+	modes = json_array();
+	umodes = json_array();
+	
+	json_object_set_new(payload, "exp", json_integer(TStime()+config->exp_delay));
+	json_object_set_new(payload, "iss", json_string_unreal(me.name));
+	json_object_set_new(payload, "sub", json_string_unreal(client->name));
+	json_object_set_new(payload, "account", json_string_unreal(IsLoggedIn(client)?client->user->account:""));
+	
+	if (config->vfy) /* also add the URL */
+		json_object_set_new(payload, "vfy", json_string_unreal(config->vfy));
+
+	if (IsOper(client)) /* add "o" ircop flag */
+		json_array_append_new(umodes, json_string("o"));
+	json_object_set_new(payload, "umodes", umodes);
+
+	if (channel)
+	{ /* fill in channel information and user flags */
+		lp = find_membership_link(client->user->channel, channel);
+		if (lp)
+		{
+			modestring = lp->member_modes;
+			while (*modestring)
+			{
+				singlemode[0] = *modestring;
+				json_array_append_new(modes, json_string(singlemode));
+				modestring++;
+			}
+		}
+		json_object_set_new(payload, "channel", json_string_unreal(channel->name));
+		json_object_set_new(payload, "joined", json_integer(lp?1:0));
+		json_object_set_new(payload, "cmodes", modes);
+	}
+	result = json_dumps(payload, JSON_COMPACT);
+	json_decref(modes);
+	json_decref(umodes);
+	json_decref(payload);
+	return result;
+}
+
+void b64url(char *b64)
+{ /* convert base64 to base64-url */
+	while (*b64)
+	{
+		if (*b64 == '+')
+			*b64 = '-';
+		if (*b64 == '/')
+			*b64 = '_';
+		if (*b64 == '=')
+		{
+			*b64 = '\0';
+			return;
+		}
+		b64++;
+	}
+}
+
+unsigned char *extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen)
+{
+	switch(method)
+	{
+		case EXTJWT_METHOD_HS256: case EXTJWT_METHOD_HS384: case EXTJWT_METHOD_HS512:
+			return extjwt_hmac_extjwt_hash(method, key, keylen, data, datalen, resultlen);
+		case EXTJWT_METHOD_RS256: case EXTJWT_METHOD_RS384: case EXTJWT_METHOD_RS512: case EXTJWT_METHOD_ES256: case EXTJWT_METHOD_ES384: case EXTJWT_METHOD_ES512:
+			return extjwt_sha_pem_extjwt_hash(method, key, keylen, data, datalen, resultlen);
+	}
+	return NULL;
+}
+
+unsigned char* extjwt_sha_pem_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen)
+{
+	EVP_MD_CTX *mdctx = NULL;
+	ECDSA_SIG *ec_sig = NULL;
+	const BIGNUM *ec_sig_r = NULL;
+	const BIGNUM *ec_sig_s = NULL;
+	BIO *bufkey = NULL;
+	const EVP_MD *alg;
+	int type;
+	EVP_PKEY *pkey = NULL;
+	int pkey_type;
+	unsigned char *sig = NULL;
+	int ret = 0;
+	size_t slen;
+	char *retval = NULL;
+	char *output = NULL;
+	char *sig_ptr;
+
+	do
+	{
+		switch (method)
+		{
+			case EXTJWT_METHOD_RS256:
+				alg = EVP_sha256();
+				type = EVP_PKEY_RSA;
+				break;
+			case EXTJWT_METHOD_RS384:
+				alg = EVP_sha384();
+				type = EVP_PKEY_RSA;
+				break;
+			case EXTJWT_METHOD_RS512:
+				alg = EVP_sha512();
+				type = EVP_PKEY_RSA;
+				break;
+			case EXTJWT_METHOD_ES256:
+				alg = EVP_sha256();
+				type = EVP_PKEY_EC;
+				break;
+			case EXTJWT_METHOD_ES384:
+				alg = EVP_sha384();
+				type = EVP_PKEY_EC;
+				break;
+			case EXTJWT_METHOD_ES512:
+				alg = EVP_sha512();
+				type = EVP_PKEY_EC;
+				break;
+			default:
+				return NULL;
+		}
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100003L) /* https://github.com/openssl/openssl/commit/8ab31975bacb9c907261088937d3aa4102e3af84 */
+		if (!(bufkey = BIO_new_mem_buf((void *)key, keylen)))
+			break; /* out of memory */
+#else
+		if (!(bufkey = BIO_new_mem_buf(key, keylen)))
+			break; /* out of memory */
+#endif
+		if (!(pkey = PEM_read_bio_PrivateKey(bufkey, NULL, NULL, NULL)))
+			break; /* invalid key? */
+		pkey_type = EVP_PKEY_id(pkey);
+		if (type != pkey_type)
+			break; /* invalid key type */
+		if (!(mdctx = EVP_MD_CTX_create()))
+			break; /* out of memory */
+		if (EVP_DigestSignInit(mdctx, NULL, alg, NULL, pkey) != 1)
+			break; /* initialize error */
+		if (EVP_DigestSignUpdate(mdctx, data, datalen) != 1)
+			break; /* signing error */
+		if (EVP_DigestSignFinal(mdctx, NULL, &slen) != 1) /* get required buffer length */
+			break;
+		sig = safe_alloc(slen);
+		if (EVP_DigestSignFinal(mdctx, sig, &slen) != 1)
+			break;
+		if (pkey_type != EVP_PKEY_EC)
+		{
+			*resultlen = slen;
+			output = safe_alloc(slen);
+			memcpy(output, sig, slen);
+			retval = output;
+		} else
+		{
+			unsigned int degree, bn_len, r_len, s_len, buf_len;
+			unsigned char *raw_buf = NULL;
+			EC_KEY *ec_key;
+			if (!(ec_key = EVP_PKEY_get1_EC_KEY(pkey)))
+				break; /* out of memory */
+			degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key));
+			EC_KEY_free(ec_key);
+			sig_ptr = sig;
+			if (!(ec_sig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&sig_ptr, slen)))
+				break; /* out of memory */
+			ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s);
+			r_len = BN_num_bytes(ec_sig_r);
+			s_len = BN_num_bytes(ec_sig_s);
+			bn_len = (degree+7)/8;
+			if (r_len>bn_len || s_len > bn_len)
+				break;
+			buf_len = bn_len*2;
+			raw_buf = safe_alloc(buf_len);
+			BN_bn2bin(ec_sig_r, raw_buf+bn_len-r_len);
+			BN_bn2bin(ec_sig_s, raw_buf+buf_len-s_len);
+			output = safe_alloc(buf_len);
+			*resultlen = buf_len;
+			memcpy(output, raw_buf, buf_len);
+			retval = output;
+			safe_free(raw_buf);
+		}
+	} while (0);
+
+	if (bufkey)
+		BIO_free(bufkey);
+	if (pkey)
+		EVP_PKEY_free(pkey);
+	if (mdctx)
+		EVP_MD_CTX_destroy(mdctx);
+	if (ec_sig)
+		ECDSA_SIG_free(ec_sig);
+	safe_free(sig);
+	return retval;
+}
+
+unsigned char* extjwt_hmac_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen)
+{
+	const EVP_MD* typ;
+	char *hmac = safe_alloc(EVP_MAX_MD_SIZE);
+	switch (method)
+	{
+		default:
+		case EXTJWT_METHOD_HS256:
+			typ = EVP_sha256();
+			break;
+		case EXTJWT_METHOD_HS384:
+			typ = EVP_sha384();
+			break;
+		case EXTJWT_METHOD_HS512:
+			typ = EVP_sha512();
+			break;
+	}
+	if (HMAC(typ, key, keylen, data, datalen, hmac, resultlen))
+	{ /* openssl call */
+		return hmac;
+	} else {
+		safe_free(hmac);
+		return NULL;
+	}
+}
+
+char *extjwt_gen_header(int method)
+{ /* returns header json */
+	json_t *header = NULL;
+	json_t *alg;
+	char *result;
+
+	header = json_object();
+	json_object_set_new(header, "typ", json_string("JWT"));
+
+	switch (method)
+	{
+		default:
+		case EXTJWT_METHOD_HS256:
+			alg = json_string("HS256");
+			break;
+		case EXTJWT_METHOD_HS384:
+			alg = json_string("HS384");
+			break;
+		case EXTJWT_METHOD_HS512:
+			alg = json_string("HS512");
+			break;
+		case EXTJWT_METHOD_RS256:
+			alg = json_string("RS256");
+			break;
+		case EXTJWT_METHOD_RS384:
+			alg = json_string("RS384");
+			break;
+		case EXTJWT_METHOD_RS512:
+			alg = json_string("RS512");
+			break;
+		case EXTJWT_METHOD_ES256:
+			alg = json_string("ES256");
+			break;
+		case EXTJWT_METHOD_ES384:
+			alg = json_string("ES384");
+			break;
+		case EXTJWT_METHOD_ES512:
+			alg = json_string("ES512");
+			break;
+		case EXTJWT_METHOD_NONE:
+			alg = json_string("none");
+			break;
+	}
+	json_object_set_new(header, "alg", alg);
+	result = json_dumps(header, JSON_COMPACT);
+	json_decref(header);
+	return result;
+}
+
+char *extjwt_generate_token(const char *payload, struct extjwt_config *config)
+{
+	char *header = extjwt_gen_header(config->method);
+	size_t b64header_size = strlen(header)*4/3 + 8; // base64 has 4/3 overhead
+	size_t b64payload_size = strlen(payload)*4/3 + 8;
+	size_t b64sig_size = 4096*4/3 + 8;
+	size_t b64data_size = b64header_size + b64payload_size + b64sig_size + 4;
+	char *b64header = safe_alloc(b64header_size);
+	char *b64payload = safe_alloc(b64payload_size);
+	char *b64sig = safe_alloc(b64sig_size);
+	char *b64data = safe_alloc(b64data_size);
+	unsigned int extjwt_hashsize;
+	char *extjwt_hash_val = NULL;
+	char *retval = NULL;
+	b64_encode(header, strlen(header), b64header, b64header_size);
+	b64_encode(payload, strlen(payload), b64payload, b64payload_size);
+	b64url(b64header);
+	b64url(b64payload);
+	snprintf(b64data, b64data_size, "%s.%s", b64header, b64payload); // generate first part of the token
+	if (config->method != EXTJWT_METHOD_NONE)
+	{
+		extjwt_hash_val = extjwt_hash(config->method, config->secret, strlen(config->secret), b64data, strlen(b64data), &extjwt_hashsize); // calculate the signature extjwt_hash
+		if (extjwt_hash_val)
+		{
+			b64_encode(extjwt_hash_val, extjwt_hashsize, b64sig, b64sig_size);
+			b64url(b64sig);
+			strlcat(b64data, ".", b64data_size); // append signature extjwt_hash to token
+			strlcat(b64data, b64sig, b64data_size);
+			retval = b64data;
+		}
+	} else
+	{
+		retval = b64data;
+	}
+	safe_free(header);
+	safe_free(b64header);
+	safe_free(b64payload);
+	safe_free(b64sig);
+	safe_free(extjwt_hash_val);
+
+	if (retval != b64data)
+		safe_free(b64data);
+
+	return retval;
+}
diff --git a/src/modules/geoip_base.c b/src/modules/geoip_base.c
@@ -0,0 +1,326 @@
+/*
+ * GEOIP Base module, needed for all geoip functions
+ * as this stores the geo information in ModData.
+ * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"geoip_base",
+	"5.0",
+	"Base module for geoip",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+struct geoip_base_config_s {
+	int check_on_load;
+};
+
+/* Forward declarations */
+void geoip_base_free(ModData *m);
+const char *geoip_base_serialize(ModData *m);
+void geoip_base_unserialize(const char *str, ModData *m);
+int geoip_base_handshake(Client *client);
+int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list);
+int geoip_connect_extinfo(Client *client, NameValuePrioList **list);
+int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(geoip_base_set_existing_users_evt);
+CMD_FUNC(cmd_geoip);
+
+ModDataInfo *geoip_md; /* Module Data structure which we acquire */
+struct geoip_base_config_s geoip_base_config;
+
+/* We can use GEOIPDATA() and GEOIPDATARAW() for fast access.
+ * People wanting to get this information from outside this module
+ * should use geoip_client(client) !
+ */
+
+#define GEOIPDATARAW(x)	(moddata_client((x), geoip_md).ptr)
+#define GEOIPDATA(x)	((GeoIPResult *)moddata_client((x), geoip_md).ptr)
+
+int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	int i;
+	
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "check-on-load"))
+		{
+			CheckNull(cep);
+			continue;
+		}
+		config_warn("%s:%i: unknown item geoip::%s", cep->file->filename, cep->line_number, cep->name);
+	}
+	
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "check-on-load"))
+			geoip_base_config.check_on_load = config_checkval(cep->value, CFG_YESNO);
+	}
+	return 1;
+}
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_base_configtest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "geoip";
+	mreq.free = geoip_base_free;
+	mreq.serialize = geoip_base_serialize;
+	mreq.unserialize = geoip_base_unserialize;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	geoip_md = ModDataAdd(modinfo->handle, mreq);
+	if (!geoip_md)
+		abort();
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_base_configrun);
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, geoip_base_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, geoip_base_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 1, geoip_connect_extinfo); /* (prio: near-first) */
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0,geoip_base_handshake); /* in case the IP changed in registration phase (WEBIRC, HTTP Forwarded) */
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, geoip_base_handshake); /* remote user */
+	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois);
+
+	CommandAdd(modinfo->handle, "GEOIP", cmd_geoip, MAXPARA, CMD_USER);
+
+	/* set defaults */
+	geoip_base_config.check_on_load = 1;
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	/* add info for all users upon module loading if enabled, but delay it a bit for data provider module to load */
+	if (geoip_base_config.check_on_load)
+	{
+		EventAdd(modinfo->handle, "geoip_base_set_existing_users", geoip_base_set_existing_users_evt, NULL, 1000, 1);
+	}
+	return MOD_SUCCESS;
+}
+
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int geoip_base_handshake(Client *client)
+{
+	if (!client->ip)
+		return 0;
+	GeoIPResult *res = geoip_lookup(client->ip);
+
+	if (!res)
+		return 0;
+
+	if (GEOIPDATA(client))
+	{
+		free_geoip_result(GEOIPDATA(client));
+		GEOIPDATARAW(client) = NULL;
+	}
+	GEOIPDATARAW(client) = res;
+	return 0;
+}
+
+void geoip_base_free(ModData *m)
+{
+	if (m->ptr)
+	{
+		free_geoip_result((GeoIPResult *)m->ptr);
+		m->ptr = NULL;
+	}
+}
+
+const char *geoip_base_serialize(ModData *m)
+{
+	static char buf[512];
+	GeoIPResult *geo;
+
+	if (!m->ptr)
+		return NULL;
+
+	geo = m->ptr;
+	snprintf(buf, sizeof(buf), "cc=%s|cd=%s",
+	         geo->country_code,
+	         geo->country_name);
+
+	return buf;
+}
+
+void geoip_base_unserialize(const char *str, ModData *m)
+{
+	char buf[512], *p=NULL, *varname, *value;
+	char *country_name = NULL;
+	char *country_code = NULL;
+	GeoIPResult *res;
+
+	if (m->ptr == NULL)
+	{
+		free_geoip_result((GeoIPResult *)m->ptr);
+		m->ptr = NULL;
+	}
+	if (str == NULL)
+		return;
+
+	strlcpy(buf, str, sizeof(buf));
+	for (varname = strtoken(&p, buf, "|"); varname; varname = strtoken(&p, NULL, "|"))
+	{
+		value = strchr(varname, '=');
+		if (!value)
+			continue;
+		*value++ = '\0';
+		if (!strcmp(varname, "cc"))
+			country_code = value;
+		else if (!strcmp(varname, "cd"))
+			country_name = value;
+	}
+
+	if (!country_code || !country_name)
+		return; /* does not meet minimum criteria */
+
+	res = safe_alloc(sizeof(GeoIPResult));
+	safe_strdup(res->country_name, country_name);
+	safe_strdup(res->country_code, country_code);
+	m->ptr = res;
+}
+
+EVENT(geoip_base_set_existing_users_evt){
+	Client *client;
+	list_for_each_entry(client, &client_list, client_node){
+		if (!IsUser(client))
+			continue;
+		geoip_base_handshake(client);
+	}
+}
+
+int geoip_connect_extinfo(Client *client, NameValuePrioList **list)
+{
+	GeoIPResult *geo = GEOIPDATA(client);
+	if (geo)
+		add_nvplist(list, 0, "country", geo->country_code);
+	return 0;
+}
+
+int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list)
+{
+	GeoIPResult *geo;
+	char buf[512];
+	int policy = whois_get_policy(client, target, "geo");
+
+	if (policy == WHOIS_CONFIG_DETAILS_NONE)
+		return 0;
+
+	geo = GEOIPDATA(target);
+	if (!geo)
+		return 0;
+
+	// we only have country atm, but if we add city then city goes in 'full' and
+	// country goes in 'limited'
+	// if policy == WHOIS_CONFIG_DETAILS_LIMITED ...
+	add_nvplist_numeric_fmt(list, 0, "geo", client, RPL_WHOISCOUNTRY,
+	                        "%s %s :is connecting from %s",
+	                        target->name,
+	                        geo->country_code,
+	                        geo->country_name);
+	return 0;
+}
+
+CMD_FUNC(cmd_geoip)
+{
+	const char *ip = NULL;
+	Client *target;
+	GeoIPResult *res;
+
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		/* Maybe some report */
+		return;
+	}
+
+	if (strchr(parv[1], '.') || strchr(parv[1], ':'))
+	{
+		ip = parv[1];
+	} else {
+		target = find_user(parv[1], NULL);
+		if (!target)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+			return;
+		}
+		ip = target->ip;
+		if (!ip)
+		{
+			sendnotice(client, "User %s has no known IP address", client->name); // (eg: services bot)
+			return;
+		}
+	}
+
+	res = geoip_lookup(ip);
+
+	sendnotice(client, "*** GEOIP information for IP %s ***", ip);
+	if (!res)
+	{
+		sendnotice(client, "- No information available");
+		return;
+	} else {
+		if (res->country_code)
+			sendnotice(client, "- Country code: %s", res->country_code);
+		if (res->country_name)
+			sendnotice(client, "- Country name: %s", res->country_name);
+	}
+
+	free_geoip_result(res);
+
+	sendnotice(client, "*** End of information ***");
+}
diff --git a/src/modules/geoip_classic.c b/src/modules/geoip_classic.c
@@ -0,0 +1,297 @@
+/* GEOIP Classic module
+ * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2
+ */
+
+#include "unrealircd.h"
+#include <GeoIP.h>
+
+ModuleHeader MOD_HEADER
+  = {
+	"geoip_classic",
+	"5.0",
+	"GEOIP using classic databases", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+struct geoip_classic_config_s {
+	char *v4_db_file;
+	char *v6_db_file;
+/* for config reading only */
+	int have_config;
+	int have_ipv4_database;
+	int have_ipv6_database;
+};
+
+/* Variables */
+
+struct geoip_classic_config_s geoip_classic_config;
+GeoIP *gi4 = NULL;
+GeoIP *gi6 = NULL;
+
+/* Forward declarations */
+int geoip_classic_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int geoip_classic_configposttest(int *errs);
+int geoip_classic_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+void geoip_classic_free(void);
+GeoIPResult *geoip_lookup_classic(char *ip);
+
+int geoip_classic_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	int i;
+	
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip-classic"))
+		return 0;
+
+	geoip_classic_config.have_config = 1;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "ipv4-database"))
+		{
+			if (geoip_classic_config.have_ipv4_database)
+			{
+				config_error("%s:%i: duplicate item set::geoip-classic::%s", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+			if (!is_file_readable(cep->value, PERMDATADIR))
+			{
+				config_error("%s:%i: set::geoip-classic::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
+				errors++;
+				continue;
+			}
+			geoip_classic_config.have_ipv4_database = 1;
+			continue;
+		}
+		if (!strcmp(cep->name, "ipv6-database"))
+		{
+			if (geoip_classic_config.have_ipv6_database)
+			{
+				config_error("%s:%i: duplicate item set::geoip-classic::%s", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+			if (!is_file_readable(cep->value, PERMDATADIR))
+			{
+				config_error("%s:%i: set::geoip-classic::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
+				errors++;
+				continue;
+			}
+			geoip_classic_config.have_ipv6_database = 1;
+			continue;
+		}
+		config_warn("%s:%i: unknown item set::geoip-classic::%s", cep->file->filename, cep->line_number, cep->name);
+	}
+	
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_classic_configposttest(int *errs)
+{
+	int errors = 0;
+	if (geoip_classic_config.have_config)
+	{
+		if (!geoip_classic_config.have_ipv4_database && !geoip_classic_config.have_ipv6_database)
+		{
+			config_error("geoip_classic: no database files specified! Remove set::geoip-classic to use defaults");
+			errors++;
+		}
+	} else
+	{
+		safe_strdup(geoip_classic_config.v4_db_file, "GeoIP.dat");
+		safe_strdup(geoip_classic_config.v6_db_file, "GeoIPv6.dat");
+
+		if (is_file_readable(geoip_classic_config.v4_db_file, PERMDATADIR))
+		{
+			geoip_classic_config.have_ipv4_database = 1;
+		} else
+		{
+			config_warn("[geoip_classic] cannot open IPv4 database file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_classic_config.v4_db_file, strerror(errno));
+			safe_free(geoip_classic_config.v4_db_file);
+		}
+		if (is_file_readable(geoip_classic_config.v6_db_file, PERMDATADIR))
+		{
+			geoip_classic_config.have_ipv6_database = 1;
+		} else
+		{
+			config_warn("[geoip_classic] cannot open IPv6 database file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_classic_config.v6_db_file, strerror(errno));
+			safe_free(geoip_classic_config.v6_db_file);
+		}
+		if (!geoip_classic_config.have_ipv4_database && !geoip_classic_config.have_ipv6_database)
+		{
+			config_error("[geoip_classic] couldn't read any database! Either put these in %s location "
+					"or specify another in set::geoip-classic config block", PERMDATADIR);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_classic_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip-classic"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "ipv4-database") && geoip_classic_config.have_ipv4_database)
+			safe_strdup(geoip_classic_config.v4_db_file, cep->value);
+		if (!strcmp(cep->name, "ipv6-database") && geoip_classic_config.have_ipv6_database)
+			safe_strdup(geoip_classic_config.v6_db_file, cep->value);
+	}
+	return 1;
+}
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (!CallbackAddPVoid(modinfo->handle, CALLBACKTYPE_GEOIP_LOOKUP, TO_PVOIDFUNC(geoip_lookup_classic)))
+	{
+		unreal_log(ULOG_ERROR, "geoip_classic", "GEOIP_ADD_CALLBACK_FAILED", NULL,
+		           "geoip_classic: Could not install GEOIP_LOOKUP callback. "
+		           "Most likely another geoip module is already loaded. "
+		           "You can only load one!");
+		return MOD_FAILED;
+	}
+
+	geoip_classic_config.have_config = 0;
+	geoip_classic_config.have_ipv4_database = 0;
+	geoip_classic_config.have_ipv6_database = 0;
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_classic_configtest);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, geoip_classic_configposttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	geoip_classic_free();
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_classic_configrun);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	int found_good_file = 0;
+
+	if (geoip_classic_config.v4_db_file)
+	{
+		convert_to_absolute_path(&geoip_classic_config.v4_db_file, PERMDATADIR);
+		gi4 = GeoIP_open(geoip_classic_config.v4_db_file, GEOIP_STANDARD | GEOIP_CHECK_CACHE | GEOIP_SILENCE);
+		if (gi4)
+		{
+			found_good_file = 1;
+		} else
+		{
+			int save_err = errno;
+			unreal_log(ULOG_WARNING, "geoip_classic", "GEOIP_CANNOT_OPEN_DB", NULL,
+				       "[IPv4] Could not open '$filename': $system_error",
+				       log_data_string("filename", geoip_classic_config.v4_db_file),
+				       log_data_string("system_error", strerror(save_err)));
+		}
+	}
+	if (geoip_classic_config.v6_db_file)
+	{
+		convert_to_absolute_path(&geoip_classic_config.v6_db_file, PERMDATADIR);
+		gi6 = GeoIP_open(geoip_classic_config.v6_db_file, GEOIP_STANDARD | GEOIP_CHECK_CACHE | GEOIP_SILENCE);
+		if (gi6)
+		{
+			found_good_file = 1;
+		} else
+		{
+			int save_err = errno;
+			unreal_log(ULOG_WARNING, "geoip_classic", "GEOIP_CANNOT_OPEN_DB", NULL,
+				       "[IPv6] Could not open '$filename': $system_error",
+				       log_data_string("filename", geoip_classic_config.v6_db_file),
+				       log_data_string("system_error", strerror(save_err)));
+		}
+		convert_to_absolute_path(&geoip_classic_config.v6_db_file, PERMDATADIR);
+	}
+
+	if (!found_good_file)
+	{
+		unreal_log(ULOG_ERROR, "geoip_classic", "GEOIP_CANNOT_OPEN_DB", NULL,
+					"could not open any database!");
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	geoip_classic_free();
+	return MOD_SUCCESS;
+}
+
+void geoip_classic_free(void)
+{
+	if (gi4)
+		GeoIP_delete(gi4);
+	if (gi6)
+		GeoIP_delete(gi6);
+	gi4 = NULL;
+	gi6 = NULL;
+	safe_free(geoip_classic_config.v4_db_file);
+	safe_free(geoip_classic_config.v6_db_file);
+}
+
+GeoIPResult *geoip_lookup_classic(char *ip)
+{
+	static char buf[256];
+	const char *country_code, *country_name;
+	GeoIPLookup gl;
+	GeoIP *gi;
+	int geoid;
+	GeoIPResult *r;
+
+	if (!ip)
+		return NULL;
+
+	if (strchr(ip, ':'))
+	{
+		if (!gi6)
+			return NULL;
+		geoid = GeoIP_id_by_addr_v6_gl(gi6, ip, &gl);
+		gi = gi6;
+	} else
+	{
+		if (!gi4 || !strcmp(ip, "255.255.255.255"))
+			return NULL;
+		geoid = GeoIP_id_by_addr_gl(gi4, ip, &gl);
+		gi = gi4;
+	}
+
+	if (geoid == 0)
+		return NULL;
+
+	country_code = GeoIP_code_by_id(geoid);
+	country_name = GeoIP_country_name_by_id(gi, geoid);
+
+	if (!country_code || !country_name)
+		return NULL;
+
+	r = safe_alloc(sizeof(GeoIPResult));
+	safe_strdup(r->country_code, country_code);
+	safe_strdup(r->country_name, country_name);
+	return r;
+}
+
diff --git a/src/modules/geoip_csv.c b/src/modules/geoip_csv.c
@@ -0,0 +1,838 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/geoip_csv.c
+ *   (C) 2021 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
+  = {
+	"geoip_csv",
+	"5.0",
+	"GEOIP using csv data files", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+struct geoip_csv_config_s {
+	char *v4_db_file;
+	char *v6_db_file;
+	char *countries_db_file;
+/* for config reading only */
+	int have_config;
+	int have_ipv4_database;
+	int have_ipv6_database;
+	int have_countries;
+};
+
+struct geoip_csv_ip_range {
+	uint32_t addr;
+	uint32_t mask;
+	int geoid;
+	struct geoip_csv_ip_range *next;
+};
+
+struct geoip_csv_ip6_range {
+	uint16_t addr[8];
+	uint16_t mask[8];
+	int geoid;
+	struct geoip_csv_ip6_range *next;
+};
+
+struct geoip_csv_country {
+	char code[10];
+	char name[100];
+	char continent[25];
+	int id;
+	struct geoip_csv_country *next;
+};
+
+/* Variables */
+struct geoip_csv_config_s geoip_csv_config;
+struct geoip_csv_ip_range *geoip_csv_ip_range_list[256]; // we are keeping a separate list for each possible first octet to speed up searching
+struct geoip_csv_ip6_range *geoip_csv_ip6_range_list = NULL; // for ipv6 there would be too many separate lists so just use a single one
+struct geoip_csv_country *geoip_csv_country_list = NULL;
+
+/* Forward declarations */
+static void geoip_csv_free_ipv4(void);
+static void geoip_csv_free_ipv6(void);
+static void geoip_csv_free_ipv6(void);
+static void geoip_csv_free_countries(void);
+static void geoip_csv_free(void);
+static int geoip_csv_read_ipv4(char *file);
+static int geoip_csv_ip6_convert(char *ip, uint16_t out[8]);
+static int geoip_csv_read_ipv6(char *file);
+static int geoip_csv_read_countries(char *file);
+static struct geoip_csv_country *geoip_csv_get_country(int id);
+static int geoip_csv_get_v4_geoid(char *iip);
+static int geoip_csv_get_v6_geoid(char *iip);
+int geoip_csv_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int geoip_csv_configposttest(int *errs);
+int geoip_csv_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+void geoip_csv_free(void);
+GeoIPResult *geoip_lookup_csv(char *ip);
+
+int geoip_csv_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	int i;
+	
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip-csv"))
+		return 0;
+
+	geoip_csv_config.have_config = 1;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "ipv4-blocks-file"))
+		{
+			if (geoip_csv_config.have_ipv4_database)
+			{
+				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+			if (!is_file_readable(cep->value, PERMDATADIR))
+			{
+				config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
+				errors++;
+				continue;
+			}
+			geoip_csv_config.have_ipv4_database = 1;
+			continue;
+		}
+		if (!strcmp(cep->name, "ipv6-blocks-file"))
+		{
+			if (geoip_csv_config.have_ipv6_database)
+			{
+				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+			if (!is_file_readable(cep->value, PERMDATADIR))
+			{
+				config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
+				errors++;
+				continue;
+			}
+			geoip_csv_config.have_ipv6_database = 1;
+			continue;
+		}
+		if (!strcmp(cep->name, "countries-file"))
+		{
+			if (geoip_csv_config.have_countries)
+			{
+				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+			if (!is_file_readable(cep->value, PERMDATADIR))
+			{
+				config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
+				errors++;
+				continue;
+			}
+			geoip_csv_config.have_countries = 1;
+			continue;
+		}
+		config_warn("%s:%i: unknown item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
+	}
+	
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_csv_configposttest(int *errs)
+{
+	int errors = 0;
+	if (geoip_csv_config.have_config)
+	{
+		if (!geoip_csv_config.have_countries)
+		{
+			config_error("[geoip_csv] no countries file specified! Remove set::geoip-csv to use defaults");
+			errors++;
+		}
+		if (!geoip_csv_config.have_ipv4_database && !geoip_csv_config.have_ipv6_database)
+		{
+			config_error("[geoip_csv] no database files specified! Remove set::geoip-csv to use defaults");
+			errors++;
+		}
+	} else
+	{
+		safe_strdup(geoip_csv_config.v4_db_file, "GeoLite2-Country-Blocks-IPv4.csv");
+		safe_strdup(geoip_csv_config.v6_db_file, "GeoLite2-Country-Blocks-IPv6.csv");
+		safe_strdup(geoip_csv_config.countries_db_file, "GeoLite2-Country-Locations-en.csv");
+
+		if (is_file_readable(geoip_csv_config.v4_db_file, PERMDATADIR))
+		{
+			geoip_csv_config.have_ipv4_database = 1;
+		} else
+		{
+			config_warn("[geoip_csv] cannot open IPv4 blocks file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.v4_db_file, strerror(errno));
+			safe_free(geoip_csv_config.v4_db_file);
+		}
+		if (is_file_readable(geoip_csv_config.v6_db_file, PERMDATADIR))
+		{
+			geoip_csv_config.have_ipv6_database = 1;
+		} else
+		{
+			config_warn("[geoip_csv] cannot open IPv6 blocks file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.v6_db_file, strerror(errno));
+			safe_free(geoip_csv_config.v6_db_file);
+		}
+		if (!is_file_readable(geoip_csv_config.countries_db_file, PERMDATADIR))
+		{
+			config_error("[geoip_csv] cannot open countries file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.countries_db_file, strerror(errno));
+			safe_free(geoip_csv_config.countries_db_file);
+			errors++;
+		}
+		if (!geoip_csv_config.have_ipv4_database && !geoip_csv_config.have_ipv6_database)
+		{
+			config_error("[geoip_csv] couldn't read any blocks file! Either put these in %s location "
+					"or specify another in set::geoip-csv config block", PERMDATADIR);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_csv_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip-csv"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "ipv4-blocks-file") && geoip_csv_config.have_ipv4_database)
+			safe_strdup(geoip_csv_config.v4_db_file, cep->value);
+		if (!strcmp(cep->name, "ipv6-blocks-file") && geoip_csv_config.have_ipv6_database)
+			safe_strdup(geoip_csv_config.v6_db_file, cep->value);
+		if (!strcmp(cep->name, "countries-file"))
+			safe_strdup(geoip_csv_config.countries_db_file, cep->value);
+	}
+	return 1;
+}
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (!CallbackAddPVoid(modinfo->handle, CALLBACKTYPE_GEOIP_LOOKUP, TO_PVOIDFUNC(geoip_lookup_csv)))
+	{
+		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_ADD_CALLBACK_FAILED", NULL,
+		           "geoip_csv: Could not install GEOIP_LOOKUP callback. "
+		           "Most likely another geoip module is already loaded. "
+		           "You can only load one!");
+		return MOD_FAILED;
+	}
+
+	geoip_csv_config.have_config = 0;
+	geoip_csv_config.have_ipv4_database = 0;
+	geoip_csv_config.have_ipv6_database = 0;
+	geoip_csv_config.have_countries = 0;
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_csv_configtest);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, geoip_csv_configposttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	geoip_csv_free();
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_csv_configrun);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	int found_good_file = 0;
+
+	if (geoip_csv_config.v4_db_file)
+	{
+		convert_to_absolute_path(&geoip_csv_config.v4_db_file, PERMDATADIR);
+		if (!geoip_csv_read_ipv4(geoip_csv_config.v4_db_file))
+		{
+			found_good_file = 1;
+		}
+	}
+	if (geoip_csv_config.v6_db_file)
+	{
+		convert_to_absolute_path(&geoip_csv_config.v6_db_file, PERMDATADIR);
+		if (!geoip_csv_read_ipv6(geoip_csv_config.v6_db_file))
+		{
+			found_good_file = 1;
+		}
+	}
+	if (!geoip_csv_config.countries_db_file)
+	{
+		unreal_log(ULOG_DEBUG, "geoip_csv", "GEOIP_NO_COUNTRIES", NULL,
+				"[BUG] No countries file specified");
+		geoip_csv_free();
+		return MOD_FAILED;
+	}
+	convert_to_absolute_path(&geoip_csv_config.countries_db_file, PERMDATADIR);
+	if (geoip_csv_read_countries(geoip_csv_config.countries_db_file))
+	{
+		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_CANNOT_OPEN_DB", NULL,
+					"could not open required countries file!");
+		geoip_csv_free();
+		return MOD_FAILED;
+	}
+
+	if (!found_good_file)
+	{
+		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_CANNOT_OPEN_DB", NULL,
+					"could not open any database!");
+		geoip_csv_free();
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	geoip_csv_free();
+	return MOD_SUCCESS;
+}
+
+static void geoip_csv_free_ipv4(void)
+{
+	struct geoip_csv_ip_range *ptr, *oldptr;
+	int i;
+	for (i=0; i<256; i++)
+	{
+		ptr = geoip_csv_ip_range_list[i];
+		geoip_csv_ip_range_list[i] = NULL;
+		while (ptr)
+		{
+			oldptr = ptr;
+			ptr = ptr->next;
+			safe_free(oldptr);
+		}
+	}
+}
+
+static void geoip_csv_free_ipv6(void)
+{
+	struct geoip_csv_ip6_range *ptr, *oldptr;
+	ptr = geoip_csv_ip6_range_list;
+	geoip_csv_ip6_range_list = NULL;
+	while (ptr)
+	{
+		oldptr = ptr;
+		ptr = ptr->next;
+		safe_free(oldptr);
+	}
+}
+
+static void geoip_csv_free_countries(void)
+{
+	struct geoip_csv_country *ptr, *oldptr;
+	ptr = geoip_csv_country_list;
+	geoip_csv_country_list = NULL;
+	while (ptr)
+	{
+		oldptr = ptr;
+		ptr = ptr->next;
+		safe_free(oldptr);
+	}
+}
+
+static void geoip_csv_free(void)
+{
+	geoip_csv_free_ipv4();
+	geoip_csv_free_ipv6();
+	geoip_csv_free_countries();
+}
+
+/* reading data from files */
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+#define BUFLEN 8191
+
+static int geoip_csv_read_ipv4(char *file)
+{
+	FILE *u;
+	char buf[BUFLEN+1];
+	int cidr, geoid;
+	char ip[24];
+	char netmask[24];
+	uint32_t addr;
+	uint32_t mask;
+	struct geoip_csv_ip_range *curr[256];
+	struct geoip_csv_ip_range *ptr;
+	memset(curr, 0, sizeof(curr));
+	int i;
+	char *filename = NULL;
+	
+	safe_strdup(filename, file);
+	convert_to_absolute_path(&filename, CONFDIR);
+	u = fopen(filename, "r");
+	safe_free(filename);
+	if (!u)
+	{
+		config_warn("[geoip_csv] Cannot open IPv4 ranges list file");
+		return 1;
+	}
+	
+	if (!fgets(buf, BUFLEN, u))
+	{
+		config_warn("[geoip_csv] IPv4 list file is empty");
+		fclose(u);
+		return 1;
+	}
+	buf[BUFLEN] = '\0';
+	while (fscanf(u, "%23[^/\n]/%d,%" STR(BUFLEN) "[^\n]\n", ip, &cidr, buf) == 3)
+	{
+		if (sscanf(buf, "%d,", &geoid) != 1)
+		{
+			/* missing geoid: can happen with valid files */
+			continue;
+		}
+
+		if (cidr < 1 || cidr > 32)
+		{
+			config_warn("[geoip_csv] Invalid CIDR found! IP=%s CIDR=%d! Bad CSV file?", ip, cidr);
+			continue;
+		}
+
+		if (inet_pton(AF_INET, ip, &addr) < 1)
+		{
+			config_warn("[geoip_csv] Invalid IP found! \"%s\" Bad CSV file?", ip);
+			continue;
+		}
+		addr = htonl(addr);
+		
+		mask = 0;
+		while (cidr)
+		{ /* calculate netmask */
+			mask >>= 1;
+			mask |= (1<<31);
+			cidr--;
+		}
+		
+		i=0;
+		do
+		{ /* multiple iterations in case CIDR is <8 and we have multiple first octets matching */
+			uint8_t index = addr>>24;
+			if (!curr[index])
+			{
+				geoip_csv_ip_range_list[index] = safe_alloc(sizeof(struct geoip_csv_ip_range));
+				curr[index] = geoip_csv_ip_range_list[index];
+			} else
+			{
+				curr[index]->next = safe_alloc(sizeof(struct geoip_csv_ip_range));
+				curr[index] = curr[index]->next;
+			}
+			ptr = curr[index];
+			ptr->next = NULL;
+			ptr->addr = addr;
+			ptr->mask = mask;
+			ptr->geoid = geoid;
+			i++;
+			index++;
+		} while (i<=((~mask)>>24));
+	}
+	fclose(u);
+	return 0;
+}
+
+static int geoip_csv_ip6_convert(char *ip, uint16_t out[8])
+{ /* convert text to binary form */
+	uint16_t tmp[8];
+	int i;
+	if (inet_pton(AF_INET6, ip, out) < 1)
+		return 0;
+	for (i=0; i<8; i++)
+	{
+		out[i] = htons(out[i]);
+	}
+	return 1;
+}
+
+#define IPV6_STRING_SIZE	40
+
+static int geoip_csv_read_ipv6(char *file)
+{
+	FILE *u;
+	char buf[BUFLEN+1];
+	char *bptr, *optr;
+	int cidr, geoid;
+	char ip[IPV6_STRING_SIZE];
+	uint16_t addr[8];
+	uint16_t mask[8];
+	struct geoip_csv_ip6_range *curr = NULL;
+	struct geoip_csv_ip6_range *ptr;
+	int error;
+	int length;
+	char *filename = NULL;
+
+	safe_strdup(filename, file);
+	convert_to_absolute_path(&filename, CONFDIR);
+	u = fopen(filename, "r");
+	safe_free(filename);
+	if (!u)
+	{
+		config_warn("[geoip_csv] Cannot open IPv6 ranges list file");
+		return 1;
+	}
+	if (!fgets(buf, BUFLEN, u))
+	{
+		config_warn("[geoip_csv] IPv6 list file is empty");
+		fclose(u);
+		return 1;
+	}
+	while (fgets(buf, BUFLEN, u))
+	{
+		error = 0;
+		bptr = buf;
+		optr = ip;
+		length = 0;
+		while (*bptr != '/')
+		{
+			if (!*bptr)
+			{
+				error = 1;
+				break;
+			}
+			if (++length >= IPV6_STRING_SIZE)
+			{
+				ip[IPV6_STRING_SIZE-1] = '\0';
+				config_warn("[geoip_csv] Too long IPv6 address found, starts with %s. Bad CSV file?", ip);
+				error = 1;
+				break;
+			}
+			*optr++ = *bptr++;
+		}
+		if (error)
+			continue;
+		*optr = '\0';
+		bptr++;
+		if (!geoip_csv_ip6_convert(ip, addr))
+		{
+			config_warn("[geoip_csv] Invalid IP found! \"%s\" Bad CSV file?", ip);
+			continue;
+		}
+		sscanf(bptr, "%d,%d,", &cidr, &geoid);
+		if (cidr < 1 || cidr > 128)
+		{
+			config_warn("[geoip_csv] Invalid CIDR found! CIDR=%d Bad CSV file?", cidr);
+			continue;
+		}
+
+		memset(mask, 0, 16);
+		
+		int mask_bit = 0;
+		while (cidr)
+		{ /* calculate netmask */
+			mask[mask_bit/16] |= 1<<(15-(mask_bit%16));
+			mask_bit++;
+			cidr--;
+		}
+
+		if (!curr)
+		{
+			geoip_csv_ip6_range_list = safe_alloc(sizeof(struct geoip_csv_ip6_range));
+			curr = geoip_csv_ip6_range_list;
+		} else
+		{
+			curr->next = safe_alloc(sizeof(struct geoip_csv_ip6_range));
+			curr = curr->next;
+		}
+		ptr = curr;
+		ptr->next = NULL;
+		memcpy(ptr->addr, addr, 16);
+		memcpy(ptr->mask, mask, 16);
+		ptr->geoid = geoid;
+	}
+	fclose(u);
+	return 0;
+}
+
+/* CSV fields; no STATE_GEONAME_ID because of using %d in fscanf */
+#define STATE_LOCALE_CODE	0
+#define STATE_CONTINENT_CODE	1
+#define STATE_CONTINENT_NAME	2
+#define STATE_COUNTRY_ISO_CODE	3
+#define STATE_COUNTRY_NAME	4
+#define STATE_IS_IN_EU	5
+
+#define MEMBER_SIZE(type,member) sizeof(((type *)0)->member)
+
+static int geoip_csv_read_countries(char *file)
+{
+	FILE *u;
+	char code[MEMBER_SIZE(struct geoip_csv_country, code)];
+	char continent[MEMBER_SIZE(struct geoip_csv_country, continent)];
+	char name[MEMBER_SIZE(struct geoip_csv_country, name)];
+	char buf[BUFLEN+1];
+	int state;
+	int id;
+	struct geoip_csv_country *curr = NULL;
+	char *filename = NULL;
+
+	safe_strdup(filename, file);
+	convert_to_absolute_path(&filename, CONFDIR);
+	u = fopen(filename, "r");
+	safe_free(filename);
+	if (!u)
+	{
+		config_warn("[geoip_csv] Cannot open countries list file");
+		return 1;
+	}
+	
+	if (!fgets(buf, BUFLEN, u))
+	{
+		config_warn("[geoip_csv] Countries list file is empty");
+		fclose(u);
+		return 1;
+	}
+	while (fscanf(u, "%d,%" STR(BUFLEN) "[^\n]", &id, buf) == 2)
+	{ /* getting country ID integer and all other data in string */
+		char *ptr = buf;
+		char *codeptr = code;
+		char *contptr = continent;
+		char *nptr = name;
+		int quote_open = 0;
+		int length = 0;
+		state = STATE_LOCALE_CODE;
+		while (*ptr)
+		{
+			switch (state)
+			{
+				case STATE_CONTINENT_NAME:
+					if (*ptr == ',')
+						goto next_line; /* no continent? */
+					if (length >= MEMBER_SIZE(struct geoip_csv_country, continent))
+					{
+						*contptr = '\0';
+						config_warn("[geoip_csv] Too long continent name found: `%s`. If you are sure your countries file is correct, please file a bug report.", continent);
+						goto next_line;
+					}
+					*contptr = *ptr; /* scan for continent name */
+					contptr++;
+					length++;
+					break;
+				case STATE_COUNTRY_ISO_CODE:
+					if (*ptr == ',')		/* country code is empty */
+						goto next_line;	/* -- that means only the continent is specified - we ignore it completely */
+					if (length >= MEMBER_SIZE(struct geoip_csv_country, code))
+					{
+						*codeptr = '\0';
+						config_warn("[geoip_csv] Too long country code found: `%s`. If you are sure your countries file is correct, please file a bug report.", code);
+						goto next_line;
+					}
+					*codeptr = *ptr; // scan for country code (DE, PL, US etc)
+					codeptr++;
+					length++;
+					break;
+				case STATE_COUNTRY_NAME:
+					goto read_country_name;
+				default:
+					break; // ignore this field and wait for next one
+			}
+			ptr++;
+			if (*ptr == ',')
+			{
+				length = 0;
+				ptr++;
+				state++;
+			}
+		}
+		read_country_name:
+		*codeptr = '\0';
+		*contptr = '\0';
+		length = 0;
+		while (*ptr)
+		{
+			switch (*ptr)
+			{
+				case '"':
+					quote_open = !quote_open;
+					ptr++;
+					continue;
+				case ',':
+					if (!quote_open)
+						goto end_country_name; /* we reached the end of current CSV field */
+				/* fall through */
+				default:
+					*nptr++ = *ptr++;
+					if (length >= MEMBER_SIZE(struct geoip_csv_country, name))
+					{
+						*nptr = '\0';
+						config_warn("[geoip_csv] Too long country name found: `%s`. If you are sure your countries file is correct, please file a bug report.", name);
+						goto next_line;
+					}
+					break; // scan for country name
+			}
+		}
+		end_country_name:
+		*nptr = '\0';
+		if (geoip_csv_country_list)
+		{
+			curr->next = safe_alloc(sizeof(struct geoip_csv_country));
+			curr = curr->next;
+		} else
+		{
+			geoip_csv_country_list = safe_alloc(sizeof(struct geoip_csv_country));
+			curr = geoip_csv_country_list;
+		}
+		curr->next = NULL;
+		strcpy(curr->code, code);
+		strcpy(curr->name, name);
+		strcpy(curr->continent, continent);
+		curr->id = id;
+		next_line: continue;
+	}
+	fclose(u);
+	return 0;
+}
+
+static struct geoip_csv_country *geoip_csv_get_country(int id)
+{
+	struct geoip_csv_country *curr = geoip_csv_country_list;
+	if (!curr)
+		return NULL;
+	int found = 0;
+	for (;curr;curr = curr->next)
+	{
+		if (curr->id == id)
+		{
+			found = 1;
+			break;
+		}
+	}
+	if (found)
+		return curr;
+	return NULL;
+}
+
+static int geoip_csv_get_v4_geoid(char *iip)
+{
+	uint32_t addr, tmp_addr;
+	struct geoip_csv_ip_range *curr;
+	int i;
+	int found = 0;
+	if (inet_pton(AF_INET, iip, &addr) < 1)
+	{
+		unreal_log(ULOG_WARNING, "geoip_csv", "UNSUPPORTED_IP", NULL, "Invalid or unsupported client IP $ip", log_data_string("ip", iip));
+		return 0;
+	}
+	addr = htonl(addr);
+	curr = geoip_csv_ip_range_list[addr>>24];
+	if (curr)
+	{
+		i = 0;
+		for (;curr;curr = curr->next)
+		{
+			tmp_addr = addr;
+			tmp_addr &= curr->mask; /* mask the address to filter out net prefix only */
+			if (tmp_addr == curr->addr)
+			{ /* ... and match it to the loaded data */
+				found = 1;
+				break;
+			}
+			i++;
+		}
+	}
+	if (found)
+		return curr->geoid;
+	return 0;
+}
+
+static int geoip_csv_get_v6_geoid(char *iip)
+{
+	uint16_t addr[8];
+	struct geoip_csv_ip6_range *curr;
+	int i;
+	int found = 0;
+	
+	if (!geoip_csv_ip6_convert(iip, addr))
+	{
+		unreal_log(ULOG_WARNING, "geoip_csv", "UNSUPPORTED_IP", NULL, "Invalid or unsupported client IP $ip", log_data_string("ip", iip));
+		return 0;
+	}
+	curr = geoip_csv_ip6_range_list;
+	if (curr)
+	{
+		for (;curr;curr = curr->next)
+		{
+			found = 1;
+			for (i=0; i<8; i++)
+			{
+				if (curr->addr[i] != (addr[i] & curr->mask[i]))
+				{ /* compare net address to loaded data */
+					found = 0;
+					break;
+				}
+			}
+			if(found)
+				break;
+		}
+	}
+	if (found)
+		return curr->geoid;
+	return 0;
+}
+
+GeoIPResult *geoip_lookup_csv(char *ip)
+{
+	int geoid;
+	struct geoip_csv_country *country;
+	GeoIPResult *r;
+
+	if (!ip)
+		return NULL;
+
+	if (strchr(ip, ':'))
+	{
+		geoid = geoip_csv_get_v6_geoid(ip);
+	} else
+	{
+		geoid = geoip_csv_get_v4_geoid(ip);
+	}
+
+	if (geoid == 0)
+		return NULL;
+
+	country = geoip_csv_get_country(geoid);
+
+	if (!country)
+		return NULL;
+
+	r = safe_alloc(sizeof(GeoIPResult));
+	safe_strdup(r->country_code, country->code);
+	safe_strdup(r->country_name, country->name);
+	return r;
+}
+
diff --git a/src/modules/geoip_maxmind.c b/src/modules/geoip_maxmind.c
@@ -0,0 +1,239 @@
+/* GEOIP maxmind module
+ * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2
+ */
+
+#include "unrealircd.h"
+#include <maxminddb.h>
+
+ModuleHeader MOD_HEADER
+  = {
+	"geoip_maxmind",
+	"5.0",
+	"GEOIP using maxmind databases", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+struct geoip_maxmind_config_s {
+	char *db_file;
+/* for config reading only */
+	int have_config;
+	int have_database;
+};
+
+/* Variables */
+
+struct geoip_maxmind_config_s geoip_maxmind_config;
+MMDB_s mmdb;
+
+/* Forward declarations */
+int geoip_maxmind_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int geoip_maxmind_configposttest(int *errs);
+int geoip_maxmind_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+void geoip_maxmind_free(void);
+GeoIPResult *geoip_lookup_maxmind(char *ip);
+
+int geoip_maxmind_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	int i;
+	
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip-maxmind"))
+		return 0;
+
+	geoip_maxmind_config.have_config = 1;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database"))
+		{
+			if (geoip_maxmind_config.have_database)
+			{
+				config_error("%s:%i: duplicate item set::geoip-maxmind::%s", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+			if (!is_file_readable(cep->value, PERMDATADIR))
+			{
+				config_error("%s:%i: set::geoip-maxmind::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
+				errors++;
+				continue;
+			}
+			geoip_maxmind_config.have_database = 1;
+			continue;
+		}
+		config_warn("%s:%i: unknown item set::geoip-maxmind::%s", cep->file->filename, cep->line_number, cep->name);
+	}
+	
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_maxmind_configposttest(int *errs)
+{
+	int errors = 0;
+	if (geoip_maxmind_config.have_config)
+	{
+		if (!geoip_maxmind_config.have_database)
+		{
+			config_error("geoip_maxmind: no database file specified! Remove set::geoip-maxmind to use defaults");
+			errors++;
+		}
+	} else
+	{
+		safe_strdup(geoip_maxmind_config.db_file, "GeoLite2-Country.mmdb");
+
+		if (is_file_readable(geoip_maxmind_config.db_file, PERMDATADIR))
+		{
+			geoip_maxmind_config.have_database = 1;
+		} else
+		{
+			config_error("[geoip_maxmind] cannot open database file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_maxmind_config.db_file, strerror(errno));
+			safe_free(geoip_maxmind_config.db_file);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int geoip_maxmind_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name)
+		return 0;
+
+	if (strcmp(ce->name, "geoip-maxmind"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database") && geoip_maxmind_config.have_database)
+			safe_strdup(geoip_maxmind_config.db_file, cep->value);
+	}
+	return 1;
+}
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (!CallbackAddPVoid(modinfo->handle, CALLBACKTYPE_GEOIP_LOOKUP, TO_PVOIDFUNC(geoip_lookup_maxmind)))
+	{
+		unreal_log(ULOG_ERROR, "geoip_maxmind", "GEOIP_ADD_CALLBACK_FAILED", NULL,
+				   "geoip_maxmind: Could not install GEOIP_LOOKUP callback. "
+				   "Most likely another geoip module is already loaded. "
+				   "You can only load one!");
+		return MOD_FAILED;
+	}
+
+	geoip_maxmind_config.have_config = 0;
+	geoip_maxmind_config.have_database = 0;
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_maxmind_configtest);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, geoip_maxmind_configposttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_maxmind_configrun);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	geoip_maxmind_free();
+	convert_to_absolute_path(&geoip_maxmind_config.db_file, PERMDATADIR);
+	
+	int status = MMDB_open(geoip_maxmind_config.db_file, MMDB_MODE_MMAP, &mmdb);
+
+	if (status != MMDB_SUCCESS) {
+		int save_err = errno;
+		unreal_log(ULOG_WARNING, "geoip_maxmind", "GEOIP_CANNOT_OPEN_DB", NULL,
+				   "Could not open '$filename' - $maxmind_error; IO error: $io_error",
+				   log_data_string("filename", geoip_maxmind_config.db_file),
+				   log_data_string("maxmind_error", MMDB_strerror(status)),
+				   log_data_string("io_error", (status == MMDB_IO_ERROR)?strerror(save_err):"none"));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	geoip_maxmind_free();
+	return MOD_SUCCESS;
+}
+
+void geoip_maxmind_free(void)
+{
+	MMDB_close(&mmdb);
+}
+
+GeoIPResult *geoip_lookup_maxmind(char *ip)
+{
+	int gai_error, mmdb_error, status;
+	MMDB_lookup_result_s result;
+	MMDB_entry_data_s country_code;
+	MMDB_entry_data_s country_name;
+	char *country_code_str, *country_name_str;
+	GeoIPResult *r;
+
+	if (!ip)
+		return NULL;
+
+	result = MMDB_lookup_string(&mmdb, ip, &gai_error, &mmdb_error);
+	if (gai_error)
+	{
+		unreal_log(ULOG_DEBUG, "geoip_maxmind", "GEOIP_DB_ERROR", NULL,
+				"libmaxminddb: getaddrinfo error for $ip: $error",
+				log_data_string("ip", ip),
+				log_data_string("error", gai_strerror(gai_error)));
+		return NULL;
+	}
+	
+	if (mmdb_error != MMDB_SUCCESS)
+	{
+		unreal_log(ULOG_DEBUG, "geoip_maxmind", "GEOIP_DB_ERROR", NULL,
+				"libmaxminddb: library error for $ip: $error",
+				log_data_string("ip", ip),
+				log_data_string("error", MMDB_strerror(mmdb_error)));
+		return NULL;
+	}
+
+	if (!result.found_entry) /* no result */
+		return NULL;
+
+	status = MMDB_get_value(&result.entry, &country_code, "country", "iso_code", NULL);
+	if (status != MMDB_SUCCESS || !country_code.has_data || country_code.type != MMDB_DATA_TYPE_UTF8_STRING)
+		return NULL;
+	status = MMDB_get_value(&result.entry, &country_name, "country", "names", "en", NULL);
+	if (status != MMDB_SUCCESS || !country_name.has_data || country_name.type != MMDB_DATA_TYPE_UTF8_STRING)
+		return NULL;
+
+	/* these results are not null-terminated */
+	country_code_str = safe_alloc(country_code.data_size + 1);
+	country_name_str = safe_alloc(country_name.data_size + 1);
+	memcpy(country_code_str, country_code.utf8_string, country_code.data_size);
+	country_code_str[country_code.data_size] = '\0';
+	memcpy(country_name_str, country_name.utf8_string, country_name.data_size);
+	country_name_str[country_name.data_size] = '\0';
+
+	r = safe_alloc(sizeof(GeoIPResult));
+	r->country_code = country_code_str;
+	r->country_name = country_name_str;
+	return r;
+}
+
diff --git a/src/modules/globops.c b/src/modules/globops.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /globops", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -57,9 +57,7 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_globops)
 {
-	char *message;
-
-	message = parc > 1 ? parv[1] : NULL;
+	const char *message = parc > 1 ? parv[1] : NULL;
 
 	if (BadPtr(message))
 	{
diff --git a/src/modules/help.c b/src/modules/help.c
@@ -33,7 +33,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /help", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -57,7 +57,7 @@ MOD_UNLOAD()
 #define HDR(str) sendto_one(client, NULL, ":%s 290 %s :%s", me.name, client->name, str);
 #define SND(str) sendto_one(client, NULL, ":%s 292 %s :%s", me.name, client->name, str);
 
-ConfigItem_help *find_Help(char *command)
+ConfigItem_help *find_Help(const char *command)
 {
 	ConfigItem_help *help;
 
@@ -80,7 +80,7 @@ ConfigItem_help *find_Help(char *command)
 	return NULL;
 }
 
-void parse_help(Client *client, char *name, char *help)
+void parse_help(Client *client, const char *help)
 {
 	ConfigItem_help *helpitem;
 	MOTDLine *text;
@@ -109,7 +109,7 @@ void parse_help(Client *client, char *name, char *help)
 		SND("   We're sorry, we don't have help available for the command you requested.");
 		SND(" -");
 		sendto_one(client, NULL, ":%s 292 %s : ***** Go to %s if you have any further questions *****",
-		    me.name, client->name, helpchan);
+		    me.name, client->name, HELP_CHANNEL);
 		SND(" -");
 		return;
 	}
@@ -131,7 +131,7 @@ void parse_help(Client *client, char *name, char *help)
 */
 CMD_FUNC(cmd_help)
 {
-	char *helptopic;
+	const char *helptopic;
 
 	if (!MyUser(client))
 		return; /* never remote */
@@ -141,5 +141,5 @@ CMD_FUNC(cmd_help)
 	if (helptopic && (*helptopic == '?'))
 		helptopic++;
 
-	parse_help(client, client->name, BadPtr(helptopic) ? NULL : helptopic);
+	parse_help(client, BadPtr(helptopic) ? NULL : helptopic);
 }
diff --git a/src/modules/hideserver.c b/src/modules/hideserver.c
@@ -49,7 +49,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Hide servers from /MAP & /LINKS",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 static void InitConf()
@@ -95,10 +95,10 @@ MOD_INIT()
 
 MOD_LOAD()
 {
-	if (!CommandOverrideAdd(MyMod, "MAP", override_map))
+	if (!CommandOverrideAdd(MyMod, "MAP", 0, override_map))
 		return MOD_FAILED;
 
-	if (!CommandOverrideAdd(MyMod, "LINKS", override_links))
+	if (!CommandOverrideAdd(MyMod, "LINKS", 0, override_links))
 		return MOD_FAILED;
 
 	return MOD_SUCCESS;
@@ -118,35 +118,35 @@ static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 
 	if (type == CONFIG_MAIN)
 	{
-		if (!strcmp(ce->ce_varname, "hideserver"))
+		if (!strcmp(ce->name, "hideserver"))
 		{
-			for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+			for (cep = ce->items; cep; cep = cep->next)
 			{
-				if (!strcmp(cep->ce_varname, "hide"))
+				if (!strcmp(cep->name, "hide"))
 				{
 					/* No checking needed */
 				}
-				else if (!cep->ce_vardata)
+				else if (!cep->value)
 				{
 					config_error("%s:%i: %s::%s without value",
-						cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum,
-						ce->ce_varname, cep->ce_varname);
+						cep->file->filename,
+						cep->line_number,
+						ce->name, cep->name);
 					errors++;
 					continue;
 				}
-				else if (!strcmp(cep->ce_varname, "disable-map"))
+				else if (!strcmp(cep->name, "disable-map"))
 					;
-				else if (!strcmp(cep->ce_varname, "disable-links"))
+				else if (!strcmp(cep->name, "disable-links"))
 					;
-				else if (!strcmp(cep->ce_varname, "map-deny-message"))
+				else if (!strcmp(cep->name, "map-deny-message"))
 					;
-				else if (!strcmp(cep->ce_varname, "links-deny-message"))
+				else if (!strcmp(cep->name, "links-deny-message"))
 					;
 				else
 				{
 					config_error("%s:%i: unknown directive hideserver::%s",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+						cep->file->filename, cep->line_number, cep->name);
 					errors++;
 				}
 			}
@@ -165,31 +165,31 @@ static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
 
 	if (type == CONFIG_MAIN)
 	{
-		if (!strcmp(ce->ce_varname, "hideserver"))
+		if (!strcmp(ce->name, "hideserver"))
 		{
-			for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+			for (cep = ce->items; cep; cep = cep->next)
 			{
-				if (!strcmp(cep->ce_varname, "disable-map"))
-					Settings.disable_map = config_checkval(cep->ce_vardata, CFG_YESNO);
-				else if (!strcmp(cep->ce_varname, "disable-links"))
-					Settings.disable_links = config_checkval(cep->ce_vardata, CFG_YESNO);
-				else if (!strcmp(cep->ce_varname, "map-deny-message"))
+				if (!strcmp(cep->name, "disable-map"))
+					Settings.disable_map = config_checkval(cep->value, CFG_YESNO);
+				else if (!strcmp(cep->name, "disable-links"))
+					Settings.disable_links = config_checkval(cep->value, CFG_YESNO);
+				else if (!strcmp(cep->name, "map-deny-message"))
 				{
-					safe_strdup(Settings.map_deny_message, cep->ce_vardata);
+					safe_strdup(Settings.map_deny_message, cep->value);
 				}
-				else if (!strcmp(cep->ce_varname, "links-deny-message"))
+				else if (!strcmp(cep->name, "links-deny-message"))
 				{
-					safe_strdup(Settings.links_deny_message, cep->ce_vardata);
+					safe_strdup(Settings.links_deny_message, cep->value);
 				}
-				else if (!strcmp(cep->ce_varname, "hide"))
+				else if (!strcmp(cep->name, "hide"))
 				{
-					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+					for (cepp = cep->items; cepp; cepp = cepp->next)
 					{
-						if (!strcasecmp(cepp->ce_varname, me.name))
+						if (!strcasecmp(cepp->name, me.name))
 							continue;
 
 						ca = safe_alloc(sizeof(ConfigItem_ulines));
-						safe_strdup(ca->servername, cepp->ce_varname);
+						safe_strdup(ca->servername, cepp->name);
 						AddListItem(ca, HiddenServers);
 					}
 				}
@@ -231,7 +231,7 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 	else
 	{
 		sendnumeric(client, RPL_MAP, prompt,
-		            length, server->name, server->serv->users, IsOper(client) ? server->id : "");
+		            length, server->name, server->server->users, IsOper(client) ? server->id : "");
 		cnt = 0;
 	}
 
@@ -248,7 +248,7 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
-		if (acptr->srvptr != server ||
+		if (acptr->uplink != server ||
  		    (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)))
 			continue;
 		if (FindHiddenServer(acptr->name))
@@ -263,7 +263,7 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 			continue;
 		if (FindHiddenServer(acptr->name))
 			break;
-		if (acptr->srvptr != server)
+		if (acptr->uplink != server)
 			continue;
 		if (!IsMap(acptr))
 			continue;
@@ -284,7 +284,7 @@ void dump_flat_map(Client *client, Client *server, int length)
 
 	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
 
-	sendnumeric(client, RPL_MAP, "", length, server->name, server->serv->users, "");
+	sendnumeric(client, RPL_MAP, "", length, server->name, server->server->users, "");
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
@@ -304,7 +304,7 @@ void dump_flat_map(Client *client, Client *server, int length)
 			break;
 		if (--cnt == 0)
 			*buf = '`';
-		sendnumeric(client, RPL_MAP, buf, length-2, acptr->name, acptr->serv->users, "");
+		sendnumeric(client, RPL_MAP, buf, length-2, acptr->name, acptr->server->users, "");
 	}
 }
 
@@ -388,7 +388,7 @@ CMD_OVERRIDE_FUNC(override_links)
 			sendnumeric(client, RPL_LINKS, acptr->name, me.name,
 			    1, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
 		else
-			sendnumeric(client, RPL_LINKS, acptr->name, acptr->serv->up,
+			sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink->name,
 			    acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
 	}
 
diff --git a/src/modules/history.c b/src/modules/history.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Simple history command for end-users",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 #define HISTORY_LINES_DEFAULT 100
@@ -76,13 +76,16 @@ CMD_FUNC(cmd_history)
 	Channel *channel;
 	int lines = HISTORY_LINES_DEFAULT;
 
+	if (!MyUser(client))
+		return;
+
 	if ((parc < 2) || BadPtr(parv[1]))
 	{
 		history_usage(client);
 		return;
 	}
 
-	channel = find_channel(parv[1], NULL);
+	channel = find_channel(parv[1]);
 	if (!channel)
 	{
 		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
@@ -91,13 +94,13 @@ CMD_FUNC(cmd_history)
 
 	if (!IsMember(client, channel))
 	{
-		sendnumeric(client, ERR_NOTONCHANNEL, channel->chname);
+		sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
 		return;
 	}
 
 	if (!has_channel_mode(channel, 'H'))
 	{
-		sendnotice(client, "Channel %s does not have channel mode +H set", channel->chname);
+		sendnotice(client, "Channel %s does not have channel mode +H set", channel->name);
 		return;
 	}
 
@@ -125,7 +128,7 @@ CMD_FUNC(cmd_history)
 	filter.cmd = HFC_SIMPLE;
 	filter.last_lines = lines;
 
-	if ((r = history_request(channel->chname, &filter)))
+	if ((r = history_request(channel->name, &filter)))
 	{
 		history_send_result(client, r);
 		free_history_result(r);
diff --git a/src/modules/history_backend_mem.c b/src/modules/history_backend_mem.c
@@ -17,7 +17,7 @@ ModuleHeader MOD_HEADER
 	"2.0",
 	"History backend: memory",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Defines */
@@ -97,16 +97,16 @@ static void freecfg(struct cfgstruct *cfg);
 static void hbm_init_hashes(ModuleInfo *m);
 static void init_history_storage(ModuleInfo *modinfo);
 int hbm_modechar_del(Channel *channel, int modechar);
-int hbm_history_add(char *object, MessageTag *mtags, char *line);
+int hbm_history_add(const char *object, MessageTag *mtags, const char *line);
 int hbm_history_cleanup(HistoryLogObject *h);
-HistoryResult *hbm_history_request(char *object, HistoryFilter *filter);
-int hbm_history_destroy(char *object);
-int hbm_history_set_limit(char *object, int max_lines, long max_time);
+HistoryResult *hbm_history_request(const char *object, HistoryFilter *filter);
+int hbm_history_destroy(const char *object);
+int hbm_history_set_limit(const char *object, int max_lines, long max_time);
 EVENT(history_mem_clean);
 EVENT(history_mem_init);
 static int hbm_read_masterdb(void);
 static void hbm_read_dbs(void);
-static int hbm_read_db(char *fname);
+static int hbm_read_db(const char *fname);
 static int hbm_write_masterdb(void);
 static int hbm_write_db(HistoryLogObject *h);
 static void hbm_delete_db(HistoryLogObject *h);
@@ -200,7 +200,7 @@ EVENT(history_mem_init)
 
 MOD_UNLOAD()
 {
-	if (loop.ircd_terminating)
+	if (loop.terminating)
 		hbm_flush();
 	freecfg(&test);
 	freecfg(&cfg);
@@ -235,6 +235,7 @@ static void setcfg(struct cfgstruct *cfg)
 
 static void freecfg(struct cfgstruct *cfg)
 {
+	safe_free(cfg->masterdb);
 	safe_free(cfg->directory);
 	safe_free(cfg->db_secret);
 }
@@ -264,40 +265,40 @@ int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
 
-	if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->ce_varname)
+	if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->name)
 		return 0;
 
-	if (!strcmp(ce->ce_varname, "persist"))
+	if (!strcmp(ce->name, "persist"))
 	{
-		if (!ce->ce_vardata)
+		if (!ce->value)
 		{
 			config_error("%s:%i: missing parameter",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				ce->file->filename, ce->line_number);
 			errors++;
 		} else {
-			test.persist = config_checkval(ce->ce_vardata, CFG_YESNO);
+			test.persist = config_checkval(ce->value, CFG_YESNO);
 		}
 	} else
-	if (!strcmp(ce->ce_varname, "db-secret"))
+	if (!strcmp(ce->name, "db-secret"))
 	{
-		char *err;
-		if ((err = unrealdb_test_secret(ce->ce_vardata)))
+		const char *err;
+		if ((err = unrealdb_test_secret(ce->value)))
 		{
-			config_error("%s:%i: set::history::channel::db-secret: %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, err);
+			config_error("%s:%i: set::history::channel::db-secret: %s", ce->file->filename, ce->line_number, err);
 			errors++;
 		}
-		safe_strdup(test.db_secret, ce->ce_vardata);
+		safe_strdup(test.db_secret, ce->value);
 	} else
-	if (!strcmp(ce->ce_varname, "directory")) // or "path" ?
+	if (!strcmp(ce->name, "directory")) // or "path" ?
 	{
-		if (!ce->ce_vardata)
+		if (!ce->value)
 		{
 			config_error("%s:%i: missing parameter",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				ce->file->filename, ce->line_number);
 			errors++;
 		} else
 		{
-			safe_strdup(test.directory, ce->ce_vardata);
+			safe_strdup(test.directory, ce->value);
 			hbm_set_masterdb_filename(&test);
 		}
 	} else
@@ -322,7 +323,7 @@ int hbm_config_posttest(int *errs)
 	} else
 	if (!test.db_secret && test.persist)
 	{
-		config_error("set::history::channel::db-secret needs to be set."); // TODO: REFER TO FAQ OR OTHER ENTRY!!!!
+		config_error("set::history::channel::db-secret needs to be set.");
 		errors++;
 	} else
 	if (test.db_secret && test.persist)
@@ -367,22 +368,22 @@ hbm_config_posttest_end:
 /** Configure ourselves based on the set::history::channel settings */
 int hbm_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 {
-	if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->ce_varname)
+	if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->name)
 		return 0;
 
-	if (!strcmp(ce->ce_varname, "persist"))
+	if (!strcmp(ce->name, "persist"))
 	{
-		cfg.persist = config_checkval(ce->ce_vardata, CFG_YESNO);
+		cfg.persist = config_checkval(ce->value, CFG_YESNO);
 	} else
-	if (!strcmp(ce->ce_varname, "directory")) // or "path" ?
+	if (!strcmp(ce->name, "directory")) // or "path" ?
 	{
-		safe_strdup(cfg.directory, ce->ce_vardata);
+		safe_strdup(cfg.directory, ce->value);
 		convert_to_absolute_path(&cfg.directory, PERMDATADIR);
 		hbm_set_masterdb_filename(&cfg);
 	} else
-	if (!strcmp(ce->ce_varname, "db-secret"))
+	if (!strcmp(ce->name, "db-secret"))
 	{
-		safe_strdup(cfg.db_secret, ce->ce_vardata);
+		safe_strdup(cfg.db_secret, ce->value);
 	} else
 	{
 		return 0; /* unknown option to us, let another module handle it */
@@ -403,7 +404,7 @@ int hbm_rehash_complete(void)
 	return 0;
 }
 
-char *history_storage_capability_parameter(Client *client)
+const char *history_storage_capability_parameter(Client *client)
 {
 	static char buf[128];
 
@@ -426,12 +427,12 @@ static void init_history_storage(ModuleInfo *modinfo)
 	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
 }
 
-uint64_t hbm_hash(char *object)
+uint64_t hbm_hash(const char *object)
 {
 	return siphash_nocase(object, siphashkey_history_backend_mem) % HISTORY_BACKEND_MEM_HASH_TABLE_SIZE;
 }
 
-HistoryLogObject *hbm_find_object(char *object)
+HistoryLogObject *hbm_find_object(const char *object)
 {
 	int hashv = hbm_hash(object);
 	HistoryLogObject *h;
@@ -444,7 +445,7 @@ HistoryLogObject *hbm_find_object(char *object)
 	return NULL;
 }
 
-HistoryLogObject *hbm_find_or_add_object(char *object)
+HistoryLogObject *hbm_find_or_add_object(const char *object)
 {
 	int hashv = hbm_hash(object);
 	HistoryLogObject *h;
@@ -480,7 +481,7 @@ int hbm_modechar_del(Channel *channel, int modechar)
 	if (!cfg.persist)
 		return 0;
 
-	if ((modechar == 'P') && ((h = hbm_find_object(channel->chname))))
+	if ((modechar == 'P') && ((h = hbm_find_object(channel->name))))
 	{
 		/* Channel went from +P to -P and also has channel history: delete the history file */
 		hbm_delete_db(h);
@@ -538,7 +539,7 @@ void hbm_duplicate_mtags(HistoryLogLine *l, MessageTag *m)
 }
 
 /** Add a line to a history object */
-void hbm_history_add_line(HistoryLogObject *h, MessageTag *mtags, char *line)
+void hbm_history_add_line(HistoryLogObject *h, MessageTag *mtags, const char *line)
 {
 	HistoryLogLine *l = safe_alloc(sizeof(HistoryLogLine) + strlen(line));
 	strcpy(l->line, line); /* safe, see memory allocation above ^ */
@@ -589,12 +590,14 @@ void hbm_history_del_line(HistoryLogObject *h, HistoryLogLine *l)
 }
 
 /** Add history entry */
-int hbm_history_add(char *object, MessageTag *mtags, char *line)
+int hbm_history_add(const char *object, MessageTag *mtags, const char *line)
 {
 	HistoryLogObject *h = hbm_find_or_add_object(object);
 	if (!h->max_lines)
 	{
-		sendto_realops("hbm_history_add() for '%s', which has no limit", h->name);
+		unreal_log(ULOG_WARNING, "history", "BUG_HISTORY_ADD_NO_LIMIT", NULL,
+		           "[BUG] hbm_history_add() called for $object, which has no limit set",
+		           log_data_string("object", h->name));
 #ifdef DEBUGMODE
 		abort();
 #else
@@ -955,7 +958,7 @@ static int hbm_return_between(HistoryResult *r, HistoryLogObject *h, HistoryFilt
 	return 0;
 }
 
-HistoryResult *hbm_history_request(char *object, HistoryFilter *filter)
+HistoryResult *hbm_history_request(const char *object, HistoryFilter *filter)
 {
 	HistoryResult *r;
 	HistoryLogObject *h = hbm_find_object(object);
@@ -1050,7 +1053,7 @@ int hbm_history_cleanup(HistoryLogObject *h)
 	return 1;
 }
 
-int hbm_history_destroy(char *object)
+int hbm_history_destroy(const char *object)
 {
 	HistoryLogObject *h = hbm_find_object(object);
 	HistoryLogLine *l, *l_next;
@@ -1075,7 +1078,7 @@ int hbm_history_destroy(char *object)
 }
 
 /** Set new limit on history object */
-int hbm_history_set_limit(char *object, int max_lines, long max_time)
+int hbm_history_set_limit(const char *object, int max_lines, long max_time)
 {
 	HistoryLogObject *h = hbm_find_or_add_object(object);
 	h->max_lines = max_lines;
@@ -1104,8 +1107,6 @@ static int hbm_read_masterdb(void)
 		{
 			/* Database does not exist. Could be first boot */
 			config_warn("[history] No database present at '%s', will start a new one", test.masterdb);
-			// TODO: maybe check for condition where 'master.db' does not exist but
-			// there are other .db files.
 			if (!hbm_write_masterdb())
 				return 0; /* fatal error */
 			return 1;
@@ -1125,10 +1126,10 @@ static int hbm_read_masterdb(void)
 	    !unrealdb_read_str(db, &prehash) ||
 	    !unrealdb_read_str(db, &posthash))
 	{
-		safe_free(prehash);
-		safe_free(posthash);
 		config_error("[history] Read error from database file '%s': %s",
 			test.masterdb, unrealdb_get_error_string());
+		safe_free(prehash);
+		safe_free(posthash);
 		unrealdb_close(db);
 		return 0;
 	}
@@ -1138,14 +1139,24 @@ static int hbm_read_masterdb(void)
 	{
 		config_error("[history] Read error from database file '%s': unexpected values encountered",
 			test.masterdb);
+		safe_free(prehash);
+		safe_free(posthash);
 		return 0;
 	}
 
 	/* Now, safely switch over.. */
-	safe_free(hbm_prehash);
-	safe_free(hbm_posthash);
-	hbm_prehash = prehash;
-	hbm_posthash = posthash;
+	if (hbm_prehash && !strcmp(hbm_prehash, prehash) && hbm_posthash && !strcmp(hbm_posthash, posthash))
+	{
+		/* Identical sets */
+		safe_free(prehash);
+		safe_free(posthash);
+	} else {
+		/* Diffferent */
+		safe_free(hbm_prehash);
+		safe_free(hbm_posthash);
+		hbm_prehash = prehash;
+		hbm_posthash = posthash;
+	}
 
 	return 1;
 }
@@ -1275,7 +1286,7 @@ static void hbm_read_dbs(void)
 
 
 /** Read a channel history db file */
-static int hbm_read_db(char *fname)
+static int hbm_read_db(const char *fname)
 {
 	UnrealDB *db = NULL;
 	// header
@@ -1475,7 +1486,7 @@ EVENT(history_mem_clean)
 	} while(loopcnt++ < HISTORY_CLEAN_PER_LOOP);
 }
 
-char *hbm_history_filename(HistoryLogObject *h)
+const char *hbm_history_filename(HistoryLogObject *h)
 {
 	static char fname[512];
 	char oname[OBJECTLEN+1];
@@ -1495,9 +1506,10 @@ char *hbm_history_filename(HistoryLogObject *h)
 
 #define WARN_WRITE_ERROR(fname) \
 	do { \
-		sendto_realops_and_log("[history] Error writing to temporary database file " \
-		                       "'%s': %s (DATABASE NOT SAVED)", \
-		                       fname, unrealdb_get_error_string()); \
+		unreal_log(ULOG_ERROR, "history", "HISTORYDB_FILE_WRITE_ERROR", NULL, \
+			   "[historydb] 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) \
@@ -1515,7 +1527,7 @@ char *hbm_history_filename(HistoryLogObject *h)
 static int hbm_write_db(HistoryLogObject *h)
 {
 	UnrealDB *db;
-	char *realfname;
+	const char *realfname;
 	char tmpfname[512];
 	HistoryLogLine *l;
 	MessageTag *m;
@@ -1524,7 +1536,7 @@ static int hbm_write_db(HistoryLogObject *h)
 	if (!cfg.db_secret)
 		abort();
 
-	channel = find_channel(h->name, NULL);
+	channel = find_channel(h->name);
 	if (!channel || !has_channel_mode(channel, 'P'))
 		return 1; /* Don't save this channel, pretend success */
 
@@ -1575,7 +1587,7 @@ static int hbm_write_db(HistoryLogObject *h)
 #endif
 	if (rename(tmpfname, realfname) < 0)
 	{
-		sendto_realops_and_log("[history] Error renaming '%s' to '%s': %s (HISTORY NOT SAVED)",
+		config_error("[history] Error renaming '%s' to '%s': %s (HISTORY NOT SAVED)",
 			tmpfname, realfname, strerror(errno));
 		return 0;
 	}
@@ -1588,7 +1600,7 @@ static int hbm_write_db(HistoryLogObject *h)
 static void hbm_delete_db(HistoryLogObject *h)
 {
 	UnrealDB *db;
-	char *fname;
+	const char *fname;
 	if (!cfg.persist || !hbm_prehash || !hbm_posthash)
 	{
 #ifdef DEBUGMODE
diff --git a/src/modules/history_backend_null.c b/src/modules/history_backend_null.c
@@ -16,14 +16,14 @@ ModuleHeader MOD_HEADER
 	"2.0",
 	"History backend: null/none",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
-int hbn_history_set_limit(char *object, int max_lines, long max_time);
-int hbn_history_add(char *object, MessageTag *mtags, char *line);
-HistoryResult *hbn_history_request(char *object, HistoryFilter *filter);
-int hbn_history_destroy(char *object);
+int hbn_history_set_limit(const char *object, int max_lines, long max_time);
+int hbn_history_add(const char *object, MessageTag *mtags, const char *line);
+HistoryResult *hbn_history_request(const char *object, HistoryFilter *filter);
+int hbn_history_destroy(const char *object);
 
 MOD_INIT()
 {
@@ -53,22 +53,22 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int hbn_history_add(char *object, MessageTag *mtags, char *line)
+int hbn_history_add(const char *object, MessageTag *mtags, const char *line)
 {
 	return 1;
 }
 
-HistoryResult *hbn_history_request(char *object, HistoryFilter *filter)
+HistoryResult *hbn_history_request(const char *object, HistoryFilter *filter)
 {
 	return NULL;
 }
 
-int hbn_history_set_limit(char *object, int max_lines, long max_time)
+int hbn_history_set_limit(const char *object, int max_lines, long max_time)
 {
 	return 1;
 }
 
-int hbn_history_destroy(char *object)
+int hbn_history_destroy(const char *object)
 {
 	return 1;
 }
diff --git a/src/modules/ident_lookup.c b/src/modules/ident_lookup.c
@@ -10,7 +10,7 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"Ident lookups (RFC1413)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
@@ -43,7 +43,6 @@ MOD_UNLOAD()
 
 static void ident_lookup_failed(Client *client)
 {
-	Debug((DEBUG_NOTICE, "ident_lookup_failed() for %p", client));
 	ircstats.is_abad++;
 	if (client->local->authfd != -1)
 	{
@@ -68,12 +67,12 @@ static EVENT(check_ident_timeout)
 			if (IsIdentLookupSent(client))
 			{
 				/* set::ident::connect-timeout */
-				if ((TStime() - client->local->firsttime) > IDENT_CONNECT_TIMEOUT)
+				if ((TStime() - client->local->creationtime) > IDENT_CONNECT_TIMEOUT)
 					ident_lookup_failed(client);
 			} else
 			{
 				/* set::ident::read-timeout */
-				if ((TStime() - client->local->firsttime) > IDENT_READ_TIMEOUT)
+				if ((TStime() - client->local->creationtime) > IDENT_READ_TIMEOUT)
 					ident_lookup_failed(client);
 			}
 		}
@@ -93,7 +92,8 @@ static int ident_lookup_connect(Client *client)
 	}
 	if (++OpenFiles >= maxclients+1)
 	{
-		sendto_ops("Can't allocate fd, too many connections.");
+		unreal_log(ULOG_FATAL, "io", "IDENT_ERROR_MAXCLIENTS", client,
+		           "Cannot do ident connection for $client.details: All connections in use");
 		fd_close(client->local->authfd);
 		--OpenFiles;
 		client->local->authfd = -1;
diff --git a/src/modules/invite.c b/src/modules/invite.c
@@ -22,9 +22,27 @@
 
 #include "unrealircd.h"
 
+#define MSG_INVITE 	"INVITE"
+
+#define CLIENT_INVITES(client)		(moddata_local_client(client, userInvitesMD).ptr)
+#define CHANNEL_INVITES(channel)	(moddata_channel(channel, channelInvitesMD).ptr)
+
+ModDataInfo *userInvitesMD;
+ModDataInfo *channelInvitesMD;
+long CAP_INVITE_NOTIFY = 0L;
+int invite_always_notify = 0;
+
 CMD_FUNC(cmd_invite);
 
-#define MSG_INVITE 	"INVITE"	
+void invite_free(ModData *md);
+int invite_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int invite_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+void add_invite(Client *from, Client *to, Channel *channel, MessageTag *mtags);
+void del_invite(Client *client, Channel *channel);
+static int invite_channel_destroy(Channel *channel, int *should_destroy);
+int invite_user_quit(Client *client, MessageTag *mtags, const char *comment);
+int invite_user_join(Client *client, Channel *channel, MessageTag *mtags);
+int invite_is_invited(Client *client, Channel *channel, int *invited);
 
 ModuleHeader MOD_HEADER
   = {
@@ -32,13 +50,63 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /invite", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, invite_config_test);
+	return MOD_SUCCESS;
+}
+
 MOD_INIT()
 {
-	CommandAdd(modinfo->handle, MSG_INVITE, cmd_invite, MAXPARA, CMD_USER);
+	ClientCapabilityInfo cap;
+	ClientCapability *c;
+	ModDataInfo mreq;
+
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	CommandAdd(modinfo->handle, MSG_INVITE, cmd_invite, MAXPARA, CMD_USER|CMD_SERVER);	
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "invite-notify";
+	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_INVITE_NOTIFY);
+	if (!c)
+	{
+		config_error("[%s] Failed to request invite-notify cap: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
+	memset(&mreq, 0 , sizeof(mreq));
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	mreq.name = "invite",
+	mreq.free = invite_free;
+	userInvitesMD = ModDataAdd(modinfo->handle, mreq);
+	if (!userInvitesMD)
+	{
+		config_error("[%s] Failed to request user invite moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	
+	memset(&mreq, 0 , sizeof(mreq));
+	mreq.type = MODDATATYPE_CHANNEL;
+	mreq.name = "invite",
+	mreq.free = invite_free;
+	channelInvitesMD = ModDataAdd(modinfo->handle, mreq);
+	if (!channelInvitesMD)
+	{
+		config_error("[%s] Failed to request channel invite moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
+	invite_always_notify = 0; /* the default */
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, invite_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_DESTROY, 1000000, invite_channel_destroy);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, invite_user_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, invite_user_join);
+	HookAdd(modinfo->handle, HOOKTYPE_IS_INVITED, 0, invite_is_invited);
+	
 	return MOD_SUCCESS;
 }
 
@@ -52,62 +120,252 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+void invite_free(ModData *md)
+{
+	Link **inv, *tmp;
+
+	if (!md->ptr)
+		return; // was not set
+
+	for (inv = (Link **)md->ptr; (tmp = *inv); inv = &tmp->next)
+	{
+		*inv = tmp->next;
+		free_link(tmp);
+	}
+}
+
+int invite_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name || strcmp(ce->name, "normal-user-invite-notification"))
+		return 0;
+
+	if (!ce->value)
+	{
+		config_error_empty(ce->file->filename, ce->line_number, "set", ce->name);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int invite_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || !ce->name || strcmp(ce->name, "normal-user-invite-notification"))
+		return 0;
+
+	invite_always_notify = config_checkval(ce->value, CFG_YESNO);
+
+	return 1;
+}
+
+static int invite_channel_destroy(Channel *channel, int *should_destroy)
+{
+	Link *lp;
+	while ((lp = CHANNEL_INVITES(channel)))
+		del_invite(lp->value.client, channel);
+	return 0;
+}
+
+int invite_user_quit(Client *client, MessageTag *mtags, const char *comment)
+{
+	Link *lp;
+	/* Clean up invitefield */
+	while ((lp = CLIENT_INVITES(client)))
+		del_invite(client, lp->value.channel);
+	return 0;
+}
+
+int invite_user_join(Client *client, Channel *channel, MessageTag *mtags)
+{
+	del_invite(client, channel);
+	return 0;
+}
+
 /* Send the user their list of active invites */
 void send_invite_list(Client *client)
 {
 	Link *inv;
 
-	for (inv = client->user->invited; inv; inv = inv->next)
+	for (inv = CLIENT_INVITES(client); inv; inv = inv->next)
 	{
 		sendnumeric(client, RPL_INVITELIST,
-			   inv->value.channel->chname);	
+			   inv->value.channel->name);	
 	}
 	sendnumeric(client, RPL_ENDOFINVITELIST);
 }
 
+int invite_is_invited(Client *client, Channel *channel, int *invited)
+{
+	Link *lp;
+	
+	if (!MyConnect(client))
+		return 0; // not handling invite lists for remote clients
+
+	for (lp = CLIENT_INVITES(client); lp; lp = lp->next)
+		if (lp->value.channel == channel)
+		{
+			*invited = 1;
+			return 0;
+		}
+	return 0;
+}
+
+void invite_process(Client *client, Client *target, Channel *channel, MessageTag *recv_mtags, int override)
+{
+	MessageTag *mtags = NULL;
+
+	new_message(client, recv_mtags, &mtags);
+
+	/* broadcast to other servers */
+	sendto_server(client, 0, 0, mtags, ":%s INVITE %s %s %d", client->id, target->id, channel->name, override);
+
+	/* send chanops notifications */
+	if (IsUser(client) && (check_channel_access(client, channel, "oaq")
+	    || IsULine(client)
+	    || ValidatePermissionsForPath("channel:override:invite:self",client,NULL,channel,NULL)
+	    || invite_always_notify
+	    ))
+	{
+		if (override == 1)
+		{
+			sendto_channel(channel, &me, NULL, "o",
+				0, SEND_LOCAL, mtags,
+				":%s NOTICE @%s :OperOverride -- %s invited him/herself into the channel.",
+				me.name, channel->name, client->name);
+		}
+		if (override == 0)
+		{
+			sendto_channel(channel, &me, NULL, "o",
+				CAP_INVITE_NOTIFY | CAP_INVERT, SEND_LOCAL, mtags,
+				":%s NOTICE @%s :%s invited %s into the channel.",
+				me.name, channel->name, client->name, target->name);
+		}
+		/* always send IRCv3 invite-notify if possible */
+		sendto_channel(channel, client, NULL, "o",
+			CAP_INVITE_NOTIFY, SEND_LOCAL, mtags,
+			":%s INVITE %s %s",
+			client->name, target->name, channel->name);
+	}
+
+	/* add to list and notify the person who got invited */
+	if (MyConnect(target))
+	{
+		if (IsUser(client) && (check_channel_access(client, channel, "oaq")
+			|| IsULine(client)
+			|| ValidatePermissionsForPath("channel:override:invite:self",client,NULL,channel,NULL)
+			))
+		{
+			add_invite(client, target, channel, mtags);
+		}
+
+		if (!is_silenced(client, target))
+		{
+			sendto_prefix_one(target, client, mtags, ":%s INVITE %s :%s", client->name,
+				target->name, channel->name);
+		}
+	}
+	free_message_tags(mtags);
+}
+
+void invite_operoverride_msg(Client *client, Channel *channel, char *override_mode, char *override_mode_text)
+{
+	unreal_log(ULOG_INFO, "operoverride", "OPEROVERRIDE_INVITE", client,
+		   "OperOverride: $client.details invited him/herself into $channel (Overriding $override_mode_text)",
+		   log_data_string("override_type", "join"),
+		   log_data_string("override_mode", override_mode),
+		   log_data_string("override_mode_text", override_mode_text),
+		   log_data_channel("channel", channel));
+}
+
 /*
 ** cmd_invite
 **	parv[1] - user to invite
-**	parv[2] - channel number
+**	parv[2] - channel name
+**  parv[3] - override (S2S only)
 */
 CMD_FUNC(cmd_invite)
 {
-	Client *target;
-	Channel *channel;
+	Client *target = NULL;
+	Channel *channel = NULL;
 	int override = 0;
 	int i = 0;
+	int params_ok = 0;
 	Hook *h;
 
-	if (parc == 1)
+	if (parc >= 3 && *parv[1] != '\0')
 	{
-		send_invite_list(client);
-		return;
+		params_ok = 1;
+		target = find_user(parv[1], NULL);
+		channel = find_channel(parv[2]);
 	}
 	
-	if (parc < 3 || *parv[1] == '\0')
+	if (!MyConnect(client))
+	/*** remote invite ***/
 	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "INVITE");
+		if (!params_ok)
+			return;
+		/* the client or channel may be already gone */
+		if (!target)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+			return;
+		}
+		if (!channel)
+		{
+			sendnumeric(client, ERR_NOSUCHCHANNEL, parv[2]);
+			return;
+		}
+		if (parc >= 4 && !BadPtr(parv[3]))
+		{
+			override = atoi(parv[3]);
+		}
+
+		/* no further checks */
+
+		invite_process(client, target, channel, recv_mtags, override);
 		return;
 	}
 
-	if (!(target = find_person(parv[1], NULL)))
+	/*** local invite ***/
+
+	/* the client requested own invite list */
+	if (parc == 1)
 	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		send_invite_list(client);
 		return;
 	}
 
-	if (MyConnect(client) && !valid_channelname(parv[2]))
+	/* notify user about bad parameters */
+	if (!params_ok)
 	{
-		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[2]);
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "INVITE");
+		return;
+	}
+
+	if (!target)
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
 	}
 
-	if (!(channel = find_channel(parv[2], NULL)))
+	if (!channel)
 	{
 		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[2]);
 		return;
 	}
 
+	/* proceed with the command */
 	for (h = Hooks[HOOKTYPE_PRE_INVITE]; h; h = h->next)
 	{
 		i = (*(h->func.intfunc))(client,target,channel,&override);
@@ -134,15 +392,15 @@ CMD_FUNC(cmd_invite)
 		return;
 	}
 
-	if (channel->mode.mode & MODE_INVITEONLY)
+	if (has_channel_mode(channel, 'i'))
 	{
-		if (!is_chan_op(client, channel) && !IsULine(client))
+		if (!check_channel_access(client, channel, "oaq") && !IsULine(client))
 		{
 			if (ValidatePermissionsForPath("channel:override:invite:invite-only",client,NULL,channel,NULL) && client == target)
 			{
 				override = 1;
 			} else {
-				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
+				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
 				return;
 			}
 		}
@@ -152,100 +410,52 @@ CMD_FUNC(cmd_invite)
 			{
 				override = 1;
 			} else {
-				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
+				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
 				return;
 			}
 		}
 	}
 
 	if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN &&
-	    !strcasecmp(channel->chname, SPAMFILTER_VIRUSCHAN) &&
-	    !is_chan_op(client, channel) && !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL))
+	    !strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN) &&
+	    !check_channel_access(client, channel, "oaq") && !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL))
 	{
-		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
 		return;
 	}
 
-	if (MyUser(client))
-	{
-		if (target_limit_exceeded(client, target, target->name))
-			return;
+	if (target_limit_exceeded(client, target, target->name))
+		return;
 
-		if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL) &&
-		    flood_limit_exceeded(client, FLD_INVITE))
-		{
-			sendnumeric(client, RPL_TRYAGAIN, "INVITE");
-			return;
-		}
+	if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL) &&
+	    flood_limit_exceeded(client, FLD_INVITE))
+	{
+		sendnumeric(client, RPL_TRYAGAIN, "INVITE");
+		return;
+	}
 
-		if (!override)
+	if (!override)
+	{
+		sendnumeric(client, RPL_INVITING, target->name, channel->name);
+		if (target->user->away)
 		{
-			sendnumeric(client, RPL_INVITING, target->name, channel->chname);
-			if (target->user->away)
-			{
-				sendnumeric(client, RPL_AWAY, target->name, target->user->away);
-			}
+			sendnumeric(client, RPL_AWAY, target->name, target->user->away);
 		}
 	}
-
-	/* Send OperOverride messages */
-	if (override && MyConnect(target))
+	else
 	{
+		/* Send OperOverride messages */
+		char override_what = '\0';
 		if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
-		{
-			sendto_snomask_global(SNO_EYES,
-			  "*** OperOverride -- %s (%s@%s) invited him/herself into %s (overriding +b).",
-			  client->name, client->user->username, client->user->realhost, channel->chname);
-
-			/* Logging implementation added by XeRXeS */
-			ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) invited him/herself into %s (Overriding Ban).",
-				client->name, client->user->username, client->user->realhost, channel->chname);
-
-		}
-		else if (channel->mode.mode & MODE_INVITEONLY)
-		{
-			sendto_snomask_global(SNO_EYES,
-			  "*** OperOverride -- %s (%s@%s) invited him/herself into %s (overriding +i).",
-			  client->name, client->user->username, client->user->realhost, channel->chname);
-
-			/* Logging implementation added by XeRXeS */
-			ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) invited him/herself into %s (Overriding Invite Only)",
-				client->name, client->user->username, client->user->realhost, channel->chname);
-
-		}
-		else if (channel->mode.limit)
-		{
-			sendto_snomask_global(SNO_EYES,
-			  "*** OperOverride -- %s (%s@%s) invited him/herself into %s (overriding +l).",
-			  client->name, client->user->username, client->user->realhost, channel->chname);
-
-			/* Logging implementation added by XeRXeS */
-			ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) invited him/herself into %s (Overriding Limit)",
-				client->name, client->user->username, client->user->realhost, channel->chname);
-
-		}
-
-		else if (*channel->mode.key)
-		{
-			sendto_snomask_global(SNO_EYES,
-			  "*** OperOverride -- %s (%s@%s) invited him/herself into %s (overriding +k).",
-			  client->name, client->user->username, client->user->realhost, channel->chname);
-
-			/* Logging implementation added by XeRXeS */
-			ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) invited him/herself into %s (Overriding Key)",
-				client->name, client->user->username, client->user->realhost, channel->chname);
-
-		}
+			invite_operoverride_msg(client, channel, "b", "ban");
+		else if (has_channel_mode(channel, 'i'))
+			invite_operoverride_msg(client, channel, "i", "invite only");
+		else if (has_channel_mode(channel, 'l'))
+			invite_operoverride_msg(client, channel, "l", "user limit");
+		else if (has_channel_mode(channel, 'k'))
+			invite_operoverride_msg(client, channel, "k", "key");
 		else if (has_channel_mode(channel, 'z'))
-		{
-			sendto_snomask_global(SNO_EYES,
-			  "*** OperOverride -- %s (%s@%s) invited him/herself into %s (overriding +z).",
-			  client->name, client->user->username, client->user->realhost, channel->chname);
-
-			/* Logging implementation added by XeRXeS */
-			ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) invited him/herself into %s (Overriding SSL/TLS-Only)",
-				client->name, client->user->username, client->user->realhost, channel->chname);
-		}
+			invite_operoverride_msg(client, channel, "z", "secure only");
 #ifdef OPEROVERRIDE_VERIFY
 		else if (channel->mode.mode & MODE_SECRET || channel->mode.mode & MODE_PRIVATE)
 		       override = -1;
@@ -254,40 +464,79 @@ CMD_FUNC(cmd_invite)
 			return;
 	}
 
-	if (MyConnect(target))
+	/* allowed to proceed */
+	invite_process(client, target, channel, recv_mtags, override);
+}
+
+/** Register an invite from someone to a channel - so they can bypass +i etc.
+ * @param from		The person sending the invite
+ * @param to		The person who is invited to join
+ * @param channel	The channel
+ * @param mtags		Message tags associated with this INVITE command
+ */
+void add_invite(Client *from, Client *to, Channel *channel, MessageTag *mtags)
+{
+	Link *inv, *tmp;
+
+	del_invite(to, channel);
+	/* If too many invite entries then delete the oldest one */
+	if (link_list_length(CLIENT_INVITES(to)) >= MAXCHANNELSPERUSER)
 	{
-		if (IsUser(client) 
-		    && (is_chan_op(client, channel)
-		    || IsULine(client)
-		    || ValidatePermissionsForPath("channel:override:invite:self",client,NULL,channel,NULL)
-		    ))
-		{
-			MessageTag *mtags = NULL;
+		for (tmp = CLIENT_INVITES(to); tmp->next; tmp = tmp->next)
+			;
+		del_invite(to, tmp->value.channel);
 
-			new_message(&me, NULL, &mtags);
-			if (override == 1)
-			{
-				sendto_channel(channel, &me, NULL, PREFIX_OP|PREFIX_ADMIN|PREFIX_OWNER,
-				               0, SEND_ALL, mtags,
-				               ":%s NOTICE @%s :OperOverride -- %s invited him/herself into the channel.",
-				               me.name, channel->chname, client->name);
-			} else
-			if (override == 0)
-			{
-				sendto_channel(channel, &me, NULL, PREFIX_OP|PREFIX_ADMIN|PREFIX_OWNER,
-				               0, SEND_ALL, mtags,
-				               ":%s NOTICE @%s :%s invited %s into the channel.",
-				               me.name, channel->chname, client->name, target->name);
-			}
-			add_invite(client, target, channel, mtags);
-			free_message_tags(mtags);
-		}
 	}
-
-	/* Notify the person who got invited */
-	if (!is_silenced(client, target))
+	/* We get pissy over too many invites per channel as well now,
+	 * since otherwise mass-inviters could take up some major
+	 * resources -Donwulff
+	 */
+	if (link_list_length(CHANNEL_INVITES(channel)) >= MAXCHANNELSPERUSER)
 	{
-		sendto_prefix_one(target, client, NULL, ":%s INVITE %s :%s", client->name,
-			target->name, channel->chname);
+		for (tmp = CHANNEL_INVITES(channel); tmp->next; tmp = tmp->next)
+			;
+		del_invite(tmp->value.client, channel);
 	}
+	/*
+	 * add client to the beginning of the channel invite list
+	 */
+	inv = make_link();
+	inv->value.client = to;
+	inv->next = CHANNEL_INVITES(channel);
+	CHANNEL_INVITES(channel) = inv;
+	/*
+	 * add channel to the beginning of the client invite list
+	 */
+	inv = make_link();
+	inv->value.channel = channel;
+	inv->next = CLIENT_INVITES(to);
+	CLIENT_INVITES(to) = inv;
+
+	RunHook(HOOKTYPE_INVITE, from, to, channel, mtags);
 }
+
+/** Delete a previous invite of someone to a channel.
+ * @param client	The client who was invited
+ * @param channel	The channel to which the person was invited
+ */
+void del_invite(Client *client, Channel *channel)
+{
+	Link **inv, *tmp;
+
+	for (inv = (Link **)&CHANNEL_INVITES(channel); (tmp = *inv); inv = &tmp->next)
+		if (tmp->value.client == client)
+		{
+			*inv = tmp->next;
+			free_link(tmp);
+			break;
+		}
+
+	for (inv = (Link **)&CLIENT_INVITES(client); (tmp = *inv); inv = &tmp->next)
+		if (tmp->value.channel == channel)
+		{
+			*inv = tmp->next;
+			free_link(tmp);
+			break;
+		}
+}
+
diff --git a/src/modules/ircops.c b/src/modules/ircops.c
@@ -31,7 +31,7 @@ ModuleHeader MOD_HEADER
 	"3.71",
 	"/IRCOPS command that lists IRC Operators",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
diff --git a/src/modules/ison.c b/src/modules/ison.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /ison", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -66,6 +66,7 @@ static char buf[BUFSIZE];
 
 CMD_FUNC(cmd_ison)
 {
+	char request[BUFSIZE];
 	char namebuf[USERLEN + HOSTLEN + 4];
 	Client *acptr;
 	char *s, *user;
@@ -82,11 +83,12 @@ CMD_FUNC(cmd_ison)
 
 	ircsnprintf(buf, sizeof(buf), ":%s %d %s :", me.name, RPL_ISON, client->name);
 
-	for (s = strtoken(&p, parv[1], " "); s; s = strtoken(&p, NULL, " "))
+	strlcpy(request, parv[1], sizeof(request));
+	for (s = strtoken(&p, request, " "); s; s = strtoken(&p, NULL, " "))
 	{
 		if ((user = strchr(s, '!')))
 			*user++ = '\0';
-		if ((acptr = find_person(s, NULL)))
+		if ((acptr = find_user(s, NULL)))
 		{
 			if (user)
 			{
diff --git a/src/modules/join.c b/src/modules/join.c
@@ -24,12 +24,12 @@
 
 /* Forward declarations */
 CMD_FUNC(cmd_join);
-void _join_channel(Channel *channel, Client *client, MessageTag *mtags, int flags);
-void _do_join(Client *client, int parc, char *parv[]);
-int _can_join(Client *client, Channel *channel, char *key, char *parv[]);
-void _userhost_save_current(Client *client);
-void _userhost_changed(Client *client);
+void _join_channel(Channel *channel, Client *client, MessageTag *mtags, const char *member_modes);
+void _do_join(Client *client, int parc, const char *parv[]);
+int _can_join(Client *client, Channel *channel, const char *key, char **errmsg);
 void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags);
+char *_get_chmodes_for_user(Client *client, const char *flags);
+void send_cannot_join_error(Client *client, int numeric, char *fmtstr, char *channel_name);
 
 /* Externs */
 extern MODVAR int spamf_ugly_vchanoverride;
@@ -37,6 +37,7 @@ extern int find_invex(Channel *channel, Client *client);
 
 /* Local vars */
 static int bouncedtimes = 0;
+long CAP_EXTENDED_JOIN = 0L;
 
 /* Macros */
 #define MAXBOUNCE   5 /** Most sensible */
@@ -48,7 +49,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /join", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_TEST()
@@ -57,15 +58,19 @@ MOD_TEST()
 	EfunctionAddVoid(modinfo->handle, EFUNC_JOIN_CHANNEL, _join_channel);
 	EfunctionAddVoid(modinfo->handle, EFUNC_DO_JOIN, _do_join);
 	EfunctionAdd(modinfo->handle, EFUNC_CAN_JOIN, _can_join);
-	EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_SAVE_CURRENT, _userhost_save_current);
-	EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_CHANGED, _userhost_changed);
 	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_JOIN_TO_LOCAL_USERS, _send_join_to_local_users);
+	EfunctionAddPVoid(modinfo->handle, EFUNC_GET_CHMODES_FOR_USER, TO_PVOIDFUNC(_get_chmodes_for_user));
 
 	return MOD_SUCCESS;
 }
 
 MOD_INIT()
 {
+	ClientCapabilityInfo c;
+	memset(&c, 0, sizeof(c));
+	c.name = "extended-join";
+	ClientCapabilityAdd(modinfo->handle, &c, &CAP_EXTENDED_JOIN);
+
 	CommandAdd(modinfo->handle, MSG_JOIN, cmd_join, MAXPARA, CMD_USER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
@@ -87,7 +92,7 @@ MOD_UNLOAD()
  * (eg: bans at the end), so don't change it unless you have a good reason
  * to do so -- Syzop.
  */
-int _can_join(Client *client, Channel *channel, char *key, char *parv[])
+int _can_join(Client *client, Channel *channel, const char *key, char **errmsg)
 {
 	Link *lp;
 	Ban *banned;
@@ -96,7 +101,7 @@ int _can_join(Client *client, Channel *channel, char *key, char *parv[])
 
 	for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next)
 	{
-		i = (*(h->func.intfunc))(client,channel,key,parv);
+		i = (*(h->func.intfunc))(client,channel,key, errmsg);
 		if (i != 0)
 			return i;
 	}
@@ -111,47 +116,32 @@ int _can_join(Client *client, Channel *channel, char *key, char *parv[])
 	/* See if we can evade this ban */
 	banned = is_banned(client, channel, BANCHK_JOIN, NULL, NULL);
 	if (banned && j == HOOK_DENY)
-		return (ERR_BANNEDFROMCHAN);
+	{
+		*errmsg = STR_ERR_BANNEDFROMCHAN;
+		return ERR_BANNEDFROMCHAN;
+	}
 
 	if (is_invited(client, channel))
-		return 0; /* allowed */
-
-        if (channel->users >= channel->mode.limit)
-        {
-                /* Hmmm.. don't really like this.. and not at this place */
-                
-                for (h = Hooks[HOOKTYPE_CAN_JOIN_LIMITEXCEEDED]; h; h = h->next) 
-                {
-                        i = (*(h->func.intfunc))(client,channel,key,parv);
-                        if (i != 0)
-                                return i;
-                }
-
-                /* We later check again for this limit (in case +L was not set) */
-        }
-
+		return 0; /* allowed to walk through all the other modes */
 
-        if (*channel->mode.key && (BadPtr(key) || strcmp(channel->mode.key, key)))
-                return (ERR_BADCHANNELKEY);
-
-        if ((channel->mode.mode & MODE_INVITEONLY) && !find_invex(channel, client))
-                return (ERR_INVITEONLYCHAN);
-
-        if ((channel->mode.limit && channel->users >= channel->mode.limit))
-                return (ERR_CHANNELISFULL);
-
-        if (banned)
-                return (ERR_BANNEDFROMCHAN);
+	if (banned)
+	{
+		*errmsg = STR_ERR_BANNEDFROMCHAN;
+		return ERR_BANNEDFROMCHAN;
+	}
 
 #ifndef NO_OPEROVERRIDE
 #ifdef OPEROVERRIDE_VERIFY
-        if (ValidatePermissionsForPath("channel:override:privsecret",client,NULL,channel,NULL) && (channel->mode.mode & MODE_SECRET ||
-            channel->mode.mode & MODE_PRIVATE) && !is_autojoin_chan(channel->chname))
-                return (ERR_OPERSPVERIFY);
+	if (ValidatePermissionsForPath("channel:override:privsecret",client,NULL,channel,NULL) && (channel->mode.mode & MODE_SECRET ||
+	    channel->mode.mode & MODE_PRIVATE) && !is_autojoin_chan(channel->name))
+	{
+		*errmsg = STR_ERR_OPERSPVERIFY;
+		return (ERR_OPERSPVERIFY);
+	}
 #endif
 #endif
 
-        return 0;
+	return 0;
 }
 
 /*
@@ -170,7 +160,12 @@ CMD_FUNC(cmd_join)
 	int r;
 
 	if (bouncedtimes)
-		sendto_realops("join: bouncedtimes=%d??? [please report at https://bugs.unrealircd.org/]", bouncedtimes);
+	{
+		unreal_log(ULOG_ERROR, "join", "BUG_JOIN_BOUNCEDTIMES", NULL,
+		           "[BUG] join: bouncedtimes is not initialized to zero ($bounced_times)!! "
+		           "Please report at https://bugs.unrealircd.org/",
+		           log_data_integer("bounced_times", bouncedtimes));
+	}
 
 	bouncedtimes = 0;
 	if (IsServer(client))
@@ -190,15 +185,13 @@ void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mta
 	Client *acptr;
 	char joinbuf[512];
 	char exjoinbuf[512];
-	long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
-	long CAP_AWAY_NOTIFY = ClientCapabilityBit("away-notify");
 
 	ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN :%s",
-		client->name, client->user->username, GetHost(client), channel->chname);
+		client->name, client->user->username, GetHost(client), channel->name);
 
 	ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s",
-		client->name, client->user->username, GetHost(client), channel->chname,
-		!isdigit(*client->user->svid) ? client->user->svid : "*",
+		client->name, client->user->username, GetHost(client), channel->name,
+		IsLoggedIn(client) ? client->user->account : "*",
 		client->info);
 
 	for (lp = channel->members; lp; lp = lp->next)
@@ -208,46 +201,37 @@ void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mta
 		if (!MyConnect(acptr))
 			continue; /* only locally connected clients */
 
-		if (chanops_only && !(lp->flags & (CHFL_HALFOP|CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN)) && (client != acptr))
+		if (chanops_only && !check_channel_access_member(lp, "hoaq") && (client != acptr))
 			continue; /* skip non-ops if requested to (used for mode +D), but always send to 'client' */
 
 		if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN))
 			sendto_one(acptr, mtags, "%s", exjoinbuf);
 		else
 			sendto_one(acptr, mtags, "%s", joinbuf);
-
-		if (client->user->away && HasCapabilityFast(acptr, CAP_AWAY_NOTIFY))
-		{
-			MessageTag *mtags_away = NULL;
-			new_message(client, NULL, &mtags_away);
-			sendto_one(acptr, mtags_away, ":%s!%s@%s AWAY :%s",
-			           client->name, client->user->username, GetHost(client), client->user->away);
-			free_message_tags(mtags_away);
-		}
 	}
 }
 
 /* Routine that actually makes a user join the channel
  * this does no actual checking (banned, etc.) it just adds the user
  */
-void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, int flags)
+void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, const char *member_modes)
 {
 	MessageTag *mtags = NULL; /** Message tags to send to local users (sender is :user) */
 	MessageTag *mtags_sjoin = NULL; /* Message tags to send to remote servers for SJOIN (sender is :me.id) */
-	char *parv[] = { 0, 0 };
+	const char *parv[3];
 
 	/* Same way as in SJOIN */
-	new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->chname);
+	new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->name);
 
 	new_message(&me, recv_mtags, &mtags_sjoin);
 
-	add_user_to_channel(channel, client, flags);
+	add_user_to_channel(channel, client, member_modes);
 
 	send_join_to_local_users(client, channel, mtags);
 
 	sendto_server(client, 0, 0, mtags_sjoin, ":%s SJOIN %lld %s :%s%s ",
 		me.id, (long long)channel->creationtime,
-		channel->chname, chfl_to_sjoin_symbol(flags), client->id);
+		channel->name, modes_to_sjoin_prefix(member_modes), client->id);
 
 	if (MyUser(client))
 	{
@@ -260,56 +244,63 @@ void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, int
 		{
 			channel->creationtime = TStime();
 			sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld",
-			    me.id, channel->chname, (long long)channel->creationtime);
+			    me.id, channel->name, (long long)channel->creationtime);
 		}
-		del_invite(client, channel);
 
 		if (channel->topic)
 		{
-			sendnumeric(client, RPL_TOPIC, channel->chname, channel->topic);
-			sendnumeric(client, RPL_TOPICWHOTIME, channel->chname, channel->topic_nick,
-			    channel->topic_time);
+			sendnumeric(client, RPL_TOPIC, channel->name, channel->topic);
+			sendnumeric(client, RPL_TOPICWHOTIME, channel->name, channel->topic_nick, (long long)channel->topic_time);
 		}
 		
 		/* Set default channel modes (set::modes-on-join).
 		 * Set only if it's the 1st user and only if no other modes have been set
 		 * already (eg: +P, permanent).
 		 */
-		if ((channel->users == 1) && !channel->mode.mode && !channel->mode.extmode &&
-		    (MODES_ON_JOIN || iConf.modes_on_join.extmodes))
+		if ((channel->users == 1) && !channel->mode.mode && MODES_ON_JOIN)
 		{
-			int i;
 			MessageTag *mtags_mode = NULL;
+			Cmode *cm;
+			char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
+			channel->mode.mode = MODES_ON_JOIN;
 
-			channel->mode.extmode =  iConf.modes_on_join.extmodes;
 			/* Param fun */
-			for (i = 0; i <= Channelmode_highest; i++)
+			for (cm=channelmodes; cm; cm = cm->next)
 			{
-				if (!Channelmode_Table[i].flag || !Channelmode_Table[i].paracount)
+				if (!cm->letter || !cm->paracount)
 					continue;
-				if (channel->mode.extmode & Channelmode_Table[i].mode)
-				        cm_putparameter(channel, Channelmode_Table[i].flag, iConf.modes_on_join.extparams[i]);
+				if (channel->mode.mode & cm->mode)
+				        cm_putparameter(channel, cm->letter, iConf.modes_on_join.extparams[cm->letter]);
 			}
 
-			channel->mode.mode = MODES_ON_JOIN;
-
 			*modebuf = *parabuf = 0;
-			channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel);
+			channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 0);
 			/* This should probably be in the SJOIN stuff */
-			new_message_special(&me, recv_mtags, &mtags_mode, ":%s MODE %s %s %s", me.name, channel->chname, modebuf, parabuf);
+			new_message_special(&me, recv_mtags, &mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf);
 			sendto_server(NULL, 0, 0, mtags_mode, ":%s MODE %s %s %s %lld",
-			    me.id, channel->chname, modebuf, parabuf, (long long)channel->creationtime);
-			sendto_one(client, mtags_mode, ":%s MODE %s %s %s", me.name, channel->chname, modebuf, parabuf);
+			    me.id, channel->name, modebuf, parabuf, (long long)channel->creationtime);
+			sendto_one(client, mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf);
 			free_message_tags(mtags_mode);
 		}
 
-		parv[0] = client->name;
-		parv[1] = channel->chname;
+		parv[0] = NULL;
+		parv[1] = channel->name;
+		parv[2] = NULL;
 		do_cmd(client, NULL, "NAMES", 2, parv);
 
-		RunHook4(HOOKTYPE_LOCAL_JOIN, client, channel, mtags, parv);
+		unreal_log(ULOG_INFO, "join", "LOCAL_CLIENT_JOIN", client,
+			   "User $client joined $channel",
+			   log_data_channel("channel", channel),
+			   log_data_string("modes", member_modes));
+
+		RunHook(HOOKTYPE_LOCAL_JOIN, client, channel, mtags);
 	} else {
-		RunHook4(HOOKTYPE_REMOTE_JOIN, client, channel, mtags, parv);
+		unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", client,
+			   "User $client joined $channel",
+			   log_data_channel("channel", channel),
+			   log_data_string("modes", member_modes));
+		RunHook(HOOKTYPE_REMOTE_JOIN, client, channel, mtags);
 	}
 
 	free_message_tags(mtags);
@@ -323,18 +314,21 @@ void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, int
  * increased every time we enter this loop and decreased anytime we leave the
  * loop. So be carefull not to use a simple 'return' after bouncedtimes++. -- Syzop
  */
-void _do_join(Client *client, int parc, char *parv[])
+void _do_join(Client *client, int parc, const char *parv[])
 {
+	char request[BUFSIZE];
+	char request_key[BUFSIZE];
 	char jbuf[BUFSIZE], jbuf2[BUFSIZE];
-	char *orig_parv1;
+	const char *orig_parv1;
 	Membership *lp;
 	Channel *channel;
 	char *name, *key = NULL;
-	int i, flags = 0, ishold;
+	int i, ishold;
 	char *p = NULL, *p2 = NULL;
 	TKL *tklban;
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("JOIN");
+	const char *member_modes = "";
 
 #define RET() do { bouncedtimes--; parv[1] = orig_parv1; return; } while(0)
 
@@ -343,6 +337,11 @@ void _do_join(Client *client, int parc, char *parv[])
 		sendnumeric(client, ERR_NEEDMOREPARAMS, "JOIN");
 		return;
 	}
+
+	/* For our tests we need super accurate time for JOINs or they mail fail. */
+	gettimeofday(&timeofday_tv, NULL);
+	timeofday = timeofday_tv.tv_sec;
+
 	bouncedtimes++;
 	orig_parv1 = parv[1];
 	/* don't use 'return;' but 'RET();' from here ;p */
@@ -359,9 +358,8 @@ void _do_join(Client *client, int parc, char *parv[])
 	   ** Rebuild list of channels joined to be the actual result of the
 	   ** JOIN.  Note that "JOIN 0" is the destructive problem.
 	 */
-	for (i = 0, name = strtoken(&p, parv[1], ",");
-	     name;
-	     i++, name = strtoken(&p, NULL, ","))
+	strlcpy(request, parv[1], sizeof(request));
+	for (i = 0, name = strtoken(&p, request, ","); name; i++, name = strtoken(&p, NULL, ","))
 	{
 		if (MyUser(client) && (++ntargets > maxtargets))
 		{
@@ -370,7 +368,7 @@ void _do_join(Client *client, int parc, char *parv[])
 		}
 		if (*name == '0' && !atoi(name))
 		{
-			/* UnrealIRCd 5: we only support "JOIN 0",
+			/* UnrealIRCd 5+: we only support "JOIN 0",
 			 * "JOIN 0,#somechan" etc... so only at the beginning.
 			 * We do not support it half-way like "JOIN #a,0,#b"
 			 * since that doesn't make sense, unless you are flooding...
@@ -384,7 +382,7 @@ void _do_join(Client *client, int parc, char *parv[])
 		if (MyConnect(client) && !valid_channelname(name))
 		{
 			send_invalid_channelname(client, name);
-			if (IsOper(client) && find_channel(name, NULL))
+			if (IsOper(client) && find_channel(name))
 			{
 				/* Give IRCOps a bit more information */
 				sendnotice(client, "Channel '%s' is unjoinable because it contains illegal characters. "
@@ -415,7 +413,10 @@ void _do_join(Client *client, int parc, char *parv[])
 
 	p = NULL;
 	if (parv[2])
-		key = strtoken(&p2, parv[2], ",");
+	{
+		strlcpy(request_key, parv[2], sizeof(request_key));
+		key = strtoken(&p2, request_key, ",");
+	}
 	parv[2] = NULL;		/* for cmd_names call later, parv[parc] must == NULL */
 
 	for (name = strtoken(&p, jbuf, ",");
@@ -442,13 +443,13 @@ void _do_join(Client *client, int parc, char *parv[])
 
 				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 				               ":%s PART %s :%s",
-				               client->name, channel->chname, "Left all channels");
-				sendto_server(client, 0, 0, mtags, ":%s PART %s :Left all channels", client->name, channel->chname);
+				               client->name, channel->name, "Left all channels");
+				sendto_server(client, 0, 0, mtags, ":%s PART %s :Left all channels", client->name, channel->name);
 
 				if (MyConnect(client))
-					RunHook4(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels");
+					RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels");
 
-				remove_user_from_channel(client, channel);
+				remove_user_from_channel(client, channel, 0);
 				free_message_tags(mtags);
 			}
 			continue;
@@ -456,19 +457,7 @@ void _do_join(Client *client, int parc, char *parv[])
 
 		if (MyConnect(client))
 		{
-			/*
-			   ** local client is first to enter previously nonexistant
-			   ** channel so make them (rightfully) the Channel
-			   ** Operator.
-			 */
-			/* Where did this come from? Potvin ? --Stskeeps
-			   flags = (ChannelExists(name)) ? CHFL_DEOPPED :
-			   CHFL_CHANOWNER;
-
-			 */
-
-			flags =
-			    (ChannelExists(name)) ? CHFL_DEOPPED : LEVEL_ON_JOIN;
+			member_modes = (ChannelExists(name)) ? "" : LEVEL_ON_JOIN;
 
 			if (!ValidatePermissionsForPath("immune:maxchannelsperuser",client,NULL,NULL,NULL))	/* opers can join unlimited chans */
 				if (client->user->joined >= MAXCHANNELSPERUSER)
@@ -486,8 +475,9 @@ void _do_join(Client *client, int parc, char *parv[])
 					{
 						if (d->warn)
 						{
-							sendto_snomask(SNO_EYES, "*** %s tried to join forbidden channel %s",
-								get_client_name(client, 1), name);
+							unreal_log(ULOG_INFO, "join", "JOIN_DENIED_FORBIDDEN_CHANNEL", client,
+							           "Client $client.details tried to join forbidden channel $channel",
+							           log_data_string("channel", name));
 						}
 						if (d->reason)
 							sendnumeric(client, ERR_FORBIDDENCHANNEL, name, d->reason);
@@ -514,7 +504,7 @@ void _do_join(Client *client, int parc, char *parv[])
 			    !strcasecmp(name, SPAMFILTER_VIRUSCHAN) &&
 			    !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL) && !spamf_ugly_vchanoverride)
 			{
-				Channel *channel = find_channel(name, NULL);
+				Channel *channel = find_channel(name);
 				
 				if (!channel || !is_invited(client, channel))
 				{
@@ -525,7 +515,7 @@ void _do_join(Client *client, int parc, char *parv[])
 			}
 		}
 
-		channel = get_channel(client, name, CREATE);
+		channel = make_channel(name);
 		if (channel && (lp = find_membership_link(client->user->channel, channel)))
 			continue;
 
@@ -534,23 +524,14 @@ void _do_join(Client *client, int parc, char *parv[])
 
 		i = HOOK_CONTINUE;
 		if (!MyConnect(client))
-			flags = CHFL_DEOPPED;
+			member_modes = "";
 		else
 		{
 			Hook *h;
+			char *errmsg = NULL;
 			for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) 
 			{
-				/* Note: this is just a hack not to break the ABI but still be
-				 * able to fix https://bugs.unrealircd.org/view.php?id=5644
-				 * In the future we should just drop the parv/parx argument
-				 * and use key as an argument instead.
-				 */
-				char *parx[4];
-				parx[0] = NULL;
-				parx[1] = name;
-				parx[2] = key;
-				parx[3] = NULL;
-				i = (*(h->func.intfunc))(client,channel,parx);
+				i = (*(h->func.intfunc))(client,channel,key);
 				if (i == HOOK_DENY || i == HOOK_ALLOW)
 					break;
 			}
@@ -564,12 +545,10 @@ void _do_join(Client *client, int parc, char *parv[])
 			}
 			/* If they are allowed, don't check can_join */
 			if (i != HOOK_ALLOW && 
-			   (i = can_join(client, channel, key, parv)))
+			   (i = can_join(client, channel, key, &errmsg)))
 			{
 				if (i != -1)
-				{
-					sendnumeric(client, i, name);
-				}
+					send_cannot_join_error(client, i, errmsg, name);
 				continue;
 			}
 		}
@@ -585,43 +564,42 @@ void _do_join(Client *client, int parc, char *parv[])
 		 * and so on, each with their own unique msgid and such.
 		 */
 		new_message(client, NULL, &mtags);
-		join_channel(channel, client, mtags, flags);
+		join_channel(channel, client, mtags, member_modes);
 		free_message_tags(mtags);
 	}
 	RET();
 #undef RET
 }
 
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+void send_cannot_join_error(Client *client, int numeric, char *fmtstr, char *channel_name)
+{
+	// TODO: add single %s validation !
+	sendnumericfmt(client, numeric, fmtstr, channel_name);
+}
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
 /* Additional channel-related functions. I've put it here instead
  * of the core so it could be upgraded on the fly should it be necessary.
  */
 
-char *get_chmodes_for_user(Client *client, int flags)
+char *_get_chmodes_for_user(Client *client, const char *member_flags)
 {
 	static char modebuf[512]; /* returned */
 	char flagbuf[8]; /* For holding "vhoaq" */
-	char *p = flagbuf;
 	char parabuf[512];
 	int n, i;
 
-	if (!flags)
+	if (BadPtr(member_flags))
 		return "";
 
-	if (flags & MODE_CHANOWNER)
-		*p++ = 'q';
-	if (flags & MODE_CHANADMIN)
-		*p++ = 'a';
-	if (flags & MODE_CHANOP)
-		*p++ = 'o';
-	if (flags & MODE_VOICE)
-		*p++ = 'v';
-	if (flags & MODE_HALFOP)
-		*p++ = 'h';
-	*p = '\0';
-
 	parabuf[0] = '\0';
-
-	n = strlen(flagbuf);
+	n = strlen(member_flags);
 	if (n)
 	{
 		for (i=0; i < n; i++)
@@ -631,182 +609,9 @@ char *get_chmodes_for_user(Client *client, int flags)
 				strlcat(parabuf, " ", sizeof(parabuf));
 		}
 		/* And we have our mode line! */
-		snprintf(modebuf, sizeof(modebuf), "+%s %s", flagbuf, parabuf);
+		snprintf(modebuf, sizeof(modebuf), "+%s %s", member_flags, parabuf);
 		return modebuf;
 	}
 
 	return "";
 }
-
-static char remember_nick[NICKLEN+1];
-static char remember_user[USERLEN+1];
-static char remember_host[HOSTLEN+1];
-
-/** Save current nick/user/host. Used later by userhost_changed(). */
-void _userhost_save_current(Client *client)
-{
-	strlcpy(remember_nick, client->name, sizeof(remember_nick));
-	strlcpy(remember_user, client->user->username, sizeof(remember_user));
-	strlcpy(remember_host, GetHost(client), sizeof(remember_host));
-}
-
-/** User/Host changed for user.
- * Note that userhost_save_current() needs to be called before this
- * to save the old username/hostname.
- * This userhost_changed() function deals with notifying local clients
- * about the user/host change by sending PART+JOIN+MODE if
- * set::allow-userhost-change force-rejoin is in use,
- * and it wills end "CAP chghost" to such capable clients.
- * It will also deal with bumping fakelag for the user since a user/host
- * change is costly, doesn't matter if it was self-induced or not.
- *
- * Please call this function for any user/host change by doing:
- * userhost_save_current(client);
- * << change username or hostname here >>
- * userhost_changed(client);
- */
-void _userhost_changed(Client *client)
-{
-	Membership *channels;
-	Member *lp;
-	Client *acptr;
-	int impact = 0;
-	char buf[512];
-	long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join");
-	long CAP_CHGHOST = ClientCapabilityBit("chghost");
-
-	if (strcmp(remember_nick, client->name))
-	{
-		ircd_log(LOG_ERROR, "[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: %s",
-			client->name);
-		ircd_log(LOG_ERROR, "Please report above bug on https://bugs.unrealircd.org/");
-		sendto_realops("[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: %s",
-			client->name);
-		sendto_realops("Please report above bug on https://bugs.unrealircd.org/");
-		return; /* We cannot safely process this request anymore */
-	}
-
-	/* It's perfectly acceptable to call us even if the userhost didn't change. */
-	if (!strcmp(remember_user, client->user->username) && !strcmp(remember_host, GetHost(client)))
-		return; /* Nothing to do */
-
-	/* Most of the work is only necessary for set::allow-userhost-change force-rejoin */
-	if (UHOST_ALLOWED == UHALLOW_REJOIN)
-	{
-		/* Walk through all channels of this user.. */
-		for (channels = client->user->channel; channels; channels = channels->next)
-		{
-			Channel *channel = channels->channel;
-			int flags = channels->flags;
-			char *modes;
-			char partbuf[512]; /* PART */
-			char joinbuf[512]; /* JOIN */
-			char exjoinbuf[512]; /* JOIN (for CAP extended-join) */
-			char modebuf[512]; /* MODE (if any) */
-			int chanops_only = invisible_user_in_channel(client, channel);
-
-			modebuf[0] = '\0';
-
-			/* If the user is banned, don't send any rejoins, it would only be annoying */
-			if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
-				continue;
-
-			/* Prepare buffers for PART, JOIN, MODE */
-			ircsnprintf(partbuf, sizeof(partbuf), ":%s!%s@%s PART %s :%s",
-						remember_nick, remember_user, remember_host,
-						channel->chname,
-						"Changing host");
-
-			ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s",
-						client->name, client->user->username, GetHost(client), channel->chname);
-
-			ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s",
-				client->name, client->user->username, GetHost(client), channel->chname,
-				!isdigit(*client->user->svid) ? client->user->svid : "*",
-				client->info);
-
-			modes = get_chmodes_for_user(client, flags);
-			if (!BadPtr(modes))
-				ircsnprintf(modebuf, sizeof(modebuf), ":%s MODE %s %s", me.name, channel->chname, modes);
-
-			for (lp = channel->members; lp; lp = lp->next)
-			{
-				acptr = lp->client;
-
-				if (acptr == client)
-					continue; /* skip self */
-
-				if (!MyConnect(acptr))
-					continue; /* only locally connected clients */
-
-				if (chanops_only && !(lp->flags & (CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN)))
-					continue; /* skip non-ops if requested to (used for mode +D) */
-
-				if (HasCapabilityFast(acptr, CAP_CHGHOST))
-					continue; /* we notify 'CAP chghost' users in a different way, so don't send it here. */
-
-				impact++;
-
-				/* FIXME: if a client does not have the "chghost" cap then
-				 * here we will not generate a proper new message, probably
-				 * needs to be fixed... I skipped doing it for now.
-				 */
-				sendto_one(acptr, NULL, "%s", partbuf);
-
-				if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN))
-					sendto_one(acptr, NULL, "%s", exjoinbuf);
-				else
-					sendto_one(acptr, NULL, "%s", joinbuf);
-
-				if (*modebuf)
-					sendto_one(acptr, NULL, "%s", modebuf);
-			}
-		}
-	}
-
-	/* Now deal with "CAP chghost" clients.
-	 * This only needs to be sent one per "common channel".
-	 * This would normally call sendto_common_channels_local_butone() but the user already
-	 * has the new user/host.. so we do it here..
-	 */
-	ircsnprintf(buf, sizeof(buf), ":%s!%s@%s CHGHOST %s %s",
-	            remember_nick, remember_user, remember_host,
-	            client->user->username,
-	            GetHost(client));
-	current_serial++;
-	for (channels = client->user->channel; channels; channels = channels->next)
-	{
-		for (lp = channels->channel->members; lp; lp = lp->next)
-		{
-			acptr = lp->client;
-			if (MyUser(acptr) && HasCapabilityFast(acptr, CAP_CHGHOST) &&
-			    (acptr->local->serial != current_serial) && (client != acptr))
-			{
-				/* FIXME: send mtag */
-				sendto_one(acptr, NULL, "%s", buf);
-				acptr->local->serial = current_serial;
-			}
-		}
-	}
-
-	if (MyUser(client))
-	{
-		/* We take the liberty of sending the CHGHOST to the impacted user as
-		 * well. This makes things easy for client coders.
-		 * (Note that this cannot be merged with the for loop from 15 lines up
-		 *  since the user may not be in any channels)
-		 */
-		if (HasCapabilityFast(client, CAP_CHGHOST))
-			sendto_one(client, NULL, "%s", buf);
-
-		/* A userhost change always generates the following network traffic:
-		 * server to server traffic, CAP "chghost" notifications, and
-		 * possibly PART+JOIN+MODE if force-rejoin had work to do.
-		 * We give the user a penalty so they don't flood...
-		 */
-		if (impact)
-			client->local->since += 7; /* Resulted in rejoins and such. */
-		else
-			client->local->since += 4; /* No rejoins */
-	}
-}
diff --git a/src/modules/jointhrottle.c b/src/modules/jointhrottle.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Join flood protection (set::anti-flood::join-flood)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 ModuleInfo *ModInfo = NULL;
@@ -40,15 +40,15 @@ typedef struct JoinFlood JoinFlood;
 
 struct JoinFlood {
 	JoinFlood *prev, *next;
-	char chname[CHANNELLEN+1];
+	char name[CHANNELLEN+1];
 	time_t firstjoin;
 	unsigned short numjoins;
 };
 
 /* Forward declarations */
 void jointhrottle_md_free(ModData *m);
-int jointhrottle_can_join(Client *client, Channel *channel, char *key, char *parv[]);
-int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+int jointhrottle_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
+int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags);
 static int isjthrottled(Client *client, Channel *channel);
 static void jointhrottle_increase_usercounter(Client *client, Channel *channel);
 EVENT(jointhrottle_cleanup_structs);
@@ -104,7 +104,7 @@ static int isjthrottled(Client *client, Channel *channel)
 
 	/* Grab user<->chan entry.. */
 	for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
-		if (!strcasecmp(e->chname, channel->chname))
+		if (!strcasecmp(e->name, channel->name))
 			break;
 	
 	if (!e)
@@ -129,7 +129,7 @@ static void jointhrottle_increase_usercounter(Client *client, Channel *channel)
 		
 	/* Grab user<->chan entry.. */
 	for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
-		if (!strcasecmp(e->chname, channel->chname))
+		if (!strcasecmp(e->name, channel->name))
 			break;
 	
 	if (!e)
@@ -149,15 +149,18 @@ static void jointhrottle_increase_usercounter(Client *client, Channel *channel)
 	}
 }
 
-int jointhrottle_can_join(Client *client, Channel *channel, char *key, char *parv[])
+int jointhrottle_can_join(Client *client, Channel *channel, const char *key, char **errmsg)
 {
 	if (!ValidatePermissionsForPath("immune:join-flood",client,NULL,channel,NULL) && isjthrottled(client, channel))
+	{
+		*errmsg = STR_ERR_TOOMANYJOINS;
 		return ERR_TOOMANYJOINS;
+	}
 	return 0;
 }
 
 
-int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[])
+int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags)
 {
 	jointhrottle_increase_usercounter(client, channel);
 	return 0;
@@ -175,12 +178,12 @@ JoinFlood *jointhrottle_addentry(Client *client, Channel *channel)
 		abort();
 
 	for (e=moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
-		if (!strcasecmp(e->chname, channel->chname))
+		if (!strcasecmp(e->name, channel->name))
 			abort(); /* already exists -- should never happen */
 #endif
 
 	e = safe_alloc(sizeof(JoinFlood));
-	strlcpy(e->chname, channel->chname, sizeof(e->chname));
+	strlcpy(e->name, channel->name, sizeof(e->name));
 
 	/* Insert our new entry as (new) head */
 	if (moddata_local_client(client, jointhrottle_md).ptr)
@@ -211,11 +214,6 @@ EVENT(jointhrottle_cleanup_structs)
 			
 			if (jf->firstjoin + iConf.floodsettings->period[FLD_JOIN] > TStime())
 				continue; /* still valid entry */
-#ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "jointhrottle_cleanup_structs(): freeing %s/%s (%ld[%ld], %ld)",
-				client->name, jf->chname, jf->firstjoin, (long)(TStime() - jf->firstjoin),
-				iConf.floodsettings->period[FLD_JOIN]);
-#endif
 			if (moddata_local_client(client, jointhrottle_md).ptr == jf)
 			{
 				/* change head */
diff --git a/src/modules/json-log-tag.c b/src/modules/json-log-tag.c
@@ -0,0 +1,90 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/monitor.c
+ *   (C) 2021 Bram Matthys and The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"json-log-tag",
+	"5.0",
+	"unrealircd.org/json-log tag for S2S and ircops",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Variables */
+long CAP_JSON_LOG = 0L;
+
+/* Forward declarations */
+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_INIT()
+{
+	ClientCapabilityInfo cap;
+	ClientCapability *c;
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "unrealircd.org/json-log";
+	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_JSON_LOG);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/json-log";
+	mtag.is_ok = json_log_mtag_is_ok;
+	mtag.should_send_to_client = json_log_mtag_should_send_to_client;
+	mtag.clicap_handler = c;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending
+ * We simply allow from servers without any syntax checking.
+ */
+int json_log_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client) || IsMe(client))
+		return 1;
+
+	return 0;
+}
+
+/** Outgoing filter for this message tag */
+int json_log_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target) || (target->local && IsOper(target) && HasCapabilityFast(target, CAP_JSON_LOG)))
+		return 1;
+	return 0;
+}
diff --git a/src/modules/jumpserver.c b/src/modules/jumpserver.c
@@ -25,12 +25,9 @@ ModuleHeader MOD_HEADER
 	"1.1",
 	"/jumpserver command",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
-/* Defines */
-#define MSG_JUMPSERVER 	"JUMPSERVER"
-
 /* Forward declarations */
 CMD_FUNC(cmd_jumpserver);
 int jumpserver_preconnect(Client *);
@@ -43,8 +40,8 @@ struct JSS
 	char *reason;
 	char *server;
 	int port;
- 	char *ssl_server;
-	int ssl_port;
+	char *tls_server;
+	int tls_port;
 };
 
 JSS *jss=NULL; /**< JumpServer Status. NULL=disabled. */
@@ -53,7 +50,7 @@ MOD_INIT()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	LoadPersistentPointer(modinfo, jss, jumpserver_free_jss);
-	CommandAdd(modinfo->handle, MSG_JUMPSERVER, cmd_jumpserver, 3, CMD_USER);
+	CommandAdd(modinfo->handle, "JUMPSERVER", cmd_jumpserver, 3, CMD_USER);
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, jumpserver_preconnect);
 	return MOD_SUCCESS;
 }
@@ -71,8 +68,8 @@ MOD_UNLOAD()
 
 static void do_jumpserver_exit_client(Client *client)
 {
-	if (IsSecure(client) && jss->ssl_server)
-		sendnumeric(client, RPL_REDIR, jss->ssl_server, jss->ssl_port);
+	if (IsSecure(client) && jss->tls_server)
+		sendnumeric(client, RPL_REDIR, jss->tls_server, jss->tls_port);
 	else
 		sendnumeric(client, RPL_REDIR, jss->server, jss->port);
 	exit_client(client, NULL, jss->reason);
@@ -91,8 +88,9 @@ static void redirect_all_clients(void)
 			count++;
 		}
 	}
-	sendto_realops("JUMPSERVER: Redirected %d client%s",
-		count, count == 1 ? "" : "s"); /* Language fun... ;p */
+	unreal_log(ULOG_INFO, "jumpserver", "JUMPSERVER_REPORT", NULL,
+	           "[jumpserver] Redirected $num_clients client(s)",
+	           log_data_integer("num_clients", count));
 }
 
 int jumpserver_preconnect(Client *client)
@@ -111,7 +109,7 @@ void free_jss(void)
 	{
 		safe_free(jss->server);
 		safe_free(jss->reason);
-		safe_free(jss->ssl_server);
+		safe_free(jss->tls_server);
 		safe_free(jss);
 		jss = NULL;
 	}
@@ -124,8 +122,10 @@ void jumpserver_free_jss(ModData *m)
 
 CMD_FUNC(cmd_jumpserver)
 {
-	char *serv, *sslserv=NULL, *reason, *p;
+	char *serv, *tlsserv=NULL, *p;
+	const char *reason;
 	int all=0, port=6667, sslport=6697;
+	char request[BUFSIZE];
 	char logbuf[512];
 
 	if (!IsOper(client))
@@ -136,9 +136,9 @@ CMD_FUNC(cmd_jumpserver)
 
 	if ((parc < 2) || BadPtr(parv[1]))
 	{
-		if (jss && jss->ssl_server)
-			sendnotice(client, "JumpServer is \002ENABLED\002 to %s:%d (SSL/TLS: %s:%d) with reason '%s'",
-				jss->server, jss->port, jss->ssl_server, jss->ssl_port, jss->reason);
+		if (jss && jss->tls_server)
+			sendnotice(client, "JumpServer is \002ENABLED\002 to %s:%d (TLS: %s:%d) with reason '%s'",
+				jss->server, jss->port, jss->tls_server, jss->tls_port, jss->reason);
 		else
 		if (jss)
 			sendnotice(client, "JumpServer is \002ENABLED\002 to %s:%d with reason '%s'",
@@ -156,10 +156,8 @@ CMD_FUNC(cmd_jumpserver)
 			return;
 		}
 		free_jss();
-		snprintf(logbuf, sizeof(logbuf), "%s (%s@%s) turned JUMPSERVER OFF",
-			client->name, client->user->username, client->user->realhost);
-		sendto_realops("%s", logbuf);
-		ircd_log(LOG_ERROR, "%s", logbuf);
+		unreal_log(ULOG_INFO, "jumpserver", "JUMPSERVER_DISABLED", client,
+		           "[jumpserver] $client.details turned jumpserver OFF");
 		return;
 	}
 
@@ -167,7 +165,7 @@ CMD_FUNC(cmd_jumpserver)
 	{
 		/* Waah, pretty verbose usage info ;) */
 		sendnotice(client, "Use: /JUMPSERVER <server>[:port] <NEW|ALL> <reason>");
-		sendnotice(client, " Or: /JUMPSERVER <server>[:port]/<sslserver>[:port] <NEW|ALL> <reason>");
+		sendnotice(client, " Or: /JUMPSERVER <server>[:port]/<tlsserver>[:port] <NEW|ALL> <reason>");
 		sendnotice(client, "if 'NEW' is chosen then only new (incoming) connections will be redirected");
 		sendnotice(client, "if 'ALL' is chosen then all clients except opers will be redirected immediately (+incoming connections)");
 		sendnotice(client, "Example: /JUMPSERVER irc2.test.net NEW This server will be upgraded, please use irc2.test.net for now");
@@ -177,19 +175,16 @@ CMD_FUNC(cmd_jumpserver)
 		return;
 	}
 
-	/* Parsing code follows...
-	 * The parsing of the SSL stuff is still done even on non-SSL,
-	 * but it's simply not used/applied :).
-	 * Reason for this is to reduce non-SSL/SSL inconsistency issues.
-	 */
+	/* Parsing code follows... */
 
-	serv = parv[1];
+	strlcpy(request, parv[1], sizeof(request));
+	serv = request;
 	
 	p = strchr(serv, '/');
 	if (p)
 	{
 		*p = '\0';
-		sslserv = p+1;
+		tlsserv = p+1;
 	}
 	
 	p = strchr(serv, ':');
@@ -203,21 +198,21 @@ CMD_FUNC(cmd_jumpserver)
 			return;
 		}
 	}
-	if (sslserv)
+	if (tlsserv)
 	{
-		p = strchr(sslserv, ':');
+		p = strchr(tlsserv, ':');
 		if (p)
 		{
 			*p++ = '\0';
 			sslport = atoi(p);
 			if ((sslport < 1) || (sslport > 65535))
 			{
-				sendnotice(client, "Invalid SSL/TLS serverport specified (%d)", sslport);
+				sendnotice(client, "Invalid TLS serverport specified (%d)", sslport);
 				return;
 			}
 		}
-		if (!*sslserv)
-			sslserv = NULL;
+		if (!*tlsserv)
+			tlsserv = NULL;
 	}
 	if (!strcasecmp(parv[2], "new"))
 		all = 0;
@@ -239,27 +234,37 @@ CMD_FUNC(cmd_jumpserver)
 	/* Set it */
 	safe_strdup(jss->server, serv);
 	jss->port = port;
-	if (sslserv)
+	if (tlsserv)
 	{
-		safe_strdup(jss->ssl_server, sslserv);
-		jss->ssl_port = sslport;
+		safe_strdup(jss->tls_server, tlsserv);
+		jss->tls_port = sslport;
 	}
 	safe_strdup(jss->reason, reason);
 
 	/* Broadcast/log */
-	if (sslserv)
-		snprintf(logbuf, sizeof(logbuf), "%s (%s@%s) added JUMPSERVER redirect for %s to %s:%d [SSL/TLS: %s:%d] with reason '%s'",
-			client->name, client->user->username, client->user->realhost,
-			all ? "ALL CLIENTS" : "all new clients",
-			jss->server, jss->port, jss->ssl_server, jss->ssl_port, jss->reason);
-	else
-		snprintf(logbuf, sizeof(logbuf), "%s (%s@%s) added JUMPSERVER redirect for %s to %s:%d with reason '%s'",
-			client->name, client->user->username, client->user->realhost,
-			all ? "ALL CLIENTS" : "all new clients",
-			jss->server, jss->port, jss->reason);
-
-	sendto_realops("%s", logbuf);
-	ircd_log(LOG_ERROR, "%s", logbuf);
+	if (tlsserv)
+	{
+		unreal_log(ULOG_INFO, "jumpserver", "JUMPSERVER_ENABLED", client,
+		           "[jumpserver] $client.details turned jumpserver ON for $jumpserver_who "
+		           "to $jumpserver_server:$jumpserver_port "
+		           "[TLS: $jumpserver_tls_server:$jumpserver_tls_port] "
+		           "($reason)",
+		           log_data_string("jumpserver_who", all ? "ALL CLIENTS" : "all new clients"),
+		           log_data_string("jumpserver_server", jss->server),
+		           log_data_integer("jumpserver_port", jss->port),
+		           log_data_string("jumpserver_tls_server", jss->tls_server),
+		           log_data_integer("jumpserver_tls_port", jss->tls_port),
+		           log_data_string("reason", jss->reason));
+	} else {
+		unreal_log(ULOG_INFO, "jumpserver", "JUMPSERVER_ENABLED", client,
+		           "[jumpserver] $client.details turned jumpserver ON for $jumpserver_who "
+		           "to $jumpserver_server:$jumpserver_port "
+		           "($reason)",
+		           log_data_string("jumpserver_who", all ? "ALL CLIENTS" : "all new clients"),
+		           log_data_string("jumpserver_server", jss->server),
+		           log_data_integer("jumpserver_port", jss->port),
+		           log_data_string("reason", jss->reason));
+	}
 
 	if (all)
 		redirect_all_clients();
diff --git a/src/modules/kick.c b/src/modules/kick.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /kick",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
@@ -59,6 +59,16 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+void kick_operoverride_msg(Client *client, Channel *channel, Client *target, char *reason)
+{
+	unreal_log(ULOG_INFO, "operoverride", "OPEROVERRIDE_KICK", client,
+		   "OperOverride: $client.details kicked $target from $channel ($reason)",
+		   log_data_string("override_type", "kick"),
+		   log_data_string("reason", reason),
+		   log_data_client("target", target),
+		   log_data_channel("channel", channel));
+}
+
 /** Kick a user from a channel.
  * @param initial_mtags	Message tags associated with this KICK (can be NULL)
  * @param channel	The channel where the KICK should happen
@@ -84,37 +94,37 @@ void _kick_user(MessageTag *initial_mtags, Channel *channel, Client *client, Cli
 		new_message(client, NULL, &initial_mtags);
 	}
 
-	new_message_special(client, initial_mtags, &mtags, ":%s KICK %s %s", client->name, channel->chname, victim->name);
+	new_message_special(client, initial_mtags, &mtags, ":%s KICK %s %s", client->name, channel->name, victim->name);
 	/* The same message is actually sent at 5 places below (though max 4 at most) */
 
 	if (MyUser(client))
-		RunHook5(HOOKTYPE_LOCAL_KICK, client, victim, channel, mtags, comment);
+		RunHook(HOOKTYPE_LOCAL_KICK, client, victim, channel, mtags, comment);
 	else
-		RunHook5(HOOKTYPE_REMOTE_KICK, client, victim, channel, mtags, comment);
+		RunHook(HOOKTYPE_REMOTE_KICK, client, victim, channel, mtags, comment);
 
 	if (invisible_user_in_channel(victim, channel))
 	{
 		/* Send it only to chanops & victim */
 		sendto_channel(channel, client, victim,
-			       PREFIX_HALFOP|PREFIX_OP|PREFIX_OWNER|PREFIX_ADMIN, 0,
+			       "h", 0,
 			       SEND_LOCAL, mtags,
 			       ":%s KICK %s %s :%s",
-			       client->name, channel->chname, victim->name, comment);
+			       client->name, channel->name, victim->name, comment);
 
 		if (MyUser(victim))
 		{
 			sendto_prefix_one(victim, client, mtags, ":%s KICK %s %s :%s",
-				client->name, channel->chname, victim->name, comment);
+				client->name, channel->name, victim->name, comment);
 		}
 	} else {
 		/* NORMAL */
 		sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 			       ":%s KICK %s %s :%s",
-			       client->name, channel->chname, victim->name, comment);
+			       client->name, channel->name, victim->name, comment);
 	}
 
 	sendto_server(client, 0, 0, mtags, ":%s KICK %s %s :%s",
-	    client->id, channel->chname, victim->id, comment);
+	    client->id, channel->name, victim->id, comment);
 
 	free_message_tags(mtags);
 	if (initial_mtags_generated)
@@ -123,34 +133,44 @@ void _kick_user(MessageTag *initial_mtags, Channel *channel, Client *client, Cli
 		initial_mtags = NULL;
 	}
 
-	remove_user_from_channel(victim, channel);
+	if (MyUser(victim))
+	{
+		unreal_log(ULOG_INFO, "kick", "LOCAL_CLIENT_KICK", victim,
+		           "User $client kicked from $channel",
+		           log_data_channel("channel", channel));
+	} else {
+		unreal_log(ULOG_INFO, "kick", "REMOTE_CLIENT_KICK", victim,
+		           "User $client kicked from $channel",
+		           log_data_channel("channel", channel));
+	}
+
+	remove_user_from_channel(victim, channel, 1);
 }
 
 /*
 ** cmd_kick
-**	parv[1] = channel
-**	parv[2] = client to kick
+**	parv[1] = channel (single channel)
+**	parv[2] = client to kick (comma separated)
 **	parv[3] = kick comment
 */
 
-#ifdef PREFIX_AQ
-#define CHFL_ISOP (CHFL_CHANOWNER|CHFL_CHANADMIN|CHFL_CHANOP)
-#else
-#define CHFL_ISOP (CHFL_CHANOP)
-#endif
-
 CMD_FUNC(cmd_kick)
 {
-	Client *who;
+	Client *target;
 	Channel *channel;
 	int  chasing = 0;
-	char *comment, *name, *p = NULL, *user, *p2 = NULL, *badkick;
+	char *p = NULL, *user, *p2 = NULL, *badkick;
+	char comment[MAXKICKLEN+1];
 	Membership *lp;
 	Hook *h;
 	int ret;
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("KICK");
 	MessageTag *mtags;
+	char request[BUFSIZE];
+	char request_chans[BUFSIZE];
+	const char *client_member_modes = NULL;
+	const char *target_member_modes;
 
 	if (parc < 3 || *parv[1] == '\0')
 	{
@@ -158,216 +178,192 @@ CMD_FUNC(cmd_kick)
 		return;
 	}
 
-	comment = (BadPtr(parv[3])) ? client->name : parv[3];
+	if (BadPtr(parv[3]))
+		strlcpy(comment, client->name, sizeof(comment));
+	else
+		strlncpy(comment, parv[3], sizeof(comment), iConf.kick_length);
+
+	strlcpy(request_chans, parv[1], sizeof(request_chans));
+	p = strchr(request_chans, ',');
+	if (p)
+		*p = '\0';
+	channel = find_channel(request_chans);
+	if (!channel)
+	{
+		sendnumeric(client, ERR_NOSUCHCHANNEL, request_chans);
+		return;
+	}
 
-	if (!BadPtr(parv[3]) && (strlen(comment) > iConf.kick_length))
-		comment[iConf.kick_length] = '\0';
+	/* Store "client" access flags */
+	if (IsUser(client))
+		client_member_modes = get_channel_access(client, channel);
+	if (MyUser(client) && !IsULine(client) &&
+	    !op_can_override("channel:override:kick:no-ops",client,channel,NULL) &&
+	    !check_channel_access(client, channel, "hoaq"))
+	{
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return;
+	}
 
-	for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL)
+	strlcpy(request, parv[2], sizeof(request));
+	for (user = strtoken(&p2, request, ","); user; user = strtoken(&p2, NULL, ","))
 	{
-		long client_flags = 0;
-		channel = get_channel(client, name, !CREATE);
-		if (!channel)
+		if (MyUser(client) && (++ntargets > maxtargets))
 		{
-			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
-			continue;
+			sendnumeric(client, ERR_TOOMANYTARGETS, user, maxtargets, "KICK");
+			break;
 		}
-		/* Store "client" access flags */
-		if (IsUser(client))
-			client_flags = get_access(client, channel);
-		if (MyUser(client) && !IsULine(client) && !op_can_override("channel:override:kick:no-ops",client,channel,NULL)
-		    && !(client_flags & CHFL_ISOP) && !(client_flags & CHFL_HALFOP))
+
+		if (!(target = find_chasing(client, user, &chasing)))
+			continue;	/* No such user left! */
+
+		if (!target->user)
+			continue; /* non-user */
+
+		lp = find_membership_link(target->user->channel, channel);
+		if (!lp)
 		{
-			sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
+			if (MyUser(client))
+				sendnumeric(client, ERR_USERNOTINCHANNEL, user, request_chans);
 			continue;
 		}
 
-		for (; (user = strtoken(&p2, parv[2], ",")); parv[2] = NULL)
-		{
-			long who_flags;
+		if (IsULine(client) || IsServer(client))
+			goto attack;
 
-			if (MyUser(client) && (++ntargets > maxtargets))
-			{
-				sendnumeric(client, ERR_TOOMANYTARGETS, user, maxtargets, "KICK");
-				break;
-			}
+		/* Note for coders regarding oper override:
+		 * always let a remote kick (=from a user on another server) through or
+		 * else we will get desynced. In short this means all the denying should
+		 * always contain a && MyUser(client) and at the end
+		 * a remote kick should always be allowed (pass through). -- Syzop
+		 */
 
-			if (!(who = find_chasing(client, user, &chasing)))
-				continue;	/* No such user left! */
+		/* Store "target" access flags */
+		target_member_modes = get_channel_access(target, channel);
 
-			if (!who->user)
-				continue; /* non-user */
+		badkick = NULL;
+		ret = EX_ALLOW;
+		for (h = Hooks[HOOKTYPE_CAN_KICK]; h; h = h->next) {
+			int n = (*(h->func.intfunc))(client, target, channel, comment, client_member_modes, target_member_modes, &badkick);
 
-			lp = find_membership_link(who->user->channel, channel);
-			if (!lp)
+			if (n == EX_DENY)
+				ret = n;
+			else if (n == EX_ALWAYS_DENY)
 			{
-				if (MyUser(client))
-					sendnumeric(client, ERR_USERNOTINCHANNEL, user, name);
-				continue;
+				ret = n;
+				break;
 			}
+		}
 
-			if (IsULine(client) || IsServer(client))
-				goto attack;
+		if (ret == EX_ALWAYS_DENY)
+		{
+			if (MyUser(client) && badkick)
+				sendto_one(client, NULL, "%s", badkick); /* send error, if any */
 
-			/* Note for coders regarding oper override:
-			 * always let a remote kick (=from a user on another server) through or
-			 * else we will get desynced. In short this means all the denying should
-			 * always contain a && MyUser(client) and at the end
-			 * a remote kick should always be allowed (pass through). -- Syzop
-			 */
-
-			/* Store "who" access flags */
-			who_flags = get_access(who, channel);
-
-			badkick = NULL;
-			ret = EX_ALLOW;
-			for (h = Hooks[HOOKTYPE_CAN_KICK]; h; h = h->next) {
-				int n = (*(h->func.intfunc))(client, who, channel, comment, client_flags, who_flags, &badkick);
-
-				if (n == EX_DENY)
-					ret = n;
-				else if (n == EX_ALWAYS_DENY)
-				{
-					ret = n;
-					break;
-				}
-			}
+			if (MyUser(client))
+				continue; /* reject the kick (note: we never block remote kicks) */
+		}
 
-			if (ret == EX_ALWAYS_DENY)
+		if (ret == EX_DENY)
+		{
+			/* If set it means 'not allowed to kick'.. now check if (s)he can override that.. */
+			if (op_can_override("channel:override:kick:no-ops",client,channel,NULL))
 			{
+				kick_operoverride_msg(client, channel, target, comment);
+				goto attack; /* all other checks don't matter anymore (and could cause double msgs) */
+			} else {
+				/* Not an oper overriding */
 				if (MyUser(client) && badkick)
 					sendto_one(client, NULL, "%s", badkick); /* send error, if any */
 
-				if (MyUser(client))
-					continue; /* reject the kick (note: we never block remote kicks) */
+				continue; /* reject the kick */
 			}
+		}
 
-			if (ret == EX_DENY)
-			{
-				/* If set it means 'not allowed to kick'.. now check if (s)he can override that.. */
-				if (op_can_override("channel:override:kick:no-ops",client,channel,NULL))
-				{
-					sendto_snomask(SNO_EYES,
-						"*** OperOverride -- %s (%s@%s) KICK %s %s (%s)",
-						client->name, client->user->username, client->user->realhost,
-						channel->chname, who->name, comment);
-					ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) KICK %s %s (%s)",
-						client->name, client->user->username, client->user->realhost,
-						channel->chname, who->name, comment);
-					goto attack; /* all other checks don't matter anymore (and could cause double msgs) */
-				} else {
-					/* Not an oper overriding */
-					if (MyUser(client) && badkick)
-						sendto_one(client, NULL, "%s", badkick); /* send error, if any */
-
-					continue; /* reject the kick */
-				}
-			}
+		// FIXME: Most, maybe even all, of these must be moved to HOOKTYPE_CAN_KICK checks in the corresponding halfop/chanop/chanadmin/chanowner modules :)
+		// !!!! FIXME
 
-			/* we are neither +o nor +h, OR..
-			 * we are +h but victim is +o, OR...
-			 * we are +h and victim is +h
-			 */
-			if (op_can_override("channel:override:kick:no-ops",client,channel,NULL))
+		/* we are neither +o nor +h, OR..
+		 * we are +h but target is +o, OR...
+		 * we are +h and target is +h
+		 */
+		if (op_can_override("channel:override:kick:no-ops",client,channel,NULL))
+		{
+			if ((!check_channel_access_string(client_member_modes, "o") && !check_channel_access_string(client_member_modes, "h")) ||
+			    (check_channel_access_string(client_member_modes, "h") && check_channel_access_string(target_member_modes, "h")) ||
+			    (check_channel_access_string(client_member_modes, "h") && check_channel_access_string(target_member_modes, "o")))
 			{
-				if ((!(client_flags & CHFL_ISOP) && !(client_flags & CHFL_HALFOP)) ||
-				    ((client_flags & CHFL_HALFOP) && (who_flags & CHFL_ISOP)) ||
-				    ((client_flags & CHFL_HALFOP) && (who_flags & CHFL_HALFOP)))
-				{
-					sendto_snomask(SNO_EYES,
-					    "*** OperOverride -- %s (%s@%s) KICK %s %s (%s)",
-					    client->name, client->user->username, client->user->realhost,
-					    channel->chname, who->name, comment);
-
-					/* Logging Implementation added by XeRXeS */
-					ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) KICK %s %s (%s)",
-						client->name, client->user->username, client->user->realhost,
-						channel->chname, who->name, comment);
-
-					goto attack;
-				}	/* is_chan_op */
-
-			}
+				kick_operoverride_msg(client, channel, target, comment);
+				goto attack;
+			}	/* is_chan_op */
 
-			/* victim is +a or +q, we are not +q */
-			if ((who_flags & (CHFL_CHANOWNER|CHFL_CHANADMIN))
-				 && !(client_flags & CHFL_CHANOWNER)) {
-				if (client == who)
-					goto attack; /* kicking self == ok */
-				if (op_can_override("channel:override:kick:owner",client,channel,NULL)) /* (and f*ck local ops) */
-				{	/* IRCop kicking owner/prot */
-					sendto_snomask(SNO_EYES,
-					    "*** OperOverride -- %s (%s@%s) KICK %s %s (%s)",
-					    client->name, client->user->username, client->user->realhost,
-					    channel->chname, who->name, comment);
-
-					/* Logging Implementation added by XeRXeS */
-					ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) KICK %s %s (%s)",
-						client->name, client->user->username, client->user->realhost,
-						channel->chname, who->name, comment);
-
-					goto attack;
-				}
-				else if (!IsULine(client) && (who != client) && MyUser(client))
-				{
-					char errbuf[NICKLEN+25];
-					if (who_flags & CHFL_CHANOWNER)
-						ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel owner",
-							   who->name);
-					else
-						ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel admin",
-							   who->name);
-					sendnumeric(client, ERR_CANNOTDOCOMMAND, "KICK",
-						   errbuf);
-					goto deny;
-				}	/* chanadmin/chanowner */
-			}
+		}
 
-			/* victim is +o, we are +h [operoverride is already taken care of 2 blocks above] */
-			if ((who_flags & CHFL_ISOP) && (client_flags & CHFL_HALFOP)
-			    && !(client_flags & CHFL_ISOP) && !IsULine(client) && MyUser(client))
+		/* target is +a/+q, and we are not +q? */
+		if (check_channel_access_string(target_member_modes, "qa") && !check_channel_access_string(client_member_modes, "q"))
+		{
+			if (client == target)
+				goto attack; /* kicking self == ok */
+			if (op_can_override("channel:override:kick:owner",client,channel,NULL)) /* (and f*ck local ops) */
 			{
-				char errbuf[NICKLEN+30];
-				ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel operator", who->name);
-				sendnumeric(client, ERR_CANNOTDOCOMMAND, "KICK",
-					   errbuf);
-				goto deny;
+				/* IRCop kicking owner/prot */
+				kick_operoverride_msg(client, channel, target, comment);
+				goto attack;
 			}
-
-			/* victim is +h, we are +h [operoverride is already taken care of 3 blocks above] */
-			if ((who_flags & CHFL_HALFOP) && (client_flags & CHFL_HALFOP)
-			    && !(client_flags & CHFL_ISOP) && MyUser(client))
+			else if (!IsULine(client) && (target != client) && MyUser(client))
 			{
-				char errbuf[NICKLEN+15];
-				ircsnprintf(errbuf, sizeof(errbuf), "%s is a halfop", who->name);
-				sendnumeric(client, ERR_CANNOTDOCOMMAND, "KICK",
-					   errbuf);
+				char errbuf[NICKLEN+25];
+				if (check_channel_access_string(target_member_modes, "q"))
+					ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel owner", target->name);
+				else
+					ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel admin", target->name);
+				sendnumeric(client, ERR_CANNOTDOCOMMAND, "KICK", errbuf);
 				goto deny;
-			}	/* halfop */
-
-			/* allowed (either coz access granted or a remote kick), so attack! */
-			goto attack;
+			}
+		}
 
-		      deny:
-			continue;
+		/* target is +o, we are +h [operoverride is already taken care of 2 blocks above] */
+		if (check_channel_access_string(target_member_modes, "h") && check_channel_access_string(client_member_modes, "h")
+		    && !check_channel_access_string(client_member_modes, "o") && !IsULine(client) && MyUser(client))
+		{
+			char errbuf[NICKLEN+30];
+			ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel operator", target->name);
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "KICK",
+				   errbuf);
+			goto deny;
+		}
 
-		      attack:
-			if (MyConnect(client)) {
-				int breakit = 0;
-				Hook *h;
-				for (h = Hooks[HOOKTYPE_PRE_LOCAL_KICK]; h; h = h->next) {
-					if((*(h->func.intfunc))(client,who,channel,comment) > 0) {
-						breakit = 1;
-						break;
-					}
+		/* target is +h, we are +h [operoverride is already taken care of 3 blocks above] */
+		if (check_channel_access_string(target_member_modes, "o") && check_channel_access_string(client_member_modes, "h")
+		    && !check_channel_access_string(client_member_modes, "o") && MyUser(client))
+		{
+			char errbuf[NICKLEN+15];
+			ircsnprintf(errbuf, sizeof(errbuf), "%s is a halfop", target->name);
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "KICK",
+				   errbuf);
+			goto deny;
+		}	/* halfop */
+
+		/* allowed (either coz access granted or a remote kick), so attack! */
+		goto attack;
+
+	      deny:
+		continue;
+
+	      attack:
+		if (MyConnect(client)) {
+			int breakit = 0;
+			Hook *h;
+			for (h = Hooks[HOOKTYPE_PRE_LOCAL_KICK]; h; h = h->next) {
+				if ((*(h->func.intfunc))(client,target,channel,comment) > 0) {
+					breakit = 1;
+					break;
 				}
-				if (breakit)
-					continue;
 			}
+			if (breakit)
+				continue;
+		}
 
-			kick_user(recv_mtags, channel, client, who, comment);
-		} /* loop on parv[2] */
-		if (MyUser(client))
-			break;
-	} /* loop on parv[1] */
+		kick_user(recv_mtags, channel, client, target, comment);
+	}
 }
diff --git a/src/modules/kill.c b/src/modules/kill.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /kill",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -56,7 +56,9 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_kill)
 {
-	char *targetlist, *reason;
+	char targetlist[BUFSIZE];
+	char reason[BUFSIZE];
+	char *str;
 	char *nick, *save = NULL;
 	Client *target;
 	Hook *h;
@@ -69,20 +71,18 @@ CMD_FUNC(cmd_kill)
 		return;
 	}
 
-	targetlist = parv[1];
-	reason = parv[2];
-
 	if (!IsServer(client->direction) && !ValidatePermissionsForPath("kill:global",client,NULL,NULL,NULL) && !ValidatePermissionsForPath("kill:local",client,NULL,NULL,NULL))
 	{
 		sendnumeric(client, ERR_NOPRIVILEGES);
 		return;
 	}
 
-	if (strlen(reason) > iConf.quit_length)
-		reason[iConf.quit_length] = '\0';
-
 	if (MyUser(client))
-		targetlist = canonize(targetlist);
+		strlcpy(targetlist, canonize(parv[1]), sizeof(targetlist));
+	else
+		strlcpy(targetlist, parv[1], sizeof(targetlist));
+
+	strlncpy(reason, parv[2], sizeof(reason), iConf.quit_length);
 
 	for (nick = strtoken(&save, targetlist, ","); nick; nick = strtoken(&save, NULL, ","))
 	{
@@ -94,7 +94,7 @@ CMD_FUNC(cmd_kill)
 			break;
 		}
 
-		target = find_person(nick, NULL);
+		target = find_user(nick, NULL);
 
 		/* If a local user issued the /KILL then we will "chase" the user.
 		 * In other words: we'll check the history for recently changed nicks.
@@ -138,16 +138,10 @@ CMD_FUNC(cmd_kill)
 
 		/* From here on, the kill is probably going to be successful. */
 
-		sendto_snomask(SNO_KILLS,
-			"*** Received KILL message for %s (%s@%s) from %s: %s",
-			target->name, target->user->username, GetHost(target),
-			client->name,
-			reason);
-
-		ircd_log(LOG_KILL, "KILL (%s) by %s (%s)",
-			make_nick_user_host(target->name, target->user->username, GetHost(target)),
-			client->name,
-			reason);
+		unreal_log(ULOG_INFO, "kill", "KILL_COMMAND", client,
+		           "Client killed: $target.details [by: $client] ($reason)",
+		           log_data_client("target", target),
+		           log_data_string("reason", reason));
 
 		new_message(client, recv_mtags, &mtags);
 
@@ -177,7 +171,7 @@ CMD_FUNC(cmd_kill)
 		}
 
 		if (MyUser(client))
-			RunHook3(HOOKTYPE_LOCAL_KILL, client, target, reason);
+			RunHook(HOOKTYPE_LOCAL_KILL, client, target, reason);
 
 		ircsnprintf(buf2, sizeof(buf2), "Killed by %s (%s)", client->name, reason);
 		exit_client(target, mtags, buf2);
diff --git a/src/modules/knock.c b/src/modules/knock.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /knock", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -78,7 +78,7 @@ CMD_FUNC(cmd_knock)
 	Hook *h;
 	int i = 0;
 	MessageTag *mtags = NULL;
-	char *reason;
+	const char *reason;
 
 	if (IsServer(client))
 		return;
@@ -97,7 +97,7 @@ CMD_FUNC(cmd_knock)
 		return;
 	}
 
-	if (!(channel = find_channel(parv[1], NULL)))
+	if (!(channel = find_channel(parv[1])))
 	{
 		sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "Channel does not exist!");
 		return;
@@ -106,25 +106,25 @@ CMD_FUNC(cmd_knock)
 	/* IsMember bugfix by codemastr */
 	if (IsMember(client, channel) == 1)
 	{
-		sendnumeric(client, ERR_CANNOTKNOCK, channel->chname, "You're already there!");
+		sendnumeric(client, ERR_CANNOTKNOCK, channel->name, "You're already there!");
 		return;
 	}
 
-	if (!(channel->mode.mode & MODE_INVITEONLY))
+	if (!has_channel_mode(channel, 'i'))
 	{
-		sendnumeric(client, ERR_CANNOTKNOCK, channel->chname, "Channel is not invite only!");
+		sendnumeric(client, ERR_CANNOTKNOCK, channel->name, "Channel is not invite only!");
 		return;
 	}
 
 	if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
 	{
-		sendnumeric(client, ERR_CANNOTKNOCK, channel->chname, "You're banned!");
+		sendnumeric(client, ERR_CANNOTKNOCK, channel->name, "You're banned!");
 		return;
 	}
 
 	for (h = Hooks[HOOKTYPE_PRE_KNOCK]; h; h = h->next)
 	{
-		i = (*(h->func.intfunc))(client,channel);
+		i = (*(h->func.intfunc))(client, channel, &reason);
 		if (i == HOOK_DENY || i == HOOK_ALLOW)
 			break;
 	}
@@ -142,19 +142,19 @@ CMD_FUNC(cmd_knock)
 
 	new_message(&me, NULL, &mtags);
 
-	sendto_channel(channel, &me, NULL, PREFIX_OP|PREFIX_ADMIN|PREFIX_OWNER,
+	sendto_channel(channel, &me, NULL, "o",
 	               0, SEND_LOCAL, mtags,
 	               ":%s NOTICE @%s :[Knock] by %s!%s@%s (%s)",
-	               me.name, channel->chname,
+	               me.name, channel->name,
 	               client->name, client->user->username, GetHost(client),
 	               reason);
 
-	sendto_server(client, 0, 0, mtags, ":%s KNOCK %s :%s", client->id, channel->chname, reason);
+	sendto_server(client, 0, 0, mtags, ":%s KNOCK %s :%s", client->id, channel->name, reason);
 
 	if (MyUser(client))
-		sendnotice(client, "Knocked on %s", channel->chname);
+		sendnotice(client, "Knocked on %s", channel->name);
 
-        RunHook4(HOOKTYPE_KNOCK, client, channel, mtags, parv[2]);
+        RunHook(HOOKTYPE_KNOCK, client, channel, mtags, parv[2]);
 
 	free_message_tags(mtags);
 }
diff --git a/src/modules/labeled-response.c b/src/modules/labeled-response.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Labeled response CAP",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Data structures */
@@ -43,8 +43,8 @@ struct LabeledResponseContext {
 };
 
 /* Forward declarations */
-int lr_pre_command(Client *from, MessageTag *mtags, char *buf);
-int lr_post_command(Client *from, MessageTag *mtags, char *buf);
+int lr_pre_command(Client *from, MessageTag *mtags, const char *buf);
+int lr_post_command(Client *from, MessageTag *mtags, const char *buf);
 int lr_close_connection(Client *client);
 int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len);
 void *_labeled_response_save_context(void);
@@ -61,7 +61,7 @@ static long CAP_LABELED_RESPONSE = 0L;
 
 static char packet[8192];
 
-int labeled_response_mtag_is_ok(Client *client, char *name, char *value);
+int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value);
 
 MOD_TEST()
 {
@@ -111,7 +111,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int lr_pre_command(Client *from, MessageTag *mtags, char *buf)
+int lr_pre_command(Client *from, MessageTag *mtags, const char *buf)
 {
 	memset(&currentcmd, 0, sizeof(currentcmd));
 	labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0;
@@ -156,7 +156,7 @@ char *gen_start_batch(void)
 	return buf;
 }
 
-int lr_post_command(Client *from, MessageTag *mtags, char *buf)
+int lr_post_command(Client *from, MessageTag *mtags, const char *buf)
 {
 	/* ** IMPORTANT **
 	 * Take care NOT to return here, use 'goto done' instead
@@ -336,7 +336,7 @@ int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *le
 /** This function verifies if the client sending the
  * tag is permitted to do so and uses a permitted syntax.
  */
-int labeled_response_mtag_is_ok(Client *client, char *name, char *value)
+int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (BadPtr(value))
 		return 0;
diff --git a/src/modules/lag.c b/src/modules/lag.c
@@ -31,7 +31,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /lag", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -78,7 +78,7 @@ CMD_FUNC(cmd_lag)
 		return;
 	}
 
-	if (hunt_server(client, recv_mtags, ":%s LAG :%s", 1, parc, parv) == HUNTED_NOSUCH)
+	if (hunt_server(client, recv_mtags, "LAG", 1, parc, parv) == HUNTED_NOSUCH)
 		return;
 
 	sendnotice(client, "Lag reply -- %s %s %lld", me.name, parv[1], (long long)TStime());
diff --git a/src/modules/link-security.c b/src/modules/link-security.c
@@ -29,14 +29,14 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Link Security CAP",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Forward declarations */
-char *link_security_md_serialize(ModData *m);
-void link_security_md_unserialize(char *str, ModData *m);
+const char *link_security_md_serialize(ModData *m);
+void link_security_md_unserialize(const char *str, ModData *m);
 EVENT(checklinksec);
-char *link_security_capability_parameter(Client *client);
+const char *link_security_capability_parameter(Client *client);
 CMD_FUNC(cmd_linksecurity);
 
 /* Global variables */
@@ -59,6 +59,7 @@ MOD_INIT()
 	mreq.serialize = link_security_md_serialize;
 	mreq.unserialize = link_security_md_unserialize;
 	mreq.sync = 1;
+	mreq.self_write = 1;
 	link_security_md = ModDataAdd(modinfo->handle, mreq);
 	if (!link_security_md)
 	{
@@ -97,7 +98,7 @@ MOD_UNLOAD()
  */
 #define LNKSECMAGIC 100
 
-char *link_security_md_serialize(ModData *m)
+const char *link_security_md_serialize(ModData *m)
 {
 	static char buf[32];
 	if (m->i == 0)
@@ -106,7 +107,7 @@ char *link_security_md_serialize(ModData *m)
 	return buf;
 }
 
-void link_security_md_unserialize(char *str, ModData *m)
+void link_security_md_unserialize(const char *str, ModData *m)
 {
 	m->i = atoi(str) + LNKSECMAGIC;
 }
@@ -118,9 +119,9 @@ int certificate_verification_active(Client *client)
 {
 	ConfigItem_link *conf;
 	
-	if (!client->serv || !client->serv->conf)
+	if (!client->server || !client->server->conf)
 		return 0; /* wtf? */
-	conf = client->serv->conf;
+	conf = client->server->conf;
 	
 	if (conf->verify_certificate)
 		return 1; /* yes, verify-certificate is 'yes' */
@@ -140,7 +141,7 @@ int certificate_verification_active(Client *client)
 
 /** Calculate our (local) link-security level.
  * This means stepping through the list of directly linked
- * servers and determining if they are linked via SSL and
+ * servers and determining if they are linked via TLS and
  * certificate verification is active.
  * @returns value from 0 to 2.
  */
@@ -154,7 +155,7 @@ int our_link_security(void)
 		if (IsLocalhost(client))
 			continue; /* server connected via localhost */
 		if (!IsSecure(client))
-			return 0; /* Any non-SSL server (which is not localhost) results in level 0. */
+			return 0; /* Any non-TLS server (which is not localhost) results in level 0. */
 		if (!certificate_verification_active(client))
 			level = 1; /* downgrade to level 1 */
 	}
@@ -177,7 +178,6 @@ EVENT(checklinksec)
 	int last_local_link_security = local_link_security;
 	int last_global_link_security = global_link_security;
 	Client *client;
-	char *s;
 	int v;
 	int warning_sent = 0;
 	
@@ -193,7 +193,7 @@ EVENT(checklinksec)
 	global_link_security = 2;
 	list_for_each_entry(client, &global_server_list, client_node)
 	{
-		s = moddata_client_get(client, "link-security");
+		const char *s = moddata_client_get(client, "link-security");
 		if (s)
 		{
 			v = atoi(s);
@@ -209,15 +209,19 @@ EVENT(checklinksec)
 	
 	if (local_link_security < last_local_link_security)
 	{
-		sendto_realops("Local link-security downgraded from level %d to %d due to just linked in server(s)",
-			last_local_link_security, local_link_security);
+		unreal_log(ULOG_INFO, "link-security", "LOCAL_LINK_SECURITY_DOWNGRADED", NULL,
+		           "Local link-security downgraded from level $previous_level to $new_level due to just linked in server(s)",
+		           log_data_integer("previous_level", last_local_link_security),
+		           log_data_integer("new_level", local_link_security));
 		warning_sent = 1;
 	}
 	
 	if (global_link_security < last_global_link_security)
 	{
-		sendto_realops("Global link-security downgraded from level %d to %d due to just linked in server(s)",
-			last_global_link_security, global_link_security);
+		unreal_log(ULOG_INFO, "link-security", "GLOBAL_LINK_SECURITY_DOWNGRADED", NULL,
+		           "Global link-security downgraded from level $previous_level to $new_level due to just linked in server(s)",
+		           log_data_integer("previous_level", last_global_link_security),
+		           log_data_integer("new_level", global_link_security));
 		warning_sent = 1;
 	}
 	
@@ -225,12 +229,14 @@ EVENT(checklinksec)
 
 	if (warning_sent)
 	{
-		sendto_realops("Effective (network-wide) link-security is: level %d", effective_link_security);
-		sendto_realops("More information about this can be found at https://www.unrealircd.org/docs/Link_security");
+		unreal_log(ULOG_INFO, "link-security", "EFFECTIVE_LINK_SECURITY_REPORT", NULL,
+		           "Effective (network-wide) link-security is now: level $effective_link_security\n"
+		           "More information about this can be found at https://www.unrealircd.org/docs/Link_security",
+		           log_data_integer("effective_link_security", effective_link_security));
 	}
 }
 
-char *link_security_capability_parameter(Client *client)
+const char *link_security_capability_parameter(Client *client)
 {
 	return valtostr(effective_link_security);
 }
@@ -239,8 +245,6 @@ char *link_security_capability_parameter(Client *client)
 CMD_FUNC(cmd_linksecurity)
 {
 	Client *acptr;
-	char *s;
-	int v;
 	
 	if (!IsOper(client))
 	{
@@ -253,15 +257,11 @@ CMD_FUNC(cmd_linksecurity)
 	sendtxtnumeric(client, "= By server =");
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
-		v = -1;
-		s = moddata_client_get(acptr, "link-security");
+		const char *s = moddata_client_get(acptr, "link-security");
 		if (s)
-		{
-			v = atoi(s);
-			sendtxtnumeric(client, "%s: level %d", acptr->name, v);
-		} else {
+			sendtxtnumeric(client, "%s: level %d", acptr->name, atoi(s));
+		else
 			sendtxtnumeric(client, "%s: level UNKNOWN", acptr->name);
-		}
 	}
 	
 	sendtxtnumeric(client, "-");
@@ -271,9 +271,9 @@ CMD_FUNC(cmd_linksecurity)
 	sendtxtnumeric(client, "= Legend =");
 	sendtxtnumeric(client, "Higher level means better link security");
 	sendtxtnumeric(client, "Level UNKNOWN: Not an UnrealIRCd server (eg: services) or an old version (<4.0.14)");
-	sendtxtnumeric(client, "Level 0: One or more servers linked insecurely (not using SSL/TLS)");
-	sendtxtnumeric(client, "Level 1: Servers are linked with SSL/TLS but at least one of them is not verifying certificates");
-	sendtxtnumeric(client, "Level 2: Servers linked with SSL/TLS and certificates are properly verified");
+	sendtxtnumeric(client, "Level 0: One or more servers linked insecurely (not using TLS)");
+	sendtxtnumeric(client, "Level 1: Servers are linked with TLS but at least one of them is not verifying certificates");
+	sendtxtnumeric(client, "Level 2: Servers linked with TLS and certificates are properly verified");
 	sendtxtnumeric(client, "-");
 	sendtxtnumeric(client, "= More information =");
 	sendtxtnumeric(client, "To understand more about link security and how to improve your level");
diff --git a/src/modules/links.c b/src/modules/links.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /links", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -69,7 +69,7 @@ CMD_FUNC(cmd_links)
 			sendnumeric(client, RPL_LINKS, acptr->name, me.name,
 			    1, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
 		else
-			sendnumeric(client, RPL_LINKS, acptr->name, acptr->serv->up,
+			sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink ? acptr->uplink->name : me.name,
 			    acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
 	}
 
diff --git a/src/modules/list.c b/src/modules/list.c
@@ -33,7 +33,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /list", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 typedef struct ChannelListOptions ChannelListOptions;
@@ -54,6 +54,7 @@ struct ChannelListOptions {
 
 /* Global variables */
 ModDataInfo *list_md = NULL;
+char modebuf[BUFSIZE], parabuf[BUFSIZE];
 
 /* Macros */
 #define CHANNELLISTOPTIONS(x)       ((ChannelListOptions *)moddata_local_client(x, list_md).ptr)
@@ -125,6 +126,7 @@ CMD_FUNC(cmd_list)
 	NameList *nolist = NULL;
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("LIST");
+	char request[BUFSIZE];
 
 	static char *usage[] = {
 		"   Usage: /LIST <options>",
@@ -187,8 +189,8 @@ CMD_FUNC(cmd_list)
 	usermin = 0;		/* Minimum of 0 */
 	usermax = -1;		/* No maximum */
 
-	for (name = strtoken(&p, parv[1], ","); name && !error;
-	    name = strtoken(&p, NULL, ","))
+	strlcpy(request, parv[1], sizeof(request));
+	for (name = strtoken(&p, request, ","); name && !error; name = strtoken(&p, NULL, ","))
 	{
 		if (MyUser(client) && (++ntargets > maxtargets))
 		{
@@ -269,21 +271,17 @@ CMD_FUNC(cmd_list)
 			  }
 			  else	/* Just a normal channel */
 			  {
-				  channel = find_channel(name, NULL);
+				  channel = find_channel(name);
 				  if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))) {
-#ifdef LIST_SHOW_MODES
 					modebuf[0] = '[';
-					channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel);
+					channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
 					if (modebuf[2] == '\0')
 						modebuf[0] = '\0';
 					else
 						strlcat(modebuf, "]", sizeof modebuf);
-#endif
 					  sendnumeric(client, RPL_LIST,
 					      name, channel->users,
-#ifdef LIST_SHOW_MODES
 					      modebuf,
-#endif
 					      (channel->topic ? channel->topic :
 					      ""));
 }
@@ -343,13 +341,11 @@ int send_list(Client *client)
 		ConfigItem_offchans *x;
 		for (x = conf_offchans; x; x = x->next)
 		{
-			if (find_channel(x->chname, NULL))
+			if (find_channel(x->name))
 				continue; /* exists, >0 users.. will be sent later */
-			sendnumeric(client, RPL_LIST, x->chname,
+			sendnumeric(client, RPL_LIST, x->name,
 			    0,
-#ifdef LIST_SHOW_MODES
 			    "",
-#endif					    
 			    x->topic ? x->topic : "");
 		}
 	}
@@ -366,11 +362,11 @@ int send_list(Client *client)
 					continue;
 
 				/* set::hide-list { deny-channel } */
-				if (!IsOper(client) && iConf.hide_list && find_channel_allowed(client, channel->chname))
+				if (!IsOper(client) && iConf.hide_list && find_channel_allowed(client, channel->name))
 					continue;
 
 				/* Similarly, hide unjoinable channels for non-ircops since it would be confusing */
-				if (!IsOper(client) && !valid_channelname(channel->chname))
+				if (!IsOper(client) && !valid_channelname(channel->name))
 					continue;
 
 				/* Much more readable like this -- codemastr */
@@ -394,39 +390,33 @@ int send_list(Client *client)
 						continue;
 
 					/* Must not be on nolist (if it exists) */
-					if (lopt->nolist && find_name_list_match(lopt->nolist, channel->chname))
+					if (lopt->nolist && find_name_list_match(lopt->nolist, channel->name))
 						continue;
 
 					/* Must be on yeslist (if it exists) */
-					if (lopt->yeslist && !find_name_list_match(lopt->yeslist, channel->chname))
+					if (lopt->yeslist && !find_name_list_match(lopt->yeslist, channel->name))
 						continue;
 				}
-#ifdef LIST_SHOW_MODES
 				modebuf[0] = '[';
-				channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel);
+				channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
 				if (modebuf[2] == '\0')
 					modebuf[0] = '\0';
 				else
 					strlcat(modebuf, "]", sizeof modebuf);
-#endif
 				if (!ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
 					sendnumeric(client, RPL_LIST,
 					    ShowChannel(client,
-					    channel) ? channel->chname :
+					    channel) ? channel->name :
 					    "*", channel->users,
-#ifdef LIST_SHOW_MODES
 					    ShowChannel(client, channel) ?
 					    modebuf : "",
-#endif
 					    ShowChannel(client,
 					    channel) ? (channel->topic ?
 					    channel->topic : "") : "");
 				else
-					sendnumeric(client, RPL_LIST, channel->chname,
+					sendnumeric(client, RPL_LIST, channel->name,
 					    channel->users,
-#ifdef LIST_SHOW_MODES
 					    modebuf,
-#endif					    
 					    (channel->topic ? channel->topic : ""));
 				numsend--;
 			}
diff --git a/src/modules/locops.c b/src/modules/locops.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /locops", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -58,9 +58,7 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_locops)
 {
-	char *message;
-
-	message = parc > 1 ? parv[1] : NULL;
+	const char *message = parc > 1 ? parv[1] : NULL;
 
 	if (BadPtr(message))
 	{
diff --git a/src/modules/lusers.c b/src/modules/lusers.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /lusers", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -59,7 +59,7 @@ CMD_FUNC(cmd_lusers)
 {
 char flatmap;
 
-	if (hunt_server(client, recv_mtags, ":%s LUSERS :%s", 1, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "LUSERS", 1, parc, parv) != HUNTED_ISME)
 		return;
 
 	flatmap = (FLAT_MAP && !ValidatePermissionsForPath("server:info:lusers",client,NULL,NULL,NULL)) ? 1 : 0;
@@ -83,12 +83,14 @@ char flatmap;
 	sendnumeric(client, RPL_LUSERME, irccounts.me_clients, flatmap ? 0 : irccounts.me_servers);
 	sendnumeric(client, RPL_LOCALUSERS, irccounts.me_clients, irccounts.me_max, irccounts.me_clients, irccounts.me_max);
 	sendnumeric(client, RPL_GLOBALUSERS, irccounts.clients, irccounts.global_max, irccounts.clients, irccounts.global_max);
-	if ((irccounts.me_clients + irccounts.me_servers) > max_connection_count)
+	if (irccounts.me_clients > max_connection_count)
 	{
-		max_connection_count =
-		    irccounts.me_clients + irccounts.me_servers;
+		max_connection_count = irccounts.me_clients;
 		if (max_connection_count % 10 == 0)	/* only send on even tens */
-			sendto_ops("New record on this server: %d connections (%d clients)",
-			    max_connection_count, irccounts.me_clients);
+		{
+			unreal_log(ULOG_INFO, "client", "NEW_USER_RECORD", NULL,
+			           "New record on this server: $num_users connections",
+			           log_data_integer("num_users", max_connection_count));
+		}
 	}
 }
diff --git a/src/modules/map.c b/src/modules/map.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /map", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -71,7 +71,7 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 	else
 	{
 		sendnumeric(client, RPL_MAP, prompt,
-		            length, server->name, server->serv->users, IsOper(client) ? server->id : "");
+		            length, server->name, server->server->users, IsOper(client) ? server->id : "");
 		cnt = 0;
 	}
 
@@ -88,7 +88,7 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
-		if (acptr->srvptr != server ||
+		if (acptr->uplink != server ||
  		    (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)))
 			continue;
 		SetMap(acptr);
@@ -99,7 +99,7 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 	{
 		if (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
 			continue;
-		if (acptr->srvptr != server)
+		if (acptr->uplink != server)
 			continue;
 		if (!IsMap(acptr))
 			continue;
@@ -120,7 +120,7 @@ void dump_flat_map(Client *client, Client *server, int length)
 
 	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
 
-	sendnumeric(client, RPL_MAP, "", length, server->name, server->serv->users, "");
+	sendnumeric(client, RPL_MAP, "", length, server->name, server->server->users, "");
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
@@ -136,7 +136,7 @@ void dump_flat_map(Client *client, Client *server, int length)
 			continue;
 		if (--cnt == 0)
 			*buf = '`';
-		sendnumeric(client, RPL_MAP, buf, length-2, acptr->name, acptr->serv->users, "");
+		sendnumeric(client, RPL_MAP, buf, length-2, acptr->name, acptr->server->users, "");
 	}
 }
 
diff --git a/src/modules/md.c b/src/modules/md.c
@@ -14,7 +14,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /MD (S2S only)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 CMD_FUNC(cmd_md);
@@ -23,11 +23,13 @@ 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);
 void _broadcast_md_globalvar(ModDataInfo *mdi, ModData *md);
-void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, char *varname, char *value);
-void _broadcast_md_channel_cmd(Client *except, Client *sender, Channel *channel, char *varname, char *value);
-void _broadcast_md_member_cmd(Client *except, Client *sender, Channel *channel, Client *client, char *varname, char *value);
-void _broadcast_md_membership_cmd(Client *except, Client *sender, Client *client, Channel *channel, char *varname, char *value);
-void _broadcast_md_globalvar_cmd(Client *except, Client *sender, char *varname, char *value);
+void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, const char *varname, const char *value);
+void _broadcast_md_channel_cmd(Client *except, Client *sender, Channel *channel, const char *varname, const char *value);
+void _broadcast_md_member_cmd(Client *except, Client *sender, Channel *channel, Client *client, const char *varname, const char *value);
+void _broadcast_md_membership_cmd(Client *except, Client *sender, Client *client, Channel *channel, const char *varname, const char *value);
+void _broadcast_md_globalvar_cmd(Client *except, Client *sender, const char *varname, const char *value);
+void _moddata_add_s2s_mtags(Client *client, MessageTag **mtags);
+void _moddata_extract_s2s_mtags(Client *client, MessageTag *mtags);
 void _send_moddata_client(Client *srv, Client *client);
 void _send_moddata_channel(Client *srv, Channel *channel);
 void _send_moddata_members(Client *srv);
@@ -48,6 +50,8 @@ MOD_TEST()
 	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_MEMBER_CMD, _broadcast_md_member_cmd);
 	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_MEMBERSHIP_CMD, _broadcast_md_membership_cmd);
 	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_GLOBALVAR_CMD, _broadcast_md_globalvar_cmd);
+	EfunctionAddVoid(modinfo->handle, EFUNC_MODDATA_ADD_S2S_MTAGS, _moddata_add_s2s_mtags);
+	EfunctionAddVoid(modinfo->handle, EFUNC_MODDATA_EXTRACT_S2S_MTAGS, _moddata_extract_s2s_mtags);
 	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_MODDATA_CLIENT, _send_moddata_client);
 	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_MODDATA_CHANNEL, _send_moddata_channel);
 	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_MODDATA_MEMBERS, _send_moddata_members);
@@ -72,6 +76,25 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+/** Check if client may write to this MD object */
+int md_access_check(Client *client, ModDataInfo *md, Client *target)
+{
+	if ((client == target) && md->self_write)
+		return 1;
+
+	if (MyConnect(target) && !md->remote_write)
+	{
+		unreal_log(ULOG_WARNING, "md", "REMOTE_MD_WRITE_DENIED", client,
+		           "Remote server $client tried to write moddata $moddata_name "
+		           "of a client from ours ($target.name) -- attempt BLOCKED",
+		           log_data_string("moddata_name", md->name),
+		           log_data_client("target", target));
+		return 0;
+	}
+
+	return 1;
+}
+
 /** Set ModData command.
  *  Syntax: MD <type> <object name> <variable name> <value>
  * Example: MD client Syzop sslfp 123456789
@@ -88,7 +111,7 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_md)
 {
-	char *type, *objname, *varname, *value;
+	const char *type, *objname, *varname, *value;
 	ModDataInfo *md;
 
 	if (!IsServer(client) || (parc < 4) || BadPtr(parv[3]))
@@ -105,6 +128,10 @@ CMD_FUNC(cmd_md)
 		md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
 		if (!md || !md->unserialize || !target)
 			return;
+
+		if (!md_access_check(client, md, target))
+			return;
+
 		if (value)
 			md->unserialize(value, &moddata_client(target, md));
 		else
@@ -118,7 +145,7 @@ CMD_FUNC(cmd_md)
 	} else
 	if (!strcmp(type, "channel"))
 	{
-		Channel *channel = find_channel(objname, NULL);
+		Channel *channel = find_channel(objname);
 		md = findmoddata_byname(varname, MODDATATYPE_CHANNEL);
 		if (!md || !md->unserialize || !channel)
 			return;
@@ -146,11 +173,11 @@ CMD_FUNC(cmd_md)
 			return;
 		*p++ = '\0';
 
-		channel = find_channel(objname, NULL);
+		channel = find_channel(objname);
 		if (!channel)
 			return;
 
-		target = find_person(p, NULL);
+		target = find_user(p, NULL);
 		if (!target)
 			return;
 
@@ -162,6 +189,9 @@ CMD_FUNC(cmd_md)
 		if (!md || !md->unserialize)
 			return;
 
+		if (!md_access_check(client, md, target))
+			return;
+
 		if (value)
 			md->unserialize(value, &moddata_member(m, md));
 		else
@@ -186,11 +216,11 @@ CMD_FUNC(cmd_md)
 			return;
 		*p++ = '\0';
 
-		target = find_person(objname, NULL);
+		target = find_user(objname, NULL);
 		if (!target)
 			return;
 
-		channel = find_channel(p, NULL);
+		channel = find_channel(p);
 		if (!channel)
 			return;
 
@@ -202,6 +232,9 @@ CMD_FUNC(cmd_md)
 		if (!md || !md->unserialize)
 			return;
 
+		if (!md_access_check(client, md, target))
+			return;
+
 		if (value)
 			md->unserialize(value, &moddata_membership(m, md));
 		else
@@ -232,7 +265,7 @@ CMD_FUNC(cmd_md)
 	}
 }
 
-void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, char *varname, char *value)
+void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, const char *varname, const char *value)
 {
 	if (value)
 	{
@@ -246,45 +279,45 @@ void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, ch
 	}
 }
 
-void _broadcast_md_channel_cmd(Client *except, Client *sender, Channel *channel, char *varname, char *value)
+void _broadcast_md_channel_cmd(Client *except, Client *sender, Channel *channel, const char *varname, const char *value)
 {
 	if (value)
 		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s :%s",
-			sender->id, "channel", channel->chname, varname, value);
+			sender->id, "channel", channel->name, varname, value);
 	else
 		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s",
-			sender->id, "channel", channel->chname, varname);
+			sender->id, "channel", channel->name, varname);
 }
 
-void _broadcast_md_member_cmd(Client *except, Client *sender, Channel *channel, Client *client, char *varname, char *value)
+void _broadcast_md_member_cmd(Client *except, Client *sender, Channel *channel, Client *client, const char *varname, const char *value)
 {
 	if (value)
 	{
 		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s :%s",
-			sender->id, "member", channel->chname, client->id, varname, value);
+			sender->id, "member", channel->name, client->id, varname, value);
 	}
 	else
 	{
 		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s",
-			sender->id, "member", channel->chname, client->id, varname);
+			sender->id, "member", channel->name, client->id, varname);
 	}
 }
 
-void _broadcast_md_membership_cmd(Client *except, Client *sender, Client *client, Channel *channel, char *varname, char *value)
+void _broadcast_md_membership_cmd(Client *except, Client *sender, Client *client, Channel *channel, const char *varname, const char *value)
 {
 	if (value)
 	{
 		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s :%s",
-			sender->id, "membership", client->id, channel->chname, varname, value);
+			sender->id, "membership", client->id, channel->name, varname, value);
 	}
 	else
 	{
 		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s",
-			sender->id, "membership", client->id, channel->chname, varname);
+			sender->id, "membership", client->id, channel->name, varname);
 	}
 }
 
-void _broadcast_md_globalvar_cmd(Client *except, Client *sender, char *varname, char *value)
+void _broadcast_md_globalvar_cmd(Client *except, Client *sender, const char *varname, const char *value)
 {
 	if (value)
 	{
@@ -306,35 +339,35 @@ void _broadcast_md_globalvar_cmd(Client *except, Client *sender, char *varname, 
  
 void _broadcast_md_client(ModDataInfo *mdi, Client *client, ModData *md)
 {
-	char *value = md ? mdi->serialize(md) : NULL;
+	const char *value = md ? mdi->serialize(md) : NULL;
 
 	broadcast_md_client_cmd(NULL, &me, client, mdi->name, value);
 }
 
 void _broadcast_md_channel(ModDataInfo *mdi, Channel *channel, ModData *md)
 {
-	char *value = md ? mdi->serialize(md) : NULL;
+	const char *value = md ? mdi->serialize(md) : NULL;
 
 	broadcast_md_channel_cmd(NULL, &me, channel, mdi->name, value);
 }
 
 void _broadcast_md_member(ModDataInfo *mdi, Channel *channel, Member *m, ModData *md)
 {
-	char *value = md ? mdi->serialize(md) : NULL;
+	const char *value = md ? mdi->serialize(md) : NULL;
 
 	broadcast_md_member_cmd(NULL, &me, channel, m->client, mdi->name, value);
 }
 
 void _broadcast_md_membership(ModDataInfo *mdi, Client *client, Membership *m, ModData *md)
 {
-	char *value = md ? mdi->serialize(md) : NULL;
+	const char *value = md ? mdi->serialize(md) : NULL;
 
 	broadcast_md_membership_cmd(NULL, &me, client, m->channel, mdi->name, value);
 }
 
 void _broadcast_md_globalvar(ModDataInfo *mdi, ModData *md)
 {
-	char *value = md ? mdi->serialize(md) : NULL;
+	const char *value = md ? mdi->serialize(md) : NULL;
 
 	broadcast_md_globalvar_cmd(NULL, &me, mdi->name, value);
 }
@@ -348,7 +381,7 @@ void _send_moddata_client(Client *srv, Client *client)
 	{
 		if ((mdi->type == MODDATATYPE_CLIENT) && mdi->sync && mdi->serialize)
 		{
-			char *value = mdi->serialize(&moddata_client(client, mdi));
+			const char *value = mdi->serialize(&moddata_client(client, mdi));
 			if (value)
 				sendto_one(srv, NULL, ":%s MD %s %s %s :%s",
 					me.id, "client", client->id, mdi->name, value);
@@ -356,6 +389,62 @@ void _send_moddata_client(Client *srv, Client *client)
 	}
 }
 
+/** Enhance the command with moddata message tags, so we can send
+ * traffic like @s2s-md/certfp=xxxxx UID ....
+ */
+void _moddata_add_s2s_mtags(Client *client, MessageTag **mtags_list)
+{
+	ModDataInfo *mdi;
+	char name[128];
+
+	for (mdi = MDInfo; mdi; mdi = mdi->next)
+	{
+		if ((mdi->type == MODDATATYPE_CLIENT) && (mdi->sync == MODDATA_SYNC_EARLY) && mdi->serialize)
+		{
+			MessageTag *m;
+			const char *value = mdi->serialize(&moddata_client(client, mdi));
+			if (!value)
+				continue;
+			snprintf(name, sizeof(name), "s2s-md/%s", mdi->name);
+
+			m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, name);
+			safe_strdup(m->value, value);
+			AddListItem(m, *mtags_list);
+		}
+	}
+}
+
+/** Extract the s2s-md/<moddataname> tags again from an incoming command,
+ * eg @s2s-md/certfp=xxxxx UID ....
+ */
+void _moddata_extract_s2s_mtags(Client *client, MessageTag *mtags)
+{
+	MessageTag *m;
+	ModDataInfo *md;
+
+	for (m = mtags; m; m = m->next)
+	{
+		if (!strncmp(m->name, "s2s-md/", 7))
+		{
+			char *varname = m->name + 7;
+			char *value = m->value;
+
+			if (!value)
+				continue;
+
+			md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
+			if (!md || !md->unserialize)
+				continue;
+
+			if (!md_access_check(client, md, client))
+				return;
+
+			md->unserialize(value, &moddata_client(client, md));
+		}
+	}
+}
+
 /** Send all moddata attached to channel 'channel' to remote server 'srv' (if the module wants this), called by SJOIN */
 void _send_moddata_channel(Client *srv, Channel *channel)
 {
@@ -365,10 +454,10 @@ void _send_moddata_channel(Client *srv, Channel *channel)
 	{
 		if ((mdi->type == MODDATATYPE_CHANNEL) && mdi->sync && mdi->serialize)
 		{
-			char *value = mdi->serialize(&moddata_channel(channel, mdi));
+			const char *value = mdi->serialize(&moddata_channel(channel, mdi));
 			if (value)
 				sendto_one(srv, NULL, ":%s MD %s %s %s :%s",
-					me.id, "channel", channel->chname, mdi->name, value);
+					me.id, "channel", channel->name, mdi->name, value);
 		}
 	}
 }
@@ -392,10 +481,10 @@ void _send_moddata_members(Client *srv)
 			{
 				if ((mdi->type == MODDATATYPE_MEMBER) && mdi->sync && mdi->serialize)
 				{
-					char *value = mdi->serialize(&moddata_member(m, mdi));
+					const char *value = mdi->serialize(&moddata_member(m, mdi));
 					if (value)
 						sendto_one(srv, NULL, ":%s MD %s %s:%s %s :%s",
-							me.id, "member", channel->chname, client->id, mdi->name, value);
+							me.id, "member", channel->name, client->id, mdi->name, value);
 				}
 			}
 		}
@@ -416,10 +505,10 @@ void _send_moddata_members(Client *srv)
 			{
 				if ((mdi->type == MODDATATYPE_MEMBERSHIP) && mdi->sync && mdi->serialize)
 				{
-					char *value = mdi->serialize(&moddata_membership(m, mdi));
+					const char *value = mdi->serialize(&moddata_membership(m, mdi));
 					if (value)
 						sendto_one(srv, NULL, ":%s MD %s %s:%s %s :%s",
-							me.id, "membership", client->id, m->channel->chname, mdi->name, value);
+							me.id, "membership", client->id, m->channel->name, mdi->name, value);
 				}
 			}
 		}
diff --git a/src/modules/mdex.c b/src/modules/mdex.c
@@ -1,317 +0,0 @@
-/*
- * Example module for ModData usage
- * NEVER LOAD THIS ON A LIVE SERVER!!
- *
- * (C) Copyright 2014 Bram Matthys and the UnrealIRCd team
- * License: GPLv2
- */
-
-#include "unrealircd.h"
-
-CMD_FUNC(cmd_mdex);
-
-ModuleHeader MOD_HEADER
-  = {
-	"mdex",
-	"5.0",
-	"Command /MDEX",
-	"UnrealIRCd Team",
-	"unrealircd-5",
-    };
-
-ModDataInfo *mdex_cli = NULL, *mdex_chan = NULL, *mdex_member = NULL, *mdex_membership = NULL;
-void mdex_free(ModData *m);
-char *mdex_serialize(ModData *m);
-void mdex_unserialize(char *str, ModData *m);
-
-MOD_INIT()
-{
-ModDataInfo mreq;
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "mdex";
-	mreq.free = mdex_free;
-	mreq.serialize = mdex_serialize;
-	mreq.unserialize = mdex_unserialize;
-	mreq.sync = 1;
-	mreq.type = MODDATATYPE_CLIENT;
-	mdex_cli = ModDataAdd(modinfo->handle, mreq);
-	if (!mdex_cli)
-	        abort();
-	mreq.type = MODDATATYPE_CHANNEL;
-	mdex_chan = ModDataAdd(modinfo->handle, mreq);
-	if (!mdex_cli)
-	        abort();
-	mreq.type = MODDATATYPE_MEMBER;
-	mdex_member = ModDataAdd(modinfo->handle, mreq);
-	if (!mdex_cli)
-	        abort();
-	mreq.type = MODDATATYPE_MEMBERSHIP;
-	mdex_membership = ModDataAdd(modinfo->handle, mreq);
-	if (!mdex_cli)
-	        abort();
-
-	CommandAdd(modinfo->handle, "MDEX", cmd_mdex, MAXPARA, CMD_USER);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-CMD_FUNC(cmd_mdex)
-{
-	char *action, *type, *objname, *varname, *value;
-	ModDataInfo *md;
-
-	if (!IsOper(client) || (parc < 5) || BadPtr(parv[4]))
-		return 0;
-
-	action = parv[1]; /* get / set */
-	type = parv[2];
-	objname = parv[3];
-#ifdef DEBUGMODE
-	varname = parv[4];
-#else
-	varname = "mdex";
-#endif
-	value = parv[5]; /* may be NULL */
-
-	if (!strcmp(action, "set"))
-	{
-		if (!strcmp(type, "client"))
-		{
-			Client *target = find_client(objname, NULL);
-			md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
-			if (!md || !md->unserialize || !md->free || !target)
-				return 0;
-			if (value)
-				md->unserialize(value, &moddata_client(target, md));
-			else
-			{
-				md->free(&moddata_client(target, md));
-				memset(&moddata_client(target, md), 0, sizeof(ModData));
-			}
-			broadcast_md_client(md, target, &moddata_client(target, md));
-		} else
-		if (!strcmp(type, "channel"))
-		{
-			Channel *channel = find_channel(objname, NULL);
-			md = findmoddata_byname(varname, MODDATATYPE_CHANNEL);
-			if (!md || !md->unserialize || !md->free || !channel)
-				return 0;
-			if (value)
-				md->unserialize(value, &moddata_channel(channel, md));
-			else
-			{
-				md->free(&moddata_channel(channel, md));
-				memset(&moddata_channel(channel, md), 0, sizeof(ModData));
-			}
-			broadcast_md_channel(md, channel, &moddata_channel(channel, md));
-		} else
-		if (!strcmp(type, "member"))
-		{
-			Client *target;
-			Channel *channel;
-			Member *m;
-			char *p;
-			
-			/* for member the object name is like '#channel/Syzop' */
-			p = strchr(objname, ':');
-			if (!p)
-				return 0;
-			*p++ = '\0';
-
-			channel = find_channel(objname, NULL);
-			if (!channel)
-				return 0;
-			
-			target = find_person(p, NULL);
-			if (!target)
-				return 0;
-
-			m = find_member_link(channel->members, target);
-			if (!m)
-				return 0;
-			
-			md = findmoddata_byname(varname, MODDATATYPE_MEMBER);
-			if (!md || !md->unserialize || !md->free)
-				return 0;
-
-			if (value)
-				md->unserialize(value, &moddata_member(m, md));
-			else
-			{
-				md->free(&moddata_member(m, md));
-				memset(&moddata_member(m, md), 0, sizeof(ModData));
-			}
-			broadcast_md_member(md, channel, m, &moddata_member(m, md));
-		} else
-		if (!strcmp(type, "membership"))
-		{
-			Client *target;
-			Channel *channel;
-			Membership *m;
-			char *p;
-			
-			/* for membership the object name is like 'Syzop/#channel' */
-			p = strchr(objname, ':');
-			if (!p)
-				return 0;
-			*p++ = '\0';
-
-			target = find_person(objname, NULL);
-			if (!target)
-				return 0;
-			
-			channel = find_channel(p, NULL);
-			if (!channel)
-				return 0;
-
-			m = find_membership_link(target->user->channel, channel);
-			if (!m)
-				return 0;
-			
-			md = findmoddata_byname(varname, MODDATATYPE_MEMBERSHIP);
-			if (!md || !md->unserialize || !md->free)
-				return 0;
-
-			if (value)
-				md->unserialize(value, &moddata_membership(m, md));
-			else
-			{
-				md->free(&moddata_membership(m, md));
-				memset(&moddata_membership(m, md), 0, sizeof(ModData));
-			}
-			broadcast_md_membership(md, target, m, &moddata_membership(m, md));
-		}
-	} else
-	if (!strcmp(action, "get"))
-	{
-		if (!strcmp(type, "client"))
-		{
-			Client *target = find_client(objname, NULL);
-			char *str;
-			
-			md = findmoddata_byname(varname, MODDATATYPE_CLIENT);
-			if (!md || !md->serialize || !target)
-				return 0;
-			str = md->serialize(&moddata_client(target, md));
-			if (str)
-				sendnotice(client, "Value: %s", str ? str : "<null>");
-			else
-				sendnotice(client, "No value set");
-		} else
-		if (!strcmp(type, "channel"))
-		{
-			Channel *channel = find_channel(objname, NULL);
-			char *str;
-			
-			md = findmoddata_byname(varname, MODDATATYPE_CHANNEL);
-			if (!md || !md->serialize || !channel)
-				return 0;
-			str = md->serialize(&moddata_channel(channel, md));
-			if (str)
-				sendnotice(client, "Value: %s", str ? str : "<null>");
-			else
-				sendnotice(client, "No value set");
-		} else
-		if (!strcmp(type, "member"))
-		{
-			Client *target;
-			Channel *channel;
-			Member *m;
-			char *p, *str;
-			
-			/* for member the object name is like '#channel/Syzop' */
-			p = strchr(objname, ':');
-			if (!p)
-				return 0;
-			*p++ = '\0';
-
-			channel = find_channel(objname, NULL);
-			if (!channel)
-				return 0;
-			
-			target = find_person(p, NULL);
-			if (!target)
-				return 0;
-
-			m = find_member_link(channel->members, target);
-			if (!m)
-				return 0;
-			
-			md = findmoddata_byname(varname, MODDATATYPE_MEMBER);
-			if (!md || !md->serialize)
-				return 0;
-
-			str = md->serialize(&moddata_member(m, md));
-			if (str)
-				sendnotice(client, "Value: %s", str ? str : "<null>");
-			else
-				sendnotice(client, "No value set");
-		} else
-		if (!strcmp(type, "membership"))
-		{
-			Client *target;
-			Channel *channel;
-			Membership *m;
-			char *p, *str;
-			
-			/* for membership the object name is like 'Syzop/#channel' */
-			p = strchr(objname, ':');
-			if (!p)
-				return 0;
-			*p++ = '\0';
-
-			target = find_person(objname, NULL);
-			if (!target)
-				return 0;
-			
-			channel = find_channel(p, NULL);
-			if (!channel)
-				return 0;
-
-			m = find_membership_link(target->user->channel, channel);
-			if (!m)
-				return 0;
-			
-			md = findmoddata_byname(varname, MODDATATYPE_MEMBERSHIP);
-			if (!md || !md->serialize)
-				return 0;
-
-			str = md->serialize(&moddata_membership(m, md));
-			if (str)
-				sendnotice(client, "Value: %s", str ? str : "<null>");
-			else
-				sendnotice(client, "No value set");
-		}
-	}
-	
-	return 0;
-}
-
-void mdex_free(ModData *m)
-{
-	safe_free(m->str);
-}
-
-char *mdex_serialize(ModData *m)
-{
-	if (!m->str)
-		return NULL;
-	return m->str;
-}
-
-void mdex_unserialize(char *str, ModData *m)
-{
-	safe_strdup(m->str, str);
-}
diff --git a/src/modules/message-ids.c b/src/modules/message-ids.c
@@ -28,14 +28,14 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"msgid CAP",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
 long CAP_ACCOUNT_TAG = 0L;
 
-int msgid_mtag_is_ok(Client *client, char *name, char *value);
-void mtag_add_or_inherit_msgid(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int msgid_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_or_inherit_msgid(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -69,7 +69,7 @@ MOD_UNLOAD()
  * syntax.
  * We simply allow msgid ONLY from servers and with any syntax.
  */
-int msgid_mtag_is_ok(Client *client, char *name, char *value)
+int msgid_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client) && !BadPtr(value))
 		return 1;
@@ -103,7 +103,7 @@ MessageTag *mtag_generate_msgid(void)
 }
 
 
-void mtag_add_or_inherit_msgid(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_or_inherit_msgid(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m = find_mtag(recv_mtags, "msgid");
 	if (m)
@@ -146,9 +146,7 @@ void mtag_add_or_inherit_msgid(Client *sender, MessageTag *recv_mtags, MessageTa
 		char newbuf[256];
 		memset(&binaryhash, 0, sizeof(binaryhash));
 		memset(&b64hash, 0, sizeof(b64hash));
-		SHA256_Init(&hash);
-		SHA256_Update(&hash, signature, strlen(signature));
-		SHA256_Final(binaryhash, &hash);
+		sha256hash_binary(binaryhash, signature, strlen(signature));
 		b64_encode(binaryhash, sizeof(binaryhash)/2, b64hash, sizeof(b64hash));
 		b64hash[22] = '\0'; /* cut off at '=' */
 		snprintf(newbuf, sizeof(newbuf), "%s-%s", prefix, b64hash);
diff --git a/src/modules/message-tags.c b/src/modules/message-tags.c
@@ -28,18 +28,18 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Message tags CAP", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 long CAP_MESSAGE_TAGS = 0L;
-char *_mtags_to_string(MessageTag *m, Client *client);
+const char *_mtags_to_string(MessageTag *m, Client *client);
 void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list);
 
 MOD_TEST()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
-	EfunctionAddPChar(modinfo->handle, EFUNC_MTAGS_TO_STRING, _mtags_to_string);
+	EfunctionAddConstString(modinfo->handle, EFUNC_MTAGS_TO_STRING, _mtags_to_string);
 	EfunctionAddVoid(modinfo->handle, EFUNC_PARSE_MESSAGE_TAGS, _parse_message_tags);
 
 	return 0;
@@ -148,7 +148,13 @@ int message_tag_ok(Client *client, char *name, char *value)
 
 	m = MessageTagHandlerFind(name);
 	if (!m)
+	{
+		/* Permit unknown message tags from trusted servers */
+		if (IsServer(client) || !MyConnect(client))
+			return 1;
+
 		return 0;
+	}
 
 	if (m->is_ok(client, name, value))
 		return 1;
@@ -198,7 +204,7 @@ void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list)
 			m = safe_alloc(sizeof(MessageTag));
 			safe_strdup(m->name, name);
 			/* Both NULL and empty become NULL: */
-			if (BadPtr(value))
+			if (!*value)
 				m->value = NULL;
 			else /* a real value... */
 				safe_strdup(m->value, value);
@@ -227,7 +233,7 @@ int client_accepts_tag(const char *token, Client *client)
 		return 0;
 
 	/* Maybe there is an outgoing filter in effect (usually not) */
-	if (m->can_send && !m->can_send(client))
+	if (m->should_send_to_client && !m->should_send_to_client(client))
 		return 0;
 
 	/* If the client has indicated 'message-tags' support then we can
@@ -257,10 +263,10 @@ int client_accepts_tag(const char *token, Client *client)
  * Taking into account the restrictions that 'client' may have.
  * @returns A string (static buffer) or NULL if no tags at all (!)
  */
-char *_mtags_to_string(MessageTag *m, Client *client)
+const char *_mtags_to_string(MessageTag *m, Client *client)
 {
 	static char buf[4096], name[8192], value[8192];
-	char tbuf[512];
+	static char tbuf[4094];
 
 	if (!m)
 		return NULL;
diff --git a/src/modules/message.c b/src/modules/message.c
@@ -21,15 +21,15 @@
 #include "unrealircd.h"
 
 /* Forward declarations */
-char *_StripColors(unsigned char *text);
-char *_StripControlCodes(unsigned char *text);
-int ban_version(Client *client, char *text);
+const char *_StripColors(const char *text);
+const char *_StripControlCodes(const char *text);
+int ban_version(Client *client, const char *text);
 CMD_FUNC(cmd_private);
 CMD_FUNC(cmd_notice);
 CMD_FUNC(cmd_tagmsg);
-void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], SendType sendtype);
-int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, SendType sendtype);
-int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, SendType sendtype);
+void cmd_message(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], SendType sendtype);
+int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype);
+int can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype);
 
 /* Variables */
 long CAP_MESSAGE_TAGS = 0; /**< Looked up at MOD_LOAD, may stay 0 if message-tags support is absent */
@@ -40,14 +40,14 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"private message and notice", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_TEST()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddPChar(modinfo->handle, EFUNC_STRIPCOLORS, _StripColors);
-	EfunctionAddPChar(modinfo->handle, EFUNC_STRIPCONTROLCODES, _StripControlCodes);
+	EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCOLORS, _StripColors);
+	EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCONTROLCODES, _StripControlCodes);
 	EfunctionAdd(modinfo->handle, EFUNC_CAN_SEND_TO_CHANNEL, _can_send_to_channel);
 	return MOD_SUCCESS;
 }
@@ -85,7 +85,7 @@ MOD_UNLOAD()
  * text:	Pointer to a pointer to a text [in, out]
  * cmd:		Pointer to a pointer which contains the command to use [in, out]
  */
-int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, SendType sendtype)
+int can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype)
 {
 	int ret;
 	Hook *h;
@@ -111,7 +111,7 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
 
 	if (is_silenced(client, target))
 	{
-		RunHook3(HOOKTYPE_SILENCED, client, target, sendtype);
+		RunHook(HOOKTYPE_SILENCED, client, target, sendtype);
 		/* Silently discarded, no error message */
 		return 0;
 	}
@@ -120,7 +120,7 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
 	if (MyUser(client))
 	{
 		int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG);
-		char *cmd = sendtype_to_cmd(sendtype);
+		const char *cmd = sendtype_to_cmd(sendtype);
 
 		if (match_spamfilter(client, *msgtext, spamtype, cmd, target->name, 0, NULL))
 			return 0;
@@ -134,7 +134,9 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
 		{
 			if (!*errmsg)
 			{
-				ircd_log(LOG_ERROR, "Module %s did not set errmsg!!!", h->owner->header->name);
+				unreal_log(ULOG_ERROR, "main", "BUG_CAN_SEND_TO_USER_NO_ERRMSG", client,
+					   "[BUG] Module $module did not set errmsg!!!",
+					   log_data_string("module", h->owner->header->name));
 				abort();
 			}
 			return 0;
@@ -151,85 +153,12 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
 	return 1;
 }
 
-#ifdef PREFIX_AQ
- #define PREFIX_REST (PREFIX_ADMIN|PREFIX_OWNER)
-#else
- #define PREFIX_REST (0)
-#endif
-
-/** Convert a string of prefixes (like "+%@") to values (like PREFIX_VOICE|PREFIX_HALFOP|PREFIX_OP).
- * @param str	The string containing the prefixes and the channel name.
- * @param end	The position of the hashmark (#)
- * @returns A value of PREFIX_*, potentially OR'ed if there are multiple values.
- */
-int prefix_string_to_values(char *str, char *end)
-{
-	char *p;
-	int prefix = 0;
-
-	for (p = str; p != end; p++)
-	{
-		switch (*p)
-		{
-			case '+':
-				prefix |= PREFIX_VOICE | PREFIX_HALFOP | PREFIX_OP | PREFIX_REST;
-				break;
-			case '%':
-				prefix |= PREFIX_HALFOP | PREFIX_OP | PREFIX_REST;
-				break;
-			case '@':
-				prefix |= PREFIX_OP | PREFIX_REST;
-				break;
-#ifdef PREFIX_AQ
-			case '&':
-				prefix |= PREFIX_ADMIN | PREFIX_OWNER;
-				break;
-			case '~':
-				prefix |= PREFIX_OWNER;
-				break;
-#else
-			case '&':
-				prefix |= PREFIX_OP | PREFIX_REST;
-				break;
-			case '~':
-				prefix |= PREFIX_OP | PREFIX_REST;
-				break;
-#endif
-			default:
-				break;	/* ignore it :P */
-		}
-	}
-	return prefix;
-}
-
-/** Find out the lowest prefix to use, so @&~#chan becomes @#chan.
- * @param prefix	One or more of PREFIX_* values (OR'ed)
- * @returns A single character
- * @note prefix must be >0, so must contain at least one PREFIX_xx value!
- */
-char prefix_values_to_char(int prefix)
-{
-	if (prefix & PREFIX_VOICE)
-		return '+';
-	if (prefix & PREFIX_HALFOP)
-		return '%';
-	if (prefix & PREFIX_OP)
-		return '@';
-#ifdef PREFIX_AQ
-	if (prefix & PREFIX_ADMIN)
-		return '&';
-	if (prefix & PREFIX_OWNER)
-		return '~';
-#endif
-	abort();
-}
-
 /** Check if user is allowed to send to a prefix (eg: @#channel).
  * @param client	The client (sender)
  * @param channel	The target channel
- * @param prefix	The prefix mask (eg: PREFIX_CHANOP)
+ * @param mode		The member mode to send to (eg: 'o')
  */
-int can_send_to_prefix(Client *client, Channel *channel, int prefix)
+int can_send_to_member_mode(Client *client, Channel *channel, char mode)
 {
 	Membership *lp;
 
@@ -242,18 +171,20 @@ int can_send_to_prefix(Client *client, Channel *channel, int prefix)
 	 * Need at least voice (+) in order to send to +,% or @
 	 * Need at least ops (@) in order to send to & or ~
 	 */
-	if (!lp || !(lp->flags & (CHFL_VOICE|CHFL_HALFOP|CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN)))
+	if (!lp || !check_channel_access_membership(lp, "vhoaq"))
 	{
-		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
 		return 0;
 	}
 
+#if 0
 	if (!(prefix & PREFIX_OP) && ((prefix & PREFIX_OWNER) || (prefix & PREFIX_ADMIN)) &&
-	    !(lp->flags & (CHFL_CHANOP|CHFL_CHANOWNER|CHFL_CHANADMIN)))
+	    !check_channel_access_membership(lp, "oaq"))
 	{
-		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
 		return 0;
 	}
+#endif
 
 	return 1;
 }
@@ -270,16 +201,16 @@ int has_client_mtags(MessageTag *mtags)
 
 /* General message handler to users and channels. Used by PRIVMSG, NOTICE, etc.
  */
-void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], SendType sendtype)
+void cmd_message(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], SendType sendtype)
 {
 	Client *target;
 	Channel *channel;
-	char *targetstr, *p, *p2, *pc, *text, *errmsg;
-	int  prefix = 0;
-	char pfixchan[CHANNELLEN + 4];
+	char targets[BUFSIZE];
+	char *targetstr, *p, *p2, *pc;
+	const char *text, *errmsg;
 	int ret;
 	int ntargets = 0;
-	char *cmd = sendtype_to_cmd(sendtype);
+	const char *cmd = sendtype_to_cmd(sendtype);
 	int maxtargets = max_targets_for_command(cmd);
 	Hook *h;
 	MessageTag *mtags;
@@ -306,7 +237,8 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 	if (MyConnect(client))
 		parv[1] = (char *)canonize(parv[1]);
 
-	for (p = NULL, targetstr = strtoken(&p, parv[1], ","); targetstr; targetstr = strtoken(&p, NULL, ","))
+	strlcpy(targets, parv[1], sizeof(targets));
+	for (p = NULL, targetstr = strtoken(&p, targets, ","); targetstr; targetstr = strtoken(&p, NULL, ","))
 	{
 		if (MyUser(client) && (++ntargets > maxtargets))
 		{
@@ -331,29 +263,44 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 		}
 
 		p2 = strchr(targetstr, '#');
-		prefix = 0;
 
 		/* Message to channel */
-		if (p2 && (channel = find_channel(p2, NULL)))
+		if (p2 && (channel = find_channel(p2)))
 		{
-			prefix = prefix_string_to_values(targetstr, p2);
-			if (prefix)
+			char pfixchan[CHANNELLEN + 4];
+			int replaced = 0;
+			char member_modes_tmp[2];
+			char *member_modes = NULL;
+			if (p2 - targetstr > 0)
+			{
+				/* There is (posssibly) a prefix involved... */
+				char prefix_tmp[32];
+				char prefix;
+				strlncpy(prefix_tmp, targetstr, sizeof(prefix_tmp), p2 - targetstr);
+				prefix = lowest_ranking_prefix(prefix_tmp);
+				if (prefix)
+				{
+					/* Rewrite the target. Eg: @&~#chan becomes @#chan */
+					snprintf(pfixchan, sizeof(pfixchan), "%c%s", prefix, channel->name);
+					targetstr = pfixchan;
+					replaced = 1;
+					/* And set 'member_modes' */
+					member_modes_tmp[0] = prefix_to_mode(prefix);
+					member_modes_tmp[1] = '\0';
+					member_modes = member_modes_tmp;
+					/* Oh, and some access check */
+					if (MyUser(client) && !can_send_to_member_mode(client, channel, *member_modes))
+						continue;
+				}
+			}
+			if (!replaced)
 			{
-				if (MyUser(client) && !can_send_to_prefix(client, channel, prefix))
-					continue;
-				/* Now find out the lowest prefix and rewrite the target.
-				 * Eg: @&~#chan becomes @#chan
-				 */
-				pfixchan[0] = prefix_values_to_char(prefix);
-				strlcpy(pfixchan+1, channel->chname, sizeof(pfixchan)-1);
-				targetstr = pfixchan;
-			} else {
 				/* Replace target so the privmsg always goes to the "official" channel name */
-				strlcpy(pfixchan, channel->chname, sizeof(pfixchan));
+				strlcpy(pfixchan, channel->name, sizeof(pfixchan));
 				targetstr = pfixchan;
 			}
 
-			if (IsVirus(client) && strcasecmp(channel->chname, SPAMFILTER_VIRUSCHAN))
+			if (IsVirus(client) && strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN))
 			{
 				sendnotice(client, "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
 				continue;
@@ -372,7 +319,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 					if (IsDead(client))
 						return;
 					if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE) && errmsg)
-						sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->chname, errmsg, p2);
+						sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->name, errmsg, p2);
 					continue; /* skip delivery to this target */
 				}
 			}
@@ -389,13 +336,13 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 			{
 				int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG);
 
-				if (match_spamfilter(client, text, spamtype, cmd, channel->chname, 0, NULL))
+				if (match_spamfilter(client, text, spamtype, cmd, channel->name, 0, NULL))
 					return;
 			}
 
 			new_message(client, recv_mtags, &mtags);
 
-			RunHook5(HOOKTYPE_PRE_CHANMSG, client, channel, mtags, text, sendtype);
+			RunHook(HOOKTYPE_PRE_CHANMSG, client, channel, mtags, text, sendtype);
 
 			if (!text)
 			{
@@ -407,7 +354,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 			{
 				/* PRIVMSG or NOTICE */
 				sendto_channel(channel, client, client->direction,
-					       prefix, 0, sendflags, mtags,
+					       member_modes, 0, sendflags, mtags,
 					       ":%s %s %s :%s",
 					       client->name, cmd, targetstr, text);
 			} else {
@@ -422,12 +369,12 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 					continue;
 				}
 				sendto_channel(channel, client, client->direction,
-					       prefix, CAP_MESSAGE_TAGS, sendflags, mtags,
+					       member_modes, CAP_MESSAGE_TAGS, sendflags, mtags,
 					       ":%s TAGMSG %s",
 					       client->name, targetstr);
 			}
 
-			RunHook8(HOOKTYPE_CHANMSG, client, channel, sendflags, prefix, targetstr, mtags, text, sendtype);
+			RunHook(HOOKTYPE_CHANMSG, client, channel, sendflags, member_modes, targetstr, mtags, text, sendtype);
 
 			free_message_tags(mtags);
 
@@ -469,7 +416,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 		target = hash_find_nickatserver(targetstr, NULL);
 		if (target)
 		{
-			char *errmsg = NULL;
+			const char *errmsg = NULL;
 			text = parv[2];
 			if (!can_send_to_user(client, target, &text, &errmsg, sendtype))
 			{
@@ -520,7 +467,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 					}
 				}
 				labeled_response_inhibit = 0;
-				RunHook5(HOOKTYPE_USERMSG, client, target, mtags, text, sendtype);
+				RunHook(HOOKTYPE_USERMSG, client, target, mtags, text, sendtype);
 				free_message_tags(mtags);
 				continue;
 			}
@@ -581,11 +528,12 @@ CMD_FUNC(cmd_tagmsg)
  * RGB color stripping support added -- codemastr
  */
 
-char *_StripColors(unsigned char *text)
+const char *_StripColors(const char *text)
 {
 	int i = 0, len = strlen(text), save_len=0;
-	char nc = 0, col = 0, rgb = 0, *save_text=NULL;
-	static unsigned char new_str[4096];
+	char nc = 0, col = 0, rgb = 0;
+	const char *save_text=NULL;
+	static char new_str[4096];
 
 	while (len > 0) 
 	{
@@ -648,10 +596,11 @@ char *_StripColors(unsigned char *text)
 }
 
 /* strip color, bold, underline, and reverse codes from a string */
-char *_StripControlCodes(unsigned char *text) 
+const char *_StripControlCodes(const char *text) 
 {
 	int i = 0, len = strlen(text), save_len=0;
-	char nc = 0, col = 0, rgb = 0, *save_text=NULL;
+	char nc = 0, col = 0, rgb = 0;
+	const char *save_text=NULL;
 	static unsigned char new_str[4096];
 	while (len > 0) 
 	{
@@ -744,19 +693,21 @@ char *_StripControlCodes(unsigned char *text)
 }
 
 /** Check ban version { } blocks, returns 1  if banned and  0 if not. */
-int ban_version(Client *client, char *text)
+int ban_version(Client *client, const char *text)
 {
 	int len;
 	ConfigItem_ban *ban;
+	char ctcp_reply[BUFSIZE];
 
-	len = strlen(text);
+	strlcpy(ctcp_reply, text, sizeof(ctcp_reply));
+	len = strlen(ctcp_reply);
 	if (!len)
 		return 0;
+	
+	if (ctcp_reply[len-1] == '\1')
+		ctcp_reply[len-1] = '\0'; /* remove CTCP REPLY terminator (ASCII 1) */
 
-	if (text[len-1] == '\1')
-		text[len-1] = '\0'; /* remove CTCP REPLY terminator (ASCII 1) */
-
-	if ((ban = find_ban(NULL, text, CONF_BAN_VERSION)))
+	if ((ban = find_ban(NULL, ctcp_reply, CONF_BAN_VERSION)))
 	{
 		if (IsSoftBanAction(ban->action) && IsLoggedIn(client))
 			return 0; /* soft ban does not apply to us, we are logged in */
@@ -780,7 +731,7 @@ int ban_version(Client *client, char *text)
  * @returns Returns 1 if the user is allowed to send, otherwise 0.
  * (note that this behavior was reversed in UnrealIRCd versions <5.x.
  */
-int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, SendType sendtype)
+int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype)
 {
 	Membership *lp;
 	int  member, i = 0;
@@ -793,45 +744,7 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char 
 
 	member = IsMember(client, channel);
 
-	if (channel->mode.mode & MODE_NOPRIVMSGS && !member)
-	{
-		/* Channel does not accept external messages (+n).
-		 * Reject, unless HOOKTYPE_CAN_BYPASS_NO_EXTERNAL_MSGS tells otherwise.
-		 */
-		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_EXTERNAL);
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-		if (i != HOOK_ALLOW)
-		{
-			*errmsg = "No external channel messages";
-			return 0;
-		}
-	}
-
 	lp = find_membership_link(client->user->channel, channel);
-	if (channel->mode.mode & MODE_MODERATED &&
-	    !op_can_override("channel:override:message:moderated",client,channel,NULL) &&
-	    (!lp /* FIXME: UGLY */
-	    || !(lp->flags & (CHFL_CHANOP | CHFL_VOICE | CHFL_CHANOWNER | CHFL_HALFOP | CHFL_CHANADMIN))))
-	{
-		/* Channel is moderated (+m).
-		 * Reject, unless HOOKTYPE_CAN_BYPASS_MODERATED tells otherwise.
-		 */
-		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_MODERATED);
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-		if (i != HOOK_ALLOW)
-		{
-			*errmsg = "You need voice (+v)";
-			return 0;
-		}
-	}
 
 	/* Modules can plug in as well */
 	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_CHANNEL]; h; h = h->next)
@@ -841,7 +754,9 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char 
 		{
 			if (!*errmsg)
 			{
-				ircd_log(LOG_ERROR, "Module %s did not set errmsg!!!", h->owner->header->name);
+				unreal_log(ULOG_ERROR, "main", "BUG_CAN_SEND_TO_CHANNEL_NO_ERRMSG", client,
+					   "[BUG] Module $module did not set errmsg!!!",
+					   log_data_string("module", h->owner->header->name));
 				abort();
 			}
 			break;
@@ -873,10 +788,10 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char 
 	if (op_can_override("channel:override:message:ban",client,channel,NULL))
 		return 1;
 
-	if ((!lp
-	    || !(lp->flags & (CHFL_CHANOP | CHFL_VOICE | CHFL_CHANOWNER |
-	    CHFL_HALFOP | CHFL_CHANADMIN))) && MyUser(client)
-	    && is_banned(client, channel, BANCHK_MSG, msgtext, errmsg))
+	/* If local client is banned and not +vhoaq... */
+	if (MyUser(client) &&
+	    !check_channel_access_membership(lp, "vhoaq") &&
+	    is_banned(client, channel, BANCHK_MSG, msgtext, errmsg))
 	{
 		/* Modules can set 'errmsg', otherwise we default to this: */
 		if (!*errmsg)
diff --git a/src/modules/mkpasswd.c b/src/modules/mkpasswd.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /mkpasswd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,8 +60,8 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_mkpasswd)
 {
-	short	type;
-	char	*result = NULL;
+	short type;
+	const char *result = NULL;
 
 	if (!MKPASSWD_FOR_EVERYONE && !IsOper(client))
 	{
@@ -73,9 +73,9 @@ CMD_FUNC(cmd_mkpasswd)
 		/* Non-opers /mkpasswd usage: lag them up, and send a notice to eyes snomask.
 		 * This notice is always sent, even in case of bad usage/bad auth methods/etc.
 		 */
-		client->local->since += 7;
-		sendto_snomask(SNO_EYES, "*** /mkpasswd used by %s (%s@%s)",
-			client->name, client->user->username, GetHost(client));
+		add_fake_lag(client, 7000);
+		unreal_log(ULOG_INFO, "mkpasswd", "MKPASSWD_COMMAND", client,
+		           "mkpasswd command used by $client.details");
 	}
 
 	if ((parc < 3) || BadPtr(parv[2]))
diff --git a/src/modules/mode.c b/src/modules/mode.c
@@ -1,6 +1,6 @@
 /*
  *   IRC - Internet Relay Chat, src/modules/mode.c
- *   (C) 2005 The UnrealIRCd Team
+ *   (C) 2005-.. The UnrealIRCd Team
  *
  *   See file AUTHORS in IRC package for additional names of
  *   the programmers.
@@ -22,54 +22,53 @@
 
 #include "unrealircd.h"
 
+ModuleHeader MOD_HEADER
+  = {
+	"mode",
+	"5.0",
+	"command /mode",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
 /* Forward declarations */
+int list_mode_request(Client *client, Channel *channel, const char *req);
 CMD_FUNC(cmd_mode);
 CMD_FUNC(cmd_mlock);
-void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc, char *parv[], time_t sendts, int samode);
-void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *pcount,
-                       char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], int bounce);
+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);
 CMD_FUNC(_cmd_umode);
 
 /* local: */
-static void bounce_mode(Channel *, Client *, int, char **);
-int do_mode_char(Channel *channel, long modetype, char modechar, char *param,
+int do_mode_char(Channel *channel, long modetype, char modechar, const char *param,
                  u_int what, Client *client,
-                 u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], char bounce, long my_access);
-int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
-                    Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3],
-                    char bounce);
-void make_mode_str(Channel *channel, long oldm, Cmode_t oldem, long oldl, int pcount,
-                   char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], char *mode_buf, char *para_buf,
-                   size_t mode_buf_size, size_t para_buf_size, char bounce);
+                 u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
+int do_extmode_char(Channel *channel, Cmode *handler, const char *param, u_int what,
+                    Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
+void do_mode_char_member_mode_new(Channel *channel, Cmode *handler, const char *param, u_int what,
+                    Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
+MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, int pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
 
-static void mode_cutoff(char *s);
-static void mode_cutoff2(Client *client, Channel *channel, int *parc_out, char *parv[]);
+static char *mode_cutoff(const char *s);
+void mode_operoverride_msg(Client *client, Channel *channel, char *modebuf, char *parabuf);
 
 static int samode_in_progress = 0;
 
-#define MSG_MODE 	"MODE"
-
-ModuleHeader MOD_HEADER
-  = {
-	"mode",
-	"5.0",
-	"command /mode",
-	"UnrealIRCd Team",
-	"unrealircd-5",
-    };
-
 MOD_TEST()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	EfunctionAddVoid(modinfo->handle, EFUNC_DO_MODE, _do_mode);
-	EfunctionAddVoid(modinfo->handle, EFUNC_SET_MODE, _set_mode);
+	EfunctionAddPVoid(modinfo->handle, EFUNC_SET_MODE, TO_PVOIDFUNC(_set_mode));
 	EfunctionAddVoid(modinfo->handle, EFUNC_CMD_UMODE, _cmd_umode);
+	EfunctionAddVoid(modinfo->handle, EFUNC_SET_CHANNEL_MODE, _set_channel_mode);
 	return MOD_SUCCESS;
 }
 
 MOD_INIT()
 {
-	CommandAdd(modinfo->handle, MSG_MODE, cmd_mode, MAXPARA, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, "MODE", cmd_mode, MAXPARA, CMD_USER|CMD_SERVER);
 	CommandAdd(modinfo->handle, MSG_MLOCK, cmd_mlock, MAXPARA, CMD_SERVER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
@@ -105,7 +104,7 @@ CMD_FUNC(cmd_mode)
 	{
 		if (*parv[1] == '#')
 		{
-			channel = find_channel(parv[1], NULL);
+			channel = find_channel(parv[1]);
 			if (!channel)
 			{
 				cmd_umode(client, recv_mtags, parc, parv);
@@ -130,102 +129,33 @@ CMD_FUNC(cmd_mode)
 
 	if (parc < 3)
 	{
+		char modebuf[BUFSIZE], parabuf[BUFSIZE];
 		*modebuf = *parabuf = '\0';
 
 		modebuf[1] = '\0';
-		channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel);
-		sendnumeric(client, RPL_CHANNELMODEIS, channel->chname, modebuf, parabuf);
-		sendnumeric(client, RPL_CREATIONTIME, channel->chname, channel->creationtime);
+		channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 0);
+		sendnumeric(client, RPL_CHANNELMODEIS, channel->name, modebuf, parabuf);
+		sendnumeric(client, RPL_CREATIONTIME, channel->name, (long long)channel->creationtime);
 		return;
 	}
 
-	if (MyUser(client))
-	{
-		/* Deal with information requests from local users, such as:
-		 * MODE #chan b    Show the ban list
-		 * MODE #chan e    Show the ban exemption list
-		 * MODE #chan I    Show the invite exception list
-		 * MODE #chan q    Show list of channel owners
-		 * MODE #chan a    Show list of channel admins
-		 */
-		if (strstr(parv[2], "b") && BadPtr(parv[3]))
-		{
-			if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remotebanlist",client,NULL,channel,NULL))
-				return;
-			/* send ban list */
-			for (ban = channel->banlist; ban; ban = ban->next)
-				sendnumeric(client, RPL_BANLIST, channel->chname, ban->banstr, ban->who, ban->when);
-			sendnumeric(client, RPL_ENDOFBANLIST, channel->chname);
-			return;
-		}
-
-		if (strstr(parv[2], "e") && BadPtr(parv[3]))
-		{
-			if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remotebanlist",client,NULL,channel,NULL))
-				return;
-			/* send exban list */
-			for (ban = channel->exlist; ban; ban = ban->next)
-				sendnumeric(client, RPL_EXLIST, channel->chname, ban->banstr, ban->who, ban->when);
-			sendnumeric(client, RPL_ENDOFEXLIST, channel->chname);
-			return;
-		}
-
-		if (strstr(parv[2], "I") && BadPtr(parv[3]))
-		{
-			if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remoteinvexlist",client,NULL,channel,NULL))
-				return;
-			for (ban = channel->invexlist; ban; ban = ban->next)
-				sendnumeric(client, RPL_INVEXLIST, channel->chname, ban->banstr, ban->who, ban->when);
-			sendnumeric(client, RPL_ENDOFINVEXLIST, channel->chname);
-			return;
-		}
-
-		if (strstr(parv[2], "q") && BadPtr(parv[3]))
-		{
-			Member *member;
-
-			if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remoteownerlist",client,NULL,channel,NULL))
-				return;
-
-			for (member = channel->members; member; member = member->next)
-			{
-				if (is_chanowner(member->client, channel))
-					sendnumeric(client, RPL_QLIST, channel->chname, member->client->name);
-			}
-			sendnumeric(client, RPL_ENDOFQLIST, channel->chname);
-			return;
-		}
-
-		if (strstr(parv[2], "a") && BadPtr(parv[3]))
-		{
-			Member *member;
-
-			if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remoteownerlist",client,NULL,channel,NULL))
-				return;
-
-			for (member = channel->members; member; member = member->next)
-			{
-				if (is_chanadmin(member->client, channel))
-					sendnumeric(client, RPL_ALIST, channel->chname, member->client->name);
-			}
-			sendnumeric(client, RPL_ENDOFALIST, channel->chname);
-			return;
-		}
-	}
+	/* List mode request? Eg: "MODE #channel b" to list all bans */
+	if (MyUser(client) && BadPtr(parv[3]) && list_mode_request(client, channel, parv[2]))
+		return;
 
 	opermode = 0;
 
 #ifndef NO_OPEROVERRIDE
-	if (IsUser(client) && !IsULine(client) && !is_chan_op(client, channel) &&
-	    !is_half_op(client, channel) && ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
+	if (IsUser(client) && !IsULine(client) && !check_channel_access(client, channel, "oaq") &&
+	    !check_channel_access(client, channel, "h") && ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
 	{
 		sendts = 0;
 		opermode = 1;
 		goto aftercheck;
 	}
 
-	if (IsUser(client) && !IsULine(client) && !is_chan_op(client, channel) &&
-	    is_half_op(client, channel) && ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
+	if (IsUser(client) && !IsULine(client) && !check_channel_access(client, channel, "oaq") &&
+	    check_channel_access(client, channel, "h") && ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
 	{
 		opermode = 2;
 		goto aftercheck;
@@ -233,36 +163,27 @@ CMD_FUNC(cmd_mode)
 #endif
 
 	/* User does not have permission to use the MODE command */
-	if (IsUser(client) && !IsULine(client) && !is_chan_op(client, channel) &&
-	    !is_half_op(client, channel) &&
+	if (MyUser(client) && !IsULine(client) && !check_channel_access(client, channel, "hoaq") &&
 	    !ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
 	{
-		if (MyUser(client))
-		{
-			sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
-			return;
-		}
-		sendto_one(client, NULL, ":%s MODE %s -oh %s %s 0",
-		    me.name, channel->chname, client->name, client->name);
-		/* Tell the other server that the user is
-		 * de-opped.  Fix op desyncs. */
-		bounce_mode(channel, client, parc - 2, parv + 2);
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return;
+	}
+
+	if (parv[2] && (*parv[2] == '&'))
+	{
+		/* We don't do any bounce-mode handling anymore since UnrealIRCd 6 */
 		return;
 	}
 
 	if (IsServer(client) && (sendts = atol(parv[parc - 1])) &&
-	    !IsULine(client) && channel->creationtime &&
-	    sendts > channel->creationtime)
+	    !IsULine(client) && (sendts > channel->creationtime))
 	{
-		if (!(*parv[2] == '&'))	/* & denotes a bounce */
-		{
-			/* !!! */
-			sendto_snomask(SNO_EYES,
-			    "*** TS bounce for %s - %lld(ours) %lld(theirs)",
-			    channel->chname, (long long)channel->creationtime,
-			    (long long)sendts);
-			bounce_mode(channel, client, parc - 2, parv + 2);
-		}
+		unreal_log(ULOG_INFO, "mode", "MODE_TS_IGNORED", client,
+		           "MODE change ignored for $channel from $client: "
+		           "timestamp mismatch, ours=$channel.creationtime, theirs=$their_ts",
+		           log_data_channel("channel", channel),
+		           log_data_integer("their_ts", sendts));
 		return;
 	}
 	if (IsServer(client) && !sendts && *parv[parc - 1] != '0')
@@ -275,8 +196,7 @@ aftercheck:
 	/* This is to prevent excess +<whatever> modes. -- Syzop */
 	if (MyUser(client) && parv[2])
 	{
-		mode_cutoff(parv[2]);
-		mode_cutoff2(client, channel, &parc, parv);
+		parv[2] = mode_cutoff(parv[2]);
 	}
 
 	/* Filter out the unprivileged FIRST. *
@@ -290,939 +210,472 @@ aftercheck:
 /** Cut off mode string (eg: +abcdfjkdsgfgs) at MAXMODEPARAMS modes.
  * @param s The mode string (modes only, no parameters)
  * @note Should only used on local clients
- * @author Syzop
- */
-static void mode_cutoff(char *s)
-{
-unsigned short modesleft = MAXMODEPARAMS * 2; /* be generous... */
-
-	for (; *s && modesleft; s++)
-		if ((*s != '-') && (*s != '+'))
-			modesleft--;
-	*s = '\0';
-}
-
-/** Another mode cutoff routine - this one for the server-side
- * amplification/enlargement problem that happens with bans/exempts/invex
- * as explained in #2837. -- Syzop
- */
-static void mode_cutoff2(Client *client, Channel *channel, int *parc_out, char *parv[])
-{
-	int len, i;
-	int parc = *parc_out;
-
-	if (parc-2 <= 3)
-		return; /* Less than 3 mode parameters? Then we don't even have to check */
-
-	/* Calculate length of MODE if it would go through fully as-is */
-	/* :nick!user@host MODE #channel +something param1 param2 etc... */
-	len = strlen(client->name) + strlen(client->user->username) + strlen(GetHost(client)) +
-	      strlen(channel->chname) + 11;
-
-	len += strlen(parv[2]);
-
-	if (*parv[2] != '+' && *parv[2] != '-')
-		len++;
-
-	for (i = 3; parv[i]; i++)
-	{
-		len += strlen(parv[i]) + 1; /* (+1 for the space character) */
-		/* +4 is another potential amplification (per-param).
-		 * If we were smart we would only check this for b/e/I and only for
-		 * relevant cases (not for all extended), but this routine is dumb,
-		 * so we just +4 for any case where the full mask is missing.
-		 * It's better than assuming +4 for all cases, though...
-		 */
-		if (!match_simple("*!*@*", parv[i]))
-			len += 4;
-	}
-
-	/* Now check if the result is acceptable... */
-	if (len < 510)
-		return; /* Ok, no problem there... */
-
-	/* Ok, we have a potential problem...
-	 * we just dump the last parameter... check how much space we saved...
-	 * and try again if that did not help
-	 */
-	for (i = parc-1; parv[i] && (i > 3); i--)
-	{
-		len -= strlen(parv[i]);
-		if (!match_simple("*!*@*", parv[i]))
-			len -= 4; /* must adjust accordingly.. */
-		parv[i] = NULL;
-		(*parc_out)--;
-		if (len < 510)
-			break;
-	}
-	/* This may be reached if like the first parameter is really insane long..
-	 * which is no problem, as other layers (eg: ban) takes care of that.
-	 * We're done...
-	 */
-}
-
-/* bounce_mode -- written by binary
- *	User or server is NOT authorized to change the mode.  This takes care
- * of making the bounce string and bounce it.  Because of the 1 for the bounce
- * param (last param) of the calls to set_mode and make_mode_str, it will not
- * set the mode, but create the bounce string.
+ * @returns The cleaned up string
  */
-static void bounce_mode(Channel *channel, Client *client, int parc, char *parv[])
+static char *mode_cutoff(const char *i)
 {
-	char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
-	int  pcount;
-
-	set_mode(channel, client, parc, parv, &pcount, pvar, 1);
+	static char newmodebuf[BUFSIZE];
+	char *o;
+	unsigned short modesleft = MAXMODEPARAMS * 2; /* be generous... */
 
-	if (channel->creationtime)
-		sendto_one(client, NULL, ":%s MODE %s &%s %s %lld", me.id,
-		    channel->chname, modebuf, parabuf, (long long)channel->creationtime);
-	else
-		sendto_one(client, NULL, ":%s MODE %s &%s %s", me.id, channel->chname,
-		    modebuf, parabuf);
+	strlcpy(newmodebuf, i, sizeof(newmodebuf));
 
-	/* the '&' denotes a bounce so servers won't bounce a bounce */
+	for (o = newmodebuf; *o && modesleft; o++)
+		if ((*o != '-') && (*o != '+'))
+			modesleft--;
+	*o = '\0';
+	return newmodebuf;
 }
 
 /* do_mode -- written by binary
  *	User or server is authorized to do the mode.  This takes care of
  * setting the mode and relaying it to other users and servers.
  */
-void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc, char *parv[], time_t sendts, int samode)
+void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc, const char *parv[], time_t sendts, int samode)
 {
+	Client *orig_client = client; /* (needed for samode replacement in a loop) */
 	char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
 	int  pcount;
-	char tschange = 0, isbounce = 0;	/* fwd'ing bounce */
-	MessageTag *mtags = NULL;
-
-	new_message(client, recv_mtags, &mtags);
-
-	/* IMPORTANT: if you return, don't forget to free mtags!! */
-
-	if (**parv == '&')
-		isbounce = 1;
+	int i;
+	char tschange = 0;
+	MultiLineMode *m;
 
 	/* Please keep the next 3 lines next to each other */
 	samode_in_progress = samode;
-	set_mode(channel, client, parc, parv, &pcount, pvar, 0);
+	m = set_mode(channel, client, parc, parv, &pcount, pvar);
 	samode_in_progress = 0;
 
-	if (MyConnect(client))
-		RunHook7(HOOKTYPE_PRE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
-	else
-		RunHook7(HOOKTYPE_PRE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
-
 	if (IsServer(client))
 	{
 		if (sendts > 0)
 		{
-			if (!channel->creationtime || sendts < channel->creationtime)
+			if (IsInvalidChannelTS(sendts))
 			{
+				unreal_log(ULOG_WARNING, "mode", "MODE_INVALID_TIMESTAMP", client,
+				           "MODE for channel $channel has invalid timestamp $send_timestamp (from $client.name)\n"
+				           "Buffer: $modebuf $parabuf",
+				           log_data_channel("channel", channel),
+				           log_data_integer("send_timestamp", sendts),
+				           log_data_string("modebuf", m?m->modeline[0]:""),
+				           log_data_string("parabuf", m?m->modeline[0]:""));
+				/* Yeah, so what to do in this case?
+				 * Don't set channel->creationtime
+				 * and assume merging.
+				 */
+				sendts = channel->creationtime;
+			} else
+			if (sendts < channel->creationtime)
+			{
+				/* Our timestamp is wrong or this is a new channel */
 				tschange = 1;
 				channel->creationtime = sendts;
-				if (sendts < 750000)
-				{
-					sendto_realops(
-						"Warning! Possible desync: MODE for channel %s ('%s %s') has fishy timestamp (%lld) (from %s/%s)",
-						channel->chname, modebuf, parabuf, (long long)sendts, client->direction->name, client->name);
-					ircd_log(LOG_ERROR, "Possible desync: MODE for channel %s ('%s %s') has fishy timestamp (%lld) (from %s/%s)",
-						channel->chname, modebuf, parabuf, (long long)sendts, client->direction->name, client->name);
-				}
-				/* new chan or our timestamp is wrong */
-				/* now works for double-bounce prevention */
 
 			}
 			if (sendts > channel->creationtime && channel->creationtime)
 			{
-				/* theirs is wrong but we let it pass anyway */
+				/* Their timestamp is wrong */
 				sendts = channel->creationtime;
 				sendto_one(client, NULL, ":%s MODE %s + %lld", me.name,
-				    channel->chname, (long long)channel->creationtime);
+				    channel->name, (long long)channel->creationtime);
 			}
 		}
-		if (sendts == -1 && channel->creationtime)
+		if (sendts == -1)
 			sendts = channel->creationtime;
 	}
 
-	if (*modebuf == '\0' || (*(modebuf + 1) == '\0' && (*modebuf == '+' || *modebuf == '-')))
+	if (!m)
 	{
-		if (tschange || isbounce)
+		/* No modes changed (empty mode change) */
+		if (tschange && !m)
 		{
-			/* relay bounce time changes */
-			if (channel->creationtime)
-			{
-				sendto_server(client, 0, 0, NULL, ":%s MODE %s %s+ %lld",
-				    me.id, channel->chname, isbounce ? "&" : "",
-				    (long long)channel->creationtime);
-			} else {
-				sendto_server(client, 0, 0, NULL, ":%s MODE %s %s+",
-				    me.id, channel->chname, isbounce ? "&" : "");
-			}
-			free_message_tags(mtags);
-			return; /* nothing to send */
+			/* Message from the other server is an empty mode, BUT they
+			 * did change the channel->creationtime to an earlier TS
+			 * (see above "Our timestamp is wrong or this is a new channel").
+			 * We need to relay this MODE message to all other servers
+			 * (all except from where it came from, client).
+			 */
+			sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld",
+				      me.id, channel->name,
+				      (long long)channel->creationtime);
 		}
+		/* Nothing to send */
+		safe_free_multilinemode(m);
+		opermode = 0;
+		return;
 	}
 
-	/* opermode for twimodesystem --sts */
-#ifndef NO_OPEROVERRIDE
-	if ((opermode == 1) && IsUser(client))
+	/* Now loop through the multiline modes... */
+	for (i = 0; i < m->numlines; i++)
 	{
-		if (modebuf[1])
+		char *modebuf = m->modeline[i];
+		char *parabuf = m->paramline[i];
+		MessageTag *mtags = NULL;
+		int should_destroy = 0;
+
+		if (m->numlines == 1)
 		{
-			sendto_snomask(SNO_EYES,
-			    "*** OperOverride -- %s (%s@%s) MODE %s %s %s",
-			    client->name, client->user->username, client->user->realhost,
-			    channel->chname, modebuf, parabuf);
-
-			/* Logging Implementation added by XeRXeS */
-			ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) MODE %s %s %s",
-				client->name, client->user->username, client->user->realhost,
-				channel->chname, modebuf, parabuf);
+			/* Single mode lines are easy: retain original msgid etc */
+			new_message(client, recv_mtags, &mtags);
+		} else {
+			/* We have a multi-mode line:
+			 * This only happens when the input was a single mode line
+			 * that got expanded into a multi mode line due to expansion
+			 * issues. The sender could be a local client, but could also
+			 * be a remote server like UnrealIRCd 5.
+			 * We can't use the same msgid multiple times, and (if the
+			 * sender was a server) then we can't use the original msgid
+			 * either, not for both events and not for the first event
+			 * (since the modeline differs for all events, including first).
+			 * Obviously message ids must be unique for the event...
+			 * So here is our special version again, just like we use in
+			 * SJOIN and elsewhere sporadically for cases like this:
+			 */
+			new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
 		}
 
-		sendts = 0;
-	}
-#endif
+		/* IMPORTANT: if you return, don't forget to free mtags!! */
 
-	/* Should stop null modes */
-	if (*(modebuf + 1) == '\0')
-	{
-		free_message_tags(mtags);
-		return;
-	}
+		if (MyConnect(client))
+			RunHook(HOOKTYPE_PRE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
+		else
+			RunHook(HOOKTYPE_PRE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
 
-	if (IsUser(client) && samode && MyUser(client))
-	{
-		if (!sajoinmode)
-			sendto_umode_global(UMODE_OPER, "%s used SAMODE %s (%s%s%s)",
-				client->name, channel->chname, modebuf, *parabuf ? " " : "", parabuf);
+		/* opermode for twimodesystem --sts */
+#ifndef NO_OPEROVERRIDE
+		if ((opermode == 1) && IsUser(client))
+		{
+			mode_operoverride_msg(client, channel, modebuf, parabuf);
 
-		client = &me;
-		sendts = 0;
-	}
+			sendts = 0;
+		}
+#endif
 
-	sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-	               ":%s MODE %s %s %s",
-	               client->name, channel->chname, modebuf, parabuf);
+		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));
+			}
 
-	if (IsServer(client) && sendts != -1)
-	{
-		sendto_server(client, 0, 0, mtags,
-		              ":%s MODE %s %s%s %s %lld",
-		              client->id, channel->chname,
-		              isbounce ? "&" : "", modebuf, parabuf,
-		              (long long)sendts);
-	} else
-	if (samode && IsMe(client))
-	{
-		/* SAMODE is a special case: always send a TS of 0 (omitting TS==desync) */
-		sendto_server(client, 0, 0, mtags,
-		              ":%s MODE %s %s %s 0",
-		              client->id, channel->chname, modebuf, parabuf);
-	} else
-	{
-		sendto_server(client, 0, 0, mtags,
-		              ":%s MODE %s %s%s %s",
-		              client->id, channel->chname, isbounce ? "&" : "", modebuf, parabuf);
-		/* tell them it's not a timestamp, in case the last param
-		   ** is a number. */
-	}
+			client = &me;
+			sendts = 0;
+		}
 
-	if (MyConnect(client))
-		RunHook7(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
-	else
-		RunHook7(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode);
+		sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
+			       ":%s MODE %s %s %s",
+			       client->name, channel->name, modebuf, parabuf);
 
-	/* After this, don't touch 'channel' anymore! As permanent module may have destroyed the channel. */
+		if (IsServer(client) && sendts != -1)
+		{
+			sendto_server(client, 0, 0, mtags,
+				      ":%s MODE %s %s %s %lld",
+				      client->id, channel->name,
+				      modebuf, parabuf,
+				      (long long)sendts);
+		} else
+		if (samode && IsMe(client))
+		{
+			/* SAMODE is a special case: always send a TS of 0 (omitting TS==desync) */
+			sendto_server(client, 0, 0, mtags,
+				      ":%s MODE %s %s %s 0",
+				      client->id, channel->name,
+				      modebuf, parabuf);
+		} else
+		{
+			sendto_server(client, 0, 0, mtags,
+				      ":%s MODE %s %s %s",
+				      client->id, channel->name,
+				      modebuf, parabuf);
+			/* tell them it's not a timestamp, in case the last param is a number. */
+		}
+
+		if (MyConnect(client))
+			RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode, &should_destroy);
+		else
+			RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode, &should_destroy);
 
-	free_message_tags(mtags);
+		free_message_tags(mtags);
 
+		if (should_destroy)
+			break; /* eg channel went -P with nobody in it. 'channel' is freed now */
+	}
+	safe_free_multilinemode(m);
+	opermode = 0;
 }
+
 /* make_mode_str -- written by binary
  *	Reconstructs the mode string, to make it look clean.  mode_buf will
  *  contain the +x-y stuff, and the parabuf will contain the parameters.
- *  If bounce is set to 1, it will make the string it needs for a bounce.
  */
-void make_mode_str(Channel *channel, long oldm, Cmode_t oldem, long oldl, int pcount,
-    char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], char *mode_buf, char *para_buf,
-    size_t mode_buf_size, size_t para_buf_size, char bounce)
+MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, int pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
 {
-	char tmpbuf[MODEBUFLEN+3], *tmpstr;
-	CoreChannelModeTable *tab = &corechannelmodetable[0];
-	char *x = mode_buf;
-	int  what, cnt, z;
-	int i;
-	char *m;
-	what = 0;
+	Cmode *cm;
+	int what;
+	int cnt, z, i;
+	MultiLineMode *m = safe_alloc(sizeof(MultiLineMode));
+	int curr = 0;
+	int initial_len;
+
+	if (client->user)
+		initial_len = strlen(client->name) + strlen(client->user->username) + strlen(GetHost(client)) + strlen(channel->name) + 11;
+	else
+		initial_len = strlen(client->name) + strlen(channel->name) + 11;
 
-	*tmpbuf = '\0';
-	*mode_buf = '\0';
-	*para_buf = '\0';
+	/* Reserve room for the first element */
+	curr = 0;
+	m->modeline[curr] = safe_alloc(BUFSIZE);
+	m->paramline[curr] = safe_alloc(BUFSIZE);
+	m->numlines = curr+1;
 	what = 0;
-	/* + param-less modes */
-	tab = &corechannelmodetable[0];
-	while (tab->mode != 0x0)
-	{
-		if (channel->mode.mode & tab->mode)
-		{
-			if (!(oldm & tab->mode))
-			{
-				if (what != MODE_ADD)
-				{
-					*x++ = bounce ? '-' : '+';
-					what = MODE_ADD;
-				}
-				*x++ = tab->flag;
-			}
-		}
-		tab++;
-	}
 
-	/* + paramless extmodes... */
-	for (i=0; i <= Channelmode_highest; i++)
+	/* The first element will be filled with all paramless modes.
+	 * That is: both the ones that got set, and the ones that got unset.
+	 * This will always fit.
+	 */
+
+	/* Which paramless modes got set? Eg +snt */
+	for (cm=channelmodes; cm; cm = cm->next)
 	{
-		if (!Channelmode_Table[i].flag || Channelmode_Table[i].paracount)
+		if (!cm->letter || cm->paracount)
 			continue;
 		/* have it now and didn't have it before? */
-		if ((channel->mode.extmode & Channelmode_Table[i].mode) &&
-		    !(oldem & Channelmode_Table[i].mode))
+		if ((channel->mode.mode & cm->mode) &&
+		    !(oldem & cm->mode))
 		{
 			if (what != MODE_ADD)
 			{
-				*x++ = bounce ? '-' : '+';
+				strlcat_letter(m->modeline[curr], '+', BUFSIZE);
 				what = MODE_ADD;
 			}
-			*x++ = Channelmode_Table[i].flag;
+			strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
 		}
 	}
 
-	*x = '\0';
-	/* - param-less modes */
-	tab = &corechannelmodetable[0];
-	while (tab->mode != 0x0)
+	/* Which paramless modes got unset? Eg -r */
+	for (cm=channelmodes; cm; cm = cm->next)
 	{
-		if (!(channel->mode.mode & tab->mode))
-		{
-			if (oldm & tab->mode)
-			{
-				if (what != MODE_DEL)
-				{
-					*x++ = bounce ? '+' : '-';
-					what = MODE_DEL;
-				}
-				*x++ = tab->flag;
-			}
-		}
-		tab++;
-	}
-
-	/* - extmodes (both "param modes" and paramless don't have
-	 * any params when unsetting... well, except one special type, that is (we skip those here)
-	 */
-	for (i=0; i <= Channelmode_highest; i++)
-	{
-		if (!Channelmode_Table[i].flag || Channelmode_Table[i].unset_with_param)
+		if (!cm->letter || cm->unset_with_param)
 			continue;
 		/* don't have it now and did have it before */
-		if (!(channel->mode.extmode & Channelmode_Table[i].mode) &&
-		    (oldem & Channelmode_Table[i].mode))
+		if (!(channel->mode.mode & cm->mode) && (oldem & cm->mode))
 		{
 			if (what != MODE_DEL)
 			{
-				*x++ = bounce ? '+' : '-';
+				strlcat_letter(m->modeline[curr], '-', BUFSIZE);
 				what = MODE_DEL;
 			}
-			*x++ = Channelmode_Table[i].flag;
+			strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
 		}
 	}
 
-	*x = '\0';
-	/* user limit */
-	if (channel->mode.limit != oldl)
+	/* Now for parameter modes we do both addition and removal. Eg +b-e ban!x@y exempt!z@z */
+	for (cnt = 0; cnt < pcount; cnt++)
 	{
-		if ((!bounce && channel->mode.limit == 0) ||
-		    (bounce && channel->mode.limit != 0))
+		if ((strlen(m->modeline[curr]) + strlen(m->paramline[curr]) + strlen(&pvar[cnt][2])) > 507)
 		{
-			if (what != MODE_DEL)
+			if (curr == MAXMULTILINEMODES)
 			{
-				*x++ = '-';
-				what = MODE_DEL;
-			}
-			if (bounce)
-				channel->mode.limit = 0;	/* set it back */
-			*x++ = 'l';
-		}
-		else
-		{
-			if (what != MODE_ADD)
-			{
-				*x++ = '+';
-				what = MODE_ADD;
+				/* Should be impossible.. */
+				unreal_log(ULOG_ERROR, "mode", "MODE_MULTINE_EXCEEDED", client,
+				           "A mode string caused an avalanche effect of more than $max_multiline modes "
+				           "in channel $channel. Caused by client $client. Expect a desync.",
+				           log_data_integer("max_multiline_modes", MAXMULTILINEMODES),
+				           log_data_channel("channel", channel));
+				break;
 			}
-			*x++ = 'l';
-			if (bounce)
-				channel->mode.limit = oldl;	/* set it back */
-			ircsnprintf(para_buf, para_buf_size, "%s%d ", para_buf, channel->mode.limit);
+			curr++;
+			m->modeline[curr] = safe_alloc(BUFSIZE);
+			m->paramline[curr] = safe_alloc(BUFSIZE);
+			m->numlines = curr+1;
+			what = 0;
 		}
-	}
-	/* reconstruct bkov chain */
-	for (cnt = 0; cnt < pcount; cnt++)
-	{
 		if ((*(pvar[cnt]) == '+') && what != MODE_ADD)
 		{
-			*x++ = bounce ? '-' : '+';
+			strlcat_letter(m->modeline[curr], '+', BUFSIZE);
 			what = MODE_ADD;
 		}
 		if ((*(pvar[cnt]) == '-') && what != MODE_DEL)
 		{
-			*x++ = bounce ? '+' : '-';
+			strlcat_letter(m->modeline[curr], '-', BUFSIZE);
 			what = MODE_DEL;
 		}
-		*x++ = *(pvar[cnt] + 1);
-		tmpstr = &pvar[cnt][2];
-		z = (MODEBUFLEN * MAXMODEPARAMS);
-		m = para_buf;
-		while ((*m)) { m++; }
-		while ((*tmpstr) && ((m-para_buf) < z))
-		{
-			*m = *tmpstr;
-			m++;
-			tmpstr++;
-		}
-		*m++ = ' ';
-		*m = '\0';
+		strlcat_letter(m->modeline[curr], *(pvar[cnt] + 1), BUFSIZE);
+		strlcat(m->paramline[curr], &pvar[cnt][2], BUFSIZE);
+		strlcat_letter(m->paramline[curr], ' ', BUFSIZE);
 	}
-	if (bounce)
+
+	for (i = 0; i <= curr; i++)
 	{
-		channel->mode.mode = oldm;
-		channel->mode.extmode = oldem;
+		char *para_buf = m->paramline[i];
+		/* Strip off useless space character (' ') at the end, if there is any */
+		z = strlen(para_buf);
+		if ((z > 0) && (para_buf[z - 1] == ' '))
+			para_buf[z - 1] = '\0';
 	}
-	z = strlen(para_buf);
-	if ((z > 0) && (para_buf[z - 1] == ' '))
-		para_buf[z - 1] = '\0';
-	*x = '\0';
-	if (*mode_buf == '\0')
+
+	/* Now check for completely empty mode: */
+	if ((curr == 0) && empty_mode(m->modeline[0]))
 	{
-		*mode_buf = '+';
-		mode_buf++;
-		*mode_buf = '\0';
-		/* Don't send empty lines. */
+		/* And convert it to a NULL result */
+		safe_free_multilinemode(m);
+		return NULL;
 	}
-	return;
-}
-
-
-/* do_mode_char
- *  processes one mode character
- *  returns 1 if it ate up a param, otherwise 0
- *	written by binary
- *  modified for Unreal by stskeeps..
- */
 
-#define REQUIRE_PARAMETER() { if (!param || *pcount >= MAXMODEPARAMS) { retval = 0; break; } retval = 1; }
-
-#ifdef PREFIX_AQ
-#define is_xchanop(x) ((x & (CHFL_CHANOP|CHFL_CHANADMIN|CHFL_CHANOWNER)))
-#else
-#define is_xchanop(x) ((x & CHFL_CHANOP))
-#endif
+	return m;
+}
 
-int  do_mode_char(Channel *channel, long modetype, char modechar, char *param,
-                  u_int what, Client *client,
-                  u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3],
-                  char bounce, long my_access)
+const char *mode_ban_handler(Client *client, Channel *channel, const char *param, int what, int extbtype, Ban **banlist)
 {
-	CoreChannelModeTable *tab = &corechannelmodetable[0];
-	int  retval = 0;
-	Member *member = NULL;
-	Membership *membership = NULL;
-	Client *target;
-	unsigned int tmp = 0;
-	char tmpbuf[512], *tmpstr;
-	char tc = ' ';		/* */
-	int  chasing = 0, x;
-	Hook *h;
+	const char *tmpstr;
+	BanContext *b;
 
-	if ((my_access & CHFL_HALFOP) && !is_xchanop(my_access) && !IsULine(client) &&
-	    !op_can_override("channel:override:mode",client,channel,&modetype) && !samode_in_progress)
+	tmpstr = clean_ban_mask(param, what, client, 0);
+	if (BadPtr(tmpstr))
 	{
-		if (MyUser(client) && (modetype == MODE_HALFOP) && (what == MODE_DEL) &&
-		    param && (find_client(param, NULL) == client))
+		/* Invalid ban. See if we can send an error about that (only for extbans) */
+		if (MyUser(client) && is_extended_ban(param))
 		{
-			/* halfop -h'ing him/herself */
-			/* ALLOW */
-		} else
+			const char *nextbanstr;
+			Extban *extban = findmod_by_bantype(param, &nextbanstr);
+			BanContext *b;
+
+			b = safe_alloc(sizeof(BanContext));
+			b->client = client;
+			b->channel = channel;
+			b->banstr = nextbanstr;
+			b->is_ok_check = EXBCHK_PARAM;
+			b->what = what;
+			b->ban_type = extbtype;
+			if (extban && extban->is_ok)
+				extban->is_ok(b);
+			safe_free(b);
+		}
+
+		return NULL;
+	}
+	if (MyUser(client) && is_extended_ban(param))
+	{
+		/* extban: check access if needed */
+		const char *nextbanstr;
+		Extban *extban = findmod_by_bantype(tmpstr, &nextbanstr);
+		if (extban)
 		{
-			/* Ugly halfop hack --sts
-			   - this allows halfops to do +b +e +v and so on */
-			/* (Syzop/20040413: Allow remote halfop modes */
-			if ((Halfop_mode(modetype) == FALSE) && MyUser(client))
+			if ((extbtype == EXBTYPE_INVEX) && !(extban->options & EXTBOPT_INVEX))
+				return NULL; /* this extended ban type does not support INVEX */
+			if (extban->is_ok)
 			{
-				int eaten = 0;
-				while (tab->mode != 0x0)
+				BanContext *b = safe_alloc(sizeof(BanContext));
+				b->client = client;
+				b->channel = channel;
+				b->what = what;
+				b->ban_type = extbtype;
+
+				b->is_ok_check = EXBCHK_ACCESS;
+				b->banstr = nextbanstr;
+				if (!extban->is_ok(b))
 				{
-					if (tab->mode == modetype)
+					if (ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
 					{
-						sendnumeric(client, ERR_NOTFORHALFOPS, tab->flag);
-						eaten = tab->parameters;
-						break;
+						/* TODO: send operoverride notice */
+					} else {
+						b->banstr = nextbanstr;
+						b->is_ok_check = EXBCHK_ACCESS_ERR;
+						extban->is_ok(b);
+						safe_free(b);
+						return NULL;
 					}
-					tab++;
 				}
-				return eaten;
-			}
-		} /* not -h self */
-	}
-	switch (modetype)
-	{
-		case MODE_RGSTR:
-			if (!IsServer(client) && !IsULine(client))
-			{
-				sendnumeric(client, ERR_ONLYSERVERSCANCHANGE, channel->chname);
-				break;
-			}
-			goto setmode;
-		case MODE_SECRET:
-		case MODE_PRIVATE:
-		case MODE_MODERATED:
-		case MODE_TOPICLIMIT:
-		case MODE_NOPRIVMSGS:
-		case MODE_INVITEONLY:
-			goto setmode;
-		setmode:
-			retval = 0;
-			if (what == MODE_ADD) {
-				/* +sp bugfix.. (by JK/Luke)*/
-		 	 if ((modetype == MODE_SECRET) && (channel->mode.mode & MODE_PRIVATE))
-					channel->mode.mode &= ~MODE_PRIVATE;
-				if ((modetype == MODE_PRIVATE) && (channel->mode.mode & MODE_SECRET))
-					channel->mode.mode &= ~MODE_SECRET;
-				channel->mode.mode |= modetype;
-			}
-			else
-			{
-				channel->mode.mode &= ~modetype;
-				RunHook2(HOOKTYPE_MODECHAR_DEL, channel, (int)modechar);
-			}
-			break;
-
-/* do pro-opping here (popping) */
-		case MODE_CHANOWNER:
-			REQUIRE_PARAMETER()
-			if (!IsULine(client) && !IsServer(client) && !is_chanowner(client, channel) && !samode_in_progress)
-			{
-					if (MyUser(client) && !op_can_override("channel:override:mode",client,channel,&modetype))
-					{
-						sendnumeric(client, ERR_CHANOWNPRIVNEEDED, channel->chname);
-						break;
-					}
-					if (!is_half_op(client, channel)) /* htrig will take care of halfop override notices */
-						opermode = 1;
-			}
-			goto process_listmode;
-		case MODE_CHANADMIN:
-			REQUIRE_PARAMETER()
-			/* not uline, not server, not chanowner, not an samode, not -a'ing yourself... */
-			if (!IsULine(client) && !IsServer(client) && !is_chanowner(client, channel) && !samode_in_progress &&
-			    !(param && (what == MODE_DEL) && (find_client(param, NULL) == client)))
-			{
-					if (MyUser(client) && !op_can_override("channel:override:mode",client,channel,&modetype))
-					{
-						sendnumeric(client, ERR_CHANOWNPRIVNEEDED, channel->chname);
-						break;
-					}
-					if (!is_half_op(client, channel)) /* htrig will take care of halfop override notices */
-						opermode = 1;
-			}
-			goto process_listmode;
-
-		case MODE_HALFOP:
-		case MODE_CHANOP:
-		case MODE_VOICE:
-			REQUIRE_PARAMETER()
-process_listmode:
-			if (!(target = find_chasing(client, param, &chasing)))
-				break;
-			if (!target->user)
-				break;
-			if (!(membership = find_membership_link(target->user->channel, channel)))
-			{
-				sendnumeric(client, ERR_USERNOTINCHANNEL, target->name, channel->chname);
-				break;
-			}
-			member = find_member_link(channel->members, target);
-			if (!member)
-			{
-				/* should never happen */
-				sendto_realops("crap! find_membership_link && !find_member_link !!. Report to unreal team");
-				break;
-			}
-			/* we make the rules, we bend the rules */
-			if (IsServer(client) || IsULine(client))
-				goto breaktherules;
-
-			if (what == MODE_DEL)
-			{
-				int ret = EX_ALLOW;
-				char *badmode = NULL;
-
-				for (h = Hooks[HOOKTYPE_MODE_DEOP]; h; h = h->next)
-				{
-					int n = (*(h->func.intfunc))(client, member->client, channel, what, modechar, my_access, &badmode);
-					if (n == EX_DENY)
-						ret = n;
-				else if (n == EX_ALWAYS_DENY)
+				b->banstr = nextbanstr;
+				b->is_ok_check = EXBCHK_PARAM;
+				if (!extban->is_ok(b))
 				{
-					ret = n;
-					break;
-				}
+					safe_free(b);
+					return NULL;
 				}
+				safe_free(b);
+			}
+		}
+	}
 
-				if (ret == EX_ALWAYS_DENY)
-				{
-					if (MyUser(client) && badmode)
-						sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
+	if ( (what == MODE_ADD && add_listmode(banlist, client, channel, tmpstr)) ||
+	     (what == MODE_DEL && del_listmode(banlist, channel, tmpstr)))
+	{
+		return NULL;	/* already exists */
+	}
 
-				if (MyUser(client))
-					break; /* stop processing this mode */
-				}
+	return tmpstr;
+}
 
-				/* This probably should work but is completely untested (the operoverride stuff, I mean): */
-				if (ret == EX_DENY)
-				{
-					if (!op_can_override("channel:override:mode:del",client,channel,&modetype))
-					{
-						if (badmode)
-							sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
-						break; /* stop processing this mode */
-					} else {
-						opermode = 1;
-					}
-				}
-			}
+/** Write the result of a mode change.
+ * This is used by do_mode_char_list_mode(), do_mode_char_member_mode()
+ * and do_extmode_char().
+ * The result is later used by make_mode_str() to create the
+ * actual MODE line to be broadcasted to the channel and other servers.
+ */
+void do_mode_char_write(char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], u_int *pcount, u_int what, char modeletter, const char *str)
+{
+	/* Caller should have made sure there was room! */
+	if (*pcount >= MAXMODEPARAMS)
+#ifdef DEBUGMODE
+		abort();
+#else
+		return;
+#endif
 
-			/* This check not only prevents unprivileged users from doing a -q on chanowners,
-			 * it also protects against -o/-h/-v on them.
-			 */
-			if (is_chanowner(member->client, channel)
-			    && member->client != client
-			    && !is_chanowner(client, channel) && !IsServer(client)
-			    && !IsULine(client) && !opermode && !samode_in_progress && (what == MODE_DEL))
-			{
-				if (MyUser(client))
-				{
-					/* Need this !op_can_override() here again, even with the !opermode
-					 * check a few lines up, all due to halfops. -- Syzop
-					 */
-					if (!op_can_override("channel:override:mode:del",client,channel,&modetype))
-					{
-						char errbuf[NICKLEN+30];
-						ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel owner", member->client->name);
-						sendnumeric(client, ERR_CANNOTCHANGECHANMODE, modechar, errbuf);
-						break;
-					}
-				} else {
-					if (IsOper(client))
-						opermode = 1;
-				}
-			}
+	ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
+	            "%c%c%s",
+	            (what == MODE_ADD) ? '+' : '-',
+	            modeletter,
+	            str);
+	(*pcount)++;
+}
 
-			/* This check not only prevents unprivileged users from doing a -a on chanadmins,
-			 * it also protects against -o/-h/-v on them.
-			 */
-			if (is_chanadmin(member->client, channel)
-			    && member->client != client
-			    && !is_chanowner(client, channel) && !IsServer(client) && !opermode && !samode_in_progress
-			    && modetype != MODE_CHANOWNER && (what == MODE_DEL))
-			{
-				if (MyUser(client))
-				{
-					/* Need this !op_can_override() here again, even with the !opermode
-					 * check a few lines up, all due to halfops. -- Syzop
-					 */
-					if (!op_can_override("channel:override:mode:del",client,channel,&modetype))
-					{
-						char errbuf[NICKLEN+30];
-						ircsnprintf(errbuf, sizeof(errbuf), "%s is a channel admin", member->client->name);
-						sendnumeric(client, ERR_CANNOTCHANGECHANMODE, modechar, errbuf);
-						break;
-					}
-				} else {
-					if (IsOper(client))
-						opermode = 1;
-				}
-			}
-		breaktherules:
-			tmp = member->flags;
-			if (what == MODE_ADD)
-				member->flags |= modetype;
-			else
-				member->flags &= ~modetype;
-			if ((tmp == member->flags) && (bounce || !IsULine(client)))
-				break;
-			/* It's easier to undo the mode here instead of later
-			 * when you call make_mode_str for a bounce string.
-			 * Why set it if it will be instantly removed?
-			 * Besides, pvar keeps a log of it. */
-			if (bounce)
-				member->flags = tmp;
-			if (modetype == MODE_CHANOWNER)
-				tc = 'q';
-			if (modetype == MODE_CHANADMIN)
-				tc = 'a';
-			if (modetype == MODE_CHANOP)
-				tc = 'o';
-			if (modetype == MODE_HALFOP)
-				tc = 'h';
-			if (modetype == MODE_VOICE)
-				tc = 'v';
-			/* Make sure membership->flags and member->flags is the same */
-			membership->flags = member->flags;
-			ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
-			            "%c%c%s",
-			            (what == MODE_ADD) ? '+' : '-', tc, target->name);
-			(*pcount)++;
-			break;
-		case MODE_LIMIT:
-			if (what == MODE_ADD)
-			{
-				int v;
-				REQUIRE_PARAMETER()
-				v = atoi(param);
-				if (v < 0)
-					v = 1; /* setting +l with a negative number makes no sense */
-				if (v > 1000000000)
-					v = 1000000000; /* some kind of limit, 1 billion (mrah...) */
-				if (channel->mode.limit == v)
-					break;
-				channel->mode.limit = v;
-			}
-			else
-			{
-				retval = 0;
-				if (!channel->mode.limit)
-					break;
-				channel->mode.limit = 0;
-				RunHook2(HOOKTYPE_MODECHAR_DEL, channel, (int)modechar);
-			}
-			break;
-		case MODE_KEY:
-			REQUIRE_PARAMETER()
-			for (x = 0; x < *pcount; x++)
-			{
-				if (pvar[x][1] == 'k')
-				{	/* don't allow user to change key
-				 * more than once per command. */
-					retval = 0;
-					break;
-				}
-			}
-			if (retval == 0)	/* you can't break a case from loop */
-				break;
-			if (what == MODE_ADD)
-			{
-				if (!bounce) {	/* don't do the mode at all. */
-					char *tmp;
-					if ((tmp = strchr(param, ' ')))
-					*tmp = '\0';
-					if ((tmp = strchr(param, ':')))
-					*tmp = '\0';
-					if ((tmp = strchr(param, ',')))
-					*tmp = '\0';
-					if (*param == '\0')
-					break;
-					if (strlen(param) > KEYLEN)
-						param[KEYLEN] = '\0';
-					if (!strcmp(channel->mode.key, param))
-					break;
-					strlcpy(channel->mode.key, param, sizeof(channel->mode.key));
-				}
-				tmpstr = param;
-			}
-			else
-			{
-				if (!*channel->mode.key)
-					break;	/* no change */
-				strlcpy(tmpbuf, channel->mode.key, sizeof(tmpbuf));
-				tmpstr = tmpbuf;
-				if (!bounce)
-					strcpy(channel->mode.key, "");
-				RunHook2(HOOKTYPE_MODECHAR_DEL, channel, (int)modechar);
-			}
-			retval = 1;
+int do_mode_char_list_mode(Channel *channel, long modetype, char modechar, const char *param,
+                           u_int what, Client *client,
+                           u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
+{
+	const char *tmpstr;
 
-			ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
-			            "%ck%s",
-			            (what == MODE_ADD) ? '+' : '-', tmpstr);
-			(*pcount)++;
-			break;
+	/* Check if there is a parameter present */
+	if (!param || *pcount >= MAXMODEPARAMS)
+		return 0;
 
+	switch (modetype)
+	{
 		case MODE_BAN:
-			REQUIRE_PARAMETER()
-			retval = 1;
-			tmpstr = clean_ban_mask(param, what, client);
-			if (BadPtr(tmpstr))
-			{
-				/* Invalid ban. See if we can send an error about that (only for extbans) */
-				if (MyUser(client) && !bounce && is_extended_ban(param))
-				{
-					Extban *p = findmod_by_bantype(param[1]);
-					if (p && p->is_ok)
-						p->is_ok(client, channel, param, EXBCHK_PARAM, what, EXBTYPE_BAN);
-				}
-
-				break; /* ignore ban, but eat param */
-			}
-			if (MyUser(client) && !bounce && is_extended_ban(param))
-			{
-				/* extban: check access if needed */
-				Extban *p = findmod_by_bantype(tmpstr[1]);
-				if (p && p->is_ok)
-				{
-					if (!p->is_ok(client, channel, tmpstr, EXBCHK_ACCESS, what, EXBTYPE_BAN))
-					{
-						if (ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
-						{
-							/* TODO: send operoverride notice */
-						} else {
-							p->is_ok(client, channel, tmpstr, EXBCHK_ACCESS_ERR, what, EXBTYPE_BAN);
-							break;
-						}
-					}
-					if (!p->is_ok(client, channel, tmpstr, EXBCHK_PARAM, what, EXBTYPE_BAN))
-						break;
-				}
-			}
-			/* For bounce, we don't really need to worry whether
-			 * or not it exists on our server.  We'll just always
-			 * bounce it. */
-			if (!bounce &&
-			    ((what == MODE_ADD && add_listmode(&channel->banlist, client, channel, tmpstr))
-			    || (what == MODE_DEL && del_listmode(&channel->banlist, channel, tmpstr))))
-			{
-				break;	/* already exists */
-			}
-			ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
-			            "%cb%s",
-			            (what == MODE_ADD) ? '+' : '-', tmpstr);
-			(*pcount)++;
+			if (!(tmpstr = mode_ban_handler(client, channel, param, what, EXBTYPE_BAN, &channel->banlist)))
+				break; /* rejected or duplicate */
+			do_mode_char_write(pvar, pcount, what, modechar, tmpstr);
 			break;
 		case MODE_EXCEPT:
-			REQUIRE_PARAMETER()
-			tmpstr = clean_ban_mask(param, what, client);
-			if (BadPtr(tmpstr))
-			{
-				/* Invalid except. See if we can send an error about that (only for extbans) */
-				if (MyUser(client) && !bounce && is_extended_ban(param))
-				{
-					Extban *p = findmod_by_bantype(param[1]);
-					if (p && p->is_ok)
-						p->is_ok(client, channel, param, EXBCHK_PARAM, what, EXBTYPE_EXCEPT);
-				}
-
-				break; /* ignore except, but eat param */
-			}
-			if (MyUser(client) && !bounce && is_extended_ban(param))
-			{
-				/* extban: check access if needed */
-				Extban *p = findmod_by_bantype(tmpstr[1]);
-				if (p && p->is_ok)
-				{
-					if (!p->is_ok(client, channel, tmpstr, EXBCHK_ACCESS, what, EXBTYPE_EXCEPT))
-					{
-						if (ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
-						{
-							/* TODO: send operoverride notice */
-						} else {
-							p->is_ok(client, channel, tmpstr, EXBCHK_ACCESS_ERR, what, EXBTYPE_EXCEPT);
-							break;
-						}
-					}
-					if (!p->is_ok(client, channel, tmpstr, EXBCHK_PARAM, what, EXBTYPE_EXCEPT))
-						break;
-				}
-			}
-			/* For bounce, we don't really need to worry whether
-			 * or not it exists on our server.  We'll just always
-			 * bounce it. */
-			if (!bounce &&
-			    ((what == MODE_ADD && add_listmode(&channel->exlist, client, channel, tmpstr))
-			    || (what == MODE_DEL && del_listmode(&channel->exlist, channel, tmpstr))))
-			{
-				break;	/* already exists */
-			}
-			ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
-			            "%ce%s",
-			            (what == MODE_ADD) ? '+' : '-', tmpstr);
-			(*pcount)++;
+			if (!(tmpstr = mode_ban_handler(client, channel, param, what, EXBTYPE_EXCEPT, &channel->exlist)))
+				break; /* rejected or duplicate */
+			do_mode_char_write(pvar, pcount, what, modechar, tmpstr);
 			break;
 		case MODE_INVEX:
-			REQUIRE_PARAMETER()
-			tmpstr = clean_ban_mask(param, what, client);
-			if (BadPtr(tmpstr))
-			{
-				/* Invalid invex. See if we can send an error about that (only for extbans) */
-				if (MyUser(client) && !bounce && is_extended_ban(param))
-				{
-					Extban *p = findmod_by_bantype(param[1]);
-					if (p && p->is_ok)
-						p->is_ok(client, channel, param, EXBCHK_PARAM, what, EXBTYPE_INVEX);
-				}
-
-				break; /* ignore invex, but eat param */
-			}
-			if (MyUser(client) && !bounce && is_extended_ban(param))
-			{
-				/* extban: check access if needed */
-				Extban *p = findmod_by_bantype(tmpstr[1]);
-				if (p)
-				{
-					if (!(p->options & EXTBOPT_INVEX))
-						break; /* this extended ban type does not support INVEX */
-					if (p->is_ok && !p->is_ok(client, channel, tmpstr, EXBCHK_ACCESS, what, EXBTYPE_INVEX))
-					{
-						if (ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
-						{
-							/* TODO: send operoverride notice */
-						} else {
-							p->is_ok(client, channel, tmpstr, EXBCHK_ACCESS_ERR, what, EXBTYPE_INVEX);
-							break;
-						}
-					}
-					if (p->is_ok && !p->is_ok(client, channel, tmpstr, EXBCHK_PARAM, what, EXBTYPE_INVEX))
-						break;
-				}
-			}
-			/* For bounce, we don't really need to worry whether
-			 * or not it exists on our server.  We'll just always
-			 * bounce it. */
-			if (!bounce &&
-			    ((what == MODE_ADD && add_listmode(&channel->invexlist, client, channel, tmpstr))
-			    || (what == MODE_DEL && del_listmode(&channel->invexlist, channel, tmpstr))))
-			{
-				break;	/* already exists */
-			}
-			ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
-			            "%cI%s",
-			            (what == MODE_ADD) ? '+' : '-', tmpstr);
-			(*pcount)++;
+			if (!(tmpstr = mode_ban_handler(client, channel, param, what, EXBTYPE_INVEX, &channel->invexlist)))
+				break; /* rejected or duplicate */
+			do_mode_char_write(pvar, pcount, what, modechar, tmpstr);
 			break;
 	}
-	return retval;
+	return 1;
 }
 
 /** Check access and if granted, set the extended chanmode to the requested value in memory.
-  * note: if bounce is requested then the mode will not be set.
   * @returns amount of params eaten (0 or 1)
   */
-int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
-                    Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3],
-                    char bounce)
+int do_extmode_char(Channel *channel, Cmode *handler, const char *param, u_int what,
+                    Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
 {
 	int paracnt = (what == MODE_ADD) ? handler->paracount : 0;
-	char mode = handler->flag;
+	char mode = handler->letter;
 	int x;
-	char *morphed;
+	const char *morphed;
 
 	if ((what == MODE_DEL) && handler->unset_with_param)
 		paracnt = 1; /* there's always an exception! */
@@ -1244,7 +697,7 @@ int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
 			handler->is_ok(client, channel, mode, param, EXCHK_ACCESS_ERR, what);
 			return paracnt; /* Denied & error msg sent */
 		}
-		if (x == EX_DENY)
+		if ((x == EX_DENY) && !samode_in_progress)
 			opermode = 1; /* override in progress... */
 	} else {
 		/* remote user: we only need to check if we need to generate an operoverride msg */
@@ -1255,10 +708,16 @@ int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
 		}
 	}
 
+	if (handler->type == CMODE_MEMBER)
+	{
+		do_mode_char_member_mode_new(channel, handler, param, what, client, pcount, pvar);
+		return 1;
+	}
+
 	/* Check for multiple changes in 1 command (like +y-y+y 1 2, or +yy 1 2). */
 	for (x = 0; x < *pcount; x++)
 	{
-		if (pvar[x][1] == handler->flag)
+		if (pvar[x][1] == handler->letter)
 		{
 			/* this is different than the old chanmode system, coz:
 			 * "mode #chan +kkL #a #b #c" will get "+kL #a #b" which is wrong :p.
@@ -1273,16 +732,14 @@ int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
 	{
 		if (what == MODE_DEL)
 		{
-			if (!(channel->mode.extmode & handler->mode))
+			if (!(channel->mode.mode & handler->mode))
 				return paracnt; /* There's nothing to remove! */
 			if (handler->unset_with_param)
 			{
 				/* Special extended channel mode requiring a parameter on unset.
 				 * Any provided parameter is ok, the current one (that is set) will be used.
 				 */
-				ircsnprintf(pvar[*pcount], MODEBUFLEN + 3, "-%c%s",
-					handler->flag, cm_getparameter(channel, handler->flag));
-				(*pcount)++;
+				do_mode_char_write(pvar, pcount, what, handler->letter, cm_getparameter(channel, handler->letter));
 			} else {
 				/* Normal extended channel mode: deleting is just -X, no parameter.
 				 * Nothing needs to be done here.
@@ -1298,40 +755,92 @@ int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
 				return paracnt; /* rejected by conv_param */
 
 			/* is it already set at the same value? if so, ignore it. */
-			if (channel->mode.extmode & handler->mode)
+			if (channel->mode.mode & handler->mode)
 			{
-				char *now, *requested;
-				char flag = handler->flag;
+				const char *now, *requested;
+				char flag = handler->letter;
 				now = cm_getparameter(channel, flag);
 				requested = handler->conv_param(param, client, channel);
 				if (now && requested && !strcmp(now, requested))
 					return paracnt; /* ignore... */
 			}
-			ircsnprintf(pvar[*pcount], MODEBUFLEN + 3, "+%c%s",
-				handler->flag, handler->conv_param(param, client, channel));
-			(*pcount)++;
+			do_mode_char_write(pvar, pcount, what, handler->letter, handler->conv_param(param, client, channel));
 			param = morphed; /* set param to converted parameter. */
 		}
 	}
 
-	if (bounce) /* bounce here means: only check access and return return value */
-		return paracnt;
-
 	if (what == MODE_ADD)
 	{	/* + */
-		channel->mode.extmode |= handler->mode;
+		channel->mode.mode |= handler->mode;
 		if (handler->paracount)
-			cm_putparameter(channel, handler->flag, param);
+			cm_putparameter(channel, handler->letter, param);
+		RunHook(HOOKTYPE_MODECHAR_ADD, channel, (int)mode);
 	} else
 	{	/* - */
-		channel->mode.extmode &= ~(handler->mode);
-		RunHook2(HOOKTYPE_MODECHAR_DEL, channel, (int)mode);
+		channel->mode.mode &= ~(handler->mode);
+		RunHook(HOOKTYPE_MODECHAR_DEL, channel, (int)mode);
 		if (handler->paracount)
-			cm_freeparameter(channel, handler->flag);
+			cm_freeparameter(channel, handler->letter);
 	}
 	return paracnt;
 }
 
+/** Set or unset a mode on a member (eg +vhoaq/-vhoaq) */
+void do_mode_char_member_mode_new(Channel *channel, Cmode *handler, const char *param, u_int what,
+                    Client *client, u_int *pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
+{
+	Member *member = NULL;
+	Membership *membership = NULL;
+	Client *target;
+	int chasing = 0;
+	Hook *h;
+	char c[2];
+	char modechar = handler->letter;
+
+	if (!(target = find_chasing(client, param, &chasing)))
+		return;
+
+	if (!target->user)
+		return;
+
+	if (!(membership = find_membership_link(target->user->channel, channel)))
+	{
+		sendnumeric(client, ERR_USERNOTINCHANNEL, target->name, channel->name);
+		return;
+	}
+	member = find_member_link(channel->members, target);
+	if (!member)
+	{
+		/* should never happen */
+		unreal_log(ULOG_ERROR, "mode", "BUG_FIND_MEMBER_LINK_FAILED", target,
+			   "[BUG] Client $target.details on channel $channel: "
+			   "found via find_membership_link() but NOT found via find_member_link(). "
+			   "This should never happen! Please report on https://bugs.unrealircd.org/",
+			   log_data_channel("channel", channel));
+		return;
+	}
+
+	if ((what == MODE_ADD) && strchr(member->member_modes, modechar))
+		return; /* already set */
+	if ((what == MODE_DEL) && !strchr(member->member_modes, modechar))
+		return; /* already unset */
+
+	if (what == MODE_ADD)
+	{
+		if (strchr(member->member_modes, modechar))
+			return; /* already set */
+		/* Set the mode */
+		add_member_mode_fast(member, membership, modechar);
+	} else {
+		if (!strchr(member->member_modes, modechar))
+			return; /* already unset */
+		del_member_mode_fast(member, membership, modechar);
+	}
+
+	/* And write out the mode */
+	do_mode_char_write(pvar, pcount, what, modechar, target->name);
+}
+
 /** In 2003 I introduced PROTOCTL CHANMODES= so remote servers (and services)
  * could deal with unknown "parameter eating" channel modes, minimizing desyncs.
  * Now, in 2015, I finally added the code to deal with this. -- Syzop
@@ -1341,27 +850,27 @@ int paracount_for_chanmode_from_server(Client *client, u_int what, char mode)
 	if (MyUser(client))
 		return 0; /* no server, we have no idea, assume 0 paracount */
 
-	if (!client->serv)
+	if (!client->server)
 	{
 		/* If it's from a remote client then figure out from which "uplink" we
 		 * received this MODE. The uplink is the directly-connected-server to us
 		 * and may differ from the server the user is actually on. This is correct.
 		 */
-		if (!client->direction || !client->direction->serv)
+		if (!client->direction || !client->direction->server)
 			return 0;
 		client = client->direction;
 	}
 
-	if (client->serv->features.chanmodes[0] && strchr(client->serv->features.chanmodes[0], mode))
+	if (client->server->features.chanmodes[0] && strchr(client->server->features.chanmodes[0], mode))
 		return 1; /* 1 parameter for set, 1 parameter for unset */
 
-	if (client->serv->features.chanmodes[1] && strchr(client->serv->features.chanmodes[1], mode))
+	if (client->server->features.chanmodes[1] && strchr(client->server->features.chanmodes[1], mode))
 		return 1; /* 1 parameter for set, 1 parameter for unset */
 
-	if (client->serv->features.chanmodes[2] && strchr(client->serv->features.chanmodes[2], mode))
+	if (client->server->features.chanmodes[2] && strchr(client->server->features.chanmodes[2], mode))
 		return (what == MODE_ADD) ? 1 : 0; /* 1 parameter for set, no parameter for unset */
 
-	if (client->serv->features.chanmodes[3] && strchr(client->serv->features.chanmodes[3], mode))
+	if (client->server->features.chanmodes[3] && strchr(client->server->features.chanmodes[3], mode))
 		return 0; /* no parameter for set, no parameter for unset */
 
 	if (mode == '&')
@@ -1374,10 +883,10 @@ int paracount_for_chanmode_from_server(Client *client, u_int what, char mode)
 	 * channel mode. That's actually pretty bad. This shouldn't happen since CHANMODES=
 	 * is sent since 2003 and the (often also required) EAUTH PROTOCTL is in there since 2010.
 	 */
-	sendto_realops("Unknown channel mode %c%c from server %s!",
-		(what == MODE_ADD) ? '+' : '-',
-		mode,
-		client->name);
+	unreal_log(ULOG_WARNING, "mode", "REMOTE_UNKNOWN_CHANNEL_MODE", client,
+	           "Server $client sent us an unknown channel mode $what$mode_character!",
+	           log_data_string("what", ((what == MODE_ADD) ? "+" : "-")),
+	           log_data_char("mode_character", mode));
 
 	return 0;
 }
@@ -1388,30 +897,30 @@ int paracount_for_chanmode_from_server(Client *client, u_int what, char mode)
  */
 int paracount_for_chanmode(u_int what, char mode)
 {
-	if (me.serv->features.chanmodes[0] && strchr(me.serv->features.chanmodes[0], mode))
+	if (me.server->features.chanmodes[0] && strchr(me.server->features.chanmodes[0], mode))
 		return 1; /* 1 parameter for set, 1 parameter for unset */
 
-	if (me.serv->features.chanmodes[1] && strchr(me.serv->features.chanmodes[1], mode))
+	if (me.server->features.chanmodes[1] && strchr(me.server->features.chanmodes[1], mode))
 		return 1; /* 1 parameter for set, 1 parameter for unset */
 
-	if (me.serv->features.chanmodes[2] && strchr(me.serv->features.chanmodes[2], mode))
+	if (me.server->features.chanmodes[2] && strchr(me.server->features.chanmodes[2], mode))
 		return (what == MODE_ADD) ? 1 : 0; /* 1 parameter for set, no parameter for unset */
 
-	if (me.serv->features.chanmodes[3] && strchr(me.serv->features.chanmodes[3], mode))
+	if (me.server->features.chanmodes[3] && strchr(me.server->features.chanmodes[3], mode))
 		return 0; /* no parameter for set, no parameter for unset */
 
 	/* Not found: */
 	return 0;
 }
 
-/* set_mode
- *	written by binary
- */
-void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *pcount,
-               char pvar[MAXMODEPARAMS][MODEBUFLEN + 3], int bounce)
+MultiLineMode *_set_mode(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
+                        char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
 {
-	char *curchr;
-	char *argument;
+	Cmode *cm = NULL;
+	MultiLineMode *mlm = NULL;
+	const char *curchr;
+	const char *argument;
+	char argumentbuf[MODEBUFLEN+1];
 	u_int what = MODE_ADD;
 	long modetype = 0;
 	int paracount = 1;
@@ -1422,24 +931,15 @@ void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *
 	CoreChannelModeTable foundat;
 	int found = 0;
 	int sent_mlock_warning = 0;
-	unsigned int htrig = 0;
-	long oldm, oldl;
 	int checkrestr = 0, warnrestr = 1;
-	int extm = 1000000; /* (default value not used but stops gcc from complaining) */
 	Cmode_t oldem;
-	long my_access;
 	paracount = 1;
 	*pcount = 0;
 
-	oldm = channel->mode.mode;
-	oldl = channel->mode.limit;
-	oldem = channel->mode.extmode;
+	oldem = channel->mode.mode;
 	if (RESTRICT_CHANNELMODES && !ValidatePermissionsForPath("immune:restrict-channelmodes",client,NULL,channel,NULL)) /* "cache" this */
 		checkrestr = 1;
 
-	/* Set access to the status we have */
-	my_access = IsUser(client) ? get_access(client, channel) : 0;
-
 	for (curchr = parv[0]; *curchr; curchr++)
 	{
 		switch (*curchr)
@@ -1458,7 +958,7 @@ void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *
 					{
 						if (!sent_mlock_warning)
 						{
-							sendnumeric(client, ERR_MLOCKRESTRICTED, channel->chname, *curchr, channel->mode_lock);
+							sendnumeric(client, ERR_MLOCKRESTRICTED, channel->name, *curchr, channel->mode_lock);
 							sent_mlock_warning++;
 						}
 						continue;
@@ -1480,9 +980,9 @@ void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *
 					modetype = foundat.mode;
 				} else {
 					/* Maybe in extmodes */
-					for (extm=0; extm <= Channelmode_highest; extm++)
+					for (cm=channelmodes; cm; cm = cm->next)
 					{
-						if (Channelmode_Table[extm].flag == *curchr)
+						if (cm->letter == *curchr)
 						{
 							found = 2;
 							break;
@@ -1510,74 +1010,24 @@ void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *
 					break;
 				}
 
-				if (paracount < parc)
-					argument = parv[paracount]; /* can still be NULL */
-				else
-					argument = NULL;
-
-#ifndef NO_OPEROVERRIDE
-				if (found == 1)
+				if ((paracount < parc) && parv[paracount])
 				{
-					if ((Halfop_mode(modetype) == FALSE) && opermode == 2 && htrig != 1)
-					{
-						/* YUCK! */
-						if ((foundat.flag == 'h') && argument && (find_person(argument, NULL) == client))
-						{
-							/* ircop with halfop doing a -h on himself. no warning. */
-						} else {
-							opermode = 0;
-							htrig = 1;
-						}
-					}
-				}
-				else if (found == 2) {
-					/* Extended mode: all override stuff is in do_extmode_char which will set
-					 * opermode if appropriate. -- Syzop
-					 */
+					strlcpy(argumentbuf, parv[paracount], sizeof(argumentbuf));
+					argument = argumentbuf;
+				} else {
+					argument = NULL;
 				}
-#endif /* !NO_OPEROVERRIDE */
-
-				/* Not sure how useful this is, but I'll let it stay... */
-				if (argument && strlen(argument) >= MODEBUFLEN)
-					argument[MODEBUFLEN-1] = '\0';
 
 				if (found == 1)
-				{
-					paracount += do_mode_char(channel, modetype, *curchr,
-								  argument, what, client, pcount,
-								  pvar, bounce, my_access);
-				}
+					paracount += do_mode_char_list_mode(channel, modetype, *curchr, argument, what, client, pcount, pvar);
 				else if (found == 2)
-				{
-					paracount += do_extmode_char(channel, &Channelmode_Table[extm], argument,
-								     what, client, pcount, pvar, bounce);
-				}
+					paracount += do_extmode_char(channel, cm, argument, what, client, pcount, pvar);
 				break;
 		} /* switch(*curchr) */
 	} /* for loop through mode letters */
 
-	make_mode_str(channel, oldm, oldem, oldl, *pcount, pvar, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), bounce);
-
-#ifndef NO_OPEROVERRIDE
-	if ((htrig == 1) && IsUser(client))
-	{
-		/* This is horrible. Just horrible. */
-		if (!((modebuf[0] == '+' || modebuf[0] == '-') && modebuf[1] == '\0'))
-		{
-			sendto_snomask(SNO_EYES, "*** OperOverride -- %s (%s@%s) MODE %s %s %s",
-			               client->name, client->user->username, client->user->realhost,
-			               channel->chname, modebuf, parabuf);
-		}
-
-		/* Logging Implementation added by XeRXeS */
-		ircd_log(LOG_OVERRIDE,"OVERRIDE: %s (%s@%s) MODE %s %s %s",
-		         client->name, client->user->username, client->user->realhost,
-		         channel->chname, modebuf, parabuf);
-
-		htrig = 0;
-		opermode = 0; /* stop double override notices... but is this ok??? -- Syzop */
-	}
-#endif
+	mlm = make_mode_str(client, channel, oldem, *pcount, pvar);
+	return mlm;
 }
 
 /*
@@ -1587,16 +1037,17 @@ void _set_mode(Channel *channel, Client *client, int parc, char *parv[], u_int *
  */
 CMD_FUNC(_cmd_umode)
 {
-	int i;
-	char **p, *m;
+	Umode *um;
+	const char *m;
 	Client *acptr;
-	int what, setsnomask = 0;
+	int what;
 	long oldumodes = 0;
-	int oldsnomasks = 0;
+	char oldsnomask[64];
 	/* (small note: keep 'what' as an int. -- Syzop). */
 	short rpterror = 0, umode_restrict_err = 0, chk_restrict = 0, modex_err = 0;
 
 	what = MODE_ADD;
+	*oldsnomask = '\0';
 
 	if (parc < 2)
 	{
@@ -1604,7 +1055,7 @@ CMD_FUNC(_cmd_umode)
 		return;
 	}
 
-	if (!(acptr = find_person(parv[1], NULL)))
+	if (!(acptr = find_user(parv[1], NULL)))
 	{
 		if (MyConnect(client))
 		{
@@ -1622,31 +1073,24 @@ CMD_FUNC(_cmd_umode)
 	{
 		sendnumeric(client, RPL_UMODEIS, get_usermode_string(client));
 		if (client->user->snomask)
-			sendnumeric(client, RPL_SNOMASK, get_snomask_string(client));
+			sendnumeric(client, RPL_SNOMASK, client->user->snomask);
 		return;
 	}
 
 	userhost_save_current(client); /* save host, in case we do any +x/-x or similar */
 
-	/* find flags already set for user */
-	for (i = 0; i <= Usermode_highest; i++)
-		if ((client->umodes & Usermode_Table[i].mode))
-			oldumodes |= Usermode_Table[i].mode;
-
-	for (i = 0; i <= Snomask_highest; i++)
-		if ((client->user->snomask & Snomask_Table[i].mode))
-			oldsnomasks |= Snomask_Table[i].mode;
+	oldumodes = client->umodes;
 
 	if (RESTRICT_USERMODES && MyUser(client) && !ValidatePermissionsForPath("immune:restrict-usermodes",client,NULL,NULL,NULL))
 		chk_restrict = 1;
 
-	if (MyConnect(client))
-		setsnomask = client->user->snomask;
+	if (client->user->snomask)
+		strlcpy(oldsnomask, client->user->snomask, sizeof(oldsnomask));
+
 	/*
 	 * parse mode change string(s)
 	 */
-	p = &parv[2];
-	for (m = *p; *m; m++)
+	for (m = parv[2]; *m; m++)
 	{
 		if (chk_restrict && strchr(RESTRICT_USERMODES, *m))
 		{
@@ -1678,7 +1122,7 @@ CMD_FUNC(_cmd_umode)
 					if (parc >= 4 && client->user->snomask)
 					{
 						set_snomask(client, parv[3]);
-						if (client->user->snomask == 0)
+						if (client->user->snomask == NULL)
 							goto def;
 						break;
 					} else {
@@ -1686,10 +1130,10 @@ CMD_FUNC(_cmd_umode)
 						goto def;
 					}
 				}
-				if (what == MODE_ADD)
+				if ((what == MODE_ADD) && IsOper(client))
 				{
 					if (parc < 4)
-						set_snomask(client, IsOper(client) ? SNO_DEFOPER : SNO_DEFUSER);
+						set_snomask(client, OPER_SNOMASKS);
 					else
 						set_snomask(client, parv[3]);
 					goto def;
@@ -1699,7 +1143,8 @@ CMD_FUNC(_cmd_umode)
 			case 'O':
 				if (IsQuarantined(client->direction))
 				{
-					sendto_realops("QUARANTINE: Oper %s on server %s killed, due to quarantine", client->name, client->srvptr->name);
+					unreal_log(ULOG_INFO, "mode", "OPER_KILLED_QUARANTINE", client,
+					           "QUARANTINE: Oper $client.details on server $client.user.servername killed, due to quarantine");
 					sendto_server(NULL, 0, 0, NULL, ":%s KILL %s :Quarantined: no oper privileges allowed", me.id, client->name);
 					exit_client(client, NULL, "Quarantined: no oper privileges allowed");
 					return;
@@ -1749,24 +1194,23 @@ CMD_FUNC(_cmd_umode)
 				break;
 			default:
 			def:
-				for (i = 0; i <= Usermode_highest; i++)
+				for (um = usermodes; um; um = um->next)
 				{
-					if (*m == Usermode_Table[i].flag)
+					if (um->letter == *m)
 					{
-						if (Usermode_Table[i].allowed)
-						if (!Usermode_Table[i].allowed(client,what))
+						if (um->allowed && !um->allowed(client,what))
 							break;
 						if (what == MODE_ADD)
-							client->umodes |= Usermode_Table[i].mode;
+							client->umodes |= um->mode;
 						else
-							client->umodes &= ~Usermode_Table[i].mode;
+							client->umodes &= ~um->mode;
 						break;
 					}
-					else if (i == Usermode_highest && MyConnect(client) && !rpterror)
-					{
-						sendnumeric(client, ERR_UMODEUNKNOWNFLAG);
-						rpterror = 1;
-					}
+				}
+				if (!um && MyConnect(client) && !rpterror)
+				{
+					sendnumeric(client, ERR_UMODEUNKNOWNFLAG);
+					rpterror = 1;
 				}
 				break;
 		} /* switch */
@@ -1784,35 +1228,44 @@ CMD_FUNC(_cmd_umode)
 			int i;
 
 			/* MODES */
-			for (i = 0; i <= Usermode_highest; i++)
+			for (um = usermodes; um; um = um->next)
 			{
-				if (!Usermode_Table[i].flag)
-					continue;
-				if (Usermode_Table[i].unset_on_deoper)
+				if (um->unset_on_deoper)
 				{
 					/* This is an oper mode. Is it set now and wasn't earlier?
 					 * then it needs to be stripped, as setting it is not
 					 * permitted.
 					 */
-					long m = Usermode_Table[i].mode;
-					if ((client->umodes & m) && !(oldumodes & m))
-						client->umodes &= ~Usermode_Table[i].mode; /* remove */
+					if ((client->umodes & um->mode) && !(oldumodes & um->mode))
+						client->umodes &= ~um->mode; /* remove */
 				}
 			}
 
 			/* SNOMASKS: user can delete existing but not add new ones */
-			for (i = 0; i <= Snomask_highest; i++)
+			if (client->user->snomask)
 			{
-				int sno = Snomask_Table[i].mode;
+				char rerun;
+				do {
+					char *p;
 
-				if (!Snomask_Table[i].flag)
-					continue;
-				/* Is it set now and wasn't earlier? Then it
-				 * needs to be stripped, as setting it is not
-				 * permitted.
-				 */
-				if ((client->user->snomask & sno) && !(oldsnomasks & sno))
-					client->user->snomask &= ~Snomask_Table[i].mode; /* remove */
+					rerun = 0;
+					for (p = client->user->snomask; *p; p++)
+					{
+						if (!strchr(oldsnomask, *p))
+						{
+							/* It is set now, but was not earlier?
+							 * Then it needs to be stripped, as setting is not permitted.
+							 * And re-run the loop
+							 */
+							delletterfromstring(client->user->snomask, *p);
+							rerun = 1;
+							break;
+						}
+					}
+				} while(rerun);
+				/* And make sure an empty snomask ("") becomes a NULL pointer */
+				if (client->user->snomask && !*client->user->snomask)
+					remove_all_snomasks(client);
 			}
 		} else {
 			/* User isn't an ircop at all. The solution is simple: */
@@ -1876,8 +1329,9 @@ CMD_FUNC(_cmd_umode)
 	if ((oldumodes & UMODE_OPER) && !IsOper(client) && MyConnect(client))
 	{
 		list_del(&client->special_node);
+		if (MyUser(client))
+			RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL);
 		remove_oper_privileges(client, 0);
-		RunHook2(HOOKTYPE_LOCAL_OPER, client, 0);
 	}
 
 	if (!(oldumodes & UMODE_OPER) && IsOper(client))
@@ -1913,12 +1367,12 @@ CMD_FUNC(_cmd_umode)
 	 * will cause servers to update correctly.
 	 */
 	if (oldumodes != client->umodes)
-		RunHook3(HOOKTYPE_UMODE_CHANGE, client, oldumodes, client->umodes);
+		RunHook(HOOKTYPE_UMODE_CHANGE, client, oldumodes, client->umodes);
 	if (dontspread == 0)
 		send_umode_out(client, 1, oldumodes);
 
-	if (MyConnect(client) && setsnomask != client->user->snomask)
-		sendnumeric(client, RPL_SNOMASK, get_snomask_string(client));
+	if (MyConnect(client) && client->user->snomask && strcmp(oldsnomask, client->user->snomask))
+		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
 }
 
 CMD_FUNC(cmd_mlock)
@@ -1932,7 +1386,7 @@ CMD_FUNC(cmd_mlock)
 	t = (time_t) atol(parv[1]);
 
 	/* Now, try to find the channel in question */
-	channel = find_channel(parv[2], NULL);
+	channel = find_channel(parv[2]);
 	if (!channel)
 		return;
 
@@ -1943,3 +1397,117 @@ CMD_FUNC(cmd_mlock)
 	if (IsServer(client))
 		set_channel_mlock(client, channel, parv[3], TRUE);
 }
+
+void mode_operoverride_msg(Client *client, Channel *channel, char *modebuf, char *parabuf)
+{
+	char buf[1024];
+
+	if (empty_mode(modebuf))
+		return;
+
+	/* Internally we have this distinction between modebuf and parabuf,
+	 * but this makes little sense to maintain in JSON.
+	 */
+	snprintf(buf, sizeof(buf), "%s %s", modebuf, parabuf);
+
+	unreal_log(ULOG_INFO, "operoverride", "OPEROVERRIDE_MODE", client,
+		   "OperOverride: $client.details changed channel mode of $channel to: $channel_mode",
+		   log_data_string("override_type", "mode"),
+		   log_data_string("channel_mode", buf),
+		   log_data_channel("channel", channel));
+}
+
+/* Deal with information requests from local users, such as:
+ * MODE #chan b    Show the ban list
+ * MODE #chan e    Show the ban exemption list
+ * MODE #chan I    Show the invite exception list
+ * MODE #chan q    Show list of channel owners
+ * MODE #chan a    Show list of channel admins
+ * @returns 1 if processed as a mode list (please return),
+ *          0 if not (continue with the MODE as it likely is a set request).
+ */
+int list_mode_request(Client *client, Channel *channel, const char *req)
+{
+	const char *p;
+	Ban *ban;
+	Member *member;
+
+	for (p = req; *p; p++)
+		if (strchr("beIqa", *p))
+			break;
+
+	if (!*p)
+		return 0; /* not handled, proceed with the MODE set attempt */
+
+	/* First, check access */
+	if (strchr("beI", *p))
+	{
+		if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remotebanlist",client,NULL,channel,NULL))
+		{
+			sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
+			return 1; /* handled */
+		}
+	} else {
+		if (!IsMember(client, channel) && !ValidatePermissionsForPath("channel:see:mode:remoteownerlist",client,NULL,channel,NULL))
+		{
+			sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
+			return 1; /* handled */
+		}
+	}
+
+	switch(*p)
+	{
+		case 'b':
+			for (ban = channel->banlist; ban; ban = ban->next)
+				sendnumeric(client, RPL_BANLIST, channel->name, ban->banstr, ban->who, (long long)ban->when);
+			sendnumeric(client, RPL_ENDOFBANLIST, channel->name);
+			break;
+		case 'e':
+			for (ban = channel->exlist; ban; ban = ban->next)
+				sendnumeric(client, RPL_EXLIST, channel->name, ban->banstr, ban->who, (long long)ban->when);
+			sendnumeric(client, RPL_ENDOFEXLIST, channel->name);
+			break;
+		case 'I':
+			for (ban = channel->invexlist; ban; ban = ban->next)
+				sendnumeric(client, RPL_INVEXLIST, channel->name, ban->banstr, ban->who, (long long)ban->when);
+			sendnumeric(client, RPL_ENDOFINVEXLIST, channel->name);
+			break;
+		case 'q':
+			for (member = channel->members; member; member = member->next)
+				if (strchr(member->member_modes, 'q'))
+					sendnumeric(client, RPL_QLIST, channel->name, member->client->name);
+			sendnumeric(client, RPL_ENDOFQLIST, channel->name);
+			break;
+		case 'a':
+			for (member = channel->members; member; member = member->next)
+				if (strchr(member->member_modes, 'a'))
+					sendnumeric(client, RPL_ALIST, channel->name, member->client->name);
+			sendnumeric(client, RPL_ENDOFALIST, channel->name);
+			break;
+	}
+
+	return 1; /* handled */
+}
+
+void _set_channel_mode(Channel *channel, char *modes, char *parameters)
+{
+	char buf[512];
+	char *p, *param;
+	int myparc = 1, i;
+	char *myparv[512];
+
+	memset(&myparv, 0, sizeof(myparv));
+	myparv[0] = raw_strdup(modes);
+
+	strlcpy(buf, parameters, sizeof(buf));
+	for (param = strtoken(&p, buf, " "); param; param = strtoken(&p, NULL, " "))
+		myparv[myparc++] = raw_strdup(param);
+	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);
+	ClearULine(&me); // and clear it again..
+
+	for (i = 0; i < myparc; i++)
+		safe_free(myparv[i]);
+}
diff --git a/src/modules/monitor.c b/src/modules/monitor.c
@@ -0,0 +1,232 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/monitor.c
+ *   (C) 2021 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_MONITOR 	"MONITOR"
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+CMD_FUNC(cmd_monitor);
+char *monitor_isupport_param(void);
+int monitor_nickchange(Client *client, MessageTag *mtags, const char *newnick);
+int monitor_post_nickchange(Client *client, MessageTag *mtags, const char *oldnick);
+int monitor_quit(Client *client, MessageTag *mtags, const char *comment);
+int monitor_connect(Client *client);
+int monitor_notification(Client *client, Watch *watch, Link *lp, int event);
+
+ModuleHeader MOD_HEADER
+  = {
+	"monitor",
+	"5.0",
+	"command /monitor", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	CommandAdd(modinfo->handle, MSG_MONITOR, cmd_monitor, 2, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_NICKCHANGE, 0, monitor_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_NICKCHANGE, 0, monitor_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_POST_LOCAL_NICKCHANGE, 0, monitor_post_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_POST_REMOTE_NICKCHANGE, 0, monitor_post_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_QUIT, 0, monitor_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, monitor_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, monitor_connect);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, monitor_connect);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	ISupportAdd(modinfo->handle, "MONITOR", monitor_isupport_param());
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+char *monitor_isupport_param(void)
+{
+	/* i find it unlikely for a client to use WATCH and MONITOR at the same time, so keep a single limit for both */
+	return STR(MAXWATCH);
+}
+
+int monitor_nickchange(Client *client, MessageTag *mtags, const char *newnick)
+{
+	if (!smycmp(client->name, newnick)) // new nick is same as old one, maybe the case changed
+		return 0;
+
+	watch_check(client, WATCH_EVENT_OFFLINE, monitor_notification);
+	return 0;
+}
+
+int monitor_post_nickchange(Client *client, MessageTag *mtags, const char *oldnick)
+{
+	if (!smycmp(client->name, oldnick)) // new nick is same as old one, maybe the case changed
+		return 0;
+
+	watch_check(client, WATCH_EVENT_ONLINE, monitor_notification);
+	return 0;
+}
+
+int monitor_quit(Client *client, MessageTag *mtags, const char *comment)
+{
+	watch_check(client, WATCH_EVENT_OFFLINE, monitor_notification);
+	return 0;
+}
+
+int monitor_connect(Client *client)
+{
+	watch_check(client, WATCH_EVENT_ONLINE, monitor_notification);
+	return 0;
+}
+
+int monitor_notification(Client *client, Watch *watch, Link *lp, int event)
+{
+	if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR))
+		return 0;
+
+	switch (event)
+	{
+		case WATCH_EVENT_ONLINE:
+			sendnumeric(lp->value.client, RPL_MONONLINE, client->name, client->user->username, GetHost(client));
+			break;
+		case WATCH_EVENT_OFFLINE:
+			sendnumeric(lp->value.client, RPL_MONOFFLINE, client->name);
+			break;
+		default:
+			break; /* may be handled by other modules */
+	}
+	
+	return 0;
+}
+
+void send_status(Client *client, MessageTag *recv_mtags, const char *nick)
+{
+	MessageTag *mtags = NULL;
+	Client *user;
+	user = find_user(nick, NULL);
+	new_message(client, recv_mtags, &mtags);
+	if (!user){
+		sendnumeric(client, RPL_MONOFFLINE, nick);
+	} else {
+		sendnumeric(client, RPL_MONONLINE, user->name, user->user->username, GetHost(user));
+	}
+	free_message_tags(mtags);
+}
+
+#define WATCHES(client) (moddata_local_client(client, watchCounterMD).i)
+#define WATCH(client) (moddata_local_client(client, watchListMD).ptr)
+
+CMD_FUNC(cmd_monitor)
+{
+	char request[BUFSIZE];
+	char cmd;
+	char *s, *p = NULL;
+	int i;
+	int toomany = 0;
+	Link *lp;
+
+	if (!MyUser(client))
+		return;
+
+	if (parc < 2 || BadPtr(parv[1]))
+		cmd = 'l';
+	else
+		cmd = tolower(*parv[1]);
+
+	ModDataInfo *watchCounterMD = findmoddata_byname("watchCount", MODDATATYPE_LOCAL_CLIENT);
+	ModDataInfo *watchListMD = findmoddata_byname("watchList", MODDATATYPE_LOCAL_CLIENT);
+	
+	if (!watchCounterMD || !watchListMD)
+	{
+		unreal_log(ULOG_ERROR, "monitor", "WATCH_BACKEND_MISSING", NULL,
+		           "[monitor] moddata unavailable. Is the 'watch-backend' module loaded?");
+		sendnotice(client, "MONITOR command is not available at this moment. Please try again later.");
+		return;
+	}
+	
+	switch(cmd)
+	{
+		case 'c':
+			watch_del_list(client, WATCH_FLAG_TYPE_MONITOR);
+			break;
+		case 'l':
+			lp = WATCH(client);
+			while (lp)
+			{
+				if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR))
+				{
+					lp = lp->next;
+					continue; /* this one is not ours */
+				}
+				sendnumeric(client, RPL_MONLIST, lp->value.wptr->nick);
+				lp = lp->next;
+			}
+
+			sendnumeric(client, RPL_ENDOFMONLIST);
+			break;
+		case 's':
+			lp = WATCH(client);
+			while (lp)
+			{
+				if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR))
+				{
+					lp = lp->next;
+					continue; /* this one is not ours */
+				}
+				send_status(client, recv_mtags, lp->value.wptr->nick);
+				lp = lp->next;
+			}
+			break;
+		case '-':
+		case '+':
+			if (parc < 3 || BadPtr(parv[2]))
+				return;
+			strlcpy(request, parv[2], sizeof(request));
+			for (s = strtoken(&p, request, ","); s; s = strtoken(&p, NULL, ","))
+			{
+				if (cmd == '-') {
+					watch_del(s, client, WATCH_FLAG_TYPE_MONITOR);
+				} else {
+					if (WATCHES(client) >= MAXWATCH)
+					{
+						sendnumeric(client, ERR_MONLISTFULL, MAXWATCH, s);
+						continue;
+					}
+					if (do_nick_name(s))
+						watch_add(s, client, WATCH_FLAG_TYPE_MONITOR);
+					send_status(client, recv_mtags, s);
+				}
+			}
+			break;
+	}
+}
+
diff --git a/src/modules/motd.c b/src/modules/motd.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /motd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -66,10 +66,10 @@ CMD_FUNC(cmd_motd)
 	if (IsServer(client))
 		return;
 
-	if (hunt_server(client, recv_mtags, ":%s MOTD :%s", 1, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "MOTD", 1, parc, parv) != HUNTED_ISME)
 	{
 		if (MyUser(client))
-			client->local->since += 15;
+			add_fake_lag(client, 15000);
 		return;
 	}
 
@@ -101,7 +101,7 @@ CMD_FUNC(cmd_motd)
 	}
 
 	motdline = NULL;
-	if(themotd)
+	if (themotd)
 		motdline = themotd->lines;
 	while (motdline)
 	{
diff --git a/src/modules/names.c b/src/modules/names.c
@@ -24,6 +24,9 @@
 
 CMD_FUNC(cmd_names);
 
+long CAP_MULTI_PREFIX = 0L;
+long CAP_USERHOST_IN_NAMES = 0L;
+
 #define MSG_NAMES 	"NAMES"
 
 ModuleHeader MOD_HEADER
@@ -32,11 +35,19 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /names", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
 {
+	ClientCapabilityInfo c;
+	memset(&c, 0, sizeof(c));
+	c.name = "multi-prefix";
+	ClientCapabilityAdd(modinfo->handle, &c, &CAP_MULTI_PREFIX);
+	memset(&c, 0, sizeof(c));
+	c.name = "userhost-in-names";
+	ClientCapabilityAdd(modinfo->handle, &c, &CAP_USERHOST_IN_NAMES);
+
 	CommandAdd(modinfo->handle, MSG_NAMES, cmd_names, MAXPARA, CMD_USER|CMD_SERVER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
@@ -66,8 +77,8 @@ static char buf[BUFSIZE];
 #define TRUNCATED_NAMES 64
 CMD_FUNC(cmd_names)
 {
-	int multiprefix = (MyConnect(client) && HasCapability(client, "multi-prefix"));
-	int uhnames = (MyConnect(client) && HasCapability(client, "userhost-in-names")); // cache UHNAMES support
+	int multiprefix = (MyConnect(client) && HasCapabilityFast(client, CAP_MULTI_PREFIX));
+	int uhnames = (MyConnect(client) && HasCapabilityFast(client, CAP_USERHOST_IN_NAMES)); // cache UHNAMES support
 	int bufLen = NICKLEN + (!uhnames ? 0 : (1 + USERLEN + 1 + HOSTLEN));
 	int mlen = strlen(me.name) + bufLen + 7;
 	Channel *channel;
@@ -75,7 +86,7 @@ CMD_FUNC(cmd_names)
 	int member;
 	Member *cm;
 	int idx, flag = 1, spos;
-	char *s, *para = parv[1];
+	const char *para = parv[1], *s;
 	char nuhBuffer[NICKLEN+USERLEN+HOSTLEN+3];
 
 	if (parc < 2 || !MyConnect(client))
@@ -88,16 +99,12 @@ CMD_FUNC(cmd_names)
 	{
 		if (*s == ',')
 		{
-			if (strlen(para) > TRUNCATED_NAMES)
-				para[TRUNCATED_NAMES] = '\0';
-			sendto_realops("names abuser %s %s",
-			    get_client_name(client, FALSE), para);
 			sendnumeric(client, ERR_TOOMANYTARGETS, s+1, 1, "NAMES");
 			return;
 		}
 	}
 
-	channel = find_channel(para, NULL);
+	channel = find_channel(para);
 
 	if (!channel || (!ShowChannel(client, channel) && !ValidatePermissionsForPath("channel:see:names:secret",client,NULL,channel,NULL)))
 	{
@@ -108,6 +115,8 @@ CMD_FUNC(cmd_names)
 	/* cache whether this user is a member of this channel or not */
 	member = IsMember(client, channel);
 
+	// FIXME: consider rewriting this whole thing to get rid of pointer juggling and stuff.
+
 	if (PubChannel(channel))
 		buf[0] = '=';
 	else if (SecretChannel(channel))
@@ -117,7 +126,7 @@ CMD_FUNC(cmd_names)
 
 	idx = 1;
 	buf[idx++] = ' ';
-	for (s = channel->chname; *s; s++)
+	for (s = channel->name; *s; s++)
 		buf[idx++] = *s;
 	buf[idx++] = ' ';
 	buf[idx++] = ':';
@@ -140,34 +149,14 @@ CMD_FUNC(cmd_names)
 
 		if (!multiprefix)
 		{
-			/* Standard NAMES reply */
-#ifdef PREFIX_AQ
-			if (cm->flags & CHFL_CHANOWNER)
-				buf[idx++] = '~';
-			else if (cm->flags & CHFL_CHANADMIN)
-				buf[idx++] = '&';
-			else
-#endif
-			if (cm->flags & CHFL_CHANOP)
-				buf[idx++] = '@';
-			else if (cm->flags & CHFL_HALFOP)
-				buf[idx++] = '%';
-			else if (cm->flags & CHFL_VOICE)
-				buf[idx++] = '+';
+			/* Standard NAMES reply (single character) */
+			char c = mode_to_prefix(*cm->member_modes);
+			if (c)
+				buf[idx++] = c;
 		} else {
 			/* NAMES reply with all rights included (multi-prefix / NAMESX) */
-#ifdef PREFIX_AQ
-			if (cm->flags & CHFL_CHANOWNER)
-				buf[idx++] = '~';
-			if (cm->flags & CHFL_CHANADMIN)
-				buf[idx++] = '&';
-#endif
-			if (cm->flags & CHFL_CHANOP)
-				buf[idx++] = '@';
-			if (cm->flags & CHFL_HALFOP)
-				buf[idx++] = '%';
-			if (cm->flags & CHFL_VOICE)
-				buf[idx++] = '+';
+			strcpy(&buf[idx], modes_to_prefix(cm->member_modes));
+			idx += strlen(&buf[idx]);
 		}
 
 		if (!uhnames) {
@@ -187,7 +176,7 @@ CMD_FUNC(cmd_names)
 			buf[idx++] = ' ';
 		buf[idx] = '\0';
 		flag = 1;
-		if (mlen + idx + bufLen > BUFSIZE - 7)
+		if (mlen + idx + bufLen + MEMBERMODESLEN >= BUFSIZE - 1)
 		{
 			sendnumeric(client, RPL_NAMREPLY, buf);
 			idx = spos;
diff --git a/src/modules/netinfo.c b/src/modules/netinfo.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /netinfo", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -61,7 +61,7 @@ MOD_UNLOAD()
  * parv[1] = max global count
  * parv[2] = time of end sync
  * parv[3] = unreal protocol using (numeric)
- * parv[4] = cloak-crc (> u2302)
+ * parv[4] = cloak key check (> u2302)
  * parv[5] = free(**)
  * parv[6] = free(**)
  * parv[7] = free(**)
@@ -70,7 +70,6 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_netinfo)
 {
 	long 		lmax;
-	time_t	 	xx;
 	long 		endsync, protocol;
 	char		buf[512];
 
@@ -83,7 +82,8 @@ CMD_FUNC(cmd_netinfo)
 
 	if (IsNetInfo(client))
 	{
-		sendto_realops("Already got NETINFO from Link %s", client->name);
+		unreal_log(ULOG_WARNING, "link", "NETINFO_ALREADY_RECEIVED", client,
+		           "Got NETINFO from server $client, but we already received it earlier!");
 		return;
 	}
 
@@ -96,46 +96,46 @@ CMD_FUNC(cmd_netinfo)
 	if (lmax > irccounts.global_max)
 	{
 		irccounts.global_max = lmax;
-		sendto_realops("Max Global Count is now %li (set by link %s)",
-		    lmax, client->name);
+		unreal_log(ULOG_INFO, "link", "NEW_GLOBAL_RECORD", client,
+		           "Record global users is now $record_global_users (set by server $client)",
+		           log_data_integer("record_global_users", lmax));
 	}
 
-	xx = TStime();
-	if ((xx - endsync) < -2)
-	{
-		char *emsg = "";
-		if (xx - endsync < -10)
-		{
-			emsg = " [\002PLEASE SYNC YOUR CLOCKS!\002]";
-		}
-		sendto_umode_global(UMODE_OPER,
-			"Possible negative TS split at link %s (%lld - %lld = %lld)%s",
-			client->name, (long long)(xx), (long long)(endsync), (long long)(xx - endsync), emsg);
-	}
-	sendto_umode_global(UMODE_OPER,
-	    "Link %s -> %s is now synced [secs: %lld recv: %ld.%hu sent: %ld.%hu]",
-	    client->name, me.name, (long long)(TStime() - endsync), client->local->receiveK,
-	    client->local->receiveB, client->local->sendK, client->local->sendB);
+	unreal_log(ULOG_INFO, "link", "SERVER_SYNCED", client,
+	           "Link $client -> $me is now synced "
+	           "[secs: $synced_after_seconds, recv: $received_bytes, sent: $sent_bytes]",
+	           log_data_client("me", &me),
+	           log_data_integer("synced_after_seconds", TStime() - endsync),
+	           log_data_integer("received_bytes", client->local->traffic.bytes_received),
+	           log_data_integer("sent_bytes", client->local->traffic.bytes_sent));
 
-	if (!(strcmp(ircnetwork, parv[8]) == 0))
+	if (!(strcmp(NETWORK_NAME, parv[8]) == 0))
 	{
-		sendto_umode_global(UMODE_OPER,
-			"Network name mismatch from link %s (%s != %s)",
-			client->name, parv[8], ircnetwork);
+		unreal_log(ULOG_WARNING, "link", "NETWORK_NAME_MISMATCH", client,
+		           "Network name mismatch: server $client has '$their_network_name', "
+		           "server $me has '$our_network_name'.",
+		           log_data_client("me", &me),
+		           log_data_string("their_network_name", parv[8]),
+		           log_data_string("our_network_name", NETWORK_NAME));
 	}
 
 	if ((protocol != UnrealProtocol) && (protocol != 0))
 	{
-		sendto_umode_global(UMODE_OPER,
-			"Link %s is running Protocol %li while %s is running %d",
-			client->name, protocol, me.name, UnrealProtocol);
+		unreal_log(ULOG_INFO, "link", "LINK_PROTOCOL_MISMATCH", client,
+		           "Server $client is running UnrealProtocol $their_link_protocol, "
+		           "server $me uses $our_link_protocol.",
+		           log_data_client("me", &me),
+		           log_data_integer("their_link_protocol", protocol),
+		           log_data_integer("our_link_protocol", UnrealProtocol));
 	}
-	strlcpy(buf, CLOAK_KEYCRC, sizeof(buf));
+	strlcpy(buf, CLOAK_KEY_CHECKSUM, sizeof(buf));
 	if (*parv[4] != '*' && strcmp(buf, parv[4]))
 	{
-		sendto_realops
-			("Link %s has a DIFFERENT CLOAK KEY - %s != %s. \002YOU SHOULD CORRECT THIS ASAP\002.",
-				client->name, parv[4], buf);
+		unreal_log(ULOG_WARNING, "link", "CLOAK_KEY_MISMATCH", client,
+		           "Server $client has a DIFFERENT CLOAK KEY (OR METHOD)!!! You should fix this ASAP!\n"
+		           "When the cloaking configuration is different on servers, this will cause "
+		           "channel bans on cloaked hosts/IPs not to work correctly, "
+		           "meaning users can bypass channel bans!");
 	}
 	SetNetInfo(client);
 }
diff --git a/src/modules/nick.c b/src/modules/nick.c
@@ -28,17 +28,35 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /nick",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
+/* Defines */
+
+#define NICKCOL_EQUAL         0
+#define NICKCOL_NEW_WON       1
+#define NICKCOL_EXISTING_WON  2
+
+/* Assume that on collision a NICK is in flight and the other server will take
+ * the exact same decision we would do, and thus we don't send a KILL to cptr?
+ * This works great with this code, seems to kill the correct person and not
+ * cause desyncs even without UID/SID. HOWEVER.. who knows what code the other servers run?
+ * Should use UID/SID anyway, then this whole problem doesn't exist.
+ */
+#define ASSUME_NICK_IN_FLIGHT
+
+/* Variables */
+static char buf[BUFSIZE];
+static char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64];
+
 /* Forward declarations */
 CMD_FUNC(cmd_nick);
 CMD_FUNC(cmd_nick_local);
 CMD_FUNC(cmd_nick_remote);
 CMD_FUNC(cmd_uid);
-int _register_user(Client *client, char *nick, char *username, char *umode, char *virthost, char *ip);
-void nick_collision(Client *cptr, char *newnick, char *newid, Client *new, Client *existing, int type);
-int AllowClient(Client *client, char *username);
+int _register_user(Client *client);
+void nick_collision(Client *cptr, const char *newnick, const char *newid, Client *new, Client *existing, int type);
+int AllowClient(Client *client);
 
 MOD_TEST()
 {
@@ -65,67 +83,52 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static char buf[BUFSIZE];
-static char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64];
+/** Hmm.. don't we already have such a function? */
+void set_user_modes_dont_spread(Client *client, const char *umode)
+{
+	const char *args[4];
 
-#define NICKCOL_EQUAL         0
-#define NICKCOL_NEW_WON       1
-#define NICKCOL_EXISTING_WON  2
+	args[0] = client->name;
+	args[1] = client->name;
+	args[2] = umode;
+	args[3] = NULL;
 
-/* Assume that on collision a NICK is in flight and the other server will take
- * the exact same decision we would do, and thus we don't send a KILL to cptr?
- * This works great with this code, seems to kill the correct person and not
- * cause desyncs even without UID/SID. HOWEVER.. who knows what code the other servers run?
- * Should use UID/SID anyway, then this whole problem doesn't exist.
- */
-#define ASSUME_NICK_IN_FLIGHT
+	dontspread = 1;
+	do_cmd(client, NULL, "MODE", 3, args);
+	dontspread = 0;
+}
 
-/** The NICK command.
- * In UnrealIRCd 4/5 this is only used in 2 cases:
- * 1) A local user setting or changing the nick name ("NICK xyz")
- * 2) A remote user changing their nick name (":<uid> NICK <newnick>")
- */
+/** Remote client (already fully registered) changing their nick */
 CMD_FUNC(cmd_nick_remote)
 {
 	TKL *tklban;
 	int ishold;
 	Client *acptr;
 	char nick[NICKLEN + 2];
+	char oldnick[NICKLEN + 1];
 	time_t lastnick = 0;
 	int differ = 1;
 	unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
-	char *nickid = (IsUser(client) && *client->id) ? client->id : NULL;
-	Client *cptr = client->direction; /* Pending a complete overhaul... (TODO) */
 	MessageTag *mtags = NULL;
 
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NONICKNAMEGIVEN);
-		return;
-	}
-
-	if (!IsUser(client))
-	{
-		/* Old NICK protocol for introducing users, not supported as you should use UID */
-		sendto_umode_global(UMODE_OPER, "Old NICK protocol detected from server %s, should use UID instead -- delinking",
-		                                client->name);
-		exit_client(cptr->direction, NULL, "Old NICK protocol detected, bad, use UID!");
-		return;
-	}
+	/* 'client' is always the fully registered user doing the nick change */
 
 	strlcpy(nick, parv[1], NICKLEN + 1);
+	strlcpy(oldnick, client->name, sizeof(oldnick));
 
 	if (parc > 2)
 		lastnick = atol(parv[2]);
 
-	if (!do_remote_nick_name(nick))
+	if (!do_remote_nick_name(nick) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
 	{
 		ircstats.is_kill++;
-		sendto_umode(UMODE_OPER, "Bad Nick: %s From: %s %s",
-		    parv[1], client->name, get_client_name(cptr, FALSE));
+		unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client,
+		           "Server link $server tried to introduce bad nick '$nick' -- rejected.",
+		           log_data_string("nick", parv[1]),
+		           log_data_client("server", client->direction));
 		mtags = NULL;
 		new_message(client, NULL, &mtags);
-		sendto_one(cptr, mtags, ":%s KILL %s :Illegal nick name", me.id, client->id);
+		sendto_one(client, mtags, ":%s KILL %s :Illegal nick name", me.id, client->id);
 		SetKilled(client);
 		exit_client(client, mtags, "Illegal nick name");
 		free_message_tags(mtags);
@@ -133,26 +136,16 @@ CMD_FUNC(cmd_nick_remote)
 		return;
 	}
 
-	if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
-	{
-		sendto_umode(UMODE_OPER, "Bad Reserved Nick: %s From: %s %s",
-		    parv[1], client->name, get_client_name(cptr, FALSE));
-		mtags = NULL;
-		new_message(client, NULL, &mtags);
-		sendto_one(cptr, mtags, ":%s KILL %s :Reserved nick name", me.id, client->id);
-		SetKilled(client);
-		exit_client(client, mtags, "Reserved nick name");
-		free_message_tags(mtags);
-		mtags = NULL;
-		return;
-	}
-
 	/* Check Q-lines / ban nick */
 	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)) && !ishold)
 	{
-		/* Remote user changing nick - warning only */
-		sendto_snomask(SNO_QLINE, "Q-Lined nick %s from %s on %s", nick,
-			client->name, client->srvptr ? client->srvptr->name : "<unknown>");
+		unreal_log(ULOG_INFO, "nick", "QLINE_NICK_REMOTE", client,
+			   "Banned nick $nick [$ip] from server $server ($reason)",
+			   log_data_string("nick", parv[1]),
+			   log_data_string("ip", GetIP(client)),
+			   log_data_client("server", client->uplink),
+			   log_data_string("reason", tklban->ptr.nameban->reason));
+		/* Let it through */
 	}
 
 	if ((acptr = find_client(nick, NULL)))
@@ -178,26 +171,21 @@ CMD_FUNC(cmd_nick_remote)
 			differ = (mycmp(acptr->user->username, client->user->username) ||
 			          mycmp(acptr->user->realhost, client->user->realhost));
 
-			sendto_umode(UMODE_OPER, "Nick change collision from %s to %s (%s %lld <- %s %lld)",
-			    client->name, acptr->name, acptr->direction->name,
-			    (long long)acptr->lastnick,
-			    client->direction->name, (long long)lastnick);
-
 			if (!(parc > 2) || lastnick == acptr->lastnick)
 			{
-				nick_collision(client, parv[1], nickid, client, acptr, NICKCOL_EQUAL);
+				nick_collision(client, parv[1], client->id, client, acptr, NICKCOL_EQUAL);
 				return; /* Now that I killed them both, ignore the NICK */
 			} else
 			if ((differ && (acptr->lastnick > lastnick)) ||
 			    (!differ && (acptr->lastnick < lastnick)))
 			{
-				nick_collision(client, parv[1], nickid, client, acptr, NICKCOL_NEW_WON);
+				nick_collision(client, parv[1], client->id, client, acptr, NICKCOL_NEW_WON);
 				/* fallthrough: their user won, continue and proceed with the nick change */
 			} else
 			if ((differ && (acptr->lastnick < lastnick)) ||
 			    (!differ && (acptr->lastnick > lastnick)))
 			{
-				nick_collision(client, parv[1], nickid, client, acptr, NICKCOL_EXISTING_WON);
+				nick_collision(client, parv[1], client->id, client, acptr, NICKCOL_EXISTING_WON);
 				return; /* their user lost, ignore the NICK */
 			} else
 			{
@@ -208,53 +196,50 @@ CMD_FUNC(cmd_nick_remote)
 
 	mtags = NULL;
 
-	/* Existing client nick-changing */
-
 	if (!IsULine(client))
-		sendto_snomask(SNO_FNICKCHANGE, "*** %s (%s@%s) has changed their nickname to %s",
-			client->name, client->user->username, client->user->realhost, nick);
+	{
+		unreal_log(ULOG_INFO, "nick", "REMOTE_NICK_CHANGE", client,
+		           "Client $client.details has changed their nickname to $new_nick",
+		           log_data_string("new_nick", nick));
+	}
 
 	new_message(client, recv_mtags, &mtags);
-	RunHook3(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
+	RunHook(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
 	client->lastnick = lastnick ? lastnick : TStime();
 	add_history(client, 1);
 	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);
-	free_message_tags(mtags);
 	if (removemoder)
 		client->umodes &= ~UMODE_REGNICK;
 
 	/* Finally set new nick name. */
 	del_from_client_hash_table(client->name, client);
-	hash_check_watch(client, RPL_LOGOFF);
-
-	strcpy(client->name, nick);
+	strlcpy(client->name, nick, sizeof(client->name));
 	add_to_client_hash_table(nick, client);
 
-	hash_check_watch(client, RPL_LOGON);
+	RunHook(HOOKTYPE_POST_REMOTE_NICKCHANGE, client, mtags, oldnick);
+	free_message_tags(mtags);
 }
 
+/* Local user: either setting their nick for the first time (registration)
+ * or changing their nick (fully registered already, or not)
+ */
 CMD_FUNC(cmd_nick_local)
 {
 	TKL *tklban;
 	int ishold;
 	Client *acptr;
-	char nick[NICKLEN + 2], descbuf[BUFSIZE];
+	char nick[NICKLEN + 2];
+	char oldnick[NICKLEN + 1];
+	char descbuf[BUFSIZE];
 	Membership *mp;
-	long lastnick = 0l;
-	int  differ = 1, update_watch = 1;
+	int newuser = 0;
 	unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
 	Hook *h;
-	int i = 0;
-	char *nickid = (IsUser(client) && *client->id) ? client->id : NULL;
-	Client *cptr = client->direction; /* Pending a complete overhaul... (TODO) */
+	int ret;
 
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NONICKNAMEGIVEN);
-		return;
-	}
+	strlcpy(oldnick, client->name, sizeof(oldnick));
 
 	/* Enforce minimum nick length */
 	if (iConf.min_nick_length && !IsOper(client) && !IsULine(client) && strlen(parv[1]) < iConf.min_nick_length)
@@ -299,27 +284,21 @@ CMD_FUNC(cmd_nick_local)
 		}
 		if (!ValidatePermissionsForPath("immune:server-ban:ban-nick",client,NULL,NULL,nick))
 		{
-			client->local->since += 4; /* lag them up */
+			add_fake_lag(client, 4000); /* lag them up */
 			sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, tklban->ptr.nameban->reason);
-			sendto_snomask(SNO_QLINE, "Forbidding Q-lined nick %s from %s (%s)",
-			    nick, get_client_name(cptr, FALSE), tklban->ptr.nameban->reason);
+			unreal_log(ULOG_INFO, "nick", "QLINE_NICK_LOCAL_ATTEMPT", client,
+				   "Attempt to use banned nick $nick [$ip] blocked ($reason)",
+				   log_data_string("nick", parv[1]),
+				   log_data_string("ip", GetIP(client)),
+				   log_data_client("server", client->uplink),
+				   log_data_string("reason", tklban->ptr.nameban->reason));
 			return;	/* NICK message ignored */
 		}
 		/* fallthrough for ircops that have sufficient privileges */
 	}
 
-	/* set::anti-flood::nick-flood */
-	if (client->user &&
-	    !ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL) &&
-	    flood_limit_exceeded(client, FLD_NICK))
-	{
-		/* Throttle... */
-		sendnumeric(client, ERR_NCHANGETOOFAST, nick);
-		return;
-	}
-
 	if (!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
-		cptr->local->since += 3;	/* Nick-flood prot. -Donwulff */
+		add_fake_lag(client, 3000);
 
 	if ((acptr = find_client(nick, NULL)))
 	{
@@ -350,9 +329,21 @@ CMD_FUNC(cmd_nick_local)
 		}
 	}
 
+	/* set::anti-flood::nick-flood */
+	if (client->user &&
+	    !ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL) &&
+	    flood_limit_exceeded(client, FLD_NICK))
+	{
+		/* Throttle... */
+		sendnumeric(client, ERR_NCHANGETOOFAST, nick);
+		return;
+	}
+
 	/* New local client? */
 	if (!client->name[0])
 	{
+		newuser = 1;
+
 		if (iConf.ping_cookie)
 		{
 			/*
@@ -380,7 +371,7 @@ CMD_FUNC(cmd_nick_local)
 				sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1", me.name, nick);
 
 			client->lastnick = TStime();
-			if (!register_user(client, nick, client->user->username, NULL, NULL, NULL))
+			if (!register_user(client))
 			{
 				if (IsDead(client))
 					return;
@@ -389,7 +380,6 @@ CMD_FUNC(cmd_nick_local)
 				 */
 			} else {
 				/* New user! */
-				update_watch = 0; /* already done in register_user() */
 				strlcpy(nick, client->name, sizeof(nick)); /* don't ask, but I need this. do not remove! -- Syzop */
 			}
 		}
@@ -397,6 +387,7 @@ CMD_FUNC(cmd_nick_local)
 	if (MyUser(client))
 	{
 		MessageTag *mtags = NULL;
+		int ret;
 
 		/* Existing client nick-changing */
 
@@ -409,38 +400,40 @@ CMD_FUNC(cmd_nick_local)
 		 */
 		for (mp = client->user->channel; mp; mp = mp->next)
 		{
-			if (!is_skochanop(client, mp->channel) && is_banned(client, mp->channel, BANCHK_NICK, NULL, NULL))
+			int ret = HOOK_CONTINUE;
+			Hook *h;
+			if (!check_channel_access(client, mp->channel, "hoaq") && is_banned(client, mp->channel, BANCHK_NICK, NULL, NULL))
 			{
 				sendnumeric(client, ERR_BANNICKCHANGE,
-				    mp->channel->chname);
+				    mp->channel->name);
 				return;
 			}
-			if (CHECK_TARGET_NICK_BANS && !is_skochanop(client, mp->channel) && is_banned_with_nick(client, mp->channel, BANCHK_NICK, nick, NULL, NULL))
+			if (CHECK_TARGET_NICK_BANS && !check_channel_access(client, mp->channel, "hoaq") && is_banned_with_nick(client, mp->channel, BANCHK_NICK, nick, NULL, NULL))
 			{
-				sendnumeric(client, ERR_BANNICKCHANGE, mp->channel->chname);
+				sendnumeric(client, ERR_BANNICKCHANGE, mp->channel->name);
 				return;
 			}
 
 			for (h = Hooks[HOOKTYPE_CHAN_PERMIT_NICK_CHANGE]; h; h = h->next)
 			{
-				i = (*(h->func.intfunc))(client,mp->channel);
-				if (i != HOOK_CONTINUE)
+				ret = (*(h->func.intfunc))(client,mp->channel);
+				if (ret != HOOK_CONTINUE)
 					break;
 			}
 
-			if (i == HOOK_DENY)
+			if (ret == HOOK_DENY)
 			{
-				sendnumeric(client, ERR_NONICKCHANGE,
-				    mp->channel->chname);
+				sendnumeric(client, ERR_NONICKCHANGE, mp->channel->name);
 				return;
 			}
 		}
 
-		sendto_snomask(SNO_NICKCHANGE, "*** %s (%s@%s) has changed their nickname to %s",
-			client->name, client->user->username, client->user->realhost, nick);
+		unreal_log(ULOG_INFO, "nick", "LOCAL_NICK_CHANGE", client,
+		           "Client $client.details has changed their nickname to $new_nick",
+		           log_data_string("new_nick", nick));
 
 		new_message(client, recv_mtags, &mtags);
-		RunHook3(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
+		RunHook(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
 		client->lastnick = TStime();
 		add_history(client, 1);
 		sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
@@ -456,8 +449,6 @@ CMD_FUNC(cmd_nick_local)
 	}
 
 	del_from_client_hash_table(client->name, client);
-	if (update_watch && IsUser(client))
-		hash_check_watch(client, RPL_LOGOFF);
 
 	strlcpy(client->name, nick, sizeof(client->name));
 	add_to_client_hash_table(nick, client);
@@ -466,11 +457,11 @@ CMD_FUNC(cmd_nick_local)
 	snprintf(descbuf, sizeof(descbuf), "Client: %s", nick);
 	fd_desc(client->local->fd, descbuf);
 
-	if (update_watch && IsUser(client))
-		hash_check_watch(client, RPL_LOGON);
-
 	if (removemoder && MyUser(client))
 		sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name);
+
+	if (MyUser(client) && !newuser)
+		RunHook(HOOKTYPE_POST_LOCAL_NICKCHANGE, client, recv_mtags, oldnick);
 }
 
 /*
@@ -481,7 +472,7 @@ CMD_FUNC(cmd_nick_local)
 **      parv[4] = username
 **      parv[5] = hostname
 **      parv[6] = UID
-**	parv[7] = servicestamp
+**	parv[7] = account name (SVID)
 **      parv[8] = umodes
 **	parv[9] = virthost, * if none
 **	parv[10] = cloaked host, * if none
@@ -498,9 +489,10 @@ CMD_FUNC(cmd_uid)
 	Client *acptr, *serv = NULL;
 	Client *acptrs;
 	char nick[NICKLEN + 1];
-	long lastnick = 0l;
+	long lastnick = 0;
 	int differ = 1;
-	char *hostname, *username, *sstamp, *umodes, *virthost, *ip, *realname;
+	const char *hostname, *username, *sstamp, *umodes, *virthost, *ip_raw, *realname;
+	const char *ip = NULL;
 
 	if (parc < 13)
 	{
@@ -518,67 +510,105 @@ CMD_FUNC(cmd_uid)
 	}
 
 	strlcpy(nick, parv[1], sizeof(nick));
+	hostname = parv[5];
+	sstamp = parv[7];
+	username = parv[4];
+	umodes = parv[8];
+	virthost = parv[9];
+	ip_raw = parv[11];
+	realname = parv[12];
 
 	/* Do some *MINIMAL* nick name checking for remote nicknames.
 	 * This will only catch things that severely break things. -- Syzop
 	 */
-	if (!do_remote_nick_name(nick))
+	if (!do_remote_nick_name(nick) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
 	{
-		sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], "Illegal characters");
+		unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client->uplink,
+		           "Server link $client tried to introduce bad nick '$nick' -- rejected.",
+		           log_data_string("nick", parv[1]));
+		sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], "Illegal nick name");
 
 		ircstats.is_kill++;
-		sendto_umode(UMODE_OPER, "Bad Nick: %s From: %s %s",
-		    parv[1], client->name, get_client_name(client, FALSE));
 		/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
 		sendto_one(client, NULL, ":%s KILL %s :Bad nick", me.id, parv[1]);
 		return;
 	}
 
-	/* Kill quarantined opers early... */
-	if (IsQuarantined(client->direction) && strchr(parv[8], 'o'))
+	if (!valid_uid(parv[6]) || strncmp(parv[6], client->id, 3))
 	{
 		ircstats.is_kill++;
+		unreal_log(ULOG_ERROR, "link", "BAD_UID", client,
+		           "Server link $client ($sid) used bad UID $uid in UID command.",
+		           log_data_string("sid", client->id),
+		           log_data_string("uid", parv[6]));
 		/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
-		sendto_one(client, NULL, ":%s KILL %s :Quarantined: no oper privileges allowed",
-			me.id, parv[1]);
-		sendto_umode_global(UMODE_OPER, "QUARANTINE: Oper %s on server %s killed, due to quarantine",
-			parv[1], client->name);
+		sendto_one(client, NULL, ":%s KILL %s :Bad UID", me.id, parv[6]);
 		return;
 	}
 
-	/* This one is never allowed, even from remotes */
-	if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
+	if (!valid_host(hostname, 0))
 	{
-		sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, "Reserved for internal IRCd purposes");
-		sendto_one(client, NULL, ":%s KILL %s :Bad reserved nick", me.id, parv[1]);
+		ircstats.is_kill++;
+		unreal_log(ULOG_ERROR, "link", "BAD_HOSTNAME", client,
+		           "Server link $client ($client.id) introduced user $nick with bad host name: $bad_hostname.",
+		           log_data_string("nick", nick),
+		           log_data_string("bad_hostname", hostname));
+		/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
+		sendto_one(client, NULL, ":%s KILL %s :Bad hostname", me.id, parv[6]);
 		return;
 	}
 
-	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)))
+	if (strcmp(virthost, "*") && !valid_host(virthost, 0))
 	{
-		if (IsServer(client) && !ishold) /* server introducing new client */
+		ircstats.is_kill++;
+		unreal_log(ULOG_ERROR, "link", "BAD_HOSTNAME", client,
+		           "Server link $client ($client.id) introduced user $nick with bad virtual hostname: $bad_hostname.",
+		           log_data_string("nick", nick),
+		           log_data_string("bad_hostname", virthost));
+		/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
+		sendto_one(client, NULL, ":%s KILL %s :Bad virtual host", me.id, parv[6]);
+		return;
+	}
+
+	if (strcmp(ip_raw, "*"))
+	{
+		if (!(ip = decode_ip(ip_raw)))
 		{
-			acptrs = find_server(client->user == NULL ? parv[6] : client->user->server, NULL);
-			/* (NEW: no unregistered Q-Line msgs anymore during linking) */
-			if (!acptrs || (acptrs->serv && acptrs->serv->flags.synced))
-				sendto_snomask(SNO_QLINE, "Q-Lined nick %s from %s on %s", nick,
-				    (*client->name != 0
-				    && !IsServer(client) ? client->name : "<unregistered>"),
-				    acptrs ? acptrs->name : "unknown server");
+			ircstats.is_kill++;
+			unreal_log(ULOG_ERROR, "link", "BAD_IP", client,
+				   "Server link $client ($client.id) introduced user $nick with bad IP: $bad_ip.",
+				   log_data_string("nick", nick),
+				   log_data_string("bad_ip", ip_raw));
+			/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
+			sendto_one(client, NULL, ":%s KILL %s :Bad IP in UID command", me.id, parv[6]);
+			return;
 		}
 	}
 
-	/* Now check if 'nick' already exists - first, collisions with server names/ids (extremely rare) */
-	if ((acptr = find_server(nick, NULL)) != NULL)
+	/* Kill quarantined opers early... */
+	if (IsQuarantined(client->direction) && strchr(parv[8], 'o'))
 	{
-		sendto_umode(UMODE_OPER, "Nick collision on %s(%s <- %s)",
-		    client->name, acptr->direction->name,
-		    get_client_name(client, FALSE));
 		ircstats.is_kill++;
-		sendto_one(client, NULL, ":%s KILL %s :Nick-server-collision", me.id, parv[1]);
+		/* Send kill to uplink only, hasn't been broadcasted to the rest, anyway */
+		unreal_log(ULOG_INFO, "link", "OPER_KILLED_QUARANTINE", NULL,
+		           "QUARANTINE: Oper $nick on server $server killed, due to quarantine",
+		           log_data_string("nick", parv[1]),
+		           log_data_client("server", client));
+		sendto_one(client, NULL, ":%s KILL %s :Quarantined: no oper privileges allowed", me.id, parv[6]);
 		return;
 	}
 
+	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)))
+	{
+		unreal_log(ULOG_INFO, "nick", "QLINE_NICK_REMOTE", client,
+			   "Banned nick $nick [$nick.ip] from server $server ($reason)",
+			   log_data_string("nick", parv[1]),
+			   log_data_string("ip", ip),
+			   log_data_client("server", client->uplink),
+			   log_data_string("reason", tklban->ptr.nameban->reason));
+		/* Let it through */
+	}
+
 	/* Now check if 'nick' already exists - collision with a user (or still in handshake, unknown) */
 	if ((acptr = find_client(nick, NULL)) != NULL)
 	{
@@ -594,22 +624,7 @@ CMD_FUNC(cmd_uid)
 
 		lastnick = atol(parv[3]);
 		differ = (mycmp(acptr->user->username, parv[4]) || mycmp(acptr->user->realhost, parv[5]));
-		sendto_umode(UMODE_OPER, "Nick collision on %s (%s %lld <- %s %lld)",
-		    acptr->name, acptr->direction->name, (long long)acptr->lastnick,
-		    client->direction->name, (long long)lastnick);
-		/*
-		   **    I'm putting the KILL handling here just to make it easier
-		   ** to read, it's hard to follow it the way it used to be.
-		   ** Basically, this is what it will do.  It will kill both
-		   ** users if no timestamp is given, or they are equal.  It will
-		   ** kill the user on our side if the other server is "correct"
-		   ** (user@host differ and their user is older, or user@host are
-		   ** the same and their user is younger), otherwise just kill the
-		   ** user an reintroduce our correct user.
-		   **    The old code just sat there and "hoped" the other server
-		   ** would kill their user.  Not anymore.
-		   **                                               -- binary
-		 */
+
 		if (acptr->lastnick == lastnick)
 		{
 			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EQUAL);
@@ -647,29 +662,49 @@ nickkill2done:
 
 	make_user(client);
 
-	hostname = parv[5];
-	sstamp = parv[7];
-	username = parv[4];
-	umodes = parv[8];
-	virthost = parv[9];
-	ip = parv[11];
-	realname = parv[12];
 	/* Note that cloaked host aka parv[10] is unused */
 
-	client->user->server = find_or_add(client->srvptr->name);
+	client->user->server = find_or_add(client->uplink->name);
 	strlcpy(client->user->realhost, hostname, sizeof(client->user->realhost));
-	// FIXME: some validation would be nice ^
+	if (ip)
+		safe_strdup(client->ip, ip);
 
 	if (*sstamp != '*')
-		strlcpy(client->user->svid, sstamp, sizeof(client->user->svid));
+		strlcpy(client->user->account, sstamp, sizeof(client->user->account));
 
 	strlcpy(client->info, realname, sizeof(client->info));
 	strlcpy(client->user->username, username, USERLEN + 1);
-	register_user(client, client->name, username, umodes, virthost, ip);
-	if (IsDead(client))
-		return;
+	SetUser(client);
+
+	make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
+	safe_strdup(client->user->virthost, client->user->cloakedhost);
+
+	/* Inherit flags from server, makes it easy in the send routines
+	 * and this also makes clients inherit ulines.
+	 */
+	client->flags |= client->uplink->flags;
+
+	/* Update counts */
+	irccounts.clients++;
+	if (client->uplink->server)
+		client->uplink->server->users++;
+	if (client->umodes & UMODE_INVISIBLE)
+		irccounts.invisible++;
+
+	/* Set user modes */
+	set_user_modes_dont_spread(client, umodes);
 
-	if (client->user->svid[0] != '0')
+	/* Set the vhost */
+	if (*virthost != '*')
+		safe_strdup(client->user->virthost, virthost);
+
+	build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
+
+	sendto_serv_butone_nickcmd(client->direction, recv_mtags, client, (*buf == '\0' ? "+" : buf));
+
+	moddata_extract_s2s_mtags(client, recv_mtags);
+
+	if (IsLoggedIn(client))
 	{
 		user_account_login(recv_mtags, client);
 		/* no need to check for kill upon user_account_login() here
@@ -680,510 +715,430 @@ nickkill2done:
 	RunHook(HOOKTYPE_REMOTE_CONNECT, client);
 
 	if (!IsULine(serv) && IsSynched(serv))
-		sendto_fconnectnotice(client, 0, NULL);
+	{
+		unreal_log(ULOG_INFO, "connect", "REMOTE_CLIENT_CONNECT", client,
+			   "Client connecting: $client ($client.user.username@$client.hostname) [$client.ip] $extended_client_info",
+			   log_data_string("extended_client_info", get_connect_extinfo(client)),
+		           log_data_string("from_server_name", client->user->server));
+	}
 }
 
 /** The NICK command.
- * In UnrealIRCd 4/5 this is only used in 2 cases:
+ * In UnrealIRCd 4 and later this should only happen for:
  * 1) A local user setting or changing the nick name ("NICK xyz")
+ *    -> cmd_nick_local()
  * 2) A remote user changing their nick name (":<uid> NICK <newnick>")
+ *    -> cmd_nick_remote()
  */
 CMD_FUNC(cmd_nick)
 {
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NONICKNAMEGIVEN);
+		return;
+	}
+
 	if (MyConnect(client) && !IsServer(client))
+	{
 		cmd_nick_local(client, recv_mtags, parc, parv);
-	else
+	} else
+	if (!IsUser(client))
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_OLD_PROTOCOL_NICK", client->direction,
+		           "Server link $client tried to introduce $nick using NICK command. "
+		           "Server is using an old and unsupported protocol from UnrealIRCd 3.2.x or earlier, should use the UID command. "
+		           "See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
+		           log_data_string("nick", parv[1]));
+		/* Split the entire uplink, as it should never have allowed this (and probably they are to blame too) */
+		exit_client(client->direction, NULL, "Server used NICK command, bad, must use UID!");
+		return;
+	} else
+	{
 		cmd_nick_remote(client, recv_mtags, parc, parv);
+	}
 }
 
-/** Register the connection as a User.
- * This is called after NICK + USER (in no particular order)
- * and possibly other protocol messages as well (eg CAP).
- * @param client		Client to be made a user.
- * @param nick		Nick name
- * @param username	Username
- * @param umode		User modes
- * @param virthost	Virtual host (can be NULL)
- * @param ip		IP address string (can be NULL)
- * @returns 1 if successfully registered, 0 if not (client might be killed).
- */
-int _register_user(Client *client, char *nick, char *username, char *umode, char *virthost, char *ip)
+void welcome_user(Client *client, TKL *viruschan_tkl)
 {
-	ConfigItem_ban *bconf;
-	char *tmpstr;
-	char stripuser[USERLEN + 1], *u1 = stripuser, *u2, olduser[USERLEN + 1],
-	    userbad[USERLEN * 2 + 1], *ubad = userbad, noident = 0;
-	int i, xx;
-	Hook *h;
-	User *user = client->user;
-	char *tkllayer[9] = {
-		me.name,	/*0  server.name */
-		"+",		/*1  +|- */
-		"z",		/*2  G   */
-		"*",		/*3  user */
-		NULL,		/*4  host */
-		NULL,
-		NULL,		/*6  expire_at */
-		NULL,		/*7  set_at */
-		NULL		/*8  reason */
-	};
-	TKL *savetkl = NULL;
+	int i;
 	ConfigItem_tld *tlds;
 
-	nick = client->name; /* <- The data is always the same, but the pointer is sometimes not,
-	                    *    I need this for one of my modules, so do not remove! ;) -- Syzop */
+	RunHook(HOOKTYPE_WELCOME, client, 0);
+	sendnumeric(client, RPL_WELCOME, NETWORK_NAME, client->name, client->user->username, client->user->realhost);
 
-	if (MyConnect(client))
+	RunHook(HOOKTYPE_WELCOME, client, 1);
+	sendnumeric(client, RPL_YOURHOST, me.name, version);
+
+	RunHook(HOOKTYPE_WELCOME, client, 2);
+	sendnumeric(client, RPL_CREATED, creation);
+
+	RunHook(HOOKTYPE_WELCOME, client, 3);
+	sendnumeric(client, RPL_MYINFO, me.name, version, umodestring, cmodestring);
+
+	RunHook(HOOKTYPE_WELCOME, client, 4);
+	for (i = 0; ISupportStrings[i]; i++)
+		sendnumeric(client, RPL_ISUPPORT, ISupportStrings[i]);
+
+	RunHook(HOOKTYPE_WELCOME, client, 5);
+
+	if (IsHidden(client))
 	{
-	        char temp[USERLEN + 1];
+		sendnumeric(client, RPL_HOSTHIDDEN, client->user->virthost);
+		RunHook(HOOKTYPE_WELCOME, client, 396);
+	}
 
-		if (!AllowClient(client, username))
+	if (IsSecureConnect(client))
+	{
+		if (client->local->ssl && !iConf.no_connect_tls_info)
 		{
-			ircstats.is_ref++;
-			/* For safety, we have an extra kill here */
-			if (!IsDead(client))
-				exit_client(client, NULL, "Rejected");
-			return 0;
+			sendnotice(client, "*** You are connected to %s with %s",
+				me.name, tls_get_cipher(client));
 		}
+	}
 
-		if (client->local->hostp)
-		{
-			/* reject ASCII < 32 and ASCII >= 127 (note: upper resolver might be even more strict). */
-			for (tmpstr = client->local->sockhost; *tmpstr > ' ' && *tmpstr < 127; tmpstr++);
+	{
+		const char *parv[2];
+		parv[0] = NULL;
+		parv[1] = NULL;
+		do_cmd(client, NULL, "LUSERS", 1, parv);
+		if (IsDead(client))
+			return;
+	}
 
-			/* if host contained invalid ASCII _OR_ the DNS reply is an IP-like reply
-			 * (like: 1.2.3.4 or ::ffff:1.2.3.4), then reject it and use IP instead.
-			 */
-			if (*tmpstr || !*user->realhost || (isdigit(*client->local->sockhost) && (client->local->sockhost > tmpstr && isdigit(*(tmpstr - 1))) )
-			    || (client->local->sockhost[0] == ':'))
-				strlcpy(client->local->sockhost, client->ip, sizeof(client->local->sockhost));
-		}
-		if (client->local->sockhost[0])
-		{
-			strlcpy(user->realhost, client->local->sockhost, sizeof(client->local->sockhost)); /* SET HOSTNAME */
-		} else {
-			sendto_realops("[HOSTNAME BUG] client->local->sockhost is empty for user %s (%s, %s)",
-				client->name, client->ip ? client->ip : "<null>", user->realhost);
-			ircd_log(LOG_ERROR, "[HOSTNAME BUG] client->local->sockhost is empty for user %s (%s, %s)",
-				client->name, client->ip ? client->ip : "<null>", user->realhost);
-		}
+	RunHook(HOOKTYPE_WELCOME, client, 266);
 
-		/*
-		 * I do not consider *, ~ or ! 'hostile' in usernames,
-		 * as it is easy to differentiate them (Use \*, \? and \\)
-		 * with the possible?
-		 * exception of !. With mIRC etc. ident is easy to fake
-		 * to contain @ though, so if that is found use non-ident
-		 * username. -Donwulff
-		 *
-		 * I do, We only allow a-z A-Z 0-9 _ - and . now so the
-		 * !strchr(client->ident, '@') check is out of date. -Cabal95
-		 *
-		 * Moved the noident stuff here. -OnyxDragon
-		 */
+	short_motd(client);
 
-		/* because username may point to user->username */
-		strlcpy(temp, username, USERLEN + 1);
+	RunHook(HOOKTYPE_WELCOME, client, 376);
 
-		if (!IsUseIdent(client))
-			strlcpy(user->username, temp, USERLEN + 1);
-		else if (IsIdentSuccess(client))
-			strlcpy(user->username, client->ident, USERLEN+1);
-		else
-		{
-			if (IDENT_CHECK == 0) {
-				strlcpy(user->username, temp, USERLEN+1);
-			}
-			else {
-				*user->username = '~';
-				strlcpy((user->username + 1), temp, sizeof(user->username)-1);
-				noident = 1;
-			}
+#ifdef EXPERIMENTAL
+	sendnotice(client,
+		"*** \2NOTE:\2 This server is running experimental IRC server software (UnrealIRCd %s). "
+		"If you find any bugs or problems, please report them at https://bugs.unrealircd.org/",
+		VERSIONONLY);
+#endif
 
-		}
-		/*
-		 * Limit usernames to just 0-9 a-z A-Z _ - and .
-		 * It strips the "bad" chars out, and if nothing is left
-		 * changes the username to the first 8 characters of their
-		 * nickname. After the MOTD is displayed it sends numeric
-		 * 455 to the user telling them what(if anything) happened.
-		 * -Cabal95
-		 *
-		 * Moved the noident thing to the right place - see above
-		 * -OnyxDragon
-		 *
-		 * No longer use nickname if the entire ident is invalid,
-                 * if thats the case, it is likely the user is trying to cause
-		 * problems so just ban them. (Using the nick could introduce
-		 * hostile chars) -- codemastr
-		 */
-		for (u2 = user->username + noident; *u2; u2++)
-		{
-			if (isallowed(*u2))
-				*u1++ = *u2;
-			else if (*u2 < 32)
-			{
-				/*
-				 * Make sure they can read what control
-				 * characters were in their username.
-				 */
-				*ubad++ = '^';
-				*ubad++ = *u2 + '@';
-			}
-			else
-				*ubad++ = *u2;
-		}
-		*u1 = '\0';
-		*ubad = '\0';
-		if (strlen(stripuser) != strlen(user->username + noident))
-		{
-			if (stripuser[0] == '\0')
-			{
-				exit_client(client, NULL, "Hostile username. Please use only 0-9 a-z A-Z _ - and . in your username.");
-				return 0;
-			}
+	if (client->umodes & UMODE_INVISIBLE)
+		irccounts.invisible++;
 
-			strlcpy(olduser, user->username + noident, USERLEN+1);
-			strlcpy(user->username + 1, stripuser, sizeof(user->username)-1);
-			user->username[0] = '~';
-			user->username[USERLEN] = '\0';
-		}
-		else
-			u1 = NULL;
+	build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
 
-		/* Check ban realname { } blocks */
-		if ((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME)))
-		{
-			ircstats.is_ref++;
-			banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
-			return 0;
-		}
-		/* Check G/Z lines before shuns -- kill before quite -- codemastr */
-		if (find_tkline_match(client, 0))
-		{
-			if (!IsDead(client) && client->local->class)
-			{
-				/* Fix client count bug, in case that it was a hold such as via authprompt */
-				client->local->class->clients--;
-				client->local->class = NULL;
-			}
-			ircstats.is_ref++;
-			return 0;
-		}
-		find_shun(client);
+	sendto_serv_butone_nickcmd(client->direction, NULL, client, (*buf == '\0' ? "+" : buf));
 
-		spamfilter_build_user_string(spamfilter_user, client->name, client);
-		if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, 0, &savetkl))
-		{
-			if (savetkl && ((savetkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) ||
-			                (savetkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN)))
-			{
-				/* 'viruschan' action:
-				 * Continue with registering the client, and at the end
-				 * of this function we will do the actual joining to the
-				 * virus channel.
-				 */
-			} else {
-				/* Client is either dead or blocked (will hang, on purpose, and timeout) */
-				return 0;
-			}
-		}
+	broadcast_moddata_client(client);
+	RunHook(HOOKTYPE_LOCAL_CONNECT, client);
+	if (buf[0] != '\0' && buf[1] != '\0')
+		sendto_one(client, NULL, ":%s MODE %s :%s", client->name,
+		    client->name, buf);
+	if (client->user->snomask)
+		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
 
-		for (h = Hooks[HOOKTYPE_PRE_LOCAL_CONNECT]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client);
-			if (i == HOOK_DENY)
-			{
-				if (!IsDead(client) && client->local->class)
-				{
-					/* Fix client count bug, in case that
-					 * the HOOK_DENY was only meant temporarily.
-					 */
-					client->local->class->clients--;
-					client->local->class = NULL;
-				}
-				return 0;
-			}
-			if (i == HOOK_ALLOW)
-				break;
-		}
-	}
-	else
-	{
-		strlcpy(user->username, username, USERLEN+1);
-	}
-	SetUser(client);
-	irccounts.clients++;
-	if (client->srvptr && client->srvptr->serv)
-		client->srvptr->serv->users++;
+	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_WARN))
+		sendnotice_multiline(client, iConf.plaintext_policy_user_message);
 
-	make_cloakedhost(client, user->realhost, user->cloakedhost, sizeof(user->cloakedhost));
-	safe_strdup(user->virthost, user->cloakedhost);
+	if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_WARN) && outdated_tls_client(client))
+		sendnotice(client, "%s", outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client));
 
-	if (MyConnect(client))
-	{
-		char descbuf[BUFSIZE];
-		int i;
+	/* Make creation time the real 'online since' time, excluding registration time.
+	 * Otherwise things like set::anti-spam-quit-messagetime 10s could mean
+	 * 1 second in practice (#2174).
+	 */
+	client->local->creationtime = TStime();
+	client->local->idle_since = TStime();
 
-		snprintf(descbuf, sizeof descbuf, "Client: %s", nick);
-		fd_desc(client->local->fd, descbuf);
+	/* Give the user a fresh start as far as fake-lag is concerned.
+	 * Otherwise the user could be lagged up already due to all the CAP stuff.
+	 */
+	client->local->fake_lag = TStime();
 
-		list_move(&client->lclient_node, &lclient_list);
+	RunHook(HOOKTYPE_WELCOME, client, 999);
 
-		irccounts.unknown--;
-		irccounts.me_clients++;
+	/* NOTE: Code after this 'if (viruschan_tkl)' will not be executed for quarantined-
+	 *       virus-users. So be carefull with the order. -- Syzop
+	 */
+	// FIXME: verify if this works, trace code path upstream!!!!
+	if (viruschan_tkl)
+	{
+		join_viruschan(client, viruschan_tkl, SPAMF_USER);
+		return;
+	}
 
-		if (IsSecure(client))
-		{
-			client->umodes |= UMODE_SECURE;
-			RunHook(HOOKTYPE_SECURE_CONNECT, client);
-		}
+	/* Force the user to join the given chans -- codemastr */
+	tlds = find_tld(client);
 
-		if (IsHidden(client))
-		{
-			ircd_log(LOG_CLIENT, "Connect - %s!%s@%s [%s] [vhost: %s] %s",
-				nick, user->username, user->realhost, GetIP(client), user->virthost, get_connect_extinfo(client));
-		} else
-		{
-			ircd_log(LOG_CLIENT, "Connect - %s!%s@%s [%s] %s",
-				nick, user->username, user->realhost, GetIP(client), get_connect_extinfo(client));
-		}
+	if (tlds && !BadPtr(tlds->channel))
+	{
+		char *chans = strdup(tlds->channel);
+		const char *args[3] = {
+			NULL,
+			chans,
+			NULL
+		};
+		do_cmd(client, NULL, "JOIN", 3, args);
+		safe_free(chans);
+		if (IsDead(client))
+			return;
+	}
+	else if (!BadPtr(AUTO_JOIN_CHANS) && strcmp(AUTO_JOIN_CHANS, "0"))
+	{
+		char *chans = strdup(AUTO_JOIN_CHANS);
+		const char *args[3] = {
+			NULL,
+			chans,
+			NULL
+		};
+		do_cmd(client, NULL, "JOIN", 3, args);
+		safe_free(chans);
+		if (IsDead(client))
+			return;
+	}
+}
 
-		RunHook2(HOOKTYPE_WELCOME, client, 0);
-		sendnumeric(client, RPL_WELCOME, ircnetwork, nick, user->username, user->realhost);
+/** Validate client->user->username.
+ * @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.
+ */
+int valid_username(Client *client, int noident)
+{
+	static char stripuser[USERLEN + 1];
+	char *i;
+	char *o = stripuser;
+	char filtered = 0; /* any changes? */
 
-		RunHook2(HOOKTYPE_WELCOME, client, 1);
-		sendnumeric(client, RPL_YOURHOST, me.name, version);
+	*stripuser = '\0';
 
-		RunHook2(HOOKTYPE_WELCOME, client, 2);
-		sendnumeric(client, RPL_CREATED, creation);
+	for (i = client->user->username + noident; *i; i++)
+	{
+		if (isallowed(*i))
+			*o++ = *i;
+		else
+			filtered = 1;
+	}
+	*o = '\0';
 
-		RunHook2(HOOKTYPE_WELCOME, client, 3);
-		sendnumeric(client, RPL_MYINFO, me.name, version, umodestring, cmodestring);
+	if (filtered == 0)
+		return 1; /* No change needed, all good */
 
-		RunHook2(HOOKTYPE_WELCOME, client, 4);
-		for (i = 0; ISupportStrings[i]; i++)
-			sendnumeric(client, RPL_ISUPPORT, ISupportStrings[i]);
+	if (*stripuser == '\0')
+		return 0; /* Zero valid characters, reject it */
 
-		RunHook2(HOOKTYPE_WELCOME, client, 5);
+	strlcpy(client->user->username + 1, stripuser, sizeof(client->user->username)-1);
+	client->user->username[0] = '~';
+	client->user->username[USERLEN] = '\0';
+	return 1; /* Filtered, but OK */
+}
 
-		if (IsHidden(client))
-		{
-			sendnumeric(client, RPL_HOSTHIDDEN, user->virthost);
-			RunHook2(HOOKTYPE_WELCOME, client, 396);
-		}
+/** Register the connection as a User - only for local connections!
+ * This is called after NICK + USER (in no particular order)
+ * and possibly other protocol messages as well (eg CAP).
+ * @param client	Client to be made a user.
+ * @returns 1 if successfully registered, 0 if not (client might be killed).
+ */
+int _register_user(Client *client)
+{
+	ConfigItem_ban *bconf;
+	char *tmpstr;
+	char noident = 0;
+	int i;
+	Hook *h;
+	TKL *savetkl = NULL;
+	char temp[USERLEN + 1];
+	char descbuf[BUFSIZE];
 
-		if (IsSecureConnect(client))
-		{
-			if (client->local->ssl && !iConf.no_connect_tls_info)
-			{
-				sendnotice(client, "*** You are connected to %s with %s",
-					me.name, tls_get_cipher(client->local->ssl));
-			}
-		}
+	if (!MyConnect(client))
+		abort();
 
+	/* Set client->local->sockhost:
+	 * First deal with the special 'localhost' case and
+	 * then with generic setting based on DNS.
+	 */
+	if (!strcmp(GetIP(client), "127.0.0.1") ||
+	    !strcmp(GetIP(client), "0:0:0:0:0:0:0:1") ||
+	    !strcmp(GetIP(client), "0:0:0:0:0:ffff:127.0.0.1"))
+	{
+		set_sockhost(client, "localhost");
+		if (client->local->hostp)
 		{
-			char *parv[2];
-			parv[0] = client->name;
-			parv[1] = NULL;
-			do_cmd(client, NULL, "LUSERS", 1, parv);
-			if (IsDead(client))
-				return 0;
+			unreal_free_hostent(client->local->hostp);
+			client->local->hostp = NULL;
 		}
+	} else
+	{
+		struct hostent *hp = client->local->hostp;
+		if (hp && hp->h_name)
+			set_sockhost(client, hp->h_name);
+	}
 
-		RunHook2(HOOKTYPE_WELCOME, client, 266);
-
-		short_motd(client);
-
-		RunHook2(HOOKTYPE_WELCOME, client, 376);
+	/* Set the hostname (client->user->realhost).
+	 * This may later be overwritten by the AllowClient() call to
+	 * revert to the IP again if allow::options::useip is set.
+	 */
+	strlcpy(client->user->realhost, client->local->sockhost, sizeof(client->local->sockhost));
 
-#ifdef EXPERIMENTAL
-		sendnotice(client,
-			"*** \2NOTE:\2 This server is running experimental IRC server software (UnrealIRCd %s). "
-			"If you find any bugs or problems, please report them at https://bugs.unrealircd.org/",
-			VERSIONONLY);
-#endif
-		/*
-		 * Now send a numeric to the user telling them what, if
-		 * anything, happened.
-		 */
-		if (u1)
-			sendnumeric(client, ERR_HOSTILENAME, olduser, userbad, stripuser);
-	}
-	else
+	/* Check allow { } blocks... */
+	if (!AllowClient(client))
 	{
-		Client *acptr;
+		ircstats.is_ref++;
+		/* For safety, we have an extra kill here */
+		if (!IsDead(client))
+			exit_client(client, NULL, "Rejected");
+		return 0;
+	}
 
-		/* Remote client */
-		/* The following two cases probably cannot happen anymore? at all? */
-		if (!(acptr = find_server_quick(user->server)))
-		{
-			sendto_ops("Bad USER [%s] :%s USER %s %s : No such server",
-			           client->name, nick, user->username, user->server);
-			sendto_one(client, NULL, ":%s KILL %s :No such server: %s",
-			    me.id, client->id, user->server);
-			SetKilled(client);
-			exit_client(client, NULL, "USER without prefix(2.8) or wrong prefix");
-			return 0;
-		}
-		else if (acptr->direction != client->direction)
+	if (IsUseIdent(client))
+	{
+		if (IsIdentSuccess(client))
 		{
-			sendto_ops("Bad User [%s] :%s USER %s %s, != %s[%s]",
-			    client->name, nick, user->username, user->server,
-			    acptr->name, acptr->direction->name);
-			sendto_one(client, NULL, ":%s KILL %s :Wrong user-server-direction",
-			    me.id, client->id);
-			SetKilled(client);
-			exit_client(client, NULL, "USER server wrong direction");
-			return 0;
+			/* ident succeeded: overwite client->user->username with the ident reply */
+			strlcpy(client->user->username, client->ident, sizeof(client->user->username));
 		} else
+		if (IDENT_CHECK)
 		{
-			client->flags |= acptr->flags;
+			/* ident check is enabled and it failed: prefix the username with ~ */
+			char temp[USERLEN+1];
+			strlcpy(temp, client->user->username, sizeof(temp));
+			snprintf(client->user->username, sizeof(client->user->username), "~%s", temp);
+			noident = 1;
 		}
+	}
 
-		if (IsULine(client->srvptr))
-			SetULine(client);
+	/* Now validate the username. This may alter client->user->username
+	 * or reject it completely.
+	 */
+	if (!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;
 	}
-	if (client->umodes & UMODE_INVISIBLE)
+
+	/* Check ban realname { } blocks */
+	if ((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME)))
 	{
-		irccounts.invisible++;
+		ircstats.is_ref++;
+		banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
+		return 0;
+	}
+	/* Check G/Z lines before shuns -- kill before quite -- codemastr */
+	if (find_tkline_match(client, 0))
+	{
+		if (!IsDead(client) && client->local->class)
+		{
+			/* Fix client count bug, in case that it was a hold such as via authprompt */
+			client->local->class->clients--;
+			client->local->class = NULL;
+		}
+		ircstats.is_ref++;
+		return 0;
 	}
+	find_shun(client);
 
-	if (virthost && umode)
+	spamfilter_build_user_string(spamfilter_user, client->name, client);
+	if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, 0, &savetkl))
 	{
-		/* Set the IP address first */
-		if (ip && (*ip != '*'))
+		if (savetkl && ((savetkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) ||
+				(savetkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN)))
 		{
-			char *ipstring = decode_ip(ip);
-			if (!ipstring)
+			/* 'viruschan' action:
+			 * Continue with registering the client, and at the end
+			 * of this function we will do the actual joining to the
+			 * virus channel.
+			 */
+		} else {
+			/* Client is either dead or blocked (will hang, on purpose, and timeout) */
+			return 0;
+		}
+	}
+
+	for (h = Hooks[HOOKTYPE_PRE_LOCAL_CONNECT]; h; h = h->next)
+	{
+		int ret = (*(h->func.intfunc))(client);
+		if (ret == HOOK_DENY)
+		{
+			if (!IsDead(client) && client->local->class)
 			{
-				sendto_ops("USER with invalid IP (%s) (%s) -- "
-				           "IP must be base64 encoded binary representation of either IPv4 or IPv6",
-				           client->name, ip);
-				exit_client(client, NULL, "USER with invalid IP");
-				return 0;
+				/* Fix client count bug, in case that
+				 * the HOOK_DENY was only meant temporarily.
+				 */
+				client->local->class->clients--;
+				client->local->class = NULL;
 			}
-			safe_strdup(client->ip, ipstring);
+			return 0;
 		}
-
-		/* For remote clients we recalculate the cloakedhost here because
-		 * it may depend on the IP address (bug #5064).
-		 */
-		make_cloakedhost(client, user->realhost, user->cloakedhost, sizeof(user->cloakedhost));
-		safe_strdup(user->virthost, user->cloakedhost);
-
-		/* Set the umodes */
-		tkllayer[0] = nick;
-		tkllayer[1] = nick;
-		tkllayer[2] = umode;
-		tkllayer[3] = NULL;
-		dontspread = 1;
-		do_cmd(client, NULL, "MODE", 3, tkllayer);
-		dontspread = 0;
-
-		/* Set the vhost */
-		if (virthost && *virthost != '*')
-			safe_strdup(client->user->virthost, virthost);
+		if (ret == HOOK_ALLOW)
+			break;
 	}
 
-	hash_check_watch(client, RPL_LOGON);	/* Uglier hack */
-	build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
+	SetUser(client);
 
-	sendto_serv_butone_nickcmd(client->direction, client, (*buf == '\0' ? "+" : buf));
+	make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
+	safe_strdup(client->user->virthost, client->user->cloakedhost);
 
-	if (MyConnect(client))
-	{
-		broadcast_moddata_client(client);
-		sendto_connectnotice(client, 0, NULL); /* moved down, for modules. */
-		if (buf[0] != '\0' && buf[1] != '\0')
-			sendto_one(client, NULL, ":%s MODE %s :%s", client->name,
-			    client->name, buf);
-		if (user->snomask)
-			sendnumeric(client, RPL_SNOMASK, get_snomask_string_raw(user->snomask));
-
-		if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_WARN))
-			sendnotice_multiline(client, iConf.plaintext_policy_user_message);
-
-		if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_WARN) && outdated_tls_client(client))
-			sendnotice(client, "%s", outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client));
-
-		/* Make creation time the real 'online since' time, excluding registration time.
-		 * Otherwise things like set::anti-spam-quit-messagetime 10s could mean
-		 * 1 second in practice (#2174).
-		 */
-		client->local->firsttime = TStime();
-		client->local->last = TStime();
+	snprintf(descbuf, sizeof descbuf, "Client: %s", client->name);
+	fd_desc(client->local->fd, descbuf);
 
-		/* Give the user a fresh start as far as fake-lag is concerned.
-		 * Otherwise the user could be lagged up already due to all the CAP stuff.
-		 */
-		client->local->since = TStime();
+	/* Move user from unknown list to client list */
+	list_move(&client->lclient_node, &lclient_list);
 
-		RunHook2(HOOKTYPE_WELCOME, client, 999);
+	/* Update counts */
+	irccounts.unknown--;
+	irccounts.clients++;
+	irccounts.me_clients++;
+	if (client->uplink && client->uplink->server)
+		client->uplink->server->users++;
 
-		/* NOTE: Code after this 'if (savetkl)' will not be executed for quarantined-
-		 *       virus-users. So be carefull with the order. -- Syzop
-		 */
-		// FIXME: verify if this works, trace code path upstream!!!!
-		if (savetkl)
-			return join_viruschan(client, savetkl, SPAMF_USER); /* [RETURN!] */
+	if (IsSecure(client))
+	{
+		client->umodes |= UMODE_SECURE;
+		RunHook(HOOKTYPE_SECURE_CONNECT, client);
+	}
 
-		/* Force the user to join the given chans -- codemastr */
-		tlds = find_tld(client);
+	safe_free(client->local->passwd);
 
-		if (tlds && !BadPtr(tlds->channel))
-		{
-			char *chans = strdup(tlds->channel);
-			char *args[3] = {
-				client->name,
-				chans,
-				NULL
-			};
-			do_cmd(client, NULL, "JOIN", 3, args);
-			safe_free(chans);
-			if (IsDead(client))
-				return 0;
-		}
-		else if (!BadPtr(AUTO_JOIN_CHANS) && strcmp(AUTO_JOIN_CHANS, "0"))
-		{
-			char *chans = strdup(AUTO_JOIN_CHANS);
-			char *args[3] = {
-				client->name,
-				chans,
-				NULL
-			};
-			do_cmd(client, NULL, "JOIN", 3, args);
-			safe_free(chans);
-			if (IsDead(client))
-				return 0;
-		}
-		/* NOTE: If you add something here.. be sure to check the 'if (savetkl)' note above */
-	}
+	unreal_log(ULOG_INFO, "connect", "LOCAL_CLIENT_CONNECT", client,
+		   "Client connecting: $client ($client.user.username@$client.hostname) [$client.ip] $extended_client_info",
+		   log_data_string("extended_client_info", get_connect_extinfo(client)));
 
-	if (MyConnect(client) && !BadPtr(client->local->passwd))
-	{
-		safe_free(client->local->passwd);
-		client->local->passwd = NULL;
-	}
+	/* Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything... */
+	welcome_user(client, savetkl);
 
-	/* User successfully registered */
-	return 1;
+	return IsDead(client) ? 0 : 1;
 }
 
 /** Nick collission detected. A winner has been decided upstream. Deal with killing.
  * I moved this all to a single routine here rather than having all code duplicated
  * due to SID vs NICK and some code quadruplicated.
  */
-void nick_collision(Client *cptr, char *newnick, char *newid, Client *new, Client *existing, int type)
+void nick_collision(Client *cptr, const char *newnick, const char *newid, Client *new, Client *existing, int type)
 {
 	char comment[512];
-	char *new_server, *existing_server;
-
-	ircd_log(LOG_ERROR, "Nick collision: %s[%s]@%s (new) vs %s[%s]@%s (existing). Winner: %s. Type: %s",
-		newnick, newid, cptr->name,
-		existing->name, existing->id, existing->srvptr->name,
-		(type == NICKCOL_EQUAL) ? "None (equal)" : ((type == NICKCOL_NEW_WON) ? "New won" : "Existing won"),
-		new ? "nick-change" : "new user connecting");
+	const char *new_server, *existing_server;
+	const char *who_won;
+	const char *nickcol_reason;
+
+	if (type == NICKCOL_NEW_WON)
+		who_won = "new";
+	else if (type == NICKCOL_EXISTING_WON)
+		who_won = "existing";
+	else
+		who_won = "none";
+
+	nickcol_reason = new ? "nick change" : "new user connecting";
+
+	unreal_log(ULOG_ERROR, "nick", "NICK_COLLISION", NULL,
+	           "Nick collision: "
+	           "$new_nick[$new_id]@$uplink (new) vs "
+	           "$existing_client[$existing_client.id]@$existing_client.user.servername (existing). "
+	           "Winner: $nick_collision_winner. "
+	           "Cause: $nick_collision_reason",
+	           log_data_string("new_nick", newnick),
+	           log_data_string("new_id", newid),
+	           log_data_client("uplink", cptr),
+	           log_data_client("existing_client", existing),
+	           log_data_string("nick_collision_winner", who_won),
+	           log_data_string("nick_collision_reason", nickcol_reason));
 
 	new_server = cptr->name;
 	existing_server = (existing == existing->direction) ? me.name : existing->direction->name;
@@ -1246,32 +1201,6 @@ void nick_collision(Client *cptr, char *newnick, char *newid, Client *new, Clien
 	}
 }
 
-/* This used to initialize the various name strings used to store hostnames.
- * But nowadays this takes place much earlier (in add_connection?).
- * It's mainly used for "localhost" and WEBIRC magic only now...
- */
-int check_init(Client *client, char *sockn, size_t size)
-{
-	strlcpy(sockn, client->local->sockhost, HOSTLEN);
-
-	RunHookReturnInt3(HOOKTYPE_CHECK_INIT, client, sockn, size, !=0);
-
-	/* Some silly hack to convert 127.0.0.1 and such into 'localhost' */
-	if (!strcmp(GetIP(client), "127.0.0.1") ||
-	    !strcmp(GetIP(client), "0:0:0:0:0:0:0:1") ||
-	    !strcmp(GetIP(client), "0:0:0:0:0:ffff:127.0.0.1"))
-	{
-		if (client->local->hostp)
-		{
-			unreal_free_hostent(client->local->hostp);
-			client->local->hostp = NULL;
-		}
-		strlcpy(sockn, "localhost", HOSTLEN);
-	}
-
-	return 1;
-}
-
 /** Returns 1 if allow::maxperip is exceeded by 'client' */
 int exceeds_maxperip(Client *client, ConfigItem_allow *aconf)
 {
@@ -1305,23 +1234,15 @@ int exceeds_maxperip(Client *client, ConfigItem_allow *aconf)
  * @param username   Username, for some reason...
  * @returns 1 if OK, 0 if client is rejected (likely killed too)
  */
-int AllowClient(Client *client, char *username)
+int AllowClient(Client *client)
 {
 	static char sockhost[HOSTLEN + 1];
-	struct hostent *hp = NULL;
 	int i;
 	ConfigItem_allow *aconf;
 	char *hname;
 	static char uhost[HOSTLEN + USERLEN + 3];
 	static char fullname[HOSTLEN + 1];
 
-	Debug((DEBUG_DNS, "ch_cl: check access for %s[%s]", client->name, client->local->sockhost));
-
-	if (!check_init(client, sockhost, sizeof(sockhost)))
-		return 0;
-
-	hp = client->local->hostp;
-
 	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
 	{
 		exit_client(client, NULL, iConf.plaintext_policy_user_message->line);
@@ -1330,7 +1251,7 @@ int AllowClient(Client *client, char *username)
 
 	if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
 	{
-		char *msg = outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client);
+		const char *msg = outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client);
 		exit_client(client, NULL, msg);
 		return 0;
 	}
@@ -1340,62 +1261,9 @@ int AllowClient(Client *client, char *username)
 		if (aconf->flags.tls && !IsSecure(client))
 			continue;
 
-		if (hp && hp->h_name)
-		{
-			hname = hp->h_name;
-			strlcpy(fullname, hname, sizeof(fullname));
-			Debug((DEBUG_DNS, "a_il: %s->%s", sockhost, fullname));
-			if (strchr(aconf->hostname, '@'))
-			{
-				if (aconf->flags.noident)
-					strlcpy(uhost, username, sizeof(uhost));
-				else
-					strlcpy(uhost, client->ident, sizeof(uhost));
-				strlcat(uhost, "@", sizeof(uhost));
-			}
-			else
-				*uhost = '\0';
-			strlcat(uhost, fullname, sizeof(uhost));
-			if (match_simple(aconf->hostname, uhost))
-				goto attach;
-		}
-
-		if (strchr(aconf->ip, '@'))
-		{
-			if (aconf->flags.noident)
-				strlcpy(uhost, username, sizeof(uhost));
-			else
-				strlcpy(uhost, client->ident, sizeof(uhost));
-			strlcat(uhost, "@", sizeof(uhost));
-		}
-		else
-			*uhost = '\0';
-		strlcat(uhost, sockhost, sizeof(uhost));
-		/* Check the IP */
-		if (match_user(aconf->ip, client, MATCH_CHECK_IP))
-			goto attach;
-
-		/* Hmm, localhost is a special case, hp == NULL and sockhost contains
-		 * 'localhost' instead of an ip... -- Syzop. */
-		if (!strcmp(sockhost, "localhost"))
-		{
-			if (strchr(aconf->hostname, '@'))
-			{
-				if (aconf->flags.noident)
-					strlcpy(uhost, username, sizeof(uhost));
-				else
-					strlcpy(uhost, client->ident, sizeof(uhost));
-				strlcat(uhost, "@localhost", sizeof(uhost));
-			}
-			else
-				strcpy(uhost, "localhost");
-
-			if (match_simple(aconf->hostname, uhost))
-				goto attach;
-		}
+		if (!unreal_mask_match(client, aconf->mask))
+			continue;
 
-		continue; /* No match */
-	attach:
 		/* Check authentication */
 		if (aconf->auth && !Auth_Check(client, aconf->auth, client->local->passwd))
 		{
@@ -1411,11 +1279,9 @@ int AllowClient(Client *client, char *username)
 
 		if (!aconf->flags.noident)
 			SetUseIdent(client);
-		if (!aconf->flags.useip && hp)
-			strlcpy(uhost, fullname, sizeof(uhost));
-		else
-			strlcpy(uhost, sockhost, sizeof(uhost));
-		set_sockhost(client, uhost);
+
+		if (aconf->flags.useip)
+			set_sockhost(client, GetIP(client));
 
 		if (exceeds_maxperip(client, aconf))
 		{
@@ -1432,7 +1298,7 @@ int AllowClient(Client *client, char *username)
 		else
 		{
 			/* Class is full */
-			sendnumeric(client, RPL_REDIR, aconf->server ? aconf->server : defserv, aconf->port ? aconf->port : 6667);
+			sendnumeric(client, RPL_REDIR, aconf->server ? aconf->server : DEFAULT_SERVER, aconf->port ? aconf->port : 6667);
 			exit_client(client, NULL, iConf.reject_message_server_full);
 			return 0;
 		}
diff --git a/src/modules/nocodes.c b/src/modules/nocodes.c
@@ -26,10 +26,10 @@ ModuleHeader MOD_HEADER
 	"1.3", /* Version */
 	"Strip/block color/bold/underline/italic/reverse - by Syzop", /* Short description of module */
 	"UnrealIRCd Team", /* Author */
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
-int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
 
 MOD_INIT()
 {
@@ -49,7 +49,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static int has_controlcodes(char *p)
+static int has_controlcodes(const char *p)
 {
 	for (; *p; p++)
 		if ((*p == '\002') || (*p == '\037') || (*p == '\026') || (*p == '\035')) /* bold, underline, reverse, italic */
@@ -57,7 +57,7 @@ static int has_controlcodes(char *p)
 	return 0;
 }
 
-int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	static char retbuf[4096];
 	Hook *h;
diff --git a/src/modules/oper.c b/src/modules/oper.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /oper", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -82,7 +82,7 @@ void set_oper_host(Client *client, char *host)
 CMD_FUNC(cmd_oper)
 {
 	ConfigItem_oper *operblock;
-	char *name, *password;
+	const char *operblock_name, *password;
 	long old_umodes = client->umodes & ALL_UMODES;
 
 	if (!MyUser(client))
@@ -103,24 +103,23 @@ CMD_FUNC(cmd_oper)
 
 	if (IsOper(client))
 	{
-		sendnumeric(client, RPL_YOUREOPER);
-		// TODO: de-confuse this ? ;)
+		sendnotice(client, "You are already an IRC Operator. If you want to re-oper then de-oper first via /MODE yournick -o");
 		return;
 	}
 
-	name = parv[1];
+	operblock_name = parv[1];
 	password = (parc > 2) ? parv[2] : "";
 
 	/* set::plaintext-policy::oper 'deny' */
 	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_DENY))
 	{
 		sendnotice_multiline(client, iConf.plaintext_policy_oper_message);
-		sendto_snomask_global
-		    (SNO_OPER, "Failed OPER attempt by %s (%s@%s) [not using SSL/TLS]",
-		    client->name, client->user->username, client->local->sockhost);
-		ircd_log(LOG_OPER, "OPER NO-SSL/TLS (%s) by (%s!%s@%s)", name, client->name,
-			client->user->username, client->local->sockhost);
-		client->local->since += 7;
+		unreal_log(ULOG_WARNING, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Not using TLS"),
+		           log_data_string("fail_type", "NO_TLS"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 7000);
 		return;
 	}
 
@@ -128,36 +127,40 @@ CMD_FUNC(cmd_oper)
 	if (IsSecure(client) && (iConf.outdated_tls_policy_oper == POLICY_DENY) && outdated_tls_client(client))
 	{
 		sendnotice(client, "%s", outdated_tls_client_build_string(iConf.outdated_tls_policy_oper_message, client));
-		sendto_snomask_global
-		    (SNO_OPER, "Failed OPER attempt by %s (%s@%s) [outdated SSL/TLS protocol or cipher]",
-		    client->name, client->user->username, client->local->sockhost);
-		ircd_log(LOG_OPER, "OPER OUTDATED-SSL/TLS (%s) by (%s!%s@%s)", name, client->name,
-			client->user->username, client->local->sockhost);
-		client->local->since += 7;
+		unreal_log(ULOG_WARNING, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Outdated TLS protocol or cipher"),
+		           log_data_string("fail_type", "OUTDATED_TLS_PROTOCOL_OR_CIPHER"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 7000);
 		return;
 	}
 
-	if (!(operblock = find_oper(name)))
+	if (!(operblock = find_oper(operblock_name)))
 	{
 		sendnumeric(client, ERR_NOOPERHOST);
-		sendto_snomask_global
-		    (SNO_OPER, "Failed OPER attempt by %s (%s@%s) [unknown oper]",
-		    client->name, client->user->username, client->local->sockhost);
-		ircd_log(LOG_OPER, "OPER UNKNOWNOPER (%s) by (%s!%s@%s)", name, client->name,
-			client->user->username, client->local->sockhost);
-		client->local->since += 7;
+		unreal_log(ULOG_WARNING, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Unknown oper operblock_name"),
+		           log_data_string("fail_type", "UNKNOWN_OPER_NAME"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 7000);
 		return;
 	}
 
+	/* Below here, the oper block exists, any errors here we take (even)
+	 * more seriously, they are logged as errors instead of warnings.
+	 */
+
 	if (!unreal_mask_match(client, operblock->mask))
 	{
 		sendnumeric(client, ERR_NOOPERHOST);
-		sendto_snomask_global
-		    (SNO_OPER, "Failed OPER attempt by %s (%s@%s) using UID %s [host doesnt match]",
-		    client->name, client->user->username, client->local->sockhost, name);
-		ircd_log(LOG_OPER, "OPER NOHOSTMATCH (%s) by (%s!%s@%s)", name, client->name,
-			client->user->username, client->local->sockhost);
-		client->local->since += 7;
+		unreal_log(ULOG_ERROR, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Host does not match"),
+		           log_data_string("fail_type", "NO_HOST_MATCH"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 7000);
 		return;
 	}
 
@@ -167,12 +170,12 @@ CMD_FUNC(cmd_oper)
 		if (FAILOPER_WARN)
 			sendnotice(client,
 			    "*** Your attempt has been logged.");
-		ircd_log(LOG_OPER, "OPER FAILEDAUTH (%s) by (%s!%s@%s)", name, client->name,
-			client->user->username, client->local->sockhost);
-		sendto_snomask_global
-		    (SNO_OPER, "Failed OPER attempt by %s (%s@%s) using UID %s [FAILEDAUTH]",
-		    client->name, client->user->username, client->local->sockhost, name);
-		client->local->since += 7;
+		unreal_log(ULOG_ERROR, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Authentication failed"),
+		           log_data_string("fail_type", "AUTHENTICATION_FAILED"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 7000);
 		return;
 	}
 
@@ -185,25 +188,23 @@ CMD_FUNC(cmd_oper)
 	if (operblock->require_modes & ~client->umodes)
 	{
 		sendnumericfmt(client, ERR_NOOPERHOST, ":You are missing user modes required to OPER");
-		sendto_snomask_global
-			(SNO_OPER, "Failed OPER attempt by %s (%s@%s) [lacking modes '%s' in oper::require-modes]",
-			 client->name, client->user->username, client->local->sockhost, get_usermode_string_raw(operblock->require_modes & ~client->umodes));
-		ircd_log(LOG_OPER, "OPER MISSINGMODES (%s) by (%s!%s@%s), needs modes=%s",
-			 name, client->name, client->user->username, client->local->sockhost,
-			 get_usermode_string_raw(operblock->require_modes & ~client->umodes));
-		client->local->since += 7;
+		unreal_log(ULOG_WARNING, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Not matching oper::require-modes"),
+		           log_data_string("fail_type", "REQUIRE_MODES_NOT_SATISFIED"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 7000);
 		return;
 	}
 
 	if (!find_operclass(operblock->operclass))
 	{
 		sendnotice(client, "ERROR: There is a non-existant oper::operclass specified for your oper block");
-		ircd_log(LOG_ERROR, "OPER MISSINGOPERCLASS (%s) by (%s!%s@%s), oper::operclass does not exist: %s",
-			name, client->name, client->user->username, client->local->sockhost,
-			operblock->operclass);
-		sendto_snomask_global
-			(SNO_OPER, "Failed OPER attempt by %s (%s@%s) [oper::operclass does not exist: '%s']",
-			client->name, client->user->username, client->local->sockhost, operblock->operclass);
+		unreal_log(ULOG_WARNING, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "Config error: invalid oper::operclass"),
+		           log_data_string("fail_type", "OPER_OPERCLASS_INVALID"),
+		           log_data_string("oper_block", parv[1]));
 		return;
 	}
 
@@ -212,12 +213,12 @@ CMD_FUNC(cmd_oper)
 		sendnumeric(client, ERR_NOOPERHOST);
 		sendnotice(client, "Your maximum number of concurrent oper logins has been reached (%d)",
 			operblock->maxlogins);
-		sendto_snomask_global
-			(SNO_OPER, "Failed OPER attempt by %s (%s@%s) using UID %s [maxlogins reached]",
-			client->name, client->user->username, client->local->sockhost, name);
-		ircd_log(LOG_OPER, "OPER TOOMANYLOGINS (%s) by (%s!%s@%s)", name, client->name,
-			client->user->username, client->local->sockhost);
-		client->local->since += 4;
+		unreal_log(ULOG_WARNING, "oper", "OPER_FAILED", client,
+		           "Failed OPER attempt by $client.details [reason: $reason] [oper-block: $oper_block]",
+		           log_data_string("reason", "oper::maxlogins limit reached"),
+		           log_data_string("fail_type", "OPER_MAXLOGINS_LIMIT"),
+		           log_data_string("oper_block", parv[1]));
+		add_fake_lag(client, 4000);
 		return;
 	}
 
@@ -258,13 +259,9 @@ CMD_FUNC(cmd_oper)
 		safe_strdup(client->user->virthost, client->user->cloakedhost);
 	}
 
-	sendto_snomask_global(SNO_OPER,
-		"%s (%s@%s) [%s] is now an operator",
-		client->name, client->user->username, client->local->sockhost,
-		parv[1]);
-
-	ircd_log(LOG_OPER, "OPER (%s) by (%s!%s@%s)", name, client->name, client->user->username,
-		client->local->sockhost);
+	unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client,
+		   "$client.details is now an IRC Operator [oper-block: $oper_block]",
+		   log_data_string("oper_block", parv[1]));
 
 	/* set oper snomasks */
 	if (operblock->snomask)
@@ -272,19 +269,13 @@ CMD_FUNC(cmd_oper)
 	else
 		set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */
 
-	/* some magic to set user mode +s (and snomask +s) if you have any snomasks set */
-	if (client->user->snomask)
-	{
-		client->user->snomask |= SNO_SNOTICE;
-		client->umodes |= UMODE_SERVNOTICE;
-	}
-	
 	send_umode_out(client, 1, old_umodes);
-	sendnumeric(client, RPL_SNOMASK, get_snomask_string(client));
+	if (client->user->snomask)
+		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
 
 	list_add(&client->special_node, &oper_list);
 
-	RunHook2(HOOKTYPE_LOCAL_OPER, client, 1);
+	RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock);
 
 	sendnumeric(client, RPL_YOUREOPER);
 
@@ -300,7 +291,7 @@ CMD_FUNC(cmd_oper)
 	if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0"))
 	{
 		char *chans = strdup(OPER_AUTO_JOIN_CHANS);
-		char *args[3] = {
+		const char *args[3] = {
 			client->name,
 			chans,
 			NULL
@@ -316,17 +307,19 @@ CMD_FUNC(cmd_oper)
 	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_WARN))
 	{
 		sendnotice_multiline(client, iConf.plaintext_policy_oper_message);
-		sendto_snomask_global
-		    (SNO_OPER, "OPER %s [%s] used an insecure (non-SSL/TLS) connection to /OPER.",
-		    client->name, name);
+		unreal_log(ULOG_WARNING, "oper", "OPER_UNSAFE", client,
+			   "Insecure (non-TLS) connection used to OPER up by $client.details [oper-block: $oper_block]",
+			   log_data_string("oper_block", parv[1]),
+		           log_data_string("warn_type", "NO_TLS"));
 	}
 
 	/* set::outdated-tls-policy::oper 'warn' */
 	if (IsSecure(client) && (iConf.outdated_tls_policy_oper == POLICY_WARN) && outdated_tls_client(client))
 	{
 		sendnotice(client, "%s", outdated_tls_client_build_string(iConf.outdated_tls_policy_oper_message, client));
-		sendto_snomask_global
-		    (SNO_OPER, "OPER %s [%s] used a connection with an outdated SSL/TLS protocol or cipher to /OPER.",
-		    client->name, name);
+		unreal_log(ULOG_WARNING, "oper", "OPER_UNSAFE", client,
+			   "Outdated TLS protocol/cipher used to OPER up by $client.details [oper-block: $oper_block]",
+			   log_data_string("oper_block", parv[1]),
+		           log_data_string("warn_type", "OUTDATED_TLS_PROTOCOL_OR_CIPHER"));
 	}
 }
diff --git a/src/modules/operinfo.c b/src/modules/operinfo.c
@@ -0,0 +1,99 @@
+/*
+ * Store oper login in ModData, used by WHOIS and for auditting purposes.
+ * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"operinfo",
+	"5.0",
+	"Store oper login in ModData",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+int operinfo_local_oper(Client *client, int up, ConfigItem_oper *oper_block);
+void operinfo_free(ModData *m);
+const char *operinfo_serialize(ModData *m);
+void operinfo_unserialize(const char *str, ModData *m);
+
+ModDataInfo *operlogin_md = NULL; /* Module Data structure which we acquire */
+ModDataInfo *operclass_md = NULL; /* Module Data structure which we acquire */
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "operlogin";
+	mreq.free = operinfo_free;
+	mreq.serialize = operinfo_serialize;
+	mreq.unserialize = operinfo_unserialize;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	operlogin_md = ModDataAdd(modinfo->handle, mreq);
+	if (!operlogin_md)
+		abort();
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "operclass";
+	mreq.free = operinfo_free;
+	mreq.serialize = operinfo_serialize;
+	mreq.unserialize = operinfo_unserialize;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	operclass_md = ModDataAdd(modinfo->handle, mreq);
+	if (!operclass_md)
+		abort();
+
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_OPER, 0, operinfo_local_oper);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int operinfo_local_oper(Client *client, int up, ConfigItem_oper *oper_block)
+{
+	if (up)
+	{
+		moddata_client_set(client, "operlogin", oper_block->name);
+		moddata_client_set(client, "operclass", oper_block->operclass);
+	} else {
+		moddata_client_set(client, "operlogin", NULL);
+		moddata_client_set(client, "operclass", NULL);
+	}
+	return 0;
+}
+
+void operinfo_free(ModData *m)
+{
+	safe_free(m->str);
+}
+
+const char *operinfo_serialize(ModData *m)
+{
+	if (!m->str)
+		return NULL;
+	return m->str;
+}
+
+void operinfo_unserialize(const char *str, ModData *m)
+{
+	safe_strdup(m->str, str);
+}
diff --git a/src/modules/opermotd.c b/src/modules/opermotd.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /opermotd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
diff --git a/src/modules/part.c b/src/modules/part.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /part", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -59,11 +59,12 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_part)
 {
+	char request[BUFSIZE];
 	Channel *channel;
 	Membership *lp;
 	char *p = NULL, *name;
-	char *commentx = (parc > 2 && parv[2]) ? parv[2] : NULL;
-	char *comment;
+	const char *commentx = (parc > 2 && parv[2]) ? parv[2] : NULL;
+	const char *comment;
 	int n;
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("PART");
@@ -96,7 +97,8 @@ CMD_FUNC(cmd_part)
 		}
 	}
 
-	for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL)
+	strlcpy(request, parv[1], sizeof(request));
+	for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
 	{
 		MessageTag *mtags = NULL;
 
@@ -106,7 +108,7 @@ CMD_FUNC(cmd_part)
 			break;
 		}
 
-		channel = get_channel(client, name, 0);
+		channel = find_channel(name);
 		if (!channel)
 		{
 			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
@@ -130,36 +132,30 @@ CMD_FUNC(cmd_part)
 			continue;
 		}
 
-		if (!ValidatePermissionsForPath("channel:override:banpartmsg",client,NULL,channel,NULL) && !is_chan_op(client, channel)) {
+		if (!ValidatePermissionsForPath("channel:override:banpartmsg",client,NULL,channel,NULL) && !check_channel_access(client, channel, "oaq")) {
 			/* Banned? No comment allowed ;) */
 			if (comment && is_banned(client, channel, BANCHK_MSG, &comment, NULL))
 				comment = NULL;
 			if (comment && is_banned(client, channel, BANCHK_LEAVE_MSG, &comment, NULL))
 				comment = NULL;
-			/* Same for +m */
-			if ((channel->mode.mode & MODE_MODERATED) && comment &&
-				 !has_voice(client, channel) && !is_half_op(client, channel))
-			{
-				comment = NULL;
-			}
 		}
 
 		if (MyConnect(client))
 		{
 			Hook *tmphook;
 			for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_PART]; tmphook; tmphook = tmphook->next) {
-				comment = (*(tmphook->func.pcharfunc))(client, channel, comment);
+				comment = (*(tmphook->func.stringfunc))(client, channel, comment);
 				if (!comment)
 					break;
 			}
 		}
 
 		/* Create a new message, this one is actually used by 8 calls (though at most 4 max) */
-		new_message_special(client, recv_mtags, &mtags, ":%s PART %s", client->name, channel->chname);
+		new_message_special(client, recv_mtags, &mtags, ":%s PART %s", client->name, channel->name);
 
 		/* Send to other servers... */
 		sendto_server(client, 0, 0, mtags, ":%s PART %s :%s",
-			client->id, channel->chname, comment ? comment : "");
+			client->id, channel->name, comment ? comment : "");
 
 		if (invisible_user_in_channel(client, channel))
 		{
@@ -167,29 +163,29 @@ CMD_FUNC(cmd_part)
 			if (!comment)
 			{
 				sendto_channel(channel, client, client,
-					       PREFIX_HALFOP|PREFIX_OP|PREFIX_OWNER|PREFIX_ADMIN, 0,
+					       "ho", 0,
 					       SEND_LOCAL, mtags,
 					       ":%s PART %s",
-					       client->name, channel->chname);
+					       client->name, channel->name);
 				if (MyUser(client))
 				{
 					sendto_one(client, mtags, ":%s!%s@%s PART %s",
-						client->name, client->user->username, GetHost(client), channel->chname);
+						client->name, client->user->username, GetHost(client), channel->name);
 				}
 			}
 			else
 			{
 				sendto_channel(channel, client, client,
-					       PREFIX_HALFOP|PREFIX_OP|PREFIX_OWNER|PREFIX_ADMIN, 0,
+					       "ho", 0,
 					       SEND_LOCAL, mtags,
 					       ":%s PART %s %s",
-					       client->name, channel->chname, comment);
+					       client->name, channel->name, comment);
 				if (MyUser(client))
 				{
 					sendto_one(client, mtags,
 						":%s!%s@%s PART %s %s",
 						client->name, client->user->username, GetHost(client),
-						channel->chname, comment);
+						channel->name, comment);
 				}
 			}
 		}
@@ -200,21 +196,21 @@ CMD_FUNC(cmd_part)
 			{
 				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 				               ":%s PART %s",
-				               client->name, channel->chname);
+				               client->name, channel->name);
 			} else {
 				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 				               ":%s PART %s :%s",
-				               client->name, channel->chname, comment);
+				               client->name, channel->name, comment);
 			}
 		}
 
 		if (MyUser(client))
-			RunHook4(HOOKTYPE_LOCAL_PART, client, channel, mtags, comment);
+			RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, comment);
 		else
-			RunHook4(HOOKTYPE_REMOTE_PART, client, channel, mtags, comment);
+			RunHook(HOOKTYPE_REMOTE_PART, client, channel, mtags, comment);
 
 		free_message_tags(mtags);
 
-		remove_user_from_channel(client, channel);
+		remove_user_from_channel(client, channel, 0);
 	}
 }
diff --git a/src/modules/pass.c b/src/modules/pass.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /pass", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
@@ -119,7 +119,7 @@ int _check_banned(Client *client, int exitflags)
 */
 CMD_FUNC(cmd_pass)
 {
-	char *password = parc > 1 ? parv[1] : NULL;
+	const char *password = parc > 1 ? parv[1] : NULL;
 
 	if (!MyConnect(client) || (!IsUnknown(client) && !IsHandshake(client)))
 	{
@@ -137,5 +137,5 @@ CMD_FUNC(cmd_pass)
 	safe_strldup(client->local->passwd, password, PASSWDLEN+1);
 
 	/* note: the original non-truncated password is supplied as 2nd parameter. */
-	RunHookReturn2(HOOKTYPE_LOCAL_PASS, client, password, !=0);
+	RunHookReturn(HOOKTYPE_LOCAL_PASS, !=0, client, password);
 }
diff --git a/src/modules/pingpong.c b/src/modules/pingpong.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"ping, pong and nospoof", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 /* This is called on module init, before Server Ready */
 MOD_INIT()
@@ -66,7 +66,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_ping)
 {
 	Client *target;
-	char *origin, *destination;
+	const char *origin, *destination;
 
 	if (parc < 2 || BadPtr(parv[1]))
 	{
@@ -147,7 +147,7 @@ CMD_FUNC(cmd_nospoof)
 			   me.name, client->name);
 
 	if (is_handshake_finished(client))
-		register_user(client, client->name, client->user->username, NULL, NULL, NULL);
+		register_user(client);
 }
 
 /*
@@ -158,7 +158,7 @@ CMD_FUNC(cmd_nospoof)
 CMD_FUNC(cmd_pong)
 {
 	Client *target;
-	char *origin, *destination;
+	const char *origin, *destination;
 
 	if (!IsRegistered(client))
 	{
diff --git a/src/modules/plaintext-policy.c b/src/modules/plaintext-policy.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Plaintext Policy CAP",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 MOD_INIT()
@@ -52,7 +52,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-char *plaintext_policy_capability_parameter(Client *client)
+const char *plaintext_policy_capability_parameter(Client *client)
 {
 	static char buf[128];
 	
diff --git a/src/modules/protoctl.c b/src/modules/protoctl.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /protoctl", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -94,6 +94,10 @@ CMD_FUNC(cmd_protoctl)
 		{
 			SetCapability(client, "userhost-in-names");
 		}
+		else if (IsUser(client))
+		{
+			return;
+		}
 		else if (!strcmp(name, "VL"))
 		{
 			SetVL(client);
@@ -114,6 +118,10 @@ CMD_FUNC(cmd_protoctl)
 		{
 			SetMTAGS(client);
 		}
+		else if (!strcmp(name, "NEXTBANS"))
+		{
+			SetNEXTBANS(client);
+		}
 		else if (!strcmp(name, "NICKCHARS") && value)
 		{
 			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
@@ -124,22 +132,23 @@ CMD_FUNC(cmd_protoctl)
 			 */
 			if (strstr(charsys_get_current_languages(), "utf8") && !strstr(value, "utf8"))
 			{
-				char buf[512];
-				snprintf(buf, sizeof(buf), "Server %s has utf8 in set::allowed-nickchars but %s does not. Link rejected.",
-					me.name, *client->name ? client->name : "other side");
-				sendto_realops("\002ERROR\001 %s", buf);
-				exit_client(client, NULL, buf);
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CHARSYS_INCOMPATIBLE", client,
+					   "Server link $client rejected. Server $me_name has utf8 in set::allowed-nickchars but $client does not.",
+					   log_data_string("me_name", me.name));
+				exit_client(client, NULL, "Incompatible set::allowed-nickchars setting");
 				return;
 			}
 			/* We compare the character sets to see if we should warn opers about any mismatch... */
 			if (strcmp(value, charsys_get_current_languages()))
 			{
-				sendto_realops("\002WARNING!!!!\002 Link %s does not have the same set::allowed-nickchars settings (or is "
-							"a different UnrealIRCd version), this MAY cause display issues. Our charset: '%s', theirs: '%s'",
-					get_client_name(client, FALSE), charsys_get_current_languages(), value);
+				unreal_log(ULOG_WARNING, "link", "LINK_WARNING_CHARSYS", client,
+					   "Server link $client does not have the same set::allowed-nickchars settings, "
+					   "this may possibly cause display issues. Our charset: '$our_charsys', theirs: '$their_charsys'",
+					   log_data_string("our_charsys", charsys_get_current_languages()),
+					   log_data_string("their_charsys", value));
 			}
-			if (client->serv)
-				safe_strdup(client->serv->features.nickchars, value);
+			if (client->server)
+				safe_strdup(client->server->features.nickchars, value);
 
 			/* If this is a runtime change (so post-handshake): */
 			if (IsServer(client))
@@ -155,15 +164,13 @@ CMD_FUNC(cmd_protoctl)
 			their_value = allowed_channelchars_strtoval(value);
 			if (their_value != iConf.allowed_channelchars)
 			{
-				char linkerr[256];
-				ircsnprintf(linkerr, sizeof(linkerr),
-					"Link rejected. Server %s has set::allowed-channelchars '%s' "
-					"while %s has a value of '%s'. "
-					"Please choose the same value on all servers.",
-					client->name, value,
-					me.name, allowed_channelchars_valtostr(iConf.allowed_channelchars));
-				sendto_realops("ERROR: %s", linkerr);
-				exit_client(client, NULL, linkerr);
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_ALLOWED_CHANNELCHARS_INCOMPATIBLE", client,
+					   "Server link $client rejected. Server has set::allowed-channelchars setting "
+					   "of $their_allowed_channelchars, while we have $our_allowed_channelchars.\n"
+					   "Please set set::allowed-channelchars to the same value on all servers.",
+					   log_data_string("their_allowed_channelchars", value),
+					   log_data_string("our_allowed_channelchars", allowed_channelchars_valtostr(iConf.allowed_channelchars)));
+				exit_client(client, NULL, "Incompatible set::allowed-channelchars setting");
 				return;
 			}
 		}
@@ -198,9 +205,10 @@ CMD_FUNC(cmd_protoctl)
 
 			if ((aclient = hash_find_id(sid, NULL)) != NULL)
 			{
-				sendto_one(client, NULL, "ERROR :SID %s already exists from %s", aclient->id, aclient->name);
-				sendto_snomask(SNO_SNOTICE, "Link %s rejected - SID %s already exists from %s",
-						get_client_name(client, FALSE), aclient->id, aclient->name);
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SID_COLLISION", client,
+					   "Server link $client rejected. Server with SID $sid already exist via uplink $exiting_client.server.uplink.",
+					   log_data_string("sid", sid),
+					   log_data_client("existing_client", aclient));
 				exit_client(client, NULL, "SID collision");
 				return;
 			}
@@ -236,13 +244,8 @@ CMD_FUNC(cmd_protoctl)
 			}
 			
 			servername = strtoken(&p, buf, ",");
-			if (!servername || (strlen(servername) > HOSTLEN) || !strchr(servername, '.'))
+			if (!servername || !valid_server_name(servername))
 			{
-				sendto_one(client, NULL, "ERROR :Bogus server name in EAUTH (%s)", servername ? servername : "");
-				sendto_snomask
-				    (SNO_JUNK,
-				    "WARNING: Bogus server name (%s) from %s in EAUTH (maybe just a fishy client)",
-				    servername ? servername : "", get_client_name(client, TRUE));
 				exit_client(client, NULL, "Bogus server name");
 				return;
 			}
@@ -258,7 +261,12 @@ CMD_FUNC(cmd_protoctl)
 				}
 			}
 			
-			if (!verify_link(client, servername, &aconf))
+			/* Set client->name but don't add to hash list, this gives better
+			 * log messages and should be safe. See CMTSRV941 in server.c.
+			 */
+			strlcpy(client->name, servername, sizeof(client->name));
+
+			if (!verify_link(client, &aconf))
 				return;
 
 			/* note: software, protocol and flags may be NULL */
@@ -266,16 +274,11 @@ CMD_FUNC(cmd_protoctl)
 				return;
 
 			SetEAuth(client);
-			make_server(client); /* allocate and set client->serv */
-			/* Set client->name but don't add to hash list. The real work on
-			 * that is done in cmd_server. We just set it here for display
-			 * purposes of error messages (such as reject due to clock).
-			 */
-			strlcpy(client->name, servername, sizeof(client->name));
+			make_server(client); /* allocate and set client->server */
 			if (protocol)
-				client->serv->features.protocol = atoi(protocol);
+				client->server->features.protocol = atoi(protocol);
 			if (software)
-				safe_strdup(client->serv->features.software, software);
+				safe_strdup(client->server->features.software, software);
 			if (!IsHandshake(client) && aconf) /* Send PASS early... */
 				sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
 		}
@@ -287,12 +290,12 @@ CMD_FUNC(cmd_protoctl)
 			if (!IsEAuth(client))
 				continue;
 				
-			if (client->serv->features.protocol < 2351)
+			if (client->server->features.protocol < 2351)
 				continue; /* old SERVERS= version */
 			
 			/* Other side lets us know which servers are behind it.
 			 * SERVERS=<sid-of-server-1>[,<sid-of-server-2[,..etc..]]
-			 * Eg: SERVER=001,002,0AB,004,005
+			 * Eg: SERVERS=001,002,0AB,004,005
 			 */
 
 			add_pending_net(client, value);
@@ -300,10 +303,9 @@ CMD_FUNC(cmd_protoctl)
 			aclient = find_non_pending_net_duplicates(client);
 			if (aclient)
 			{
-				sendto_one(client, NULL, "ERROR :Server with SID %s (%s) already exists",
-					aclient->id, aclient->name);
-				sendto_realops("Link %s cancelled, server with SID %s (%s) already exists",
-					get_client_name(aclient, TRUE), aclient->id, aclient->name);
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DUPLICATE_SID", client,
+					   "Denied server $client: Server with SID $existing_client.id ($existing_client) is already linked.",
+					   log_data_client("existing_client", aclient));
 				exit_client(client, NULL, "Server Exists (or non-unique me::sid)");
 				return;
 			}
@@ -311,12 +313,13 @@ CMD_FUNC(cmd_protoctl)
 			aclient = find_pending_net_duplicates(client, &srv, &sid);
 			if (aclient)
 			{
-				sendto_one(client, NULL, "ERROR :Server with SID %s is being introduced by another server as well. "
-				                 "Just wait a moment for it to synchronize...", sid);
-				sendto_realops("Link %s cancelled, server would introduce server with SID %s, which "
-				               "server %s is also about to introduce. Just wait a moment for it to synchronize...",
-				               get_client_name(aclient, TRUE), sid, get_client_name(srv, TRUE));
-				exit_client(client, NULL, "Server Exists (just wait a moment)");
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DUPLICATE_SID_LINKED", client,
+					   "Denied server $client: Server would (later) introduce SID $sid, "
+					   "but we already have SID $sid linked ($existing_client)\n"
+					   "Possible race condition, just wait a moment for the network to synchronize...",
+					   log_data_string("sid", sid),
+					   log_data_client("existing_client", aclient));
+				exit_client(client, NULL, "Server Exists (just wait a moment...)");
 				return;
 			}
 
@@ -327,51 +330,34 @@ CMD_FUNC(cmd_protoctl)
 		else if (!strcmp(name, "TS") && value && (IsServer(client) || IsEAuth(client)))
 		{
 			long t = atol(value);
-			char msg[512], linkerr[512];
-			
+
 			if (t < 10000)
 				continue; /* ignore */
-			
-			*msg = *linkerr = '\0';
-			
+
 			if ((TStime() - t) > MAX_SERVER_TIME_OFFSET)
 			{
-				snprintf(linkerr, sizeof(linkerr),
-				         "Your clock is %lld seconds behind my clock. "
-				         "Please verify both your clock and mine, "
-				         "fix it and try linking again.",
-				         (long long)(TStime() - t));
-				snprintf(msg, sizeof(msg),
-				         "Rejecting link %s: our clock is %lld seconds ahead. "
-				         "Please verify the clock on both %s (them) and %s (us). "
-				         "Correct time is very important for IRC servers, "
-				         "see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
-				         get_client_name(client, TRUE),
-				         (long long)(TStime() - t),
-				         client->name, me.name);
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CLOCK_INCORRECT", client,
+				           "Denied server $client: clock on server $client is $time_delta "
+				           "seconds behind the clock of $me_name.\n"
+				           "Correct time is very important for IRC servers, "
+				           "see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
+				           log_data_integer("time_delta", TStime() - t),
+				           log_data_string("me_name", me.name));
+				exit_client_fmt(client, NULL, "Incorrect clock. Our clocks are %lld seconds apart.",
+				                (long long)(TStime() - t));
+				return;
 			} else
 			if ((t - TStime()) > MAX_SERVER_TIME_OFFSET)
 			{
-				snprintf(linkerr, sizeof(linkerr),
-				         "Your clock is %lld seconds ahead of my clock. "
-				         "Please verify both your clock and mine, fix it, "
-				         "and try linking again.",
-				         (long long)(t - TStime()));
-				snprintf(msg, sizeof(msg),
-				         "Rejecting link %s: our clock is %lld seconds behind. "
-				         "Please verify the clock on both %s (them) and %s (us). "
-				         "Correct time is very important for IRC servers, "
-				         "see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
-					get_client_name(client, TRUE),
-					(long long)(t - TStime()),
-					client->name, me.name);
-			}
-			
-			if (*msg)
-			{
-				sendto_realops("%s", msg);
-				ircd_log(LOG_ERROR, "%s", msg);
-				exit_client(client, NULL, linkerr);
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CLOCK_INCORRECT", client,
+				           "Denied server $client: clock on server $client is $time_delta "
+				           "seconds ahead the clock of $me_name.\n"
+				           "Correct time is very important for IRC servers, "
+				           "see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
+				           log_data_integer("time_delta", t - TStime()),
+				           log_data_string("me_name", me.name));
+				exit_client_fmt(client, NULL, "Incorrect clock. Our clocks are %lld seconds apart.",
+				                (long long)(t - TStime()));
 				return;
 			}
 		}
@@ -379,23 +365,23 @@ CMD_FUNC(cmd_protoctl)
 		{
 			client->local->proto |= PROTO_MLOCK;
 		}
-		else if (!strcmp(name, "CHANMODES") && value && client->serv)
+		else if (!strcmp(name, "CHANMODES") && value && client->server)
 		{
 			parse_chanmodes_protoctl(client, value);
 			/* If this is a runtime change (so post-handshake): */
 			if (IsServer(client))
 				broadcast_sinfo(client, NULL, client);
 		}
-		else if (!strcmp(name, "USERMODES") && value && client->serv)
+		else if (!strcmp(name, "USERMODES") && value && client->server)
 		{
-			safe_strdup(client->serv->features.usermodes, value);
+			safe_strdup(client->server->features.usermodes, value);
 			/* If this is a runtime change (so post-handshake): */
 			if (IsServer(client))
 				broadcast_sinfo(client, NULL, client);
 		}
-		else if (!strcmp(name, "BOOTED") && value && client->serv)
+		else if (!strcmp(name, "BOOTED") && value && client->server)
 		{
-			client->serv->boottime = atol(value);
+			client->server->boottime = atol(value);
 		}
 		else if (!strcmp(name, "EXTSWHOIS"))
 		{
@@ -409,7 +395,7 @@ CMD_FUNC(cmd_protoctl)
 		 */
 	}
 
-	if (first_protoctl && IsHandshake(client) && client->serv && !IsServerSent(client)) /* first & outgoing connection to server */
+	if (first_protoctl && IsHandshake(client) && client->server && !IsServerSent(client)) /* first & outgoing connection to server */
 	{
 		/* SERVER message moved from completed_connection() to here due to EAUTH/SERVERS PROTOCTL stuff,
 		 * which needed to be delayed until after both sides have received SERVERS=xx (..or not.. in case
diff --git a/src/modules/quit.c b/src/modules/quit.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /quit", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -59,11 +59,17 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_quit)
 {
-	char *comment = (parc > 1 && parv[1]) ? parv[1] : client->name;
-	static char commentbuf[MAXQUITLEN + 1];
+	const char *comment = (parc > 1 && parv[1]) ? parv[1] : client->name;
+	char commentbuf[MAXQUITLEN + 1];
+	char commentbuf2[MAXQUITLEN + 1];
 
-	if (parv[1] && (strlen(comment) > iConf.quit_length))
-		comment[iConf.quit_length] = '\0';
+	if (parc > 1 && parv[1])
+	{
+		strlncpy(commentbuf, parv[1], sizeof(commentbuf), iConf.quit_length);
+		comment = commentbuf;
+	} else {
+		comment = client->name;
+	}
 
 	if (MyUser(client))
 	{
@@ -91,14 +97,14 @@ CMD_FUNC(cmd_quit)
 		
 		if (!ValidatePermissionsForPath("immune:anti-spam-quit-message-time",client,NULL,NULL,NULL) && ANTI_SPAM_QUIT_MSG_TIME)
 		{
-			if (client->local->firsttime+ANTI_SPAM_QUIT_MSG_TIME > TStime())
+			if (client->local->creationtime+ANTI_SPAM_QUIT_MSG_TIME > TStime())
 				comment = client->name;
 		}
 
 		if (iConf.part_instead_of_quit_on_comment_change && MyUser(client))
 		{
 			Membership *lp, *lp_next;
-			char *newcomment;
+			const char *newcomment;
 			Channel *channel;
 
 			for (lp = client->user->channel; lp; lp = lp_next)
@@ -109,7 +115,7 @@ CMD_FUNC(cmd_quit)
 
 				for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_QUIT_CHAN]; tmphook; tmphook = tmphook->next)
 				{
-					newcomment = (*(tmphook->func.pcharfunc))(client, channel, comment);
+					newcomment = (*(tmphook->func.stringfunc))(client, channel, comment);
 					if (!newcomment)
 						break;
 				}
@@ -120,13 +126,21 @@ CMD_FUNC(cmd_quit)
 				/* Comment changed? Then PART the user before we do the QUIT. */
 				if (comment != newcomment)
 				{
-					char *parx[4];
+					const char *parx[4];
+					char tmp[512];
 					int ret;
 
+
 					parx[0] = NULL;
-					parx[1] = channel->chname;
-					parx[2] = newcomment;
-					parx[3] = NULL;
+					parx[1] = channel->name;
+					if (newcomment)
+					{
+						strlcpy(tmp, newcomment, sizeof(tmp));
+						parx[2] = tmp;
+						parx[3] = NULL;
+					} else {
+						parx[2] = NULL;
+					}
 
 					do_cmd(client, recv_mtags, "PART", newcomment ? 3 : 2, parx);
 					/* This would be unusual, but possible (somewhere in the future perhaps): */
@@ -138,7 +152,7 @@ CMD_FUNC(cmd_quit)
 
 		for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_QUIT]; tmphook; tmphook = tmphook->next)
 		{
-			comment = (*(tmphook->func.pcharfunc))(client, comment);
+			comment = (*(tmphook->func.stringfunc))(client, comment);
 			if (!comment)
 			{			
 				comment = client->name;
@@ -147,11 +161,11 @@ CMD_FUNC(cmd_quit)
 		}
 
 		if (PREFIX_QUIT)
-			snprintf(commentbuf, sizeof(commentbuf), "%s: %s", PREFIX_QUIT, comment);
+			snprintf(commentbuf2, sizeof(commentbuf2), "%s: %s", PREFIX_QUIT, comment);
 		else
-			strlcpy(commentbuf, comment, sizeof(commentbuf));
+			strlcpy(commentbuf2, comment, sizeof(commentbuf2));
 
-		exit_client(client, recv_mtags, commentbuf);
+		exit_client(client, recv_mtags, commentbuf2);
 	}
 	else
 	{
diff --git a/src/modules/reply-tag.c b/src/modules/reply-tag.c
@@ -30,11 +30,11 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"+reply client tag",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
-int replytag_mtag_is_ok(Client *client, char *name, char *value);
-void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int replytag_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -73,9 +73,9 @@ MOD_UNLOAD()
 
 /** This function verifies if the client sending the mtag is permitted to do so.
  */
-int replytag_mtag_is_ok(Client *client, char *name, char *value)
+int replytag_mtag_is_ok(Client *client, const char *name, const char *value)
 {
-	char *p;
+	const char *p;
 
 	/* Require a non-empty parameter */
 	if (BadPtr(value))
@@ -92,7 +92,7 @@ int replytag_mtag_is_ok(Client *client, char *name, char *value)
 	return 1; /* OK */
 }
 
-void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m;
 
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
@@ -64,7 +64,7 @@ ModuleHeader MOD_HEADER
 	REPUTATION_VERSION,
 	"Known IP's scoring system",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Defines */
@@ -81,9 +81,10 @@ ModuleHeader MOD_HEADER
 
 #define WARN_WRITE_ERROR(fname) \
 	do { \
-		sendto_realops_and_log("[reputation] Error writing to temporary database file " \
-		                       "'%s': %s (DATABASE NOT SAVED)", \
-		                       fname, unrealdb_get_error_string()); \
+		unreal_log(ULOG_ERROR, "reputation", "REPUTATION_FILE_WRITE_ERROR", NULL, \
+			   "[reputation] 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) \
@@ -131,21 +132,21 @@ ModDataInfo *reputation_md; /* Module Data structure which we acquire */
 
 /* Forward declarations */
 void reputation_md_free(ModData *m);
-char *reputation_md_serialize(ModData *m);
-void reputation_md_unserialize(char *str, ModData *m);
+const char *reputation_md_serialize(ModData *m);
+void reputation_md_unserialize(const char *str, ModData *m);
 void reputation_config_setdefaults(struct cfgstruct *cfg);
 void reputation_free_config(struct cfgstruct *cfg);
 CMD_FUNC(reputation_cmd);
 CMD_FUNC(reputationunperm);
-int reputation_whois(Client *client, Client *target);
+int reputation_whois(Client *client, Client *target, NameValuePrioList **list);
 int reputation_set_on_connect(Client *client);
 int reputation_pre_lconnect(Client *client);
 int reputation_connect_extinfo(Client *client, NameValuePrioList **list);
 int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
 int reputation_config_posttest(int *errs);
-static uint64_t hash_reputation_entry(char *ip);
-ReputationEntry *find_reputation_entry(char *ip);
+static uint64_t hash_reputation_entry(const char *ip);
+ReputationEntry *find_reputation_entry(const char *ip);
 void add_reputation_entry(ReputationEntry *e);
 EVENT(delete_old_records);
 EVENT(add_scores);
@@ -162,7 +163,7 @@ MOD_TEST()
 	reputation_config_setdefaults(&test);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reputation_config_test);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, reputation_config_posttest);
-	CallbackAddEx(modinfo->handle, CALLBACKTYPE_REPUTATION_STARTTIME, reputation_starttime_callback);
+	CallbackAdd(modinfo->handle, CALLBACKTYPE_REPUTATION_STARTTIME, reputation_starttime_callback);
 	return MOD_SUCCESS;
 }
 
@@ -239,7 +240,7 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
-	if (loop.ircd_terminating)
+	if (loop.terminating)
 		reputation_save_db();
 	reputation_free_config(&test);
 	reputation_free_config(&cfg);
@@ -283,37 +284,37 @@ int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 
 	/* We are only interrested in set::reputation.. */
-	if (!ce || strcmp(ce->ce_varname, "reputation"))
+	if (!ce || strcmp(ce->name, "reputation"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
 			config_error("%s:%i: blank set::reputation::%s without value",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		} else
-		if (!strcmp(cep->ce_varname, "database"))
+		if (!strcmp(cep->name, "database"))
 		{
-			convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
-			safe_strdup(test.database, cep->ce_vardata);
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "db-secret"))
+		if (!strcmp(cep->name, "db-secret"))
 		{
-			char *err;
-			if ((err = unrealdb_test_secret(cep->ce_vardata)))
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
 			{
-				config_error("%s:%i: set::channeldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+				config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
 				errors++;
 				continue;
 			}
-			safe_strdup(test.db_secret, cep->ce_vardata);
+			safe_strdup(test.db_secret, cep->value);
 		} else
 		{
 			config_error("%s:%i: unknown directive set::reputation::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
@@ -331,18 +332,18 @@ int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 
 	/* We are only interrested in set::reputation.. */
-	if (!ce || strcmp(ce->ce_varname, "reputation"))
+	if (!ce || strcmp(ce->name, "reputation"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "database"))
+		if (!strcmp(cep->name, "database"))
 		{
-			safe_strdup(cfg.database, cep->ce_vardata);
+			safe_strdup(cfg.database, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "db-secret"))
+		if (!strcmp(cep->name, "db-secret"))
 		{
-			safe_strdup(cfg.db_secret, cep->ce_vardata);
+			safe_strdup(cfg.db_secret, cep->value);
 		}
 	}
 	return 1;
@@ -465,8 +466,9 @@ void reputation_load_db_old(void)
 
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "Reputation benchmark: LOAD DB: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+	unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL,
+	           "[reputation] 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
 }
 
@@ -529,8 +531,9 @@ int reputation_load_db_new(UnrealDB *db)
 	unrealdb_close(db);
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "Reputation benchmark: LOAD DB: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+	unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL,
+	           "Reputation 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;
 }
@@ -656,8 +659,9 @@ write_fail:
 
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "Reputation benchmark: SAVE DB: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+	unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL,
+	           "Reputation benchmark: SAVE 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;
@@ -677,7 +681,7 @@ int reputation_save_db(void)
 #endif
 
 #ifdef TEST
-	sendto_realops("REPUTATION IS RUNNING IN TEST MODE. SAVING DB'S...");
+	unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_TEST", NULL, "Reputation in running in test mode. Saving DB's....");
 #endif
 
 	/* Comment this out after one or more releases (means you cannot downgrade to <=5.0.9.1 anymore) */
@@ -741,13 +745,14 @@ int reputation_save_db(void)
 
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "Reputation benchmark: SAVE DB: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+	unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL,
+	           "Reputation benchmark: SAVE 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;
 }
 
-static uint64_t hash_reputation_entry(char *ip)
+static uint64_t hash_reputation_entry(const char *ip)
 {
 	return siphash(ip, siphashkey_reputation) % REPUTATION_HASH_TABLE_SIZE;
 }
@@ -759,7 +764,7 @@ void add_reputation_entry(ReputationEntry *e)
 	AddListItem(e, ReputationHashTable[hashv]);
 }
 
-ReputationEntry *find_reputation_entry(char *ip)
+ReputationEntry *find_reputation_entry(const char *ip)
 {
 	ReputationEntry *e;
 	int hashv = hash_reputation_entry(ip);
@@ -924,8 +929,11 @@ EVENT(delete_old_records)
 			if (is_reputation_expired(e))
 			{
 #ifdef DEBUGMODE
-				ircd_log(LOG_ERROR, "Deleting expired entry for '%s' (score %hd, last seen %lld seconds ago)",
-				         e->ip, e->score, (long long)(TStime() - e->last_seen));
+				unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_EXPIRY", NULL,
+				           "Deleting expired entry for $ip (score $score, last seen $time_delta seconds ago)",
+				           log_data_string("ip", e->ip),
+				           log_data_integer("score", e->score),
+				           log_data_integer("time_delta", TStime() - e->last_seen));
 #endif
 				DelListItem(e, ReputationHashTable[i]);
 				safe_free(e);
@@ -935,8 +943,9 @@ EVENT(delete_old_records)
 
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "Reputation benchmark: EXPIRY IN MEM: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+	unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_BENCHMARK", NULL,
+	           "Reputation benchmark: EXPIRY IN MEM: $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
 }
 
@@ -955,9 +964,9 @@ CMD_FUNC(reputationunperm)
 
 	ModuleSetOptions(ModInf.handle, MOD_OPT_PERM, 0);
 
-	sendto_realops("%s used /REPUTATIONUNPERM. On next REHASH the module can be RELOADED or UNLOADED. "
-	               "Note however that for a few minutes the scoring may be skipped, so don't do this too often.",
-	               client->name);
+	unreal_log(ULOG_INFO, "reputation", "REPUTATIONUNPERM_COMMAND", client,
+	           "$client used /REPUTATIONUNPERM. On next REHASH the module can be RELOADED or UNLOADED. "
+	           "Note however that for a few minutes the scoring may be skipped, so don't do this too often.");
 }
 
 int reputation_connect_extinfo(Client *client, NameValuePrioList **list)
@@ -989,7 +998,7 @@ void reputation_channel_query(Client *client, Channel *channel)
 	int cnt = 0, i, j;
 	ReputationEntry *e;
 
-	sendtxtnumeric(client, "Users and reputation scores for %s:", channel->chname);
+	sendtxtnumeric(client, "Users and reputation scores for %s:", channel->name);
 
 	/* Step 1: build a list of nicks and their reputation */
 	nicks = safe_alloc((channel->users+1) * sizeof(char *));
@@ -1005,8 +1014,11 @@ void reputation_channel_query(Client *client, Channel *channel)
 		}
 		if (++cnt > channel->users)
 		{
-			sendto_ops("[BUG] reputation_channel_query() expected %d users but %d (or more) were present in %s",
-				channel->users, cnt, channel->chname);
+			unreal_log(ULOG_WARNING, "bug", "REPUTATION_CHANNEL_QUERY_BUG", client,
+				   "[BUG] reputation_channel_query() expected $expected_users users, but $found_users (or more) users were present in $channel",
+				   log_data_integer("expected_users", channel->users),
+				   log_data_integer("found_users", cnt),
+				   log_data_string("channel", channel->name));
 #ifdef DEBUGMODE
 			abort();
 #endif
@@ -1083,7 +1095,7 @@ void reputation_list_query(Client *client, int maxscore)
 CMD_FUNC(reputation_user_cmd)
 {
 	ReputationEntry *e;
-	char *ip;
+	const char *ip;
 
 	if (!IsOper(client))
 	{
@@ -1121,16 +1133,16 @@ CMD_FUNC(reputation_user_cmd)
 	} else
 	if (parv[1][0] == '#')
 	{
-		Channel *channel = find_channel(parv[1], NULL);
+		Channel *channel = find_channel(parv[1]);
 		if (!channel)
 		{
 			sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
 			return;
 		}
 		/* corner case: ircop without proper permissions and not in channel */
-		if (!ValidatePermissionsForPath("channel:see:names:invisible",client,NULL,NULL,NULL) && !get_access(client,channel))
+		if (!ValidatePermissionsForPath("channel:see:names:invisible",client,NULL,NULL,NULL) && !IsMember(client,channel))
 		{
-			sendnumeric(client, ERR_NOTONCHANNEL, channel->chname);
+			sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
 			return;
 		}
 		reputation_channel_query(client, channel);
@@ -1147,7 +1159,7 @@ CMD_FUNC(reputation_user_cmd)
 		reputation_list_query(client, max);
 		return;
 	} else {
-		Client *target = find_person(parv[1], NULL);
+		Client *target = find_user(parv[1], NULL);
 		if (!target)
 		{
 			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
@@ -1206,7 +1218,7 @@ CMD_FUNC(reputation_user_cmd)
 CMD_FUNC(reputation_server_cmd)
 {
 	ReputationEntry *e;
-	char *ip;
+	const char *ip;
 	int score;
 	int allow_reply;
 
@@ -1239,8 +1251,11 @@ CMD_FUNC(reputation_server_cmd)
 		 */
 		sendto_one(client, NULL, ":%s REPUTATION %s *%d", me.id, parv[1], e->score);
 #ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "[reputation] Score for '%s' from %s is %d, but we have %d, sending back %d",
-			ip, client->name, score, e->score, e->score);
+		unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_DIFFERS", client,
+			   "Reputation score for for $ip from $client is $their_score, but we have $score, sending back $score",
+			   log_data_string("ip", ip),
+			   log_data_integer("their_score", score),
+			   log_data_integer("score", e->score));
 #endif
 		score = e->score; /* Update for propagation in the non-client direction */
 	}
@@ -1249,8 +1264,11 @@ CMD_FUNC(reputation_server_cmd)
 	if (e && (score > e->score))
 	{
 #ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "[reputation] Score for '%s' from %s is %d, but we have %d, updating our score to %d",
-			ip, client->name, score, e->score, score);
+		unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_DIFFERS", client,
+			   "Reputation score for for $ip from $client is $their_score, but we have $score, updating our score to $score",
+			   log_data_string("ip", ip),
+			   log_data_integer("their_score", score),
+			   log_data_integer("score", e->score));
 #endif
 		e->score = score;
 	}
@@ -1259,8 +1277,11 @@ CMD_FUNC(reputation_server_cmd)
 	if (!e && (score > 0))
 	{
 #ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "[reputation] Score for '%s' from %s is %d, we had no entry, adding it",
-			ip, client->name, score);
+		unreal_log(ULOG_DEBUG, "reputation", "REPUTATION_NEW", client,
+			   "Reputation score for for $ip from $client is $their_score, we had no entry, adding it",
+			   log_data_string("ip", ip),
+			   log_data_integer("their_score", score),
+			   log_data_integer("score", 0));
 #endif
 		e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
 		strcpy(e->ip, ip); /* safe, see alloc above */
@@ -1286,18 +1307,19 @@ CMD_FUNC(reputation_cmd)
 		reputation_server_cmd(client, recv_mtags, parc, parv);
 }
 
-int reputation_whois(Client *client, Client *target)
+int reputation_whois(Client *client, Client *target, NameValuePrioList **list)
 {
-	int reputation = Reputation(target);
+	int reputation;
 
-	if (!IsOper(client))
-		return 0; /* only opers can see this.. */
+	if (whois_get_policy(client, target, "reputation") != WHOIS_CONFIG_DETAILS_FULL)
+		return 0;
 
+	reputation = Reputation(target);
 	if (reputation > 0)
 	{
-		sendto_one(client, NULL, ":%s %d %s %s :is using an IP with a reputation score of %d",
-			me.name, RPL_WHOISSPECIAL, client->name,
-			target->name, reputation);
+		add_nvplist_numeric_fmt(list, 0, "reputation", client, RPL_WHOISSPECIAL,
+		                        "%s :is using an IP with a reputation score of %d",
+		                        target->name, reputation);
 	}
 	return 0;
 }
@@ -1308,7 +1330,7 @@ void reputation_md_free(ModData *m)
 	m->l = 0;
 }
 
-char *reputation_md_serialize(ModData *m)
+const char *reputation_md_serialize(ModData *m)
 {
 	static char buf[32];
 	if (m->i == 0)
@@ -1317,7 +1339,7 @@ char *reputation_md_serialize(ModData *m)
 	return buf;
 }
 
-void reputation_md_unserialize(char *str, ModData *m)
+void reputation_md_unserialize(const char *str, ModData *m)
 {
 	m->i = atoi(str);
 }
diff --git a/src/modules/require-module.c b/src/modules/require-module.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER = {
 	"5.0.1",
 	"Require/deny modules across the network",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 typedef struct _denymod DenyMod;
@@ -187,50 +187,50 @@ int reqmods_configtest_deny(ConfigFile *cf, ConfigEntry *ce, int type, int *errs
 	int has_name, has_reason;
 
 	// We are only interested in deny module { }
-	if (strcmp(ce->ce_vardata, "module"))
+	if (strcmp(ce->value, "module"))
 		return 0;
 
 	has_name = has_reason = 0;
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strlen(cep->ce_varname))
+		if (!strlen(cep->name))
 		{
-			config_error("%s:%i: blank directive for deny module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			config_error("%s:%i: blank directive for deny module { } block", cep->file->filename, cep->line_number);
 			errors++;
 			continue;
 		}
 
-		if (!cep->ce_vardata || !strlen(cep->ce_vardata))
+		if (!cep->value || !strlen(cep->value))
 		{
-			config_error("%s:%i: blank %s without value for deny module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%i: blank %s without value for deny module { } block", cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "name"))
+		if (!strcmp(cep->name, "name"))
 		{
 			if (has_name)
 			{
-				config_error("%s:%i: duplicate %s for deny module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				config_error("%s:%i: duplicate %s for deny module { } block", cep->file->filename, cep->line_number, cep->name);
 				continue;
 			}
 
 			// We do a loose check here because a module might not be fully loaded yet
-			if (find_modptr_byname(cep->ce_vardata, 0))
+			if (find_modptr_byname(cep->value, 0))
 			{
-				config_error("[require-module] Module '%s' was specified as denied but we've actually loaded it ourselves", cep->ce_vardata);
+				config_error("[require-module] Module '%s' was specified as denied but we've actually loaded it ourselves", cep->value);
 				errors++;
 			}
 			has_name = 1;
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "reason")) // Optional
+		if (!strcmp(cep->name, "reason")) // Optional
 		{
 			// Still check for duplicate directives though
 			if (has_reason)
 			{
-				config_error("%s:%i: duplicate %s for deny module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				config_error("%s:%i: duplicate %s for deny module { } block", cep->file->filename, cep->line_number, cep->name);
 				errors++;
 				continue;
 			}
@@ -238,13 +238,13 @@ int reqmods_configtest_deny(ConfigFile *cf, ConfigEntry *ce, int type, int *errs
 			continue;
 		}
 
-		config_error("%s:%i: unknown directive %s for deny module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+		config_error("%s:%i: unknown directive %s for deny module { } block", cep->file->filename, cep->line_number, cep->name);
 		errors++;
 	}
 
 	if (!has_name)
 	{
-		config_error("%s:%i: missing required 'name' directive for deny module { } block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		config_error("%s:%i: missing required 'name' directive for deny module { } block", ce->file->filename, ce->line_number);
 		errors++;
 	}
 
@@ -257,21 +257,21 @@ int reqmods_configrun_deny(ConfigFile *cf, ConfigEntry *ce, int type)
 	ConfigEntry *cep;
 	DenyMod *dmod;
 
-	if (strcmp(ce->ce_vardata, "module"))
+	if (strcmp(ce->value, "module"))
 		return 0;
 
 	dmod = safe_alloc(sizeof(DenyMod));
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "name"))
+		if (!strcmp(cep->name, "name"))
 		{
-			safe_strdup(dmod->name, cep->ce_vardata);
+			safe_strdup(dmod->name, cep->value);
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "reason"))
+		if (!strcmp(cep->name, "reason"))
 		{
-			safe_strdup(dmod->reason, cep->ce_vardata);
+			safe_strdup(dmod->reason, cep->value);
 			continue;
 		}
 	}
@@ -290,37 +290,37 @@ int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 	int has_name, has_minversion;
 
 	// We are only interested in require module { }
-	if (strcmp(ce->ce_vardata, "module"))
+	if (strcmp(ce->value, "module"))
 		return 0;
 
 	has_name = has_minversion = 0;
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strlen(cep->ce_varname))
+		if (!strlen(cep->name))
 		{
-			config_error("%s:%i: blank directive for require module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+			config_error("%s:%i: blank directive for require module { } block", cep->file->filename, cep->line_number);
 			errors++;
 			continue;
 		}
 
-		if (!cep->ce_vardata || !strlen(cep->ce_vardata))
+		if (!cep->value || !strlen(cep->value))
 		{
-			config_error("%s:%i: blank %s without value for require module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%i: blank %s without value for require module { } block", cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "name"))
+		if (!strcmp(cep->name, "name"))
 		{
 			if (has_name)
 			{
-				config_error("%s:%i: duplicate %s for require module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				config_error("%s:%i: duplicate %s for require module { } block", cep->file->filename, cep->line_number, cep->name);
 				continue;
 			}
 
-			if (!find_modptr_byname(cep->ce_vardata, 0))
+			if (!find_modptr_byname(cep->value, 0))
 			{
-				config_error("[require-module] Module '%s' was specified as required but we didn't even load it ourselves (maybe double check the name?)", cep->ce_vardata);
+				config_error("[require-module] Module '%s' was specified as required but we didn't even load it ourselves (maybe double check the name?)", cep->value);
 				errors++;
 			}
 
@@ -329,12 +329,12 @@ int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "min-version")) // Optional
+		if (!strcmp(cep->name, "min-version")) // Optional
 		{
 			// Still check for duplicate directives though
 			if (has_minversion)
 			{
-				config_error("%s:%i: duplicate %s for require module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				config_error("%s:%i: duplicate %s for require module { } block", cep->file->filename, cep->line_number, cep->name);
 				errors++;
 				continue;
 			}
@@ -343,13 +343,13 @@ int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 		}
 
 		// Reason directive is not used for require module { }, so error on that too
-		config_error("%s:%i: unknown directive %s for require module { } block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+		config_error("%s:%i: unknown directive %s for require module { } block", cep->file->filename, cep->line_number, cep->name);
 		errors++;
 	}
 
 	if (!has_name)
 	{
-		config_error("%s:%i: missing required 'name' directive for require module { } block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		config_error("%s:%i: missing required 'name' directive for require module { } block", ce->file->filename, ce->line_number);
 		errors++;
 	}
 
@@ -364,28 +364,28 @@ int reqmods_configrun_require(ConfigFile *cf, ConfigEntry *ce, int type)
 	ReqMod *rmod;
 	char *name, *minversion;
 
-	if (strcmp(ce->ce_vardata, "module"))
+	if (strcmp(ce->value, "module"))
 		return 0;
 
 	name = minversion = NULL;
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "name"))
+		if (!strcmp(cep->name, "name"))
 		{
-			if (!(mod = find_modptr_byname(cep->ce_vardata, 0)))
+			if (!(mod = find_modptr_byname(cep->value, 0)))
 			{
 				// Something went very wrong :D
-				config_warn("[require-module] [BUG?] Passed configtest_require() but not configrun_require() for module '%s' (seems to not be loaded after all)", cep->ce_vardata);
+				config_warn("[require-module] [BUG?] Passed configtest_require() but not configrun_require() for module '%s' (seems to not be loaded after all)", cep->value);
 				continue;
 			}
 
-			name = cep->ce_vardata;
+			name = cep->value;
 			continue;
 		}
 
-		if (!strcmp(cep->ce_varname, "min-version"))
+		if (!strcmp(cep->name, "min-version"))
 		{
-			minversion = cep->ce_vardata;
+			minversion = cep->value;
 			continue;
 		}
 	}
@@ -443,7 +443,11 @@ CMD_FUNC(cmd_smod)
 		if ((dmod = find_denymod_byname(name)))
 		{
 			// Send this particular notice to local opers only
-			sendto_umode_global(UMODE_OPER, "Server %s is using module '%s', which is specified in a deny module { } config block (reason: %s)", client->name, name, dmod->reason);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENY_MODULE", client,
+			           "Server $client is using module '$module_name', "
+			           "which is specified in a deny module { } config block (reason: $ban_reason) -- aborting link",
+			           log_data_string("module_name", name),
+			           log_data_string("ban_reason", dmod->reason));
 			abort = 1; // Always SQUIT because it was explicitly denied by admins
 			continue;
 		}
@@ -458,13 +462,21 @@ CMD_FUNC(cmd_smod)
 			if (modflag == 'R')
 			{
 				// We don't need to check the version yet because there's nothing to compare it to, so we'll treat it as if no require module::min-version was specified
-				sendto_umode_global(UMODE_OPER, "Required module wasn't (fully) loaded or is missing entirely: %s", name);
+				unreal_log(ULOG_ERROR, "link", "LINK_MISSING_REQUIRED_MODULE", client,
+				           "Server $me is missing module '$module_name' which "
+				           "is required by server $client. -- aborting link",
+				           log_data_client("me", &me),
+				           log_data_string("module_name", name));
 				abort = 1; // Always SQUIT here too (explicitly required by admins)
 			}
-
 			else if (modflag == 'G')
-				sendto_umode_global(UMODE_OPER, "[WARN] Module marked as global wasn't (fully) loaded or is missing entirely: %s", name);
-
+			{
+				unreal_log(ULOG_WARNING, "link", "LINK_MISSING_GLOBAL_MODULE", client,
+				           "Server $me is missing module '$module_name', which is "
+				           "marked as global at $client",
+				           log_data_client("me", &me),
+				           log_data_string("module_name", name));
+			}
 			continue;
 		}
 
@@ -476,15 +488,21 @@ CMD_FUNC(cmd_smod)
 		// An explicit version was specified in require module { } but our module version is less than that
 		if (*version != '*' && strnatcasecmp(mod->header->version, version) < 0)
 		{
-			sendto_umode_global(UMODE_OPER, "Module version mismatch for required module '%s' (should be equal to or greater than %s but we're running %s)", name, version, mod->header->version);
+			unreal_log(ULOG_ERROR, "link", "LINK_MODULE_OLD_VERSION", client,
+			           "Server $me is using an old version of module '$module_name'. "
+			           "Server $client requires us to have version $minimum_module_version or later (we have $our_module_version). "
+			           "-- aborting link",
+			           log_data_client("me", &me),
+			           log_data_string("module_name", name),
+			           log_data_string("minimum_module_version", version),
+			           log_data_string("our_module_version", mod->header->version));
 			abort = 1;
 		}
 	}
 
 	if (abort)
 	{
-		sendto_umode_global(UMODE_OPER, "ABORTING LINK: %s <=> %s", me.name, client->name);
-		exit_client(client, NULL, "ABORTING LINK");
+		exit_client_fmt(client, NULL, "Link aborted due to missing or banned modules (see previous errors)");
 		return;
 	}
 }
diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c
@@ -24,7 +24,7 @@ ModuleHeader MOD_HEADER = {
 	"1.0.2",
 	"Restrict specific commands unless certain conditions have been met",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 typedef struct RestrictedCommand RestrictedCommand;
@@ -45,14 +45,14 @@ typedef struct {
 } CmdMap;
 
 // Forward declarations
-char *find_cmd_byconftag(char *conftag);
-RestrictedCommand *find_restrictions_bycmd(char *cmd);
-RestrictedCommand *find_restrictions_byconftag(char *conftag);
+const char *find_cmd_byconftag(const char *conftag);
+RestrictedCommand *find_restrictions_bycmd(const char *cmd);
+RestrictedCommand *find_restrictions_byconftag(const char *conftag);
 int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
-int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
-int rcmd_block_message(Client *client, char *text, SendType sendtype, char **errmsg, char *display, char *conftag);
+int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+int rcmd_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
+int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag);
 CMD_OVERRIDE_FUNC(rcmd_override);
 
 // Globals
@@ -110,7 +110,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-char *find_cmd_byconftag(char *conftag) {
+const char *find_cmd_byconftag(const char *conftag) {
 	CmdMap *cmap;
 	for (cmap = conf_cmdmaps; cmap->conftag; cmap++)
 	{
@@ -120,7 +120,7 @@ char *find_cmd_byconftag(char *conftag) {
 	return NULL;
 }
 
-RestrictedCommand *find_restrictions_bycmd(char *cmd) {
+RestrictedCommand *find_restrictions_bycmd(const char *cmd) {
 	RestrictedCommand *rcmd;
 	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
 	{
@@ -130,7 +130,7 @@ RestrictedCommand *find_restrictions_bycmd(char *cmd) {
 	return NULL;
 }
 
-RestrictedCommand *find_restrictions_byconftag(char *conftag) {
+RestrictedCommand *find_restrictions_byconftag(const char *conftag) {
 	RestrictedCommand *rcmd;
 	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
 	{
@@ -150,17 +150,17 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!ce || strcmp(ce->ce_varname, "restrict-commands"))
+	if (!ce || strcmp(ce->name, "restrict-commands"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
 		{
-			if (!strcmp(cep2->ce_varname, "disable"))
+			if (!strcmp(cep2->name, "disable"))
 			{
 				config_warn("%s:%i: set::restrict-commands::%s: the 'disable' option has been removed.",
-				            cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
+				            cep2->file->filename, cep2->line_number, cep->name);
 				if (!warn_disable)
 				{
 					config_warn("Simply remove 'disable yes;' from the configuration file and "
@@ -170,45 +170,45 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 				continue;
 			}
 
-			if (!cep2->ce_vardata)
+			if (!cep2->value)
 			{
-				config_error("%s:%i: blank set::restrict-commands::%s:%s without value", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname, cep2->ce_varname);
+				config_error("%s:%i: blank set::restrict-commands::%s:%s without value", cep2->file->filename, cep2->line_number, cep->name, cep2->name);
 				errors++;
 				continue;
 			}
 
-			if (!strcmp(cep2->ce_varname, "connect-delay"))
+			if (!strcmp(cep2->name, "connect-delay"))
 			{
-				long v = config_checkval(cep2->ce_vardata, CFG_TIME);
+				long v = config_checkval(cep2->value, CFG_TIME);
 				if ((v < 1) || (v > 3600))
 				{
-					config_error("%s:%i: set::restrict-commands::%s::connect-delay should be in range 1-3600", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
+					config_error("%s:%i: set::restrict-commands::%s::connect-delay should be in range 1-3600", cep2->file->filename, cep2->line_number, cep->name);
 					errors++;
 				}
 				continue;
 			}
 
-			if (!strcmp(cep2->ce_varname, "exempt-identified"))
+			if (!strcmp(cep2->name, "exempt-identified"))
 				continue;
 
-			if (!strcmp(cep2->ce_varname, "exempt-webirc"))
+			if (!strcmp(cep2->name, "exempt-webirc"))
 				continue;
 
-			if (!strcmp(cep2->ce_varname, "exempt-tls"))
+			if (!strcmp(cep2->name, "exempt-tls"))
 				continue;
 
-			if (!strcmp(cep2->ce_varname, "exempt-reputation-score"))
+			if (!strcmp(cep2->name, "exempt-reputation-score"))
 			{
-				int v = atoi(cep2->ce_vardata);
+				int v = atoi(cep2->value);
 				if (v <= 0)
 				{
-					config_error("%s:%i: set::restrict-commands::%s::exempt-reputation-score must be greater than 0", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
+					config_error("%s:%i: set::restrict-commands::%s::exempt-reputation-score must be greater than 0", cep2->file->filename, cep2->line_number, cep->name);
 					errors++;
 				}
 				continue;
 			}
 
-			config_error("%s:%i: unknown directive set::restrict-commands::%s::%s", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname, cep2->ce_varname);
+			config_error("%s:%i: unknown directive set::restrict-commands::%s::%s", cep2->file->filename, cep2->line_number, cep->name, cep2->name);
 			errors++;
 		}
 	}
@@ -220,24 +220,24 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
 {
 	ConfigEntry *cep, *cep2;
-	char *cmd, *conftag;
+	const char *cmd, *conftag;
 	RestrictedCommand *rcmd;
 
 	// We are only interested in set::restrict-commands
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!ce || strcmp(ce->ce_varname, "restrict-commands"))
+	if (!ce || strcmp(ce->name, "restrict-commands"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		// May need to switch some stuff around for special cases where the config directive doesn't match the actual command
 		conftag = NULL;
-		if ((cmd = find_cmd_byconftag(cep->ce_varname)))
-			conftag = cep->ce_varname;
+		if ((cmd = find_cmd_byconftag(cep->name)))
+			conftag = cep->name;
 		else
-			cmd = cep->ce_varname;
+			cmd = cep->name;
 
 		// Try to add override before even allocating the struct so we can bail early
 		// Also don't override anything from the conf_cmdmaps[] list because those are handled through hooks instead
@@ -250,7 +250,7 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
 				continue;
 			}
 
-			if (!CommandOverrideAdd(ModInf.handle, cmd, rcmd_override))
+			if (!CommandOverrideAdd(ModInf.handle, cmd, 0, rcmd_override))
 			{
 				config_warn("[restrict-commands] Failed to add override for '%s' (NO RESTRICTIONS APPLY)", cmd);
 				continue;
@@ -260,38 +260,38 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
 		rcmd = safe_alloc(sizeof(RestrictedCommand));
 		safe_strdup(rcmd->cmd, cmd);
 		safe_strdup(rcmd->conftag, conftag);
-		for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
 		{
-			if (!cep2->ce_vardata)
+			if (!cep2->value)
 				continue;
 
-			if (!strcmp(cep2->ce_varname, "connect-delay"))
+			if (!strcmp(cep2->name, "connect-delay"))
 			{
-				rcmd->connect_delay = config_checkval(cep2->ce_vardata, CFG_TIME);
+				rcmd->connect_delay = config_checkval(cep2->value, CFG_TIME);
 				continue;
 			}
 
-			if (!strcmp(cep2->ce_varname, "exempt-identified"))
+			if (!strcmp(cep2->name, "exempt-identified"))
 			{
-				rcmd->exempt_identified = config_checkval(cep2->ce_vardata, CFG_YESNO);
+				rcmd->exempt_identified = config_checkval(cep2->value, CFG_YESNO);
 				continue;
 			}
 			
-			if (!strcmp(cep2->ce_varname, "exempt-webirc"))
+			if (!strcmp(cep2->name, "exempt-webirc"))
 			{
-				rcmd->exempt_webirc = config_checkval(cep2->ce_vardata, CFG_YESNO);
+				rcmd->exempt_webirc = config_checkval(cep2->value, CFG_YESNO);
 				continue;
 			}
 
-			if (!strcmp(cep2->ce_varname, "exempt-tls"))
+			if (!strcmp(cep2->name, "exempt-tls"))
 			{
-				rcmd->exempt_tls = config_checkval(cep2->ce_vardata, CFG_YESNO);
+				rcmd->exempt_tls = config_checkval(cep2->value, CFG_YESNO);
 				continue;
 			}
 
-			if (!strcmp(cep2->ce_varname, "exempt-reputation-score"))
+			if (!strcmp(cep2->name, "exempt-reputation-score"))
 			{
-				rcmd->exempt_reputation_score = atoi(cep2->ce_vardata);
+				rcmd->exempt_reputation_score = atoi(cep2->value);
 				continue;
 			}
 		}
@@ -313,12 +313,12 @@ int rcmd_canbypass(Client *client, RestrictedCommand *rcmd)
 		return 1;
 	if (rcmd->exempt_reputation_score > 0 && (GetReputation(client) >= rcmd->exempt_reputation_score))
 		return 1;
-	if (rcmd->connect_delay && client->local && (TStime() - client->local->firsttime >= rcmd->connect_delay))
+	if (rcmd->connect_delay && client->local && (TStime() - client->local->creationtime >= rcmd->connect_delay))
 		return 1;
 	return 0;
 }
 
-int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	if (rcmd_block_message(client, *msg, sendtype, errmsg, "channel", (sendtype == SEND_TYPE_NOTICE ? "channel-notice" : "channel-message")))
 		return HOOK_DENY;
@@ -326,7 +326,7 @@ int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, c
 	return HOOK_CONTINUE;
 }
 
-int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int rcmd_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	// Need a few extra exceptions for user messages only =]
 	if ((client == target) || IsULine(target))
@@ -338,7 +338,7 @@ int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **er
 	return HOOK_CONTINUE;
 }
 
-int rcmd_block_message(Client *client, char *text, SendType sendtype, char **errmsg, char *display, char *conftag)
+int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag)
 {
 	RestrictedCommand *rcmd;
 	static char errbuf[256];
diff --git a/src/modules/rmtkl.c b/src/modules/rmtkl.c
@@ -24,7 +24,7 @@ ModuleHeader MOD_HEADER = {
 	"1.4",
 	"Adds /rmtkl command to easily remove *-Lines in bulk",
 	"Gottem and the UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 #define IsParam(x) (parc > (x) && !BadPtr(parv[(x)]))
@@ -37,10 +37,10 @@ typedef struct {
 	char *operpriv;
 } TKLType;
 
-static void dump_str(Client *client, char **buf);
+static void dump_str(Client *client, const char **buf);
 static TKLType *find_TKLType_by_flag(char flag);
-void rmtkl_check_options(char *param, int *skipperm, int *silent);
-int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, char *uhmask, char *commentmask, int skipperm, int silent);
+void rmtkl_check_options(const char *param, int *skipperm, int *silent);
+int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, const char *uhmask, const char *commentmask, int skipperm, int silent);
 CMD_FUNC(rmtkl);
 
 TKLType tkl_types[] = {
@@ -53,7 +53,7 @@ TKLType tkl_types[] = {
 	{ 0, 0, "Unknown *-Line", 0 },
 };
 
-static char *rmtkl_help[] = {
+static const char *rmtkl_help[] = {
 	"*** \002Help on /rmtkl\002 *** ",
 	"Removes all TKLs matching the given conditions from the local server, or the entire",
 	"network if it's a global-type ban.",
@@ -104,7 +104,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static void dump_str(Client *client, char **buf)
+static void dump_str(Client *client, const char **buf)
 {
 	if (!MyUser(client))
 		return;
@@ -114,7 +114,7 @@ static void dump_str(Client *client, char **buf)
 		sendto_one(client, NULL, ":%s %03d %s :%s", me.name, RPL_TEXT, client->name, *buf);
 
 	// Let user take 8 seconds to read it
-	client->local->since += 8;
+	add_fake_lag(client, 8000);
 }
 
 static TKLType *find_TKLType_by_flag(char flag)
@@ -126,14 +126,14 @@ static TKLType *find_TKLType_by_flag(char flag)
 	return t;
 }
 
-void rmtkl_check_options(char *param, int *skipperm, int *silent) {
+void rmtkl_check_options(const char *param, int *skipperm, int *silent) {
 	if (!strcasecmp("-skipperm", param))
 		*skipperm = 1;
 	if (!strcasecmp("-silent", param))
 		*silent = 1;
 }
 
-int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, char *uhmask, char *commentmask, int skipperm, int silent)
+int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, const char *uhmask, const char *commentmask, int skipperm, int silent)
 {
 	if (tkl->type != tkltype->type)
 		return 0;
@@ -171,7 +171,7 @@ int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, char *uhmask, ch
 	if (!silent)
 		sendnotice_tkl_del(client->name, tkl);
 
-	RunHook2(HOOKTYPE_TKL_DEL, client, tkl);
+	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
 
 	if (tkl->type & TKL_SHUN)
 		tkl_check_local_remove_shun(tkl);
@@ -183,7 +183,7 @@ CMD_FUNC(rmtkl)
 {
 	TKL *tkl, *next;
 	TKLType *tkltype;
-	char *types, *uhmask, *commentmask, *p;
+	const char *types, *uhmask, *commentmask, *p;
 	char tklchar;
 	int tklindex, tklindex2, skipperm, silent;
 	unsigned int count;
@@ -289,5 +289,7 @@ CMD_FUNC(rmtkl)
 		}
 	}
 
-	sendto_snomask(SNO_TKL, "*** %s removed %d TKLine(s) using /rmtkl", client->name, count);
+	unreal_log(ULOG_INFO, "tkl", "RMTKL_COMMAND", client,
+	           "[rmtkl] $client removed $tkl_removed_count TKLine(s) using /RMTKL",
+	           log_data_integer("tkl_removed_count", count));
 }
diff --git a/src/modules/rules.c b/src/modules/rules.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /rules", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -63,14 +63,14 @@ CMD_FUNC(cmd_rules)
 
 	temp = NULL;
 
-	if (hunt_server(client, recv_mtags, ":%s RULES :%s", 1, parc, parv) != HUNTED_ISME)
+	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)
+	if (!temp)
 		temp = rules.lines;
 
 	if (temp == NULL)
diff --git a/src/modules/sajoin.c b/src/modules/sajoin.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /sajoin", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -52,6 +52,13 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+static void log_sajoin(Client *client, 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));
+}
+
 /* cmd_sajoin() - Lamego - Wed Jul 21 20:04:48 1999
    Copied off PTlink IRCd (C) PTlink coders team.
    Coded for Sadmin by Stskeeps
@@ -62,10 +69,8 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_sajoin)
 {
 	Client *target;
+	char request[BUFSIZE];
 	char jbuf[BUFSIZE];
-	char mode = '\0';
-	char sjmode = '\0';
-	char *mode_args[3];
 	int did_anything = 0;
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("SAJOIN");
@@ -76,7 +81,7 @@ CMD_FUNC(cmd_sajoin)
 		return;
 	}
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
@@ -89,32 +94,35 @@ CMD_FUNC(cmd_sajoin)
 		return;
 	}
 
+	/* 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 (!MyUser(target))
 	{
-		sendto_one(target, NULL, ":%s SAJOIN %s %s", client->id, target->id, parv[2]);
-
-		/* Logging function added by XeRXeS */
-		ircd_log(LOG_SACMDS,"SAJOIN: %s used SAJOIN to make %s join %s",
-			client->name, target->name, parv[2]);
-
+		log_sajoin(client, target, parv[2]);
 		return;
 	}
 
+	/* 'target' is our client... */
+
 	/* Can't this just use do_join() or something with a parameter to bypass some checks?
 	 * This duplicate code is damn ugly. Ah well..
 	 */
 	{
 		char *name, *p = NULL;
-		int i, parted = 0;
+		int parted = 0;
 	
 		*jbuf = 0;
 
 		/* Now works like cmd_join */
-		for (i = 0, name = strtoken(&p, parv[2], ","); name; name = strtoken(&p, NULL, ","))
+		strlcpy(request, parv[2], sizeof(request));
+		for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
 		{
 			Channel *channel;
 			Membership *lp;
+			char mode = '\0';
+			char prefix = '\0';
 
 			if (++ntargets > maxtargets)
 			{
@@ -122,38 +130,11 @@ CMD_FUNC(cmd_sajoin)
 				break;
 			}
 
-			switch (name[0])
+			mode = prefix_to_mode(*name);
+			if (mode)
 			{
-#ifdef PREFIX_AQ
-				case '~':
-					mode = 'q';
-					sjmode = '~';
-					++name;
-					break;
-				case '&':
-					mode = 'a';
-					sjmode = '&';
-					++name;
-					break;
-#endif
-				case '@':
-					mode = 'o';
-					sjmode = '@';
-					++name;
-					break;
-				case '%':
-					mode = 'h';
-					sjmode = '%';
-					++name;
-					break;
-				case '+':
-					mode = 'v';
-					sjmode = '+';
-					++name;
-					break;
-				default:
-					mode = sjmode = '\0'; /* make sure sjmode is 0. */
-					break;
+				prefix = *name;
+				name++; /* skip the prefix */
 			}
 
 			if (strlen(name) > CHANNELLEN)
@@ -162,10 +143,9 @@ CMD_FUNC(cmd_sajoin)
 				continue;
 			}
 
-			if (*name == '0' && !atoi(name) && !sjmode)
+			if (*name == '0' && !atoi(name) && !mode)
 			{
-				strcpy(jbuf, "0");
-				i = 1;
+				strlcpy(jbuf, "0", sizeof(jbuf));
 				parted = 1;
 				continue;
 			}
@@ -176,7 +156,7 @@ CMD_FUNC(cmd_sajoin)
 				continue;
 			}
 
-			channel = get_channel(target, name, 0);
+			channel = make_channel(name);
 
 			/* If this _specific_ channel is not permitted, skip it */
 			if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sajoin",client,target,channel,NULL))
@@ -191,25 +171,36 @@ CMD_FUNC(cmd_sajoin)
 				continue;
 			}
 			if (*jbuf)
-				strlcat(jbuf, ",", sizeof jbuf);
-			strlncat(jbuf, name, sizeof jbuf, sizeof(jbuf) - i - 1);
-			i += strlen(name) + 1;
+				strlcat(jbuf, ",", sizeof(jbuf));
+			if (prefix)
+				strlcat_letter(jbuf, prefix, sizeof(jbuf));
+			strlcat(jbuf, name, sizeof(jbuf));
 		}
 		if (!*jbuf)
 			return;
-		i = 0;
-		strcpy(parv[2], jbuf);
+
+		strlcpy(request, jbuf, sizeof(request));
 		*jbuf = 0;
-		for (name = strtoken(&p, parv[2], ","); name; name = strtoken(&p, NULL, ","))
+		for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
 		{
 			MessageTag *mtags = NULL;
-			int flags;
+			const char *member_modes;
 			Channel *channel;
 			Membership *lp;
 			Hook *h;
 			int i = 0;
+			char mode = '\0';
+			char prefix = '\0';
+
+			mode = prefix_to_mode(*name);
+			if (mode != '\0')
+			{
+				/* Yup, it was a real prefix. */
+				prefix = *name;
+				name++;
+			}
 
-			if (*name == '0' && !atoi(name) && !sjmode)
+			if (*name == '0' && !atoi(name) && !mode)
 			{
 				/* Rewritten so to generate a PART for each channel to servers,
 				 * so the same msgid is used for each part on all servers. -- Syzop
@@ -223,18 +214,18 @@ CMD_FUNC(cmd_sajoin)
 					new_message(target, NULL, &mtags);
 					sendto_channel(channel, target, NULL, 0, 0, SEND_LOCAL, mtags,
 					               ":%s PART %s :%s",
-					               target->name, channel->chname, "Left all channels");
-					sendto_server(NULL, 0, 0, mtags, ":%s PART %s :Left all channels", target->name, channel->chname);
+					               target->name, channel->name, "Left all channels");
+					sendto_server(NULL, 0, 0, mtags, ":%s PART %s :Left all channels", target->name, channel->name);
 					if (MyConnect(target))
-						RunHook4(HOOKTYPE_LOCAL_PART, target, channel, mtags, "Left all channels");
+						RunHook(HOOKTYPE_LOCAL_PART, target, channel, mtags, "Left all channels");
 					free_message_tags(mtags);
-					remove_user_from_channel(target, channel);
+					remove_user_from_channel(target, channel, 0);
 				}
-				strcpy(jbuf, "0");
+				strlcpy(jbuf, "0", sizeof(jbuf));
 				continue;
 			}
-			flags = (ChannelExists(name)) ? CHFL_DEOPPED : LEVEL_ON_JOIN;
-			channel = get_channel(target, name, CREATE);
+			member_modes = (ChannelExists(name)) ? "" : LEVEL_ON_JOIN;
+			channel = make_channel(name);
 			if (channel && (lp = find_membership_link(target->user->channel, channel)))
 				continue;
 
@@ -255,19 +246,26 @@ CMD_FUNC(cmd_sajoin)
 			 * Each with their own unique msgid.
 			 */
 			new_message(target, NULL, &mtags);
-			join_channel(channel, target, mtags, flags);
-			if (sjmode)
+			join_channel(channel, target, mtags, member_modes);
+			if (prefix)
 			{
+				char *modes;
+				const char *mode_args[3];
+
 				opermode = 0;
 				sajoinmode = 1;
-				mode_args[0] = safe_alloc(2);
-				mode_args[0][0] = mode;
-				mode_args[0][1] = '\0';
+
+				modes = safe_alloc(2);
+				modes[0] = mode;
+
+				mode_args[0] = modes;
 				mode_args[1] = target->name;
 				mode_args[2] = 0;
+
 				do_mode(channel, target, NULL, 3, mode_args, 0, 1);
+
 				sajoinmode = 0;
-				safe_free(mode_args[0]);
+				safe_free(modes);
 			}
 			free_message_tags(mtags);
 			did_anything = 1;
@@ -275,5 +273,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);
+		}
 	}
 }
diff --git a/src/modules/samode.c b/src/modules/samode.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /samode", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -68,7 +68,7 @@ CMD_FUNC(cmd_samode)
 		return;
 	}
 
-	channel = find_channel(parv[1], NULL);
+	channel = find_channel(parv[1]);
 	if (!channel)
 	{
 		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /sapart", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -52,6 +52,24 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+static void log_sapart(Client *client, 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
+	{
+		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));
+	}
+}
+
+
 /* cmd_sapart() - Lamego - Wed Jul 21 20:04:48 1999
    Copied off PTlink IRCd (C) PTlink coders team.
    Coded for Sadmin by Stskeeps
@@ -68,8 +86,9 @@ CMD_FUNC(cmd_sapart)
 	Membership *lp;
 	char *name, *p = NULL;
 	int i;
-	char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
+	const char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
 	char commentx[512];
+	char request[BUFSIZE];
 	char jbuf[BUFSIZE];
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("SAPART");
@@ -80,7 +99,7 @@ CMD_FUNC(cmd_sapart)
                 return;
         }
 
-        if (!(target = find_person(parv[1], NULL)))
+        if (!(target = find_user(parv[1], NULL)))
         {
                 sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
                 return;
@@ -93,37 +112,33 @@ CMD_FUNC(cmd_sapart)
 		return;
 	}
 
-	/* Relay it on, if it's not my target */
+	/* Broadcast so other servers can log it appropriately as an SAPART */
+	if (parv[3])
+		sendto_server(client, 0, 0, recv_mtags, ":%s SAPART %s %s :%s", client->id, target->id, parv[2], comment);
+	else
+		sendto_server(client, 0, 0, recv_mtags, ":%s SAPART %s %s", client->id, target->id, parv[2]);
+
 	if (!MyUser(target))
 	{
-		if (comment)
-		{
-			sendto_one(target, NULL, ":%s SAPART %s %s :%s", client->id, target->id, parv[2], comment);
-			ircd_log(LOG_SACMDS,"SAPART: %s used SAPART to make %s part %s (%s)",
-			         client->name, target->name, parv[2], comment);
-		}
-		else
-		{
-			sendto_one(target, NULL, ":%s SAPART %s %s", client->id, target->id, parv[2]);
-			ircd_log(LOG_SACMDS,"SAPART: %s used SAPART to make %s part %s",
-			         client->name, target->name, parv[2]);
-		}
+		log_sapart(client, target, parv[2], comment);
 		return;
 	}
 
-	/* Now works like cmd_join */
+	/* 'target' is our client... */
+
 	*jbuf = 0;
-	for (i = 0, name = strtoken(&p, parv[2], ","); name; name = strtoken(&p, NULL, ","))
+	strlcpy(request, parv[2], sizeof(request));
+	for (i = 0, name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
 	{
 		if (++ntargets > maxtargets)
 		{
 			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "SAPART");
 			break;
 		}
-		if (!(channel = get_channel(target, name, 0)))
+
+		if (!(channel = find_channel(name)))
 		{
-			sendnumeric(client, ERR_NOSUCHCHANNEL,
-				name);
+			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
 			continue;
 		}
 
@@ -148,17 +163,24 @@ CMD_FUNC(cmd_sapart)
 	if (!*jbuf)
 		return;
 
-	strcpy(parv[2], jbuf);
+	strlcpy(request, jbuf, sizeof(request));
+
+	log_sapart(client, target, request, comment);
 
 	if (comment)
 	{
-		strcpy(commentx, "SAPart: ");
-		strlcat(commentx, comment, 512);
+		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] = parv[2]; // chan
+	parv[1] = request; // chan
 	parv[2] = comment ? commentx : NULL; // comment
+
+	/* Now, do the actual parting: */
 	do_cmd(target, NULL, "PART", comment ? 3 : 2, parv);
-	/* target may be killed now due to the part reason @ spamfilter */
+
+	/* NOTE: target may be killed now due to the part reason @ spamfilter */
 }
diff --git a/src/modules/sasl.c b/src/modules/sasl.c
@@ -25,17 +25,17 @@
 ModuleHeader MOD_HEADER
   = {
 	"sasl",
-	"5.0",
+	"5.2.1",
 	"SASL", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
 void saslmechlist_free(ModData *m);
-char *saslmechlist_serialize(ModData *m);
-void saslmechlist_unserialize(char *str, ModData *m);
-char *sasl_capability_parameter(Client *client);
+const char *saslmechlist_serialize(ModData *m);
+void saslmechlist_unserialize(const char *str, ModData *m);
+const char *sasl_capability_parameter(Client *client);
 int sasl_server_synced(Client *client);
 int sasl_account_login(Client *client, MessageTag *mtags);
 EVENT(sasl_timeout);
@@ -69,14 +69,15 @@ int sasl_account_login(Client *client, MessageTag *mtags)
 {
 	if (!MyConnect(client))
 		return 0;
+
 	/* Notify user */
-	if (client->user->svid[0] != '0')
+	if (IsLoggedIn(client))
 	{
 		sendnumeric(client, RPL_LOGGEDIN,
 			BadPtr(client->name) ? "*" : client->name,
 			BadPtr(client->user->username) ? "*" : client->user->username,
 			BadPtr(client->user->realhost) ? "*" : client->user->realhost,
-			client->user->svid, client->user->svid);
+			client->user->account, client->user->account);
 	}
 	else
 	{
@@ -93,13 +94,13 @@ int sasl_account_login(Client *client, MessageTag *mtags)
  *
  * parv[1]: propagation mask
  * parv[2]: target
- * parv[3]: ESVID
+ * parv[3]: account name (SVID)
  */
 CMD_FUNC(cmd_svslogin)
 {
 	Client *target;
 
-	if (!SASL_SERVER || MyUser(client) || (parc < 3) || !parv[3])
+	if (MyUser(client) || (parc < 3) || !parv[3])
 		return;
 
 	/* We actually ignore parv[1] since this is a broadcast message.
@@ -116,7 +117,7 @@ CMD_FUNC(cmd_svslogin)
 		if (target->user == NULL)
 			make_user(target);
 
-		strlcpy(target->user->svid, parv[3], sizeof(target->user->svid));
+		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 */
@@ -169,7 +170,7 @@ CMD_FUNC(cmd_sasl)
 
 		if (*parv[3] == 'C')
 		{
-			RunHookReturn2(HOOKTYPE_SASL_CONTINUATION, target, parv[4], !=0);
+			RunHookReturn(HOOKTYPE_SASL_CONTINUATION, !=0, target, parv[4]);
 			sendto_one(target, NULL, "AUTHENTICATE %s", parv[4]);
 		}
 		else if (*parv[3] == 'D')
@@ -178,15 +179,15 @@ CMD_FUNC(cmd_sasl)
 			if (*parv[4] == 'F')
 			{
 				target->local->sasl_sent_time = 0;
-				target->local->since += 7; /* bump fakelag due to failed authentication attempt */
-				RunHookReturn2(HOOKTYPE_SASL_RESULT, target, 0, !=0);
+				add_fake_lag(target, 7000); /* bump fakelag due to failed authentication attempt */
+				RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 0);
 				sendnumeric(target, ERR_SASLFAIL);
 			}
 			else if (*parv[4] == 'S')
 			{
 				target->local->sasl_sent_time = 0;
 				target->local->sasl_complete++;
-				RunHookReturn2(HOOKTYPE_SASL_RESULT, target, 1, !=0);
+				RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 1);
 				sendnumeric(target, RPL_SASLSUCCESS);
 			}
 		}
@@ -232,7 +233,7 @@ CMD_FUNC(cmd_authenticate)
 	if (agent_p == NULL)
 	{
 		char *addr = BadPtr(client->ip) ? "0" : client->ip;
-		char *certfp = moddata_client_get(client, "certfp");
+		const char *certfp = moddata_client_get(client, "certfp");
 
 		sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s H %s %s",
 		    me.name, SASL_SERVER, client->id, addr, addr);
@@ -308,7 +309,7 @@ int sasl_connect(Client *client)
 	return abort_sasl(client);
 }
 
-int sasl_quit(Client *client, MessageTag *mtags, char *comment)
+int sasl_quit(Client *client, MessageTag *mtags, const char *comment)
 {
 	return abort_sasl(client);
 }
@@ -335,14 +336,9 @@ void auto_discover_sasl_server(int justlinked)
 			/* SASL server found */
 			if (justlinked)
 			{
-				/* Let's send this message only on link and not also on /rehash */
-				sendto_realops("Services server '%s' provides SASL authentication, good! "
-				               "I'm setting set::sasl-server to '%s' internally.",
-				               SERVICES_NAME, SERVICES_NAME);
-				/* We should really get some LOG_INFO or something... I keep abusing LOG_ERROR :) */
-				ircd_log(LOG_ERROR, "Services server '%s' provides SASL authentication, good! "
-				                    "I'm setting set::sasl-server to '%s' internally.",
-				                    SERVICES_NAME, SERVICES_NAME);
+				unreal_log(ULOG_INFO, "config", "SASL_SERVER_AUTODETECT", client,
+				           "Services server $client provides SASL authentication, good! "
+				           "I'm setting set::sasl-server to \"$client\" internally.");
 			}
 			safe_strdup(SASL_SERVER, SERVICES_NAME);
 			if (justlinked)
@@ -394,7 +390,8 @@ MOD_INIT()
 	mreq.free = saslmechlist_free;
 	mreq.serialize = saslmechlist_serialize;
 	mreq.unserialize = saslmechlist_unserialize;
-	mreq.sync = 1;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.self_write = 1;
 	mreq.type = MODDATATYPE_CLIENT;
 	ModDataAdd(modinfo->handle, mreq);
 
@@ -419,19 +416,19 @@ void saslmechlist_free(ModData *m)
 	safe_free(m->str);
 }
 
-char *saslmechlist_serialize(ModData *m)
+const char *saslmechlist_serialize(ModData *m)
 {
 	if (!m->str)
 		return NULL;
 	return m->str;
 }
 
-void saslmechlist_unserialize(char *str, ModData *m)
+void saslmechlist_unserialize(const char *str, ModData *m)
 {
 	safe_strdup(m->str, str);
 }
 
-char *sasl_capability_parameter(Client *client)
+const char *sasl_capability_parameter(Client *client)
 {
 	Client *server;
 
diff --git a/src/modules/sdesc.c b/src/modules/sdesc.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /sdesc", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -80,13 +80,13 @@ CMD_FUNC(cmd_sdesc)
 				REALLEN);
 			return;
 		}
-		parv[1][REALLEN] = '\0';
 	}
 
-	ircsnprintf(client->srvptr->info, sizeof(client->srvptr->info), "%s", parv[1]);
+	strlncpy(client->uplink->info, parv[1], sizeof(client->uplink->info), REALLEN);
 
 	sendto_server(client, 0, 0, NULL, ":%s SDESC :%s", client->name, parv[1]);
 
-	sendto_ops("Server description for %s is now '%s' (changed by %s)",
-		client->srvptr->name, client->srvptr->info, client->name);
+	unreal_log(ULOG_INFO, "sdesc", "SDESC_COMMAND", client,
+	           "Server description for $server is now '$server.server.info' (changed by $client)",
+	           log_data_client("server", client->uplink));
 }
diff --git a/src/modules/sendsno.c b/src/modules/sendsno.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /sendsno", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -63,9 +63,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_sendsno)
 {
 	MessageTag *mtags = NULL;
-	char *sno, *msg, *p;
-	long snomask = 0;
-	int i;
+	const char *sno, *msg, *p;
 	Client *acptr;
 
 	if ((parc < 3) || BadPtr(parv[2]))
@@ -81,23 +79,23 @@ CMD_FUNC(cmd_sendsno)
 	/* Forward to others... */
 	sendto_server(client, 0, 0, mtags, ":%s SENDSNO %s :%s", client->id, parv[1], parv[2]);
 
-	for (p = sno; *p; p++)
+	list_for_each_entry(acptr, &oper_list, special_node)
 	{
-		for(i = 0; i <= Snomask_highest; i++)
+		if (acptr->user->snomask)
 		{
-			if (Snomask_Table[i].flag == *p)
+			char found = 0;
+			for (p = sno; *p; p++)
 			{
-				snomask |= Snomask_Table[i].mode;
-				break;
+				if (strchr(acptr->user->snomask, *p))
+				{
+					found = 1;
+					break;
+				}
 			}
+			if (found)
+				sendto_one(acptr, mtags, ":%s NOTICE %s :%s", client->name, acptr->name, msg);
 		}
 	}
 
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->user->snomask & snomask)
-			sendto_one(acptr, mtags, ":%s NOTICE %s :%s", client->name, acptr->name, msg);
-	}
-
 	free_message_tags(mtags);
 }
diff --git a/src/modules/sendumode.c b/src/modules/sendumode.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /sendumode", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -66,8 +66,8 @@ CMD_FUNC(cmd_sendumode)
 {
 	MessageTag *mtags = NULL;
 	Client *acptr;
-	char *message;
-	char *p;
+	const char *message;
+	const char *p;
 	int i;
 	long umode_s = 0;
 
@@ -83,19 +83,7 @@ CMD_FUNC(cmd_sendumode)
 
 	sendto_server(client, 0, 0, mtags, ":%s SENDUMODE %s :%s", client->id, parv[1], message);
 
-	for (p = parv[1]; *p; p++)
-	{
-		for(i = 0; i <= Usermode_highest; i++)
-		{
-			if (!Usermode_Table[i].flag)
-				continue;
-			if (Usermode_Table[i].flag == *p)
-			{
-				umode_s |= Usermode_Table[i].mode;
-				break;
-			}
-		}
-	}
+	umode_s = set_usermode(parv[1]);
 
 	list_for_each_entry(acptr, &oper_list, special_node)
 	{
diff --git a/src/modules/server-time.c b/src/modules/server-time.c
@@ -28,14 +28,14 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"server-time CAP",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
 long CAP_SERVER_TIME = 0L;
 
-int server_time_mtag_is_ok(Client *client, char *name, char *value);
-void mtag_add_or_inherit_time(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int server_time_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_or_inherit_time(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -75,7 +75,7 @@ MOD_UNLOAD()
  * syntax.
  * We simply allow server-time ONLY from servers.
  */
-int server_time_mtag_is_ok(Client *client, char *name, char *value)
+int server_time_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client) && !BadPtr(value))
 		return 1;
@@ -83,7 +83,7 @@ int server_time_mtag_is_ok(Client *client, char *name, char *value)
 	return 0;
 }
 
-void mtag_add_or_inherit_time(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_or_inherit_time(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m = find_mtag(recv_mtags, "time");
 	if (m)
diff --git a/src/modules/server.c b/src/modules/server.c
@@ -22,21 +22,46 @@
 
 #include "unrealircd.h"
 
+/* Definitions */
+typedef enum AutoConnectStrategy {
+	AUTOCONNECT_PARALLEL = 0,
+	AUTOCONNECT_SEQUENTIAL = 1,
+	AUTOCONNECT_SEQUENTIAL_FALLBACK = 2
+} AutoConnectStrategy;
+
+typedef struct cfgstruct cfgstruct;
+struct cfgstruct {
+	AutoConnectStrategy autoconnect_strategy;
+	long connect_timeout;
+	long handshake_timeout;
+};
+
 /* Forward declarations */
+void server_config_setdefaults(cfgstruct *cfg);
+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);
+EVENT(server_handshake_timeout);
 void send_channel_modes_sjoin3(Client *to, Channel *channel);
 CMD_FUNC(cmd_server);
 CMD_FUNC(cmd_sid);
-int _verify_link(Client *client, char *servername, ConfigItem_link **link_out);
+int _verify_link(Client *client, ConfigItem_link **link_out);
 void _send_protoctl_servers(Client *client, int response);
 void _send_server_message(Client *client);
 void _introduce_user(Client *to, Client *acptr);
 int _check_deny_version(Client *cptr, char *software, int protocol, char *flags);
 void _broadcast_sinfo(Client *acptr, Client *to, Client *except);
+int server_sync(Client *cptr, ConfigItem_link *conf, int incoming);
+void tls_link_notification_verify(Client *acptr, ConfigItem_link *aconf);
+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 *);
 
 /* Global variables */
 static char buf[BUFSIZE];
-
-#define MSG_SERVER 	"SERVER"	
+static cfgstruct cfg;
+static char *last_autoconnect_server = NULL;
 
 ModuleHeader MOD_HEADER
   = {
@@ -44,7 +69,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /server", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_TEST()
@@ -56,30 +81,410 @@ MOD_TEST()
 	EfunctionAddVoid(modinfo->handle, EFUNC_INTRODUCE_USER, _introduce_user);
 	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);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, server_config_test);
 	return MOD_SUCCESS;
 }
 
 MOD_INIT()
 {
-	CommandAdd(modinfo->handle, MSG_SERVER, cmd_server, MAXPARA, CMD_UNREGISTERED|CMD_SERVER);
-	CommandAdd(modinfo->handle, "SID", cmd_sid, MAXPARA, CMD_SERVER);
-
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+	LoadPersistentPointer(modinfo, last_autoconnect_server, server_generic_free);
+	server_config_setdefaults(&cfg);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, server_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_POST_SERVER_CONNECT, 0, server_post_connect);
+	CommandAdd(modinfo->handle, "SERVER", cmd_server, MAXPARA, CMD_UNREGISTERED|CMD_SERVER);
+	CommandAdd(modinfo->handle, "SID", cmd_sid, MAXPARA, CMD_SERVER);
 
 	return MOD_SUCCESS;
 }
 
 MOD_LOAD()
 {
+	EventAdd(modinfo->handle, "server_autoconnect", server_autoconnect, NULL, 2000, 0);
+	EventAdd(modinfo->handle, "server_handshake_timeout", server_handshake_timeout, NULL, 1000, 0);
 	return MOD_SUCCESS;
 }
 
 MOD_UNLOAD()
 {
+	SavePersistentPointer(modinfo, last_autoconnect_server);
 	return MOD_SUCCESS;
 }
 
-int server_sync(Client *cptr, ConfigItem_link *conf);
+/** Convert 'str' to a AutoConnectStrategy value.
+ * @param str	The string, eg "parallel"
+ * @returns a valid AutoConnectStrategy value or -1 if not found.
+ */
+AutoConnectStrategy autoconnect_strategy_strtoval(char *str)
+{
+	if (!strcmp(str, "parallel"))
+		return AUTOCONNECT_PARALLEL;
+	if (!strcmp(str, "sequential"))
+		return AUTOCONNECT_SEQUENTIAL;
+	if (!strcmp(str, "sequential-fallback"))
+		return AUTOCONNECT_SEQUENTIAL_FALLBACK;
+	return -1;
+}
+
+/** Convert an AutoConnectStrategy value to a string.
+ * @param val	The value to convert to a string
+ * @returns a string, such as "parallel".
+ */
+char *autoconnect_strategy_valtostr(AutoConnectStrategy val)
+{
+	switch (val)
+	{
+		case AUTOCONNECT_PARALLEL:
+			return "parallel";
+		case AUTOCONNECT_SEQUENTIAL:
+			return "sequential";
+		case AUTOCONNECT_SEQUENTIAL_FALLBACK:
+			return "sequential-fallback";
+		default:
+			return "???";
+	}
+}
+
+void server_config_setdefaults(cfgstruct *cfg)
+{
+	cfg->autoconnect_strategy = AUTOCONNECT_SEQUENTIAL;
+	cfg->connect_timeout = 10;
+	cfg->handshake_timeout = 20;
+}
+
+int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	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 (!cep->value)
+		{
+			config_error("%s:%i: blank set::server-linking::%s without value",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		} else
+		if (!strcmp(cep->name, "autoconnect-strategy"))
+		{
+			if (autoconnect_strategy_strtoval(cep->value) < 0)
+			{
+				config_error("%s:%i: set::server-linking::autoconnect-strategy: invalid value '%s'. "
+				             "Should be one of: parallel",
+				             cep->file->filename, cep->line_number, cep->value);
+				errors++;
+				continue;
+			}
+		} else
+		if (!strcmp(cep->name, "connect-timeout"))
+		{
+			long v = config_checkval(cep->value, CFG_TIME);
+			if ((v < 5) || (v > 30))
+			{
+				config_error("%s:%i: set::server-linking::connect-timeout should be between 5 and 60 seconds",
+					cep->file->filename, cep->line_number);
+				errors++;
+				continue;
+			}
+		} else
+		if (!strcmp(cep->name, "handshake-timeout"))
+		{
+			long v = config_checkval(cep->value, CFG_TIME);
+			if ((v < 10) || (v > 120))
+			{
+				config_error("%s:%i: set::server-linking::handshake-timeout should be between 10 and 120 seconds",
+					cep->file->filename, cep->line_number);
+				errors++;
+				continue;
+			}
+		} else
+		{
+			config_error("%s:%i: unknown directive set::server-linking::%s",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int server_config_run(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"))
+		{
+			cfg.autoconnect_strategy = autoconnect_strategy_strtoval(cep->value);
+		} else
+		if (!strcmp(cep->name, "connect-timeout"))
+		{
+			cfg.connect_timeout = config_checkval(cep->value, CFG_TIME);
+		} else
+		if (!strcmp(cep->name, "handshake-timeout"))
+		{
+			cfg.handshake_timeout = config_checkval(cep->value, CFG_TIME);
+		}
+	}
+	return 1;
+}
+
+int server_needs_linking(ConfigItem_link *aconf)
+{
+	ConfigItem_deny_link *deny;
+	Client *client;
+	ConfigItem_class *class;
+
+	/* We're only interested in autoconnect blocks that are valid. Also, we ignore temporary link blocks. */
+	if (!(aconf->outgoing.options & CONNECT_AUTO) || !aconf->outgoing.hostname || (aconf->flag.temporary == 1))
+		return 0;
+
+	class = aconf->class;
+
+	/* Never do more than one connection attempt per <connfreq> seconds (for the same server) */
+	if ((aconf->hold > TStime()))
+		return 0;
+
+	aconf->hold = TStime() + class->connfreq;
+
+	client = find_client(aconf->servername, NULL);
+	if (client)
+		return 0; /* Server already connected (or connecting) */
+
+	if (class->clients >= class->maxclients)
+		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;
+
+	/* Yes, this server is a linking candidate */
+	return 1;
+}
+
+void server_autoconnect_parallel(void)
+{
+	ConfigItem_link *aconf;
+
+	for (aconf = conf_link; aconf; aconf = aconf->next)
+	{
+		if (!server_needs_linking(aconf))
+			continue;
+
+		connect_server(aconf, NULL, NULL);
+	}
+}
+
+/** Find first (valid) autoconnect server in link blocks.
+ * This function should not be used directly. It is a helper function
+ * for find_next_autoconnect_server().
+ */
+ConfigItem_link *find_first_autoconnect_server(void)
+{
+	ConfigItem_link *aconf;
+
+	for (aconf = conf_link; aconf; aconf = aconf->next)
+	{
+		if (!server_needs_linking(aconf))
+			continue;
+		return aconf; /* found! */
+	}
+	return NULL; /* none */
+}
+
+/** Find next server that we should try to autoconnect to.
+ * Taking into account that we last tried server 'current'.
+ * @param current	Server the previous autoconnect attempt was made to
+ * @returns A link block, or NULL if no servers are suitable.
+ */
+ConfigItem_link *find_next_autoconnect_server(char *current)
+{
+	ConfigItem_link *aconf;
+
+	/* If the current autoconnect server is NULL then
+	 * just find whichever valid server is first.
+	 */
+	if (current == NULL)
+		return find_first_autoconnect_server();
+
+	/* Next code is a bit convulted, it would have
+	 * been easier if conf_link was a circular list ;)
+	 */
+
+	/* Otherwise, walk the list up to 'current' */
+	for (aconf = conf_link; aconf; aconf = aconf->next)
+	{
+		if (!strcmp(aconf->servername, current))
+			break;
+	}
+
+	/* If the 'current' server dissapeared, then let's
+	 * just pick the first one from the list.
+	 * It is a rare event to have the link { } block
+	 * removed of a server that we just happened to
+	 * try to link to before, so we can afford to do
+	 * it this way.
+	 */
+	if (!aconf)
+		return find_first_autoconnect_server();
+
+	/* Check the remainder for the list, in other words:
+	 * check all servers after 'current' if they are
+	 * ready for an outgoing connection attempt...
+	 */
+	for (aconf = aconf->next; aconf; aconf = aconf->next)
+	{
+		if (!server_needs_linking(aconf))
+			continue;
+		return aconf; /* found! */
+	}
+
+	/* If we get here then there are no valid servers
+	 * after 'current', so now check for before 'current'
+	 * (and including 'current', since we may
+	 *  have to autoconnect to that one again,
+	 *  eg if it is the only autoconnect server)...
+	 */
+	for (aconf = conf_link; aconf; aconf = aconf->next)
+	{
+		if (!server_needs_linking(aconf))
+		{
+			if (!strcmp(aconf->servername, current))
+				break; /* need to stop here */
+			continue;
+		}
+		return aconf; /* found! */
+	}
+
+	return NULL; /* none */
+}
+
+/** Check if we are currently connecting to a server (outgoing).
+ * This function takes into account not only an outgoing TCP/IP connect
+ * or TLS handshake, but also if we are 'somewhat connected' to that
+ * server but have not completed the full sync, eg we may still need
+ * to receive SIDs or other sync data.
+ * NOTE: This implicitly assumes that outgoing links only go to
+ *       servers that will (eventually) send "EOS".
+ *       Should be a reasonable assumption given that in nearly all
+ *       cases we only connect to UnrealIRCd servers for the outgoing
+ *       case, as services are "always" incoming links.
+ * @returns 1 if an outgoing link is in progress, 0 if not.
+ */
+int current_outgoing_link_in_process(void)
+{
+	Client *client;
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (client->server && *client->server->by && client->local->creationtime &&
+		    (IsConnecting(client) || IsTLSConnectHandshake(client) || !IsSynched(client)))
+		{
+			return 1;
+		}
+	}
+
+	list_for_each_entry(client, &server_list, special_node)
+	{
+		if (client->server && *client->server->by && client->local->creationtime &&
+		    (IsConnecting(client) || IsTLSConnectHandshake(client) || !IsSynched(client)))
+		{
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+void server_autoconnect_sequential(void)
+{
+	ConfigItem_link *aconf;
+
+	if (current_outgoing_link_in_process())
+		return;
+
+	/* We are currently not in the process of doing an outgoing connect,
+	 * let's see if we need to connect to somewhere...
+	 */
+	aconf = find_next_autoconnect_server(last_autoconnect_server);
+	if (aconf == NULL)
+		return; /* No server to connect to at this time */
+
+	/* Start outgoing link attempt */
+	safe_strdup(last_autoconnect_server, aconf->servername);
+	connect_server(aconf, NULL, NULL);
+}
+
+/** Perform autoconnect to servers that are not linked yet. */
+EVENT(server_autoconnect)
+{
+	switch (cfg.autoconnect_strategy)
+	{
+		case AUTOCONNECT_PARALLEL:
+			server_autoconnect_parallel();
+			break;
+		case AUTOCONNECT_SEQUENTIAL:
+		/* Fallback is the same as sequential but we reset last_autoconnect_server on connect */
+		case AUTOCONNECT_SEQUENTIAL_FALLBACK:
+			server_autoconnect_sequential();
+			break;
+	}
+}
+
+EVENT(server_handshake_timeout)
+{
+	Client *client, *next;
+
+	list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
+	{
+		/* We are only interested in outgoing server connects */
+		if (!client->server || !*client->server->by || !client->local->creationtime)
+			continue;
+
+		/* Handle set::server-linking::connect-timeout */
+		if ((IsConnecting(client) || IsTLSConnectHandshake(client)) &&
+		    ((TStime() - client->local->creationtime) >= cfg.connect_timeout))
+		{
+			/* If this is a connect timeout to an outgoing server then notify ops & log it */
+			unreal_log(ULOG_INFO, "link", "LINK_CONNECT_TIMEOUT", client,
+			           "Connect timeout while trying to link to server '$client' ($client.ip)");
+
+			exit_client(client, NULL, "Connection timeout");
+			continue;
+		}
+
+		/* Handle set::server-linking::handshake-timeout */
+		if ((TStime() - client->local->creationtime) >= cfg.handshake_timeout)
+		{
+			/* If this is a handshake timeout to an outgoing server then notify ops & log it */
+			unreal_log(ULOG_INFO, "link", "LINK_HANDSHAKE_TIMEOUT", client,
+			           "Connect handshake timeout while trying to link to server '$client' ($client.ip)");
+
+			exit_client(client, NULL, "Handshake Timeout");
+			continue;
+		}
+	}
+}
 
 /** Check deny version { } blocks.
  * @param cptr		Client (a server)
@@ -211,7 +616,7 @@ void _send_protoctl_servers(Client *client, int response)
 
 void _send_server_message(Client *client)
 {
-	if (client->serv && client->serv->flags.server_sent)
+	if (client->server && client->server->flags.server_sent)
 	{
 #ifdef DEBUGMODE
 		abort();
@@ -222,24 +627,22 @@ void _send_server_message(Client *client)
 	sendto_one(client, NULL, "SERVER %s 1 :U%d-%s%s-%s %s",
 		me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", me.id, me.info);
 
-	if (client->serv)
-		client->serv->flags.server_sent = 1;
+	if (client->server)
+		client->server->flags.server_sent = 1;
 }
 
+#define LINK_DEFAULT_ERROR_MSG "Link denied (No link block found with your server name or link::incoming::mask did not match)"
 
 /** Verify server link.
  * This does authentication and authorization checks.
  * @param cptr The client directly connected to us (cptr).
  * @param client The client which (originally) issued the server command (client).
- * @param servername The server name provided by the client.
  * @param link_out Pointer-to-pointer-to-link block. Will be set when auth OK. Caller may pass NULL if he doesn't care.
  * @returns This function returns 1 on successful authentication, 0 otherwise - in which case the client has been killed.
  */
-int _verify_link(Client *client, char *servername, ConfigItem_link **link_out)
+int _verify_link(Client *client, ConfigItem_link **link_out)
 {
-	char xerrmsg[256];
-	ConfigItem_link *link;
-	char *inpath = get_client_name(client, TRUE);
+	ConfigItem_link *link, *orig_link;
 	Client *acptr = NULL, *ocptr = NULL;
 	ConfigItem_ban *bconf;
 
@@ -252,81 +655,72 @@ int _verify_link(Client *client, char *servername, ConfigItem_link **link_out)
 	if (link_out)
 		*link_out = NULL;
 	
-	strcpy(xerrmsg, "No matching link configuration");
-
 	if (!client->local->passwd)
 	{
-		sendto_one(client, NULL, "ERROR :Missing password");
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_PASSWORD", client,
+			   "Link with server $client.details denied: No password provided. Protocol error.");
 		exit_client(client, NULL, "Missing password");
 		return 0;
 	}
 
-	/* First check if the server is in the list */
-	if (!servername) {
-		strcpy(xerrmsg, "Null servername");
-		goto errlink;
-	}
-	
-	if (client->serv && client->serv->conf)
+	if (client->server && client->server->conf)
 	{
 		/* This is an outgoing connect so we already know what link block we are
-		 * dealing with. It's the one in: client->serv->conf
+		 * dealing with. It's the one in: client->server->conf
 		 */
 
 		/* Actually we still need to double check the servername to avoid confusion. */
-		if (strcasecmp(servername, client->serv->conf->servername))
+		if (strcasecmp(client->name, client->server->conf->servername))
 		{
-			ircsnprintf(xerrmsg, sizeof(xerrmsg), "Outgoing connect from link block '%s' but server "
-				"introduced himself as '%s'. Server name mismatch.",
-				client->serv->conf->servername,
-				servername);
-
-			sendto_one(client, NULL, "ERROR :%s", xerrmsg);
-			sendto_ops_and_log("Outgoing link aborted to %s(%s@%s) (%s) %s",
-				client->serv->conf->servername, client->ident, client->local->sockhost, xerrmsg, inpath);
-			exit_client(client, NULL, xerrmsg);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVERNAME_MISMATCH", client,
+			           "Link with server $client.details denied: "
+			           "Outgoing connect from link block '$link_block' but server "
+			           "introduced itself as '$client'. Server name mismatch.",
+			           log_data_link_block(client->server->conf));
+			exit_client_fmt(client, NULL, "Servername (%s) does not match name in my link block (%s)",
+			                client->name, client->server->conf->servername);
 			return 0;
 		}
-		link = client->serv->conf;
+		link = client->server->conf;
 		goto skip_host_check;
 	} else {
 		/* Hunt the linkblock down ;) */
 		for(link = conf_link; link; link = link->next)
-			if (match_simple(link->servername, servername))
+			if (match_simple(link->servername, client->name))
 				break;
 	}
 	
 	if (!link)
 	{
-		ircsnprintf(xerrmsg, sizeof(xerrmsg), "No link block named '%s'", servername);
-		goto errlink;
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_UNKNOWN_SERVER", client,
+		           "Link with server $client.details denied: No link block named '$client'");
+		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
+		return 0;
 	}
 	
 	if (!link->incoming.mask)
 	{
-		ircsnprintf(xerrmsg, sizeof(xerrmsg), "Link block '%s' exists but has no link::incoming::mask", servername);
-		goto errlink;
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_INCOMING", client,
+		           "Link with server $client.details denied: Link block exists, but there is no link::incoming::mask set.",
+		           log_data_link_block(link));
+		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
+		return 0;
 	}
 
-	link = find_link(servername, client);
+	orig_link = link;
+	link = find_link(client->name, client);
 
 	if (!link)
 	{
-		ircsnprintf(xerrmsg, sizeof(xerrmsg), "Server is in link block but link::incoming::mask didn't match");
-errlink:
-		/* Send the "simple" error msg to the server */
-		sendto_one(client, NULL,
-		    "ERROR :Link denied (No link block found named '%s' or link::incoming::mask did not match your IP %s) %s",
-		    servername, GetIP(client), inpath);
-		/* And send the "verbose" error msg only to locally connected ircops */
-		sendto_ops_and_log("Link denied for %s(%s@%s) (%s) %s",
-		    servername, client->ident, client->local->sockhost, xerrmsg, inpath);
-		exit_client(client, NULL, "Link denied (No link block found with your server name or link::incoming::mask did not 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",
+		           log_data_link_block(orig_link));
+		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
 		return 0;
 	}
 
 skip_host_check:
-	/* Now for checking passwords */
+	/* Try to authenticate the server... */
 	if (!Auth_Check(client, link->auth, client->local->passwd))
 	{
 		/* Let's help admins a bit with a good error message in case
@@ -339,32 +733,39 @@ skip_host_check:
 		if (((link->auth->type == AUTHTYPE_PLAINTEXT) && client->local->passwd && !strcmp(client->local->passwd, "*")) ||
 		    ((link->auth->type != AUTHTYPE_PLAINTEXT) && client->local->passwd && strcmp(client->local->passwd, "*")))
 		{
-			sendto_ops_and_log("Link denied for '%s' (Authentication failed due to different password types on both sides of the link) %s",
-				servername, inpath);
-			sendto_ops_and_log("Read https://www.unrealircd.org/docs/FAQ#auth-fail-mixed for more information");
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
+			           "Link with server $client.details denied: Authentication failed: $auth_failure_msg",
+			           log_data_string("auth_failure_msg", "different password types on both sides of the link\n"
+			                                               "Read https://www.unrealircd.org/docs/FAQ#auth-fail-mixed for more information"),
+			           log_data_link_block(link));
 		} else
 		if (link->auth->type == AUTHTYPE_SPKIFP)
 		{
-			sendto_ops_and_log("Link denied for '%s' (Authentication failed [spkifp mismatch]) %s",
-				servername, inpath);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
+			           "Link with server $client.details denied: Authentication failed: $auth_failure_msg",
+			           log_data_string("auth_failure_msg", "spkifp mismatch"),
+			           log_data_link_block(link));
 		} else
 		if (link->auth->type == AUTHTYPE_TLS_CLIENTCERT)
 		{
-			sendto_ops_and_log("Link denied for '%s' (Authentication failed [tlsclientcert mismatch]) %s",
-				servername, inpath);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
+			           "Link with server $client.details denied: Authentication failed: $auth_failure_msg",
+			           log_data_string("auth_failure_msg", "tlsclientcert mismatch"),
+			           log_data_link_block(link));
 		} else
 		if (link->auth->type == AUTHTYPE_TLS_CLIENTCERTFP)
 		{
-			sendto_ops_and_log("Link denied for '%s' (Authentication failed [tlsclientcertfp mismatch]) %s",
-				servername, inpath);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
+			           "Link with server $client.details denied: Authentication failed: $auth_failure_msg",
+			           log_data_string("auth_failure_msg", "certfp mismatch"),
+			           log_data_link_block(link));
 		} else
 		{
-			sendto_ops_and_log("Link denied for '%s' (Authentication failed [Bad password?]) %s",
-				servername, inpath);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_AUTH_FAILED", client,
+			           "Link with server $client.details denied: Authentication failed: $auth_failure_msg",
+			           log_data_string("auth_failure_msg", "bad password"),
+			           log_data_link_block(link));
 		}
-		sendto_one(client, NULL,
-		    "ERROR :Link '%s' denied (Authentication failed) %s",
-		    servername, inpath);
 		exit_client(client, NULL, "Link denied (Authentication failed)");
 		return 0;
 	}
@@ -376,88 +777,90 @@ skip_host_check:
 
 		if (!IsTLS(client))
 		{
-			sendto_one(client, NULL,
-				"ERROR :Link '%s' denied (Not using SSL/TLS) %s",
-				servername, inpath);
-			sendto_ops_and_log("Link denied for '%s' (Not using SSL/TLS and verify-certificate is on) %s",
-				servername, inpath);
-			exit_client(client, NULL, "Link denied (Not using SSL/TLS)");
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_VERIFY_CERTIFICATE_FAILED", client,
+			           "Link with server $client.details denied: verify-certificate failed: $certificate_failure_msg",
+			           log_data_string("certificate_failure_msg", "not using TLS"),
+			           log_data_link_block(link));
+			exit_client(client, NULL, "Link denied (Not using TLS)");
 			return 0;
 		}
 		if (!verify_certificate(client->local->ssl, link->servername, &errstr))
 		{
-			sendto_one(client, NULL,
-				"ERROR :Link '%s' denied (Certificate verification failed) %s",
-				servername, inpath);
-			sendto_ops_and_log("Link denied for '%s' (Certificate verification failed) %s",
-				servername, inpath);
-			sendto_ops_and_log("Reason for certificate verification failure: %s", errstr);
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_VERIFY_CERTIFICATE_FAILED", client,
+			           "Link with server $client.details denied: verify-certificate failed: $certificate_failure_msg",
+			           log_data_string("certificate_failure_msg", errstr),
+			           log_data_link_block(link));
 			exit_client(client, NULL, "Link denied (Certificate verification failed)");
 			return 0;
 		}
 	}
 
-	/*
-	 * Third phase, we check that the server does not exist
-	 * already
-	 */
-	if ((acptr = find_server(servername, NULL)))
-	{
-		/* Found. Bad. Quit. */
-
-		if (IsMe(acptr))
-		{
-			sendto_ops_and_log("Link %s rejected, server trying to link with my name (%s)",
-				get_client_name(client, TRUE), me.name);
-			sendto_one(client, NULL, "ERROR: Server %s exists (it's me!)", me.name);
-			exit_client(client, NULL, "Server Exists");
-			return 0;
-		}
-
-		acptr = acptr->direction;
-		ocptr = (client->local->firsttime > acptr->local->firsttime) ? acptr : client;
-		acptr = (client->local->firsttime > acptr->local->firsttime) ? client : acptr;
-		sendto_one(acptr, NULL,
-		    "ERROR :Server %s already exists from %s",
-		    servername,
-		    (ocptr->direction ? ocptr->direction->name : "<nobody>"));
-		sendto_ops_and_log
-		    ("Link %s cancelled, server %s already exists from %s",
-		    get_client_name(acptr, TRUE), servername,
-		    (ocptr->direction ? ocptr->direction->name : "<nobody>"));
-		exit_client(acptr, NULL, "Server Exists");
-		return 0;
-	}
-	if ((bconf = find_ban(NULL, servername, CONF_BAN_SERVER)))
+	if ((bconf = find_ban(NULL, client->name, CONF_BAN_SERVER)))
 	{
-		sendto_ops_and_log
-			("Cancelling link %s, banned server",
-			get_client_name(client, TRUE));
-		sendto_one(client, NULL, "ERROR :Banned server (%s)", bconf->reason ? bconf->reason : "no reason");
-		exit_client(client, NULL, "Banned server");
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVER_BAN", client,
+		           "Link with server $client.details denied: "
+		           "Server is banned ($ban_reason)",
+		           log_data_string("ban_reason", bconf->reason),
+		           log_data_link_block(link));
+		exit_client_fmt(client, NULL, "Banned server: %s", bconf->reason);
 		return 0;
 	}
+
 	if (link->class->clients + 1 > link->class->maxclients)
 	{
-		sendto_ops_and_log("Cancelling link %s, full class",
-				get_client_name(client, TRUE));
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_CLASS_FULL", client,
+		           "Link with server $client.details denied: "
+		           "class '$link_block.class' is full",
+		           log_data_link_block(link));
 		exit_client(client, NULL, "Full class");
 		return 0;
 	}
 	if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_DENY) && !IsSecure(client))
 	{
-		sendto_one(client, NULL, "ERROR :Servers need to use SSL/TLS (set::plaintext-policy::server is 'deny')");
-		sendto_ops_and_log("Rejected insecure server %s. See https://www.unrealircd.org/docs/FAQ#ERROR:_Servers_need_to_use_SSL.2FTLS", client->name);
-		exit_client(client, NULL, "Servers need to use SSL/TLS (set::plaintext-policy::server is 'deny')");
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_TLS", client,
+		           "Link with server $client.details denied: "
+		           "Server needs to use TLS (set::plaintext-policy::server is 'deny')\n"
+		           "See https://www.unrealircd.org/docs/FAQ#server-requires-tls",
+		           log_data_link_block(link));
+		exit_client(client, NULL, "Servers need to use TLS (set::plaintext-policy::server is 'deny')");
 		return 0;
 	}
 	if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_DENY) && outdated_tls_client(client))
 	{
-		sendto_one(client, NULL, "ERROR :Server is using an outdated SSL/TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')");
-		sendto_ops_and_log("Rejected server %s using outdated %s. See https://www.unrealircd.org/docs/FAQ#server-outdated-tls", tls_get_cipher(client->local->ssl), client->name);
-		exit_client(client, NULL, "Server using outdates SSL/TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')");
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_OUTDATED_TLS", client,
+		           "Link with server $client.details denied: "
+		           "Server is using an outdated TLS protocol or cipher ($tls_cipher) and set::outdated-tls-policy::server is 'deny'.\n"
+		           "See https://www.unrealircd.org/docs/FAQ#server-outdated-tls",
+		           log_data_link_block(link),
+			   log_data_string("tls_cipher", tls_get_cipher(client)));
+		exit_client(client, NULL, "Server using outdates TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')");
 		return 0;
 	}
+	/* This one is at the end, because it causes us to delink another server,
+	 * so we want to be (reasonably) sure that this one will succeed before
+	 * breaking the other one.
+	 */
+	if ((acptr = find_server(client->name, NULL)))
+	{
+		if (IsMe(acptr))
+		{
+			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SERVER_EXISTS", client,
+			           "Link with server $client.details denied: "
+			           "Server is trying to link with my name ($me_name)",
+			           log_data_string("me_name", me.name),
+			           log_data_link_block(link));
+			exit_client(client, NULL, "Server Exists (server trying to link with same name as myself)");
+			return 0;
+		} else {
+			unreal_log(ULOG_ERROR, "link", "LINK_DROPPED_REINTRODUCED", client,
+				   "Link with server $client.details causes older link "
+				   "with same server via $existing_client.server.uplink to be dropped.",
+				   log_data_client("existing_client", acptr),
+			           log_data_link_block(link));
+			exit_client_ex(acptr, client->direction, NULL, "Old link dropped, resyncing");
+		}
+	}
+
 	if (link_out)
 		*link_out = link;
 	return 1;
@@ -470,7 +873,7 @@ skip_host_check:
  */
 CMD_FUNC(cmd_server)
 {
-	char *servername = NULL;	/* Pointer for servername */
+	const char *servername = NULL;	/* Pointer for servername */
 	char *ch = NULL;	/* */
 	char descbuf[BUFSIZE];
 	int  hop = 0;
@@ -478,6 +881,7 @@ CMD_FUNC(cmd_server)
 	ConfigItem_link *aconf = NULL;
 	ConfigItem_deny_link *deny;
 	char *flags = NULL, *protocol = NULL, *inf = NULL, *num = NULL;
+	int incoming;
 
 	if (IsUser(client))
 	{
@@ -488,8 +892,7 @@ CMD_FUNC(cmd_server)
 
 	if (parc < 4 || (!*parv[3]))
 	{
-		sendto_one(client, NULL, "ERROR :Not enough SERVER parameters");
-		exit_client(client, NULL,  "Not enough parameters");
+		exit_client(client, NULL,  "Not enough SERVER parameters");
 		return;
 	}
 
@@ -498,10 +901,11 @@ CMD_FUNC(cmd_server)
 	/* Remote 'SERVER' command is not possible on a 100% SID network */
 	if (!MyConnect(client))
 	{
-		char buf[256];
-		sendto_umode_global(UMODE_OPER, "Server %s introduced %s which is using old unsupported protocol from UnrealIRCd 3.2.x or earlier. " 
-		                                "See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
-		                                client->direction->name, servername);
+		unreal_log(ULOG_ERROR, "link", "LINK_OLD_PROTOCOL", client,
+		           "Server link $client tried to introduce $servername using SERVER command. "
+		           "Server is using an old and unsupported protocol from UnrealIRCd 3.2.x or earlier. "
+		           "See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
+		           log_data_string("servername", servername));
 		exit_client(client->direction, NULL, "Introduced another server with unsupported protocol");
 		return;
 	}
@@ -514,38 +918,37 @@ CMD_FUNC(cmd_server)
 
 	if (!valid_server_name(servername))
 	{
-		sendto_one(client, NULL, "ERROR :Bogus server name (%s)", servername);
-		sendto_snomask
-		    (SNO_JUNK,
-		    "WARNING: Bogus server name (%s) from %s (maybe just a fishy client)",
-		    servername, get_client_name(client, TRUE));
 		exit_client(client, NULL, "Bogus server name");
 		return;
 	}
 
 	if (!client->local->passwd)
 	{
-		sendto_one(client, NULL, "ERROR :Missing password");
 		exit_client(client, NULL, "Missing password");
 		return;
 	}
 
-	if (!verify_link(client, servername, &aconf))
+	/* We set the client->name early here, even though it is not authenticated yet.
+	 * Reason is that it makes the notices and logging more useful.
+	 * This should be safe as it is not in the server linked list yet or hash table.
+	 * CMTSRV941 -- Syzop.
+	 */
+	strlcpy(client->name, servername, sizeof(client->name));
+
+	if (!verify_link(client, &aconf))
 		return; /* Rejected */
 
 	/* From this point the server is authenticated, so we can be more verbose
 	 * with notices to ircops and in exit_client() and such.
 	 */
 
-	strlcpy(client->name, servername, sizeof(client->name));
 
 	if (strlen(client->id) != 3)
 	{
-		sendto_umode_global(UMODE_OPER, "Server %s is using old unsupported protocol from UnrealIRCd 3.2.x or earlier. " 
-		                                "See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
-		                                servername);
-		ircd_log(LOG_ERROR, "Server using old unsupported protocol from UnrealIRCd 3.2.x or earlier. "
-		                    "See https://www.unrealircd.org/docs/FAQ#old-server-protocol");
+		unreal_log(ULOG_ERROR, "link", "LINK_OLD_PROTOCOL", client,
+		           "Server link $servername rejected. Server is using an old and unsupported protocol from UnrealIRCd 3.2.x or earlier. "
+		           "See https://www.unrealircd.org/docs/FAQ#old-server-protocol",
+		           log_data_string("servername", servername));
 		exit_client(client, NULL, "Server using old unsupported protocol from UnrealIRCd 3.2.x or earlier. "
 		                          "See https://www.unrealircd.org/docs/FAQ#old-server-protocol");
 		return;
@@ -554,8 +957,10 @@ CMD_FUNC(cmd_server)
 	hop = atol(parv[2]);
 	if (hop != 1)
 	{
-		sendto_umode_global(UMODE_OPER, "Directly linked server %s provided a hopcount of %d, while 1 was expected",
-		                                servername, hop);
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_INVALID_HOPCOUNT", client,
+		           "Server link $servername rejected. Directly linked server provided a hopcount of $hopcount, while 1 was expected.",
+		           log_data_string("servername", servername),
+		           log_data_integer("hopcount", hop));
 		exit_client(client, NULL, "Invalid SERVER message, hop count must be 1");
 		return;
 	}
@@ -597,11 +1002,12 @@ CMD_FUNC(cmd_server)
 	/* Process deny server { } restrictions */
 	for (deny = conf_deny_link; deny; deny = deny->next)
 	{
-		if (deny->flag.type == CRULE_ALL && match_simple(deny->mask, servername)
+		if (deny->flag.type == CRULE_ALL && unreal_mask_match_string(servername, deny->mask)
 			&& crule_eval(deny->rule))
 		{
-			sendto_ops_and_log("Refused connection from %s. Rejected by deny link { } block.",
-				get_client_host(client));
+			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;
 		}
@@ -613,7 +1019,86 @@ CMD_FUNC(cmd_server)
 	ircsnprintf(descbuf, sizeof descbuf, "Server: %s", servername);
 	fd_desc(client->local->fd, descbuf);
 
-	server_sync(client, aconf);
+	incoming = IsUnknown(client) ? 1 : 0;
+
+	if (client->local->passwd)
+		safe_free(client->local->passwd);
+
+	/* Set up server structure */
+	free_pending_net(client);
+	SetServer(client);
+	irccounts.me_servers++;
+	irccounts.servers++;
+	irccounts.unknown--;
+	list_move(&client->client_node, &global_server_list);
+	list_move(&client->lclient_node, &lclient_list);
+	list_add(&client->special_node, &server_list);
+
+	if (find_uline(client->name))
+	{
+		if (client->server && client->server->features.software && !strncmp(client->server->features.software, "UnrealIRCd-", 11))
+		{
+			unreal_log(ULOG_ERROR, "link", "BAD_ULINES", client,
+			           "Bad ulines! Server $client matches your ulines { } block, but this server "
+			           "is an UnrealIRCd server. UnrealIRCd servers should never be ulined as it "
+			           "causes security issues. Ulines should only be added for services! "
+			           "See https://www.unrealircd.org/docs/FAQ#bad-ulines.");
+			exit_client(client, NULL, "Bad ulines. See https://www.unrealircd.org/docs/FAQ#bad-ulines");
+		}
+		SetULine(client);
+	}
+
+	find_or_add(client->name);
+
+	if (IsSecure(client))
+	{
+		unreal_log(ULOG_INFO, "link", "SERVER_LINKED", client,
+		           "Server linked: $me -> $client [secure: $tls_cipher]",
+		           log_data_string("tls_cipher", tls_get_cipher(client)),
+		           log_data_client("me", &me));
+		tls_link_notification_verify(client, aconf);
+	}
+	else
+	{
+		unreal_log(ULOG_INFO, "link", "SERVER_LINKED", client,
+		           "Server linked: $me -> $client",
+		           log_data_client("me", &me));
+		/* Print out a warning if linking to a non-TLS server unless it's localhost.
+		 * Yeah.. there are still other cases when non-TLS links are fine (eg: local IP
+		 * of the same machine), we won't bother with detecting that. -- Syzop
+		 */
+		if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_WARN))
+		{
+			unreal_log(ULOG_WARNING, "link", "LINK_WARNING_NO_TLS", client,
+				   "Link with server $client.details is unencrypted (not TLS). "
+				   "We highly recommend to use TLS for server linking. "
+				   "See https://www.unrealircd.org/docs/Linking_servers",
+				   log_data_link_block(aconf));
+		}
+		if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_WARN) && outdated_tls_client(client))
+		{
+			unreal_log(ULOG_WARNING, "link", "LINK_WARNING_OUTDATED_TLS", client,
+				   "Link with server $client.details is using an outdated "
+				   "TLS protocol or cipher ($tls_cipher).",
+				   log_data_link_block(aconf),
+				   log_data_string("tls_cipher", tls_get_cipher(client)));
+		}
+	}
+
+	add_to_client_hash_table(client->name, client);
+	/* doesnt duplicate client->server if allocted this struct already */
+	make_server(client);
+	client->uplink = &me;
+	if (!client->server->conf)
+		client->server->conf = aconf; /* Only set serv->conf to aconf if not set already! Bug #0003913 */
+	if (incoming)
+		client->server->conf->refcount++;
+	client->server->conf->class->clients++;
+	client->local->class = client->server->conf->class;
+
+	RunHook(HOOKTYPE_SERVER_CONNECT, client);
+
+	server_sync(client, aconf, incoming);
 }
 
 /** Remote server command (SID).
@@ -627,9 +1112,9 @@ CMD_FUNC(cmd_sid)
 	Client *acptr, *ocptr;
 	ConfigItem_link	*aconf;
 	ConfigItem_ban *bconf;
-	int 	hop;
-	char	*servername = parv[1];
-	Client *cptr = client->direction; /* lazy, since this function may be removed soon */
+	int hop;
+	const char *servername = parv[1];
+	Client *direction = client->direction; /* lazy, since this function may be removed soon */
 
 	/* Only allow this command from server sockets */
 	if (!IsServer(client->direction))
@@ -640,7 +1125,25 @@ CMD_FUNC(cmd_sid)
 
 	if (parc < 4 || BadPtr(parv[3]))
 	{
-		sendto_one(client, NULL, "ERROR :Not enough SID parameters");
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SID");
+		return;
+	}
+
+	/* The SID check is done early because we do all the killing by SID,
+	 * so we want to know if that won't work first.
+	 */
+	if (!valid_sid(parv[3]))
+	{
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_INVALID_SID", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Invalid SID.",
+			   log_data_string("servername", servername),
+			   log_data_string("sid", parv[3]));
+		/* Since we cannot SQUIT via SID (since it is invalid), this gives
+		 * us huge doubts about the accuracy of the uplink, so in this case
+		 * we terminate the entire uplink.
+		 */
+		exit_client(client, NULL, "Trying to introduce a server with an invalid SID");
 		return;
 	}
 
@@ -651,87 +1154,116 @@ CMD_FUNC(cmd_sid)
 
 		if (IsMe(acptr))
 		{
-			sendto_ops_and_log("Link %s rejected, server trying to link with my name (%s)",
-				get_client_name(client, TRUE), me.name);
+			/* This should never happen, not even due to a race condition.
+			 * We cannot send SQUIT here either since it is unclear what
+			 * side would be squitted.
+			 * As said, not really important, as this does not happen anyway.
+			 */
+			unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_DUPLICATE_SERVER_IS_ME", client,
+			           "Denied remote server $servername which was introduced by $client: "
+			           "Server is using our servername, this should be impossible!",
+			           log_data_string("servername", servername));
 			sendto_one(client, NULL, "ERROR: Server %s exists (it's me!)", me.name);
 			exit_client(client, NULL, "Server Exists");
 			return;
 		}
 
-		// FIXME: verify this code:
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_DUPLICATE_SERVER", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Already linked via $existing_client.server.uplink.",
+			   log_data_string("servername", servername),
+			   log_data_client("existing_client", acptr));
+		// FIXME: oldest should die.
+		// FIXME: code below looks wrong, it checks direction TS instead of anything else
 		acptr = acptr->direction;
-		ocptr = (cptr->local->firsttime > acptr->local->firsttime) ? acptr : cptr;
-		acptr = (cptr->local->firsttime > acptr->local->firsttime) ? cptr : acptr;
-		sendto_one(acptr, NULL,
-		    "ERROR :Server %s already exists from %s",
-		    servername,
-		    (ocptr->direction ? ocptr->direction->name : "<nobody>"));
-		sendto_ops_and_log
-		    ("Link %s cancelled, server %s already exists from %s",
-		    get_client_name(acptr, TRUE), servername,
-		    (ocptr->direction ? ocptr->direction->name : "<nobody>"));
+		ocptr = (direction->local->creationtime > acptr->local->creationtime) ? acptr : direction;
+		acptr = (direction->local->creationtime > acptr->local->creationtime) ? direction : acptr;
+		// FIXME: Wait, this kills entire acptr? Without sending SQUIT even :D
 		exit_client(acptr, NULL, "Server Exists");
 		return;
 	}
 
+	if ((acptr = find_client(parv[3], NULL)))
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DUPLICATE_SID_SERVER", client,
+			   "Denied server $servername with SID $sid: Server with SID $existing_client.id ($existing_client) is already linked.",
+			   log_data_string("servername", servername),
+			   log_data_string("sid", parv[3]),
+			   log_data_client("existing_client", acptr));
+		sendto_one(client, NULL, "SQUIT %s :Server with this SID (%s) already exists (%s)", parv[3], parv[3], acptr->name);
+		return;
+	}
+
 	/* Check deny server { } */
 	if ((bconf = find_ban(NULL, servername, CONF_BAN_SERVER)))
 	{
-		sendto_ops_and_log("Cancelling link %s, banned server %s",
-			get_client_name(cptr, TRUE), servername);
-		sendto_one(cptr, NULL, "ERROR :Banned server (%s)", bconf->reason ? bconf->reason : "no reason");
-		exit_client(cptr, NULL, "Brought in banned server");
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_SERVER_BAN", client,
+		           "Denied remote server $servername which was introduced by $client: "
+		           "Server is banned ($ban_reason)",
+		           log_data_string("ban_reason", bconf->reason));
+		/* Before UnrealIRCd 6 this would SQUIT the server who introduced
+		 * this server. That seems a bit of an overreaction, so we now
+		 * send a SQUIT instead.
+		 */
+		sendto_one(client, NULL, "SQUIT %s :Banned server: %s", parv[3], bconf->reason);
 		return;
 	}
 
 	/* OK, let us check the data now */
 	if (!valid_server_name(servername))
 	{
-		sendto_ops_and_log("Link %s introduced server with bad server name '%s' -- disconnecting",
-		                   client->name, servername);
-		exit_client(cptr, NULL, "Introduced server with bad server name");
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_INVALID_SERVERNAME", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Invalid server name.",
+			   log_data_string("servername", servername));
+		sendto_one(client, NULL, "SQUIT %s :Invalid servername", parv[3]);
 		return;
 	}
 
-	hop = atol(parv[2]);
+	hop = atoi(parv[2]);
 	if (hop < 2)
 	{
-		sendto_ops_and_log("Server %s introduced server %s with hop count of %d, while >1 was expected",
-		                   client->name, servername, hop);
-		exit_client(cptr, NULL, "ERROR :Invalid hop count");
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_INVALID_HOP_COUNT", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Invalid server name.",
+			   log_data_string("servername", servername),
+			   log_data_integer("hop_count", hop));
+		sendto_one(client, NULL, "SQUIT %s :Invalid hop count (%d)", parv[3], hop);
 		return;
 	}
 
-	if (!valid_sid(parv[3]))
+	if (!client->direction->server->conf)
 	{
-		sendto_ops_and_log("Server %s introduced server %s with invalid SID '%s' -- disconnecting",
-		                   client->name, servername, parv[3]);
-		exit_client(cptr, NULL, "ERROR :Invalid SID");
+		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIG", client,
+			   "[BUG] Lost link conf record for link $direction.",
+			   log_data_client("direction", direction));
+		exit_client(client->direction, NULL, "BUG: lost link configuration");
 		return;
 	}
 
-	if (!cptr->serv->conf)
-	{
-		sendto_ops_and_log("Internal error: lost conf for %s!!, dropping link", cptr->name);
-		exit_client(cptr, NULL, "Internal error: lost configuration");
-		return;
-	}
-
-	aconf = cptr->serv->conf;
+	aconf = client->direction->server->conf;
 
 	if (!aconf->hub)
 	{
-		sendto_ops_and_log("Link %s cancelled, is Non-Hub but introduced Leaf %s",
-			cptr->name, servername);
-		exit_client(cptr, NULL, "Non-Hub Link");
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_NO_HUB", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Server may not introduce this server ($direction is not a hub).",
+			   log_data_string("servername", servername),
+			   log_data_client("direction", client->direction));
+		sendto_one(client, NULL, "SQUIT %s :Server is not permitted to be a hub: %s",
+			parv[3], client->direction->name);
 		return;
 	}
 
 	if (!match_simple(aconf->hub, servername))
 	{
-		sendto_ops_and_log("Link %s cancelled, linked in %s, which hub config disallows",
-			cptr->name, servername);
-		exit_client(cptr, NULL, "Not matching hub configuration");
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_NO_MATCHING_HUB", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Server may not introduce this server ($direction hubmask does not allow it).",
+			   log_data_string("servername", servername),
+			   log_data_client("direction", client->direction));
+		sendto_one(client, NULL, "SQUIT %s :Hub config for %s does not allow introducing this server",
+			parv[3], client->direction->name);
 		return;
 	}
 
@@ -739,31 +1271,37 @@ CMD_FUNC(cmd_sid)
 	{
 		if (!match_simple(aconf->leaf, servername))
 		{
-			sendto_ops_and_log("Link %s(%s) cancelled, disallowed by leaf configuration",
-				cptr->name, servername);
-			exit_client(cptr, NULL, "Disallowed by leaf configuration");
+			unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_NO_MATCHING_LEAF", client,
+				   "Denied remote server $servername which was introduced by $client: "
+				   "Server may not introduce this server ($direction leaf config does not allow it).",
+				   log_data_string("servername", servername),
+				   log_data_client("direction", client->direction));
+			sendto_one(client, NULL, "SQUIT %s :Leaf config for %s does not allow introducing this server",
+				parv[3], client->direction->name);
 			return;
 		}
 	}
 
 	if (aconf->leaf_depth && (hop > aconf->leaf_depth))
 	{
-		sendto_ops_and_log("Link %s(%s) cancelled, too deep depth",
-			cptr->name, servername);
-		exit_client(cptr, NULL, "Too deep link depth (leaf)");
+		unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_LEAF_DEPTH", client,
+			   "Denied remote server $servername which was introduced by $client: "
+			   "Server may not introduce this server ($direction leaf depth config does not allow it).",
+			   log_data_string("servername", servername),
+			   log_data_client("direction", client->direction));
+		sendto_one(client, NULL, "SQUIT %s :Leaf depth config for %s does not allow introducing this server",
+			parv[3], client->direction->name);
 		return;
 	}
 
 	/* All approved, add the server */
-	acptr = make_client(cptr, find_server(client->name, cptr));
+	acptr = make_client(direction, find_server(client->name, direction));
 	strlcpy(acptr->name, servername, sizeof(acptr->name));
 	acptr->hopcount = hop;
 	strlcpy(acptr->id, parv[3], sizeof(acptr->id));
 	strlcpy(acptr->info, parv[parc - 1], sizeof(acptr->info));
 	make_server(acptr);
-	acptr->serv->up = find_or_add(acptr->srvptr->name);
 	SetServer(acptr);
-	ircd_log(LOG_SERVER, "SERVER %s (from %s)", acptr->name, acptr->srvptr->name);
 	/* If this server is U-lined, or the parent is, then mark it as U-lined */
 	if (IsULine(client) || find_uline(acptr->name))
 		SetULine(acptr);
@@ -774,10 +1312,18 @@ CMD_FUNC(cmd_sid)
 	add_to_id_hash_table(acptr->id, acptr);
 	list_move(&acptr->client_node, &global_server_list);
 
+	if (IsULine(client->direction) || IsSynched(client->direction))
+	{
+		/* Log these (but don't show when still syncing) */
+		unreal_log(ULOG_INFO, "link", "SERVER_LINKED_REMOTE", acptr,
+			   "Server linked: $client -> $other_server",
+			   log_data_client("other_server", client));
+	}
+
 	RunHook(HOOKTYPE_SERVER_CONNECT, acptr);
 
 	sendto_server(client, 0, 0, NULL, ":%s SID %s %d %s :%s",
-		    acptr->srvptr->id, acptr->name, hop + 1, acptr->id, acptr->info);
+		    acptr->uplink->id, acptr->name, hop + 1, acptr->id, acptr->info);
 
 	RunHook(HOOKTYPE_POST_SERVER_CONNECT, acptr);
 }
@@ -786,7 +1332,7 @@ void _introduce_user(Client *to, Client *acptr)
 {
 	build_umode_string(acptr, 0, SEND_UMODES, buf);
 
-	sendto_one_nickcmd(to, acptr, buf);
+	sendto_one_nickcmd(to, NULL, acptr, buf);
 	
 	send_moddata_client(to, acptr);
 
@@ -811,65 +1357,6 @@ void _introduce_user(Client *to, Client *acptr)
 	}
 }
 
-void tls_link_notification_verify(Client *acptr, ConfigItem_link *aconf)
-{
-	char *spki_fp;
-	char *tls_fp;
-	char *errstr = NULL;
-	int verify_ok;
-
-	if (!MyConnect(acptr) || !acptr->local->ssl || !aconf)
-		return;
-
-	if ((aconf->auth->type == AUTHTYPE_TLS_CLIENTCERT) ||
-	    (aconf->auth->type == AUTHTYPE_TLS_CLIENTCERTFP) ||
-	    (aconf->auth->type == AUTHTYPE_SPKIFP))
-	{
-		/* Link verified by certificate or SPKI */
-		return;
-	}
-
-	if (aconf->verify_certificate)
-	{
-		/* Link verified by trust chain */
-		return;
-	}
-
-	tls_fp = moddata_client_get(acptr, "certfp");
-	spki_fp = spki_fingerprint(acptr);
-	if (!tls_fp || !spki_fp)
-		return; /* wtf ? */
-
-	/* Only bother the user if we are linking to UnrealIRCd 4.0.16+,
-	 * since only for these versions we can give precise instructions.
-	 */
-	if (!acptr->serv || acptr->serv->features.protocol < 4016)
-		return;
-
-	sendto_realops("You may want to consider verifying this server link.");
-	sendto_realops("More information about this can be found on https://www.unrealircd.org/Link_verification");
-
-	verify_ok = verify_certificate(acptr->local->ssl, aconf->servername, &errstr);
-	if (errstr && strstr(errstr, "not valid for hostname"))
-	{
-		sendto_realops("Unfortunately the certificate of server '%s' has a name mismatch:", acptr->name);
-		sendto_realops("%s", errstr);
-		sendto_realops("This isn't a fatal error but it will prevent you from using verify-certificate yes;");
-	} else
-	if (!verify_ok)
-	{
-		sendto_realops("In short: in the configuration file, change the 'link %s {' block to use this as a password:", acptr->name);
-		sendto_realops("password \"%s\" { spkifp; };", spki_fp);
-		sendto_realops("And follow the instructions on the other side of the link as well (which will be similar, but will use a different hash)");
-	} else
-	{
-		sendto_realops("In short: in the configuration file, add the following to your 'link %s {' block:", acptr->name);
-		sendto_realops("verify-certificate yes;");
-		sendto_realops("Alternatively, you could use SPKI fingerprint verification. Then change the password in the link block to be:");
-		sendto_realops("password \"%s\" { spkifp; };", spki_fp);
-	}
-}
-
 #define SafeStr(x)    ((x && *(x)) ? (x) : "*")
 
 /** Broadcast SINFO.
@@ -884,24 +1371,24 @@ void _broadcast_sinfo(Client *acptr, Client *to, Client *except)
 {
 	char chanmodes[128], buf[512];
 
-	if (acptr->serv->features.chanmodes[0])
+	if (acptr->server->features.chanmodes[0])
 	{
 		snprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
-			 acptr->serv->features.chanmodes[0],
-			 acptr->serv->features.chanmodes[1],
-			 acptr->serv->features.chanmodes[2],
-			 acptr->serv->features.chanmodes[3]);
+			 acptr->server->features.chanmodes[0],
+			 acptr->server->features.chanmodes[1],
+			 acptr->server->features.chanmodes[2],
+			 acptr->server->features.chanmodes[3]);
 	} else {
 		strlcpy(chanmodes, "*", sizeof(chanmodes));
 	}
 
 	snprintf(buf, sizeof(buf), "%lld %d %s %s %s :%s",
-		      (long long)acptr->serv->boottime,
-		      acptr->serv->features.protocol,
-		      SafeStr(acptr->serv->features.usermodes),
+		      (long long)acptr->server->boottime,
+		      acptr->server->features.protocol,
+		      SafeStr(acptr->server->features.usermodes),
 		      chanmodes,
-		      SafeStr(acptr->serv->features.nickchars),
-		      SafeStr(acptr->serv->features.software));
+		      SafeStr(acptr->server->features.nickchars),
+		      SafeStr(acptr->server->features.software));
 
 	if (to)
 	{
@@ -913,118 +1400,49 @@ void _broadcast_sinfo(Client *acptr, Client *to, Client *except)
 	}
 }
 
-int	server_sync(Client *cptr, ConfigItem_link *aconf)
+/** Sync all information with server 'client'.
+ * Eg: users, channels, everything.
+ * @param client	The newly linked in server
+ * @param aconf		The link block that belongs to this server
+ * @note This function (via cmd_server) is called from both sides, so
+ *       from the incoming side and the outgoing side.
+ */
+int server_sync(Client *client, ConfigItem_link *aconf, int incoming)
 {
-	char		*inpath = get_client_name(cptr, TRUE);
-	Client		*acptr;
-	int incoming = IsUnknown(cptr) ? 1 : 0;
-
-	ircd_log(LOG_SERVER, "SERVER %s", cptr->name);
+	Client *acptr;
 
-	if (cptr->local->passwd)
-	{
-		safe_free(cptr->local->passwd);
-		cptr->local->passwd = NULL;
-	}
 	if (incoming)
 	{
 		/* If this is an incomming connection, then we have just received
-		 * their stuff and now send our stuff back.
+		 * their stuff and now send our PASS, PROTOCTL and SERVER messages back.
 		 */
-		if (!IsEAuth(cptr)) /* if eauth'd then we already sent the passwd */
-			sendto_one(cptr, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
-
-		send_proto(cptr, aconf);
-		send_server_message(cptr);
-	}
+		if (!IsEAuth(client)) /* if eauth'd then we already sent the passwd */
+			sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
 
-	/* Set up server structure */
-	free_pending_net(cptr);
-	SetServer(cptr);
-	irccounts.me_servers++;
-	irccounts.servers++;
-	irccounts.unknown--;
-	list_move(&cptr->client_node, &global_server_list);
-	list_move(&cptr->lclient_node, &lclient_list);
-	list_add(&cptr->special_node, &server_list);
-	if (find_uline(cptr->name))
-	{
-		if (cptr->serv && cptr->serv->features.software && !strncmp(cptr->serv->features.software, "UnrealIRCd-", 11))
-		{
-			sendto_realops("\002WARNING:\002 Bad ulines! It seems your server is misconfigured: "
-			               "your ulines { } block is matching an UnrealIRCd server (%s). "
-			               "This is not correct and will cause security issues. "
-			               "ULines should only be added for services! "
-			               "See https://www.unrealircd.org/docs/FAQ#bad-ulines",
-			               cptr->name);
-		}
-		SetULine(cptr);
-	}
-	find_or_add(cptr->name);
-	if (IsSecure(cptr))
-	{
-		sendto_umode_global(UMODE_OPER,
-			"(\2link\2) Secure link %s -> %s established (%s)",
-			me.name, inpath, tls_get_cipher(cptr->local->ssl));
-		tls_link_notification_verify(cptr, aconf);
-	}
-	else
-	{
-		sendto_umode_global(UMODE_OPER,
-			"(\2link\2) Link %s -> %s established",
-			me.name, inpath);
-		/* Print out a warning if linking to a non-TLS server unless it's localhost.
-		 * Yeah.. there are still other cases when non-TLS links are fine (eg: local IP
-		 * of the same machine), we won't bother with detecting that. -- Syzop
-		 */
-		if (!IsLocalhost(cptr) && (iConf.plaintext_policy_server == POLICY_WARN))
-		{
-			sendto_realops("\002WARNING:\002 This link is unencrypted (not SSL/TLS). We highly recommend to use "
-			               "SSL/TLS for server linking. See https://www.unrealircd.org/docs/Linking_servers");
-		}
-		if (IsSecure(cptr) && (iConf.outdated_tls_policy_server == POLICY_WARN) && outdated_tls_client(cptr))
-		{
-			sendto_realops("\002WARNING:\002 This link is using an outdated SSL/TLS protocol or cipher (%s).",
-			               tls_get_cipher(cptr->local->ssl));
-		}
+		send_proto(client, aconf);
+		send_server_message(client);
 	}
-	add_to_client_hash_table(cptr->name, cptr);
-	/* doesnt duplicate cptr->serv if allocted this struct already */
-	make_server(cptr);
-	cptr->serv->up = me.name;
-	cptr->srvptr = &me;
-	if (!cptr->serv->conf)
-		cptr->serv->conf = aconf; /* Only set serv->conf to aconf if not set already! Bug #0003913 */
-	if (incoming)
-	{
-		cptr->serv->conf->refcount++;
-		Debug((DEBUG_ERROR, "reference count for %s (%s) is now %d",
-			cptr->name, cptr->serv->conf->servername, cptr->serv->conf->refcount));
-	}
-	cptr->serv->conf->class->clients++;
-	cptr->local->class = cptr->serv->conf->class;
-	RunHook(HOOKTYPE_SERVER_CONNECT, cptr);
 
 	/* Broadcast new server to the rest of the network */
-	sendto_server(cptr, 0, 0, NULL, ":%s SID %s 2 %s :%s",
-		    cptr->srvptr->id, cptr->name, cptr->id, cptr->info);
+	sendto_server(client, 0, 0, NULL, ":%s SID %s 2 %s :%s",
+		    client->uplink->id, client->name, client->id, client->info);
 
 	/* Broadcast the just-linked-in featureset to other servers on our side */
-	broadcast_sinfo(cptr, NULL, cptr);
+	broadcast_sinfo(client, NULL, client);
 
 	/* Send moddata of &me (if any, likely minimal) */
-	send_moddata_client(cptr, &me);
+	send_moddata_client(client, &me);
 
 	list_for_each_entry_reverse(acptr, &global_server_list, client_node)
 	{
-		/* acptr->direction == acptr for acptr == cptr */
-		if (acptr->direction == cptr)
+		/* acptr->direction == acptr for acptr == client */
+		if (acptr->direction == client)
 			continue;
 
 		if (IsServer(acptr))
 		{
-			sendto_one(cptr, NULL, ":%s SID %s %d %s :%s",
-			    acptr->srvptr->id,
+			sendto_one(client, NULL, ":%s SID %s %d %s :%s",
+			    acptr->uplink->id,
 			    acptr->name, acptr->hopcount + 1,
 			    acptr->id, acptr->info);
 
@@ -1037,28 +1455,22 @@ int	server_sync(Client *cptr, ConfigItem_link *aconf)
 			 * then you would think the other one is fully linked
 			 * while in fact he was not.. -- Syzop.
 			 */
-			if (acptr->serv->flags.synced)
-			{
-				sendto_one(cptr, NULL, ":%s EOS", acptr->id);
-#ifdef DEBUGMODE
-				ircd_log(LOG_ERROR, "[EOSDBG] server_sync: sending to uplink '%s' with src %s...",
-					cptr->name, acptr->name);
-#endif
-			}
+			if (acptr->server->flags.synced)
+				sendto_one(client, NULL, ":%s EOS", acptr->id);
 			/* Send SINFO of our servers to their side */
-			broadcast_sinfo(acptr, cptr, NULL);
-			send_moddata_client(cptr, acptr); /* send moddata of server 'acptr' (if any, likely minimal) */
+			broadcast_sinfo(acptr, client, NULL);
+			send_moddata_client(client, acptr); /* send moddata of server 'acptr' (if any, likely minimal) */
 		}
 	}
 
 	/* Synching nick information */
 	list_for_each_entry_reverse(acptr, &client_list, client_node)
 	{
-		/* acptr->direction == acptr for acptr == cptr */
-		if (acptr->direction == cptr)
+		/* acptr->direction == acptr for acptr == client */
+		if (acptr->direction == client)
 			continue;
 		if (IsUser(acptr))
-			introduce_user(cptr, acptr);
+			introduce_user(client, acptr);
 	}
 	/*
 	   ** Last, pass all channels plus statuses
@@ -1067,38 +1479,106 @@ int	server_sync(Client *cptr, ConfigItem_link *aconf)
 		Channel *channel;
 		for (channel = channels; channel; channel = channel->nextch)
 		{
-			send_channel_modes_sjoin3(cptr, channel);
+			send_channel_modes_sjoin3(client, channel);
 			if (channel->topic_time)
-				sendto_one(cptr, NULL, "TOPIC %s %s %lld :%s",
-				    channel->chname, channel->topic_nick,
+				sendto_one(client, NULL, "TOPIC %s %s %lld :%s",
+				    channel->name, channel->topic_nick,
 				    (long long)channel->topic_time, channel->topic);
-			send_moddata_channel(cptr, channel);
+			send_moddata_channel(client, channel);
 		}
 	}
 	
 	/* Send ModData for all member(ship) structs */
-	send_moddata_members(cptr);
+	send_moddata_members(client);
 	
 	/* pass on TKLs */
-	tkl_sync(cptr);
+	tkl_sync(client);
 
-	RunHook(HOOKTYPE_SERVER_SYNC, cptr);
+	RunHook(HOOKTYPE_SERVER_SYNC, client);
 
-	sendto_one(cptr, NULL, "NETINFO %i %lld %i %s 0 0 0 :%s",
+	sendto_one(client, NULL, "NETINFO %i %lld %i %s 0 0 0 :%s",
 	    irccounts.global_max, (long long)TStime(), UnrealProtocol,
-	    CLOAK_KEYCRC,
-	    ircnetwork);
+	    CLOAK_KEY_CHECKSUM,
+	    NETWORK_NAME);
 
 	/* Send EOS (End Of Sync) to the just linked server... */
-	sendto_one(cptr, NULL, ":%s EOS", me.id);
-#ifdef DEBUGMODE
-	ircd_log(LOG_ERROR, "[EOSDBG] server_sync: sending to justlinked '%s' with src ME...",
-			cptr->name);
-#endif
-	RunHook(HOOKTYPE_POST_SERVER_CONNECT, cptr);
+	sendto_one(client, NULL, ":%s EOS", me.id);
+	RunHook(HOOKTYPE_POST_SERVER_CONNECT, client);
 	return 0;
 }
 
+void tls_link_notification_verify(Client *client, ConfigItem_link *aconf)
+{
+	const char *spki_fp;
+	const char *tls_fp;
+	char *errstr = NULL;
+	int verify_ok;
+
+	if (!MyConnect(client) || !client->local->ssl || !aconf)
+		return;
+
+	if ((aconf->auth->type == AUTHTYPE_TLS_CLIENTCERT) ||
+	    (aconf->auth->type == AUTHTYPE_TLS_CLIENTCERTFP) ||
+	    (aconf->auth->type == AUTHTYPE_SPKIFP))
+	{
+		/* Link verified by certificate or SPKI */
+		return;
+	}
+
+	if (aconf->verify_certificate)
+	{
+		/* Link verified by trust chain */
+		return;
+	}
+
+	tls_fp = moddata_client_get(client, "certfp");
+	spki_fp = spki_fingerprint(client);
+	if (!tls_fp || !spki_fp)
+		return; /* wtf ? */
+
+	/* Only bother the user if we are linking to UnrealIRCd 4.0.16+,
+	 * since only for these versions we can give precise instructions.
+	 */
+	if (!client->server || client->server->features.protocol < 4016)
+		return;
+
+
+	verify_ok = verify_certificate(client->local->ssl, aconf->servername, &errstr);
+	if (errstr && strstr(errstr, "not valid for hostname"))
+	{
+		unreal_log(ULOG_INFO, "link", "HINT_VERIFY_LINK", client,
+		          "You may want to consider verifying this server link.\n"
+		          "More information about this can be found on https://www.unrealircd.org/Link_verification\n"
+		          "Unfortunately the certificate of server '$client' has a name mismatch:\n"
+		          "$tls_verify_error\n"
+		          "This isn't a fatal error but it will prevent you from using verify-certificate yes;",
+		          log_data_link_block(aconf),
+		          log_data_string("tls_verify_error", errstr));
+	} else
+	if (!verify_ok)
+	{
+		unreal_log(ULOG_INFO, "link", "HINT_VERIFY_LINK", client,
+		          "You may want to consider verifying this server link.\n"
+		          "More information about this can be found on https://www.unrealircd.org/Link_verification\n"
+		          "In short: in the configuration file, change the 'link $client {' block to use this as a password:\n"
+		          "password \"$spki_fingerprint\" { spkifp; };\n"
+		          "And follow the instructions on the other side of the link as well (which will be similar, but will use a different hash)",
+		          log_data_link_block(aconf),
+		          log_data_string("spki_fingerprint", spki_fp));
+	} else
+	{
+		unreal_log(ULOG_INFO, "link", "HINT_VERIFY_LINK", client,
+		          "You may want to consider verifying this server link.\n"
+		          "More information about this can be found on https://www.unrealircd.org/Link_verification\n"
+		          "In short: in the configuration file, add the following to your 'link $client {' block:\n"
+		          "verify-certificate yes;\n"
+		          "Alternatively, you could use SPKI fingerprint verification. Then change the password in the link block to be:\n"
+		          "password \"$spki_fingerprint\" { spki_fp; };",
+		          log_data_link_block(aconf),
+		          log_data_string("spki_fingerprint", spki_fp));
+	}
+}
+
 /** This will send "to" a full list of the modes for channel channel,
  *
  * Half of it recoded by Syzop: the whole buffering and size checking stuff
@@ -1118,8 +1598,9 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 	char *p; /* points to somewhere in 'tbuf' */
 	int prebuflen = 0; /* points to after the <sjointoken> <TS> <chan> <fixmodes> <fixparas <..>> : part */
 	int sent = 0; /* we need this so we send at least 1 message about the channel (eg if +P and no members, no bans, #4459) */
+	char modebuf[BUFSIZE], parabuf[BUFSIZE];
 
-	if (*channel->chname != '#')
+	if (*channel->name != '#')
 		return;
 
 	nomode = 0;
@@ -1129,7 +1610,11 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 	/* First we'll send channel, channel modes and members and status */
 
 	*modebuf = *parabuf = '\0';
-	channel_modes(to, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel);
+	channel_modes(to, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
+
+	/* Strip final space if needed */
+	if (*parabuf && (parabuf[strlen(parabuf)-1] == ' '))
+		parabuf[strlen(parabuf)-1] = '\0';
 
 	if (!modebuf[1])
 		nomode = 1;
@@ -1148,19 +1633,19 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 	{
 		ircsnprintf(buf, sizeof(buf),
 		    ":%s SJOIN %lld %s :", me.id,
-		    (long long)channel->creationtime, channel->chname);
+		    (long long)channel->creationtime, channel->name);
 	}
 	if (nopara && !nomode)
 	{
 		ircsnprintf(buf, sizeof(buf),
 		    ":%s SJOIN %lld %s %s :", me.id,
-		    (long long)channel->creationtime, channel->chname, modebuf);
+		    (long long)channel->creationtime, channel->name, modebuf);
 	}
 	if (!nopara && !nomode)
 	{
 		ircsnprintf(buf, sizeof(buf),
 		    ":%s SJOIN %lld %s %s %s :", me.id,
-		    (long long)channel->creationtime, channel->chname, modebuf, parabuf);
+		    (long long)channel->creationtime, channel->name, modebuf, parabuf);
 	}
 
 	prebuflen = strlen(buf);
@@ -1190,19 +1675,8 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 
 	for (lp = members; lp; lp = lp->next)
 	{
-		p = tbuf;
-		if (lp->flags & MODE_CHANOP)
-			*p++ = '@';
-		if (lp->flags & MODE_VOICE)
-			*p++ = '+';
-		if (lp->flags & MODE_HALFOP)
-			*p++ = '%';
-		if (lp->flags & MODE_CHANOWNER)
-			*p++ = '*';
-		if (lp->flags & MODE_CHANADMIN)
-			*p++ = '~';
-
-		p = mystpcpy(p, lp->client->id);
+		p = mystpcpy(tbuf, modes_to_sjoin_prefix(lp->member_modes)); /* eg @+ */
+		p = mystpcpy(p, lp->client->id); /* nick (well, id) */
 		*p++ = ' ';
 		*p = '\0';
 
@@ -1212,6 +1686,10 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 			/* Would overflow, so send our current stuff right now (except new stuff) */
 			sendto_one(to, mtags, "%s", buf);
 			sent++;
+			ircsnprintf(buf, sizeof(buf),
+			    ":%s SJOIN %lld %s :", me.id,
+			    (long long)channel->creationtime, channel->name);
+			prebuflen = strlen(buf);
 			bufptr = buf + prebuflen;
 			*bufptr = '\0';
 		}
@@ -1235,6 +1713,10 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 			/* Would overflow, so send our current stuff right now (except new stuff) */
 			sendto_one(to, mtags, "%s", buf);
 			sent++;
+			ircsnprintf(buf, sizeof(buf),
+			    ":%s SJOIN %lld %s :", me.id,
+			    (long long)channel->creationtime, channel->name);
+			prebuflen = strlen(buf);
 			bufptr = buf + prebuflen;
 			*bufptr = '\0';
 		}
@@ -1258,6 +1740,10 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 			/* Would overflow, so send our current stuff right now (except new stuff) */
 			sendto_one(to, mtags, "%s", buf);
 			sent++;
+			ircsnprintf(buf, sizeof(buf),
+			    ":%s SJOIN %lld %s :", me.id,
+			    (long long)channel->creationtime, channel->name);
+			prebuflen = strlen(buf);
 			bufptr = buf + prebuflen;
 			*bufptr = '\0';
 		}
@@ -1281,6 +1767,10 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 			/* Would overflow, so send our current stuff right now (except new stuff) */
 			sendto_one(to, mtags, "%s", buf);
 			sent++;
+			ircsnprintf(buf, sizeof(buf),
+			    ":%s SJOIN %lld %s :", me.id,
+			    (long long)channel->creationtime, channel->name);
+			prebuflen = strlen(buf);
 			bufptr = buf + prebuflen;
 			*bufptr = '\0';
 		}
@@ -1293,3 +1783,203 @@ void send_channel_modes_sjoin3(Client *to, Channel *channel)
 
 	free_message_tags(mtags);
 }
+
+void server_generic_free(ModData *m)
+{
+	safe_free(m->ptr);
+}
+
+int server_post_connect(Client *client) {
+	if (cfg.autoconnect_strategy == AUTOCONNECT_SEQUENTIAL_FALLBACK && last_autoconnect_server
+		&& !strcmp(last_autoconnect_server, client->name))
+	{
+		last_autoconnect_server = NULL;
+	}
+	return 0;
+}
+
+/** Start an outgoing connection to a server, for server linking.
+ * @param aconf		Configuration attached to this server
+ * @param by		The user initiating the connection (can be NULL)
+ * @param hp		The address to connect to.
+ */
+void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp)
+{
+	Client *client;
+
+	if (!aconf->outgoing.hostname)
+	{
+		/* Actually the caller should make sure that this doesn't happen,
+		 * so this error may never be triggered:
+		 */
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NO_OUTGOING", NULL,
+		           "Connect to $link_block failed: link block is for incoming only (no link::outgoing::hostname set)",
+		           log_data_link_block(aconf));
+		return;
+	}
+		
+	if (!hp)
+	{
+		/* Remove "cache" */
+		safe_free(aconf->connect_ip);
+	}
+	/*
+	 * If we dont know the IP# for this host and itis a hostname and
+	 * not a ip# string, then try and find the appropriate host record.
+	 */
+	if (!aconf->connect_ip)
+	{
+		if (is_valid_ip(aconf->outgoing.hostname))
+		{
+			/* link::outgoing::hostname is an IP address. No need to resolve host. */
+			safe_strdup(aconf->connect_ip, aconf->outgoing.hostname);
+		} else
+		{
+			/* It's a hostname, let the resolver look it up. */
+			int ipv4_explicit_bind = 0;
+
+			if (aconf->outgoing.bind_ip && (is_valid_ip(aconf->outgoing.bind_ip) == 4))
+				ipv4_explicit_bind = 1;
+			
+			/* We need this 'aconf->refcount++' or else there's a race condition between
+			 * starting resolving the host and the result of the resolver (we could
+			 * REHASH in that timeframe) leading to an invalid (freed!) 'aconf'.
+			 * -- Syzop, bug #0003689.
+			 */
+			aconf->refcount++;
+			unrealdns_gethostbyname_link(aconf->outgoing.hostname, aconf, ipv4_explicit_bind);
+			unreal_log(ULOG_INFO, "link", "LINK_RESOLVING", NULL,
+				   "Resolving hostname $link_block.hostname...",
+				   log_data_link_block(aconf));
+			/* Going to resolve the hostname, in the meantime we return (asynchronous operation) */
+			return;
+		}
+	}
+	client = make_client(NULL, &me);
+	client->local->hostp = hp;
+	/*
+	 * Copy these in so we have something for error detection.
+	 */
+	strlcpy(client->name, aconf->servername, sizeof(client->name));
+	strlcpy(client->local->sockhost, aconf->outgoing.hostname, HOSTLEN + 1);
+
+	if (!connect_server_helper(aconf, client))
+	{
+		fd_close(client->local->fd);
+		--OpenFiles;
+		client->local->fd = -2;
+		free_client(client);
+		/* Fatal error */
+		return;
+	}
+	/* The socket has been connected or connect is in progress. */
+	make_server(client);
+	client->server->conf = aconf;
+	client->server->conf->refcount++;
+	if (by && IsUser(by))
+		strlcpy(client->server->by, by->name, sizeof(client->server->by));
+	else
+		strlcpy(client->server->by, "AutoConn.", sizeof client->server->by);
+	SetConnecting(client);
+	SetOutgoing(client);
+	irccounts.unknown++;
+	list_add(&client->lclient_node, &unknown_list);
+	set_sockhost(client, aconf->outgoing.hostname);
+	add_client_to_list(client);
+
+	if (aconf->outgoing.options & CONNECT_TLS)
+	{
+		SetTLSConnectHandshake(client);
+		fd_setselect(client->local->fd, FD_SELECT_WRITE, unreal_tls_client_handshake, client);
+	}
+	else
+		fd_setselect(client->local->fd, FD_SELECT_WRITE, completed_connection, client);
+
+	unreal_log(ULOG_INFO, "link", "LINK_CONNECTING", client,
+		   "Trying to activate link with server $client ($link_block.ip:$link_block.port)...",
+		   log_data_link_block(aconf));
+}
+
+/** Helper function for connect_server() to prepare the actual bind()'ing and connect().
+ * This will also take care of logging/sending error messages.
+ * @param aconf		Configuration entry of the server.
+ * @param client	The client entry that we will use and fill in.
+ * @returns 1 on success, 0 on failure.
+ */
+static int connect_server_helper(ConfigItem_link *aconf, Client *client)
+{
+	char *bindip;
+	char buf[BUFSIZE];
+
+	if (!aconf->connect_ip)
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NOIP", client,
+		           "Connect to $client failed: no IP address to connect to",
+		           log_data_link_block(aconf));
+		return 0; /* handled upstream or shouldn't happen */
+	}
+	
+	if (strchr(aconf->connect_ip, ':'))
+		SetIPV6(client);
+	
+	safe_strdup(client->ip, aconf->connect_ip);
+	
+	snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(client, TRUE));
+	client->local->fd = fd_socket(IsIPV6(client) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf);
+	if (client->local->fd < 0)
+	{
+		if (ERRNO == P_EMFILE)
+		{
+			unreal_log(ULOG_ERROR, "link", "LINK_ERROR_MAXCLIENTS", client,
+				   "Connect to $client failed: no more sockets available",
+				   log_data_link_block(aconf));
+			return 0;
+		}
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_SOCKET", client,
+			   "Connect to $client failed: could not create socket: $socket_error",
+			   log_data_socket_error(-1),
+			   log_data_link_block(aconf));
+		return 0;
+	}
+	if (++OpenFiles >= maxclients)
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_MAXCLIENTS", client,
+			   "Connect to $client failed: no more connections available",
+			   log_data_link_block(aconf));
+		return 0;
+	}
+
+	set_sockhost(client, aconf->outgoing.hostname);
+
+	if (!aconf->outgoing.bind_ip && iConf.link_bindip)
+		bindip = iConf.link_bindip;
+	else
+		bindip = aconf->outgoing.bind_ip;
+
+	if (bindip && strcmp("*", bindip))
+	{
+		if (!unreal_bind(client->local->fd, bindip, 0, IsIPV6(client)))
+		{
+			unreal_log(ULOG_ERROR, "link", "LINK_ERROR_SOCKET_BIND", client,
+				   "Connect to $client failed: could not bind socket to $link_block.bind_ip: $socket_error -- "
+				   "Your link::outgoing::bind-ip is probably incorrect.",
+				   log_data_socket_error(client->local->fd),
+				   log_data_link_block(aconf));
+			return 0;
+		}
+	}
+
+	set_sock_opts(client->local->fd, client, IsIPV6(client));
+
+	if (!unreal_connect(client->local->fd, client->ip, aconf->outgoing.port, IsIPV6(client)))
+	{
+			unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
+				   "Connect to $client ($link_block.ip:$link_block.port) failed: $socket_error",
+				   log_data_socket_error(client->local->fd),
+				   log_data_link_block(aconf));
+		return 0;
+	}
+
+	return 1;
+}
+
diff --git a/src/modules/sethost.c b/src/modules/sethost.c
@@ -33,7 +33,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /sethost", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -61,7 +61,7 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_sethost)
 {
-	char *vhost;
+	const char *vhost;
 
 	if (MyUser(client) && !ValidatePermissionsForPath("self:set:host",client,NULL,NULL,NULL))
 	{
@@ -88,7 +88,7 @@ CMD_FUNC(cmd_sethost)
 		return;
 	}
 
-	if (!valid_host(vhost))
+	if (!valid_host(vhost, 0))
 	{
 		sendnotice(client, "*** /SetHost Error: A hostname may only contain a-z, A-Z, 0-9, '-' & '.'.");
 		return;
diff --git a/src/modules/setident.c b/src/modules/setident.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"/setident", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -61,8 +61,7 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_setident)
 {
-
-	char *vident, *s;
+	const char *vident, *s;
 
 	if ((parc < 2) || BadPtr(parv[1]))
 	{
diff --git a/src/modules/setname.c b/src/modules/setname.c
@@ -23,8 +23,13 @@
 #include "unrealircd.h"
 
 CMD_FUNC(cmd_setname);
+char *setname_isupport_param(void);
 
 #define MSG_SETNAME 	"SETNAME"	/* setname */
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+long CAP_SETNAME = 0L;
 
 ModuleHeader MOD_HEADER
   = {
@@ -32,18 +37,32 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /setname", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
 {
-	CommandAdd(modinfo->handle, MSG_SETNAME, cmd_setname, 1, CMD_USER);
+	ClientCapabilityInfo cap;
+	ClientCapability *c;
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	CommandAdd(modinfo->handle, MSG_SETNAME, cmd_setname, 1, CMD_USER);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "setname";
+	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SETNAME);
+	if (!c)
+	{
+		config_error("[%s] Failed to request setname cap: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
 	return MOD_SUCCESS;
 }
 
 MOD_LOAD()
 {
+	ISupportAdd(modinfo->handle, "NAMELEN", setname_isupport_param());
 	return MOD_SUCCESS;
 }
 
@@ -52,19 +71,23 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+char *setname_isupport_param(void){
+	return STR(REALLEN);
+}
+
 /* cmd_setname - 12/05/1999 - Stskeeps
  * :prefix SETNAME :gecos
  * parv[1] - gecos
  * D: This will set your gecos to be <x> (like (/setname :The lonely wanderer))
-   yes it is experimental but anyways ;P
-    FREEDOM TO THE USERS! ;) 
+   this is now compatible with IRCv3 SETNAME --k4be
 */ 
 CMD_FUNC(cmd_setname)
 {
 	int xx;
-	char tmpinfo[REALLEN + 1];
+	char oldinfo[REALLEN + 1];
 	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64];
 	ConfigItem_ban *bconf;
+	MessageTag *mtags = NULL;
 
 	if ((parc < 2) || BadPtr(parv[1]))
 	{
@@ -74,25 +97,38 @@ CMD_FUNC(cmd_setname)
 
 	if (strlen(parv[1]) > REALLEN)
 	{
-		if (MyConnect(client))
+		if (!MyConnect(client))
+			return;
+		if (HasCapabilityFast(client, CAP_SETNAME))
 		{
-			sendnotice(client, "*** /SetName Error: \"Real names\" may maximum be %i characters of length",
-				REALLEN);
+			new_message(client, recv_mtags, &mtags);
+			sendto_one(client, mtags, ":%s FAIL SETNAME INVALID_REALNAME :\"Real names\" may maximum be %i characters of length", me.name, REALLEN);
+			free_message_tags(mtags);
+		}
+		else
+		{
+			sendnotice(client, "*** /SetName Error: \"Real names\" may maximum be %i characters of length", REALLEN);
 		}
 		return;
 	}
 
+	strlcpy(oldinfo, client->info, sizeof(oldinfo));
+
 	if (MyUser(client))
 	{
-		/* set temp info for spamfilter check*/
-		strcpy(tmpinfo, client->info);
 		/* set the new name before we check, but don't send to servers unless it is ok */
-		strcpy(client->info, parv[1]);
+		strlcpy(client->info, parv[1], sizeof(client->info));
 		spamfilter_build_user_string(spamfilter_user, client->name, client);
 		if (match_spamfilter(client, spamfilter_user, SPAMF_USER, "SETNAME", NULL, 0, NULL))
 		{
 			/* Was rejected by spamfilter, restore the realname */
-			strcpy(client->info, tmpinfo);
+			if (HasCapabilityFast(client, CAP_SETNAME))
+			{
+				new_message(client, recv_mtags, &mtags);
+				sendto_one(client, mtags, "%s FAIL SETNAME CANNOT_CHANGE_REALNAME :Rejected by server", me.name);
+				free_message_tags(mtags);
+			}
+			strlcpy(client->info, oldinfo, sizeof(client->info));
 			return;
 		}
 
@@ -105,14 +141,22 @@ CMD_FUNC(cmd_setname)
 		}
 	} else {
 		/* remote user */
-		strcpy(client->info, parv[1]);
+		strlcpy(client->info, parv[1], sizeof(client->info));
 	}
 
-	sendto_server(client, 0, 0, NULL, ":%s SETNAME :%s", client->id, parv[1]);
+	new_message(client, recv_mtags, &mtags);
+	sendto_local_common_channels(client, client, CAP_SETNAME, mtags, ":%s SETNAME :%s", client->name, client->info);
+	sendto_server(client, 0, 0, mtags, ":%s SETNAME :%s", client->id, parv[1]);
 
+	/* notify the sender */
 	if (MyConnect(client))
 	{
-		sendnotice(client, "Your \"real name\" is now set to be %s - you have to set it manually to undo it",
-		           parv[1]);
+		if (HasCapabilityFast(client, CAP_SETNAME))
+			sendto_prefix_one(client, client, mtags, ":%s SETNAME :%s", client->name, client->info);
+		else
+			sendnotice(client, "Your \"real name\" is now set to be %s - you have to set it manually to undo it", parv[1]);
 	}
+	free_message_tags(mtags);
+	
+	RunHook(HOOKTYPE_REALNAME_CHANGED, client, oldinfo);
 }
diff --git a/src/modules/silence.c b/src/modules/silence.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /silence", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Structs */
@@ -104,7 +104,8 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_silence)
 {
 	Silence *s;
-	char action, *p;
+	const char *p;
+	char action;
 
 	if (MyUser(client))
 	{
@@ -121,7 +122,7 @@ CMD_FUNC(cmd_silence)
 		{
 			p++;
 		} else
-		if (!strchr(p, '@') && !strchr(p, '.') && !strchr(p, '!') && !strchr(p, '*') && !find_person(p, NULL))
+		if (!strchr(p, '@') && !strchr(p, '.') && !strchr(p, '!') && !strchr(p, '*') && !find_user(p, NULL))
 		{
 			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 			return;
diff --git a/src/modules/sinfo.c b/src/modules/sinfo.c
@@ -12,7 +12,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Server information",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Forward declarations */
@@ -73,36 +73,36 @@ CMD_FUNC(sinfo_server)
 		return;
 	}
 
-	client->serv->boottime = atol(parv[1]);
-	client->serv->features.protocol = atoi(parv[2]);
+	client->server->boottime = atol(parv[1]);
+	client->server->features.protocol = atoi(parv[2]);
 
 	if (!strcmp(parv[3], "*"))
-		safe_free(client->serv->features.usermodes);
+		safe_free(client->server->features.usermodes);
 	else
-		safe_strdup(client->serv->features.usermodes, parv[3]);
+		safe_strdup(client->server->features.usermodes, parv[3]);
 
 	if (!strcmp(parv[4], "*"))
 	{
-		safe_free(client->serv->features.chanmodes[0]);
-		safe_free(client->serv->features.chanmodes[1]);
-		safe_free(client->serv->features.chanmodes[2]);
-		safe_free(client->serv->features.chanmodes[3]);
+		safe_free(client->server->features.chanmodes[0]);
+		safe_free(client->server->features.chanmodes[1]);
+		safe_free(client->server->features.chanmodes[2]);
+		safe_free(client->server->features.chanmodes[3]);
 	} else {
 		parse_chanmodes_protoctl(client, parv[4]);
 	}
 
 	if (!strcmp(parv[5], "*"))
-		safe_free(client->serv->features.nickchars);
+		safe_free(client->server->features.nickchars);
 	else
-		safe_strdup(client->serv->features.nickchars, parv[5]);
+		safe_strdup(client->server->features.nickchars, parv[5]);
 
 	/* Software is always the last parameter. It is currently parv[6]
 	 * but may change later. So always use parv[parc-1].
 	 */
 	if (!strcmp(parv[parc-1], "*"))
-		safe_free(client->serv->features.software);
+		safe_free(client->server->features.software);
 	else
-		safe_strdup(client->serv->features.software, parv[parc-1]);
+		safe_strdup(client->server->features.software, parv[parc-1]);
 
 	/* Broadcast to 'the other side' of the net */
 	concat_params(buf, sizeof(buf), parc, parv);
@@ -124,33 +124,33 @@ CMD_FUNC(sinfo_user)
 	{
 		sendtxtnumeric(client, "*** Server %s:", acptr->name);
 		sendtxtnumeric(client, "Protocol: %d",
-		               acptr->serv->features.protocol);
+		               acptr->server->features.protocol);
 		sendtxtnumeric(client, "Software: %s",
-		               SafeDisplayStr(acptr->serv->features.software));
-		if (!acptr->serv->boottime)
+		               SafeDisplayStr(acptr->server->features.software));
+		if (!acptr->server->boottime)
 		{
 			sendtxtnumeric(client, "Up since: -");
 			sendtxtnumeric(client, "Uptime: -");
 		} else {
 			sendtxtnumeric(client, "Up since: %s",
-			               pretty_date(acptr->serv->boottime));
+			               pretty_date(acptr->server->boottime));
 			sendtxtnumeric(client, "Uptime: %s",
-			               pretty_time_val(TStime() - acptr->serv->boottime));
+			               pretty_time_val(TStime() - acptr->server->boottime));
 		}
 		sendtxtnumeric(client, "User modes: %s",
-		               SafeDisplayStr(acptr->serv->features.usermodes));
-		if (!acptr->serv->features.chanmodes[0])
+		               SafeDisplayStr(acptr->server->features.usermodes));
+		if (!acptr->server->features.chanmodes[0])
 		{
 			sendtxtnumeric(client, "Channel modes: -");
 		} else {
 			sendtxtnumeric(client, "Channel modes: %s,%s,%s,%s",
-			               SafeDisplayStr(acptr->serv->features.chanmodes[0]),
-			               SafeDisplayStr(acptr->serv->features.chanmodes[1]),
-			               SafeDisplayStr(acptr->serv->features.chanmodes[2]),
-			               SafeDisplayStr(acptr->serv->features.chanmodes[3]));
+			               SafeDisplayStr(acptr->server->features.chanmodes[0]),
+			               SafeDisplayStr(acptr->server->features.chanmodes[1]),
+			               SafeDisplayStr(acptr->server->features.chanmodes[2]),
+			               SafeDisplayStr(acptr->server->features.chanmodes[3]));
 		}
 		sendtxtnumeric(client, "Allowed nick characters: %s",
-		               SafeDisplayStr(acptr->serv->features.nickchars));
+		               SafeDisplayStr(acptr->server->features.nickchars));
 	}
 }
 
diff --git a/src/modules/sjoin.c b/src/modules/sjoin.c
@@ -29,12 +29,14 @@ CMD_FUNC(cmd_sjoin);
 ModuleHeader MOD_HEADER
   = {
 	"sjoin",
-	"5.0",
+	"5.1",
 	"command /sjoin", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
+char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
 MOD_INIT()
 {
 	CommandAdd(modinfo->handle, MSG_SJOIN, cmd_sjoin, MAXPARA, CMD_SERVER);
@@ -55,7 +57,7 @@ MOD_UNLOAD()
 typedef struct xParv aParv;
 struct xParv {
 	int  parc;
-	char *parv[256];
+	const char *parv[256];
 };
 
 aParv pparv;
@@ -78,20 +80,32 @@ aParv *mp2parv(char *xmbuf, char *parmbuf)
 	return (&pparv);
 }
 
-void send_local_chan_mode(MessageTag *recv_mtags, Client *client, Channel *channel, char *modebuf, char *parabuf)
+static void send_local_chan_mode(MessageTag *recv_mtags, Client *client, Channel *channel, char *modebuf, char *parabuf)
 {
 	MessageTag *mtags = NULL;
+	int destroy_channel = 0;
 
-	new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->chname, modebuf, parabuf);
+	new_message_special(client, recv_mtags, &mtags, ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
 	sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-	               ":%s MODE %s %s %s", client->name, channel->chname, modebuf, parabuf);
+	               ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
 	if (MyConnect(client))
-		RunHook7(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1);
+		RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel);
 	else
-		RunHook7(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1);
+		RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel);
 	free_message_tags(mtags);
 }
 
+/** Call send_local_chan_mode() for multiline modes */
+static void send_local_chan_mode_mlm(MessageTag *recv_mtags, Client *client, Channel *channel, MultiLineMode *mlm)
+{
+	if (mlm)
+	{
+		int i;
+		for (i = 0; i < mlm->numlines; i++)
+			send_local_chan_mode(recv_mtags, client, channel, mlm->modeline[i], mlm->paramline[i]);
+	}
+}
+
 /** SJOIN: Synchronize channel modes, +beI lists and users (server-to-server command)
  * Extensive technical documentation is available at:
  * https://www.unrealircd.org/docs/Server_protocol:SJOIN_command
@@ -147,9 +161,10 @@ CMD_FUNC(cmd_sjoin)
 	unsigned short merge;	/**< same timestamp: merge their & our modes */
 	char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
 	char cbuf[1024];
-	char nick[1024]; /**< nick or ban/invex/exempt being processed */
 	char scratch_buf[1024]; /**< scratch buffer */
-	char prefix[16]; /**< prefix of nick for server to server traffic (eg: @) */
+	char item[1024]; /**< nick or ban/invex/exempt being processed */
+	char item_modes[MEMBERMODESLEN]; /**< item modes, eg "b" or "vhoaq" */
+	char prefix[16]; /**< SJOIN prefix of item for server to server traffic (eg: @) */
 	char uid_buf[BUFSIZE];  /**< Buffer for server-to-server traffic which will be broadcasted to others (servers supporting SID/UID) */
 	char uid_sjsby_buf[BUFSIZE];  /**< Buffer for server-to-server traffic which will be broadcasted to others (servers supporting SID/UID and SJSBY) */
 	char sj3_parabuf[BUFSIZE]; /**< Prefix for the above SJOIN buffers (":xxx SJOIN #channel +mode :") */
@@ -158,11 +173,10 @@ CMD_FUNC(cmd_sjoin)
 	aParv *ap;
 	int pcount, i;
 	Hook *h;
+	Cmode *cm;
 	time_t ts, oldts;
 	unsigned short b=0;
 	char *tp, *p, *saved = NULL;
-	long modeflags;
-	char queue_s=0, queue_c=0; /* oh this is soooooo ugly :p */
 	
 	if (!IsServer(client) || parc < 4)
 		return;
@@ -178,17 +192,38 @@ CMD_FUNC(cmd_sjoin)
 	if (parc < 5)
 		nomode = 1;
 
-	channel = get_channel(client, parv[2], CREATE);
+	channel = find_channel(parv[2]);
+	if (!channel)
+	{
+		channel = make_channel(parv[2]);
+		oldts = -1;
+	} else {
+		oldts = channel->creationtime;
+	}
 
 	ts = (time_t)atol(parv[1]);
 
+	if (IsInvalidChannelTS(ts))
+	{
+		unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_TIMESTAMP", client,
+			   "SJOIN for channel $channel has invalid timestamp $send_timestamp (from $client)",
+			   log_data_channel("channel", channel),
+			   log_data_integer("send_timestamp", ts));
+		/* Pretend they match our creation time (matches U6 behavior in m_mode.c) */
+		ts = channel->creationtime;
+	}
+
+	if (oldts == -1)
+	{
+		/* Newly created channel (from our POV), so set the correct creationtime here */
+		channel->creationtime = ts;
+	} else
 	if (channel->creationtime > ts)
 	{
 		removeours = 1;
-		oldts = channel->creationtime;
 		channel->creationtime = ts;
 	}
-	else if ((channel->creationtime < ts) && (channel->creationtime != 0))
+	else if (channel->creationtime < ts)
 	{
 		removetheirs = 1;
 	}
@@ -197,47 +232,29 @@ CMD_FUNC(cmd_sjoin)
 		merge = 1;
 	}
 
-	if (channel->creationtime == 0)
-	{
-		oldts = -1;
-		channel->creationtime = ts;
-	}
-	else
-	{
-		oldts = channel->creationtime;
-	}
-
-	// FIXME: make it so services cannot screw this up so easily --- if possible...
-	if (ts < 750000)
-	{
-		if (ts != 0)
-			sendto_ops
-			    ("Warning! Possible desync: SJOIN for channel %s has a fishy timestamp (%lld) [%s/%s]",
-			    channel->chname, (long long)ts, client->name, client->direction->name);
-	}
-
 	parabuf[0] = '\0';
 	modebuf[0] = '+';
 	modebuf[1] = '\0';
 
 	/* Grab current modes -> modebuf & parabuf */
-	channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel);
+	channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 1);
 
 	/* Do we need to remove all our modes, bans/exempt/inves lists and -vhoaq our users? */
 	if (removeours)
 	{
 		Member *lp;
-		Membership *lp2;
 
 		modebuf[0] = '-';
 
 		/* remove our modes if any */
-		if (modebuf[1] != '\0')
+		if (!empty_mode(modebuf))
 		{
 			MessageTag *mtags = NULL;
+			MultiLineMode *mlm;
 			ap = mp2parv(modebuf, parabuf);
-			set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar, 0);
-			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
+			mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar);
+			send_local_chan_mode_mlm(recv_mtags, client, channel, mlm);
+			safe_free_multilinemode(mlm);
 		}
 		/* remove bans */
 		/* reset the buffers */
@@ -274,39 +291,15 @@ CMD_FUNC(cmd_sjoin)
 		}
 		for (lp = channel->members; lp; lp = lp->next)
 		{
-			lp2 = find_membership_link(lp->client->user->channel, channel);
-			if (!lp2)
-			{
-				sendto_realops("Oops! channel->members && !find_membership_link");
-				continue;
-			}
-			if (lp->flags & MODE_CHANOWNER)
-			{
-				lp->flags &= ~MODE_CHANOWNER;
-				Addit('q', lp->client->name);
-			}
-			if (lp->flags & MODE_CHANADMIN)
-			{
-				lp->flags &= ~MODE_CHANADMIN;
-				Addit('a', lp->client->name);
-			}
-			if (lp->flags & MODE_CHANOP)
-			{
-				lp->flags &= ~MODE_CHANOP;
-				Addit('o', lp->client->name);
-			}
-			if (lp->flags & MODE_HALFOP)
-			{
-				lp->flags &= ~MODE_HALFOP;
-				Addit('h', lp->client->name);
-			}
-			if (lp->flags & MODE_VOICE)
+			Membership *lp2 = find_membership_link(lp->client->user->channel, channel);
+
+			/* Remove all our modes, one by one */
+			for (p = lp->member_modes; *p; p++)
 			{
-				lp->flags &= ~MODE_VOICE;
-				Addit('v', lp->client->name);
+				Addit(*p, lp->client->name);
 			}
-			/* Those should always match anyways  */
-			lp2->flags = lp->flags;
+			/* And clear all the flags in memory */
+			*lp->member_modes = *lp2->member_modes = '\0';
 		}
 		if (b > 1)
 		{
@@ -327,11 +320,6 @@ CMD_FUNC(cmd_sjoin)
 	sj3_parabuf[0] = '\0';
 	for (i = 2; i <= (parc - 2); i++)
 	{
-		if (!parv[i])
-		{
-			sendto_ops("Got null parv in SJ3 code");
-			continue;
-		}
 		strlcat(sj3_parabuf, parv[i], sizeof sj3_parabuf);
 		if (((i + 1) <= (parc - 2)))
 			strlcat(sj3_parabuf, " ", sizeof sj3_parabuf);
@@ -348,7 +336,7 @@ CMD_FUNC(cmd_sjoin)
 		time_t setat = TStime(); /**< Set at timestamp */
 		int sjsby_info = 0; /**< Set to 1 if we receive SJSBY info to alter the above 2 vars */
 
-		modeflags = 0;
+		*item_modes = 0;
 		i = 0;
 		tp = s;
 
@@ -365,8 +353,10 @@ CMD_FUNC(cmd_sjoin)
 			if (!end)
 			{
 				/* this obviously should never happen */
-				sendto_ops("Malformed SJOIN piece from %s for channel %s: %s",
-					client->name, channel->chname, tp);
+				unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_SJSBY", client,
+					   "SJOIN for channel $channel has invalid SJSBY in item '$item' (from $client)",
+					   log_data_channel("channel", channel),
+					   log_data_string("item", s));
 				continue;
 			}
 			*end++ = '\0';
@@ -375,8 +365,10 @@ CMD_FUNC(cmd_sjoin)
 			if (!p)
 			{
 				/* missing setby parameter */
-				sendto_ops("Malformed SJOIN piece from %s for channel %s: %s",
-					client->name, channel->chname, tp);
+				unreal_log(ULOG_WARNING, "sjoin", "SJOIN_INVALID_SJSBY", client,
+					   "SJOIN for channel $channel has invalid SJSBY in item '$item' (from $client)",
+					   log_data_channel("channel", channel),
+					   log_data_string("item", s));
 				continue;
 			}
 			*p++ = '\0';
@@ -388,92 +380,46 @@ CMD_FUNC(cmd_sjoin)
 			tp = end; /* the remainder is used for the actual ban/exempt/invex */
 		}
 
-		while (
-		    (*tp == '@') || (*tp == '+') || (*tp == '%')
-		    || (*tp == '*') || (*tp == '~') || (*tp == '&')
-		    || (*tp == '"') || (*tp == '\''))
+		/* Process the SJOIN prefixes... */
+		for (p = tp; *p; p++)
 		{
-			switch (*(tp++))
+			char m = sjoin_prefix_to_mode(*p);
+			if (!m)
+				break; /* end of prefix stuff, or so we hope anyway :D */
+			// TODO: do we want safety here for if one side has prefixmodes loaded
+			// and the other does not? and if so, in what way do we want this?
+
+			strlcat_letter(item_modes, m, sizeof(item_modes));
+
+			/* For list modes (+beI) stop processing immediately,
+			 * so we don't accidentally eat additional prefix chars.
+			 */
+			if (strchr("beI", m))
 			{
-			  case '@':
-				  modeflags |= CHFL_CHANOP;
-				  break;
-			  case '%':
-				  modeflags |= CHFL_HALFOP;
-				  break;
-			  case '+':
-				  modeflags |= CHFL_VOICE;
-				  break;
-			  case '*':
-				  modeflags |= CHFL_CHANOWNER;
-				  break;
-			  case '~':
-				  modeflags |= CHFL_CHANADMIN;
-				  break;
-			  case '&':
-				  modeflags = CHFL_BAN;
-				  goto getnick;
-			  case '"':
-				  modeflags = CHFL_EXCEPT;
-				  goto getnick;
-			  case '\'':
-				  modeflags = CHFL_INVEX;
-				  goto getnick;
+				p++;
+				break;
 			}
 		}
-getnick:
 
-		/* First, set the appropriate prefix for server to server traffic.
-		 * Note that 'prefix' is a 16 byte buffer but it's safe due to the limited
-		 * number of choices as can be seen below:
+		/* Now set 'prefix' to the prefixes we encountered.
+		 * This is basically the range tp..p
 		 */
-		*prefix = '\0';
-		p = prefix;
-		if (modeflags == CHFL_INVEX)
-			*p++ = '\'';
-		else if (modeflags == CHFL_EXCEPT)
-			*p++ = '\"';
-		else if (modeflags == CHFL_BAN)
-			*p++ = '&';
-		else
-		{
-			/* multiple options possible at the same time */
-			if (modeflags & CHFL_CHANOWNER)
-				*p++ = '*';
-			if (modeflags & CHFL_CHANADMIN)
-				*p++ = '~';
-			if (modeflags & CHFL_CHANOP)
-				*p++ = '@';
-			if (modeflags & CHFL_HALFOP)
-				*p++ = '%';
-			if (modeflags & CHFL_VOICE)
-				*p++ = '+';
-		}
-		*p = '\0';
+		strlncpy(prefix, tp, sizeof(prefix), p - tp);
 
-		/* Now copy the "nick" (which can actually be a ban/invex/exempt).
-		 * There's no size checking here but nick is 1024 bytes and we
-		 * have 512 bytes input max.
-		 */
-		i = 0;
-		while ((*tp != ' ') && (*tp != '\0'))
-			nick[i++] = *(tp++);	/* get nick */
-		nick[i] = '\0';
-		if (nick[0] == ' ')
+		/* Now copy the "nick" (which can actually be a ban/invex/exempt) */
+		strlcpy(item, p, sizeof(item));
+		if (*item == '\0')
 			continue;
-		if (nick[0] == '\0')
-			continue;
-		Debug((DEBUG_DEBUG, "Got nick: %s", nick));
-		if (!(modeflags & CHFL_BAN) && !(modeflags & CHFL_EXCEPT) && !(modeflags & CHFL_INVEX))
+
+		/* If not a list mode... then we deal with users... */
+		if (!strchr(item_modes, 'b') && !strchr(item_modes, 'e') && !strchr(item_modes, 'I'))
 		{
 			Client *acptr;
 
-			/* A person joining */
-
 			/* The user may no longer exist. This can happen in case of a
 			 * SVSKILL traveling in the other direction. Nothing to worry about.
 			 */
-			if (!(acptr = find_person(nick, NULL)))
+			if (!(acptr = find_user(item, NULL)))
 				continue;
 
 			if (acptr->direction != client->direction)
@@ -486,18 +432,18 @@ getnick:
 			
 				sendto_one(client, NULL,
 				    ":%s KICK %s %s :Fake direction",
-				    me.id, channel->chname, acptr->name);
-				sendto_realops
-				    ("Fake direction from user %s in SJOIN from %s(%s) at %s",
-				    nick, client->srvptr->name,
-				    client->name, channel->chname);
+				    me.id, channel->name, acptr->name);
+				unreal_log(ULOG_WARNING, "sjoin", "SJOIN_FAKE_DIRECTION", client,
+				           "Fake direction from server $client in SJOIN "
+				           "for user $existing_client on $existing_client.user.servername "
+				           "(item: $buf)",
+				           log_data_client("existing_client", acptr),
+				           log_data_string("buf", item));
 				continue;
 			}
 
 			if (removetheirs)
-			{
-				modeflags = 0;
-			}
+				*item_modes = '\0';
 
 			if (!IsMember(acptr, channel))
 			{
@@ -505,30 +451,32 @@ getnick:
 				 */
 				MessageTag *mtags = NULL;
 
-				add_user_to_channel(channel, acptr, modeflags);
-				RunHook4(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags, NULL);
-				new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->chname);
+				add_user_to_channel(channel, acptr, 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);
 				free_message_tags(mtags);
 			}
 
-			CheckStatus('q', CHFL_CHANOWNER);
-			CheckStatus('a', CHFL_CHANADMIN);
-			CheckStatus('o', CHFL_CHANOP);
-			CheckStatus('h', CHFL_HALFOP);
-			CheckStatus('v', CHFL_VOICE);
+			/* Set the +vhoaq */
+			for (p = item_modes; *p; p++)
+				Addit(*p, acptr->name);
 
 			if (strlen(uid_buf) + strlen(prefix) + IDLEN > BUFSIZE - 10)
 			{
 				/* Send what we have and start a new buffer */
 				sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
-				snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
+				snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
 				/* Double-check the new buffer is sufficient to concat the data */
 				if (strlen(uid_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
 				{
-					ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s%s'",
-						uid_buf, prefix, acptr->id);
-					sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
+					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
+					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'",
+						   log_data_channel("channel", channel),
+					           log_data_string("sjoin_place", "UID-MEMBER"),
+					           log_data_string("str", prefix),
+					           log_data_string("str2", acptr->id),
+					           log_data_string("buf", uid_buf));
 					continue;
 				}
 			}
@@ -537,14 +485,18 @@ getnick:
 			if (strlen(uid_sjsby_buf) + strlen(prefix) + IDLEN > BUFSIZE - 10)
 			{
 				/* Send what we have and start a new buffer */
-				sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_sjsby_buf);
-				snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
+				sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
+				snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
 				/* Double-check the new buffer is sufficient to concat the data */
 				if (strlen(uid_sjsby_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
 				{
-					ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s%s'",
-						uid_sjsby_buf, prefix, acptr->id);
-					sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
+					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
+					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'",
+						   log_data_channel("channel", channel),
+					           log_data_string("sjoin_place", "SJS-MEMBER"),
+					           log_data_string("str", prefix),
+					           log_data_string("str2", acptr->id),
+					           log_data_string("buf", uid_sjsby_buf));
 					continue;
 				}
 			}
@@ -552,79 +504,86 @@ getnick:
 		}
 		else
 		{
+			/* It's a list mode................ */
+			const char *str;
+			
 			if (removetheirs)
 				continue;
 
-			/* For list modes (beI): validate the syntax */
-			if (modeflags & (CHFL_BAN|CHFL_EXCEPT|CHFL_INVEX))
-			{
-				char *str;
-				
-				/* non-extbans: prevent bans without ! or @. a good case of "should never happen". */
-				if ((nick[0] != '~') && (!strchr(nick, '!') || !strchr(nick, '@') || (nick[0] == '!')))
-					continue;
-				
-				str = clean_ban_mask(nick, MODE_ADD, client);
-				if (!str)
-					continue; /* invalid ban syntax */
-				strlcpy(nick, str, sizeof(nick));
-			}
+			/* Validate syntax */
+
+			/* non-extbans: prevent bans without ! or @. a good case of "should never happen". */
+			if ((item[0] != '~') && (!strchr(item, '!') || !strchr(item, '@') || (item[0] == '!')))
+				continue;
+
+			str = clean_ban_mask(item, MODE_ADD, client, 0);
+			if (!str)
+				continue; /* invalid ban syntax */
+			strlcpy(item, str, sizeof(item));
 			
 			/* Adding of list modes */
-			if (modeflags & CHFL_BAN)
+			if (*item_modes == 'b')
 			{
-				if (add_listmode_ex(&channel->banlist, client, channel, nick, setby, setat) != -1)
+				if (add_listmode_ex(&channel->banlist, client, channel, item, setby, setat) != -1)
 				{
-					Addit('b', nick);
+					Addit('b', item);
 				}
 			}
-			if (modeflags & CHFL_EXCEPT)
+			if (*item_modes == 'e')
 			{
-				if (add_listmode_ex(&channel->exlist, client, channel, nick, setby, setat) != -1)
+				if (add_listmode_ex(&channel->exlist, client, channel, item, setby, setat) != -1)
 				{
-					Addit('e', nick);
+					Addit('e', item);
 				}
 			}
-			if (modeflags & CHFL_INVEX)
+			if (*item_modes == 'I')
 			{
-				if (add_listmode_ex(&channel->invexlist, client, channel, nick, setby, setat) != -1)
+				if (add_listmode_ex(&channel->invexlist, client, channel, item, setby, setat) != -1)
 				{
-					Addit('I', nick);
+					Addit('I', item);
 				}
 			}
 
-			if (strlen(uid_buf) + strlen(prefix) + strlen(nick) > BUFSIZE - 10)
+			if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 10)
 			{
 				/* Send what we have and start a new buffer */
 				sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
-				snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
+				snprintf(uid_buf, sizeof(uid_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
 				/* Double-check the new buffer is sufficient to concat the data */
-				if (strlen(uid_buf) + strlen(prefix) + strlen(nick) > BUFSIZE - 5)
+				if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 5)
 				{
-					ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s%s'",
-						uid_buf, prefix, nick);
-					sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
+					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
+					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str$str2' to '$buf'",
+						   log_data_channel("channel", channel),
+					           log_data_string("sjoin_place", "UID-LMODE"),
+					           log_data_string("str", prefix),
+					           log_data_string("str2", item),
+					           log_data_string("buf", uid_buf));
 					continue;
 				}
 			}
-			sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, nick);
+			sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, item);
 
 			*scratch_buf = '\0';
 			if (sjsby_info)
 				add_sjsby(scratch_buf, setby, setat);
 			strcat(scratch_buf, prefix);
-			strcat(scratch_buf, nick);
+			strcat(scratch_buf, item);
 			strcat(scratch_buf, " ");
 			if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 10)
 			{
 				/* Send what we have and start a new buffer */
 				sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
-				snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, sj3_parabuf);
+				snprintf(uid_sjsby_buf, sizeof(uid_sjsby_buf), ":%s SJOIN %lld %s :", client->id, (long long)ts, channel->name);
 				/* Double-check the new buffer is sufficient to concat the data */
 				if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 5)
 				{
-					ircd_log(LOG_ERROR, "Oversized SJOIN: '%s' + '%s'", uid_sjsby_buf, scratch_buf);
-					sendto_realops("Oversized SJOIN for %s -- see ircd log", channel->chname);
+					unreal_log(ULOG_ERROR, "sjoin", "BUG_OVERSIZED_SJOIN", client,
+					           "Oversized SJOIN [$sjoin_place] in channel $channel when adding '$str' to '$buf'",
+						   log_data_channel("channel", channel),
+					           log_data_string("sjoin_place", "SJS-LMODE"),
+					           log_data_string("str", scratch_buf),
+					           log_data_string("buf", uid_sjsby_buf));
 					continue;
 				}
 			}
@@ -634,11 +593,10 @@ getnick:
 	}
 
 	/* Send out any possible remainder.. */
-	Debug((DEBUG_DEBUG, "Sending '%li %s :%s' to ", ts, parabuf, parv[parc - 1]));
 	sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
 	sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
 
-	if (modebuf[1])
+	if (!empty_mode(modebuf))
 	{
 		modebuf[b] = '\0';
 		send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
@@ -646,8 +604,8 @@ getnick:
 	
 	if (!merge && !removetheirs && !nomode)
 	{
-		char paraback[1024];
 		MessageTag *mtags = NULL;
+		MultiLineMode *mlm;
 
 		strlcpy(modebuf, parv[3], sizeof modebuf);
 		parabuf[0] = '\0';
@@ -659,22 +617,22 @@ getnick:
 				strlcat(parabuf, " ", sizeof parabuf);
 			}
 		}
-		strlcpy(paraback, parabuf, sizeof paraback);
 		ap = mp2parv(modebuf, parabuf);
-
-		set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar, 0);
-		send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
+		mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar);
+		send_local_chan_mode_mlm(recv_mtags, client, channel, mlm);
+		safe_free_multilinemode(mlm);
 	}
 
 	if (merge && !nomode)
 	{
 		CoreChannelModeTable *acp;
+		MultiLineMode *mlm;
 		Mode oldmode; /**< The old mode (OUR mode) */
 
 		/* Copy current mode to oldmode (need to duplicate all extended mode params too..) */
 		memcpy(&oldmode, &channel->mode, sizeof(oldmode));
-		memset(&oldmode.extmodeparams, 0, sizeof(oldmode.extmodeparams));
-		extcmode_duplicate_paramlist(channel->mode.extmodeparams, oldmode.extmodeparams);
+		memset(&oldmode.mode_params, 0, sizeof(oldmode.mode_params));
+		extcmode_duplicate_paramlist(channel->mode.mode_params, oldmode.mode_params);
 
 		/* Now merge the modes */
 		strlcpy(modebuf, parv[3], sizeof modebuf);
@@ -687,8 +645,15 @@ getnick:
 				strlcat(parabuf, " ", sizeof parabuf);
 			}
 		}
+
+		/* First we set the mode (in memory) BUT we don't send the
+		 * mode change out to anyone, hence the immediate freeing
+		 * of 'mlm'. We do the actual rebuilding of the string and
+		 * sending it out a few lines further down.
+		 */
 		ap = mp2parv(modebuf, parabuf);
-		set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar, 0);
+		mlm = set_mode(channel, client, ap->parc, ap->parv, &pcount, pvar);
+		safe_free_multilinemode(mlm);
 
 		/* Good, now we got modes, now for the differencing and outputting of modes
 		 * We first see if any para modes are set.
@@ -696,53 +661,40 @@ getnick:
 		strlcpy(modebuf, "-", sizeof modebuf);
 		parabuf[0] = '\0';
 		b = 1;
-		/* however, is this really going to happen at all? may be unneeded */
-		if (oldmode.limit && !channel->mode.limit)
-		{
-			Addsingle('l');
-		}
-		if (oldmode.key[0] && !channel->mode.key[0])
+
+		/* Check if we had +s and it became +p, then revert it silently (as it is no-change) */
+		if (has_channel_mode_raw(oldmode.mode, 's') && has_channel_mode(channel, 'p'))
 		{
-			Addit('k', oldmode.key);
+			/* stay +s ! */
+			long mode_p = get_extmode_bitbychar('p');
+			long mode_s = get_extmode_bitbychar('s');
+			channel->mode.mode &= ~mode_p;
+			channel->mode.mode |= mode_s;
+			/* TODO: all the code of above would ideally be in a module */
 		}
+		/* (And the other condition, +p to +s, is already handled below by the generic code) */
 
-		/* First, check if we have something they don't have..
-		 * note that: oldmode.* = us, channel->mode.* = them.
+		/* First, check if we had something that is now gone
+		 * note that: oldmode.* = us, channel->mode.* = merged.
 		 */
-		for (i=0; i <= Channelmode_highest; i++)
+		for (cm=channelmodes; cm; cm = cm->next)
 		{
-			if ((Channelmode_Table[i].flag) &&
-			    (oldmode.extmode & Channelmode_Table[i].mode) &&
-			    !(channel->mode.extmode & Channelmode_Table[i].mode))
+			if (cm->letter &&
+			    !cm->local &&
+			    (oldmode.mode & cm->mode) &&
+			    !(channel->mode.mode & cm->mode))
 			{
-				if (Channelmode_Table[i].paracount)
+				if (cm->paracount)
 				{
-					char *parax = cm_getparameter_ex(oldmode.extmodeparams, Channelmode_Table[i].flag);
-					//char *parax = Channelmode_Table[i].get_param(extcmode_get_struct(oldmode.extmodeparam, Channelmode_Table[i].flag));
-					Addit(Channelmode_Table[i].flag, parax);
+					const char *parax = cm_getparameter_ex(oldmode.mode_params, cm->letter);
+					//char *parax = cm->get_param(extcmode_get_struct(oldmode.modeparam, cm->letter));
+					Addit(cm->letter, parax);
 				} else {
-					Addsingle(Channelmode_Table[i].flag);
+					Addsingle(cm->letter);
 				}
 			}
 		}
 
-		/* Check if we had +s and it became +p, then revert it... */
-		if ((oldmode.mode & MODE_SECRET) && (channel->mode.mode & MODE_PRIVATE))
-		{
-			/* stay +s ! */
-			channel->mode.mode &= ~MODE_PRIVATE;
-			channel->mode.mode |= MODE_SECRET;
-			Addsingle('p'); /* - */
-			queue_s = 1;
-		}
-		/* Add single char modes... */
-		for (acp = corechannelmodetable; acp->mode; acp++)
-		{
-			if ((oldmode.mode & acp->mode) && !(channel->mode.mode & acp->mode) && !acp->parameters)
-			{
-				Addsingle(acp->flag);
-			}
-		}
 		if (b > 1)
 		{
 			Addsingle('+');
@@ -753,111 +705,74 @@ getnick:
 			b = 1;
 		}
 
-		if (queue_s)
-			Addsingle('s');
-
-		if (queue_c)
-			Addsingle('c');
-
-		for (acp = corechannelmodetable; acp->mode; acp++)
-		{
-			if (!(oldmode.mode & acp->mode) && (channel->mode.mode & acp->mode) && !acp->parameters)
-			{
-				Addsingle(acp->flag);
-			}
-		}
-
-		/* Now, check if they have something we don't have..
-		 * note that: oldmode.* = us, channel->mode.* = them.
+		/* Now, check if merged modes contain something we didn't have before.
+		 * note that: oldmode.* = us before, channel->mode.* = merged.
+		 *
+		 * First the simple single letter modes...
 		 */
-		for (i=0; i <= Channelmode_highest; i++)
+		for (cm=channelmodes; cm; cm = cm->next)
 		{
-			if ((Channelmode_Table[i].flag) &&
-			    !(oldmode.extmode & Channelmode_Table[i].mode) &&
-			    (channel->mode.extmode & Channelmode_Table[i].mode))
+			if ((cm->letter) &&
+			    !(oldmode.mode & cm->mode) &&
+			    (channel->mode.mode & cm->mode))
 			{
-				if (Channelmode_Table[i].paracount)
+				if (cm->paracount)
 				{
-					char *parax = cm_getparameter(channel, Channelmode_Table[i].flag);
+					const char *parax = cm_getparameter(channel, cm->letter);
 					if (parax)
 					{
-						Addit(Channelmode_Table[i].flag, parax);
+						Addit(cm->letter, parax);
 					}
 				} else {
-					Addsingle(Channelmode_Table[i].flag);
+					Addsingle(cm->letter);
 				}
 			}
 		}
 
 		/* now, if we had diffent para modes - this loop really could be done better, but */
 
-		/* +l (limit) difference? */
-		if (oldmode.limit && channel->mode.limit && (oldmode.limit != channel->mode.limit))
-		{
-			channel->mode.limit = MAX(oldmode.limit, channel->mode.limit);
-			if (oldmode.limit != channel->mode.limit)
-			{
-				Addit('l', my_itoa(channel->mode.limit));
-			}
-		}
-
-		/* +k (key) difference? */
-		if (oldmode.key[0] && channel->mode.key[0] && strcmp(oldmode.key, channel->mode.key))
-		{
-			if (strcmp(oldmode.key, channel->mode.key) > 0)			
-			{
-				strlcpy(channel->mode.key, oldmode.key, sizeof channel->mode.key);
-			}
-			else
-			{
-				Addit('k', channel->mode.key);
-			}
-		}
-
 		/* Now, check for any param differences in extended channel modes..
-		 * note that: oldmode.* = us, channel->mode.* = them.
+		 * note that: oldmode.* = us before, channel->mode.* = merged.
 		 * if we win: copy oldmode to channel mode, if they win: send the mode
 		 */
-		for (i=0; i <= Channelmode_highest; i++)
+		for (cm=channelmodes; cm; cm = cm->next)
 		{
-			if (Channelmode_Table[i].flag && Channelmode_Table[i].paracount &&
-			    (oldmode.extmode & Channelmode_Table[i].mode) &&
-			    (channel->mode.extmode & Channelmode_Table[i].mode))
+			if (cm->letter && cm->paracount &&
+			    (oldmode.mode & cm->mode) &&
+			    (channel->mode.mode & cm->mode))
 			{
 				int r;
-				char *parax;
-				char flag = Channelmode_Table[i].flag;
-				void *ourm = GETPARASTRUCTEX(oldmode.extmodeparams, flag);
+				const char *parax;
+				char flag = cm->letter;
+				void *ourm = GETPARASTRUCTEX(oldmode.mode_params, flag);
 				void *theirm = GETPARASTRUCT(channel, flag);
-				//CmodeParam *ourm = extcmode_get_struct(oldmode.extmodeparam,Channelmode_Table[i].flag);
-				//CmodeParam *theirm = extcmode_get_struct(channel->mode.extmodeparam, Channelmode_Table[i].flag);
 				
-				r = Channelmode_Table[i].sjoin_check(channel, ourm, theirm);
+				r = cm->sjoin_check(channel, ourm, theirm);
 				switch (r)
 				{
 					case EXSJ_WEWON:
-						parax = cm_getparameter_ex(oldmode.extmodeparams, flag); /* grab from old */
+						parax = cm_getparameter_ex(oldmode.mode_params, flag); /* grab from old */
 						cm_putparameter(channel, flag, parax); /* put in new (won) */
 						break;
 
 					case EXSJ_THEYWON:
 						parax = cm_getparameter(channel, flag);
-						Debug((DEBUG_DEBUG, "sjoin: they won: '%s'", parax));
-						Addit(Channelmode_Table[i].flag, parax);
+						Addit(cm->letter, parax);
 						break;
 
 					case EXSJ_SAME:
-						Debug((DEBUG_DEBUG, "sjoin: equal"));
 						break;
 
 					case EXSJ_MERGE:
-						parax = cm_getparameter_ex(oldmode.extmodeparams, flag); /* grab from old */
+						parax = cm_getparameter_ex(oldmode.mode_params, flag); /* grab from old */
 						cm_putparameter(channel, flag, parax); /* put in new (won) */
 						Addit(flag, parax);
 						break;
 
 					default:
-						ircd_log(LOG_ERROR, "channel.c:m_sjoin:param diff checker: got unk. retval 0x%x??", r);
+						unreal_log(ULOG_ERROR, "sjoin", "BUG_SJOIN_CHECK", client,
+						           "[BUG] channel.c:m_sjoin:param diff checker: unknown return value $return_value",
+						           log_data_integer("return_value", r));
 						break;
 				}
 			}
@@ -865,14 +780,11 @@ getnick:
 
 		Addsingle('\0');
 
-		if (modebuf[1])
-		{
+		if (!empty_mode(modebuf))
 			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
-		}
 
 		/* free the oldmode.* crap :( */
-		extcmode_free_paramlist(oldmode.extmodeparams);
-		/* memset(&oldmode.extmodeparams, 0, sizeof(oldmode.extmodeparams)); -- redundant? */
+		extcmode_free_paramlist(oldmode.mode_params);
 	}
 
 	for (h = Hooks[HOOKTYPE_CHANNEL_SYNCED]; h; h = h->next)
@@ -885,13 +797,12 @@ getnick:
 	/* we should be synced by now, */
 	if ((oldts != -1) && (oldts != channel->creationtime))
 	{
-		MessageTag *mtags = NULL;
-		new_message(client, NULL, &mtags);
-		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, NULL,
-			":%s NOTICE %s :*** TS for %s changed from %lld to %lld",
-			me.name, channel->chname, channel->chname,
-			(long long)oldts, (long long)channel->creationtime);
-		free_message_tags(mtags);
+		unreal_log(ULOG_INFO, "channel", "CHANNEL_SYNC_TS_CHANGE", client,
+		           "Channel $channel: timestamp changed from $old_ts -> $new_ts "
+		           "after syncing with server $client.",
+		           log_data_channel("channel", channel),
+		           log_data_integer("old_ts", oldts),
+		           log_data_integer("new_ts", channel->creationtime));
 	}
 
 	/* If something went wrong with processing of the SJOIN above and
diff --git a/src/modules/slog.c b/src/modules/slog.c
@@ -0,0 +1,190 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/monitor.c
+ *   (C) 2021 Bram Matthys and The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"slog",
+	"5.0",
+	"S2S logging", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+CMD_FUNC(cmd_slog);
+void _do_unreal_log_remote_deliver(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER, _do_unreal_log_remote_deliver);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	CommandAdd(modinfo->handle, "SLOG", cmd_slog, MAXPARA, CMD_SERVER);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Server to server logging command.
+ * This way remote servers can send a log message to all servers.
+ * The message is broadcasted on to the rest of the network.
+ * Syntax:
+ * parv[1]: loglevel (eg "info")
+ * parv[2]: subsystem (eg: "link")
+ * parv[3]: event ID (eg: "LINK_DENIED_AUTH_FAILED")
+ * parv[4]: log message (only the first line!)
+ * We also require the "unrealircd.org/json-log" message tag to be present
+ * and to contain a valid UnrealIRCd JSON log.
+ * In fact, for sending the log message to disk and everything, we ignore
+ * the message in parv[4] and use the "msg" in the JSON itself.
+ * This because the "msg" in the JSON can be multi-line (can contain \n's)
+ * while the message in parv[4] will only be the first line.
+ *
+ * Why not skip parv[4] altogether and not send it all?
+ * I think it is still useful to send these, both for easy watching
+ * at server to server traffic, and also so (services) servers don't have
+ * to implement a full JSON parser.
+ */
+CMD_FUNC(cmd_slog)
+{
+	LogLevel loglevel;
+	const char *subsystem;
+	const char *event_id;
+	const char *msg;
+	const char *msg_in_json;
+	char *json_incoming = NULL;
+	char *json_serialized = NULL;
+	MessageTag *m;
+	MultiLine *mmsg = NULL;
+	json_t *j, *jt;
+	json_error_t jerr;
+	const char *original_timestamp;
+
+	if ((parc < 4) || BadPtr(parv[4]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SLOG");
+		return;
+	}
+
+	loglevel = log_level_stringtoval(parv[1]);
+	if (loglevel == ULOG_INVALID)
+		return;
+	subsystem = parv[2];
+	if (!valid_subsystem(subsystem))
+		return;
+	event_id = parv[3];
+	if (!valid_event_id(event_id))
+		return;
+	msg = parv[4];
+
+	m = find_mtag(recv_mtags, "unrealircd.org/json-log");
+	if (m)
+		json_incoming = m->value;
+
+	if (!json_incoming)
+		return;
+	// Was previously: unreal_log_raw(loglevel, subsystem, event_id, NULL, msg); // WRONG: this may re-broadcast too, so twice, including back to direction!!!
+
+	/* Validate the JSON */
+	j = json_loads(json_incoming, JSON_REJECT_DUPLICATES, &jerr);
+	if (!j)
+	{
+		unreal_log(ULOG_INFO, "log", "REMOTE_LOG_INVALID", client,
+		           "Received malformed JSON in server-to-server log message (SLOG) from $client",
+		           log_data_string("bad_json_serialized", json_incoming));
+		return;
+	}
+
+	jt = json_object_get(j, "msg");
+	if (!jt || !(msg_in_json = json_string_value(jt)))
+	{
+		unreal_log(ULOG_INFO, "log", "REMOTE_LOG_INVALID", client,
+		           "Missing 'msg' in JSON in server-to-server log message (SLOG) from $client",
+		           log_data_string("bad_json_serialized", json_incoming));
+		json_decref(j);
+		return;
+	}
+	mmsg = line2multiline(msg_in_json);
+
+	/* Set "timestamp", and save the original one in "original_timestamp" (if it existed) */
+	jt = json_object_get(j, "timestamp");
+	if (jt)
+	{
+		original_timestamp = json_string_value(jt);
+		if (original_timestamp)
+			json_object_set_new(j, "original_timestamp", json_string(original_timestamp));
+	}
+	json_object_set_new(j, "timestamp", json_string(timestamp_iso8601_now()));
+	json_object_set_new(j, "log_source", json_string(client->name));
+
+	/* Re-serialize the result */
+	json_serialized = json_dumps(j, JSON_COMPACT);
+
+	if (json_serialized)
+		do_unreal_log_internal_from_remote(loglevel, subsystem, event_id, mmsg, json_serialized, client);
+
+	/* Broadcast to the other servers */
+	sendto_server(client, 0, 0, recv_mtags, ":%s SLOG %s %s %s :%s",
+	              client->id,
+	              parv[1], parv[2], parv[3], parv[4]);
+
+	/* Free everything */
+	safe_free(json_serialized);
+	json_decref(j);
+	safe_free_multiline(mmsg);
+}
+
+void _do_unreal_log_remote_deliver(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized)
+{
+	MessageTag *mtags = safe_alloc(sizeof(MessageTag));
+
+	safe_strdup(mtags->name, "unrealircd.org/json-log");
+	safe_strdup(mtags->value, json_serialized);
+
+	/* Note that we only send the first line (msg->line),
+	 * even for a multi-line event.
+	 * If the recipient really wants to see everything then
+	 * they can use the JSON data.
+	 */
+	sendto_server(NULL, 0, 0, mtags, ":%s SLOG %s %s %s :%s",
+	              me.id,
+	              log_level_valtostring(loglevel), subsystem, event_id, msg->line);
+
+	free_message_tags(mtags);
+}
diff --git a/src/modules/snomasks/Makefile.in b/src/modules/snomasks/Makefile.in
@@ -1,55 +0,0 @@
-#************************************************************************
-#*   IRC - Internet Relay Chat, src/modules/snomasks/Makefile
-#*   Copyright (C) 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/proto.h ../../include/dns.h \
-	../../include/resource.h ../../include/setup.h \
-	../../include/struct.h ../../include/sys.h \
-	../../include/types.h ../../include/url.h \
-	../../include/version.h ../../include/whowas.h
-
-R_MODULES=\
-	dccreject.so
-
-MODULES=$(R_MODULES)
-MODULEFLAGS=@MODULEFLAGS@
-RM=@RM@
-
-all: build
-
-build: $(MODULES)
-
-clean:
-	$(RM) -f *.o *.so *~ core
-
-#############################################################################
-#             .so's section
-#############################################################################
-
-dccreject.so: dccreject.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o dccreject.so dccreject.c
-
diff --git a/src/modules/snomasks/dccreject.c b/src/modules/snomasks/dccreject.c
@@ -1,70 +0,0 @@
-/*
- * Show DCC SEND rejection notices (Snomask +D)
- * (C) Copyright 2000-.. 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"
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"snomasks/dccreject",
-	"4.2",
-	"Snomask +D",
-	"UnrealIRCd Team",
-	"unrealircd-5",
-    };
-
-/* Global variables */
-long SNO_DCCREJECT = 0L;
-
-/* Forward declarations */
-int dccreject_dcc_denied(Client *client, char *target, char *realfile, char *displayfile, ConfigItem_deny_dcc *dccdeny);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	SnomaskAdd(modinfo->handle, 'D', umode_allow_opers, &SNO_DCCREJECT);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_DCC_DENIED, 0, dccreject_dcc_denied);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int dccreject_dcc_denied(Client *client, char *target, char *realfile, char *displayfile, ConfigItem_deny_dcc *dccdeny)
-{
-	sendto_snomask_global(SNO_DCCREJECT, 
-		"%s tried to send forbidden file %s (%s) to %s (is blocked now)",
-		client->name, displayfile, dccdeny->reason, target);
-
-	return 0;
-}
diff --git a/src/modules/sqline.c b/src/modules/sqline.c
@@ -33,7 +33,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /sqline", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -64,8 +64,8 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_sqline)
 {
 	char mo[32];
-	char *comment = (parc == 3) ? parv[2] : NULL;
-	char *tkllayer[9] = {
+	const char *comment = (parc == 3) ? parv[2] : NULL;
+	const char *tkllayer[9] = {
 		me.name,        /*0  server.name */
 		"+",            /*1  +|- */
 		"Q",            /*2  G   */
diff --git a/src/modules/squit.c b/src/modules/squit.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /squit", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -59,9 +59,9 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_squit)
 {
-	char *server;
+	const char *server;
 	Client *target;
-	char *comment = (parc > 2 && parv[parc - 1]) ? parv[parc - 1] : client->name;
+	const char *comment = (parc > 2 && parv[parc - 1]) ? parv[parc - 1] : client->name;
 
 	// FIXME: this function is way too confusing, and full of old shit?
 
@@ -130,8 +130,10 @@ CMD_FUNC(cmd_squit)
 	 */
 	if (MyConnect(target) && !MyUser(client))
 	{
-		sendto_umode_global(UMODE_OPER, "Received SQUIT %s from %s (%s)",
-		    target->name, get_client_name(client, FALSE), comment);
+		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", comment));
 	}
 	else if (MyConnect(target))
 	{
@@ -141,8 +143,10 @@ CMD_FUNC(cmd_squit)
 			           me.name);
 			return;
 		}
-		sendto_umode_global(UMODE_OPER, "Received SQUIT %s from %s (%s)",
-		    target->name, get_client_name(client, FALSE), comment);
+		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", comment));
 	}
 
 	exit_client_ex(target, client->direction, recv_mtags, comment);
diff --git a/src/modules/staff.c b/src/modules/staff.c
@@ -26,18 +26,13 @@ ModuleHeader MOD_HEADER
 	"3.8",
 	"/STAFF command",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 #define MSG_STAFF	"STAFF"
 
 #define DEF_STAFF_FILE   CONFDIR "/network.staff"
-#define CONF_STAFF_FILE  (staff_file ? staff_file : DEF_STAFF_FILE)
-#ifdef USE_LIBCURL
-#define STAFF_FILE       (Download.path ? Download.path : CONF_STAFF_FILE)
-#else
-#define STAFF_FILE       CONF_STAFF_FILE
-#endif
+#define STAFF_FILE       (staff_file ? staff_file : DEF_STAFF_FILE)
 
 #define RPL_STAFF        ":%s 700 %s :- %s"
 #define RPL_STAFFSTART   ":%s 701 %s :- %s IRC Network Staff Information -"
@@ -47,31 +42,13 @@ ModuleHeader MOD_HEADER
 /* Forward declarations */
 static void unload_motd_file(MOTDFile *list);
 CMD_FUNC(cmd_staff);
-static int cb_rehashflag(Client *client, char *flag);
 static int cb_test(ConfigFile *, ConfigEntry *, int, int *);
 static int cb_conf(ConfigFile *, ConfigEntry *, int);
-static int cb_rehash();
-static int cb_stats(Client *client, char *flag);
-#ifdef USE_LIBCURL
-static int download_staff_file(ConfigEntry *ce);
-static void download_staff_file_complete(char *url, char *file, char *errorbuf, int cached, void *dummy);
-#endif
-static void InitConf();
+static int cb_stats(Client *client, const char *flag);
 static void FreeConf();
 
 static MOTDFile staff;
-static char *staff_file;
-
-#ifdef USE_LIBCURL
-struct {
-	unsigned	is_url : 1;
-	unsigned	once_completed : 1;
-	unsigned	in_progress : 1;
-	char		*file;			// File name
-	char		*path;			// File path
-	char		*url;			// Full URL address
-} Download;
-#endif
+static char *staff_file = NULL;
 
 MOD_TEST()
 {
@@ -82,17 +59,10 @@ MOD_TEST()
 MOD_INIT()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
-#ifdef USE_LIBCURL
-	memset(&Download, 0, sizeof(Download));
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
-#endif
 	memset(&staff, 0, sizeof(staff));
-	InitConf();
 
 	CommandAdd(modinfo->handle, MSG_STAFF, cmd_staff, MAXPARA, CMD_USER);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cb_conf);
-	HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, cb_rehash);
-	HookAdd(modinfo->handle, HOOKTYPE_REHASHFLAG, 0, cb_rehashflag);
 	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, cb_stats);
 
 	return MOD_SUCCESS;
@@ -108,140 +78,14 @@ MOD_UNLOAD()
 	FreeConf();
 	unload_motd_file(&staff);
 
-#ifdef USE_LIBCURL
-	safe_free(Download.path);
-   	safe_free(Download.file);
-	safe_free(Download.url);
-#endif
-
 	return MOD_SUCCESS;
 }
 
-static int cb_rehash()
-{
-	FreeConf();
-	InitConf();
-	return 1;
-}
-
-static void InitConf()
-{
-	staff_file = NULL;
-}
-
 static void FreeConf()
 {
 	safe_free(staff_file);
 }
 
-/*** web routines */
-#ifdef USE_LIBCURL
-static void remove_staff_file()
-{
-	if (Download.path)
-	{
-		if (remove(Download.path) == -1)
-		{
-			if (config_verbose > 0)
-				config_status("Cannot remove file %s: %s",
-					Download.path, strerror(errno));
-		}
-	        safe_free(Download.path);
-	        Download.path = NULL;
-	}
-}
-
-static int download_staff_file(ConfigEntry *ce)
-{
-	int ret = 0;
-	struct stat sb;
-	char *file, *filename;
-
-	if (Download.in_progress)
-		return 0;
-
-	Download.is_url = 1;
-	safe_strdup(Download.url, ce->ce_vardata);
-
-	file = url_getfilename(ce->ce_vardata);
-	filename = unreal_getfilename(file);
-	/* TODO: handle NULL returns */
-	safe_strdup(Download.file, filename);
-	safe_free(file);
-
-	if (!loop.ircd_rehashing && !Download.once_completed)
-	{
-		char *error;
-
-		if (config_verbose > 0)
-			config_status("Downloading %s", displayurl(Download.url));
-
-		if (!(file = download_file(ce->ce_vardata, &error)))
-		{
-			config_error("%s:%i: test: error downloading '%s': %s",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-				displayurl(ce->ce_vardata), error);
-			return -1;
-		}
-
-		Download.once_completed = 1;
-		safe_strdup(Download.path, file);
-		read_motd(Download.path, &staff);
-
-		safe_free(file);
-		return 0;
-	}
-
-	file = Download.path ? Download.path : Download.file;
-
-	if ((ret = stat(file, &sb)) && errno != ENOENT)
-	{
-		/* I know, stat shouldn't fail... */
-		config_error("%s:%i: could not get the creation time of %s: stat() returned %d: %s",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
-			Download.file, ret, strerror(errno));
-		return -1;
-	}
-
-	if (config_verbose > 0)
-		config_status("Downloading %s", displayurl(Download.url));
-
-	Download.in_progress = 1;
-	download_file_async(Download.url, sb.st_ctime, download_staff_file_complete, NULL);
-	return 0;
-}
-
-static void download_staff_file_complete(char *url, char *file, char *errorbuf, int cached, void *dummy)
-{
-	Download.in_progress = 0;
-	Download.once_completed = 1;
-
-	if (!cached)
-	{
-		if (!file)
-		{
-			config_error("Error downloading %s: %s",
-				displayurl(url), errorbuf);
-			return;
-		}
-
-		remove_staff_file();
-		safe_strdup(Download.path, file);
-		read_motd(Download.path, &staff);
-	} else
-	{
-		char *urlfile = url_getfilename(url);
-		char *file = unreal_getfilename(urlfile);
-		char *tmp = unreal_mktemp("tmp", file);
-		/* TODO: handle null returns ? */
-		unreal_copyfile(Download.path, tmp);
-		remove_staff_file();
-		safe_strdup(Download.path, tmp);
-		safe_free(urlfile);
-	}
-}
-#endif
-
 static void unload_motd_file(MOTDFile *list)
 {
 	MOTDLine *old, *new;
@@ -266,28 +110,11 @@ static void unload_motd_file(MOTDFile *list)
 static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
-#ifdef USE_LIBCURL
-	char *file = NULL, *filename = NULL;
-#endif
 
 	if (type == CONFIG_SET)
 	{
-		if (!strcmp(ce->ce_varname, "staff-file"))
+		if (!strcmp(ce->name, "staff-file"))
 		{
-#ifdef USE_LIBCURL
-			if (url_is_valid(ce->ce_vardata))
-			{
-				/* TODO: hm, relax this one? */
-				if (!(file = url_getfilename(ce->ce_vardata)) || !(filename = unreal_getfilename(file)))
-				{
-					config_error("%s:%i: invalid filename in URL",
-						ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-					errors++;
-				}
-				safe_free(file);
-			}
-#endif
-
 			*errs = errors;
 			return errors ? -1 : 1;
 		}
@@ -300,26 +127,10 @@ static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
 {
 	if (type == CONFIG_SET)
 	{
-		if (!strcmp(ce->ce_varname, "staff-file"))
+		if (!strcmp(ce->name, "staff-file"))
 		{
-#ifdef USE_LIBCURL
-			if (!Download.in_progress)
-			{
-				safe_strdup(staff_file, ce->ce_vardata);
-				if (url_is_valid(ce->ce_vardata))
-				{
-					download_staff_file(ce);
-				}
-				else
-#endif
-				{
-					convert_to_absolute_path(&ce->ce_vardata, CONFDIR);
-					read_motd(ce->ce_vardata, &staff);
-				}
-#ifdef USE_LIBCURL
-			}
-
-#endif
+			convert_to_absolute_path(&ce->value, CONFDIR);
+			read_motd(ce->value, &staff);
 			return 1;
 		}
 	}
@@ -327,7 +138,7 @@ static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
 	return 0;
 }
 
-static int cb_stats(Client *client, char *flag)
+static int cb_stats(Client *client, const char *flag)
 {
 	if (*flag == 'S')
 	{
@@ -338,28 +149,6 @@ static int cb_stats(Client *client, char *flag)
 	return 0;
 }
 
-static int cb_rehashflag(Client *client, char *flag)
-{
-	int myflag = 0;
-
-	/* "-all" only keeps compatibility with beta19 */
-	if (match_simple("-all", flag) || (myflag = match_simple("-staff", flag)))
-	{
-		if (myflag)
-			sendto_ops("%sRehashing network staff file on the request of %s",
-                                MyUser(client) ? "Remotely " : "", client->name);
-
-#ifdef USE_LIBCURL
-		if (Download.is_url)
-			read_motd(Download.path, &staff);
-		else
-#endif
-			read_motd(CONF_STAFF_FILE, &staff);
-	}
-
-	return 0;
-}
-
 /** The routine that actual does the /STAFF command */
 CMD_FUNC(cmd_staff)
 {
@@ -369,7 +158,7 @@ CMD_FUNC(cmd_staff)
 	if (!IsUser(client))
 		return;
 
-	if (hunt_server(client, recv_mtags, ":%s STAFF", 1, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "STAFF", 1, parc, parv) != HUNTED_ISME)
 		return;
 
 	if (!staff.lines)
@@ -378,7 +167,7 @@ CMD_FUNC(cmd_staff)
 		return;
 	}
 
-	sendto_one(client, NULL, RPL_STAFFSTART, me.name, client->name, ircnetwork);
+	sendto_one(client, NULL, RPL_STAFFSTART, me.name, client->name, NETWORK_NAME);
 
 	temp = &staff;
 
diff --git a/src/modules/starttls.c b/src/modules/starttls.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /starttls", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 long CLICAP_STARTTLS;
@@ -71,7 +71,7 @@ CMD_FUNC(cmd_starttls)
 	ctx = client->local->listener->ssl_ctx ? client->local->listener->ssl_ctx : ctx_server;
 	tls_options = client->local->listener->tls_options ? client->local->listener->tls_options->options : iConf.tls_options->options;
 
-	/* Is SSL support enabled? (may not, if failed to load cert/keys/..) */
+	/* This should never happen? */
 	if (!ctx)
 	{
 		/* Pretend STARTTLS is an unknown command, this is the safest approach */
@@ -97,14 +97,13 @@ CMD_FUNC(cmd_starttls)
 	send_queued(client);
 
 	SetStartTLSHandshake(client);
-	Debug((DEBUG_DEBUG, "Starting SSL handshake (due to STARTTLS) for %s", client->local->sockhost));
 	if ((client->local->ssl = SSL_new(ctx)) == NULL)
 		goto fail;
 	SetTLS(client);
 	SSL_set_fd(client->local->ssl, client->local->fd);
 	SSL_set_nonblocking(client->local->ssl);
-	if (!ircd_SSL_accept(client, client->local->fd)) {
-		Debug((DEBUG_DEBUG, "Failed SSL accept handshake in instance 1: %s", client->local->sockhost));
+	if (!unreal_tls_accept(client, client->local->fd))
+	{
 		SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
 		SSL_smart_shutdown(client->local->ssl);
 		SSL_free(client->local->ssl);
diff --git a/src/modules/stats.c b/src/modules/stats.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /stats",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -53,39 +53,38 @@ MOD_UNLOAD()
 }
 
 extern MODVAR int  max_connection_count;
-extern char *get_client_name2(Client *, int);
-
-int stats_banversion(Client *, char *);
-int stats_links(Client *, char *);
-int stats_denylinkall(Client *, char *);
-int stats_gline(Client *, char *);
-int stats_except(Client *, char *);
-int stats_allow(Client *, char *);
-int stats_command(Client *, char *);
-int stats_oper(Client *, char *);
-int stats_port(Client *, char *);
-int stats_bannick(Client *, char *);
-int stats_traffic(Client *, char *);
-int stats_uline(Client *, char *);
-int stats_vhost(Client *, char *);
-int stats_denylinkauto(Client *, char *);
-int stats_kline(Client *, char *);
-int stats_banrealname(Client *, char *);
-int stats_sqline(Client *, char *);
-int stats_linkinfoint(Client *, char *, int);
-int stats_linkinfo(Client *, char *);
-int stats_linkinfoall(Client *, char *);
-int stats_chanrestrict(Client *, char *);
-int stats_shun(Client *, char *);
-int stats_set(Client *, char *);
-int stats_tld(Client *, char *);
-int stats_uptime(Client *, char *);
-int stats_denyver(Client *, char *);
-int stats_notlink(Client *, char *);
-int stats_class(Client *, char *);
-int stats_officialchannels(Client *, char *);
-int stats_spamfilter(Client *, char *);
-int stats_fdtable(Client *, char *);
+
+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 *);
+int stats_command(Client *, const char *);
+int stats_oper(Client *, const char *);
+int stats_port(Client *, const char *);
+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 *);
+int stats_linkinfoint(Client *, const char *, int);
+int stats_linkinfo(Client *, const char *);
+int stats_linkinfoall(Client *, const char *);
+int stats_chanrestrict(Client *, const char *);
+int stats_shun(Client *, const char *);
+int stats_set(Client *, const char *);
+int stats_tld(Client *, const char *);
+int stats_uptime(Client *, const char *);
+int stats_denyver(Client *, const char *);
+int stats_notlink(Client *, const char *);
+int stats_class(Client *, const char *);
+int stats_officialchannels(Client *, const char *);
+int stats_spamfilter(Client *, const char *);
+int stats_fdtable(Client *, const char *);
 
 #define SERVER_AS_PARA 0x1
 #define FLAGS_AS_PARA 0x2
@@ -93,7 +92,7 @@ int stats_fdtable(Client *, char *);
 struct statstab {
 	char flag;
 	char *longflag;
-	int (*func)(Client *client, char *para);
+	int (*func)(Client *client, const char *para);
 	int options;
 };
 
@@ -142,7 +141,7 @@ struct statstab StatsTable[] = {
 	{ 0, 	NULL, 		NULL, 			0		}
 };
 
-int stats_compare(char *s1, char *s2)
+int stats_compare(const char *s1, const char *s2)
 {
 	/* The long stats flags are always lowercase */
 	while (*s1 == tolower(*s2))
@@ -171,7 +170,7 @@ static inline struct statstab *stats_binary_search(char c) {
 	return NULL;
 }
 
-static inline struct statstab *stats_search(char *s) {
+static inline struct statstab *stats_search(const char *s) {
 	int i;
 	for (i = 0; StatsTable[i].flag; i++)
 		if (!stats_compare(StatsTable[i].longflag,s))
@@ -179,7 +178,7 @@ static inline struct statstab *stats_search(char *s) {
 	return NULL;
 }
 
-static inline char *stats_combine_parv(char *p1, char *p2)
+static inline char *stats_combine_parv(const char *p1, const char *p2)
 {
 	static char buf[BUFSIZE+1];
         ircsnprintf(buf, sizeof(buf), "%s %s", p1, p2);
@@ -259,7 +258,7 @@ static inline int allow_user_stats_short(char c)
 	return 0;
 }
 
-static inline int allow_user_stats_long(char *s)
+static inline int allow_user_stats_long(const char *s)
 {
 	OperStat *os;
 	for (os = iConf.allow_user_stats_ext; os; os = os->next)
@@ -295,12 +294,12 @@ CMD_FUNC(cmd_stats)
 
 	if (parc == 3 && parv[2][0] != '+' && parv[2][0] != '-')
 	{
-		if (hunt_server(client, recv_mtags, ":%s STATS %s :%s", 2, parc, parv) != HUNTED_ISME)
+		if (hunt_server(client, recv_mtags, "STATS", 2, parc, parv) != HUNTED_ISME)
 			return;
 	}
 	else if (parc == 4 && parv[2][0] != '+' && parv[2][0] != '-')
 	{
-		if (hunt_server(client, recv_mtags, ":%s STATS %s %s %s", 2, parc, parv) != HUNTED_ISME)
+		if (hunt_server(client, recv_mtags, "STATS", 2, parc, parv) != HUNTED_ISME)
 			return;
 	}
 	if (parc < 2 || !*parv[1])
@@ -382,20 +381,13 @@ CMD_FUNC(cmd_stats)
 	 */
 	if (stat->flag != 'S')
 	{
-		RunHook2(HOOKTYPE_STATS, client, flags);
+		RunHook(HOOKTYPE_STATS, client, flags);
 	}
 
 	sendnumeric(client, RPL_ENDOFSTATS, stat->flag);
-
-	if (!IsULine(client))
-		sendto_snomask(SNO_EYES, "Stats \'%c\' requested by %s (%s@%s)",
-			stat->flag, client->name, client->user->username, GetHost(client));
-	else
-		sendto_snomask(SNO_JUNK, "Stats \'%c\' requested by %s (%s@%s) [ulined]",
-			stat->flag, client->name, client->user->username, GetHost(client));
 }
 
-int stats_banversion(Client *client, char *para)
+int stats_banversion(Client *client, const char *para)
 {
 	ConfigItem_ban *bans;
 	for (bans = conf_ban; bans; bans = bans->next)
@@ -408,7 +400,7 @@ int stats_banversion(Client *client, char *para)
 	return 0;
 }
 
-int stats_links(Client *client, char *para)
+int stats_links(Client *client, const char *para)
 {
 	ConfigItem_link *link_p;
 #ifdef DEBUGMODE
@@ -433,40 +425,42 @@ int stats_links(Client *client, char *para)
 		else if (link_p->leaf)
 			sendnumericfmt(client, RPL_STATSLLINE, "L %s * %s %d",
 				link_p->leaf, link_p->servername, link_p->leaf_depth);
-		// TODO: send incoming allow list? (for opers only)
 	}
 #ifdef DEBUGMODE
 	list_for_each_entry(acptr, &client_list, client_node)
-		if (MyConnect(acptr) && acptr->serv && !IsMe(acptr))
+		if (MyConnect(acptr) && acptr->server && !IsMe(acptr))
 		{
-			if (!acptr->serv->conf)
+			if (!acptr->server->conf)
 				sendnotice(client, "client '%s' (%p) has NO CONF attached (? :P)",
 					acptr->name, acptr);
 			else
 				sendnotice(client, "client '%s' (%p) has conf %p attached, refcount: %d, temporary: %s",
 					acptr->name, acptr,
-					acptr->serv->conf,
-					acptr->serv->conf->refcount,
-					acptr->serv->conf->flag.temporary ? "YES" : "NO");
+					acptr->server->conf,
+					acptr->server->conf->refcount,
+					acptr->server->conf->flag.temporary ? "YES" : "NO");
 		}
 #endif
 	return 0;
 }
 
-int stats_denylinkall(Client *client, char *para)
+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)
-			sendnumeric(client, RPL_STATSDLINE,
-			'D', links->mask, links->prettyrule);
+		{
+			for (m = links->mask; m; m = m->next)
+				sendnumeric(client, RPL_STATSDLINE, 'D', m->mask, links->prettyrule);
+		}
 	}
 	return 0;
 }
 
-int stats_gline(Client *client, char *para)
+int stats_gline(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_GLOBAL|TKL_KILL, para, &cnt);
@@ -474,7 +468,7 @@ int stats_gline(Client *client, char *para)
 	return 0;
 }
 
-int stats_spamfilter(Client *client, char *para)
+int stats_spamfilter(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_SPAMF, para, &cnt);
@@ -482,7 +476,7 @@ int stats_spamfilter(Client *client, char *para)
 	return 0;
 }
 
-int stats_except(Client *client, char *para)
+int stats_except(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_EXCEPTION, para, &cnt);
@@ -490,43 +484,41 @@ int stats_except(Client *client, char *para)
 	return 0;
 }
 
-int stats_allow(Client *client, char *para)
+int stats_allow(Client *client, const char *para)
 {
 	ConfigItem_allow *allows;
+	ConfigItem_mask *m;
+
 	for (allows = conf_allow; allows; allows = allows->next)
 	{
-		sendnumeric(client, RPL_STATSILINE,
-		            allows->ip, allows->hostname,
-		            allows->maxperip,
-		            allows->global_maxperip,
-		            allows->class->name,
-		            allows->server ? allows->server : defserv,
-		            allows->port ? allows->port : 6667);
+		for (m = allows->mask; m; m = m->next)
+		{
+			sendnumeric(client, RPL_STATSILINE,
+				    m->mask, "-",
+				    allows->maxperip,
+				    allows->global_maxperip,
+				    allows->class->name,
+				    allows->server ? allows->server : DEFAULT_SERVER,
+				    allows->port ? allows->port : 6667);
+		}
 	}
 	return 0;
 }
 
-int stats_command(Client *client, char *para)
+int stats_command(Client *client, const char *para)
 {
 	int i;
 	RealCommand *mptr;
 	for (i = 0; i < 256; i++)
 		for (mptr = CommandHash[i]; mptr; mptr = mptr->next)
 			if (mptr->count)
-#ifndef DEBUGMODE
 			sendnumeric(client, RPL_STATSCOMMANDS, mptr->cmd,
 				mptr->count, mptr->bytes);
-#else
-			sendnumeric(client, RPL_STATSCOMMANDS, mptr->cmd,
-				mptr->count, mptr->bytes,
-				mptr->lticks, mptr->lticks / CLOCKS_PER_SEC,
-				mptr->rticks, mptr->rticks / CLOCKS_PER_SEC);
-#endif
 
 	return 0;
 }
 
-int stats_oper(Client *client, char *para)
+int stats_oper(Client *client, const char *para)
 {
 	ConfigItem_oper *oper_p;
 	ConfigItem_mask *m;
@@ -556,7 +548,7 @@ static char *stats_port_helper(ConfigItem_listen *listener)
 	return buf;
 }
 
-int stats_port(Client *client, char *para)
+int stats_port(Client *client, const char *para)
 {
 	ConfigItem_listen *listener;
 
@@ -577,7 +569,7 @@ int stats_port(Client *client, char *para)
 	return 0;
 }
 
-int stats_bannick(Client *client, char *para)
+int stats_bannick(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_NAME, para, &cnt);
@@ -585,7 +577,7 @@ int stats_bannick(Client *client, char *para)
 	return 0;
 }
 
-int stats_traffic(Client *client, char *para)
+int stats_traffic(Client *client, const char *para)
 {
 	Client *acptr;
 	IRCStatistics *sp;
@@ -599,41 +591,13 @@ int stats_traffic(Client *client, char *para)
 	{
 		if (IsServer(acptr))
 		{
-			sp->is_sbs += acptr->local->sendB;
-			sp->is_sbr += acptr->local->receiveB;
-			sp->is_sks += acptr->local->sendK;
-			sp->is_skr += acptr->local->receiveK;
-			sp->is_sti += now - acptr->local->firsttime;
+			sp->is_sti += now - acptr->local->creationtime;
 			sp->is_sv++;
-			if (sp->is_sbs > 1023)
-			{
-				sp->is_sks += (sp->is_sbs >> 10);
-				sp->is_sbs &= 0x3ff;
-			}
-			if (sp->is_sbr > 1023)
-			{
-				sp->is_skr += (sp->is_sbr >> 10);
-				sp->is_sbr &= 0x3ff;
-			}
 		}
 		else if (IsUser(acptr))
 		{
-			sp->is_cbs += acptr->local->sendB;
-			sp->is_cbr += acptr->local->receiveB;
-			sp->is_cks += acptr->local->sendK;
-			sp->is_ckr += acptr->local->receiveK;
-			sp->is_cti += now - acptr->local->firsttime;
+			sp->is_cti += now - acptr->local->creationtime;
 			sp->is_cl++;
-			if (sp->is_cbs > 1023)
-			{
-				sp->is_cks += (sp->is_cbs >> 10);
-				sp->is_cbs &= 0x3ff;
-			}
-			if (sp->is_cbr > 1023)
-			{
-				sp->is_ckr += (sp->is_cbr >> 10);
-				sp->is_cbr &= 0x3ff;
-			}
 		}
 		else if (IsUnknown(acptr))
 			sp->is_ni++;
@@ -648,17 +612,17 @@ int stats_traffic(Client *client, char *para)
 	sendnumericfmt(client, RPL_STATSDEBUG, "local connections %u udp packets %u", sp->is_loc, sp->is_udp);
 	sendnumericfmt(client, RPL_STATSDEBUG, "Client Server");
 	sendnumericfmt(client, RPL_STATSDEBUG, "connected %u %u", sp->is_cl, sp->is_sv);
-	sendnumericfmt(client, RPL_STATSDEBUG, "bytes sent %ld.%huK %ld.%huK",
-		sp->is_cks, sp->is_cbs, sp->is_sks, sp->is_sbs);
-	sendnumericfmt(client, RPL_STATSDEBUG, "bytes recv %ld.%huK %ld.%huK",
-	    sp->is_ckr, sp->is_cbr, sp->is_skr, sp->is_sbr);
+	sendnumericfmt(client, RPL_STATSDEBUG, "messages sent %lld", me.local->traffic.messages_sent);
+	sendnumericfmt(client, RPL_STATSDEBUG, "messages received %lld", me.local->traffic.messages_received);
+	sendnumericfmt(client, RPL_STATSDEBUG, "bytes sent %lld", me.local->traffic.bytes_sent);
+	sendnumericfmt(client, RPL_STATSDEBUG, "bytes received %lld", me.local->traffic.bytes_received);
 	sendnumericfmt(client, RPL_STATSDEBUG, "time connected %lld %lld",
 	    (long long)sp->is_cti, (long long)sp->is_sti);
 
 	return 0;
 }
 
-int stats_fdtable(Client *client, char *para)
+int stats_fdtable(Client *client, const char *para)
 {
 	int i;
 
@@ -677,14 +641,14 @@ int stats_fdtable(Client *client, char *para)
 	return 0;
 }
 
-int stats_uline(Client *client, char *para)
+int stats_uline(Client *client, const char *para)
 {
 	ConfigItem_ulines *ulines;
 	for (ulines = conf_ulines; ulines; ulines = ulines->next)
 		sendnumeric(client, RPL_STATSULINE, ulines->servername);
 	return 0;
 }
-int stats_vhost(Client *client, char *para)
+int stats_vhost(Client *client, const char *para)
 {
 	ConfigItem_mask *m;
 	ConfigItem_vhost *vhosts;
@@ -700,20 +664,23 @@ int stats_vhost(Client *client, char *para)
 	return 0;
 }
 
-int stats_denylinkauto(Client *client, char *para)
+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)
-			sendnumeric(client, RPL_STATSDLINE,
-			'd', links->mask, links->prettyrule);
+		{
+			for (m = links->mask; m; m = m->next)
+				sendnumeric(client, RPL_STATSDLINE, 'd', m->mask, links->prettyrule);
+		}
 	}
 	return 0;
 }
 
-int stats_kline(Client *client, char *para)
+int stats_kline(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_KILL, NULL, &cnt);
@@ -721,7 +688,7 @@ int stats_kline(Client *client, char *para)
 	return 0;
 }
 
-int stats_banrealname(Client *client, char *para)
+int stats_banrealname(Client *client, const char *para)
 {
 	ConfigItem_ban *bans;
 	for (bans = conf_ban; bans; bans = bans->next)
@@ -735,14 +702,14 @@ int stats_banrealname(Client *client, char *para)
 	return 0;
 }
 
-int stats_sqline(Client *client, char *para)
+int stats_sqline(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_NAME|TKL_GLOBAL, para, &cnt);
 	return 0;
 }
 
-int stats_chanrestrict(Client *client, char *para)
+int stats_chanrestrict(Client *client, const char *para)
 {
 	ConfigItem_deny_channel *dchans;
 	ConfigItem_allow_channel *achans;
@@ -757,7 +724,7 @@ int stats_chanrestrict(Client *client, char *para)
 	return 0;
 }
 
-int stats_shun(Client *client, char *para)
+int stats_shun(Client *client, const char *para)
 {
 	int cnt = 0;
 	tkl_stats(client, TKL_GLOBAL|TKL_SHUN, para, &cnt);
@@ -765,13 +732,13 @@ int stats_shun(Client *client, char *para)
 }
 
 /* should this be moved to a seperate stats flag? */
-int stats_officialchannels(Client *client, char *para)
+int stats_officialchannels(Client *client, const char *para)
 {
 	ConfigItem_offchans *x;
 
 	for (x = conf_offchans; x; x = x->next)
 	{
-		sendtxtnumeric(client, "%s %s", x->chname, x->topic ? x->topic : "");
+		sendtxtnumeric(client, "%s %s", x->name, x->topic ? x->topic : "");
 	}
 	return 0;
 }
@@ -791,6 +758,14 @@ static void stats_set_anti_flood(Client *client, FloodSettings *f)
 				f->name, floodoption_names[i],
 				(int)f->limit[i], pretty_time_val(f->period[i]));
 		}
+		if (i == FLD_LAG_PENALTY)
+		{
+			sendtxtnumeric(client, "anti-flood::%s::lag-penalty: %d msec",
+				f->name, (int)f->period[i]);
+			sendtxtnumeric(client, "anti-flood::%s::lag-penalty-bytes: %d",
+				f->name,
+				f->limit[i] == INT_MAX ? 0 : (int)f->limit[i]);
+		}
 		else
 		{
 			sendtxtnumeric(client, "anti-flood::%s::%s: %d per %s",
@@ -800,11 +775,12 @@ static void stats_set_anti_flood(Client *client, FloodSettings *f)
 	}
 }
 
-int stats_set(Client *client, char *para)
+int stats_set(Client *client, const char *para)
 {
 	char *uhallow;
 	SecurityGroup *s;
 	FloodSettings *f;
+	char modebuf[BUFSIZE], parabuf[BUFSIZE];
 
 	if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
 	{
@@ -813,8 +789,8 @@ int stats_set(Client *client, char *para)
 	}
 
 	sendtxtnumeric(client, "*** Configuration Report ***");
-	sendtxtnumeric(client, "network-name: %s", ircnetwork);
-	sendtxtnumeric(client, "default-server: %s", defserv);
+	sendtxtnumeric(client, "network-name: %s", NETWORK_NAME);
+	sendtxtnumeric(client, "default-server: %s", DEFAULT_SERVER);
 	if (SERVICES_NAME)
 	{
 		sendtxtnumeric(client, "services-server: %s", SERVICES_NAME);
@@ -827,9 +803,9 @@ int stats_set(Client *client, char *para)
 	{
 		sendtxtnumeric(client, "sasl-server: %s", SASL_SERVER);
 	}
-	sendtxtnumeric(client, "hiddenhost-prefix: %s", hidden_host);
-	sendtxtnumeric(client, "help-channel: %s", helpchan);
-	sendtxtnumeric(client, "cloak-keys: %s", CLOAK_KEYCRC);
+	sendtxtnumeric(client, "cloak-prefix: %s", CLOAK_PREFIX);
+	sendtxtnumeric(client, "help-channel: %s", HELP_CHANNEL);
+	sendtxtnumeric(client, "cloak-keys: %s", CLOAK_KEY_CHECKSUM);
 	sendtxtnumeric(client, "kline-address: %s", KLINE_ADDRESS);
 	if (GLINE_ADDRESS)
 		sendtxtnumeric(client, "gline-address: %s", GLINE_ADDRESS);
@@ -896,8 +872,6 @@ int stats_set(Client *client, char *para)
 	sendtxtnumeric(client, "static-part: %s", STATIC_PART ? STATIC_PART : "<none>");
 	sendtxtnumeric(client, "who-limit: %d", WHOLIMIT);
 	sendtxtnumeric(client, "silence-limit: %d", SILENCE_LIMIT);
-	if (DNS_BINDIP)
-		sendtxtnumeric(client, "dns::bind-ip: %s", DNS_BINDIP);
 	sendtxtnumeric(client, "ban-version-tkl-time: %s", pretty_time_val(BAN_VERSION_TKL_TIME));
 	if (LINK_BINDIP)
 		sendtxtnumeric(client, "link::bind-ip: %s", LINK_BINDIP);
@@ -932,7 +906,7 @@ int stats_set(Client *client, char *para)
 	sendtxtnumeric(client, "outdated-tls-policy::user: %s", policy_valtostr(iConf.outdated_tls_policy_user));
 	sendtxtnumeric(client, "outdated-tls-policy::oper: %s", policy_valtostr(iConf.outdated_tls_policy_oper));
 	sendtxtnumeric(client, "outdated-tls-policy::server: %s", policy_valtostr(iConf.outdated_tls_policy_server));
-	RunHook2(HOOKTYPE_STATS, client, "S");
+	RunHook(HOOKTYPE_STATS, client, "S");
 #ifndef _WIN32
 	sendtxtnumeric(client, "This server can handle %d concurrent sockets (%d clients + %d reserve)",
 		maxclients+CLIENTS_RESERVE, maxclients, CLIENTS_RESERVE);
@@ -940,34 +914,34 @@ int stats_set(Client *client, char *para)
 	return 1;
 }
 
-int stats_tld(Client *client, char *para)
+int stats_tld(Client *client, const char *para)
 {
 	ConfigItem_tld *tld;
+	ConfigItem_mask *m;
 
 	for (tld = conf_tld; tld; tld = tld->next)
 	{
-		sendnumeric(client, RPL_STATSTLINE,
-			tld->mask, tld->motd_file, tld->rules_file ?
-			tld->rules_file : "none");
+		for (m = tld->mask; m; m = m->next)
+			sendnumeric(client, RPL_STATSTLINE, m->mask, tld->motd_file, tld->rules_file ? tld->rules_file : "none");
 	}
 
 	return 0;
 }
 
-int stats_uptime(Client *client, char *para)
+int stats_uptime(Client *client, const char *para)
 {
-	time_t tmpnow;
+	long long uptime;
 
-	tmpnow = TStime() - me.local->since;
+	uptime = TStime() - me.local->fake_lag;
 	sendnumeric(client, RPL_STATSUPTIME,
-	    tmpnow / 86400, (tmpnow / 3600) % 24, (tmpnow / 60) % 60,
-	    tmpnow % 60);
+	    uptime / 86400, (uptime / 3600) % 24, (uptime / 60) % 60,
+	    uptime % 60);
 	sendnumeric(client, RPL_STATSCONN,
 	    max_connection_count, irccounts.me_max);
 	return 0;
 }
 
-int stats_denyver(Client *client, char *para)
+int stats_denyver(Client *client, const char *para)
 {
 	ConfigItem_deny_version *versions;
 	for (versions = conf_deny_version; versions; versions = versions->next)
@@ -978,7 +952,7 @@ int stats_denyver(Client *client, char *para)
 	return 0;
 }
 
-int stats_notlink(Client *client, char *para)
+int stats_notlink(Client *client, const char *para)
 {
 	ConfigItem_link *link_p;
 
@@ -993,7 +967,7 @@ int stats_notlink(Client *client, char *para)
 	return 0;
 }
 
-int stats_class(Client *client, char *para)
+int stats_class(Client *client, const char *para)
 {
 	ConfigItem_class *classes;
 
@@ -1009,31 +983,23 @@ int stats_class(Client *client, char *para)
 	return 0;
 }
 
-int stats_linkinfo(Client *client, char *para)
+int stats_linkinfo(Client *client, const char *para)
 {
 	return stats_linkinfoint(client, para, 0);
 }
 
-int stats_linkinfoall(Client *client, char *para)
+int stats_linkinfoall(Client *client, const char *para)
 {
 	return stats_linkinfoint(client, para, 1);
 }
 
-int stats_linkinfoint(Client *client, char *para, int all)
+int stats_linkinfoint(Client *client, const char *para, int all)
 {
-#ifndef DEBUGMODE
-	static char Sformat[] = "SendQ SendM SendBytes RcveM RcveBytes Open_since :Idle";
-	static char Lformat[] = "%s%s %u %u %u %u %u %u :%u";
-#else
-	static char Sformat[] = "SendQ SendM SendBytes RcveM RcveBytes Open_since CPU :Idle";
-	static char Lformat[] = "%s%s %u %u %u %u %u %u %s";
-	char pbuf[96];		/* Should be enough for to ints */
-#endif
 	int remote = 0;
 	int wilds = 0;
 	int doall = 0;
-	int showports = ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL);
 	Client *acptr;
+
 	/*
 	 * send info about connections which match, or all if the
 	 * mask matches me.name.  Only restrictions are on those who
@@ -1051,7 +1017,9 @@ int stats_linkinfoint(Client *client, char *para, int all)
 	}
 	else
 		para = me.name;
-	sendnumericfmt(client, RPL_STATSLINKINFO, "%s", Sformat);
+
+	sendnumericfmt(client, RPL_STATSLINKINFO, "Name SendQ SendM SendBytes RcveM RcveBytes Open_since :Idle");
+
 	if (!MyUser(client))
 	{
 		remote = 1;
@@ -1076,54 +1044,23 @@ int stats_linkinfoint(Client *client, char *para, int all)
 			continue;
 		}
 
-#ifdef DEBUGMODE
-		ircsnprintf(pbuf, sizeof(pbuf), "%ld :%ld", (long)acptr->local->cputime,
-		      (long)(acptr->user && MyConnect(acptr)) ? TStime() - acptr->local->last : 0);
-#endif
-		if (ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
-		{
-			sendnumericfmt(client, RPL_STATSLINKINFO, Lformat,
-				all ?
-				(get_client_name2(acptr, showports)) :
-				(get_client_name(acptr, FALSE)),
-				get_client_status(acptr),
-				(int)DBufLength(&acptr->local->sendQ),
-				(int)acptr->local->sendM, (int)acptr->local->sendK,
-				(int)acptr->local->receiveM,
-				(int)acptr->local->receiveK,
-			 	TStime() - acptr->local->firsttime,
-#ifndef DEBUGMODE
-				(acptr->user && MyConnect(acptr)) ?
-				TStime() - acptr->local->last : 0);
-#else
-				pbuf);
-#endif
-		}
-		else if (!strchr(acptr->name, '.'))
-			sendnumericfmt(client, RPL_STATSLINKINFO, Lformat,
-				IsHidden(acptr) ? acptr->name :
-				all ?	/* Potvin - PreZ */
-				get_client_name2(acptr, showports) :
-				get_client_name(acptr, FALSE),
-				get_client_status(acptr),
-				(int)DBufLength(&acptr->local->sendQ),
-				(int)acptr->local->sendM, (int)acptr->local->sendK,
-				(int)acptr->local->receiveM,
-				(int)acptr->local->receiveK,
-				TStime() - acptr->local->firsttime,
-#ifndef DEBUGMODE
-				(acptr->user && MyConnect(acptr)) ?
-				TStime() - acptr->local->last : 0);
-#else
-				pbuf);
-#endif
+		sendnumericfmt(client, RPL_STATSLINKINFO,
+		        "%s%s %lld %lld %lld %lld %lld %lld :%lld",
+			acptr->name, get_client_status(acptr),
+			(long long)DBufLength(&acptr->local->sendQ),
+			(long long)acptr->local->traffic.messages_sent,
+			(long long)acptr->local->traffic.bytes_sent,
+			(long long)acptr->local->traffic.messages_received,
+			(long long)acptr->local->traffic.bytes_received,
+			(long long)(TStime() - acptr->local->creationtime),
+			(long long)(TStime() - acptr->local->last_msg_received));
 	}
 #ifdef DEBUGMODE
 	list_for_each_entry(acptr, &client_list, client_node)
 	{
 		if (IsServer(acptr))
 			sendnotice(client, "Server %s is %s",
-				acptr->name, acptr->serv->flags.synced ? "SYNCED" : "NOT SYNCED!!");
+				acptr->name, acptr->server->flags.synced ? "SYNCED" : "NOT SYNCED!!");
 	}
 #endif
 	return 0;
diff --git a/src/modules/sts.c b/src/modules/sts.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Strict Transport Security CAP", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 MOD_INIT()
@@ -66,7 +66,7 @@ int sts_capability_visible(Client *client)
 	if (!IsSecure(client))
 	{
 		if (iConf.tls_options && iConf.tls_options->sts_port)
-			return 1; /* YES, non-SSL user and set::tls::sts-policy configured */
+			return 1; /* YES, non-TLS user and set::tls::sts-policy configured */
 		return 0; /* NO, there is no sts-policy */
 	}
 
@@ -78,7 +78,7 @@ int sts_capability_visible(Client *client)
 	return 0;
 }
 
-char *sts_capability_parameter(Client *client)
+const char *sts_capability_parameter(Client *client)
 {
 	TLSOptions *ssl;
 	static char buf[256];
diff --git a/src/modules/svsjoin.c b/src/modules/svsjoin.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /svsjoin", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -66,7 +66,7 @@ CMD_FUNC(cmd_svsjoin)
 	if (!IsULine(client))
 		return;
 
-	if ((parc < 3) || !(target = find_person(parv[1], NULL)))
+	if ((parc < 3) || !(target = find_user(parv[1], NULL)))
 		return;
 
 	if (MyUser(target))
diff --git a/src/modules/svskill.c b/src/modules/svskill.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /svskill", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 
@@ -63,7 +63,7 @@ CMD_FUNC(cmd_svskill)
 {
 	MessageTag *mtags = NULL;
 	Client *target;
-	char *comment = "SVS Killed";
+	const char *comment = "SVS Killed";
 	int n;
 
 	if (parc < 2)
@@ -76,7 +76,7 @@ CMD_FUNC(cmd_svskill)
 	if (!IsULine(client))
 		return;
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 		return;
 
 	/* for new_message() we use target here, makes sense for the exit_client, right? */
diff --git a/src/modules/svslusers.c b/src/modules/svslusers.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /svslusers", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -66,7 +66,7 @@ CMD_FUNC(cmd_svslusers)
 {
         if (!IsULine(client) || parc < 4)
 		return;  
-        if (hunt_server(client, NULL, ":%s SVSLUSERS %s %s :%s", 1, parc, parv) == HUNTED_ISME)
+        if (hunt_server(client, NULL, "SVSLUSERS", 1, parc, parv) == HUNTED_ISME)
         {
 		int temp;
 		temp = atoi(parv[2]);
diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c
@@ -39,9 +39,11 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /svsmode and svs2mode", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
+char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
 MOD_INIT()
 {
 	CommandAdd(modinfo->handle, MSG_SVSMODE, cmd_svsmode, MAXPARA, CMD_SERVER|CMD_USER);
@@ -63,8 +65,10 @@ MOD_UNLOAD()
 void unban_user(Client *client, Channel *channel, Client *acptr, char chmode)
 {
 	Extban *extban;
+	const char *nextbanstr;
 	Ban *ban, *bnext;
 	Ban **banlist;
+	BanContext *b;
 	char uhost[NICKLEN+USERLEN+HOSTLEN+6], vhost[NICKLEN+USERLEN+HOSTLEN+6];
 	char ihost[NICKLEN+USERLEN+HOSTLEN+6], chost[NICKLEN+USERLEN+HOSTLEN+6];
 
@@ -114,6 +118,11 @@ void unban_user(Client *client, Channel *channel, Client *acptr, char chmode)
 
 	/* DO THE ACTUAL WORK */
 
+	b = safe_alloc(sizeof(BanContext));
+	b->client = acptr;
+	b->channel = channel;
+	b->ban_check_types = BANCHK_JOIN;
+
 	for (ban = *banlist; ban; ban = bnext)
 	{
 		bnext = ban->next;
@@ -122,15 +131,15 @@ void unban_user(Client *client, Channel *channel, Client *acptr, char chmode)
 		    (*ihost && match_simple(ban->banstr, ihost)) ||
 		    (*chost && match_simple(ban->banstr, chost)))
 		{
-			add_send_mode_param(channel, client, '-',  chmode, 
-				ban->banstr);
+			add_send_mode_param(channel, client, '-',  chmode, ban->banstr);
 			del_listmode(banlist, channel, ban->banstr);
 		}
-		else if (chmode != 'I' && *ban->banstr == '~' && (extban = findmod_by_bantype(ban->banstr[1])))
+		else if (chmode != 'I' && *ban->banstr == '~' && (extban = findmod_by_bantype(ban->banstr, &nextbanstr)))
 		{
-			if (extban->options & EXTBOPT_CHSVSMODE) 
+			if ((extban->options & EXTBOPT_CHSVSMODE) && (extban->is_banned_events & b->ban_check_types))
 			{
-				if (extban->is_banned(acptr, channel, ban->banstr, BANCHK_JOIN, NULL, NULL))
+				b->banstr = nextbanstr;
+				if (extban->is_banned(b))
 				{
 					add_send_mode_param(channel, acptr, '-', chmode, ban->banstr);
 					del_listmode(banlist, channel, ban->banstr);
@@ -138,6 +147,7 @@ void unban_user(Client *client, Channel *channel, Client *acptr, char chmode)
 			}
 		}
 	}
+	safe_free(b);
 }
 
 void clear_bans(Client *client, Channel *channel, char chmode)
@@ -164,7 +174,7 @@ void clear_bans(Client *client, Channel *channel, char chmode)
 	for (ban = *banlist; ban; ban = bnext)
 	{
 		bnext = ban->next;
-		if (chmode != 'I' && (*ban->banstr == '~') && (extban = findmod_by_bantype(ban->banstr[1])))
+		if (chmode != 'I' && (*ban->banstr == '~') && (extban = findmod_by_bantype(ban->banstr, NULL)))
 		{
 			if (!(extban->options & EXTBOPT_CHSVSMODE))							
 				continue;
@@ -195,14 +205,14 @@ void clear_bans(Client *client, Channel *channel, char chmode)
  *
  * OLD syntax had a 'ts' parameter. No services are known to use this.
  */
-void channel_svsmode(Client *client, int parc, char *parv[]) 
+void channel_svsmode(Client *client, int parc, const char *parv[]) 
 {
 	Channel *channel;
 	Client *target;
-	char *m;
+	const char *m;
 	int what = MODE_ADD;
 	int i = 4; // wtf is this
-	Member *cm;
+	Member *member;
 	int channel_flags;
 
 	*parabuf = *modebuf = '\0';
@@ -210,67 +220,70 @@ void channel_svsmode(Client *client, int parc, char *parv[])
 	if ((parc < 3) || BadPtr(parv[2]))
 		return;
 
-	if (!(channel = find_channel(parv[1], NULL)))
+	if (!(channel = find_channel(parv[1])))
 		return;
 
-	for(m = parv[2]; *m; m++)
+	for (m = parv[2]; *m; m++)
 	{
-		switch (*m)
+		if (*m == '+') 
 		{
-			case '+':
-				what = MODE_ADD;
-				break;
-			case '-':
-				what = MODE_DEL;
-				break;
-			case 'v':
-			case 'h':
-			case 'o':
-			case 'a':
-			case 'q':
-				if (what != MODE_DEL)
-				{
-					sendto_realops("Warning! Received SVS(2)MODE with +%c for %s from %s, which is invalid!!",
-						*m, channel->chname, client->name);
-					continue;
-				}
-				channel_flags = char_to_channelflag(*m);
-				for (cm = channel->members; cm; cm = cm->next)
-				{
-					if (cm->flags & channel_flags)
-					{
-						Membership *mb;
-						mb = find_membership_link(cm->client->user->channel, channel);
-						add_send_mode_param(channel, client, '-', *m, cm->client->name);
-						cm->flags &= ~channel_flags;
-						if (mb)
-							mb->flags = cm->flags;
-					}
-				}
-				break;
-			case 'b':
-			case 'e':
-			case 'I':
-				if (parc >= i)
+			what = MODE_ADD;
+		} else
+		if (*m == '-')
+		{
+			what = MODE_DEL;
+		} else
+		if ((*m == 'b') || (*m == 'e') || (*m == 'I'))
+		{
+			if (parc >= i)
+			{
+				if (!(target = find_user(parv[i-1], NULL)))
 				{
-					if (!(target = find_person(parv[i-1], NULL)))
-					{
-						i++;
-						break;
-					}
 					i++;
-
-					unban_user(client, channel, target, *m);
+					break;
 				}
-				else {
-					clear_bans(client, channel, *m);
+				i++;
+
+				unban_user(client, channel, target, *m);
+			}
+			else {
+				clear_bans(client, channel, *m);
+			}
+		} else
+		{
+			/* Find member mode handler (vhoaq) */
+			Cmode *cm = find_channel_mode_handler(*m);
+			if (!cm || (cm->type != CMODE_MEMBER))
+			{
+				unreal_log(ULOG_WARNING, "svsmode", "INVALID_SVSMODE", client,
+				           "Invalid SVSMODE for mode '$mode_character' in channel $channel from $client.",
+				           log_data_char("mode_character", *m),
+				           log_data_channel("channel", channel));
+				continue;
+			}
+			if (what != MODE_DEL)
+			{
+				unreal_log(ULOG_WARNING, "svsmode", "INVALID_SVSMODE", client,
+				           "Invalid SVSMODE from $client trying to add '$mode_character' in $channel.",
+				           log_data_char("mode_character", *m),
+				           log_data_channel("channel", channel));
+				continue;
+			}
+			for (member = channel->members; member; member = member->next)
+			{
+				if (check_channel_access_letter(member->member_modes, *m))
+				{
+					Membership *mb = find_membership_link(member->client->user->channel, channel);
+					if (!mb)
+						continue; /* bug */
+					
+					/* Send the -x out */
+					add_send_mode_param(channel, client, '-', *m, member->client->name);
+					
+					/* And remove from memory */
+					del_member_mode_fast(member, mb, *m);
 				}
-				break;
-			default:
-				sendto_realops("Warning! Invalid mode `%c' used with 'SVSMODE %s %s %s' (from %s %s)",
-					       *m, channel->chname, parv[2], parv[3] ? parv[3] : "",
-					       client->direction->name, client->name);
-				break;
+			}
 		}
 	}
 
@@ -278,16 +291,17 @@ void channel_svsmode(Client *client, int parc, char *parv[])
 	if (*parabuf)
 	{
 		MessageTag *mtags = NULL;
+		int destroy_channel = 0;
 		/* NOTE: cannot use 'recv_mtag' here because MODE could be rewrapped. Not ideal :( */
 		new_message(client, NULL, &mtags);
 
 		sendto_channel(channel, client, client, 0, 0, SEND_LOCAL, mtags,
 		               ":%s MODE %s %s %s",
-		               client->name, channel->chname,  modebuf, parabuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s", client->id, channel->chname, modebuf, parabuf);
+		               client->name, channel->name,  modebuf, parabuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s", client->id, channel->name, modebuf, parabuf);
 
 		/* Activate this hook just like cmd_mode.c */
-		RunHook7(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, 0);
+		RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel);
 
 		free_message_tags(mtags);
 
@@ -300,17 +314,17 @@ void channel_svsmode(Client *client, int parc, char *parv[])
  * This is used by both SVSMODE and SVS2MODE, when dealing with users (not channels).
  * parv[1] - nick to change mode for
  * parv[2] - modes to change
- * parv[3] - Service Stamp (if mode == d)
+ * parv[3] - account name (if mode contains 'd')
  *
  * show_change can be 0 (for svsmode) or 1 (for svs2mode).
  */
-void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], int show_change)
+void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], int show_change)
 {
-	int i;
-	char *m;
+	Umode *um;
+	const char *m;
 	Client *target;
 	int  what;
-	long setflags = 0;
+	long oldumodes = 0;
 
 	if (!IsULine(client))
 		return;
@@ -326,15 +340,12 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 		return;
 	}
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 		return;
 
 	userhost_save_current(target);
 
-	/* initialize setflag to be the user's pre-SVSMODE flags */
-	for (i = 0; i <= Usermode_highest; i++)
-		if (Usermode_Table[i].flag && (target->umodes & Usermode_Table[i].mode))
-			setflags |= Usermode_Table[i].mode;
+	oldumodes = target->umodes;
 
 	/* parse mode change string(s) */
 	for (m = parv[2]; *m; m++)
@@ -383,6 +394,8 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 					/* User is no longer oper (after the goto below, anyway)...
 					 * so remove all oper-only modes and snomasks.
 					 */
+					if (MyUser(client))
+						RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL);
 					remove_oper_privileges(target, 0);
 				}
 				goto setmodex;
@@ -392,10 +405,15 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 					if (!IsOper(target) && !strchr(parv[2], 'o')) /* (ofcoz this strchr() is flawed) */
 					{
 						/* isn't an oper, and would not become one either.. abort! */
-						sendto_realops(
-							"[BUG] server %s tried to set +H while user not an oper, para=%s/%s, "
-							"umodes=%ld, please fix your services or if you think it's our fault, "
-							"report at https://bugs.unrealircd.org/", client->name, parv[1], parv[2], target->umodes);
+						unreal_log(ULOG_WARNING, "svsmode", "SVSMODE_INVALID", client,
+						           "[BUG] Server $client tried to set user mode +H (hidden ircop) "
+						           "on a user that is not +o (not ircop)! "
+						           "Please fix your services, or if you think it is our fault, then "
+						           "report at https://bugs.unrealircd.org/. "
+						           "Parameters: $para1 $para2. Target: $target.",
+						           log_data_string("para1", parv[1]),
+						           log_data_string("para2", parv[2]),
+						           log_data_client("target", target));
 						break; /* abort! */
 					}
 					irccounts.operators--;
@@ -406,8 +424,18 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 			case 'd':
 				if (parv[3])
 				{
-					strlcpy(target->user->svid, parv[3], sizeof(target->user->svid));
-					user_account_login(recv_mtags, target);
+					int was_logged_in = IsLoggedIn(target) ? 1 : 0;
+					strlcpy(target->user->account, parv[3], sizeof(target->user->account));
+					if (!was_logged_in && !IsLoggedIn(target))
+					{
+						/* We don't care about users going from not logged in
+						 * to not logged in, which is something that can happen
+						 * from 0 to 123456, eg from no account to unconfirmed account.
+						 */
+					} else {
+						/* LOGIN or LOGOUT (or account change) */
+						user_account_login(recv_mtags, target);
+					}
 					if (MyConnect(target) && IsDead(target))
 						return; /* was killed due to *LINE on ~a probably */
 				}
@@ -477,16 +505,14 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 				break;
 			default:
 				setmodex:
-				for (i = 0; i <= Usermode_highest; i++)
+				for (um = usermodes; um; um = um->next)
 				{
-					if (!Usermode_Table[i].flag)
-						continue;
-					if (*m == Usermode_Table[i].flag)
+					if (um->letter == *m)
 					{
 						if (what == MODE_ADD)
-							target->umodes |= Usermode_Table[i].mode;
+							target->umodes |= um->mode;
 						else
-							target->umodes &= ~Usermode_Table[i].mode;
+							target->umodes &= ~um->mode;
 						break;
 					}
 				}
@@ -503,15 +529,15 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 		    parv[1], parv[2]);
 
 	/* Here we trigger the same hooks that cmd_mode does and, likewise,
-	   only if the old flags (setflags) are different than the newly-
+	   only if the old flags (oldumodes) are different than the newly-
 	   set ones */
-	if (setflags != target->umodes)
-		RunHook3(HOOKTYPE_UMODE_CHANGE, target, setflags, target->umodes);
+	if (oldumodes != target->umodes)
+		RunHook(HOOKTYPE_UMODE_CHANGE, target, oldumodes, target->umodes);
 
 	if (show_change)
 	{
 		char buf[BUFSIZE];
-		build_umode_string(target, setflags, ALL_UMODES, buf);
+		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);
 	}
@@ -525,7 +551,7 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
  * cmd_svsmode() added by taz
  * parv[1] - username to change mode for
  * parv[2] - modes to change
- * parv[3] - Service Stamp (if mode == d)
+ * parv[3] - account name (if mode contains 'd')
  */
 CMD_FUNC(cmd_svsmode)
 {
@@ -536,7 +562,7 @@ CMD_FUNC(cmd_svsmode)
  * cmd_svs2mode() added by Potvin
  * parv[1] - username to change mode for
  * parv[2] - modes to change
- * parv[3] - Service Stamp (if mode == d)
+ * parv[3] - account name (if mode contains 'd')
  */
 CMD_FUNC(cmd_svs2mode)
 {
@@ -588,8 +614,8 @@ void add_send_mode_param(Channel *channel, Client *from, char what, char mode, c
 		new_message(from, NULL, &mtags);
 		sendto_channel(channel, from, from, 0, 0, SEND_LOCAL, mtags,
 		               ":%s MODE %s %s %s",
-		               from->name, channel->chname, modebuf, parabuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s", from->id, channel->chname, modebuf, parabuf);
+		               from->name, channel->name, modebuf, parabuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s", from->id, channel->name, modebuf, parabuf);
 		free_message_tags(mtags);
 		send = 0;
 		*parabuf = 0;
diff --git a/src/modules/svsmotd.c b/src/modules/svsmotd.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /svsmotd", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,59 +60,53 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_svsmotd)
 {
-        FILE *conf = NULL;
+	FILE *conf = NULL;
 
-        if (!IsULine(client))
-        {
-                sendnumeric(client, ERR_NOPRIVILEGES);
-                return;
-        }
-        if (parc < 2)
-        {
-                sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSMOTD");
-                return;
-        }
+	if (!IsULine(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSMOTD");
+		return;
+	}
 
-        if ((*parv[1] != '!') && parc < 3)
-        {
-                sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSMOTD");
-                return;
-        }
+	if ((*parv[1] != '!') && parc < 3)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSMOTD");
+		return;
+	}
 
-        switch (*parv[1])
-        {
-          case '#':
-                  conf = fopen(conf_files->svsmotd_file, "a");
-                  sendto_ops("Added '%s' to services motd", parv[2]);
-                  break;
-          case '!':
-          {
-                  remove(conf_files->svsmotd_file);
-                  free_motd(&svsmotd);
-                  sendto_ops("Wiped out services motd data");
-                  break;
-          }
-          default:
-                  return;
-        }
-        if (parv[2])
-                sendto_server(client, 0, 0, NULL, ":%s SVSMOTD %s :%s", client->id, parv[1], parv[2]);
-        else
-                sendto_server(client, 0, 0, NULL, ":%s SVSMOTD %s", client->id, parv[1]);
+	if (parv[2])
+		sendto_server(client, 0, 0, NULL, ":%s SVSMOTD %s :%s", client->id, parv[1], parv[2]);
+	else
+		sendto_server(client, 0, 0, NULL, ":%s SVSMOTD %s", client->id, parv[1]);
 
-        if (conf == NULL)
-                return;
+	switch (*parv[1])
+	{
+		case '#':
+			unreal_log(ULOG_INFO, "svsmotd", "SVSMOTD_ADDED", client,
+			           "Services added '$line' to services motd",
+			           log_data_string("line", parv[2]));
+			conf = fopen(conf_files->svsmotd_file, "a");
+			if (conf)
+			{
+				fprintf(conf, "%s\n", parv[2]);
+				fclose(conf);
+			}
+			break;
+		case '!':
+			unreal_log(ULOG_INFO, "svsmotd", "SVSMOTD_REMOVED", client,
+			           "Services deleted the services motd");
+			remove(conf_files->svsmotd_file);
+			free_motd(&svsmotd);
+			break;
+		default:
+			return;
+	}
 
-        if (parc < 3 && (*parv[1] == '!'))
-        {
-                fclose(conf);
-                return;
-        }
-        fprintf(conf, "%s\n", parv[2]);
-        if (*parv[1] == '!')
-                sendto_ops("Added '%s' to services motd", parv[2]);
-
-        fclose(conf);
-        /* We editted it, so rehash it -- codemastr */
-        read_motd(conf_files->svsmotd_file, &svsmotd);
+	/* We editted it, so rehash it -- codemastr */
+	read_motd(conf_files->svsmotd_file, &svsmotd);
 }
diff --git a/src/modules/svsnick.c b/src/modules/svsnick.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /svsnick", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -64,20 +64,23 @@ CMD_FUNC(cmd_svsnick)
 	Client *acptr;
 	Client *ocptr; /* Other client */
 	MessageTag *mtags = NULL;
+	char nickname[NICKLEN+1];
+	char oldnickname[NICKLEN+1];
 
 	if (!IsULine(client) || parc < 4 || (strlen(parv[2]) > NICKLEN))
 		return; /* This looks like an error anyway -Studded */
 
-	if (hunt_server(client, NULL, ":%s SVSNICK %s %s :%s", 1, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, NULL, "SVSNICK", 1, parc, parv) != HUNTED_ISME)
 		return; /* Forwarded, done */
 
-	if (do_nick_name(parv[2]) == 0)
+	strlcpy(nickname, parv[2], sizeof(nickname));
+	if (do_nick_name(nickname) == 0)
 		return;
 
-	if (!(acptr = find_person(parv[1], NULL)))
+	if (!(acptr = find_user(parv[1], NULL)))
 		return; /* User not found, bail out */
 
-	if ((ocptr = find_client(parv[2], NULL)) && ocptr != acptr) /* Collision */
+	if ((ocptr = find_client(nickname, NULL)) && ocptr != acptr) /* Collision */
 	{
 		exit_client(acptr, NULL,
 		                   "Nickname collision due to Services enforced "
@@ -86,30 +89,31 @@ CMD_FUNC(cmd_svsnick)
 	}
 
 	/* if the new nickname is identical to the old one, ignore it */
-	if (!strcmp(acptr->name, parv[2]))
+	if (!strcmp(acptr->name, nickname))
 		return;
 
+	strlcpy(oldnickname, acptr->name, sizeof(oldnickname));
+
 	if (acptr != ocptr)
 		acptr->umodes &= ~UMODE_REGNICK;
 	acptr->lastnick = atol(parv[3]);
 
 	/* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */
 	new_message(acptr, NULL, &mtags);
-	RunHook3(HOOKTYPE_LOCAL_NICKCHANGE, acptr, mtags, parv[2]);
-	sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, parv[2]);
-	sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, parv[2]);
-	sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%ld", acptr->id, parv[2], atol(parv[3]));
-	free_message_tags(mtags);
+	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);
 
 	add_history(acptr, 1);
 	del_from_client_hash_table(acptr->name, acptr);
-	hash_check_watch(acptr, RPL_LOGOFF);
 
-	sendto_snomask(SNO_NICKCHANGE,
-		"*** %s (%s@%s) has been forced to change their nickname to %s", 
-		acptr->name, acptr->user->username, acptr->user->realhost, parv[2]);
+	unreal_log(ULOG_INFO, "nick", "FORCED_NICK_CHANGE", acptr,
+	           "$client.details has been forced by services to change their nickname to $new_nick_name",
+	           log_data_string("new_nick_name", nickname));
 
-	strlcpy(acptr->name, parv[2], sizeof acptr->name);
-	add_to_client_hash_table(parv[2], acptr);
-	hash_check_watch(acptr, RPL_LOGON);
+	strlcpy(acptr->name, nickname, sizeof acptr->name);
+	add_to_client_hash_table(nickname, acptr);
+	RunHook(HOOKTYPE_POST_LOCAL_NICKCHANGE, acptr, mtags, oldnickname);
+	free_message_tags(mtags);
 }
diff --git a/src/modules/svsnline.c b/src/modules/svsnline.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /svsnline", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
diff --git a/src/modules/svsnolag.c b/src/modules/svsnolag.c
@@ -33,7 +33,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"commands /svsnolag and /svs2nolag", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -54,7 +54,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-void do_svsnolag(Client *client, int parc, char *parv[], int show_change)
+void do_svsnolag(Client *client, int parc, const char *parv[], int show_change)
 {
 	Client *target;
 	char *cmd = show_change ? MSG_SVS2NOLAG : MSG_SVSNOLAG;
@@ -65,7 +65,7 @@ void do_svsnolag(Client *client, int parc, char *parv[], int show_change)
 	if (parc < 3)
 		return;
 
-	if (!(target = find_person(parv[2], NULL)))
+	if (!(target = find_user(parv[2], NULL)))
 		return;
 
 	if (!MyUser(target))
diff --git a/src/modules/svsnoop.c b/src/modules/svsnoop.c
@@ -35,7 +35,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /svsnoop", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -62,12 +62,13 @@ CMD_FUNC(cmd_svsnoop)
 	if (!(IsULine(client) && parc > 2))
 		return;
 
-	if (hunt_server(client, NULL, ":%s SVSNOOP %s :%s", 1, parc, parv) == HUNTED_ISME)
+	if (hunt_server(client, NULL, "SVSNOOP", 1, parc, parv) == HUNTED_ISME)
 	{
 		if (parv[2][0] == '+')
 		{
 			SVSNOOP = 1;
-			sendto_ops("This server has been placed in NOOP mode");
+			unreal_log(ULOG_INFO, "svsnoop", "SVSNOOP_ENABLED", client,
+			           "This server has been placed in NOOP mode (by $client) -- all IRCOp rights disabled");
 			list_for_each_entry(acptr, &client_list, client_node)
 			{
 				if (MyUser(acptr) && IsOper(acptr))
@@ -81,15 +82,16 @@ CMD_FUNC(cmd_svsnoop)
 					if (!list_empty(&acptr->special_node))
 						list_del(&acptr->special_node);
 
+					RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL);
 					remove_oper_privileges(acptr, 1);
-					RunHook2(HOOKTYPE_LOCAL_OPER, acptr, 0);
 				}
 			}
 		}
 		else
 		{
 			SVSNOOP = 0;
-			sendto_ops("This server is no longer in NOOP mode");
+			unreal_log(ULOG_INFO, "svsnoop", "SVSNOOP_ENABLED", client,
+			           "This server is no longer in NOOP mode (by $client) -- IRCOps can oper up again");
 		}
 	}
 }
diff --git a/src/modules/svspart.c b/src/modules/svspart.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /svspart", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -62,11 +62,11 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_svspart)
 {
 	Client *target;
-	char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
+	const char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
 	if (!IsULine(client))
 		return;
 
-	if (parc < 3 || !(target = find_person(parv[1], NULL))) 
+	if (parc < 3 || !(target = find_user(parv[1], NULL))) 
 		return;
 
 	if (MyUser(target))
diff --git a/src/modules/svssilence.c b/src/modules/svssilence.c
@@ -27,7 +27,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /svssilence", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,11 +60,12 @@ CMD_FUNC(cmd_svssilence)
 	Client *target;
 	int mine;
 	char *p, *cp, c;
+	char request[BUFSIZE];
 	
 	if (!IsULine(client))
 		return;
 
-	if (parc < 3 || BadPtr(parv[2]) || !(target = find_person(parv[1], NULL)))
+	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
 		return;
 	
 	if (!MyUser(target))
@@ -74,7 +75,8 @@ CMD_FUNC(cmd_svssilence)
 	}
 
 	/* It's for our client */
-	for (p = strtok(parv[2], " "); p; p = strtok(NULL, " "))
+	strlcpy(request, parv[2], sizeof(request));
+	for (p = strtok(request, " "); p; p = strtok(NULL, " "))
 	{
 		c = *p;
 		if ((c == '-') || (c == '+'))
diff --git a/src/modules/svssno.c b/src/modules/svssno.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /svssno", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -61,9 +61,9 @@ 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, char *parv[], int show_change)
+void do_svssno(Client *client, int parc, const char *parv[], int show_change)
 {
-	char *p;
+	const char *p;
 	Client *target;
 	int what = MODE_ADD, i;
 
@@ -76,50 +76,22 @@ void do_svssno(Client *client, int parc, char *parv[], int show_change)
 	if (parv[1][0] == '#') 
 		return;
 
-	if (!(target = find_person(parv[1], NULL)))
+	if (!(target = find_user(parv[1], NULL)))
 		return;
 
-	if (hunt_server(client, NULL,
-	                      show_change ? ":%s SVS2SNO %s %s" : ":%s SVSSNO %s %s",
-	                      1, parc, parv) != HUNTED_ISME)
-	{
+	if (hunt_server(client, NULL, show_change ? "SVS2SNO" : "SVSSNO", 1, parc, parv) != HUNTED_ISME)
 		return;
-	}
 
 	if (MyUser(target))
 	{
 		if (parc == 2)
-			target->user->snomask = 0;
+			set_snomask(target, NULL);
 		else
-		{
-			for (p = parv[2]; p && *p; p++) {
-				switch (*p) {
-					case '+':
-						what = MODE_ADD;
-						break;
-					case '-':
-						what = MODE_DEL;
-						break;
-					default:
-				 	 for (i = 0; i <= Snomask_highest; i++)
-				 	 {
-				 	 	if (!Snomask_Table[i].flag)
-				 	 		continue;
-		 	 			if (*p == Snomask_Table[i].flag)
-				 	 	{
-				 	 		if (what == MODE_ADD)
-					 	 		target->user->snomask |= Snomask_Table[i].mode;
-			 			 	else
-			 	 				target->user->snomask &= ~Snomask_Table[i].mode;
-				 	 	}
-				 	 }				
-				}
-			}
-		}
+			set_snomask(target, parv[2]);
 	}
 
-	if (show_change)
-		sendnumeric(target, RPL_SNOMASK, get_snomask_string(target));
+	if (show_change && target->user->snomask)
+		sendnumeric(target, RPL_SNOMASK, target->user->snomask);
 }
 
 CMD_FUNC(cmd_svssno)
diff --git a/src/modules/svswatch.c b/src/modules/svswatch.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /svswatch", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -65,7 +65,7 @@ CMD_FUNC(cmd_svswatch)
 	if (!IsULine(client))
 		return;
 
-	if (parc < 3 || BadPtr(parv[2]) || !(target = find_person(parv[1], NULL)))
+	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
 		return;
 
 	if (MyUser(target))
diff --git a/src/modules/swhois.c b/src/modules/swhois.c
@@ -34,7 +34,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /swhois", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -77,7 +77,7 @@ CMD_FUNC(cmd_swhois)
 	if (parc < 3)
 		return;
 
-	target = find_person(parv[1], NULL);
+	target = find_user(parv[1], NULL);
 	if (!target)
 		return;
 
diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c
@@ -11,7 +11,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Target flood protection (set::anti-flood::target-flood)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 #define TFP_PRIVMSG	0
@@ -35,8 +35,8 @@ struct TargetFloodConfig {
 int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
 void targetfloodprot_mdata_free(ModData *m);
-int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
-int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
 
 /* Global variables */
 ModDataInfo *targetfloodprot_client_md = NULL;
@@ -117,13 +117,11 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
+	safe_free(channelcfg);
+	safe_free(privatecfg);
 	return MOD_SUCCESS;
 }
 
-#ifndef CheckNull
- #define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
-#endif
-
 int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
@@ -133,36 +131,36 @@ int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *
 		return 0;
 
 	/* We are only interrested in set::anti-flood::target-flood.. */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "target-flood"))
+	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		CheckNull(cep);
 
-		if (!strcmp(cep->ce_varname, "channel-privmsg") ||
-		    !strcmp(cep->ce_varname, "channel-notice") ||
-		    !strcmp(cep->ce_varname, "channel-tagmsg") ||
-		    !strcmp(cep->ce_varname, "private-privmsg") ||
-		    !strcmp(cep->ce_varname, "private-notice") ||
-		    !strcmp(cep->ce_varname, "private-tagmsg"))
+		if (!strcmp(cep->name, "channel-privmsg") ||
+		    !strcmp(cep->name, "channel-notice") ||
+		    !strcmp(cep->name, "channel-tagmsg") ||
+		    !strcmp(cep->name, "private-privmsg") ||
+		    !strcmp(cep->name, "private-notice") ||
+		    !strcmp(cep->name, "private-tagmsg"))
 		{
 			int cnt = 0, period = 0;
 
-			if (!config_parse_flood(cep->ce_vardata, &cnt, &period) ||
+			if (!config_parse_flood(cep->value, &cnt, &period) ||
 			    (cnt < 1) || (cnt > 10000) || (period < 1) || (period > 120))
 			{
 				config_error("%s:%i: set::anti-flood::target-flood::%s error. "
 				             "Syntax is '<count>:<period>' (eg 5:60). "
 				             "Count must be 1-10000 and period must be 1-120.",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				             cep->ce_varname);
+				             cep->file->filename, cep->line_number,
+				             cep->name);
 				errors++;
 			}
 		} else
 		{
 			config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
@@ -180,23 +178,23 @@ int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 
 	/* We are only interrested in set::anti-flood::target-flood.. */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "target-flood"))
+	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "channel-privmsg"))
-			config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_PRIVMSG], &channelcfg->t[TFP_PRIVMSG]);
-		else if (!strcmp(cep->ce_varname, "channel-notice"))
-			config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_NOTICE], &channelcfg->t[TFP_NOTICE]);
-		else if (!strcmp(cep->ce_varname, "channel-tagmsg"))
-			config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_TAGMSG], &channelcfg->t[TFP_TAGMSG]);
-		else if (!strcmp(cep->ce_varname, "private-privmsg"))
-			config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_PRIVMSG], &privatecfg->t[TFP_PRIVMSG]);
-		else if (!strcmp(cep->ce_varname, "private-notice"))
-			config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_NOTICE], &privatecfg->t[TFP_NOTICE]);
-		else if (!strcmp(cep->ce_varname, "private-tagmsg"))
-			config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_TAGMSG], &privatecfg->t[TFP_TAGMSG]);
+		if (!strcmp(cep->name, "channel-privmsg"))
+			config_parse_flood(cep->value, &channelcfg->cnt[TFP_PRIVMSG], &channelcfg->t[TFP_PRIVMSG]);
+		else if (!strcmp(cep->name, "channel-notice"))
+			config_parse_flood(cep->value, &channelcfg->cnt[TFP_NOTICE], &channelcfg->t[TFP_NOTICE]);
+		else if (!strcmp(cep->name, "channel-tagmsg"))
+			config_parse_flood(cep->value, &channelcfg->cnt[TFP_TAGMSG], &channelcfg->t[TFP_TAGMSG]);
+		else if (!strcmp(cep->name, "private-privmsg"))
+			config_parse_flood(cep->value, &privatecfg->cnt[TFP_PRIVMSG], &privatecfg->t[TFP_PRIVMSG]);
+		else if (!strcmp(cep->name, "private-notice"))
+			config_parse_flood(cep->value, &privatecfg->cnt[TFP_NOTICE], &privatecfg->t[TFP_NOTICE]);
+		else if (!strcmp(cep->name, "private-tagmsg"))
+			config_parse_flood(cep->value, &privatecfg->cnt[TFP_TAGMSG], &privatecfg->t[TFP_TAGMSG]);
 	}
 
 	return 1;
@@ -218,13 +216,15 @@ int sendtypetowhat(SendType sendtype)
 	if (sendtype == SEND_TYPE_TAGMSG)
 		return 2;
 #ifdef DEBUGMODE
-	ircd_log(LOG_ERROR, "sendtypetowhat() for unknown value %d", (int)sendtype);
+	unreal_log(ULOG_ERROR, "flood", "BUG_SENDTYPETOWHAT_UNKNOWN_VALUE", NULL,
+	           "[BUG] sendtypetowhat() called for unknown sendtype $send_type",
+	           log_data_integer("send_type", sendtype));
 	abort();
 #endif
 	return 0; /* otherwise, default to privmsg i guess */
 }
 
-int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	TargetFlood *flood;
 	static char errbuf[256];
@@ -269,7 +269,7 @@ int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Member
 	return HOOK_CONTINUE;
 }
 
-int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	TargetFlood *flood;
 	static char errbuf[256];
diff --git a/src/modules/third/Makefile.in b/src/modules/third/Makefile.in
@@ -25,19 +25,22 @@ INCLUDES = ../../include/channel.h \
 	../../include/ircsprintf.h \
 	../../include/license.h \
 	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
-	../../include/numeric.h ../../include/proto.h ../../include/dns.h \
+	../../include/numeric.h ../../include/dns.h \
 	../../include/resource.h ../../include/setup.h \
 	../../include/struct.h ../../include/sys.h \
-	../../include/types.h ../../include/url.h \
+	../../include/types.h \
 	../../include/version.h ../../include/whowas.h
 
 MODULEFLAGS=@MODULEFLAGS@
 RM=@RM@
 
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
 all: build
 
 build:
-	../../buildmod
+	../../buildmod $(MAKE)
 
 custommodule: $(MODULEFILE).c
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
diff --git a/src/modules/time.c b/src/modules/time.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /time", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 
@@ -61,6 +61,6 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_time)
 {
-	if (hunt_server(client, recv_mtags, ":%s TIME :%s", 1, parc, parv) == HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "TIME", 1, parc, parv) == HUNTED_ISME)
 		sendnumeric(client, RPL_TIME, me.name, long_date(0));
 }
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"Server ban commands such as /GLINE, /SPAMFILTER, etc.",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Forward declarations */
@@ -49,12 +49,13 @@ CMD_FUNC(cmd_kline);
 CMD_FUNC(cmd_zline);
 CMD_FUNC(cmd_spamfilter);
 CMD_FUNC(cmd_eline);
-void cmd_tkl_line(Client *client, int parc, char *parv[], char *type);
+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);
 int tkl_banexception_chartotype(char c);
 char *_tkl_type_string(TKL *tk);
+char *_tkl_type_config_string(TKL *tk);
 char *tkl_banexception_configname_to_chars(char *name);
 TKL *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, char *set_by,
                             time_t expire_at, time_t set_at, int soft, int flags);
@@ -71,6 +72,7 @@ void _sendnotice_tkl_add(TKL *tkl);
 void _free_tkl(TKL *tkl);
 void _tkl_del_line(TKL *tkl);
 static void _tkl_check_local_remove_shun(TKL *tmp);
+char *_tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options);
 void tkl_expire_entry(TKL * tmp);
 EVENT(tkl_check_expire);
 int _find_tkline_match(Client *client, int skip_soft);
@@ -78,18 +80,18 @@ int _find_shun(Client *client);
 int _find_spamfilter_user(Client *client, int flags);
 TKL *_find_qline(Client *client, char *nick, int *ishold);
 TKL *_find_tkline_match_zap(Client *client);
-void _tkl_stats(Client *client, int type, char *para, int *cnt);
+void _tkl_stats(Client *client, int type, const char *para, int *cnt);
 void _tkl_sync(Client *client);
 CMD_FUNC(_cmd_tkl);
 int _place_host_ban(Client *client, BanAction action, char *reason, long duration);
-int _match_spamfilter(Client *client, char *str_in, int type, char *cmd, char *target, int flags, TKL **rettk);
+int _match_spamfilter(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
 int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd);
 int check_mtag_spamfilters_present(void);
 int _join_viruschan(Client *client, TKL *tk, int type);
 void _spamfilter_build_user_string(char *buf, char *nick, Client *client);
-int _match_user(char *rmask, Client *client, int options);
-int _match_user_extended_server_ban(char *banstr, Client *client);
-void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, char **tkl_username, char **tkl_hostname);
+int _match_user(const char *rmask, Client *client, int options);
+int _match_user_extended_server_ban(const char *banstr, Client *client);
+void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, const char **tkl_username, const char **tkl_hostname);
 int _tkl_ip_hash(char *ip);
 int _tkl_ip_hash_type(int type);
 TKL *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban);
@@ -98,6 +100,7 @@ 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);
 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);
 
 /* Externals (only for us :D) */
 extern int MODVAR spamf_ugly_vchanoverride;
@@ -134,8 +137,8 @@ TKLTypeTable tkl_types[] = {
 	{ "except",               'E', TKL_EXCEPTION  | TKL_GLOBAL, "Exception",            1, 0, 0 },
 	{ "shun",                 's', TKL_SHUN       | TKL_GLOBAL, "Shun",                 1, 1, 0 },
 	{ "local-qline",          'q', TKL_NAME,                    "Local Q-Line",         1, 0, 0 },
-	{ "local-spamfilter",     'e', TKL_EXCEPTION,               "Local Exception",      1, 0, 0 },
-	{ "local-exception",      'f', TKL_SPAMF,                   "Local Spamfilter",     1, 0, 0 },
+	{ "local-exception",      'e', TKL_EXCEPTION,               "Local Exception",      1, 0, 0 },
+	{ "local-spamfilter",     'f', TKL_SPAMF,                   "Local Spamfilter",     1, 0, 0 },
 	{ "blacklist",            'b', TKL_BLACKLIST,               "Blacklist",            0, 1, 1 },
 	{ "connect-flood",        'c', TKL_CONNECT_FLOOD,           "Connect flood",        0, 1, 1 },
 	{ "maxperip",             'm', TKL_MAXPERIP,                "Max-per-IP",           0, 1, 0 },
@@ -158,9 +161,17 @@ MOD_TEST()
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_except);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_set);
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_HASH, _tkl_hash);
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_TYPETOCHAR, TO_INTFUNC(_tkl_typetochar));
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_CHARTOTYPE, TO_INTFUNC(_tkl_chartotype));
-	EfunctionAddPChar(modinfo->handle, EFUNC_TKL_TYPE_STRING, _tkl_type_string);
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+	EfunctionAddString(modinfo->handle, EFUNC_TKL_TYPE_STRING, _tkl_type_string);
+	EfunctionAddString(modinfo->handle, EFUNC_TKL_TYPE_CONFIG_STRING, _tkl_type_config_string);
 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SERVERBAN, TO_PVOIDFUNC(_tkl_add_serverban));
 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_BANEXCEPTION, TO_PVOIDFUNC(_tkl_add_banexception));
 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_NAMEBAN, TO_PVOIDFUNC(_tkl_add_nameban));
@@ -191,6 +202,7 @@ MOD_TEST()
 	EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_ADD, _sendnotice_tkl_add);
 	EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_DEL, _sendnotice_tkl_del);
 	EfunctionAdd(modinfo->handle, EFUNC_FIND_TKL_EXCEPTION, _find_tkl_exception);
+	EfunctionAddString(modinfo->handle, EFUNC_TKL_UHOST, _tkl_uhost);
 	return MOD_SUCCESS;
 }
 
@@ -237,130 +249,130 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 	int match_type = 0;
 
 	/* We are only interested in spamfilter { } blocks */
-	if ((type != CONFIG_MAIN) || strcmp(ce->ce_varname, "spamfilter"))
+	if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "target"))
+		if (!strcmp(cep->name, "target"))
 		{
 			if (has_target)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter::target");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::target");
 				continue;
 			}
 			has_target = 1;
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
-				if (!spamfilter_getconftargets(cep->ce_vardata))
+				if (!spamfilter_getconftargets(cep->value))
 				{
 					config_error("%s:%i: unknown spamfiler target type '%s'",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+						cep->file->filename, cep->line_number, cep->value);
 					errors++;
 				}
 			}
-			else if (cep->ce_entries)
+			else if (cep->items)
 			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+				for (cepp = cep->items; cepp; cepp = cepp->next)
 				{
-					if (!spamfilter_getconftargets(cepp->ce_varname))
+					if (!spamfilter_getconftargets(cepp->name))
 					{
 						config_error("%s:%i: unknown spamfiler target type '%s'",
-							cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum, cepp->ce_varname);
+							cepp->file->filename,
+							cepp->line_number, cepp->name);
 						errors++;
 					}
 				}
 			}
 			else
 			{
-				config_error_empty(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter", cep->ce_varname);
+				config_error_empty(cep->file->filename,
+					cep->line_number, "spamfilter", cep->name);
 				errors++;
 			}
 			continue;
 		}
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"spamfilter", cep->ce_varname);
+			config_error_empty(cep->file->filename, cep->line_number,
+				"spamfilter", cep->name);
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "reason"))
+		if (!strcmp(cep->name, "reason"))
 		{
 			if (has_reason)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter::reason");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::reason");
 				continue;
 			}
 			has_reason = 1;
-			reason = cep->ce_vardata;
+			reason = cep->value;
 		}
-		else if (!strcmp(cep->ce_varname, "match"))
+		else if (!strcmp(cep->name, "match"))
 		{
 			if (has_match)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter::match");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::match");
 				continue;
 			}
 			has_match = 1;
-			match = cep->ce_vardata;
+			match = cep->value;
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
 			if (has_action)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter::action");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::action");
 				continue;
 			}
 			has_action = 1;
-			if (!banact_stringtoval(cep->ce_vardata))
+			if (!banact_stringtoval(cep->value))
 			{
 				config_error("%s:%i: spamfilter::action has unknown action type '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "ban-time"))
+		else if (!strcmp(cep->name, "ban-time"))
 		{
 			if (has_bantime)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter::ban-time");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::ban-time");
 				continue;
 			}
 			has_bantime = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "match-type"))
+		else if (!strcmp(cep->name, "match-type"))
 		{
 			if (has_match_type)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "spamfilter::match-type");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::match-type");
 				continue;
 			}
-			if (!strcasecmp(cep->ce_vardata, "posix"))
+			if (!strcasecmp(cep->value, "posix"))
 			{
 				config_error("%s:%i: this spamfilter uses match-type 'posix' which is no longer supported. "
 				             "You must switch over to match-type 'regex' instead. "
 				             "See https://www.unrealircd.org/docs/FAQ#spamfilter-posix-deprecated",
-				             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				             ce->file->filename, ce->line_number);
 				errors++;
 				*errs = errors;
 				return -1; /* return now, otherwise there will be issues */
 			}
-			match_type = unreal_match_method_strtoval(cep->ce_vardata);
+			match_type = unreal_match_method_strtoval(cep->value);
 			if (match_type == 0)
 			{
 				config_error("%s:%i: spamfilter::match-type: unknown match type '%s', "
 				             "should be one of: 'simple', 'regex' or 'posix'",
-				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				             cep->ce_vardata);
+				             cep->file->filename, cep->line_number,
+				             cep->value);
 				errors++;
 				continue;
 			}
@@ -368,8 +380,8 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"spamfilter", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"spamfilter", cep->name);
 			errors++;
 			continue;
 		}
@@ -384,8 +396,8 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 		if (!m)
 		{
 			config_error("%s:%i: spamfilter::match contains an invalid regex: %s",
-				ce->ce_fileptr->cf_filename,
-				ce->ce_varlinenum,
+				ce->file->filename,
+				ce->line_number,
 				err);
 			errors++;
 		} else
@@ -396,19 +408,19 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 
 	if (!has_match)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"spamfilter::match");
 		errors++;
 	}
 	if (!has_target)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"spamfilter::target");
 		errors++;
 	}
 	if (!has_action)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"spamfilter::action");
 		errors++;
 	}
@@ -416,21 +428,16 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
 	{
 		config_error("%s:%i: spamfilter block problem: match + reason field are together over 505 bytes, "
 		             "please choose a shorter regex or reason",
-		             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		             ce->file->filename, ce->line_number);
 		errors++;
 	}
 	if (!has_match_type)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"spamfilter::match-type");
 		errors++;
 	}
 
-	if (!has_match_type && !has_match && has_action && has_target)
-	{
-		need_34_upgrade = 1;
-	}
-
 	if (match && !strcmp(match, "^LOL! //echo -a \\$\\(\\$decode\\(.+,m\\),[0-9]\\)$"))
 	{
 		config_warn("*** IMPORTANT ***");
@@ -449,47 +456,47 @@ int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
 	ConfigEntry *cep;
 	ConfigEntry *cepp;
 	char *word = NULL;
-	time_t bantime = (SPAMFILTER_BAN_TIME ? SPAMFILTER_BAN_TIME : 86400);
-	char *banreason = "<internally added by ircd>";
+	time_t bantime = tempiConf.spamfilter_ban_time;
+	char *banreason = tempiConf.spamfilter_ban_reason;
 	int action = 0, target = 0;
 	int match_type = 0;
 	Match *m;
 
 	/* We are only interested in spamfilter { } blocks */
-	if ((type != CONFIG_MAIN) || strcmp(ce->ce_varname, "spamfilter"))
+	if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "match"))
+		if (!strcmp(cep->name, "match"))
 		{
-			word = cep->ce_vardata;
+			word = cep->value;
 		}
-		else if (!strcmp(cep->ce_varname, "target"))
+		else if (!strcmp(cep->name, "target"))
 		{
-			if (cep->ce_vardata)
-				target = spamfilter_getconftargets(cep->ce_vardata);
+			if (cep->value)
+				target = spamfilter_getconftargets(cep->value);
 			else
 			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					target |= spamfilter_getconftargets(cepp->ce_varname);
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+					target |= spamfilter_getconftargets(cepp->name);
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
-			action = banact_stringtoval(cep->ce_vardata);
+			action = banact_stringtoval(cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
-			banreason = cep->ce_vardata;
+			banreason = cep->value;
 		}
-		else if (!strcmp(cep->ce_varname, "ban-time"))
+		else if (!strcmp(cep->name, "ban-time"))
 		{
-			bantime = config_checkval(cep->ce_vardata, CFG_TIME);
+			bantime = config_checkval(cep->value, CFG_TIME);
 		}
-		else if (!strcmp(cep->ce_varname, "match-type"))
+		else if (!strcmp(cep->name, "match-type"))
 		{
-			match_type = unreal_match_method_strtoval(cep->ce_vardata);
+			match_type = unreal_match_method_strtoval(cep->value);
 		}
 	}
 
@@ -518,35 +525,35 @@ int tkl_config_test_ban(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_BAN)
 		return 0;
 
-	if (strcmp(ce->ce_vardata, "nick") && strcmp(ce->ce_vardata, "user") &&
-	    strcmp(ce->ce_vardata, "ip"))
+	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
+	    strcmp(ce->value, "ip"))
 	{
 		return 0;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "ban"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
 			if (has_mask)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "ban::mask");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::mask");
 				continue;
 			}
 			has_mask = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "reason"))
+		else if (!strcmp(cep->name, "reason"))
 		{
 			if (has_reason)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "ban::reason");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::reason");
 				continue;
 			}
 			has_reason = 1;
@@ -554,23 +561,23 @@ int tkl_config_test_ban(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		else
 		{
 			config_error("%s:%i: unknown directive ban %s::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				ce->ce_vardata,
-				cep->ce_varname);
+				cep->file->filename, cep->line_number,
+				ce->value,
+				cep->name);
 			errors++;
 		}
 	}
 
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"ban::mask");
 		errors++;
 	}
 
 	if (!has_reason)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"ban::reason");
 		errors++;
 	}
@@ -592,44 +599,35 @@ int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	if (configtype != CONFIG_BAN)
 		return 0;
 
-	if (strcmp(ce->ce_vardata, "nick") && strcmp(ce->ce_vardata, "user") &&
-	    strcmp(ce->ce_vardata, "ip"))
+	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
+	    strcmp(ce->value, "ip"))
 	{
 		return 0;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			char buf[512], *p;
-			strlcpy(buf, cep->ce_vardata, sizeof(buf));
-			if (is_extended_ban(buf))
+			if (is_extended_server_ban(cep->value))
 			{
-				char *str;
-				Extban *extban;
-				char buf2[BUFSIZE];
-				extban = findmod_by_bantype(buf[1]);
-				if (!extban || !(extban->options & EXTBOPT_TKL))
-				{
-					config_warn("%s:%d: Invalid or unsupported extended server ban requested: %s",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, buf);
-					goto tcrb_end;
-				}
-				/* is_ok() is not called, since there is no client, similar to like remote bans set */
-				str = extban->conv_param(buf);
-				if (!str || (strlen(str) <= 4))
+				char mask1buf[512], mask2buf[512];
+				char *err = NULL;
+
+				if (!parse_extended_server_ban(cep->value, NULL, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
 				{
-					config_warn("%s:%d: Extended server ban has a problem: %s",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, buf);
+					config_warn("%s:%d: Could not add extended server ban '%s': %s",
+						cep->file->filename, cep->line_number, cep->value, err);
 					goto tcrb_end;
 				}
-				strlcpy(buf2, str+3, sizeof(buf2));
-				buf[3] = '\0';
-				safe_strdup(usermask, buf); /* eg ~S: */
-				safe_strdup(hostmask, buf2);
+				safe_strdup(usermask, mask1buf);
+				safe_strdup(hostmask, mask2buf);
 			} else
 			{
+				char buf[512];
+				char *p;
+
+				strlcpy(buf, cep->value, sizeof(buf));
 				p = strchr(buf, '@');
 				if (p)
 				{
@@ -637,13 +635,13 @@ int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
 					safe_strdup(usermask, buf);
 					safe_strdup(hostmask, p);
 				} else {
-					safe_strdup(hostmask, cep->ce_vardata);
+					safe_strdup(hostmask, cep->value);
 				}
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "reason"))
+		if (!strcmp(cep->name, "reason"))
 		{
-			safe_strdup(reason, cep->ce_vardata);
+			safe_strdup(reason, cep->value);
 		}
 	}
 
@@ -653,11 +651,11 @@ int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	if (!reason)
 		safe_strdup(reason, "-");
 
-	if (!strcmp(ce->ce_vardata, "nick"))
+	if (!strcmp(ce->value, "nick"))
 		tkltype = TKL_NAME;
-	else if (!strcmp(ce->ce_vardata, "user"))
+	else if (!strcmp(ce->value, "user"))
 		tkltype = TKL_KILL;
-	else if (!strcmp(ce->ce_vardata, "ip"))
+	else if (!strcmp(ce->value, "ip"))
 		tkltype = TKL_ZAP;
 	else
 		abort(); /* impossible */
@@ -685,82 +683,82 @@ int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int 
 		return 0;
 
 	/* These are the types that we handle */
-	if (strcmp(ce->ce_vardata, "ban") && strcmp(ce->ce_vardata, "throttle") &&
-	    strcmp(ce->ce_vardata, "tkl") && strcmp(ce->ce_vardata, "blacklist") &&
-	    strcmp(ce->ce_vardata, "spamfilter"))
+	if (strcmp(ce->value, "ban") && strcmp(ce->value, "throttle") &&
+	    strcmp(ce->value, "tkl") && strcmp(ce->value, "blacklist") &&
+	    strcmp(ce->value, "spamfilter"))
 	{
 		return 0;
 	}
 
-	if (!strcmp(ce->ce_vardata, "tkl"))
+	if (!strcmp(ce->value, "tkl"))
 	{
 		config_error("%s:%d: except tkl { } has been renamed to except ban { }",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		config_status("Please rename your block in the configuration file.");
 		*errs = 1;
 		return -1;
 	}
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			if (cep->ce_entries)
+			if (cep->items)
 			{
 				/* mask { *@1.1.1.1; *@2.2.2.2; *@3.3.3.3; }; */
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+				for (cepp = cep->items; cepp; cepp = cepp->next)
 				{
-					if (!cepp->ce_varname)
+					if (!cepp->name)
 					{
-						config_error_empty(cepp->ce_fileptr->cf_filename,
-							cepp->ce_varlinenum, "except ban", "mask");
+						config_error_empty(cepp->file->filename,
+							cepp->line_number, "except ban", "mask");
 						errors++;
 						continue;
 					}
 					has_mask = 1;
 				}
 			} else
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
 				/* mask *@1.1.1.1; */
-				if (!cep->ce_vardata)
+				if (!cep->value)
 				{
-					config_error_empty(cep->ce_fileptr->cf_filename,
-						cep->ce_varlinenum, "except ban", "mask");
+					config_error_empty(cep->file->filename,
+						cep->line_number, "except ban", "mask");
 					errors++;
 					continue;
 				}
 				has_mask = 1;
 			}
 		} else
-		if (!strcmp(cep->ce_varname, "type"))
+		if (!strcmp(cep->name, "type"))
 		{
-			if (cep->ce_entries)
+			if (cep->items)
 			{
 				/* type { x; y; z; }; */
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					if (!tkl_banexception_configname_to_chars(cepp->ce_varname))
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+					if (!tkl_banexception_configname_to_chars(cepp->name))
 					{
 						config_error("%s:%d: except ban::type '%s' unknown. Must be one of: %s",
-							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname,
+							cepp->file->filename, cepp->line_number, cepp->name,
 							ALL_VALID_EXCEPTION_TYPES);
 						errors++;
 					}
 			} else
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
 				/* type x; */
-				if (!tkl_banexception_configname_to_chars(cep->ce_vardata))
+				if (!tkl_banexception_configname_to_chars(cep->value))
 				{
 					config_error("%s:%d: except ban::type '%s' unknown. Must be one of: %s",
-						cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata,
+						cep->file->filename, cep->line_number, cep->value,
 						ALL_VALID_EXCEPTION_TYPES);
 					errors++;
 				}
 			}
 		} else {
-			config_error_unknown(cep->ce_fileptr->cf_filename,
-				cep->ce_varlinenum, "except", cep->ce_varname);
+			config_error_unknown(cep->file->filename,
+				cep->line_number, "except", cep->name);
 			errors++;
 			continue;
 		}
@@ -768,7 +766,7 @@ int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int 
 
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"except ban::mask");
 		errors++;
 	}
@@ -782,7 +780,10 @@ void config_create_tkl_except(char *mask, char *bantypes)
 	char *usermask = NULL;
 	char *hostmask = NULL;
 	int soft = 0;
-	char buf[256], buf2[256], *p;
+	char buf[256];
+	char mask1buf[512];
+	char mask2buf[512];
+	char *p;
 
 	if (*mask == '%')
 	{
@@ -790,27 +791,16 @@ void config_create_tkl_except(char *mask, char *bantypes)
 		mask++;
 	}
 	strlcpy(buf, mask, sizeof(buf));
-	if (is_extended_ban(buf))
+	if (is_extended_server_ban(buf))
 	{
-		char *str;
-		Extban *extban;
-		extban = findmod_by_bantype(buf[1]);
-		if (!extban || !(extban->options & EXTBOPT_TKL))
+		char *err = NULL;
+		if (!parse_extended_server_ban(buf, NULL, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
 		{
-			config_warn("Invalid or unsupported extended server ban exemption requested: %s", buf);
+			config_warn("Could not add extended server ban '%s': %s", buf, err);
 			return;
 		}
-		/* is_ok() is not called, since there is no client, similar to like remote bans set */
-		str = extban->conv_param(buf);
-		if (!str || (strlen(str) <= 4))
-		{
-			config_warn("Extended server ban exemption has a problem: %s", buf);
-			return;
-		}
-		strlcpy(buf2, str+3, sizeof(buf2));
-		buf[3] = '\0';
-		usermask = buf; /* eg ~S: */
-		hostmask = buf2;
+		usermask = mask1buf;
+		hostmask = mask2buf;
 	} else
 	{
 		p = strchr(buf, '@');
@@ -827,7 +817,7 @@ void config_create_tkl_except(char *mask, char *bantypes)
 
 	if ((*usermask == ':') || (*hostmask == ':'))
 	{
-		config_error("Cannot add illegal ban '%s': for a given user@host neither"
+		config_error("Cannot add illegal ban '%s': for a given user@host - neither "
 		             "user nor host may start with a : character (semicolon)", mask);
 		return;
 	}
@@ -846,9 +836,9 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
 		return 0;
 
 	/* These are the types that we handle */
-	if (strcmp(ce->ce_vardata, "ban") && strcmp(ce->ce_vardata, "throttle") &&
-	    strcmp(ce->ce_vardata, "blacklist") &&
-	    strcmp(ce->ce_vardata, "spamfilter"))
+	if (strcmp(ce->value, "ban") && strcmp(ce->value, "throttle") &&
+	    strcmp(ce->value, "blacklist") &&
+	    strcmp(ce->value, "spamfilter"))
 	{
 		return 0;
 	}
@@ -856,23 +846,23 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	*bantypes = '\0';
 
 	/* First configure all the types */
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "type"))
+		if (!strcmp(cep->name, "type"))
 		{
-			if (cep->ce_entries)
+			if (cep->items)
 			{
 				/* type { x; y; z; }; */
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+				for (cepp = cep->items; cepp; cepp = cepp->next)
 				{
-					char *str = tkl_banexception_configname_to_chars(cepp->ce_varname);
+					char *str = tkl_banexception_configname_to_chars(cepp->name);
 					strlcat(bantypes, str, sizeof(bantypes));
 				}
 			} else
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
 				/* type x; */
-				char *str = tkl_banexception_configname_to_chars(cep->ce_vardata);
+				char *str = tkl_banexception_configname_to_chars(cep->value);
 				strlcat(bantypes, str, sizeof(bantypes));
 			}
 		}
@@ -881,33 +871,33 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	if (!*bantypes)
 	{
 		/* Default setting if no 'type' is specified: */
-		if (!strcmp(ce->ce_vardata, "ban"))
+		if (!strcmp(ce->value, "ban"))
 			strlcpy(bantypes, "kGzZs", sizeof(bantypes));
-		else if (!strcmp(ce->ce_vardata, "throttle"))
+		else if (!strcmp(ce->value, "throttle"))
 			strlcpy(bantypes, "c", sizeof(bantypes));
-		else if (!strcmp(ce->ce_vardata, "blacklist"))
+		else if (!strcmp(ce->value, "blacklist"))
 			strlcpy(bantypes, "b", sizeof(bantypes));
-		else if (!strcmp(ce->ce_vardata, "spamfilter"))
+		else if (!strcmp(ce->value, "spamfilter"))
 			strlcpy(bantypes, "f", sizeof(bantypes));
 		else
 			abort(); /* someone can't code */
 	}
 
 	/* Now walk through all mask entries */
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			if (cep->ce_entries)
+			if (cep->items)
 			{
 				/* mask { *@1.1.1.1; *@2.2.2.2; *@3.3.3.3; }; */
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					config_create_tkl_except(cepp->ce_varname, bantypes);
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+					config_create_tkl_except(cepp->name, bantypes);
 			} else
-			if (cep->ce_vardata)
+			if (cep->value)
 			{
 				/* mask *@1.1.1.1; */
-				config_create_tkl_except(cep->ce_vardata, bantypes);
+				config_create_tkl_except(cep->value, bantypes);
 			}
 		}
 	}
@@ -923,12 +913,12 @@ int tkl_config_test_set(ConfigFile *cf, ConfigEntry *ce, int configtype, int *er
 	if (configtype != CONFIG_SET)
 		return 0;
 
-	if (!strcmp(ce->ce_varname, "max-stats-matches"))
+	if (!strcmp(ce->name, "max-stats-matches"))
 	{
-		if (!ce->ce_vardata)
+		if (!ce->value)
 		{
 			config_error("%s:%i: set::max-stats-matches: no value specified",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				ce->file->filename, ce->line_number);
 			errors++;
 		}
 		// allow any other value, including 0 and negative.
@@ -944,9 +934,9 @@ int tkl_config_run_set(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	if (configtype != CONFIG_SET)
 		return 0;
 
-	if (!strcmp(ce->ce_varname, "max-stats-matches"))
+	if (!strcmp(ce->name, "max-stats-matches"))
 	{
-		max_stats_matches = atoi(ce->ce_vardata);
+		max_stats_matches = atoi(ce->value);
 		return 1;
 	}
 
@@ -982,7 +972,7 @@ CMD_FUNC(cmd_gline)
 
 	if (parc == 1)
 	{
-		char *parv[3];
+		const char *parv[3];
 		parv[0] = NULL;
 		parv[1] = "gline";
 		parv[2] = NULL;
@@ -1008,7 +998,7 @@ CMD_FUNC(cmd_gzline)
 
 	if (parc == 1)
 	{
-		char *parv[3];
+		const char *parv[3];
 		parv[0] = NULL;
 		parv[1] = "gline"; /* (there's no /STATS gzline, it's included in /STATS gline output) */
 		parv[2] = NULL;
@@ -1033,7 +1023,7 @@ CMD_FUNC(cmd_shun)
 
 	if (parc == 1)
 	{
-		char *parv[3];
+		const char *parv[3];
 		parv[0] = NULL;
 		parv[1] = "shun";
 		parv[2] = NULL;
@@ -1049,8 +1039,8 @@ CMD_FUNC(cmd_shun)
 CMD_FUNC(cmd_tempshun)
 {
 	Client *target;
-	char *comment = ((parc > 2) && !BadPtr(parv[2])) ? parv[2] : "no reason";
-	char *name;
+	const char *comment = ((parc > 2) && !BadPtr(parv[2])) ? parv[2] : "no reason";
+	const char *name;
 	int remove = 0;
 
 	if (MyUser(client) && (!ValidatePermissionsForPath("server-ban:shun:temporary",client,NULL,NULL,NULL)))
@@ -1072,7 +1062,7 @@ CMD_FUNC(cmd_tempshun)
 	} else
 		name = parv[1];
 
-	target = find_person(name, NULL);
+	target = find_user(name, NULL);
 	if (!target)
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, name);
@@ -1095,10 +1085,10 @@ CMD_FUNC(cmd_tempshun)
 			} else
 			{
 				SetShunned(target);
-				ircsnprintf(buf, sizeof(buf), "Temporary shun added on user %s (%s@%s) by %s [%s]",
-					target->name, target->user->username, target->user->realhost,
-					client->name, comment);
-				sendto_snomask_global(SNO_TKL, "%s", buf);
+				unreal_log(ULOG_INFO, "tkl", "TKL_ADD_TEMPSHUN", client,
+					   "Temporary shun added on user $target.details [reason: $shun_reason] [by: $client]",
+					   log_data_string("shun_reason", comment),
+					   log_data_client("target", target));
 			}
 		} else {
 			if (!IsShunned(target))
@@ -1106,10 +1096,9 @@ CMD_FUNC(cmd_tempshun)
 				sendnotice(client, "User '%s' is not shunned", target->name);
 			} else {
 				ClearShunned(target);
-				ircsnprintf(buf, sizeof(buf), "Removed temporary shun on user %s (%s@%s) by %s",
-					target->name, target->user->username, target->user->realhost,
-					client->name);
-				sendto_snomask_global(SNO_TKL, "%s", buf);
+				unreal_log(ULOG_INFO, "tkl", "TKL_DEL_TEMPSHUN", client,
+					   "Temporary shun removed from user $target.details [by: $client]",
+					   log_data_client("target", target));
 			}
 		}
 	}
@@ -1130,7 +1119,7 @@ CMD_FUNC(cmd_kline)
 
 	if (parc == 1)
 	{
-		char *parv[3];
+		const char *parv[3];
 		parv[0] = NULL;
 		parv[1] = "kline";
 		parv[2] = NULL;
@@ -1184,7 +1173,7 @@ void tkl_general_stats(Client *client)
 
 /** ZLINE - Kill a user as soon as it tries to connect to the server.
  * This happens before any DNS/ident lookups have been done and
- * before any data has been processed (including no SSL/TLS handshake, etc.)
+ * before any data has been processed (including no TLS handshake, etc.)
  */
 CMD_FUNC(cmd_zline)
 {
@@ -1199,7 +1188,7 @@ CMD_FUNC(cmd_zline)
 
 	if (parc == 1)
 	{
-		char *parv[3];
+		const char *parv[3];
 		parv[0] = NULL;
 		parv[1] = "kline"; /* (there's no /STATS zline, it's included in /STATS kline output) */
 		parv[2] = NULL;
@@ -1276,22 +1265,142 @@ static int xline_exists(char *type, char *usermask, char *hostmask)
 	return find_tkl_serverban(tpe, umask, hostmask, softban) ? 1 : 0;
 }
 
+/** Parse an extended server ban such as ~S:aabbccddetc..
+ * Used for both syntax checking and to split it into userbuf/hostbuf for TKL protocol.
+ * @param mask_in	The input mask (eg: ~S:aabbccddetc)
+ * @param client	Client doing the request (used to send errors), can be NULL.
+ * @param error		Pointer to set to the error buffer (must be set!)
+ * @param skip_checking	Set this to 1 if coming from a remote user/server to skip the .is_ok() check.
+ *                      Note that a .conv_param() call can still fail.
+ * @param buf1		Buffer to store the extban starter in (eg "~S:") -- can be NULL if you don't need it
+ * @param buf1len	Length of buf1
+ * @param buf2		Buffer to store the extban remainder in (eg "aabbccddetc") -- can be NULL if you don't need it
+ * @param buf2len	Length of buf2
+ * @returns 1 if the server ban is acceptable. The ban will then be stored in buf1/buf2 (unless those
+ *            were set to NULL by the caller). On failure we return 0 and 'error' is set appropriately.
+ */
+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)
+{
+	const char *nextbanstr = NULL;
+	Extban *extban;
+	const char *str;
+	char *p;
+	BanContext *b = NULL;
+	char    mask[USERLEN + NICKLEN + HOSTLEN + 32]; // same as extban_conv_param_nuh_or_extban()
+	char newmask[USERLEN + NICKLEN + HOSTLEN + 32];
+	char soft_ban = 0;
+
+	*error = NULL;
+	if (buf1 && buf2)
+		*buf1 = *buf2 = '\0';
+
+	/* Work on a copy */
+	if (*mask_in == '%')
+	{
+		strlcpy(mask, mask_in+1, sizeof(mask));
+		soft_ban = 1;
+	} else {
+		strlcpy(mask, mask_in, sizeof(mask));
+	}
+
+	extban = findmod_by_bantype(mask, &nextbanstr);
+	if (!extban || !(extban->options & EXTBOPT_TKL))
+	{
+		*error = "Invalid or unsupported extended server ban requested. Valid types are for example ~a, ~r, ~S.";
+		goto fail_parse_extended_server_ban;
+	}
+
+	b = safe_alloc(sizeof(BanContext));
+	b->client = client;
+	b->banstr = nextbanstr;
+	b->is_ok_check = EXBCHK_PARAM;
+	b->what = MODE_ADD;
+	b->ban_type = EXBTYPE_TKL;
+
+	/* Run .is_ok() for the extban. This check is skipped if coming from a remote user/server */
+	if (skip_checking == 0)
+	{
+		if (extban->is_ok && !extban->is_ok(b))
+		{
+			*error = "Invalid extended server ban";
+			goto fail_parse_extended_server_ban;
+		}
+	}
+
+	b->banstr = nextbanstr;
+	str = extban->conv_param(b, extban);
+	if (!str)
+	{
+		*error = "Invalid extended server ban";
+		goto fail_parse_extended_server_ban;
+	}
+	str = prefix_with_extban(str, b, extban, newmask, sizeof(newmask));
+	if (str == NULL)
+	{
+		*error = "Unexpected error (1)";
+		goto fail_parse_extended_server_ban;
+	}
+
+	p = strchr(newmask, ':');
+	if (!p)
+	{
+		*error = "Unexpected error (2)";
+		goto fail_parse_extended_server_ban;
+	}
+
+	if (p[1] == ':')
+	{
+		*error = "For technical reasons you cannot use a double : at the beginning of an extended server ban (eg ~a::xyz)";
+		goto fail_parse_extended_server_ban;
+	}
+
+	if (!p[1])
+	{
+		*error = "Empty / too short extended server ban";
+		goto fail_parse_extended_server_ban;
+	}
+
+	/* Now convert the result into two buffers for TKL protocol usage */
+	if (buf1 && buf2)
+	{
+		char save;
+		p++;
+		save = *p;
+		*p = '\0';
+		/* First buffer is eg ~S: or %~S: */
+		snprintf(buf1, buf1len, "%s%s",
+		         soft_ban ? "%" : "",
+		         newmask);
+		*p = save;
+		strlcpy(buf2, p, buf2len); /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
+	}
+	safe_free(b);
+	return 1;
+
+fail_parse_extended_server_ban:
+	safe_free(b);
+	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.
  */
-void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
+void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
 {
 	time_t secs;
-	int whattodo = 0;	/* 0 = add  1 = del */
+	int add = 1;
 	time_t i;
 	Client *acptr = NULL;
-	char *mask = NULL;
+	char maskbuf[BUFSIZE];
+	char *mask;
 	char mo[64], mo2[64];
+	char mask1buf[BUFSIZE];
 	char mask2buf[BUFSIZE];
 	char *p, *usermask, *hostmask;
-	char *tkllayer[10] = {
+	const char *tkllayer[10] = {
 		me.name,		/*0  server.name */
 		NULL,			/*1  +|- */
 		NULL,			/*2  G   */
@@ -1308,15 +1417,16 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 	if ((parc == 1) || BadPtr(parv[1]))
 		return; /* shouldn't happen */
 
-	mask = parv[1];
+	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
+	mask = maskbuf;
 	if (*mask == '-')
 	{
-		whattodo = 1;
+		add = 0;
 		mask++;
 	}
 	else if (*mask == '+')
 	{
-		whattodo = 0;
+		add = 1;
 		mask++;
 	}
 
@@ -1345,57 +1455,42 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 	}
 
 	/* Check if it's an extended server ban */
-	if (is_extended_ban(mask))
+	if (is_extended_server_ban(mask))
 	{
-		if (whattodo == 0)
+		char *err;
+
+		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
 		{
-			/* Add */
-			char *str;
-			Extban *extban;
-			extban = findmod_by_bantype(mask[1]);
-			if (!extban || !(extban->options & EXTBOPT_TKL))
+			/* If adding, reject it */
+			if (add)
 			{
-				sendnotice(client, "Invalid or unsupported extended server ban requested: %s", mask);
-				sendnotice(client, "Valid types are for example ~a, ~r, ~S");
+				sendnotice(client, "ERROR: %s", err);
 				return;
-			}
-			if (extban->is_ok && !extban->is_ok(client, NULL, mask, EXBCHK_PARAM, MODE_ADD, EXBTYPE_TKL))
-				return; /* rejected */
-			str = extban->conv_param(mask);
-			if (!str || (strlen(str) <= 4))
-				return; /* rejected */
-			strlcpy(mask2buf, str+3, sizeof(mask2buf));
-			mask[3] = '\0';
-			usermask = mask; /* eg ~S: */
-			hostmask = mask2buf;
-
-			if (((*type == 'z') || (*type == 'Z')))
+			} else
 			{
-				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;
+				/* Always allow any removal attempt... */
+				char *p;
+				char save;
+				p = strchr(mask, ':');
+				p++;
+				save = *p;
+				*p = '\0';
+				strlcpy(mask1buf, mask, sizeof(mask1buf));
+				*p = save;
+				strlcpy(mask2buf, p, sizeof(mask2buf));
+				/* fallthrough */
 			}
-		} else {
-			/* Delete: allow any attempt */
-			strlcpy(mask2buf, mask+3, sizeof(mask2buf));
-			mask[3] = '\0';
-			usermask = mask; /* eg ~S: */
-			hostmask = mask2buf;
 		}
-		/* Make sure we don't screw up S2S traffic ;) */
-		if (*hostmask == ':')
+		if (add && ((*type == 'z') || (*type == 'Z')))
 		{
-			sendnotice(client, "[error] For technical reasons you cannot use double :: at the beginning "
-					   "of an extended server ban (eg ~a::xyz). You probably don't want to do this either.");
-			return;
-		}
-		if (!*hostmask)
-		{
-			sendnotice(client, "[error] Empty hostmask encountered, eg -~S:");
+			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;
 		}
+		usermask = mask1buf; /* eg ~S: */
+		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
 	} else
 	{
 		/* Check if it's a hostmask and legal .. */
@@ -1420,7 +1515,7 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 				sendnotice(client, "[error] For technical reasons you cannot start the host with a ':', sorry");
 				return;
 			}
-			if (((*type == 'z') || (*type == 'Z')) && !whattodo)
+			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.
@@ -1446,12 +1541,12 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 		else
 		{
 			/* It's seemingly a nick .. let's see if we can find the user */
-			if ((acptr = find_person(mask, NULL)))
+			if ((acptr = find_user(mask, NULL)))
 			{
 				BanAction action = BAN_ACT_KLINE; // just a dummy default
 				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, &usermask, &hostmask);
+				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
 			}
 			else
 			{
@@ -1461,7 +1556,7 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 		}
 	}
 
-	if (!whattodo && ban_too_broad(usermask, hostmask))
+	if (add && ban_too_broad(usermask, hostmask))
 	{
 		sendnotice(client, "*** [error] Too broad mask");
 		return;
@@ -1469,7 +1564,7 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 
 	secs = 0;
 
-	if (whattodo == 0 && (parc > 3))
+	if (add && (parc > 3))
 	{
 		secs = config_checkval(parv[2], CFG_TIME);
 		if (secs < 0)
@@ -1478,12 +1573,12 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 			return;
 		}
 	}
-	tkllayer[1] = whattodo == 0 ? "+" : "-";
+	tkllayer[1] = add ? "+" : "-";
 	tkllayer[2] = type;
 	tkllayer[3] = usermask;
 	tkllayer[4] = hostmask;
 	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
-	if (whattodo == 0)
+	if (add)
 	{
 		if (secs == 0)
 		{
@@ -1557,7 +1652,7 @@ void eline_syntax(Client *client)
  * exception to be placed on *@ip rather than
  * user@host or *@host. For eg zlines.
  */
-TKLTypeTable *eline_type_requires_ip(char *bantypes)
+TKLTypeTable *eline_type_requires_ip(const char *bantypes)
 {
 	int i;
 
@@ -1568,9 +1663,9 @@ TKLTypeTable *eline_type_requires_ip(char *bantypes)
 }
 
 /** Checks a string to see if it contains invalid ban exception types */
-int contains_invalid_server_ban_exception_type(char *str, char *c)
+int contains_invalid_server_ban_exception_type(const char *str, char *c)
 {
-	char *p;
+	const char *p;
 	for (p = str; *p; p++)
 	{
 		if (!tkl_banexception_chartotype(*p))
@@ -1589,9 +1684,12 @@ CMD_FUNC(cmd_eline)
 	Client *acptr = NULL;
 	char *mask = NULL;
 	char mo[64], mo2[64];
+	char maskbuf[BUFSIZE];
+	char mask1buf[BUFSIZE];
 	char mask2buf[BUFSIZE];
-	char *p, *usermask, *hostmask, *bantypes=NULL, *reason=NULL;
-	char *tkllayer[11] = {
+	const char *p, *bantypes=NULL, *reason=NULL;
+	char *usermask, *hostmask;
+	const char *tkllayer[11] = {
 		me.name,		/*0  server.name */
 		NULL,			/*1  +|- */
 		NULL,			/*2  E   */
@@ -1625,7 +1723,8 @@ CMD_FUNC(cmd_eline)
 		return;
 	}
 
-	mask = parv[1];
+	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
+	mask = maskbuf;
 	if (*mask == '-')
 	{
 		add = 0;
@@ -1665,55 +1764,40 @@ CMD_FUNC(cmd_eline)
 		return;
 
 	/* Check if it's an extended server ban */
-	if (is_extended_ban(mask))
+	if (is_extended_server_ban(mask))
 	{
-		if (add)
+		char *err;
+		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
 		{
-			/* Add */
-			char *str;
-			Extban *extban;
-			extban = findmod_by_bantype(mask[1]);
-			if (!extban || !(extban->options & EXTBOPT_TKL))
+			/* If adding, reject it */
+			if (add)
 			{
-				sendnotice(client, "Invalid or unsupported extended server ban requested: %s", mask);
-				sendnotice(client, "Valid types are for example ~a, ~r, ~S");
+				sendnotice(client, "ERROR: %s", err);
 				return;
-			}
-			if (extban->is_ok && !extban->is_ok(client, NULL, mask, EXBCHK_PARAM, MODE_ADD, EXBTYPE_TKL))
-				return; /* rejected */
-			str = extban->conv_param(mask);
-			if (!str || (strlen(str) <= 4))
-				return; /* rejected */
-			strlcpy(mask2buf, str+3, sizeof(mask2buf));
-			mask[3] = '\0';
-			usermask = mask; /* eg ~S: */
-			hostmask = mask2buf;
-			if ((t = eline_type_requires_ip(bantypes)))
+			} else
 			{
-				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;
+				/* Always allow any removal attempt... */
+				char *p;
+				char save;
+				p = strchr(mask, ':');
+				p++;
+				save = *p;
+				*p = '\0';
+				strlcpy(mask1buf, mask, sizeof(mask1buf));
+				*p = save;
+				strlcpy(mask2buf, p, sizeof(mask2buf));
+				/* fallthrough */
 			}
-		} else {
-			/* Delete: allow any attempt */
-			strlcpy(mask2buf, mask+3, sizeof(mask2buf));
-			mask[3] = '\0';
-			usermask = mask; /* eg ~S: */
-			hostmask = mask2buf;
-		}
-		/* Make sure we don't screw up S2S traffic ;) */
-		if (*hostmask == ':')
-		{
-			sendnotice(client, "[error] For technical reasons you cannot use double :: at the beginning "
-					   "of an extended server ban (eg ~a::xyz). You probably don't want to do this either.");
-			return;
 		}
-		if (!*hostmask)
+		if (add && (t = eline_type_requires_ip(bantypes)))
 		{
-			sendnotice(client, "[error] Empty hostmask encountered, eg -~S:");
+			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;
 		}
+		usermask = mask1buf; /* eg ~S: */
+		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
 	} else
 	{
 		/* Check if it's a hostmask and legal .. */
@@ -1769,12 +1853,12 @@ CMD_FUNC(cmd_eline)
 		else
 		{
 			/* It's seemingly a nick .. let's see if we can find the user */
-			if ((acptr = find_person(mask, NULL)))
+			if ((acptr = find_user(mask, NULL)))
 			{
 				BanAction action = BAN_ACT_KLINE; // just a dummy default
 				if (add && eline_type_requires_ip(bantypes))
 					action = BAN_ACT_ZLINE; // to indicate zline (no hostname, no dns, etc)
-				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, &usermask, &hostmask);
+				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
 			}
 			else
 			{
@@ -1844,7 +1928,7 @@ void spamfilter_usage(Client *client)
 }
 
 /** Helper function for cmd_spamfilter, explaining usage has changed. */
-void spamfilter_new_usage(Client *client, char *parv[])
+void spamfilter_new_usage(Client *client, const char *parv[])
 {
 	sendnotice(client, "Unknown match-type '%s'. Must be one of: -regex (new fast PCRE regexes) or "
 	                 "-simple (simple text with ? and * wildcards)",
@@ -1857,13 +1941,13 @@ void spamfilter_new_usage(Client *client, char *parv[])
 }
 
 /** Delete a spamfilter by ID (the ID can be obtained via '/SPAMFILTER del' */
-void spamfilter_del_by_id(Client *client, char *id)
+void spamfilter_del_by_id(Client *client, const char *id)
 {
 	int index;
 	TKL *tk;
 	int found = 0;
 	char mo[32], mo2[32];
-	char *tkllayer[13] = {
+	const char *tkllayer[13] = {
 		me.name,	/*  0 server.name */
 		NULL,		/*  1 +|- */
 		"F",		/*  2 F   */
@@ -1924,9 +2008,9 @@ void spamfilter_del_by_id(Client *client, char *id)
  */
 CMD_FUNC(cmd_spamfilter)
 {
-	int whattodo = 0;	/* 0 = add  1 = del */
+	int add = 1;
 	char mo[32], mo2[32];
-	char *tkllayer[13] = {
+	const char *tkllayer[13] = {
 		me.name,	/*  0 server.name */
 		NULL,		/*  1 +|- */
 		"F",		/*  2 F   */
@@ -1960,7 +2044,7 @@ CMD_FUNC(cmd_spamfilter)
 
 	if (parc == 1)
 	{
-		char *parv[3];
+		const char *parv[3];
 		parv[0] = NULL;
 		parv[1] = "spamfilter";
 		parv[2] = NULL;
@@ -1973,7 +2057,7 @@ CMD_FUNC(cmd_spamfilter)
 		if (!parv[2])
 		{
 			/* Show STATS with appropriate SPAMFILTER del command */
-			char *parv[5];
+			const char *parv[5];
 			parv[0] = NULL;
 			parv[1] = "spamfilter";
 			parv[2] = me.name;
@@ -2007,9 +2091,9 @@ CMD_FUNC(cmd_spamfilter)
 	 * parv[7]: regex
 	 */
 	if (!strcasecmp(parv[1], "add") || !strcmp(parv[1], "+"))
-		whattodo = 0;
+		add = 1;
 	else if (!strcasecmp(parv[1], "del") || !strcmp(parv[1], "-") || !strcasecmp(parv[1], "remove"))
-		whattodo = 1;
+		add = 0;
 	else
 	{
 		sendnotice(client, "1st parameter invalid");
@@ -2017,7 +2101,7 @@ CMD_FUNC(cmd_spamfilter)
 		return;
 	}
 
-	if ((whattodo == 0) && !strcasecmp(parv[2]+1, "posix"))
+	if (add && !strcasecmp(parv[2]+1, "posix"))
 	{
 		sendnotice(client, "ERROR: Spamfilter type 'posix' is DEPRECATED. You must use type 'regex' instead.");
 		sendnotice(client, "See https://www.unrealircd.org/docs/FAQ#spamfilter-posix-deprecated");
@@ -2050,7 +2134,7 @@ CMD_FUNC(cmd_spamfilter)
 	actionbuf[0] = banact_valtochar(action);
 	actionbuf[1] = '\0';
 
-	if (whattodo == 0)
+	if (add)
 	{
 		/* now check the regex / match field... */
 		m = unreal_create_match(match_type, parv[7], &err);
@@ -2062,7 +2146,7 @@ CMD_FUNC(cmd_spamfilter)
 		unreal_delete_match(m);
 	}
 
-	tkllayer[1] = whattodo ? "-" : "+";
+	tkllayer[1] = add ? "+" : "-";
 	tkllayer[3] = targetbuf;
 	tkllayer[4] = actionbuf;
 	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
@@ -2093,14 +2177,14 @@ CMD_FUNC(cmd_spamfilter)
 	 * on 50 characters for the rest... -- Syzop
 	 */
 	n = strlen(reason) + strlen(parv[7]) + strlen(tkllayer[6]) + (NICKLEN * 2) + 40;
-	if ((n > 500) && (whattodo == 0))
+	if ((n > 500) && add)
 	{
 		sendnotice(client, "Sorry, spamfilter too long. You'll either have to trim down the "
 		                 "reason or the regex (exceeded by %d bytes)", n - 500);
 		return;
 	}
 
-	if (whattodo == 0)
+	if (add)
 	{
 		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
 		tkllayer[7] = mo2;
@@ -2123,8 +2207,9 @@ int _tkl_hash(unsigned int c)
 	else if ((c >= 'A') && (c <= 'Z'))
 		return c-'A';
 	else {
-		sendto_realops("[BUG] tkl_hash() called with out of range parameter (c = '%c') !!!", c);
-		ircd_log(LOG_ERROR, "[BUG] tkl_hash() called with out of range parameter (c = '%c') !!!", c);
+		unreal_log(ULOG_ERROR, "bug", "TKL_HASH_INVALID", NULL,
+		           "tkl_hash() called with out of range parameter (c = '$tkl_char') !!!",
+		           log_data_char("tkl_char", c));
 		return 0;
 	}
 #else
@@ -2141,8 +2226,9 @@ char _tkl_typetochar(int type)
 	for (i=0; tkl_types[i].config_name; i++)
 		if ((tkl_types[i].type == type) && tkl_types[i].tkltype)
 			return tkl_types[i].letter;
-	sendto_realops("[BUG]: tkl_typetochar(): unknown type 0x%x !!!", type);
-	ircd_log(LOG_ERROR, "[BUG] tkl_typetochar(): unknown type 0x%x !!!", type);
+	unreal_log(ULOG_ERROR, "bug", "TKL_TYPETOCHAR_INVALID", NULL,
+	           "tkl_typetochar(): unknown type $tkl_type!!!",
+	           log_data_integer("tkl_type", type));
 	return 0;
 }
 
@@ -2201,13 +2287,13 @@ char *tkl_banexception_configname_to_chars(char *name)
 char *_tkl_type_string(TKL *tkl)
 {
 	static char txt[256];
+	int i;
 
 	*txt = '\0';
 
 	if (TKLIsServerBan(tkl) && (tkl->ptr.serverban->subtype == TKL_SUBTYPE_SOFT))
 		strlcpy(txt, "Soft ", sizeof(txt));
 
-	int i;
 	for (i=0; tkl_types[i].config_name; i++)
 	{
 		if ((tkl_types[i].type == tkl->type) && tkl_types[i].tkltype)
@@ -2221,6 +2307,18 @@ char *_tkl_type_string(TKL *tkl)
 	return txt;
 }
 
+/** Short config string, lowercase alnum with possibly hyphens (eg: 'kline') */
+char *_tkl_type_config_string(TKL *tkl)
+{
+	int i;
+
+	for (i=0; tkl_types[i].config_name; i++)
+		if ((tkl_types[i].type == tkl->type) && tkl_types[i].tkltype)
+			return tkl_types[i].config_name;
+
+	return "???";
+}
+
 int tkl_banexception_matches_type(TKL *except, int bantype)
 {
 	char *p;
@@ -2632,18 +2730,10 @@ void _tkl_del_line(TKL *tkl)
 				}
 			if (!really_found)
 			{
-				ircd_log(LOG_ERROR, "[BUG] [Crash] tkl_del_line() for %s (%d): "
-				                    "NOT found in tklines_ip_hash[%d][%d], "
-				                    "this should never happen!",
-				                    tkl_type_string(tkl),
-				                    tkl->type,
-				                    index, index2);
-				if (TKLIsServerBan(tkl))
-				{
-					ircd_log(LOG_ERROR, "Additional information: the ban was on %s@%s",
-						tkl->ptr.serverban->usermask ? tkl->ptr.serverban->usermask : "<null>",
-						tkl->ptr.serverban->hostmask ? tkl->ptr.serverban->hostmask : "<null>");
-				}
+				unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_DEL_LINE_HASH", NULL,
+				           "[BUG] [Crash] tkl_del_line() for $tkl (type: $tkl.type_string): "
+				           "NOT found in tklines_ip_hash. This should never happen!",
+				           log_data_tkl("tkl", tkl));
 				abort();
 			}
 #endif
@@ -2672,7 +2762,7 @@ 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.*", "localhost is always exempt",
+	tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.0/8", "localhost is always exempt",
 	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
 }
 
@@ -2720,7 +2810,7 @@ void _tkl_check_local_remove_shun(TKL *tmp)
 					 */
 					keep_shun = 0;
 					for(tk = tklines[tkl_hash('s')]; tk && !keep_shun; tk = tk->next)
-						if(tk != tmp && match_simple(tk->ptr.serverban->usermask, cname))
+						if (tk != tmp && match_simple(tk->ptr.serverban->usermask, cname))
 						{
 							if ((*tk->ptr.serverban->hostmask >= '0') && (*tk->ptr.serverban->hostmask <= '9')
 							    /* the hostmask is an IP */
@@ -2732,7 +2822,7 @@ void _tkl_check_local_remove_shun(TKL *tmp)
 									keep_shun = 1;
 						}
 
-					if(!keep_shun)
+					if (!keep_shun)
 					{
 						ClearShunned(client);
 					}
@@ -2746,11 +2836,11 @@ void _tkl_check_local_remove_shun(TKL *tmp)
  * that can be used in oper notices like expiring kline, added kline, etc.
  */
 #define NO_SOFT_PREFIX	1
-char *tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options)
+char *_tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options)
 {
 	if (TKLIsServerBan(tkl))
 	{
-		if (is_extended_ban(tkl->ptr.serverban->usermask))
+		if (is_extended_server_ban(tkl->ptr.serverban->usermask))
 		{
 			ircsnprintf(buf, buflen, "%s%s%s",
 				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
@@ -2763,7 +2853,7 @@ char *tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options)
 	} else
 	if (TKLIsBanException(tkl))
 	{
-		if (is_extended_ban(tkl->ptr.banexception->usermask))
+		if (is_extended_server_ban(tkl->ptr.banexception->usermask))
 		{
 			ircsnprintf(buf, buflen, "%s%s%s",
 				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
@@ -2784,60 +2874,33 @@ char *tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options)
  */
 void tkl_expire_entry(TKL *tkl)
 {
-	char *whattype = tkl_type_string(tkl);
-
-	if (!tkl)
-		return;
-
-	if (tkl->type & TKL_SPAMF)
-	{
-		/* Impossible */
-	} else
 	if (TKLIsServerBan(tkl))
 	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		sendto_snomask(SNO_TKL,
-		    "*** Expiring %s (%s) made by %s (Reason: %s) set %lld seconds ago",
-		    whattype, uhost, tkl->set_by, tkl->ptr.serverban->reason,
-		    (long long)(TStime() - tkl->set_at));
-		ircd_log
-		    (LOG_TKL, "Expiring %s (%s) made by %s (Reason: %s) set %lld seconds ago",
-		    whattype, uhost, tkl->set_by, tkl->ptr.serverban->reason,
-		    (long long)(TStime() - tkl->set_at));
+		unreal_log(ULOG_INFO, "tkl", "TKL_EXPIRE", NULL,
+		           "Expiring $tkl.type_string '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
+		           log_data_tkl("tkl", tkl));
 	}
 	else if (TKLIsNameBan(tkl))
 	{
 		if (!tkl->ptr.nameban->hold)
 		{
-			sendto_snomask(SNO_TKL,
-				"*** Expiring %s (%s) made by %s (Reason: %s) set %lld seconds ago",
-				whattype, tkl->ptr.nameban->name, tkl->set_by, tkl->ptr.nameban->reason,
-				(long long)(TStime() - tkl->set_at));
-			ircd_log
-				(LOG_TKL, "Expiring %s (%s) made by %s (Reason: %s) set %lld seconds ago",
-				whattype, tkl->ptr.nameban->name, tkl->set_by, tkl->ptr.nameban->reason,
-				(long long)(TStime() - tkl->set_at));
+			unreal_log(ULOG_INFO, "tkl", "TKL_EXPIRE", NULL,
+			           "Expiring $tkl.type_string '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
+				   log_data_tkl("tkl", tkl));
 		}
 	}
 	else if (TKLIsBanException(tkl))
 	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		sendto_snomask(SNO_TKL,
-		    "*** Expiring %s (%s) for types '%s' made by %s (Reason: %s) set %lld seconds ago",
-		    whattype, uhost, tkl->ptr.banexception->bantypes, tkl->set_by, tkl->ptr.banexception->reason,
-		    (long long)(TStime() - tkl->set_at));
-		ircd_log
-		    (LOG_TKL, "Expiring %s (%s) for types '%s' made by %s (Reason: %s) set %lld seconds ago",
-		    whattype, uhost, tkl->ptr.banexception->bantypes, tkl->set_by, tkl->ptr.banexception->reason,
-		    (long long)(TStime() - tkl->set_at));
+		unreal_log(ULOG_INFO, "tkl", "TKL_EXPIRE", NULL,
+			   "Expiring $tkl.type_string '$tkl' [type: $tkl.exception_types] [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
+			   log_data_tkl("tkl", tkl));
 	}
 
+	// FIXME: so.. this isn't logged? or what?
 	if (tkl->type & TKL_SHUN)
 		tkl_check_local_remove_shun(tkl);
 
-	RunHook2(HOOKTYPE_TKL_DEL, NULL, tkl);
+	RunHook(HOOKTYPE_TKL_DEL, NULL, tkl);
 	tkl_del_line(tkl);
 }
 
@@ -3032,7 +3095,7 @@ int _find_tkline_match(Client *client, int skip_soft)
 
 	/* User is banned... */
 
-	RunHookReturnInt2(HOOKTYPE_FIND_TKLINE_MATCH, client, tkl, !=99);
+	RunHookReturnInt(HOOKTYPE_FIND_TKLINE_MATCH, !=99, client, tkl);
 
 	if (tkl->type & TKL_KILL)
 	{
@@ -3161,45 +3224,13 @@ int spamfilter_check_users(TKL *tkl)
 				continue; /* No match */
 
 			/* matched! */
-			ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s!%s@%s matches filter '%s': [%s: '%s'] [%s]",
-				client->name, client->user->username, client->user->realhost,
-				tkl->ptr.spamfilter->match->str,
-				"user", spamfilter_user,
-				unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
-
-			sendto_snomask_global(SNO_SPAMF, "%s", buf);
-			ircd_log(LOG_SPAMFILTER, "%s", buf);
-			RunHook6(HOOKTYPE_LOCAL_SPAMFILTER, client, spamfilter_user, spamfilter_user, SPAMF_USER, NULL, tkl);
-			matches++;
-		}
-	}
+			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
+			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
+				   log_data_tkl("tkl", tkl),
+				   log_data_string("command", "USER"),
+				   log_data_string("str", spamfilter_user));
 
-	return matches;
-}
-
-/** Similarly to previous, but match against all global users.
- * FUNCTION IS UNUSED !!
- */
-int spamfilter_check_all_users(Client *from, TKL *tkl)
-{
-	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
-	int matches = 0;
-	Client *acptr;
-
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (IsUser(acptr))
-		{
-			spamfilter_build_user_string(spamfilter_user, acptr->name, acptr);
-			if (!unreal_match(tkl->ptr.spamfilter->match, spamfilter_user))
-				continue; /* No match */
-
-			/* matched! */
-			sendnotice(from, "[Spamfilter] %s!%s@%s matches filter '%s': [%s: '%s'] [%s]",
-				acptr->name, acptr->user->username, acptr->user->realhost,
-				tkl->ptr.spamfilter->match->str,
-				"user", spamfilter_user,
-				unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
+			RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, spamfilter_user, spamfilter_user, SPAMF_USER, NULL, tkl);
 			matches++;
 		}
 	}
@@ -3317,15 +3348,15 @@ TKL *_find_tkline_match_zap(Client *client)
 
 typedef struct {
 	int flags;
-	char *mask;
-	char *reason;
-	char *set_by;
+	const char *mask;
+	const char *reason;
+	const char *set_by;
 } TKLFlag;
 
 /** Parse STATS tkl parameters.
  * TODO: I don't think this is documented anywhere? Or underdocumented at least.
  */
-static void parse_stats_params(char *para, TKLFlag *flag)
+static void parse_stats_params(const char *para, TKLFlag *flag)
 {
 	static char paratmp[512]; /* <- copy of para, because it gets fragged by strtok() */
 	char *flags, *tmp;
@@ -3381,7 +3412,7 @@ static void parse_stats_params(char *para, TKLFlag *flag)
 /** Does this TKL entry match the search terms?
  * This is a helper function for tkl_stats().
  */
-int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, TKL *tkl)
+int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklflags, TKL *tkl)
 {
 	/***** First, handle the selection ******/
 
@@ -3460,32 +3491,32 @@ int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, T
 		if (tkl->type == (TKL_KILL | TKL_GLOBAL))
 		{
 			sendnumeric(client, RPL_STATSGLINE, 'G', uhost,
-				   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-				   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
+				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
 		} else
 		if (tkl->type == (TKL_ZAP | TKL_GLOBAL))
 		{
 			sendnumeric(client, RPL_STATSGLINE, 'Z', uhost,
-				   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-				   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
+				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
 		} else
 		if (tkl->type == (TKL_SHUN | TKL_GLOBAL))
 		{
 			sendnumeric(client, RPL_STATSGLINE, 's', uhost,
-				   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-				   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
+				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
 		} else
 		if (tkl->type == (TKL_KILL))
 		{
 			sendnumeric(client, RPL_STATSGLINE, 'K', uhost,
-				   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-				   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
+				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
 		} else
 		if (tkl->type == (TKL_ZAP))
 		{
 			sendnumeric(client, RPL_STATSGLINE, 'z', uhost,
-				   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-				   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
+				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
 		}
 	} else
 	if (TKLIsSpamfilter(tkl))
@@ -3495,9 +3526,10 @@ int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, T
 			unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
 			spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
 			banact_valtostring(tkl->ptr.spamfilter->action),
-			(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-			TStime() - tkl->set_at,
-			tkl->ptr.spamfilter->tkl_duration, tkl->ptr.spamfilter->tkl_reason,
+			(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+			(long long)(TStime() - tkl->set_at),
+			(long long)tkl->ptr.spamfilter->tkl_duration,
+			tkl->ptr.spamfilter->tkl_reason,
 			tkl->set_by,
 			tkl->ptr.spamfilter->match->str);
 		if (para && !strcasecmp(para, "del"))
@@ -3515,9 +3547,13 @@ int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, T
 	} else
 	if (TKLIsNameBan(tkl))
 	{
-		sendnumeric(client, RPL_STATSQLINE, (tkl->type & TKL_GLOBAL) ? 'Q' : 'q',
-			tkl->ptr.nameban->name, (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-			TStime() - tkl->set_at, tkl->set_by, tkl->ptr.nameban->reason);
+		sendnumeric(client, RPL_STATSQLINE,
+		            (tkl->type & TKL_GLOBAL) ? 'Q' : 'q',
+		            tkl->ptr.nameban->name,
+		            (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+		            (long long)(TStime() - tkl->set_at),
+		            tkl->set_by,
+		            tkl->ptr.nameban->reason);
 	} else
 	if (TKLIsBanException(tkl))
 	{
@@ -3525,8 +3561,8 @@ int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, T
 		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
 		sendnumeric(client, RPL_STATSEXCEPTTKL, uhost,
 			   tkl->ptr.banexception->bantypes,
-			   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
-			   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
+			   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
+			   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
 	} else
 	{
 		/* That's weird, unknown TKL type */
@@ -3536,7 +3572,7 @@ int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, T
 }
 
 /* TKL Stats. This is used by /STATS gline and all the others */
-void _tkl_stats(Client *client, int type, char *para, int *cnt)
+void _tkl_stats(Client *client, int type, const char *para, int *cnt)
 {
 	TKL *tk;
 	TKLFlag tklflags;
@@ -3661,8 +3697,10 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
 			   tkl->ptr.banexception->reason);
 	} else
 	{
-		sendto_ops_and_log("[BUG] tkl_sync_send_entry() called, but unknown type %d/'%c'",
-			tkl->type, typ);
+		unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_SYNC_SEND_ENTRY", NULL,
+			   "[BUG] tkl_sync_send_entry() called for '%s' but unknown type: $tkl.type_string ($tkl_type_int)",
+			   log_data_tkl("tkl", tkl),
+			   log_data_integer("tkl_type_int", typ));
 		abort();
 	}
 }
@@ -3809,138 +3847,81 @@ TKL *_find_tkl_spamfilter(int type, char *match_string, BanAction action, unsign
 /** Send a notice to opers about the TKL that is being added */
 void _sendnotice_tkl_add(TKL *tkl)
 {
-	char buf[512];
-	char set_at[128];
-	char expire_at[128];
-	char *tkl_type_str; /**< Eg: "K-Line" */
-
 	/* Don't show notices for temporary nick holds (issued by services) */
 	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
 		return;
 
-	tkl_type_str = tkl_type_string(tkl);
-
-	*buf = *set_at = *expire_at = '\0';
-	short_date(tkl->set_at, set_at);
-	if (tkl->expire_at > 0)
-		short_date(tkl->expire_at, expire_at);
-
 	if (TKLIsServerBan(tkl))
 	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		if (tkl->expire_at != 0)
-		{
-			ircsnprintf(buf, sizeof(buf), "%s added for %s on %s GMT (from %s to expire at %s GMT: %s)",
-				tkl_type_str, uhost,
-				set_at, tkl->set_by, expire_at, tkl->ptr.serverban->reason);
-		} else {
-			ircsnprintf(buf, sizeof(buf), "Permanent %s added for %s on %s GMT (from %s: %s)",
-				tkl_type_str, uhost,
-				set_at, tkl->set_by, tkl->ptr.serverban->reason);
-		}
+		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
+			   "$tkl.type_string added: '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
+			   log_data_tkl("tkl", tkl));
 	} else
 	if (TKLIsNameBan(tkl))
 	{
-		if (tkl->expire_at > 0)
-		{
-			ircsnprintf(buf, sizeof(buf), "%s added for %s on %s GMT (from %s to expire at %s GMT: %s)",
-				tkl_type_str, tkl->ptr.nameban->name, set_at, tkl->set_by, expire_at, tkl->ptr.nameban->reason);
-		} else {
-			ircsnprintf(buf, sizeof(buf), "Permanent %s added for %s on %s GMT (from %s: %s)",
-				tkl_type_str, tkl->ptr.nameban->name, set_at, tkl->set_by, tkl->ptr.nameban->reason);
-		}
+		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
+			   "$tkl.type_string added: '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
+			   log_data_tkl("tkl", tkl));
 	} else
 	if (TKLIsSpamfilter(tkl))
 	{
-		/* Spamfilter */
-		ircsnprintf(buf, sizeof(buf),
-		            "Spamfilter added: '%s' [type: %s] [target: %s] [action: %s] [reason: %s] on %s GMT (from %s)",
-		            tkl->ptr.spamfilter->match->str,
-			    unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
-		            spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
-		            banact_valtostring(tkl->ptr.spamfilter->action),
-		            unreal_decodespace(tkl->ptr.spamfilter->tkl_reason),
-		            set_at,
-		            tkl->set_by);
+		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
+			   "Spamfilter added: '$tkl' [type: $tkl.match_type] [targets: $tkl.spamfilter_targets] "
+			   "[action: $tkl.ban_action] [reason: $tkl.reason] [by: $tkl.set_by]",
+			   log_data_tkl("tkl", tkl));
 	} else
 	if (TKLIsBanException(tkl))
 	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		if (tkl->expire_at != 0)
-		{
-			ircsnprintf(buf, sizeof(buf), "%s added for %s for types '%s' on %s GMT (from %s to expire at %s GMT: %s)",
-				tkl_type_str, uhost,
-				tkl->ptr.banexception->bantypes,
-				set_at, tkl->set_by, expire_at, tkl->ptr.banexception->reason);
-		} else {
-			ircsnprintf(buf, sizeof(buf), "Permanent %s added for %s for types '%s' on %s GMT (from %s: %s)",
-				tkl_type_str, uhost,
-				tkl->ptr.banexception->bantypes,
-				set_at, tkl->set_by, tkl->ptr.banexception->reason);
-		}
+		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
+			   "$tkl.type_string added: '$tkl' [types: $tkl.exception_types] [by: $tkl.set_by] [duration: $tkl.duration_string]",
+			   log_data_tkl("tkl", tkl));
 	} else
 	{
-		ircsnprintf(buf, sizeof(buf), "[BUG] %s added but type unhandled in sendnotice_tkl_add()!!!", tkl_type_str);
+		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
+		           "[BUG] TKL added of unknown type, unhandled in sendnotice_tkl_add()!!!!");
 	}
-
-	sendto_snomask(SNO_TKL, "*** %s", buf);
-	ircd_log(LOG_TKL, "%s", buf);
 }
 
 /** Send a notice to opers about the TKL that is being deleted */
 void _sendnotice_tkl_del(char *removed_by, TKL *tkl)
 {
-	char buf[512];
-	char set_at[128];
-	char *tkl_type_str;
-
 	/* Don't show notices for temporary nick holds (issued by services) */
 	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
 		return;
 
-	tkl_type_str = tkl_type_string(tkl); /* eg: "K-Line" */
-
-	*buf = *set_at = '\0';
-	short_date(tkl->set_at, set_at);
-
 	if (TKLIsServerBan(tkl))
 	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		ircsnprintf(buf, sizeof(buf),
-			       "%s removed %s %s (set at %s - reason: %s)",
-			       removed_by, tkl_type_str, uhost,
-			       set_at, tkl->ptr.serverban->reason);
+		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
+			   "$tkl.type_string removed: '$tkl' [reason: $tkl.reason] [by: $removed_by] [set at: $tkl.set_at_string]",
+			   log_data_tkl("tkl", tkl),
+			   log_data_string("removed_by", removed_by));
 	} else
 	if (TKLIsNameBan(tkl))
 	{
-		ircsnprintf(buf, sizeof(buf),
-			"%s removed %s %s (set at %s - reason: %s)",
-			removed_by, tkl_type_str, tkl->ptr.nameban->name, set_at, tkl->ptr.nameban->reason);
+		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
+			   "$tkl.type_string removed: '$tkl' [reason: $tkl.reason] [by: $removed_by] [set at: $tkl.set_at_string]",
+			   log_data_tkl("tkl", tkl),
+			   log_data_string("removed_by", removed_by));
 	} else
 	if (TKLIsSpamfilter(tkl))
 	{
-		ircsnprintf(buf, sizeof(buf),
-			"%s removed Spamfilter '%s' (set at %s)",
-			removed_by, tkl->ptr.spamfilter->match->str, set_at);
+		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
+			   "Spamfilter removed: '$tkl' [type: $tkl.match_type] [targets: $tkl.spamfilter_targets] "
+			   "[action: $tkl.ban_action] [reason: $tkl.reason] [by: $removed_by] [set at: $tkl.set_at_string]",
+			   log_data_tkl("tkl", tkl),
+			   log_data_string("removed_by", removed_by));
 	} else
 	if (TKLIsBanException(tkl))
 	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		ircsnprintf(buf, sizeof(buf),
-			       "%s removed exception on %s (set at %s - reason: %s)",
-			       removed_by, uhost,
-			       set_at, tkl->ptr.banexception->reason);
+		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
+			   "$tkl.type_string removed: '$tkl' [types: $tkl.exception_types] [by: $removed_by] [set at: $tkl.set_at_string]",
+			   log_data_tkl("tkl", tkl),
+			   log_data_string("removed_by", removed_by));
 	} else
 	{
-		ircsnprintf(buf, sizeof(buf), "[BUG] %s added but type unhandled in sendnotice_tkl_del()!!!!!", tkl_type_str);
+		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
+		           "[BUG] TKL removed of unknown type, unhandled in sendnotice_tkl_del()!!!!");
 	}
-
-	sendto_snomask(SNO_TKL, "*** %s", buf);
-	ircd_log(LOG_TKL, "%s", buf);
 }
 
 /** Add a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
@@ -3949,7 +3930,7 @@ CMD_FUNC(cmd_tkl_add)
 	TKL *tkl;
 	int type;
 	time_t expire_at, set_at;
-	char *set_by;
+	const char *set_by;
 	char tkl_entry_exists = 0;
 
 	/* we rely on servers to be failsafe.. */
@@ -3977,14 +3958,18 @@ CMD_FUNC(cmd_tkl_add)
 	/* Validate set and expiry time */
 	if ((set_at < 0) || !short_date(set_at, NULL))
 	{
-		sendto_realops("Invalid TKL entry from %s, set-at time is out of range (%lld) -- not added. Clock on other server incorrect or bogus entry.",
-			client->name, (long long)set_at);
+		unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+			"Invalid TKL entry from $client: "
+			"The set-at time is out of range ($set_at). Clock on other server incorrect or bogus entry.",
+			log_data_integer("set_at", set_at));
 		return;
 	}
 	if ((expire_at < 0) || !short_date(expire_at, NULL))
 	{
-		sendto_realops("Invalid TKL entry from %s, expiry time is out of range (%lld) -- not added. Clock on other server incorrect or bogus entry.",
-			client->name, (long long)expire_at);
+		unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+			"Invalid TKL entry from $client: "
+			"The expire-at time is out of range ($expire_at). Clock on other server incorrect or bogus entry.",
+			log_data_integer("expire_at", expire_at));
 		return;
 	}
 
@@ -3996,9 +3981,9 @@ CMD_FUNC(cmd_tkl_add)
 	{
 		/* Validate server ban TKL fields */
 		int softban = 0;
-		char *usermask = parv[3];
-		char *hostmask = parv[4];
-		char *reason = parv[8];
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
+		const char *reason = parv[8];
 
 		/* Some simple validation on usermask and hostmask:
 		 * may not contain an @. Yeah, some services or self-written
@@ -4006,9 +3991,11 @@ CMD_FUNC(cmd_tkl_add)
 		 */
 		if (strchr(usermask, '@') || strchr(hostmask, '@'))
 		{
-			sendto_realops("Ignoring TKL entry %s@%s from %s. "
-			               "Invalid usermask '%s' or hostmask '%s'.",
-			               usermask, hostmask, client->name, usermask, hostmask);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Invalid user@host $usermask@$hostmask",
+				log_data_string("usermask", usermask),
+				log_data_string("hostmask", hostmask));
 			return;
 		}
 
@@ -4035,10 +4022,10 @@ CMD_FUNC(cmd_tkl_add)
 	{
 		/* Validate ban exception TKL fields */
 		int softban = 0;
-		char *usermask = parv[3];
-		char *hostmask = parv[4];
-		char *bantypes = parv[8];
-		char *reason;
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
+		const char *bantypes = parv[8];
+		const char *reason;
 
 		if (parc < 10)
 			return;
@@ -4051,9 +4038,11 @@ CMD_FUNC(cmd_tkl_add)
 		 */
 		if (strchr(usermask, '@') || strchr(hostmask, '@'))
 		{
-			sendto_realops("Ignoring TKL exception entry %s@%s from %s. "
-			               "Invalid usermask '%s' or hostmask '%s'.",
-			               usermask, hostmask, client->name, usermask, hostmask);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Invalid TKL except user@host $usermask@$hostmask",
+				log_data_string("usermask", usermask),
+				log_data_string("hostmask", hostmask));
 			return;
 		}
 
@@ -4083,8 +4072,8 @@ CMD_FUNC(cmd_tkl_add)
 	{
 		/* Validate name ban TKL fields */
 		int hold = 0;
-		char *name = parv[4];
-		char *reason = parv[8];
+		const char *name = parv[4];
+		const char *reason = parv[8];
 
 		if (*parv[3] == 'H')
 			hold = 1;
@@ -4102,10 +4091,10 @@ CMD_FUNC(cmd_tkl_add)
 	{
 		/* Validate spamfilter-specific TKL fields */
 		MatchType match_method;
-		char *match_string;
+		const char *match_string;
 		Match *m; /* compiled match_string */
 		time_t tkl_duration;
-		char *tkl_reason;
+		const char *tkl_reason;
 		BanAction action;
 		unsigned short target;
 		/* helper variables */
@@ -4113,38 +4102,42 @@ CMD_FUNC(cmd_tkl_add)
 
 		if (parc < 12)
 		{
-			sendto_realops("Ignoring spamfilter from %s. Running very old UnrealIRCd protocol (3.2.X?)", client->name);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter with too few parameters. Running very old UnrealIRCd protocol (3.2.X?)");
 			return;
 		}
 
 		match_string = parv[11];
 
-		if (!strcasecmp(parv[10], "posix"))
-		{
-			sendto_realops("Ignoring spamfilter from %s. Spamfilter is of type 'posix' (TRE) which "
-				       "is not supported in UnrealIRCd 5. Suggestion: upgrade the other server.",
-				       client->name);
-			return;
-		}
 		match_method = unreal_match_method_strtoval(parv[10]);
 		if (match_method == 0)
 		{
-			sendto_realops("Ignoring spamfilter '%s' from %s with unknown match type '%s'",
-				match_string, client->name, parv[10]);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter '$spamfilter_string' has unkown match-type '$spamfilter_type'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_type", parv[10]));
 			return;
 		}
 
 		if (!(target = spamfilter_gettargets(parv[3], NULL)))
 		{
-			sendto_realops("Ignoring spamfilter '%s' from %s with unknown target type '%s'",
-				match_string, client->name, parv[3]);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter '$spamfilter_string' has unkown targets '$spamfilter_targets'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_targets", parv[3]));
 			return;
 		}
 
 		if (!(action = banact_chartoval(*parv[4])))
 		{
-			sendto_realops("Ignoring spamfilter '%s' from %s with unknown action type '%s'",
-				match_string, client->name, parv[4]);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter '$spamfilter_string' has unkown action '$spamfilter_action'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_action", parv[4]));
 			return;
 		}
 
@@ -4160,9 +4153,11 @@ CMD_FUNC(cmd_tkl_add)
 			m = unreal_create_match(match_method, match_string, &err);
 			if (!m)
 			{
-				sendto_realops("[TKL ERROR] ERROR: Trying to add a spamfilter which does not compile. "
-					       " ERROR='%s', Spamfilter='%s', from='%s'",
-					       err, match_string, client->name);
+				unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+					"Invalid TKL entry from $client: "
+					"Spamfilter '$spamfilter_string': regex does not compile: $spamfilter_regex_error",
+					log_data_string("spamfilter_string", match_string),
+					log_data_string("spamfilter_regex_error", err));
 				return;
 			}
 			tkl = tkl_add_spamfilter(type, target, action, m, set_by, expire_at, set_at,
@@ -4213,7 +4208,7 @@ CMD_FUNC(cmd_tkl_add)
 
 	/* Below this line we will only use 'tkl'. No parc/parv reading anymore. */
 
-	RunHook2(HOOKTYPE_TKL_ADD, client, tkl);
+	RunHook(HOOKTYPE_TKL_ADD, client, tkl);
 
 	sendnotice_tkl_add(tkl);
 
@@ -4233,7 +4228,7 @@ CMD_FUNC(cmd_tkl_del)
 {
 	TKL *tkl;
 	int type;
-	char *removed_by;
+	const char *removed_by;
 
 	if (!IsServer(client) && !IsMe(client))
 		return;
@@ -4249,8 +4244,8 @@ CMD_FUNC(cmd_tkl_del)
 
 	if (TKLIsServerBanType(type))
 	{
-		char *usermask = parv[3];
-		char *hostmask = parv[4];
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
 		int softban = 0;
 
 		if (*usermask == '%')
@@ -4263,8 +4258,8 @@ CMD_FUNC(cmd_tkl_del)
 	}
 	else if (TKLIsBanExceptionType(type))
 	{
-		char *usermask = parv[3];
-		char *hostmask = parv[4];
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
 		int softban = 0;
 		/* other parameters are ignored */
 
@@ -4279,7 +4274,7 @@ CMD_FUNC(cmd_tkl_del)
 	else if (TKLIsNameBanType(type))
 	{
 		int hold = 0;
-		char *name = parv[4];
+		const char *name = parv[4];
 
 		if (*parv[3] == 'H')
 			hold = 1;
@@ -4287,14 +4282,15 @@ CMD_FUNC(cmd_tkl_del)
 	}
 	else if (TKLIsSpamfilterType(type))
 	{
-		char *match_string;
+		const char *match_string;
 		unsigned short target;
 		BanAction action;
 
 		if (parc < 9)
 		{
-			sendto_realops("[BUG] cmd_tkl called with bogus spamfilter removal request [f/F], from=%s, parc=%d",
-				       client->name, parc);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
+				"Invalid TKL deletion request from $client: "
+				"Spamfilter with too few parameters. Running very old UnrealIRCd protocol (3.2.X?)");
 			return; /* bogus */
 		}
 		if (parc >= 12)
@@ -4306,15 +4302,21 @@ CMD_FUNC(cmd_tkl_del)
 
 		if (!(target = spamfilter_gettargets(parv[3], NULL)))
 		{
-			sendto_realops("Ignoring spamfilter deletion request for '%s' from %s with unknown target type '%s'",
-				match_string, client->name, parv[3]);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
+				"Invalid TKL deletion request from $client: "
+				"Spamfilter '$spamfilter_string' has unkown targets '$spamfilter_targets'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_targets", parv[3]));
 			return;
 		}
 
 		if (!(action = banact_chartoval(*parv[4])))
 		{
-			sendto_realops("Ignoring spamfilter deletion request for '%s' from %s with unknown action type '%s'",
-				match_string, client->name, parv[4]);
+			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
+				"Invalid TKL deletion request from $client: "
+				"Spamfilter '$spamfilter_string' has unkown action '$spamfilter_action'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_action", parv[4]));
 			return;
 		}
 		tkl = find_tkl_spamfilter(type, match_string, action, target);
@@ -4338,7 +4340,7 @@ CMD_FUNC(cmd_tkl_del)
 	if (type & TKL_SHUN)
 		tkl_check_local_remove_shun(tkl);
 
-	RunHook2(HOOKTYPE_TKL_DEL, client, tkl);
+	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
 
 	if (type & TKL_GLOBAL)
 	{
@@ -4412,7 +4414,7 @@ CMD_FUNC(_cmd_tkl)
 }
 
 /** Configure the username/hostname TKL layer based on the BAN_TARGET_* configuration */
-void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, char **tkl_username, char **tkl_hostname)
+void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, const char **tkl_username, const char **tkl_hostname)
 {
 	static char username[USERLEN+1];
 	static char hostname[HOSTLEN+8];
@@ -4422,13 +4424,11 @@ void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *cli
 
 	if (ban_target == BAN_TARGET_ACCOUNT)
 	{
-		if (client->user && client->user->svid &&
-		    strcmp(client->user->svid, "0") &&
-		    (*client->user->svid != ':'))
+		if (IsLoggedIn(client) && (*client->user->account != ':'))
 		{
 			/* Place a ban on ~a:Accountname */
 			strlcpy(username, "~a:", sizeof(username));
-			strlcpy(hostname, client->user->svid, sizeof(hostname));
+			strlcpy(hostname, client->user->account, sizeof(hostname));
 			*tkl_username = username;
 			*tkl_hostname = hostname;
 			return;
@@ -4437,7 +4437,7 @@ void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *cli
 	} else
 	if (ban_target == BAN_TARGET_CERTFP)
 	{
-		char *fp = moddata_client_get(client, "certfp");
+		const char *fp = moddata_client_get(client, "certfp");
 		if (fp)
 		{
 			/* Place a ban on ~S:sha256sumofclientcertificate */
@@ -4453,7 +4453,7 @@ void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *cli
 	/* Below we deal with the more common choices... */
 
 	/* First, set the username */
-	if (((ban_target == BAN_TARGET_USERIP) || (ban_target == BAN_TARGET_USERHOST)) && client->ident && strcmp(client->ident, "unknown"))
+	if (((ban_target == BAN_TARGET_USERIP) || (ban_target == BAN_TARGET_USERHOST)) && strcmp(client->ident, "unknown"))
 		strlcpy(username, client->ident, sizeof(username));
 	else
 		strlcpy(username, "*", sizeof(username));
@@ -4494,11 +4494,10 @@ int _place_host_ban(Client *client, BanAction action, char *reason, long duratio
 	{
 		case BAN_ACT_TEMPSHUN:
 			/* We simply mark this connection as shunned and do not add a ban record */
-			sendto_snomask(SNO_TKL, "Temporary shun added at user %s (%s@%s) [%s]",
-				client->name,
-				client->user ? client->user->username : "unknown",
-				client->user ? client->user->realhost : GetIP(client),
-				reason);
+			unreal_log(ULOG_INFO, "tkl", "TKL_ADD_TEMPSHUN", &me,
+				   "Temporary shun added on user $target.details [reason: $shun_reason] [by: $client]",
+				   log_data_string("shun_reason", reason),
+				   log_data_client("target", client));
 			SetShunned(client);
 			return 1;
 		case BAN_ACT_GZLINE:
@@ -4511,7 +4510,7 @@ int _place_host_ban(Client *client, BanAction action, char *reason, long duratio
 		case BAN_ACT_SOFT_SHUN:
 		{
 			char ip[128], user[USERLEN+3], mo[100], mo2[100];
-			char *tkllayer[9] = {
+			const char *tkllayer[9] = {
 				me.name,	/*0  server.name */
 				"+",		/*1  +|- */
 				"?",		/*2  type */
@@ -4554,7 +4553,7 @@ int _place_host_ban(Client *client, BanAction action, char *reason, long duratio
 			tkllayer[7] = mo2;
 			tkllayer[8] = reason;
 			cmd_tkl(&me, NULL, 9, tkllayer);
-			RunHookReturnInt4(HOOKTYPE_PLACE_HOST_BAN, client, action, reason, duration, !=99);
+			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
 			if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
 			{
 				find_shun(client);
@@ -4565,7 +4564,7 @@ int _place_host_ban(Client *client, BanAction action, char *reason, long duratio
 		case BAN_ACT_SOFT_KILL:
 		case BAN_ACT_KILL:
 		default:
-			RunHookReturnInt4(HOOKTYPE_PLACE_HOST_BAN, client, action, reason, duration, !=99);
+			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
 			exit_client(client, NULL, reason);
 			return 1;
 	}
@@ -4618,7 +4617,7 @@ TKL *choose_winning_spamfilter(TKL *one, TKL *two)
 /** Checks if 'target' is on the spamfilter exception list.
  * RETURNS 1 if found in list, 0 if not.
  */
-static int target_is_spamexcept(char *target)
+static int target_is_spamexcept(const char *target)
 {
 	SpamExcept *e;
 
@@ -4638,12 +4637,13 @@ static int target_is_spamexcept(char *target)
  */
 int _join_viruschan(Client *client, TKL *tkl, int type)
 {
-	char *xparv[3], chbuf[CHANNELLEN + 16], buf[2048];
+	const char *xparv[3];
+	char chbuf[CHANNELLEN + 16], buf[2048];
 	Channel *channel;
 	int ret;
 
 	snprintf(buf, sizeof(buf), "0,%s", SPAMFILTER_VIRUSCHAN);
-	xparv[0] = client->name;
+	xparv[0] = NULL;
 	xparv[1] = buf;
 	xparv[2] = NULL;
 
@@ -4658,16 +4658,16 @@ int _join_viruschan(Client *client, TKL *tkl, int type)
 	sendnotice(client, "You are now restricted to talking in %s: %s",
 		SPAMFILTER_VIRUSCHAN, unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
 
-	channel = find_channel(SPAMFILTER_VIRUSCHAN, NULL);
+	channel = find_channel(SPAMFILTER_VIRUSCHAN);
 	if (channel)
 	{
 		MessageTag *mtags = NULL;
-		ircsnprintf(chbuf, sizeof(chbuf), "@%s", channel->chname);
+		ircsnprintf(chbuf, sizeof(chbuf), "@%s", channel->name);
 		ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s matched filter '%s' [%s] [%s]",
 			client->name, tkl->ptr.spamfilter->match->str, cmdname_by_spamftarget(type),
 			unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
 		new_message(&me, NULL, &mtags);
-		sendto_channel(channel, &me, NULL, PREFIX_OP|PREFIX_ADMIN|PREFIX_OWNER,
+		sendto_channel(channel, &me, NULL, "o",
 		               0, SEND_ALL|SKIP_DEAF, mtags,
 		               ":%s NOTICE %s :%s", me.name, chbuf, buf);
 		free_message_tags(mtags);
@@ -4687,11 +4687,11 @@ int _join_viruschan(Client *client, TKL *tkl, int type)
  * 1 if spamfilter matched and it should be blocked (or client exited), 0 if not matched.
  * In case of 1, be sure to check IsDead(client)..
  */
-int _match_spamfilter(Client *client, char *str_in, int target, char *cmd, char *destination, int flags, TKL **rettkl)
+int _match_spamfilter(Client *client, const char *str_in, int target, const char *cmd, const char *destination, int flags, TKL **rettkl)
 {
 	TKL *tkl;
 	TKL *winner_tkl = NULL;
-	char *str;
+	const char *str;
 	int ret = -1;
 	char *reason = NULL;
 #ifdef SPAMFILTER_DETECTSLOW
@@ -4708,7 +4708,7 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *cmd, char 
 	if (target == SPAMF_USER)
 		str = str_in;
 	else
-		str = (char *)StripControlCodes(str_in);
+		str = StripControlCodes(str_in);
 
 	/* (note: using client->user check here instead of IsUser()
 	 * due to SPAMF_USER where user isn't marked as client/person yet.
@@ -4753,22 +4753,26 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *cmd, char 
 
 		if ((SPAMFILTER_DETECTSLOW_FATAL > 0) && (ms_past > SPAMFILTER_DETECTSLOW_FATAL))
 		{
-			sendto_realops("[Spamfilter] WARNING: Too slow spamfilter detected (took %ld msec to execute) "
-			               "-- spamfilter will be \002REMOVED!\002: %s", ms_past, tkl->ptr.spamfilter->match->str);
+			unreal_log(ULOG_ERROR, "tkl", "SPAMFILTER_SLOW_FATAL", NULL,
+			           "[Spamfilter] WARNING: Too slow spamfilter detected (took $msec_time msec to execute) "
+			           "-- spamfilter will be \002REMOVED!\002: $tkl",
+			           log_data_tkl("tkl", tkl),
+			           log_data_integer("msec_time", ms_past));
 			tkl_del_line(tkl);
 			return 0; /* Act as if it didn't match, even if it did.. it's gone now anyway.. */
 		} else
 		if ((SPAMFILTER_DETECTSLOW_WARN > 0) && (ms_past > SPAMFILTER_DETECTSLOW_WARN))
 		{
-			sendto_realops("[Spamfilter] WARNING: SLOW Spamfilter detected (took %ld msec to execute): %s",
-				ms_past, tkl->ptr.spamfilter->match->str);
+			unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_SLOW_WARN", NULL,
+			           "[Spamfilter] WARNING: Slow spamfilter detected (took $msec_time msec to execute): $tkl",
+			           log_data_tkl("tkl", tkl),
+			           log_data_integer("msec_time", ms_past));
 		}
 #endif
 
 		if (ret)
 		{
 			/* We have a match! */
-			char buf[1024];
 			char destinationbuf[48];
 
 			if (destination) {
@@ -4781,15 +4785,14 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *cmd, char 
 			if (!winner_tkl && destination && target_is_spamexcept(destination))
 				return 0; /* No problem! */
 
-			ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s!%s@%s matches filter '%s': [%s%s: '%s'] [%s]",
-				client->name, client->user->username, client->user->realhost,
-				tkl->ptr.spamfilter->match->str,
-				cmd, destinationbuf, str,
-				unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
+			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
+			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
+				   log_data_tkl("tkl", tkl),
+				   log_data_string("command", cmd),
+				   log_data_string("destination", destination ? destination : ""),
+				   log_data_string("str", str));
 
-			sendto_snomask_global(SNO_SPAMF, "%s", buf);
-			ircd_log(LOG_SPAMFILTER, "%s", buf);
-			RunHook6(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
+			RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
 
 			/* If we should stop after the first match, we end here... */
 			if (SPAMFILTER_STOP_ON_FIRST_MATCH)
@@ -4990,7 +4993,7 @@ static int comp_with_mask(void *addr, void *dest, u_int mask)
  * CIDR support is available so 'host' may be like '1.2.0.0/16'.
  * @returns 1 on match, 0 on no match.
  */
-int _match_user(char *rmask, Client *client, int options)
+int _match_user(const char *rmask, Client *client, int options)
 {
 	char mask[NICKLEN+USERLEN+HOSTLEN+8];
 	char clientip[IPSZ], maskip[IPSZ];
@@ -5001,8 +5004,8 @@ int _match_user(char *rmask, Client *client, int options)
 	strlcpy(mask, rmask, sizeof(mask));
 
 	if ((options & MATCH_CHECK_EXTENDED) &&
-	    is_extended_ban(mask) &&
-	    client && client->user)
+	    is_extended_server_ban(mask) &&
+	    client->user)
 	{
 		/* Check user properties / extbans style */
 		return _match_user_extended_server_ban(rmask, client);
@@ -5144,17 +5147,29 @@ int _match_user(char *rmask, Client *client, int options)
 	return 0; /* NOMATCH: nothing of the above matched */
 }
 
-int _match_user_extended_server_ban(char *banstr, Client *client)
+int _match_user_extended_server_ban(const char *banstr, Client *client)
 {
-	char *msg = NULL, *errmsg = NULL;
+	const char *nextbanstr;
 	Extban *extban;
+	BanContext *b;
+	int ret;
 
-	if (!is_extended_ban(banstr))
+	if (!is_extended_server_ban(banstr))
 		return 0; /* we should never have been called */
 
-	extban = findmod_by_bantype(banstr[1]);
-	if (!extban || !(extban->options & EXTBOPT_TKL))
+	extban = findmod_by_bantype(banstr, &nextbanstr);
+	if (!extban ||
+	    !(extban->options & EXTBOPT_TKL) ||
+	    !(extban->is_banned_events & BANCHK_TKL))
+	{
 		return 0; /* extban not found or of incorrect type (eg ~T) */
+	}
 
-	return extban->is_banned(client, NULL, banstr, BANCHK_TKL, &msg, &errmsg);
+	b = safe_alloc(sizeof(BanContext));
+	b->client = client;
+	b->banstr = nextbanstr;
+	b->ban_check_types = BANCHK_TKL;
+	ret = extban->is_banned(b);
+	safe_free(b);
+	return ret;
 }
diff --git a/src/modules/tkldb.c b/src/modules/tkldb.c
@@ -24,7 +24,7 @@ ModuleHeader MOD_HEADER = {
 	"1.10",
 	"Stores active TKL entries (*-Lines) persistently/across IRCd restarts",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 #define TKLDB_MAGIC 0x10101010
@@ -38,8 +38,7 @@ ModuleHeader MOD_HEADER = {
  */
 #define TKLDB_SAVE_EVERY_DELTA +15
 
-#ifdef DEBUGMODE
- #define BENCHMARK
+// #undef BENCHMARK
 /* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
  * 100,000 zlines:
  * - load db: 510 ms
@@ -48,7 +47,6 @@ ModuleHeader MOD_HEADER = {
  * which executes every 5 minutes.
  * Of course, exact figures will depend on the machine.
  */
-#endif
 
 #define FreeTKLRead() \
  	do { \
@@ -59,9 +57,10 @@ ModuleHeader MOD_HEADER = {
 
 #define WARN_WRITE_ERROR(fname) \
 	do { \
-		sendto_realops_and_log("[tkldb] Error writing to temporary database file " \
-		                       "'%s': %s (DATABASE NOT SAVED)", \
-		                       fname, unrealdb_get_error_string()); \
+		unreal_log(ULOG_ERROR, "tkldb", "TKLDB_FILE_WRITE_ERROR", NULL, \
+			   "[tkldb] 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 R_SAFE(x) \
@@ -163,7 +162,7 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
-	if (loop.ircd_terminating)
+	if (loop.terminating)
 		write_tkldb();
 	freecfg(&test);
 	freecfg(&cfg);
@@ -199,34 +198,34 @@ int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!ce || strcmp(ce->ce_varname, "tkldb"))
+	if (!ce || strcmp(ce->name, "tkldb"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
-			config_error("%s:%i: blank set::tkldb::%s without value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%i: blank set::tkldb::%s without value", cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		} else
-		if (!strcmp(cep->ce_varname, "database"))
+		if (!strcmp(cep->name, "database"))
 		{
-			convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
-			safe_strdup(test.database, cep->ce_vardata);
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
 		} else
-		if (!strcmp(cep->ce_varname, "db-secret"))
+		if (!strcmp(cep->name, "db-secret"))
 		{
-			char *err;
-			if ((err = unrealdb_test_secret(cep->ce_vardata)))
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
 			{
-				config_error("%s:%i: set::tkldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+				config_error("%s:%i: set::tkldb::db-secret: %s", cep->file->filename, cep->line_number, err);
 				errors++;
 				continue;
 			}
-			safe_strdup(test.db_secret, cep->ce_vardata);
+			safe_strdup(test.db_secret, cep->value);
 		} else
 		{
-			config_error("%s:%i: unknown directive set::tkldb::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			config_error("%s:%i: unknown directive set::tkldb::%s", cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
 	}
@@ -258,15 +257,15 @@ int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_SET)
 		return 0;
 
-	if (!ce || strcmp(ce->ce_varname, "tkldb"))
+	if (!ce || strcmp(ce->name, "tkldb"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "database"))
-			safe_strdup(cfg.database, cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "db-secret"))
-			safe_strdup(cfg.db_secret, cep->ce_vardata);
+		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;
 }
@@ -370,7 +369,7 @@ int write_tkldb(void)
 #endif
 	if (rename(tmpfname, cfg.database) < 0)
 	{
-		sendto_realops_and_log("[tkldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
+		config_error("[tkldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
 		return 0;
 	}
 #ifdef BENCHMARK
@@ -748,12 +747,13 @@ int read_tkldb(void)
 	unrealdb_close(db);
 
 	if (added_cnt)
-		sendto_realops_and_log("[tkldb] Re-added %d *-Lines", added_cnt);
+		config_status("[tkldb] Re-added %d *-Lines", added_cnt);
 
 #ifdef BENCHMARK
 	gettimeofday(&tv_beta, NULL);
-	ircd_log(LOG_ERROR, "[tkldb] Benchmark: LOAD DB: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+	unreal_log(ULOG_DEBUG, "tkldb", "TKLDB_BENCHMARK", NULL,
+	           "[tkldb] 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;
 }
diff --git a/src/modules/tls_antidos.c b/src/modules/tls_antidos.c
@@ -1,6 +1,6 @@
 /*
- * SSL/TLS Anti DoS module
- * This protects against SSL renegotiation attacks while still allowing us
+ * TLS Anti DoS module
+ * This protects against TLS renegotiation attacks while still allowing us
  * to leave renegotiation on with all it's security benefits.
  *
  * (C) Copyright 2015- Bram Matthys and the UnrealIRCd team.
@@ -16,7 +16,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"TLS Renegotiation DoS protection",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 #define HANDSHAKE_LIMIT_COUNT 3
@@ -81,8 +81,7 @@ void ssl_info_callback(const SSL *ssl, int where, int ret)
 			e->n++;
 			if (e->n >= HANDSHAKE_LIMIT_COUNT)
 			{
-				ircd_log(LOG_ERROR, "TLS Handshake flood detected from %s -- killed", get_client_name(client, TRUE));
-				sendto_realops("TLS Handshake flood detected from %s -- killed", get_client_name(client, TRUE));
+				unreal_log(ULOG_INFO, "flood", "TLS_HANDSHAKE_FLOOD", client, "TLS Handshake flood detected from $client -- killed");
 				dead_socket(client, "TLS Handshake flood detected");
 			}
 		}
@@ -105,7 +104,7 @@ int tls_antidos_handshake(Client *client)
 	return 0;
 }
 
-/** Called by OpenSSL when the SSL structure is freed (so we can free up our custom struct too) */
+/** Called by OpenSSL when the SSL * structure is freed (so we can free up our custom struct too) */
 void tls_antidos_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp)
 {
 	safe_free(ptr);
diff --git a/src/modules/tls_cipher.c b/src/modules/tls_cipher.c
@@ -0,0 +1,91 @@
+/*
+ * Store TLS cipher in ModData
+ * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"tls_cipher",
+	"5.0",
+	"Store and retrieve TLS cipher string",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+void tls_cipher_free(ModData *m);
+const char *tls_cipher_serialize(ModData *m);
+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);
+
+ModDataInfo *tls_cipher_md; /* Module Data structure which we acquire */
+
+MOD_INIT()
+{
+ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "tls_cipher";
+	mreq.free = tls_cipher_free;
+	mreq.serialize = tls_cipher_serialize;
+	mreq.unserialize = tls_cipher_unserialize;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	tls_cipher_md = ModDataAdd(modinfo->handle, mreq);
+	if (!tls_cipher_md)
+		abort();
+
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, tls_cipher_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, tls_cipher_handshake);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int tls_cipher_handshake(Client *client)
+{
+	if (client->local->ssl)
+	{
+		const char *cipher = tls_get_cipher(client);
+
+		if (!cipher)
+			return 0;
+
+		moddata_client_set(client, "tls_cipher", cipher);
+	}
+	return 0;
+}
+
+void tls_cipher_free(ModData *m)
+{
+	safe_free(m->str);
+}
+
+const char *tls_cipher_serialize(ModData *m)
+{
+	if (!m->str)
+		return NULL;
+	return m->str;
+}
+
+void tls_cipher_unserialize(const char *str, ModData *m)
+{
+	safe_strdup(m->str, str);
+}
diff --git a/src/modules/topic.c b/src/modules/topic.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /topic", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -52,17 +52,13 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-void topicoverride(Client *client, Channel *channel, char *topic)
+void topic_operoverride_msg(Client *client, Channel *channel, const char *topic)
 {
-	sendto_snomask(SNO_EYES,
-	    "*** OperOverride -- %s (%s@%s) TOPIC %s \'%s\'",
-	    client->name, client->user->username, client->user->realhost,
-	    channel->chname, topic);
-
-	/* Logging implementation added by XeRXeS */
-	ircd_log(LOG_OVERRIDE, "OVERRIDE: %s (%s@%s) TOPIC %s \'%s\'",
-		client->name, client->user->username, client->user->realhost,
-		channel->chname, topic);
+	unreal_log(ULOG_INFO, "operoverride", "OPEROVERRIDE_TOPIC", client,
+		   "OperOverride: $client.details changed the topic of $channel to '$topic'",
+		   log_data_string("override_type", "topic"),
+		   log_data_string("topic", topic),
+		   log_data_channel("channel", channel));
 }
 
 /** Query or change the channel topic.
@@ -80,13 +76,12 @@ void topicoverride(Client *client, Channel *channel, char *topic)
 CMD_FUNC(cmd_topic)
 {
 	Channel *channel = NULL;
-	char *topic = NULL, *name, *tnick = client->name;
-	char *errmsg = NULL;
+	const char *topic = NULL;
+	const char *name, *tnick = client->name;
+	const char *errmsg = NULL;
 	time_t ttime = 0;
 	int i = 0;
 	Hook *h;
-	int ismember; /* cache: IsMember() */
-	long flags = 0; /* cache: membership flags */
 	MessageTag *mtags = NULL;
 
 	if ((parc < 2) || BadPtr(parv[1]))
@@ -97,20 +92,16 @@ CMD_FUNC(cmd_topic)
 
 	name = parv[1];
 
-	channel = find_channel(parv[1], NULL);
+	channel = find_channel(parv[1]);
 	if (!channel)
 	{
 		sendnumeric(client, ERR_NOSUCHCHANNEL, name);
 		return;
 	}
 
-	ismember = IsMember(client, channel); /* CACHE */
-	if (ismember)
-		flags = get_access(client, channel); /* CACHE */
-
 	if (parc > 2 || SecretChannel(channel))
 	{
-		if (!ismember && !IsServer(client)
+		if (!IsMember(client, channel) && !IsServer(client)
 		    && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL) && !IsULine(client))
 		{
 			sendnumeric(client, ERR_NOTONCHANNEL, name);
@@ -140,7 +131,7 @@ CMD_FUNC(cmd_topic)
 		}
 
 		/* If you're not a member, and you can't view outside channel, deny */
-		if ((!ismember && i == HOOK_DENY) ||
+		if ((!IsMember(client, channel) && i == HOOK_DENY) ||
 		    (is_banned(client,channel,BANCHK_JOIN,NULL,NULL) &&
 		     !ValidatePermissionsForPath("channel:see:topic",client,NULL,channel,NULL)))
 		{
@@ -149,13 +140,12 @@ CMD_FUNC(cmd_topic)
 		}
 
 		if (!channel->topic)
-			sendnumeric(client, RPL_NOTOPIC, channel->chname);
+			sendnumeric(client, RPL_NOTOPIC, channel->name);
 		else
 		{
-			sendnumeric(client, RPL_TOPIC,
-			    channel->chname, channel->topic);
-			sendnumeric(client, RPL_TOPICWHOTIME, channel->chname,
-			    channel->topic_nick, channel->topic_time);
+			sendnumeric(client, RPL_TOPIC, channel->name, channel->topic);
+			sendnumeric(client, RPL_TOPICWHOTIME, channel->name,
+			            channel->topic_nick, (long long)channel->topic_time);
 		}
 		return;
 	}
@@ -173,13 +163,13 @@ CMD_FUNC(cmd_topic)
 			channel->topic_time = ttime;
 
 			new_message(client, recv_mtags, &mtags);
-			RunHook4(HOOKTYPE_TOPIC, client, channel, mtags, topic);
+			RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
 			sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
-			    client->id, channel->chname, channel->topic_nick,
+			    client->id, channel->name, channel->topic_nick,
 			    (long long)channel->topic_time, channel->topic);
 			sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 				       ":%s TOPIC %s :%s",
-				       client->name, channel->chname, channel->topic);
+				       client->name, channel->name, channel->topic);
 			free_message_tags(mtags);
 		}
 		return;
@@ -188,65 +178,79 @@ CMD_FUNC(cmd_topic)
 	/* Topic change. Either locally (check permissions!) or remote, check permissions: */
 	if (IsUser(client))
 	{
-		char *newtopic = NULL;
+		const char *newtopic = NULL;
+		const char *errmsg = NULL;
+		int ret = EX_ALLOW;
+		int operoverride = 0;
 
-		/* +t and not +hoaq ? */
-		if ((channel->mode.mode & MODE_TOPICLIMIT) &&
-		    !is_skochanop(client, channel) && !IsULine(client) && !IsServer(client))
+		for (h = Hooks[HOOKTYPE_CAN_SET_TOPIC]; h; h = h->next)
 		{
-			if (MyUser(client) && !ValidatePermissionsForPath("channel:override:topic", client, NULL, channel, NULL))
+			int n = (*(h->func.intfunc))(client, channel, topic, &errmsg);
+
+			if (n == EX_DENY)
 			{
-				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname);
-				return;
+				ret = n;
+			} else
+			if (n == EX_ALWAYS_DENY)
+			{
+				ret = n;
+				break;
 			}
-			topicoverride(client, channel, topic);
 		}
 
-		/* -t and banned? */
-		newtopic = topic;
-		if (!(channel->mode.mode & MODE_TOPICLIMIT) &&
-		    !is_skochanop(client, channel) && is_banned(client, channel, BANCHK_MSG, &newtopic, &errmsg))
+		if (ret == EX_ALWAYS_DENY)
 		{
-			char buf[512];
+			if (MyUser(client) && errmsg)
+				sendto_one(client, NULL, "%s", errmsg); /* send error, if any */
+
+			if (MyUser(client))
+				return; /* reject the topic set (note: we never block remote sets) */
+		}
 
+		if (ret == EX_DENY)
+		{
 			if (MyUser(client) && !ValidatePermissionsForPath("channel:override:topic", client, NULL, channel, NULL))
 			{
-				ircsnprintf(buf, sizeof(buf), "You cannot change the topic on %s while being banned", channel->chname);
-				sendnumeric(client, ERR_CANNOTDOCOMMAND, "TOPIC",  buf);
-				return;
+				if (errmsg)
+					sendto_one(client, NULL, "%s", errmsg);
+				return; /* reject */
+			} else {
+				operoverride = 1; /* allow */
 			}
-			topicoverride(client, channel, topic);
 		}
-		if (MyUser(client) && newtopic)
-			topic = newtopic; /* process is_banned() changes of topic (eg: text replacement), but only for local clients */
 
-		/* -t, +m, and not +vhoaq */
-		if (((flags&CHFL_OVERLAP) == 0) && (channel->mode.mode & MODE_MODERATED))
+		/* banned? */
+		newtopic = topic;
+		if (!check_channel_access(client, channel, "hoaq") && is_banned(client, channel, BANCHK_MSG, &newtopic, &errmsg))
 		{
 			char buf[512];
 
-			if (MyUser(client) && ValidatePermissionsForPath("channel:override:topic", client, NULL, channel, NULL))
+			if (MyUser(client) && !ValidatePermissionsForPath("channel:override:topic", client, NULL, channel, NULL))
 			{
-				topicoverride(client, channel, topic);
-			} else {
-				/* With +m and -t, only voice and higher may change the topic */
-				ircsnprintf(buf, sizeof(buf), "Voice (+v) or higher is required in order to change the topic on %s (channel is +m)", channel->chname);
+				ircsnprintf(buf, sizeof(buf), "You cannot change the topic on %s while being banned", channel->name);
 				sendnumeric(client, ERR_CANNOTDOCOMMAND, "TOPIC",  buf);
 				return;
 			}
+			operoverride = 1;
 		}
 
+		if (MyUser(client) && newtopic)
+			topic = newtopic; /* process is_banned() changes of topic (eg: text replacement), but only for local clients */
+
+		if (operoverride)
+			topic_operoverride_msg(client, channel, topic);
+
 		/* For local users, run spamfilters and hooks.. */
 		if (MyUser(client))
 		{
 			Hook *tmphook;
 			int n;
 
-			if (match_spamfilter(client, topic, SPAMF_TOPIC, "TOPIC", channel->chname, 0, NULL))
+			if (match_spamfilter(client, topic, SPAMF_TOPIC, "TOPIC", channel->name, 0, NULL))
 				return;
 
 			for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_TOPIC]; tmphook; tmphook = tmphook->next) {
-				topic = (*(tmphook->func.pcharfunc))(client, channel, topic);
+				topic = (*(tmphook->func.stringfunc))(client, channel, topic);
 				if (!topic)
 					return;
 			}
@@ -270,12 +274,12 @@ CMD_FUNC(cmd_topic)
 		channel->topic_time = TStime();
 
 	new_message(client, recv_mtags, &mtags);
-	RunHook4(HOOKTYPE_TOPIC, client, channel, mtags, topic);
+	RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
 	sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
-	    client->id, channel->chname, channel->topic_nick,
+	    client->id, channel->name, channel->topic_nick,
 	    (long long)channel->topic_time, channel->topic);
 	sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 		       ":%s TOPIC %s :%s",
-		       client->name, channel->chname, channel->topic);
+		       client->name, channel->name, channel->topic);
 	free_message_tags(mtags);
 }
diff --git a/src/modules/trace.c b/src/modules/trace.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /trace", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -61,7 +61,7 @@ CMD_FUNC(cmd_trace)
 	int  i;
 	Client *acptr;
 	ConfigItem_class *cltmp;
-	char *tname;
+	const char *tname;
 	int  doall, link_s[MAXCONNECTIONS], link_u[MAXCONNECTIONS];
 	int  cnt = 0, wilds, dow;
 	time_t now;
@@ -73,7 +73,7 @@ CMD_FUNC(cmd_trace)
 	labeled_response_inhibit = 1;
 
 	if (parc > 2)
-		if (hunt_server(client, NULL, ":%s TRACE %s :%s", 2, parc, parv))
+		if (hunt_server(client, NULL, "TRACE", 2, parc, parv))
 			return;
 
 	if (parc > 1)
@@ -98,7 +98,7 @@ CMD_FUNC(cmd_trace)
 		}
 	}
 
-	switch (hunt_server(client, NULL, ":%s TRACE :%s", 1, parc, parv))
+	switch (hunt_server(client, NULL, "TRACE", 1, parc, parv))
 	{
 	  case HUNTED_PASS:	/* note: gets here only if parv[1] exists */
 	  {
@@ -140,8 +140,8 @@ CMD_FUNC(cmd_trace)
 	now = TStime();
 	list_for_each_entry(acptr, &lclient_list, lclient_node)
 	{
-		char *name;
-		char *class;
+		const char *name;
+		const char *class;
 
 		if (!ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL) && (acptr != client))
 			continue;
@@ -182,21 +182,21 @@ CMD_FUNC(cmd_trace)
 						sendnumeric(client, RPL_TRACEOPERATOR,
 						    class, acptr->name,
 						    GetHost(acptr),
-						    now - acptr->local->lasttime);
+						    (long long)(now - acptr->local->last_msg_received));
 					else
 						sendnumeric(client, RPL_TRACEUSER,
 						    class, acptr->name,
 						    acptr->user->realhost,
-						    now - acptr->local->lasttime);
+						    (long long)(now - acptr->local->last_msg_received));
 					cnt++;
 				}
 				break;
 
 			case CLIENT_STATUS_SERVER:
 				sendnumeric(client, RPL_TRACESERVER, class, acptr->local->fd >= 0 ? link_s[acptr->local->fd] : -1,
-				    acptr->local->fd >= 0 ? link_u[acptr->local->fd] : -1, name, *(acptr->serv->by) ?
-				    acptr->serv->by : "*", "*", me.name,
-				    now - acptr->local->lasttime);
+				    acptr->local->fd >= 0 ? link_u[acptr->local->fd] : -1, name, *(acptr->server->by) ?
+				    acptr->server->by : "*", "*", me.name,
+				    (long long)(now - acptr->local->last_msg_received));
 				cnt++;
 				break;
 
diff --git a/src/modules/tsctl.c b/src/modules/tsctl.c
@@ -26,7 +26,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /tsctl", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 CMD_FUNC(cmd_tsctl);
@@ -64,8 +64,10 @@ CMD_FUNC(cmd_tsctl)
 
 	if (parv[1] && !strcasecmp(parv[1], "alltime"))
 	{
-		sendnotice(client, "*** Server=%s TStime=%lld",
-			me.name, (long long)TStime());
+		struct timeval currenttime_tv;
+		gettimeofday(&currenttime_tv, NULL);
+		sendnotice(client, "*** Server=%s TStime=%lld.%ld",
+			me.name, (long long)currenttime_tv.tv_sec, (long)currenttime_tv.tv_usec);
 		sendto_server(client, 0, 0, NULL, ":%s TSCTL alltime", client->id);
 		return;
 	}
diff --git a/src/modules/typing-indicator.c b/src/modules/typing-indicator.c
@@ -28,11 +28,11 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"+typing client tag",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
-int ti_mtag_is_ok(Client *client, char *name, char *value);
-void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int ti_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -69,7 +69,7 @@ MOD_UNLOAD()
 
 /** This function verifies if the client sending the mtag is permitted to do so.
  */
-int ti_mtag_is_ok(Client *client, char *name, char *value)
+int ti_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	/* Require a non-empty parameter */
 	if (BadPtr(value))
@@ -83,7 +83,7 @@ int ti_mtag_is_ok(Client *client, char *name, char *value)
 	return 0;
 }
 
-void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m;
 
diff --git a/src/modules/umode2.c b/src/modules/umode2.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /umode2", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -60,7 +60,7 @@ MOD_UNLOAD()
 
 CMD_FUNC(cmd_umode2)
 {
-	char *xparv[5] = {
+	const char *xparv[5] = {
 		client->name,
 		client->name,
 		parv[1],
diff --git a/src/modules/unreal_server_compat.c b/src/modules/unreal_server_compat.c
@@ -0,0 +1,319 @@
+/*
+ * unreal_server_compat - Compatibility with pre-U6 servers
+ * (C) Copyright 2016-2021 Bram Matthys (Syzop)
+ * License: GPLv2
+ *
+ * Currently the only purpose of this module is to rewrite MODE
+ * and SJOIN lines to older servers so any bans/exempts/invex
+ * will show up with their single letter syntax,
+ * eg "MODE #test +b ~account:someacc" will be rewritten
+ * as "MODE #test +b ~a:someacc".
+ * It uses rather complex mode reparsing techniques to
+ * achieve this, but this was deemed to be the only way
+ * that we could achieve this in a doable way.
+ * The alternative was complicating the mode.c code with
+ * creating multiple strings for multiple clients, and
+ * doing the same in any other MODE change routine.
+ * That would have caused rather intrussive compatibility
+ * code, so I don't want that.
+ * With this we can just rip out the module at some point
+ * that we no longer want to support pre-U6 protocol.
+ * For SJOIN we do something similar, though in that case
+ * it would have been quite doable to handle it in there.
+ * Just figured I would stuff it in here as well, since
+ * it is basically the same case.
+ * -- Syzop
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"unreal_server_compat",
+	"1.0.0",
+	"Provides compatibility with non-U6 servers",
+	"Bram Matthys (Syzop)",
+	"unrealircd-6"
+    };
+
+/* Forward declarations */
+int usc_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length);
+int usc_reparse_mode(char **msg, char *p, int *length);
+int usc_reparse_sjoin(char **msg, char *p, int *length);
+void skip_spaces(char **p);
+void read_until_space(char **p);
+int eat_parameter(char **p);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_PACKET, 0, usc_packet);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int usc_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length)
+{
+	char *p, *buf = *msg;
+
+	/* We are only interested in outgoing servers
+	 * that do not support PROTOCTL NEXTBANS
+	 */
+	if (IsMe(to) || !IsServer(to) || SupportNEXTBANS(to) || !buf || !length || !*length)
+		return 0;
+
+	buf[*length] = '\0'; /* safety */
+
+	p = *msg;
+
+	skip_spaces(&p);
+	/* Skip over message tags */
+	if (*p == '@')
+	{
+		read_until_space(&p);
+		if (*p == '\0')
+			return 0; /* unexpected ending */
+		p++;
+	}
+
+	skip_spaces(&p);
+	if (*p == '\0')
+		return 0;
+
+	/* Skip origin */
+	if (*p == ':')
+	{
+		read_until_space(&p);
+		if (*p == '\0')
+			return 0; /* unexpected ending */
+	}
+
+	skip_spaces(&p);
+	if (*p == '\0')
+		return 0;
+
+	if (!strncmp(p, "MODE ", 5)) /* MODE #channel */
+	{
+		if (!eat_parameter(&p))
+			return 0;
+		/* p now points to #channel */
+
+		/* Now it gets interesting... we have to re-parse and re-write the entire MODE line. */
+		return usc_reparse_mode(msg, p, length);
+	}
+
+	if (!strncmp(p, "SJOIN ", 6)) /* SJOIN timestamp #channel */
+	{
+		if (!eat_parameter(&p) || !eat_parameter(&p))
+			return 0;
+		/* p now points to #channel */
+
+		/* Now it gets interesting... we have to re-parse and re-write the entire SJOIN line. */
+		return usc_reparse_sjoin(msg, p, length);
+	}
+
+	return 0;
+}
+
+int usc_reparse_mode(char **msg, char *p, int *length)
+{
+	static char obuf[8192];
+	char modebuf[512], *mode_buf_p, *para_buf_p;
+	char *channel_name;
+	int i;
+	int n;
+	ParseMode pm;
+	int modes_processed = 0;
+
+	channel_name = p;
+	if (!eat_parameter(&p))
+		return 0;
+
+	mode_buf_p = p;
+	if (!eat_parameter(&p))
+		return 0;
+	strlncpy(modebuf, mode_buf_p, sizeof(modebuf), p - mode_buf_p);
+
+	/* If we get here then it is (for example) a
+	 * MODE #channel +b nick!user@host
+	 * So, has at least one parameter (nick!user@host in the example).
+	 * p now points exactly to the 'n' from nick!user@host.
+	 *
+	 * Now, what we will do:
+	 * everything BEFORE p is the 'header' that we will
+	 * send exactly as-is.
+	 * The only thing we may (potentially) change is
+	 * everything AFTER p!
+	 */
+
+	/* Fill 'obuf' with that 'header' */
+	strlncpy(obuf, *msg, sizeof(obuf), p - *msg);
+	para_buf_p = p;
+
+	/* Now parse the modes */
+	for (n = parse_chanmode(&pm, modebuf, para_buf_p); n; n = parse_chanmode(&pm, NULL, NULL))
+	{
+		/* We only rewrite the parameters, so don't care about paramless modes.. */
+		if (!pm.param)
+			continue;
+
+		if ((pm.modechar == 'b') || (pm.modechar == 'e') || (pm.modechar == 'I'))
+		{
+			const char *result = clean_ban_mask(pm.param, pm.what, &me, 1);
+			strlcat(obuf, result?result:"<invalid>", sizeof(obuf));
+			strlcat(obuf, " ", sizeof(obuf));
+		} else
+		{
+			/* as-is */
+			strlcat(obuf, pm.param, sizeof(obuf));
+			strlcat(obuf, " ", sizeof(obuf));
+		}
+		modes_processed++;
+	}
+
+	/* Send line as-is */
+	if (modes_processed == 0)
+		return 0;
+
+	/* Strip final whitespace */
+	if (obuf[strlen(obuf)-1] == ' ')
+		obuf[strlen(obuf)-1] = '\0';
+
+	if (pm.parabuf && *pm.parabuf)
+	{
+		strlcat(obuf, " ", sizeof(obuf));
+		strlcat(obuf, pm.parabuf, sizeof(obuf));
+	}
+
+	/* Add CRLF */
+	if (obuf[strlen(obuf)-1] != '\n')
+		strlcat(obuf, "\r\n", sizeof(obuf));
+
+	/* Line modified, use it! */
+	*msg = obuf;
+	*length = strlen(obuf);
+
+	return 0;
+}
+
+int usc_reparse_sjoin(char **msg, char *p, int *length)
+{
+	static char obuf[8192];
+	char parabuf[512];
+	char *save = NULL;
+	char *s;
+
+	/* Skip right to the last parameter, the only one we care about */
+	p = strstr(p, " :");
+	if (!p)
+		return 0;
+	p += 2;
+
+	/* Save everything before p, put it in obuf... */
+
+	/* Fill 'obuf' with that 'header' */
+	strlncpy(obuf, *msg, sizeof(obuf), p - *msg);
+
+	/* Put parameters in parabuf so we can trash it :D */
+	strlcpy(parabuf, p, sizeof(parabuf));
+
+	/* Now parse the SJOIN */
+	for (s = strtoken(&save, parabuf, " "); s; s = strtoken(&save, NULL, " "))
+	{
+		if (*s == '<')
+		{
+			/* SJSBY */
+			char *next = strchr(s, '>');
+			const char *result;
+			if (!next)
+			{
+				unreal_log(ULOG_WARNING, "unreal_server_compat", "USC_REPARSE_SJOIN_FAILURE", NULL,
+				           "[unreal_server_compat] usc_reparse_sjoin(): sjoin data '$ban' seemed like a SJSBY but was not??",
+				           log_data_string("ban", s));
+				continue;
+			}
+			if (!strchr("&\"\\", next[1]))
+				goto fallback_usc_reparse_sjoin;
+			*next++ = '\0';
+			result = clean_ban_mask(next+1, MODE_ADD, &me, 1);
+			if (!result)
+			{
+				unreal_log(ULOG_WARNING, "unreal_server_compat", "USC_REPARSE_SJOIN_FAILURE", NULL,
+				           "[unreal_server_compat] usc_reparse_sjoin(): ban '$ban' could not be converted",
+				           log_data_string("ban", s+1));
+				continue;
+			}
+			strlcat(obuf, s, sizeof(obuf)); /* "<123,nick" */
+			strlcat(obuf, ">", sizeof(obuf)); /* > */
+			strlncat(obuf, next, sizeof(obuf), 1); /* & or \" or \\ */
+			strlcat(obuf, result, sizeof(obuf)); /* the converted result */
+			strlcat(obuf, " ", sizeof(obuf));
+		} else
+		if (strchr("&\"\\", *s))
+		{
+			/* +b / +e / +I */
+			const char *result = clean_ban_mask(s+1, MODE_ADD, &me, 1);
+			if (!result)
+			{
+				unreal_log(ULOG_WARNING, "unreal_server_compat", "USC_REPARSE_SJOIN_FAILURE", NULL,
+				           "[unreal_server_compat] usc_reparse_sjoin(): ban '$ban' could not be converted",
+				           log_data_string("ban", s+1));
+				continue;
+			}
+			strlncat(obuf, s, sizeof(obuf), 1);
+			strlcat(obuf, result, sizeof(obuf));
+			strlcat(obuf, " ", sizeof(obuf));
+		} else {
+fallback_usc_reparse_sjoin:
+			strlcat(obuf, s, sizeof(obuf));
+			strlcat(obuf, " ", sizeof(obuf));
+		}
+	}
+
+	/* Strip final whitespace */
+	if (obuf[strlen(obuf)-1] == ' ')
+		obuf[strlen(obuf)-1] = '\0';
+
+	/* Add CRLF */
+	if (obuf[strlen(obuf)-1] != '\n')
+		strlcat(obuf, "\r\n", sizeof(obuf));
+
+	/* And use it! */
+	*msg = obuf;
+	*length = strlen(obuf);
+
+	return 0;
+}
+
+/** Skip space(s), if any. */
+void skip_spaces(char **p)
+{
+	for (; **p == ' '; *p = *p + 1);
+}
+
+/** Keep reading until we hit space. */
+void read_until_space(char **p)
+{
+	for (; **p && (**p != ' '); *p = *p + 1);
+}
+
+int eat_parameter(char **p)
+{
+	read_until_space(p);
+	if (**p == '\0')
+		return 0; /* was just a "MODE #channel" query - wait.. that's weird we are a server sending this :D */
+	skip_spaces(p);
+	if (**p == '\0')
+		return 0; // impossible
+	return 1;
+}
diff --git a/src/modules/unsqline.c b/src/modules/unsqline.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /unsqline", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -58,13 +58,13 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_unsqline)
 {
-	char *tkllayer[6] = {
+	const char *tkllayer[6] = {
 		me.name,           /*0  server.name */
 		"-",               /*1  - */
 		"Q",               /*2  Q   */
 		"*",               /*3  unused */
 		parv[1],           /*4  host */
-		client->name         /*5  whoremoved */
+		client->name       /*5  whoremoved */
 	};
 
 	if (parc < 2)
diff --git a/src/modules/user.c b/src/modules/user.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /user", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -65,8 +65,9 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_user)
 {
-	char *username;
-	char *realname;
+	const char *username;
+	const char *realname;
+	char *p;
 
 	if (!MyConnect(client) || IsServer(client))
 		return;
@@ -83,23 +84,20 @@ CMD_FUNC(cmd_user)
 		return;
 	}
 
-	/* This cuts the username off at @, uh okay.. */
-	if ((username = strchr(parv[1], '@')))
-		*username = '\0';
-
 	username = parv[1];
 	realname = parv[4];
 	
-	if (strlen(username) > USERLEN)
-		username[USERLEN] = '\0'; /* cut-off */
-
 	make_user(client);
 
 	/* set::modes-on-connect */
 	client->umodes |= CONN_MODES;
 	client->user->server = me_hash;
 	strlcpy(client->info, realname, sizeof(client->info));
-	strlcpy(client->user->username, username, USERLEN + 1);
+	strlcpy(client->user->username, username, sizeof(client->user->username));
+
+	/* This cuts the username off at @, uh okay.. */
+	if ((p = strchr(client->user->username, '@')))
+		*p = '\0';
 
 	if (*client->name && is_handshake_finished(client))
 	{
@@ -109,7 +107,7 @@ CMD_FUNC(cmd_user)
 			sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
 				me.name, client->name);
 		}
-		register_user(client, client->name, username, NULL, NULL, NULL);
+		register_user(client);
 		return;
 	}
 }
diff --git a/src/modules/userhost-tag.c b/src/modules/userhost-tag.c
@@ -28,15 +28,15 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"userhost message tag",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
 long CAP_ACCOUNT_TAG = 0L;
 
-int userhost_mtag_is_ok(Client *client, char *name, char *value);
-int userhost_mtag_can_send(Client *target);
-void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int userhost_mtag_is_ok(Client *client, const char *name, const char *value);
+int userhost_mtag_should_send_to_client(Client *target);
+void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -47,7 +47,7 @@ MOD_INIT()
 	memset(&mtag, 0, sizeof(mtag));
 	mtag.name = "unrealircd.org/userhost";
 	mtag.is_ok = userhost_mtag_is_ok;
-	mtag.can_send = userhost_mtag_can_send;
+	mtag.should_send_to_client = userhost_mtag_should_send_to_client;
 	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
 	MessageTagHandlerAdd(modinfo->handle, &mtag);
 
@@ -71,7 +71,7 @@ MOD_UNLOAD()
  * syntax.
  * We simply allow userhost-tag ONLY from servers and with any syntax.
  */
-int userhost_mtag_is_ok(Client *client, char *name, char *value)
+int userhost_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client))
 		return 1;
@@ -79,7 +79,7 @@ int userhost_mtag_is_ok(Client *client, char *name, char *value)
 	return 0;
 }
 
-void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m;
 
@@ -103,7 +103,7 @@ void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag
 }
 
 /** Outgoing filter for this message tag */
-int userhost_mtag_can_send(Client *target)
+int userhost_mtag_should_send_to_client(Client *target)
 {
 	if (IsServer(target) || IsOper(target))
 		return 1;
diff --git a/src/modules/userhost.c b/src/modules/userhost.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /userhost", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -65,6 +65,7 @@ CMD_FUNC(cmd_userhost)
 	char *p;		/* scratch end pointer */
 	char *cn;		/* current name */
 	Client *acptr;
+	char request[BUFSIZE];
 	char response[MAXUSERHOSTREPLIES][NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30];
 	int i;			/* loop counter */
 	int w;
@@ -83,14 +84,15 @@ CMD_FUNC(cmd_userhost)
 	 */
 	response[0][0] = response[1][0] = response[2][0] = response[3][0] = response[4][0] = '\0';
 
-	cn = parv[1];
+	strlcpy(request, parv[1], sizeof(request));
+	cn = request;
 
 	for (w = 0, i = 0; (i < MAXUSERHOSTREPLIES) && cn; i++)
 	{
 		if ((p = strchr(cn, ' ')))
 			*p = '\0';
 
-		if ((acptr = find_person(cn, NULL)))
+		if ((acptr = find_user(cn, NULL)))
 		{
 			ircsnprintf(response[w], NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30,
                             "%s%s=%c%s@%s",
diff --git a/src/modules/userip-tag.c b/src/modules/userip-tag.c
@@ -28,15 +28,15 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"userip message tag",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 	};
 
 /* Variables */
 long CAP_ACCOUNT_TAG = 0L;
 
-int userip_mtag_is_ok(Client *client, char *name, char *value);
-int userip_mtag_can_send(Client *target);
-void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+int userip_mtag_is_ok(Client *client, const char *name, const char *value);
+int userip_mtag_should_send_to_client(Client *target);
+void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
 
 MOD_INIT()
 {
@@ -47,7 +47,7 @@ MOD_INIT()
 	memset(&mtag, 0, sizeof(mtag));
 	mtag.name = "unrealircd.org/userip";
 	mtag.is_ok = userip_mtag_is_ok;
-	mtag.can_send = userip_mtag_can_send;
+	mtag.should_send_to_client = userip_mtag_should_send_to_client;
 	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
 	MessageTagHandlerAdd(modinfo->handle, &mtag);
 
@@ -71,7 +71,7 @@ MOD_UNLOAD()
  * syntax.
  * We simply allow userip-tag ONLY from servers and with any syntax.
  */
-int userip_mtag_is_ok(Client *client, char *name, char *value)
+int userip_mtag_is_ok(Client *client, const char *name, const char *value)
 {
 	if (IsServer(client))
 		return 1;
@@ -79,7 +79,7 @@ int userip_mtag_is_ok(Client *client, char *name, char *value)
 	return 0;
 }
 
-void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
 {
 	MessageTag *m;
 
@@ -103,7 +103,7 @@ void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_l
 }
 
 /** Outgoing filter for this message tag */
-int userip_mtag_can_send(Client *target)
+int userip_mtag_should_send_to_client(Client *target)
 {
 	if (IsServer(target) || IsOper(target))
 		return 1;
diff --git a/src/modules/userip.c b/src/modules/userip.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /userip", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -69,6 +69,7 @@ CMD_FUNC(cmd_userip)
 	char *cn;		/* current name */
 	char *ip, ipbuf[HOSTLEN+1];
 	Client *acptr;
+	char request[BUFSIZE];
 	char response[MAXUSERHOSTREPLIES][NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30];
 	int  i;			/* loop counter */
 	int w;
@@ -88,17 +89,17 @@ CMD_FUNC(cmd_userip)
 	 * and our ircsnprintf() truncates it to fit anyway. There is
 	 * no danger of an overflow here. -Dianora
 	 */
-	response[0][0] = response[1][0] = response[2][0] =
-	    response[3][0] = response[4][0] = '\0';
+	response[0][0] = response[1][0] = response[2][0] = response[3][0] = response[4][0] = '\0';
 
-	cn = parv[1];
+	strlcpy(request, parv[1], sizeof(request));
+	cn = request;
 
 	for (w = 0, i = 0; (i < MAXUSERHOSTREPLIES) && cn; i++)
 	{
 		if ((p = strchr(cn, ' ')))
 			*p = '\0';
 
-		if ((acptr = find_person(cn, NULL)))
+		if ((acptr = find_user(cn, NULL)))
 		{
 			if (!(ip = GetIP(acptr)))
 				ip = "<unknown>";
diff --git a/src/modules/usermodes/Makefile.in b/src/modules/usermodes/Makefile.in
@@ -25,20 +25,23 @@ INCLUDES = ../../include/channel.h \
 	../../include/ircsprintf.h \
 	../../include/license.h \
 	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
-	../../include/numeric.h ../../include/proto.h ../../include/dns.h \
+	../../include/numeric.h ../../include/dns.h \
 	../../include/resource.h ../../include/setup.h \
 	../../include/struct.h ../../include/sys.h \
-	../../include/types.h ../../include/url.h \
+	../../include/types.h \
 	../../include/version.h ../../include/whowas.h
 
 R_MODULES=\
 	noctcp.so censor.so bot.so showwhois.so nokick.so servicebot.so \
-	privacy.so regonlymsg.so secureonlymsg.so privdeaf.so
+	privacy.so regonlymsg.so secureonlymsg.so privdeaf.so wallops.so
 
 MODULES=$(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
 RM=@RM@
 
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
 all: build
 
 build: $(MODULES)
@@ -46,46 +49,6 @@ build: $(MODULES)
 clean:
 	$(RM) -f *.o *.so *~ core
 
-#############################################################################
-#             .so's section
-#############################################################################
-
-noctcp.so: noctcp.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o noctcp.so noctcp.c
-
-censor.so: censor.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o censor.so censor.c
-
-bot.so: bot.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o bot.so bot.c
-
-showwhois.so: showwhois.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o showwhois.so showwhois.c
-
-nokick.so: nokick.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o nokick.so nokick.c
-
-servicebot.so: servicebot.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o servicebot.so servicebot.c
-
-privacy.so: privacy.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o privacy.so privacy.c
-
-regonlymsg.so: regonlymsg.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o regonlymsg.so regonlymsg.c
-
-secureonlymsg.so: secureonlymsg.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o secureonlymsg.so secureonlymsg.c
-
-privdeaf.so: privdeaf.c $(INCLUDES)
+%.so: %.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o privdeaf.so privdeaf.c
+		-o $@ $<
diff --git a/src/modules/usermodes/bot.c b/src/modules/usermodes/bot.c
@@ -28,15 +28,15 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +B",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
 long UMODE_BOT = 0L;
 
 /* Forward declarations */
-int bot_whois(Client *client, Client *acptr);
-int bot_who_status(Client *client, Client *acptr, Channel *channel, Member *cm, char *status, int cansee);
+int bot_whois(Client *client, Client *acptr, NameValuePrioList **list);
+int bot_who_status(Client *client, Client *acptr, Channel *channel, Member *cm, const char *status, int cansee);
 int bot_umode_change(Client *client, long oldmode, long newmode);
 
 MOD_TEST()
@@ -67,17 +67,24 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int bot_whois(Client *requester, Client *acptr)
+int bot_whois(Client *client, Client *target, NameValuePrioList **list)
 {
-	if (IsBot(acptr))
-		sendnumeric(requester, RPL_WHOISBOT, acptr->name, ircnetwork);
+	char buf[512];
+
+	if (!IsBot(target))
+		return 0;
+
+	if (whois_get_policy(client, target, "bot") == WHOIS_CONFIG_DETAILS_NONE)
+		return 0;
+
+	add_nvplist_numeric(list, 0, "bot", client, RPL_WHOISBOT, target->name, NETWORK_NAME);
 
 	return 0;
 }
 
-int bot_who_status(Client *requester, Client *acptr, Channel *channel, Member *cm, char *status, int cansee)
+int bot_who_status(Client *client, Client *target, Channel *channel, Member *cm, const char *status, int cansee)
 {
-	if (IsBot(acptr))
+	if (IsBot(target))
 		return 'B';
 	
 	return 0;
@@ -88,7 +95,7 @@ int bot_umode_change(Client *client, long oldmode, long newmode)
 	if ((newmode & UMODE_BOT) && !(oldmode & UMODE_BOT) && MyUser(client))
 	{
 		/* now +B */
-		char *parv[2];
+		const char *parv[2];
 		parv[0] = client->name;
 		parv[1] = NULL;
 		do_cmd(client, NULL, "BOTMOTD", 1, parv);
diff --git a/src/modules/usermodes/censor.c b/src/modules/usermodes/censor.c
@@ -12,7 +12,7 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +G",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 
@@ -20,7 +20,7 @@ long UMODE_CENSOR = 0L;
 
 #define IsCensored(x) (x->umodes & UMODE_CENSOR)
 
-int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int censor_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
 
 int censor_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int censor_config_run(ConfigFile *, ConfigEntry *, int);
@@ -31,7 +31,7 @@ ConfigItem_badword *conf_badword_message = NULL;
 
 static ConfigItem_badword *copy_badword_struct(ConfigItem_badword *ca, int regex, int regflags);
 
-int censor_stats_badwords_user(Client *client, char *para);
+int censor_stats_badwords_user(Client *client, const char *para);
 
 MOD_TEST()
 {
@@ -79,89 +79,89 @@ int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (type != CONFIG_MAIN)
 		return 0;
 	
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "badword"))
+	if (!ce || !ce->name || strcmp(ce->name, "badword"))
 		return 0; /* not interested */
 
-	if (!ce->ce_vardata)
+	if (!ce->value)
 	{
 		config_error("%s:%i: badword without type",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		return 1;
 	}
-	else if (strcmp(ce->ce_vardata, "message") && strcmp(ce->ce_vardata, "all")) {
+	else if (strcmp(ce->value, "message") && strcmp(ce->value, "all")) {
 /*			config_error("%s:%i: badword with unknown type",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum); -- can't do that.. */
+				ce->file->filename, ce->line_number); -- can't do that.. */
 		return 0; /* unhandled */
 	}
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (config_is_blankorempty(cep, "badword"))
 		{
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "word"))
+		if (!strcmp(cep->name, "word"))
 		{
-			char *errbuf;
+			const char *errbuf;
 			if (has_word)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "badword::word");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::word");
 				continue;
 			}
 			has_word = 1;
-			if ((errbuf = badword_config_check_regex(cep->ce_vardata,1,1)))
+			if ((errbuf = badword_config_check_regex(cep->value,1,1)))
 			{
 				config_error("%s:%i: badword::%s contains an invalid regex: %s",
-					cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum,
-					cep->ce_varname, errbuf);
+					cep->file->filename,
+					cep->line_number,
+					cep->name, errbuf);
 				errors++;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "replace"))
+		else if (!strcmp(cep->name, "replace"))
 		{
 			if (has_replace)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "badword::replace");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::replace");
 				continue;
 			}
 			has_replace = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "action"))
+		else if (!strcmp(cep->name, "action"))
 		{
 			if (has_action)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "badword::action");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::action");
 				continue;
 			}
 			has_action = 1;
-			if (!strcmp(cep->ce_vardata, "replace"))
+			if (!strcmp(cep->value, "replace"))
 				action = 'r';
-			else if (!strcmp(cep->ce_vardata, "block"))
+			else if (!strcmp(cep->value, "block"))
 				action = 'b';
 			else
 			{
 				config_error("%s:%d: Unknown badword::action '%s'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-					cep->ce_vardata);
+					cep->file->filename, cep->line_number,
+					cep->value);
 				errors++;
 			}
 				
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"badword", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"badword", cep->name);
 			errors++;
 		}
 	}
 
 	if (!has_word)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"badword::word");
 		errors++;
 	}
@@ -170,7 +170,7 @@ int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		if (has_replace && action == 'b')
 		{
 			config_error("%s:%i: badword::action is block but badword::replace exists",
-				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+				ce->file->filename, ce->line_number);
 			errors++;
 		}
 	}
@@ -188,41 +188,41 @@ int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_MAIN)
 		return 0;
 	
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "badword"))
+	if (!ce || !ce->name || strcmp(ce->name, "badword"))
 		return 0; /* not interested */
 
-	if (strcmp(ce->ce_vardata, "message") && strcmp(ce->ce_vardata, "all"))
+	if (strcmp(ce->value, "message") && strcmp(ce->value, "all"))
 	        return 0; /* not for us */
 
 	ca = safe_alloc(sizeof(ConfigItem_badword));
 	ca->action = BADWORD_REPLACE;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "action"))
+		if (!strcmp(cep->name, "action"))
 		{
-			if (!strcmp(cep->ce_vardata, "block"))
+			if (!strcmp(cep->value, "block"))
 			{
 				ca->action = BADWORD_BLOCK;
 			}
 		}
-		else if (!strcmp(cep->ce_varname, "replace"))
+		else if (!strcmp(cep->name, "replace"))
 		{
-			safe_strdup(ca->replace, cep->ce_vardata);
+			safe_strdup(ca->replace, cep->value);
 		}
-		else if (!strcmp(cep->ce_varname, "word"))
+		else if (!strcmp(cep->name, "word"))
 		{
 			word = cep;
 		}
 	}
 
-	badword_config_process(ca, word->ce_vardata);
+	badword_config_process(ca, word->value);
 
-	if (!strcmp(ce->ce_vardata, "message"))
+	if (!strcmp(ce->value, "message"))
 	{
 		AddListItem(ca, conf_badword_message);
 	} else
-	if (!strcmp(ce->ce_vardata, "all"))
+	if (!strcmp(ce->value, "all"))
 	{
 		AddListItem(ca, conf_badword_message);
 		return 0; /* pretend we didn't see it, so other modules can handle 'all' as well */
@@ -231,12 +231,12 @@ int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	return 1;
 }
 
-char *stripbadwords_message(char *str, int *blocked)
+const char *stripbadwords_message(const char *str, int *blocked)
 {
 	return stripbadwords(str, conf_badword_message, blocked);
 }
 
-int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int censor_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	int blocked = 0;
 
@@ -253,7 +253,7 @@ int censor_can_send_to_user(Client *client, Client *target, char **text, char **
 	return HOOK_CONTINUE;
 }
 
-int censor_stats_badwords_user(Client *client, char *para)
+int censor_stats_badwords_user(Client *client, const char *para)
 {
 	ConfigItem_badword *words;
 
diff --git a/src/modules/usermodes/noctcp.c b/src/modules/usermodes/noctcp.c
@@ -27,14 +27,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +T",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 long UMODE_NOCTCP = 0L;
 
 #define IsNoCTCP(client)    (client->umodes & UMODE_NOCTCP)
 
-int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int noctcp_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
 
 MOD_TEST()
 {
@@ -63,7 +63,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static int IsACTCP(char *s)
+static int IsACTCP(const char *s)
 {
 	if (!s)
 		return 0;
@@ -74,7 +74,7 @@ static int IsACTCP(char *s)
 	return 0;
 }
 
-int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int noctcp_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	if (MyUser(client) && (sendtype == SEND_TYPE_PRIVMSG) &&
 	    IsNoCTCP(target) && !IsOper(client) && IsACTCP(*text))
diff --git a/src/modules/usermodes/nokick.c b/src/modules/usermodes/nokick.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +q",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
@@ -37,7 +37,7 @@ long UMODE_NOKICK = 0L;
 /* Forward declarations */
 int umode_allow_unkickable_oper(Client *client, int what);
 int nokick_can_kick(Client *client, Client *target, Channel *channel,
-                    char *comment, long client_flags, long target_flags, char **reject_reason);
+                    const char *comment, const char *client_member_modes, const char *target_member_modes, const char **reject_reason);
 
 MOD_TEST()
 {
@@ -76,8 +76,8 @@ int umode_allow_unkickable_oper(Client *client, int what)
 	return 1;
 }
 
-int nokick_can_kick(Client *client, Client *target, Channel *channel, char *comment,
-                    long client_flags, long target_flags, char **reject_reason)
+int nokick_can_kick(Client *client, Client *target, Channel *channel, const char *comment,
+                    const char *client_member_modes, const char *target_member_modes, const char **reject_reason)
 {
 	static char errmsg[NICKLEN+256];
 
@@ -91,7 +91,7 @@ int nokick_can_kick(Client *client, Client *target, Channel *channel, char *comm
 
 		sendnotice(target,
 			"*** umode q: %s tried to kick you from channel %s (%s)",
-			client->name, channel->chname, comment);
+			client->name, channel->name, comment);
 		
 		return EX_ALWAYS_DENY;
 	}
diff --git a/src/modules/usermodes/privacy.c b/src/modules/usermodes/privacy.c
@@ -28,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +p",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
diff --git a/src/modules/usermodes/privdeaf.c b/src/modules/usermodes/privdeaf.c
@@ -11,13 +11,13 @@ ModuleHeader MOD_HEADER
 	"1.2",
 	"Private Messages Deaf (+D) -- by Syzop",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 static long UMODE_PRIVDEAF = 0;
 static Umode *UmodePrivdeaf = NULL;
 
-int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int privdeaf_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
 
 MOD_INIT()
 {
@@ -47,7 +47,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int privdeaf_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	if ((target->umodes & UMODE_PRIVDEAF) && !IsOper(client) &&
 	    !IsULine(client) && !IsServer(client) && (client != target))
diff --git a/src/modules/usermodes/regonlymsg.c b/src/modules/usermodes/regonlymsg.c
@@ -28,14 +28,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +R",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
 long UMODE_REGONLYMSG = 0L;
 
 /* Forward declarations */
-int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int regonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
                     
 MOD_INIT()
 {
@@ -57,11 +57,11 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int regonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	if (IsRegOnlyMsg(target) && !IsServer(client) && !IsULine(client) && !IsLoggedIn(client))
 	{
-		if (ValidatePermissionsForPath("client:override:message:regonlymsg",client,target,NULL,text))
+		if (ValidatePermissionsForPath("client:override:message:regonlymsg",client,target,NULL,text?*text:NULL))
 			return HOOK_CONTINUE; /* bypass this restriction */
 
 		*errmsg = "You must identify to a registered nick to private message this user";
diff --git a/src/modules/usermodes/secureonlymsg.c b/src/modules/usermodes/secureonlymsg.c
@@ -1,5 +1,5 @@
 /*
- * Recieve private messages only from SSL/TLS users (User mode +Z)
+ * Recieve private messages only from TLS users (User mode +Z)
  * (C) Copyright 2000-.. Bram Matthys (Syzop) and the UnrealIRCd team
  * Idea from "Stealth" <stealth@x-tab.org>
  *
@@ -29,14 +29,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +Z",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
 long UMODE_SECUREONLYMSG = 0L;
 
 /* Forward declarations */
-int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int secureonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
                     
 MOD_INIT()
 {
@@ -58,27 +58,27 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+int secureonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
 {
 	if (IsSecureOnlyMsg(target) && !IsServer(client) && !IsULine(client) && !IsSecureConnect(client))
 	{
-		if (ValidatePermissionsForPath("client:override:message:secureonlymsg",client,target,NULL,text))
+		if (ValidatePermissionsForPath("client:override:message:secureonlymsg",client,target,NULL,text?*text:NULL))
 			return HOOK_CONTINUE; /* bypass this restriction */
 
-		*errmsg = "You must be connected via SSL/TLS to message this user";
+		*errmsg = "You must be connected via TLS to message this user";
 		return HOOK_DENY;
 	} else
 	if (IsSecureOnlyMsg(client) && !IsSecureConnect(target) && !IsULine(target))
 	{
-		if (ValidatePermissionsForPath("client:override:message:secureonlymsg",client,target,NULL,text))
+		if (ValidatePermissionsForPath("client:override:message:secureonlymsg",client,target,NULL,text?*text:NULL))
 			return HOOK_CONTINUE; /* bypass this restriction */
 		
 		/* Similar to above but in this case we are +Z and are trying to message
-		 * an SSL user (who does not have +Z set, note the 'else'). This does not
+		 * a secure user (who does not have +Z set, note the 'else'). This does not
 		 * make sense since they could never message back to us. Better block the
 		 * message than leave the user confused.
 		 */
-		*errmsg = "Recipient is not connected via SSL/TLS and you are +Z";
+		*errmsg = "Recipient is not connected via TLS and you are +Z";
 		return HOOK_DENY;
 	}
 
diff --git a/src/modules/usermodes/servicebot.c b/src/modules/usermodes/servicebot.c
@@ -21,8 +21,6 @@
 
 #define IsServiceBot(client)    (client->umodes & UMODE_SERVICEBOT)
 
-#define WHOIS_SERVICE_STRING ":%s 313 %s %s :is a Network Service"
-
 /* Module header */
 ModuleHeader MOD_HEADER
   = {
@@ -30,7 +28,7 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +S",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
@@ -38,11 +36,11 @@ long UMODE_SERVICEBOT = 0L;
 
 /* Forward declarations */
 int servicebot_can_kick(Client *client, Client *target, Channel *channel,
-                    char *comment, long client_flags, long target_flags, char **reject_reason);
+                    const char *comment, const char *client_member_modes, const char *target_member_modes, const char **reject_reason);
 int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
-                    u_int what, int modechar, long my_access, char **reject_reason);
-int servicebot_pre_kill(Client *client, Client *target, char *reason);
-int servicebot_whois(Client *requester, Client *acptr);
+                    u_int what, int modechar, const char *client_access, const char *target_access, const char **reject_reason);
+int servicebot_pre_kill(Client *client, Client *target, const char *reason);
+int servicebot_whois(Client *requester, Client *acptr, NameValuePrioList **list);
 int servicebot_see_channel_in_whois(Client *client, Client *target, Channel *channel);
                     
 MOD_TEST()
@@ -74,8 +72,8 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int servicebot_can_kick(Client *client, Client *target, Channel *channel, char *comment,
-                    long client_flags, long target_flags, char **reject_reason)
+int servicebot_can_kick(Client *client, Client *target, Channel *channel, const char *comment,
+                    const char *client_member_modes, const char *target_member_modes, const char **reject_reason)
 {
 	static char errmsg[NICKLEN+256];
 
@@ -96,7 +94,7 @@ int servicebot_can_kick(Client *client, Client *target, Channel *channel, char *
 }
 
 int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
-                    u_int what, int modechar, long my_access, char **reject_reason)
+                    u_int what, int modechar, const char *client_access, const char *target_access, const char **reject_reason)
 {
 	static char errmsg[NICKLEN+256];
 	
@@ -113,7 +111,7 @@ int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
 	return EX_ALLOW;
 }
 
-int servicebot_pre_kill(Client *client, Client *target, char *reason)
+int servicebot_pre_kill(Client *client, Client *target, const char *reason)
 {
 	if (IsServiceBot(target) && !(ValidatePermissionsForPath("services:servicebot:kill",client,target,NULL,NULL) || IsULine(client)))
 	{
@@ -123,12 +121,15 @@ int servicebot_pre_kill(Client *client, Client *target, char *reason)
 	return EX_ALLOW;
 }
 
-int servicebot_whois(Client *requester, Client *acptr)
+int servicebot_whois(Client *client, Client *target, NameValuePrioList **list)
 {
-	int hideoper = (IsHideOper(acptr) && (requester != acptr) && !IsOper(requester)) ? 1 : 0;
+	int hideoper = (IsHideOper(target) && (client != target) && !IsOper(client)) ? 1 : 0;
 
-	if (IsServiceBot(acptr) && !hideoper)
-		sendto_one(requester, NULL, WHOIS_SERVICE_STRING, me.name, requester->name, acptr->name);
+	if (IsServiceBot(target) && !hideoper &&
+	    (whois_get_policy(client, target, "services") > WHOIS_CONFIG_DETAILS_NONE))
+	{
+		add_nvplist_numeric(list, 0, "services", client, RPL_WHOISOPERATOR, target->name, "a Network Service");
+	}
 
 	return 0;
 }
diff --git a/src/modules/usermodes/showwhois.c b/src/modules/usermodes/showwhois.c
@@ -28,14 +28,14 @@ ModuleHeader MOD_HEADER
 	"4.2",
 	"User Mode +W",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* Global variables */
 long UMODE_SHOWWHOIS = 0L;
 
 /* Forward declarations */
-int showwhois_whois(Client *requester, Client *target);
+int showwhois_whois(Client *requester, Client *target, NameValuePrioList **list);
 
 MOD_TEST()
 {
@@ -62,7 +62,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int showwhois_whois(Client *requester, Client *target)
+int showwhois_whois(Client *requester, Client *target, NameValuePrioList **list)
 {
 	if (IsWhois(target) && (requester != target))
 	{
diff --git a/src/modules/usermodes/wallops.c b/src/modules/usermodes/wallops.c
@@ -0,0 +1,111 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/usermodes/wallops.c
+ *   (C) 2004-2021 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"
+
+CMD_FUNC(cmd_wallops);
+
+#define MSG_WALLOPS 	"WALLOPS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/wallops",
+	"5.0",
+	"command /wallops", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+long UMODE_WALLOP = 0L;        /* send wallops to them */
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, MSG_WALLOPS, cmd_wallops, 1, CMD_USER|CMD_SERVER);
+	UmodeAdd(modinfo->handle, 'w', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_WALLOP);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+#define SendWallops(x)          (!IsMe(x) && IsUser(x) && ((x)->umodes & UMODE_WALLOP))
+
+/** Send a message to all wallops, except one.
+ * @param one		Skip sending the message to this client/direction
+ * @param from		The sender (can not be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_wallops(Client *one, Client *from, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Client *acptr;
+
+	++current_serial;
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (!SendWallops(acptr))
+			continue;
+		if (acptr->direction->local->serial == current_serial)	/* sent message along it already ? */
+			continue;
+		if (acptr->direction == one)
+			continue;	/* ...was the one I should skip */
+		acptr->direction->local->serial = current_serial;
+
+		va_start(vl, pattern);
+		vsendto_prefix_one(acptr->direction, from, NULL, pattern, vl);
+		va_end(vl);
+	}
+}
+
+/*
+** cmd_wallops (write to *all* opers currently online)
+**	parv[1] = message text
+*/
+CMD_FUNC(cmd_wallops)
+{
+	const char *message = parc > 1 ? parv[1] : NULL;
+
+	if (BadPtr(message))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "WALLOPS");
+		return;
+	}
+
+	if (!ValidatePermissionsForPath("chat:wallops",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	sendto_wallops(client->direction, client, ":%s WALLOPS :%s", client->name, message);
+	if (MyUser(client))
+		sendto_prefix_one(client, client, NULL, ":%s WALLOPS :%s", client->name, message);
+}
diff --git a/src/modules/vhost.c b/src/modules/vhost.c
@@ -30,7 +30,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /vhost", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -57,7 +57,8 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_vhost)
 {
 	ConfigItem_vhost *vhost;
-	char *login, *password;
+	char login[HOSTLEN+1];
+	const char *password;
 	char olduser[USERLEN+1];
 
 	if (!MyUser(client))
@@ -70,42 +71,42 @@ CMD_FUNC(cmd_vhost)
 
 	}
 
-	login = parv[1];
-	password = (parc > 2) ? parv[2] : "";
-
 	/* cut-off too long login names. HOSTLEN is arbitrary, we just don't want our
 	 * error messages to be cut off because the user is sending huge login names.
 	 */
-	if (strlen(login) > HOSTLEN)
-		login[HOSTLEN] = '\0';
+	strlcpy(login, parv[1], sizeof(login));
+
+	password = (parc > 2) ? parv[2] : "";
 
 	if (!(vhost = find_vhost(login)))
 	{
-		sendto_snomask(SNO_VHOST,
-		    "[\2vhost\2] Failed login for vhost %s by %s!%s@%s - incorrect password",
-		    login, client->name,
-		    client->user->username,
-		    client->user->realhost);
+		unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client,
+		           "Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]",
+		           log_data_string("reason", "Vhost block not found"),
+		           log_data_string("fail_type", "UNKNOWN_VHOST_NAME"),
+		           log_data_string("vhost_block", login));
 		sendnotice(client, "*** [\2vhost\2] Login for %s failed - password incorrect", login);
 		return;
 	}
 	
 	if (!unreal_mask_match(client, vhost->mask))
 	{
-		sendto_snomask(SNO_VHOST,
-		    "[\2vhost\2] Failed login for vhost %s by %s!%s@%s - host does not match",
-		    login, client->name, client->user->username, client->user->realhost);
+		unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client,
+		           "Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]",
+		           log_data_string("reason", "Host does not match"),
+		           log_data_string("fail_type", "NO_HOST_MATCH"),
+		           log_data_string("vhost_block", login));
 		sendnotice(client, "*** No vHost lines available for your host");
 		return;
 	}
 
 	if (!Auth_Check(client, vhost->auth, password))
 	{
-		sendto_snomask(SNO_VHOST,
-		    "[\2vhost\2] Failed login for vhost %s by %s!%s@%s - incorrect password",
-		    login, client->name,
-		    client->user->username,
-		    client->user->realhost);
+		unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client,
+		           "Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]",
+		           log_data_string("reason", "Authentication failed"),
+		           log_data_string("fail_type", "AUTHENTICATION_FAILED"),
+		           log_data_string("vhost_block", login));
 		sendnotice(client, "*** [\2vhost\2] Login for %s failed - password incorrect", login);
 		return;
 	}
@@ -141,8 +142,8 @@ CMD_FUNC(cmd_vhost)
 	safe_strdup(client->user->virthost, vhost->virthost);
 	if (vhost->virtuser)
 	{
-		strcpy(olduser, client->user->username);
-		strlcpy(client->user->username, vhost->virtuser, USERLEN);
+		strlcpy(olduser, client->user->username, sizeof(olduser));
+		strlcpy(client->user->username, vhost->virtuser, sizeof(client->user->username));
 		sendto_server(client, 0, 0, NULL, ":%s SETIDENT %s", client->id,
 		    client->user->username);
 	}
@@ -161,12 +162,22 @@ CMD_FUNC(cmd_vhost)
 		vhost->virtuser ? vhost->virtuser : "",
 		vhost->virtuser ? "@" : "",
 		vhost->virthost);
-	sendto_snomask(SNO_VHOST,
-	    "[\2vhost\2] %s (%s!%s@%s) is now using vhost %s%s%s",
-	    login, client->name,
-	    vhost->virtuser ? olduser : client->user->username,
-	    client->user->realhost, vhost->virtuser ? vhost->virtuser : "", 
-		vhost->virtuser ? "@" : "", vhost->virthost);
+
+	if (vhost->virtuser)
+	{
+		/* virtuser@virthost */
+		unreal_log(ULOG_INFO, "vhost", "VHOST_SUCCESS", client,
+			   "$client.details is now using vhost $virtuser@$virthost [vhost-block: $vhost_block]",
+			   log_data_string("virtuser", vhost->virtuser),
+			   log_data_string("virthost", vhost->virthost),
+			   log_data_string("vhost_block", login));
+	} else {
+		/* just virthost */
+		unreal_log(ULOG_INFO, "vhost", "VHOST_SUCCESS", client,
+			   "$client.details is now using vhost $virthost [vhost-block: $vhost_block]",
+			   log_data_string("virthost", vhost->virthost),
+			   log_data_string("vhost_block", login));
+	}
 
 	userhost_changed(client);
 }
diff --git a/src/modules/wallops.c b/src/modules/wallops.c
@@ -1,77 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/wallops.c
- *   (C) 2004 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"
-
-CMD_FUNC(cmd_wallops);
-
-#define MSG_WALLOPS 	"WALLOPS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"wallops",
-	"5.0",
-	"command /wallops", 
-	"UnrealIRCd Team",
-	"unrealircd-5",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_WALLOPS, cmd_wallops, 1, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_wallops (write to *all* opers currently online)
-**	parv[1] = message text
-*/
-CMD_FUNC(cmd_wallops)
-{
-	char *message;
-	message = parc > 1 ? parv[1] : NULL;
-
-	if (BadPtr(message))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "WALLOPS");
-		return;
-	}
-
-	if (!ValidatePermissionsForPath("chat:wallops",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	sendto_ops_butone(client->direction, client, ":%s WALLOPS :%s", client->name, message);
-}
diff --git a/src/modules/watch-backend.c b/src/modules/watch-backend.c
@@ -0,0 +1,382 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/watch-backend.c
+ *   (C) 2021 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 WATCH_HASH_TABLE_SIZE 32768
+
+#define WATCHES(client) (moddata_local_client(client, watchCounterMD).i)
+#define WATCH(client) (moddata_local_client(client, watchListMD).ptr)
+
+ModDataInfo *watchCounterMD;
+ModDataInfo *watchListMD;
+static Watch *watchTable[WATCH_HASH_TABLE_SIZE];
+static int watch_initialized = 0;
+static char siphashkey_watch[SIPHASH_KEY_LENGTH];
+
+void dummy_free(ModData *md);
+void watch_free(ModData *md);
+
+int watch_backend_user_quit(Client *client, MessageTag *mtags, const char *comment);
+int _watch_add(char *nick, Client *client, int flags);
+int _watch_check(Client *client, int event, int (*watch_notify)(Client *client, Watch *watch, Link *lp, int event));
+Watch *_watch_get(char *nick);
+int _watch_del(char *nick, Client *client, int flags);
+int _watch_del_list(Client *client, int flags);
+uint64_t hash_watch_nick_name(const char *name);
+
+ModuleHeader MOD_HEADER
+= {
+	"watch-backend",
+	"5.0",
+	"backend for /watch", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	EfunctionAdd(modinfo->handle, EFUNC_WATCH_ADD, _watch_add);
+	EfunctionAdd(modinfo->handle, EFUNC_WATCH_DEL, _watch_del);
+	EfunctionAdd(modinfo->handle, EFUNC_WATCH_DEL_LIST, _watch_del_list);
+	EfunctionAddPVoid(modinfo->handle, EFUNC_WATCH_GET, TO_PVOIDFUNC(_watch_get));
+	EfunctionAdd(modinfo->handle, EFUNC_WATCH_CHECK, _watch_check);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{	
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1); /* or do a complex memory freeing algorithm instead */
+	
+	if (!watch_initialized)
+	{
+		memset(watchTable, 0, sizeof(watchTable));
+		siphash_generate_key(siphashkey_watch);
+		watch_initialized = 1;
+	}
+	
+	memset(&mreq, 0 , sizeof(mreq));
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	mreq.name = "watchCount",
+	mreq.free = dummy_free;
+	watchCounterMD = ModDataAdd(modinfo->handle, mreq);
+	if (!watchCounterMD)
+	{
+		config_error("[%s] Failed to request user watchCount moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
+	memset(&mreq, 0 , sizeof(mreq));
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	mreq.name = "watchList",
+	mreq.free = watch_free;
+	watchListMD = ModDataAdd(modinfo->handle, mreq);
+	if (!watchListMD)
+	{
+		config_error("[%s] Failed to request user watchList moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, watch_backend_user_quit);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void dummy_free(ModData *md)
+{
+}
+
+void watch_free(ModData *md)
+{
+	/* it should have been never requested to free as the module is PERM */
+	if (md)
+	{
+		unreal_log(ULOG_ERROR, "watch-backend", "BUG_WATCH_FREE_MEMORY_LEAK", NULL,
+		           "[BUG] watchList moddata was not freed -- memory leak!");
+	}
+}
+
+int watch_backend_user_quit(Client *client, MessageTag *mtags, const char *comment)
+{
+	/* Clean out list and watch structures -Donwulff */
+	watch_del_list(client, 0);
+	return 0;
+}
+
+/*
+ * _watch_add
+ */
+int _watch_add(char *nick, Client *client, int flags)
+{
+	unsigned int hashv;
+	Watch *watch;
+	Link *lp;
+	
+	
+	/* Get the right bucket... */
+	hashv = hash_watch_nick_name(nick);
+	
+	/* Find the right nick (header) in the bucket, or NULL... */
+	if ((watch = (Watch *)watchTable[hashv]))
+		while (watch && mycmp(watch->nick, nick))
+		 watch = watch->hnext;
+	
+	/* If found NULL (no header for this nick), make one... */
+	if (!watch) {
+		watch = (Watch *)safe_alloc(sizeof(Watch)+strlen(nick));
+		watch->lasttime = timeofday;
+		strcpy(watch->nick, nick);
+		
+		watch->watch = NULL;
+		
+		watch->hnext = watchTable[hashv];
+		watchTable[hashv] = watch;
+	}
+	/* Is this client already on the watch-list? */
+	if ((lp = watch->watch))
+		while (lp && (lp->value.client != client))
+		 lp = lp->next;
+	
+	/* No it isn't, so add it in the bucket and client addint it */
+	if (!lp) {
+		lp = watch->watch;
+		watch->watch = make_link();
+		watch->watch->value.client = client;
+		watch->watch->flags = flags;
+		watch->watch->next = lp;
+		
+		lp = make_link();
+		lp->next = WATCH(client);
+		lp->value.wptr = watch;
+		lp->flags = flags;
+		WATCH(client) = lp;
+		WATCHES(client)++;
+	}
+	
+	return 0;
+}
+
+/*
+ *	_watch_check
+ */
+int _watch_check(Client *client, int event, int (*watch_notify)(Client *client, Watch *watch, Link *lp, int event))
+{
+	unsigned int hashv;
+	Watch *watch;
+	Link *lp;
+
+	/* Get us the right bucket */
+	hashv = hash_watch_nick_name(client->name);
+	
+	/* Find the right header in this bucket */
+	if ((watch = (Watch *)watchTable[hashv]))
+		while (watch && mycmp(watch->nick, client->name))
+		 watch = watch->hnext;
+	if (!watch)
+		return 0;	 /* This nick isn't on watch */
+	
+	/* Update the time of last change to item */
+	watch->lasttime = TStime();
+	
+	/* Send notifies out to everybody on the list in header */
+	for (lp = watch->watch; lp; lp = lp->next)
+	{
+		watch_notify(client, watch, lp, event);
+	}
+	
+	return 0;
+}
+
+/*
+ * _watch_get
+ */
+Watch *_watch_get(char *nick)
+{
+	unsigned int hashv;
+	Watch *watch;
+	
+	hashv = hash_watch_nick_name(nick);
+	
+	if ((watch = (Watch *)watchTable[hashv]))
+		while (watch && mycmp(watch->nick, nick))
+		 watch = watch->hnext;
+	
+	return watch;
+}
+
+/*
+ * _watch_del
+ */
+int _watch_del(char *nick, Client *client, int flags)
+{
+	unsigned int hashv;
+	Watch **watch, *wprev;
+	Link **lp, *prev;
+
+	/* Get the bucket for this nick... */
+	hashv = hash_watch_nick_name(nick);
+	
+	/* Find the right header, maintaining last-link pointer... */
+	watch = (Watch **)&watchTable[hashv];
+	while (*watch && mycmp((*watch)->nick, nick))
+		watch = &(*watch)->hnext;
+	if (!*watch)
+		return 0;	 /* No such watch */
+	
+	/* Find this client from the list of notifies... with last-ptr. */
+	lp = &(*watch)->watch;
+	while (*lp)
+	{
+		if ((*lp)->value.client == client && ((*lp)->flags & flags) == flags)
+			break;
+		lp = &(*lp)->next;
+	}
+	if (!*lp)
+		return 0;	 /* No such client to watch */
+	
+	/* Fix the linked list under header, then remove the watch entry */
+	prev = *lp;
+	*lp = prev->next;
+	free_link(prev);
+	
+	/* Do the same regarding the links in client-record... */
+	lp = (Link **)&WATCH(client);
+	while (*lp && ((*lp)->value.wptr != *watch))
+		lp = &(*lp)->next;
+	
+	/*
+	 * Give error on the odd case... probobly not even neccessary
+	 * No error checking in ircd is unneccessary ;) -Cabal95
+	 */
+	if (!*lp)
+	{
+		unreal_log(ULOG_WARNING, "watch", "BUG_WATCH_DEL", client,
+		           "[BUG] watch_del found a watch entry with no client counterpoint, "
+		           "while processing nick $nick on client $client.details",
+		           log_data_string("nick", nick));
+	} else {
+		prev = *lp;
+		*lp = prev->next;
+		free_link(prev);
+	}
+	/* In case this header is now empty of notices, remove it */
+	if (!(*watch)->watch) {
+		wprev = *watch;
+		*watch = wprev->hnext;
+		safe_free(wprev);
+	}
+	
+	/* Update count of notifies on nick */
+	WATCHES(client)--;
+	
+	return 0;
+}
+
+/*
+ * _watch_del_list
+ */
+int _watch_del_list(Client *client, int flags)
+{
+	unsigned int hashv;
+	Watch *watch;
+	Link **np, **lp, *prev;
+	
+	np = (Link **)&WATCH(client);
+	
+	while (*np) {
+		if (((*np)->flags & flags) != flags)
+		{
+			/* this entry is not fitting requested flags */
+			np = &(*np)->next;
+			continue;
+		}
+		
+		WATCHES(client)--;
+		
+		/* Find the watch-record from hash-table... */
+		watch = (*np)->value.wptr;
+		lp = &(watch->watch);
+		while (*lp && ((*lp)->value.client != client))
+			lp = &(*lp)->next;
+
+		/* Not found, another "worst case" debug error */
+		if (!*lp)
+		{
+			unreal_log(ULOG_WARNING, "watch", "BUG_WATCH_DEL_LIST", client,
+				   "[BUG] watch_del_list found a watch entry with no table counterpoint, "
+				   "while processing client $client.details");
+		} else {
+			/* Fix the watch-list and remove entry */
+			Link *prev = *lp;
+			*lp = prev->next;
+			free_link(prev);
+			
+			/*
+			 * If this leaves a header without notifies,
+			 * remove it. Need to find the last-pointer!
+			 */
+			if (!watch->watch) {
+				Watch **np2, *wprev;
+				
+				hashv = hash_watch_nick_name(watch->nick);
+				
+				np2 = &watchTable[hashv];
+				while (*np2 && *np2 != watch)
+					np2 = &(*np2)->hnext;
+
+				*np2 = watch->hnext;
+
+				safe_free(watch);
+			}
+		}
+		
+		prev = *np; /* Save last pointer processed */
+		*np = prev->next; /* Jump to the next pointer */
+		free_link(prev); /* Free the previous */
+	}
+	
+	if (!flags)
+		WATCHES(client) = 0;
+	
+	return 0;
+}
+
+uint64_t hash_watch_nick_name(const char *name)
+{
+	return siphash_nocase(name, siphashkey_watch) % WATCH_HASH_TABLE_SIZE;
+}
+
diff --git a/src/modules/watch.c b/src/modules/watch.c
@@ -22,9 +22,15 @@
 
 #include "unrealircd.h"
 
-CMD_FUNC(cmd_watch);
+#define MSG_WATCH 	"WATCH"
 
-#define MSG_WATCH 	"WATCH"	
+CMD_FUNC(cmd_watch);
+int watch_user_quit(Client *client, MessageTag *mtags, const char *comment);
+int watch_away(Client *client, MessageTag *mtags, const char *reason, int already_as_away);
+int watch_nickchange(Client *client, MessageTag *mtags, const char *newnick);
+int watch_post_nickchange(Client *client, MessageTag *mtags, const char *oldnick);
+int watch_user_connect(Client *client);
+int watch_notification(Client *client, Watch *watch, Link *lp, int event);
 
 ModuleHeader MOD_HEADER
   = {
@@ -32,13 +38,24 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /watch", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_WATCH, cmd_watch, 1, CMD_USER);
+{	
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	CommandAdd(modinfo->handle, MSG_WATCH, cmd_watch, 1, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, watch_user_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_QUIT, 0, watch_user_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_AWAY, 0, watch_away);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_NICKCHANGE, 0, watch_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_NICKCHANGE, 0, watch_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_POST_LOCAL_NICKCHANGE, 0, watch_post_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_POST_REMOTE_NICKCHANGE, 0, watch_post_nickchange);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, watch_user_connect);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, watch_user_connect);
+
 	return MOD_SUCCESS;
 }
 
@@ -55,44 +72,66 @@ MOD_UNLOAD()
 /*
  * RPL_NOWON	- Online at the moment (Successfully added to WATCH-list)
  * RPL_NOWOFF	- Offline at the moement (Successfully added to WATCH-list)
- * RPL_WATCHOFF	- Successfully removed from WATCH-list.
- * ERR_TOOMANYWATCH - Take a guess :>  Too many WATCH entries.
  */
-static void show_watch(Client *client, char *name, int rpl1, int rpl2, int awaynotify)
+static void show_watch(Client *client, char *name, int awaynotify)
 {
 	Client *target;
 
-	if ((target = find_person(name, NULL)))
+	if ((target = find_user(name, NULL)))
 	{
 		if (awaynotify && target->user->away)
 		{
 			sendnumeric(client, RPL_NOWISAWAY,
 			    target->name, target->user->username,
-			    IsHidden(target) ? target->user->virthost : target->user->
-			    realhost, target->user->lastaway);
+			    IsHidden(target) ? target->user->virthost : target->user->realhost,
+			    (long long)target->user->away_since);
 			return;
 		}
 		
-		sendnumeric(client, rpl1,
+		sendnumeric(client, RPL_NOWON,
+		    target->name, target->user->username,
+		    IsHidden(target) ? target->user->virthost : target->user->realhost,
+		    (long long)target->lastnick);
+	}
+	else
+	{
+		sendnumeric(client, RPL_NOWOFF, name, "*", "*", 0LL);
+	}
+}
+
+/*
+ * RPL_WATCHOFF	- Successfully removed from WATCH-list.
+ */
+static void show_watch_removed(Client *client, char *name)
+{
+	Client *target;
+
+	if ((target = find_user(name, NULL)))
+	{
+		sendnumeric(client, RPL_WATCHOFF,
 		    target->name, target->user->username,
-		    IsHidden(target) ? target->user->virthost : target->user->
-		    realhost, target->lastnick);
+		    IsHidden(target) ? target->user->virthost : target->user->realhost,
+		    (long long)target->lastnick);
 	}
 	else
 	{
-		sendnumeric(client, rpl2, name, "*", "*", 0L);
+		sendnumeric(client, RPL_WATCHOFF, name, "*", "*", 0LL);
 	}
 }
 
 static char buf[BUFSIZE];
 
+#define WATCHES(client) (moddata_local_client(client, watchCounterMD).i)
+#define WATCH(client) (moddata_local_client(client, watchListMD).ptr)
+
 /*
  * cmd_watch
  */
 CMD_FUNC(cmd_watch)
 {
+	char request[BUFSIZE];
 	Client *target;
-	char *s, **pav = parv, *user;
+	char *s, *user;
 	char *p = NULL, *def = "l";
 	int awaynotify = 0;
 	int did_l=0, did_s=0;
@@ -109,7 +148,20 @@ CMD_FUNC(cmd_watch)
 		parv[1] = def;
 	}
 
-	for (s = strtoken(&p, *++pav, " "); s; s = strtoken(&p, NULL, " "))
+
+	ModDataInfo *watchCounterMD = findmoddata_byname("watchCount", MODDATATYPE_LOCAL_CLIENT);
+	ModDataInfo *watchListMD = findmoddata_byname("watchList", MODDATATYPE_LOCAL_CLIENT);
+	
+	if (!watchCounterMD || !watchListMD)
+	{
+		unreal_log(ULOG_ERROR, "watch", "WATCH_BACKEND_MISSING", NULL,
+		           "[watch] moddata unavailable. Is the 'watch-backend' module loaded?");
+		sendnotice(client, "WATCH command is not available at this moment. Please try again later.");
+		return;
+	}
+
+	strlcpy(request, parv[1], sizeof(request));
+	for (s = strtoken(&p, request, " "); s; s = strtoken(&p, NULL, " "))
 	{
 		if ((user = strchr(s, '!')))
 			*user++ = '\0';	/* Not used */
@@ -127,16 +179,18 @@ CMD_FUNC(cmd_watch)
 				continue;
 			if (do_nick_name(s + 1))
 			{
-				if (client->local->watches >= MAXWATCH)
+				if (WATCHES(client) >= MAXWATCH)
 				{
 					sendnumeric(client, ERR_TOOMANYWATCH, s + 1);
 					continue;
 				}
 
-				add_to_watch_hash_table(s + 1, client, awaynotify);
+				watch_add(s + 1, client,
+					WATCH_FLAG_TYPE_WATCH | (awaynotify ? WATCH_FLAG_AWAYNOTIFY : 0)
+					);
 			}
 
-			show_watch(client, s + 1, RPL_NOWON, RPL_NOWOFF, awaynotify);
+			show_watch(client, s + 1, awaynotify);
 			continue;
 		}
 
@@ -148,9 +202,8 @@ CMD_FUNC(cmd_watch)
 		{
 			if (!*(s+1))
 				continue;
-			del_from_watch_hash_table(s + 1, client);
-			show_watch(client, s + 1, RPL_WATCHOFF, RPL_WATCHOFF, 0);
-
+			watch_del(s + 1, client, WATCH_FLAG_TYPE_WATCH);
+			show_watch_removed(client, s + 1);
 			continue;
 		}
 
@@ -160,8 +213,7 @@ CMD_FUNC(cmd_watch)
 		 */
 		if (*s == 'C' || *s == 'c')
 		{
-			hash_del_watch_list(client);
-
+			watch_del_list(client, WATCH_FLAG_TYPE_WATCH);
 			continue;
 		}
 
@@ -173,38 +225,38 @@ CMD_FUNC(cmd_watch)
 		if ((*s == 'S' || *s == 's') && !did_s)
 		{
 			Link *lp;
-			Watch *anptr;
+			Watch *watch;
 			int  count = 0;
 			
 			did_s = 1;
 			
 			/*
 			 * Send a list of how many users they have on their WATCH list
-			 * and how many WATCH lists they are on.
+			 * and how many WATCH lists they are on. This will also include
+			 * other WATCH types if present - we're not checking for
+			 * WATCH_FLAG_TYPE_*.
 			 */
-			anptr = hash_get_watch(client->name);
-			if (anptr)
-				for (lp = anptr->watch, count = 1;
+			watch = watch_get(client->name);
+			if (watch)
+				for (lp = watch->watch, count = 1;
 				    (lp = lp->next); count++)
 					;
-			sendnumeric(client, RPL_WATCHSTAT, client->local->watches, count);
+			sendnumeric(client, RPL_WATCHSTAT, WATCHES(client), count);
 
 			/*
 			 * Send a list of everybody in their WATCH list. Be careful
 			 * not to buffer overflow.
 			 */
-			if ((lp = client->local->watch) == NULL)
-			{
-				sendnumeric(client, RPL_ENDOFWATCHLIST, *s);
-				continue;
-			}
+			lp = WATCH(client);
 			*buf = '\0';
-			strlcpy(buf, lp->value.wptr->nick, sizeof buf);
-			count =
-			    strlen(client->name) + strlen(me.name) + 10 +
-			    strlen(buf);
-			while ((lp = lp->next))
+			count = strlen(client->name) + strlen(me.name) + 10;
+			while (lp)
 			{
+				if (!(lp->flags & WATCH_FLAG_TYPE_WATCH))
+				{
+					lp = lp->next;
+					continue; /* this one is not ours */
+				}
 				if (count + strlen(lp->value.wptr->nick) + 1 >
 				    BUFSIZE - 2)
 				{
@@ -215,8 +267,12 @@ CMD_FUNC(cmd_watch)
 				strcat(buf, " ");
 				strcat(buf, lp->value.wptr->nick);
 				count += (strlen(lp->value.wptr->nick) + 1);
+				
+				lp = lp->next;
 			}
-			sendnumeric(client, RPL_WATCHLIST, buf);
+			if (*buf)
+				/* anything to send */
+				sendnumeric(client, RPL_WATCHLIST, buf);
 
 			sendnumeric(client, RPL_ENDOFWATCHLIST, *s);
 			continue;
@@ -229,19 +285,24 @@ CMD_FUNC(cmd_watch)
 		 */
 		if ((*s == 'L' || *s == 'l') && !did_l)
 		{
-			Link *lp = client->local->watch;
+			Link *lp = WATCH(client);
 
 			did_l = 1;
 
 			while (lp)
 			{
-				if ((target = find_person(lp->value.wptr->nick, NULL)))
+				if (!(lp->flags & WATCH_FLAG_TYPE_WATCH))
+				{
+					lp = lp->next;
+					continue; /* this one is not ours */
+				}
+				if ((target = find_user(lp->value.wptr->nick, NULL)))
 				{
 					sendnumeric(client, RPL_NOWON, target->name,
 					    target->user->username,
 					    IsHidden(target) ? target->user->
 					    virthost : target->user->realhost,
-					    target->lastnick);
+					    (long long)target->lastnick);
 				}
 				/*
 				 * But actually, only show them offline if its a capital
@@ -250,7 +311,7 @@ CMD_FUNC(cmd_watch)
 				else if (isupper(*s))
 					sendnumeric(client, RPL_NOWOFF,
 					    lp->value.wptr->nick, "*", "*",
-					    lp->value.wptr->lasttime);
+					    (long long)lp->value.wptr->lasttime);
 				lp = lp->next;
 			}
 
@@ -264,3 +325,106 @@ CMD_FUNC(cmd_watch)
 		 */
 	}
 }
+
+int watch_user_quit(Client *client, MessageTag *mtags, const char *comment)
+{
+	if (IsUser(client))
+		watch_check(client, WATCH_EVENT_OFFLINE, watch_notification);
+	return 0;
+}
+
+int watch_away(Client *client, MessageTag *mtags, const char *reason, int already_as_away)
+{
+	if (reason)
+		watch_check(client, already_as_away ? WATCH_EVENT_REAWAY : WATCH_EVENT_AWAY, watch_notification);
+	else
+		watch_check(client, WATCH_EVENT_NOTAWAY, watch_notification);
+
+	return 0;
+}
+
+int watch_nickchange(Client *client, MessageTag *mtags, const char *newnick)
+{
+	watch_check(client, WATCH_EVENT_OFFLINE, watch_notification);
+
+	return 0;
+}
+
+int watch_post_nickchange(Client *client, MessageTag *mtags, const char *oldnick)
+{
+	watch_check(client, WATCH_EVENT_ONLINE, watch_notification);
+
+	return 0;
+}
+
+int watch_user_connect(Client *client)
+{
+	watch_check(client, WATCH_EVENT_ONLINE, watch_notification);
+
+	return 0;
+}
+
+int watch_notification(Client *client, Watch *watch, Link *lp, int event)
+{
+	int awaynotify = 0;
+	
+	if (!(lp->flags & WATCH_FLAG_TYPE_WATCH))
+		return 0;
+	
+	if ((event == WATCH_EVENT_AWAY) || (event == WATCH_EVENT_NOTAWAY) || (event == WATCH_EVENT_REAWAY))
+		awaynotify = 1;
+
+	if (!awaynotify)
+	{
+		if (event == WATCH_EVENT_OFFLINE)
+		{
+			sendnumeric(lp->value.client, RPL_LOGOFF,
+			            client->name,
+			            (IsUser(client) ? client->user->username : "<N/A>"),
+			            (IsUser(client) ? (IsHidden(client) ? client->user->virthost : client->user->realhost) : "<N/A>"),
+			            (long long)watch->lasttime);
+		} else {
+			sendnumeric(lp->value.client, RPL_LOGON,
+			            client->name,
+			            (IsUser(client) ? client->user->username : "<N/A>"),
+			            (IsUser(client) ? (IsHidden(client) ? client->user->virthost : client->user->realhost) : "<N/A>"),
+			            (long long)watch->lasttime);
+		}
+	}
+	else
+	{
+		/* AWAY or UNAWAY */
+		if (!(lp->flags & WATCH_FLAG_AWAYNOTIFY))
+			return 0; /* skip away/unaway notification for users not interested in them */
+
+		if (event == WATCH_EVENT_NOTAWAY)
+		{
+			sendnumeric(lp->value.client, RPL_NOTAWAY,
+			    client->name,
+			    (IsUser(client) ? client->user->username : "<N/A>"),
+			    (IsUser(client) ? (IsHidden(client) ? client->user->virthost : client->user->realhost) : "<N/A>"),
+			    (long long)client->user->away_since);
+		} else
+		if (event == RPL_GONEAWAY)
+		{
+			sendnumeric(lp->value.client, RPL_GONEAWAY,
+			            client->name,
+			            (IsUser(client) ? client->user->username : "<N/A>"),
+			            (IsUser(client) ? (IsHidden(client) ? client->user->virthost : client->user->realhost) : "<N/A>"),
+			            (long long)client->user->away_since,
+			            client->user->away);
+		} else
+		if (event == RPL_REAWAY)
+		{
+			sendnumeric(lp->value.client, RPL_REAWAY,
+			            client->name,
+			            (IsUser(client) ? client->user->username : "<N/A>"),
+			            (IsUser(client) ? (IsHidden(client) ? client->user->virthost : client->user->realhost) : "<N/A>"),
+			            (long long)client->user->away_since,
+			            client->user->away);
+		}
+	}
+	
+	return 0;
+}
+
diff --git a/src/modules/webirc.c b/src/modules/webirc.c
@@ -40,7 +40,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"WebIRC/CGI:IRC Support",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
 };
 
 /* Global variables */
@@ -49,14 +49,13 @@ ConfigItem_webirc *conf_webirc = NULL;
 
 /* Forward declarations */
 CMD_FUNC(cmd_webirc);
-int webirc_check_init(Client *client, char *sockn, size_t size);
-int webirc_local_pass(Client *client, char *password);
+int webirc_local_pass(Client *client, const char *password);
 int webirc_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int webirc_config_run(ConfigFile *, ConfigEntry *, int);
 void webirc_free_conf(void);
 void delete_webircblock(ConfigItem_webirc *e);
-char *webirc_md_serialize(ModData *m);
-void webirc_md_unserialize(char *str, ModData *m);
+const char *webirc_md_serialize(ModData *m);
+void webirc_md_unserialize(const char *str, ModData *m);
 void webirc_md_free(ModData *md);
 int webirc_secure_connect(Client *client);
 
@@ -87,7 +86,7 @@ MOD_INIT()
 	mreq.serialize = webirc_md_serialize;
 	mreq.unserialize = webirc_md_unserialize;
 	mreq.free = webirc_md_free;
-	mreq.sync = 1;
+	mreq.sync = MODDATA_SYNC_EARLY;
 	webirc_md = ModDataAdd(modinfo->handle, mreq);
 	if (!webirc_md)
 	{
@@ -96,7 +95,6 @@ MOD_INIT()
 	}
 
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webirc_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_CHECK_INIT, 0, webirc_check_init);
 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PASS, 0, webirc_local_pass);
 	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, webirc_secure_connect);
 
@@ -154,82 +152,81 @@ int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (!ce)
 		return 0;
 	
-	if (!strcmp(ce->ce_varname, "cgiirc"))
+	if (!strcmp(ce->name, "cgiirc"))
 	{
 		config_error("%s:%i: the cgiirc block has been renamed to webirc and "
 		             "the syntax has changed in UnrealIRCd 4",
-		             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
-		need_34_upgrade = 1;
+		             ce->file->filename, ce->line_number);
 		*errs = 1;
 		return -1;
 	}
 
-	if (strcmp(ce->ce_varname, "webirc"))
+	if (strcmp(ce->name, "webirc"))
 		return 0; /* not interested in non-webirc stuff.. */
 
 	/* Now actually go parse the webirc { } block */
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"webirc", cep->ce_varname);
+			config_error_empty(cep->file->filename, cep->line_number,
+				"webirc", cep->name);
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 		{
-			if (cep->ce_vardata || cep->ce_entries)
+			if (cep->value || cep->items)
 				has_mask = 1;
 		}
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 		{
 			if (has_password)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename, 
-					cep->ce_varlinenum, "webirc::password");
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "webirc::password");
 				continue;
 			}
 			has_password = 1;
 			if (Auth_CheckError(cep) < 0)
 				errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "type"))
+		else if (!strcmp(cep->name, "type"))
 		{
 			if (has_type)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "webirc::type");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "webirc::type");
 			}
 			has_type = 1;
-			if (!strcmp(cep->ce_vardata, "webirc"))
+			if (!strcmp(cep->value, "webirc"))
 				webirc_type = WEBIRC_WEBIRC;
-			else if (!strcmp(cep->ce_vardata, "old"))
+			else if (!strcmp(cep->value, "old"))
 				webirc_type = WEBIRC_PASS;
 			else
 			{
 				config_error("%s:%i: unknown webirc::type '%s', should be either 'webirc' or 'old'",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
 		}
 		else
 		{
-			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"webirc", cep->ce_varname);
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"webirc", cep->name);
 			errors++;
 		}
 	}
 	if (!has_mask)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"webirc::mask");
 		errors++;
 	}
 
 	if (!has_password && (webirc_type == WEBIRC_WEBIRC))
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"webirc::password");
 		errors++;
 	}
@@ -239,7 +236,7 @@ int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		config_error("%s:%i: webirc block has type set to 'old' but has a password set. "
 		             "Passwords are not used with type 'old'. Either remove the password or "
 		             "use the 'webirc' method instead.",
-		             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		             ce->file->filename, ce->line_number);
 		errors++;
 	}
 
@@ -255,23 +252,23 @@ int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	if (type != CONFIG_MAIN)
 		return 0;
 	
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "webirc"))
+	if (!ce || !ce->name || strcmp(ce->name, "webirc"))
 		return 0; /* not interested */
 
 	webirc = safe_alloc(sizeof(ConfigItem_webirc));
 	webirc->type = WEBIRC_WEBIRC; /* default */
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "mask"))
+		if (!strcmp(cep->name, "mask"))
 			unreal_add_masks(&webirc->mask, cep);
-		else if (!strcmp(cep->ce_varname, "password"))
+		else if (!strcmp(cep->name, "password"))
 			webirc->auth = AuthBlockToAuthConfig(cep);
-		else if (!strcmp(cep->ce_varname, "type"))
+		else if (!strcmp(cep->name, "type"))
 		{
-			if (!strcmp(cep->ce_vardata, "webirc"))
+			if (!strcmp(cep->value, "webirc"))
 				webirc->type = WEBIRC_WEBIRC;
-			else if (!strcmp(cep->ce_vardata, "old"))
+			else if (!strcmp(cep->value, "old"))
 				webirc->type = WEBIRC_PASS;
 			else
 				abort();
@@ -283,7 +280,7 @@ int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 	return 0;
 }
 
-char *webirc_md_serialize(ModData *m)
+const char *webirc_md_serialize(ModData *m)
 {
 	static char buf[32];
 	if (m->i == 0)
@@ -292,7 +289,7 @@ char *webirc_md_serialize(ModData *m)
 	return buf;
 }
 
-void webirc_md_unserialize(char *str, ModData *m)
+void webirc_md_unserialize(const char *str, ModData *m)
 {
 	m->i = atoi(str);
 }
@@ -303,7 +300,7 @@ void webirc_md_free(ModData *md)
 	md->l = 0;
 }
 
-ConfigItem_webirc *find_webirc(Client *client, char *password, WEBIRCType type, char **errorstr)
+ConfigItem_webirc *find_webirc(Client *client, const char *password, WEBIRCType type, char **errorstr)
 {
 	ConfigItem_webirc *e;
 	char *error = NULL;
@@ -337,7 +334,7 @@ ConfigItem_webirc *find_webirc(Client *client, char *password, WEBIRCType type, 
 #define WEBIRC_STRINGLEN  (sizeof(WEBIRC_STRING)-1)
 
 /* Does the CGI:IRC host spoofing work */
-void dowebirc(Client *client, char *ip, char *host, char *options)
+void dowebirc(Client *client, const char *ip, const char *host, const char *options)
 {
 	char scratch[64];
 
@@ -352,8 +349,7 @@ void dowebirc(Client *client, char *ip, char *host, char *options)
 
 	/* STEP 1: Update client->local->ip
 	   inet_pton() returns 1 on success, 0 on bad input, -1 on bad AF */
-	if ((inet_pton(AF_INET, ip, scratch) != 1) &&
-	    (inet_pton(AF_INET6, ip, scratch) != 1))
+	if (!is_valid_ip(ip))
 	{
 		/* then we have an invalid IP */
 		exit_client(client, NULL, "Invalid IP address");
@@ -371,7 +367,7 @@ void dowebirc(Client *client, char *ip, char *host, char *options)
 		client->local->hostp = NULL;
 	}
 	/* (create new) */
-	if (host && verify_hostname(host))
+	if (host && valid_host(host, 1))
 		client->local->hostp = unreal_create_hostent(host, client->ip);
 
 	/* STEP 4: Update sockhost
@@ -385,8 +381,10 @@ void dowebirc(Client *client, char *ip, char *host, char *options)
 
 	if (options)
 	{
+		char optionsbuf[BUFSIZE];
 		char *name, *p = NULL, *p2;
-		for (name = strtoken(&p, options, " "); name; name = strtoken(&p, NULL, " "))
+		strlcpy(optionsbuf, options, sizeof(optionsbuf));
+		for (name = strtoken(&p, optionsbuf, " "); name; name = strtoken(&p, NULL, " "))
 		{
 			p2 = strchr(name, '=');
 			if (p2)
@@ -413,7 +411,7 @@ void dowebirc(Client *client, char *ip, char *host, char *options)
 /* WEBIRC <pass> "cgiirc" <hostname> <ip> [:option1 [option2...]]*/
 CMD_FUNC(cmd_webirc)
 {
-	char *ip, *host, *password, *options;
+	const char *ip, *host, *password, *options;
 	ConfigItem_webirc *e;
 	char *error = NULL;
 
@@ -440,25 +438,17 @@ CMD_FUNC(cmd_webirc)
 	dowebirc(client, ip, host, options);
 }
 
-int webirc_check_init(Client *client, char *sockn, size_t size)
-{
-	if (IsWEBIRC(client))
-	{
-		strlcpy(sockn, GetIP(client), size); /* use already set value */
-		return HOOK_DENY;
-	}
-	
-	return HOOK_CONTINUE; /* nothing to do */
-}
-
-int webirc_local_pass(Client *client, char *password)
+int webirc_local_pass(Client *client, const char *password)
 {
 	if (!strncmp(password, WEBIRC_STRING, WEBIRC_STRINGLEN))
 	{
+		char buf[512];
 		char *ip, *host;
 		ConfigItem_webirc *e;
 		char *error = NULL;
 
+		/* Work on a copy as we may trash it */
+		strlcpy(buf, password, sizeof(buf));
 		e = find_webirc(client, NULL, WEBIRC_PASS, &error);
 		if (e)
 		{
@@ -467,7 +457,7 @@ int webirc_local_pass(Client *client, char *password)
 			 * The <resolvedhostname> has been checked ip->host AND host->ip by CGI:IRC itself
 			 * already so we trust it.
 			 */
-			ip = password + WEBIRC_STRINGLEN;
+			ip = buf + WEBIRC_STRINGLEN;
 			host = strchr(ip, '_');
 			if (!host)
 			{
diff --git a/src/modules/webredir.c b/src/modules/webredir.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"1.0",
 	"Do 301 redirect for HEAD/GET/POST/PUT commands", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 struct {
@@ -116,36 +116,36 @@ int webredir_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 
 	/* We are only interrested in set::webredir... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "webredir"))
+	if (!ce || !ce->name || strcmp(ce->name, "webredir"))
 		return 0;
 
 	nowebredir = 0;
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!cep->ce_vardata)
+		if (!cep->value)
 		{
 			config_error("%s:%i: set::webredir::%s with no value",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
-		else if (!strcmp(cep->ce_varname, "url"))
+		else if (!strcmp(cep->name, "url"))
 		{
-			if (!*cep->ce_vardata || strchr(cep->ce_vardata, ' '))
+			if (!*cep->value || strchr(cep->value, ' '))
 			{
 				config_error("%s:%i: set::webredir::%s with empty value",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+					cep->file->filename, cep->line_number, cep->name);
 				errors++;
 			}
-			if (!strstr(cep->ce_vardata, "://") || !strcmp(cep->ce_vardata, "https://..."))
+			if (!strstr(cep->value, "://") || !strcmp(cep->value, "https://..."))
 			{
 				config_error("%s:%i: set::webredir::url needs to be a valid URL",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+					cep->file->filename, cep->line_number);
 				errors++;
 			}
 			if (has_url)
 			{
-				config_warn_duplicate(cep->ce_fileptr->cf_filename,
-					cep->ce_varlinenum, "set::webredir::url");
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "set::webredir::url");
 				continue;
 			}
 			has_url = 1;
@@ -153,14 +153,14 @@ int webredir_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		else
 		{
 			config_error("%s:%i: unknown directive set::webredir::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 		}
 	}
 
 	if (!has_url)
 	{
-		config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
+		config_error_missing(ce->file->filename, ce->line_number,
 			"set::webredir::url");
 		errors++;
 	}
@@ -177,14 +177,14 @@ int webredir_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		return 0;
 	
 	/* We are only interrested in set::webredir... */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "webredir"))
+	if (!ce || !ce->name || strcmp(ce->name, "webredir"))
 		return 0;
 	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "url"))
+		if (!strcmp(cep->name, "url"))
 		{
-			safe_strdup(cfg.url, cep->ce_vardata);
+			safe_strdup(cfg.url, cep->value);
 		}
 	}
 	return 1;
diff --git a/src/modules/websocket.c b/src/modules/websocket.c
@@ -6,6 +6,7 @@
  */
    
 #include "unrealircd.h"
+#include "dns.h"
 
 #define WEBSOCKET_VERSION "1.1.0"
 
@@ -15,7 +16,7 @@ ModuleHeader MOD_HEADER
 	WEBSOCKET_VERSION,
 	"WebSocket support (RFC6455)",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 #if CHAR_MIN < 0
@@ -40,6 +41,8 @@ struct WebSocketUser {
 	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)
@@ -56,19 +59,33 @@ struct WebSocketUser {
 #define WSOP_PING         0x09
 #define WSOP_PONG         0x0a
 
+/* 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 */
 int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 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, char *readbuf, 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, char *readbuf, int length);
-int websocket_handle_handshake(Client *client, char *readbuf, int *length);
+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, char *buf, int len);
-int websocket_handle_packet_pong(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_create_packet(int opcode, char **buf, int *len);
-int websocket_send_pong(Client *client, char *buf, int len);
+int websocket_send_pong(Client *client, const char *buf, int len);
+int websocket_secure_connect(Client *client);
+struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input);
+int websocket_ip_compare(const char *ip1, const char *ip2);
 
 /* Global variables */
 ModDataInfo *websocket_md;
@@ -89,6 +106,7 @@ MOD_INIT()
 	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";
@@ -114,10 +132,6 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-#ifndef CheckNull
- #define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
-#endif
-
 int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
@@ -129,16 +143,16 @@ int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 		return 0;
 
 	/* We are only interrested in listen::options::websocket.. */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "websocket"))
+	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
 		return 0;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "type"))
+		if (!strcmp(cep->name, "type"))
 		{
 			CheckNull(cep);
 			has_type = 1;
-			if (!strcmp(cep->ce_vardata, "text"))
+			if (!strcmp(cep->value, "text"))
 			{
 				if (non_utf8_nick_chars_in_use && !errored_once_nick)
 				{
@@ -153,19 +167,33 @@ int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 					errors++;
 				}
 			}
-			else if (!strcmp(cep->ce_vardata, "binary"))
+			else if (!strcmp(cep->value, "binary"))
 			{
 			}
 			else
 			{
 				config_error("%s:%i: listen::options::websocket::type must be either 'binary' or 'text' (not '%s')",
-					cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
+					cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
+		} else if (!strcmp(cep->name, "forward"))
+		{
+			if (!cep->value)
+			{
+				config_error_empty(cep->file->filename, cep->line_number, "listen::options::websocket::forward", cep->name);
+				errors++;
+				continue;
+			}
+			if (!is_valid_ip(cep->value))
+			{
+				config_error("%s:%i: invalid IP address '%s' in listen::options::websocket::forward", cep->file->filename, cep->line_number, cep->value);
+				errors++;
+				continue;
+			}
 		} else
 		{
 			config_error("%s:%i: unknown directive listen::options::websocket::%s",
-				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+				cep->file->filename, cep->line_number, cep->name);
 			errors++;
 			continue;
 		}
@@ -174,7 +202,7 @@ int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	if (!has_type)
 	{
 		config_error("%s:%i: websocket set, but type unspecified. Use something like: listen { ip *; port 443; websocket { type text; } }",
-			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			ce->file->filename, ce->line_number);
 		errors++;
 	}
 
@@ -192,18 +220,18 @@ int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr
 		return 0;
 
 	/* We are only interrested in listen::options::websocket.. */
-	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "websocket"))
+	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
 		return 0;
 
 	l = (ConfigItem_listen *)ptr;
 
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->ce_varname, "type"))
+		if (!strcmp(cep->name, "type"))
 		{
-			if (!strcmp(cep->ce_vardata, "binary"))
+			if (!strcmp(cep->value, "binary"))
 				l->websocket_options = WEBSOCKET_TYPE_BINARY;
-			else if (!strcmp(cep->ce_vardata, "text"))
+			else if (!strcmp(cep->value, "text"))
 			{
 				l->websocket_options = WEBSOCKET_TYPE_TEXT;
 				if ((tempiConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY) && !warned_once_channel)
@@ -217,6 +245,9 @@ int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr
 					warned_once_channel = 1;
 				}
 			}
+		} else if (!strcmp(cep->name, "forward"))
+		{
+			safe_strdup(l->websocket_forward, cep->value);
 		}
 	}
 	return 1;
@@ -230,6 +261,8 @@ void websocket_mdata_free(ModData *m)
 	{
 		safe_free(wsu->handshake_key);
 		safe_free(wsu->lefttoparse);
+		safe_free(wsu->sec_websocket_protocol);
+		safe_free(wsu->forwarded);
 		safe_free(m->ptr);
 	}
 }
@@ -239,6 +272,8 @@ void websocket_mdata_free(ModData *m)
  */
 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 (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
@@ -246,7 +281,7 @@ int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **m
 		else if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_TEXT)
 		{
 			/* Some more conversions are needed */
-			char *safe_msg = unrl_utf8_make_valid(*msg);
+			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);
@@ -256,7 +291,7 @@ int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **m
 	return 0;
 }
 
-int websocket_handle_websocket(Client *client, char *readbuf2, int length2)
+int websocket_handle_websocket(Client *client, const char *readbuf2, int length2)
 {
 	int n;
 	char *ptr;
@@ -308,9 +343,13 @@ int websocket_handle_websocket(Client *client, char *readbuf2, int length2)
  * 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)
  */
-int websocket_packet_in(Client *client, char *readbuf, int *length)
+int websocket_packet_in(Client *client, const char *readbuf, int *length)
 {
-	if ((client->local->receiveM == 0) && WEBSOCKET_PORT(client) && !WSU(client) && (*length > 8) && !strncmp(readbuf, "GET ", 4))
+	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));
@@ -461,6 +500,141 @@ int websocket_handshake_helper(char *buffer, int len, char **key, char **value, 
 	return 0;
 }
 
+#define FHEADER_STATE_NAME	0
+#define FHEADER_STATE_VALUE	1
+#define FHEADER_STATE_VALUE_QUOTED	2
+
+#define FHEADER_ACTION_APPEND	0
+#define FHEADER_ACTION_IGNORE	1
+#define FHEADER_ACTION_PROCESS	2
+
+/** If a valid Forwarded: http header is received from a trusted source (proxy server), this function will
+  * extract remote IP address and secure (https) status from it. If more than one field with same name is received,
+  * we'll accept the last one. This should work correctly with chained proxies. */
+struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input)
+{
+	static struct HTTPForwardedHeader forwarded;
+	int i, length;
+	int state = FHEADER_STATE_NAME, action = FHEADER_ACTION_APPEND;
+	char name[FHEADER_NAMELEN+1];
+	char value[IPLEN+1];
+	int name_length = 0;
+	int value_length = 0;
+	char c;
+	
+	memset(&forwarded, 0, sizeof(struct HTTPForwardedHeader));
+	
+	length = strlen(input);
+	for (i = 0; i < length; i++)
+	{
+		c = input[i];
+		switch (c)
+		{
+			case '"':
+				switch (state)
+				{
+					case FHEADER_STATE_NAME:
+						action = FHEADER_ACTION_APPEND;
+						break;
+					case FHEADER_STATE_VALUE:
+						action = FHEADER_ACTION_IGNORE;
+						state = FHEADER_STATE_VALUE_QUOTED;
+						break;
+					case FHEADER_STATE_VALUE_QUOTED:
+						action = FHEADER_ACTION_IGNORE;
+						state = FHEADER_STATE_VALUE;
+						break;
+				}
+				break;
+			case ',': case ';': case ' ':
+				switch (state)
+				{
+					case FHEADER_STATE_NAME: /* name without value */
+						name_length = 0;
+						action = FHEADER_ACTION_IGNORE;
+						break;
+					case FHEADER_STATE_VALUE: /* end of value */
+						action = FHEADER_ACTION_PROCESS;
+						break;
+					case FHEADER_STATE_VALUE_QUOTED: /* quoted character, process as normal */
+						action = FHEADER_ACTION_APPEND;
+						break;
+				}
+				break;
+			case '=':
+				switch (state)
+				{
+					case FHEADER_STATE_NAME: /* end of name */
+						name[name_length] = '\0';
+						state = FHEADER_STATE_VALUE;
+						action = FHEADER_ACTION_IGNORE;
+						break;
+					case FHEADER_STATE_VALUE: case FHEADER_STATE_VALUE_QUOTED: /* none of the values is expected to contain = but proceed anyway */
+						action = FHEADER_ACTION_APPEND;
+						break;
+				}
+				break;
+			default:
+				action = FHEADER_ACTION_APPEND;
+				break;
+		}
+		switch (action)
+		{
+			case FHEADER_ACTION_APPEND:
+				if (state == FHEADER_STATE_NAME)
+				{
+					if (name_length < FHEADER_NAMELEN)
+					{
+						name[name_length++] = c;
+					} else
+					{
+						/* truncate */
+					}
+				} else
+				{
+					if (value_length < IPLEN)
+					{
+						value[value_length++] = c;
+					} else
+					{
+						/* truncate */
+					}
+				}
+				break;
+			case FHEADER_ACTION_IGNORE: default:
+				break;
+			case FHEADER_ACTION_PROCESS:
+				value[value_length] = '\0';
+				name[name_length] = '\0';
+				if (!strcasecmp(name, "for"))
+				{
+					strlcpy(forwarded.ip, value, IPLEN+1);
+				} else if (!strcasecmp(name, "proto"))
+				{
+					if (!strcasecmp(value, "https"))
+					{
+						forwarded.secure = 1;
+					} else if (!strcasecmp(value, "http"))
+					{
+						forwarded.secure = 0;
+					} else
+					{
+						/* ignore unknown value */
+					}
+				} else
+				{
+					/* ignore unknown field name */
+				}
+				value_length = 0;
+				name_length = 0;
+				state = FHEADER_STATE_NAME;
+				break;
+		}
+	}
+	
+	return &forwarded;
+}
+
 /** Finally, validate the websocket request (handshake) and proceed or reject. */
 int websocket_handshake_valid(Client *client)
 {
@@ -468,7 +642,7 @@ int websocket_handshake_valid(Client *client)
 	{
 		if (is_module_loaded("webredir"))
 		{
-			char *parx[2] = { NULL, NULL };
+			const char *parx[2] = { NULL, NULL };
 			do_cmd(client, NULL, "GET", 1, parx);
 		}
 		dead_socket(client, "Invalid WebSocket request");
@@ -510,13 +684,83 @@ int websocket_handshake_valid(Client *client)
 			safe_free(WSU(client)->sec_websocket_protocol);
 		}
 	}
+	if (WSU(client)->forwarded)
+	{
+		/* check for source ip */
+		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");
+			return 0;
+		}
+		/* parse the header */
+		struct HTTPForwardedHeader *forwarded;
+		forwarded = websocket_parse_forwarded_header(WSU(client)->forwarded);
+		/* check header values */
+		if (!is_valid_ip(forwarded->ip))
+		{
+			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");
+			return 0;
+		}
+		/* store data */
+		WSU(client)->secure = forwarded->secure;
+		safe_strdup(client->ip, forwarded->ip);
+		/* Update client->local->hostp */
+		strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
+		/* (free old) */
+		if (client->local->hostp)
+		{
+			unreal_free_hostent(client->local->hostp);
+			client->local->hostp = NULL;
+		}
+		/* (create new) */
+		if (!DONT_RESOLVE)
+		{
+			/* taken from socket.c */
+			struct hostent *he;
+			unrealdns_delreq_bycptr(client); /* in case the proxy ip is still in progress of being looked up */
+			ClearDNSLookup(client);
+			he = unrealdns_doclient(client); /* call this once more */
+			if (!client->local->hostp)
+			{
+				if (he)
+					client->local->hostp = he;
+				else
+					SetDNSLookup(client);
+			} else
+			{
+				/* Race condition detected, DNS has been done, continue with auth */
+			}
+		}
+		/* blacklist_start_check() */
+		if (RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK] != NULL)
+			RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK]->func.intfunc(client);
+
+		/* Check (g)zlines right now; these are normally checked upon accept(),
+		 * but since we know the IP only now after PASS/WEBIRC, we have to check
+		 * here again...
+		 */
+		check_banned(client, 0);
+	}
 	return 1;
 }
 
+int websocket_secure_connect(Client *client)
+{
+	/* Remove secure mode (-z) if the WEBIRC gateway did not ensure
+	 * us that their [client]--[webirc gateway] connection is also
+	 * secure (eg: using https)
+	 */
+	if (IsSecureConnect(client) && 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, char *readbuf, int *length)
+int websocket_handle_handshake(Client *client, const char *readbuf, int *length)
 {
 	char *key, *value;
 	int r, end_of_request;
@@ -566,12 +810,17 @@ int websocket_handle_handshake(Client *client, char *readbuf, int *length)
 		{
 			/* 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))
+		if (!websocket_handshake_valid(client) || IsDead(client))
 			return -1;
 		websocket_handshake_send_response(client);
 		return 0;
@@ -589,16 +838,12 @@ int websocket_handle_handshake(Client *client, char *readbuf, int *length)
 int websocket_handshake_send_response(Client *client)
 {
 	char buf[512], hashbuf[64];
-	SHA_CTX hash;
 	char sha1out[20]; /* 160 bits */
 
 	WSU(client)->handshake_completed = 1;
 
 	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
-	SHA1_Init(&hash);
-	SHA1_Update(&hash, buf, strlen(buf));
-	SHA1_Final(sha1out, &hash);
-
+	sha1hash_binary(sha1out, buf, strlen(buf));
 	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
 
 	snprintf(buf, sizeof(buf),
@@ -660,14 +905,16 @@ void add_lf_if_needed(char **buf, int *len)
  *          OR 0 to indicate a possible short read (want more data)
  *          OR -1 in case of an error.
  */
-int websocket_handle_packet(Client *client, char *readbuf, int length)
+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 */
-	char *p, *payload;
+	const char *p;
 	int total_packet_size;
+	char *payload = NULL;
+	static char payloadbuf[READBUF_SIZE];
 
 	if (length < 4)
 	{
@@ -728,14 +975,20 @@ int websocket_handle_packet(Client *client, char *readbuf, int length)
 
 	memcpy(maskkey, p, 4);
 	p+= 4;
-	payload = (len > 0) ? p : NULL;
+
+	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;
-		for (n = 0; n < len; n++)
+		char *p;
+		for (p = payload, n = 0; n < len; n++)
 		{
 			v = *p;
 			*p++ = v ^ maskkey[n % 4];
@@ -777,7 +1030,7 @@ int websocket_handle_packet(Client *client, char *readbuf, int length)
 	return -1; /* NOTREACHED */
 }
 
-int websocket_handle_packet_ping(Client *client, char *buf, int len)
+int websocket_handle_packet_ping(Client *client, const char *buf, int len)
 {
 	if (len > 500)
 	{
@@ -785,11 +1038,11 @@ int websocket_handle_packet_ping(Client *client, char *buf, int len)
 		return -1;
 	}
 	websocket_send_pong(client, buf, len);
-	client->local->since++; /* lag penalty of 1 second */
+	add_fake_lag(client, 1000); /* lag penalty of 1 second */
 	return 0;
 }
 
-int websocket_handle_packet_pong(Client *client, char *buf, int len)
+int websocket_handle_packet_pong(Client *client, const char *buf, int len)
 {
 	/* We don't care */
 	return 0;
@@ -799,7 +1052,7 @@ int websocket_handle_packet_pong(Client *client, char *buf, int len)
  * 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, char **buf, int *len)
+int websocket_create_packet_simple(int opcode, const char **buf, int *len)
 {
 	static char sendbuf[8192];
 
@@ -871,8 +1124,12 @@ int websocket_create_packet(int opcode, char **buf, int *len)
 		if (bytes_in_sendbuf + bytes_single_frame > sizeof(sendbuf))
 		{
 			/* Overflow. This should never happen. */
-			sendto_ops("[websocket] [BUG] Overflow prevented: %d + %d > %d",
-				bytes_in_sendbuf, bytes_single_frame, (int)sizeof(sendbuf));
+			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;
 		}
 
@@ -906,9 +1163,9 @@ int websocket_create_packet(int opcode, char **buf, int *len)
 }
 
 /** Create and send a WSOP_PONG frame */
-int websocket_send_pong(Client *client, char *buf, int len)
+int websocket_send_pong(Client *client, const char *buf, int len)
 {
-	char *b = buf;
+	const char *b = buf;
 	int l = len;
 
 	if (websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
@@ -924,3 +1181,33 @@ int websocket_send_pong(Client *client, char *buf, int len)
 	send_queued(client);
 	return 0;
 }
+
+/** Compare IP addresses (for authorization checking) */
+int websocket_ip_compare(const char *ip1, const char *ip2)
+{
+	uint32_t ip4[2];
+	uint16_t ip6[16];
+	int i;
+	if (inet_pton(AF_INET, ip1, &ip4[0]) == 1) /* IPv4 */
+	{
+		if (inet_pton(AF_INET, ip2, &ip4[1]) == 1) /* both are valid, let's compare */
+		{
+			return ip4[0] == ip4[1];
+		}
+		return 0;
+	}
+	if (inet_pton(AF_INET6, ip1, &ip6[0]) == 1) /* IPv6 */
+	{
+		if (inet_pton(AF_INET6, ip2, &ip6[8]) == 1)
+		{
+			for (i = 0; i < 8; i++)
+			{
+				if (ip6[i] != ip6[i+8])
+					return 0;
+			}
+			return 1;
+		}
+	}
+	return 0; /* neither valid IPv4 nor IPv6 */
+}
+
diff --git a/src/modules/who_old.c b/src/modules/who_old.c
@@ -36,7 +36,7 @@ ModuleHeader MOD_HEADER
 	"5.0", /* Version */
 	"command /who (old version)", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 /* This is called on module init, before Server Ready */
@@ -64,12 +64,12 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static void do_channel_who(Client *client, Channel *channel, char *mask);
+static void do_channel_who(Client *client, Channel *channel, const char *mask);
 static void make_who_status(Client *, Client *, Channel *, Member *, char *, int);
-static void do_other_who(Client *client, char *mask);
-static void send_who_reply(Client *, Client *, char *, char *, char *);
-static char *first_visible_channel(Client *, Client *, int *);
-static int parse_who_options(Client *, int, char**);
+static void do_other_who(Client *client, const char *mask);
+static void send_who_reply(Client *, Client *, const char *, const char *, const char *);
+static const char *first_visible_channel(Client *, Client *, int *);
+static int parse_who_options(Client *, int, const char **);
 static void who_sendhelp(Client *);
 
 #define WF_OPERONLY  0x01 /**< only show opers */
@@ -93,19 +93,19 @@ static int who_flags;
 struct {
 	int want_away;
 	int want_channel;
-	char *channel; /**< if they want one */
+	const char *channel; /**< if they want one */
 	int want_gecos;
-	char *gecos;
+	const char *gecos;
 	int want_server;
-	char *server;
+	const char *server;
 	int want_host;
-	char *host;
+	const char *host;
 	int want_nick;
-	char *nick;
+	const char *nick;
 	int want_user;
-	char *user;
+	const char *user;
 	int want_ip;
-	char *ip;
+	const char *ip;
 	int want_port;
 	int port;
 	int want_umode;
@@ -118,8 +118,8 @@ struct {
 CMD_FUNC(cmd_who)
 {
 	Channel *target_channel;
-	char *mask = parv[1];
-	char star[] = "*";
+	const char *mask = parv[1];
+	char maskbuf[512];
 	int i = 0;
 
 	if (!MyUser(client))
@@ -139,14 +139,17 @@ CMD_FUNC(cmd_who)
 	}
 
 	if (parc-i < 2 || strcmp(parv[1 + i], "0") == 0)
-		mask = star;
+		mask = "*";
 	else
 		mask = parv[1 + i];
 
 	if (!i && parc > 2 && *parv[2] == 'o')
 		who_flags |= WF_OPERONLY;
 
-	collapse(mask);
+	/* Pfff... collapse... hate it! */
+	strlcpy(maskbuf, mask, sizeof(maskbuf));
+	collapse(maskbuf);
+	mask = maskbuf;
 
 	if (*mask == '\0')
 	{
@@ -155,7 +158,7 @@ CMD_FUNC(cmd_who)
 		return;
 	}
 
-	if ((target_channel = find_channel(mask, NULL)) != NULL)
+	if ((target_channel = find_channel(mask)) != NULL)
 	{
 		do_channel_who(client, target_channel, mask);
 		sendnumeric(client, RPL_ENDOFWHO, mask);
@@ -163,7 +166,7 @@ CMD_FUNC(cmd_who)
 	}
 
 	if (wfl.channel && wfl.want_channel == WHO_WANT && 
-	    (target_channel = find_channel(wfl.channel, NULL)) != NULL)
+	    (target_channel = find_channel(wfl.channel)) != NULL)
 	{
 		do_channel_who(client, target_channel, mask);
 		sendnumeric(client, RPL_ENDOFWHO, mask);
@@ -247,11 +250,11 @@ static void who_sendhelp(Client *client)
 #define WHO_ADD 1
 #define WHO_DEL 2
 
-static int parse_who_options(Client *client, int argc, char **argv)
+static int parse_who_options(Client *client, int argc, const char **argv)
 {
-char *s = argv[0];
-int what = WHO_ADD;
-int i = 1;
+	const char *s = argv[0];
+	int what = WHO_ADD;
+	int i = 1;
 
 /* A few helper macro's because this is used a lot, added during recode by Syzop. */
 
@@ -319,7 +322,7 @@ int i = 1;
 			case 'm':
 				REQUIRE_PARAM()
 				{
-					char *s = argv[i];
+					const char *s = argv[i];
 					int *umodes;
 
 					if (what == WHO_ADD)
@@ -327,17 +330,7 @@ int i = 1;
 					else
 						umodes = &wfl.umodes_dontwant;
 
-					while (*s)
-					{
-					int i;
-						for (i = 0; i <= Usermode_highest; i++)
-							if (*s == Usermode_Table[i].flag)
-							{
-								*umodes |= Usermode_Table[i].mode;
-								break;
-							}
-					s++;
-					}
+					*umodes = set_usermode(s);
 
 					if (!IsOper(client))
 						*umodes = *umodes & UMODE_OPER; /* these are usermodes regular users may search for. just oper now. */
@@ -418,7 +411,7 @@ static int can_see(Client *requester, Client *target, Channel *channel)
 		/* if they only want people on a certain channel. */
 		if (wfl.want_channel != WHO_DONTCARE)
  		{
-			Channel *chan = find_channel(wfl.channel, NULL);
+			Channel *chan = find_channel(wfl.channel);
 			if (!chan && wfl.want_channel == WHO_WANT)
 				return WHO_CANTSEE;
 			if ((wfl.want_channel == WHO_WANT) && !IsMember(target, chan))
@@ -587,7 +580,7 @@ static int can_see(Client *requester, Client *target, Channel *channel)
 	}
 }
 
-static void do_channel_who(Client *client, Channel *channel, char *mask)
+static void do_channel_who(Client *client, Channel *channel, const char *mask)
 {
 	Member *cm = channel->members;
 	if (IsMember(client, channel) || ValidatePermissionsForPath("channel:see:who:onchannel",client,NULL,channel,NULL))
@@ -602,7 +595,7 @@ static void do_channel_who(Client *client, Channel *channel, char *mask)
 			continue;
 
 		make_who_status(client, acptr, channel, cm, status, cansee);
-		send_who_reply(client, acptr, channel->chname, status, "");
+		send_who_reply(client, acptr, channel->name, status, "");
     }
 }
 
@@ -617,7 +610,7 @@ static void make_who_status(Client *client, Client *acptr, Channel *channel,
 	else
 		status[i++] = 'H';
 
-	if (IsARegNick(acptr))
+	if (IsRegNick(acptr))
 		status[i++] = 'r';
 
 	if (IsSecureConnect(acptr))
@@ -643,39 +636,23 @@ static void make_who_status(Client *client, Client *acptr, Channel *channel,
 	{
 		if (HasCapability(client, "multi-prefix"))
 		{
-#ifdef PREFIX_AQ
-			if (cm->flags & CHFL_CHANOWNER)
-				status[i++] = '~';
-			if (cm->flags & CHFL_CHANADMIN)
-				status[i++] = '&';
-#endif
-			if (cm->flags & CHFL_CHANOP)
-				status[i++] = '@';
-			if (cm->flags & CHFL_HALFOP)
-				status[i++] = '%';
-			if (cm->flags & CHFL_VOICE)
-				status[i++] = '+';
-		} else {
-#ifdef PREFIX_AQ
-			if (cm->flags & CHFL_CHANOWNER)
-				status[i++] = '~';
-			else if (cm->flags & CHFL_CHANADMIN)
-				status[i++] = '&';
-			else
-#endif
-			if (cm->flags & CHFL_CHANOP)
-				status[i++] = '@';
-			else if (cm->flags & CHFL_HALFOP)
-				status[i++] = '%';
-			else if (cm->flags & CHFL_VOICE)
-				status[i++] = '+';
+			/* Standard NAMES reply (single character) */
+			char c = mode_to_prefix(*cm->member_modes);
+			if (c)
+				status[i++] = c;
+		}
+		else
+		{
+			/* NAMES reply with all rights included (multi-prefix / NAMESX) */
+			strcpy(&status[i], modes_to_prefix(cm->member_modes));
+			i += strlen(&status[i]);
 		}
 	}
 
 	status[i] = '\0';
 }
 
-static void do_other_who(Client *client, char *mask)
+static void do_other_who(Client *client, const char *mask)
 {
 int oper = IsOper(client);
 
@@ -690,7 +667,7 @@ int oper = IsOper(client);
 		{
 		int cansee;
 		char status[20];
-		char *channel;
+		const char *channel;
 		int flg;
 
 			if (!IsUser(acptr))
@@ -733,7 +710,7 @@ matchok:
 		Client *acptr = find_client(mask, NULL);
 		int cansee;
 		char status[20];
-		char *channel;
+		const char *channel;
 		int flg;
 
 		if (!acptr)
@@ -749,10 +726,10 @@ matchok:
 }
 
 static void send_who_reply(Client *client, Client *acptr, 
-			   char *channel, char *status, char *xstat)
+			   const char *channel, const char *status, const char *xstat)
 {
 	char *stat;
-	char *host;
+	const char *host;
 	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
 
 	stat = safe_alloc(strlen(status) + strlen(xstat) + 1);
@@ -799,7 +776,7 @@ static void send_who_reply(Client *client, Client *acptr,
 	safe_free(stat);
 }
 
-static char *first_visible_channel(Client *client, Client *acptr, int *flg)
+static const char *first_visible_channel(Client *client, Client *acptr, int *flg)
 {
 	Membership *lp;
 
@@ -857,7 +834,7 @@ static char *first_visible_channel(Client *client, Client *acptr, int *flg)
 			*flg |= FVC_HIDDEN;
 
 		if (showchannel)
-			return channel->chname;
+			return channel->name;
 	}
 
 	/* no channels that they can see */
diff --git a/src/modules/whois.c b/src/modules/whois.c
@@ -1,7 +1,8 @@
 /*
  *   Unreal Internet Relay Chat Daemon, src/modules/whois.c
  *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
+ *   (C) 2003-2021 Bram Matthys and the UnrealIRCd team
+ *   Moved to modules by Fish (Justin Hammond) in 2001
  *
  *   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
@@ -20,54 +21,278 @@
 
 #include "unrealircd.h"
 
-static char buf[BUFSIZE];
-
-CMD_FUNC(cmd_whois);
-
-#define MSG_WHOIS       "WHOIS"
-
+/* Structs */
 ModuleHeader MOD_HEADER
   = {
 	"whois",	/* Name of module */
 	"5.0", /* Version */
 	"command /whois", /* Short description of module */
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
-/* This is called on module init, before Server Ready */
+typedef enum WhoisConfigUser {
+	WHOIS_CONFIG_USER_EVERYONE	= 1,
+	WHOIS_CONFIG_USER_SELF		= 2,
+	WHOIS_CONFIG_USER_OPER		= 3,
+} WhoisConfigUser;
+#define HIGHEST_WHOIS_CONFIG_USER_VALUE 3 /* adjust this if you edit the enum above !! */
+
+//this one is in include/struct.h because it needs full API exposure:
+//typedef enum WhoisConfigDetails {
+//	...
+//} WhoisConfigDetails;
+//
+
+typedef struct WhoisConfig WhoisConfig;
+struct WhoisConfig {
+	WhoisConfig *prev, *next;
+	char *name;
+	WhoisConfigDetails permissions[HIGHEST_WHOIS_CONFIG_USER_VALUE+1];
+};
+
+/* Global variables */
+static char buf[BUFSIZE];
+WhoisConfig *whoisconfig = NULL;
+
+/* Forward declarations */
+WhoisConfigDetails _whois_get_policy(Client *client, Client *target, const char *name);
+CMD_FUNC(cmd_whois);
+static int whois_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+static int whois_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+static void whois_config_setdefaults(void);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAdd(modinfo->handle, EFUNC_WHOIS_GET_POLICY, TO_INTFUNC(_whois_get_policy));
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whois_config_test);
+	return MOD_SUCCESS;
+}
+
 MOD_INIT()
 {
-	CommandAdd(modinfo->handle, MSG_WHOIS, cmd_whois, MAXPARA, CMD_USER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, "WHOIS", cmd_whois, MAXPARA, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whois_config_run);
+	whois_config_setdefaults();
 	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;
 }
 
+static WhoisConfig *find_whois_config(const char *name)
+{
+	WhoisConfig *w;
+	for (w = whoisconfig; w; w = w->next)
+		if (!strcmp(w->name, name))
+			return w;
+	return NULL;
+}
 
-/*
-** cmd_whois
-**	parv[1] = nickname masklist
-*/
+/* Lazy helper for whois_config_setdefaults */
+static void whois_config_add(const char *name, WhoisConfigUser user, WhoisConfigDetails details)
+{
+	WhoisConfig *w = find_whois_config(name);
+
+	if (!w)
+	{
+		/* New one */
+		w = safe_alloc(sizeof(WhoisConfig));
+		safe_strdup(w->name, name);
+		AddListItem(w, whoisconfig);
+	}
+	w->permissions[user] = details;
+}
+
+static void whois_config_setdefaults(void)
+{
+	whois_config_add("basic", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("modes", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
+	whois_config_add("modes", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("realhost", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
+	whois_config_add("realhost", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("registered-nick", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("channels", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
+	whois_config_add("channels", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
+	whois_config_add("channels", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("server", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("away", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("oper", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
+	whois_config_add("oper", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
+	whois_config_add("oper", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("secure", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
+	whois_config_add("secure", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
+	whois_config_add("secure", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("bot", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("services", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("reputation", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("geo", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("certfp", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("shunned", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("account", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("swhois", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
+
+	whois_config_add("idle", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
+	whois_config_add("idle", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
+	whois_config_add("idle", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
+}
+
+static void whois_free_config(void)
+{
+}
+
+static WhoisConfigUser whois_config_user_strtovalue(const char *str)
+{
+	if (!strcmp(str, "everyone"))
+		return WHOIS_CONFIG_USER_EVERYONE;
+	if (!strcmp(str, "self"))
+		return WHOIS_CONFIG_USER_SELF;
+	if (!strcmp(str, "oper"))
+		return WHOIS_CONFIG_USER_OPER;
+	return 0;
+}
+
+static WhoisConfigDetails whois_config_details_strtovalue(const char *str)
+{
+	if (!strcmp(str, "full"))
+		return WHOIS_CONFIG_DETAILS_FULL;
+	if (!strcmp(str, "limited"))
+		return WHOIS_CONFIG_DETAILS_LIMITED;
+	if (!strcmp(str, "none"))
+		return WHOIS_CONFIG_DETAILS_NONE;
+	return 0;
+}
+
+static int whois_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep, *cepp;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	/* We are only interrested in set::whois-details.. */
+	if (!ce || strcmp(ce->name, "whois-details"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (cep->value)
+		{
+			config_error("%s:%i: set::whois-details::%s item has a value, which is unexpected. Check your syntax!",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		}
+		for (cepp = cep->items; cepp; cepp = cepp->next)
+		{
+			if (!whois_config_user_strtovalue(cepp->name))
+			{
+				config_error("%s:%i: set::whois-details::%s contains unknown user category called '%s', must be one of: everyone, self, ircop",
+					cepp->file->filename, cepp->line_number, cep->name, cepp->name);
+				errors++;
+				continue;
+			} else
+			if (!cepp->value || !whois_config_details_strtovalue(cepp->value))
+			{
+				config_error("%s:%i: set::whois-details::%s contains unknown details type '%s', must be one of: full, limited, none",
+					cepp->file->filename, cepp->line_number, cep->name, cepp->name);
+				errors++;
+				continue;
+			} /* else it is good */
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+static int whois_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep, *cepp;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	/* We are only interrested in set::whois-details.. */
+	if (!ce || strcmp(ce->name, "whois-details"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		WhoisConfig *w = find_whois_config(cep->name);
+		if (!w)
+		{
+			/* New one */
+			w = safe_alloc(sizeof(WhoisConfig));
+			safe_strdup(w->name, cep->name);
+			AddListItem(w, whoisconfig);
+		}
+		for (cepp = cep->items; cepp; cepp = cepp->next)
+		{
+			WhoisConfigUser user = whois_config_user_strtovalue(cepp->name);
+			WhoisConfigDetails details = whois_config_details_strtovalue(cepp->value);
+			w->permissions[user] = details;
+		}
+	}
+	return 1;
+}
+
+/** Get set::whois-details policy for an item.
+ * @param client		The client doing the /WHOIS
+ * @param target		The client being whoised, so the one to show all details for
+ * @param name			The name of the whois item (eg "modes")
+ */
+WhoisConfigDetails _whois_get_policy(Client *client, Client *target, const char *name)
+{
+	WhoisConfig *w = find_whois_config(name);
+	if (!w)
+		return WHOIS_CONFIG_DETAILS_DEFAULT;
+	if ((client == target) && (w->permissions[WHOIS_CONFIG_USER_SELF] > 0))
+		return w->permissions[WHOIS_CONFIG_USER_SELF];
+	if (IsOper(client) && (w->permissions[WHOIS_CONFIG_USER_OPER] > 0))
+		return w->permissions[WHOIS_CONFIG_USER_OPER];
+	if (w->permissions[WHOIS_CONFIG_USER_EVERYONE] > 0)
+		return w->permissions[WHOIS_CONFIG_USER_EVERYONE];
+	return WHOIS_CONFIG_DETAILS_NONE;
+}
+
+/* WHOIS command.
+ * parv[1] = list of nicks (comma separated)
+ */
 CMD_FUNC(cmd_whois)
 {
 	Membership *lp;
 	Client *target;
 	Channel *channel;
-	char *nick, *tmp, *name;
+	char *nick, *tmp;
 	char *p = NULL;
-	int  found, len, mlen;
+	int len, mlen;
 	char querybuf[BUFSIZE];
 	int ntargets = 0;
 	int maxtargets = max_targets_for_command("WHOIS");
@@ -80,7 +305,7 @@ CMD_FUNC(cmd_whois)
 
 	if (parc > 2)
 	{
-		if (hunt_server(client, recv_mtags, ":%s WHOIS %s :%s", 1, parc, parv) != HUNTED_ISME)
+		if (hunt_server(client, recv_mtags, "WHOIS", 1, parc, parv) != HUNTED_ISME)
 			return;
 		parv[1] = parv[2];
 	}
@@ -90,6 +315,8 @@ CMD_FUNC(cmd_whois)
 	for (tmp = canonize(parv[1]); (nick = strtoken(&p, tmp, ",")); tmp = NULL)
 	{
 		unsigned char showchannel, wilds, hideoper; /* <- these are all boolean-alike */
+		NameValuePrioList *list = NULL, *e;
+		int policy; /* for temporary stuff */
 
 		if (MyUser(client) && (++ntargets > maxtargets))
 		{
@@ -97,55 +324,62 @@ CMD_FUNC(cmd_whois)
 			break;
 		}
 
-		found = 0;
 		/* We do not support "WHOIS *" */
 		wilds = (strchr(nick, '?') || strchr(nick, '*'));
 		if (wilds)
 			continue;
 
-		if ((target = find_person(nick, NULL)))
+		target = find_user(nick, NULL);
+		if (!target)
 		{
-			/*
-			 * 'Rules' established for sending a WHOIS reply:
-			 * - only send replies about common or public channels
-			 *   the target user(s) are on;
-			 */
+			sendnumeric(client, ERR_NOSUCHNICK, nick);
+			continue;
+		}
 
-			if (!IsUser(target))
-				continue;
+		/* Ok, from this point we are going to proceed with the WHOIS.
+		 * The idea here is NOT to send any lines, so don't call sendto functions.
+		 * Instead, use add_nvplist_numeric() and add_nvplist_numeric_fmt()
+		 * to add items to the whois list.
+		 * Then at the end of this loop we call modules who can also add/remove
+		 * whois lines, and only after that we FINALLY send all the whois lines
+		 * in one go.
+		 */
 
-			name = (!*target->name) ? "?" : target->name;
+		hideoper = 0;
+		if (IsHideOper(target) && (target != client) && !IsOper(client))
+			hideoper = 1;
 
-			hideoper = 0;
-			if (IsHideOper(target) && (target != client) && !IsOper(client))
-				hideoper = 1;
+		if (whois_get_policy(client, target, "basic") > WHOIS_CONFIG_DETAILS_NONE)
+		{
+			add_nvplist_numeric(&list, -1000000, "basic", client, RPL_WHOISUSER, target->name,
+				target->user->username,
+				IsHidden(target) ? target->user->virthost : target->user->realhost,
+				target->info);
+		}
 
-			sendnumeric(client, RPL_WHOISUSER, name,
-			    target->user->username,
-			    IsHidden(target) ? target->user->virthost : target->user->realhost,
-			    target->info);
+		if (whois_get_policy(client, target, "modes") > WHOIS_CONFIG_DETAILS_NONE)
+		{
+			add_nvplist_numeric(&list, -100000, "modes", client, RPL_WHOISMODES, target->name,
+				get_usermode_string(target), target->user->snomask ? target->user->snomask : "");
+		}
+		if (whois_get_policy(client, target, "realhost") > WHOIS_CONFIG_DETAILS_NONE)
+		{
+			add_nvplist_numeric(&list, -90000, "realhost", client, RPL_WHOISHOST, target->name,
+				(MyConnect(target) && strcmp(target->ident, "unknown")) ? target->ident : "*",
+				target->user->realhost, target->ip ? target->ip : "");
+		}
 
-			if (IsOper(client) || target == client)
-			{
-				char sno[128];
-				strlcpy(sno, get_snomask_string(target), sizeof(sno));
-				
-				/* send the target user's modes */
-				sendnumeric(client, RPL_WHOISMODES, name,
-				    get_usermode_string(target), sno[1] == 0 ? "" : sno);
-			}
-			if ((target == client) || IsOper(client))
-			{
-				sendnumeric(client, RPL_WHOISHOST, target->name,
-					(MyConnect(target) && strcmp(target->ident, "unknown")) ? target->ident : "*",
-					target->user->realhost, target->ip ? target->ip : "");
-			}
+		if (IsRegNick(target) && (whois_get_policy(client, target, "registered-nick") > WHOIS_CONFIG_DETAILS_NONE))
+		{
+			add_nvplist_numeric(&list, -80000, "registered-nick", client, RPL_WHOISREGNICK, target->name);
+		}
 
-			if (IsARegNick(target))
-				sendnumeric(client, RPL_WHOISREGNICK, name);
-			
-			found = 1;
-			mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(name);
+		/* The following code deals with channels */
+		policy = whois_get_policy(client, target, "channels");
+		if (policy > WHOIS_CONFIG_DETAILS_NONE)
+		{
+			int channel_whois_lines = 0;
+			mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name);
 			for (len = 0, *buf = '\0', lp = target->user->channel; lp; lp = lp->next)
 			{
 				Hook *h;
@@ -181,7 +415,12 @@ CMD_FUNC(cmd_whois)
 				if (ret == EX_DENY)
 					showchannel = 0;
 				
-				if (!showchannel && (ValidatePermissionsForPath("channel:see:whois",client,NULL,channel,NULL)))
+				/* If the channel is normally hidden, but the user is an IRCOp,
+				 * and has the channel:see:whois privilege,
+				 * and set::whois-details for 'channels' has 'oper full',
+				 * then show it:
+				 */
+				if (!showchannel && (ValidatePermissionsForPath("channel:see:whois",client,NULL,channel,NULL)) && (policy == WHOIS_CONFIG_DETAILS_FULL))
 				{
 					showchannel = 1; /* OperOverride */
 					operoverride = 1;
@@ -190,19 +429,19 @@ CMD_FUNC(cmd_whois)
 				if ((ret == EX_ALWAYS_DENY) && (target != client))
 					continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */
 
-				if (target == client)
+				/* This deals with target==client but also for unusual set::whois-details overrides
+				 * such as 'everyone full'
+				 */
+				if (policy == WHOIS_CONFIG_DETAILS_FULL)
 					showchannel = 1;
 
 				if (showchannel)
 				{
-					long access;
-					if (len + strlen(channel->chname) > (size_t)BUFSIZE - 4 - mlen)
+					if (len + strlen(channel->name) > (size_t)BUFSIZE - 4 - mlen)
 					{
-						sendto_one(client, NULL,
-						    ":%s %d %s %s :%s",
-						    me.name,
-						    RPL_WHOISCHANNELS,
-						    client->name, name, buf);
+						add_nvplist_numeric_fmt(&list, -70500-channel_whois_lines, "channels", client, RPL_WHOISCHANNELS,
+						                        "%s :%s", target->name, buf);
+						channel_whois_lines++;
 						*buf = '\0';
 						len = 0;
 					}
@@ -224,125 +463,152 @@ CMD_FUNC(cmd_whois)
 						}
 					}
 
-					access = get_access(target, channel);
 					if (!MyUser(client) || !HasCapability(client, "multi-prefix"))
 					{
-#ifdef PREFIX_AQ
-						if (access & CHFL_CHANOWNER)
-							*(buf + len++) = '~';
-						else if (access & CHFL_CHANADMIN)
-							*(buf + len++) = '&';
-						else
-#endif
-						if (access & CHFL_CHANOP)
-							*(buf + len++) = '@';
-						else if (access & CHFL_HALFOP)
-							*(buf + len++) = '%';
-						else if (access & CHFL_VOICE)
-							*(buf + len++) = '+';
+						/* Standard NAMES reply (single character) */
+						char c = mode_to_prefix(*lp->member_modes);
+						if (c)
+							*(buf + len++) = c;
 					}
 					else
 					{
-#ifdef PREFIX_AQ
-						if (access & CHFL_CHANOWNER)
-							*(buf + len++) = '~';
-						if (access & CHFL_CHANADMIN)
-							*(buf + len++) = '&';
-#endif
-						if (access & CHFL_CHANOP)
-							*(buf + len++) = '@';
-						if (access & CHFL_HALFOP)
-							*(buf + len++) = '%';
-						if (access & CHFL_VOICE)
-							*(buf + len++) = '+';
+						/* NAMES reply with all rights included (multi-prefix / NAMESX) */
+						strcpy(buf + len, modes_to_prefix(lp->member_modes));
+						len += strlen(buf + len);
 					}
 					if (len)
 						*(buf + len) = '\0';
-					strcpy(buf + len, channel->chname);
-					len += strlen(channel->chname);
+					strcpy(buf + len, channel->name);
+					len += strlen(channel->name);
 					strcat(buf + len, " ");
 					len++;
 				}
 			}
 
 			if (buf[0] != '\0')
-				sendnumeric(client, RPL_WHOISCHANNELS, name, buf); 
+			{
+				add_nvplist_numeric_fmt(&list, -70500-channel_whois_lines, "channels", client, RPL_WHOISCHANNELS,
+							"%s :%s", target->name, buf);
+				channel_whois_lines++;
+			}
+		}
 
-                        if (!(IsULine(target) && !IsOper(client) && HIDE_ULINES))
-				sendnumeric(client, RPL_WHOISSERVER, name, target->user->server,
-				    target->srvptr ? target->srvptr->info : "*Not On This Net*");
+		if (!(IsULine(target) && !IsOper(client) && HIDE_ULINES) &&
+		    whois_get_policy(client, target, "server") > WHOIS_CONFIG_DETAILS_NONE)
+		{
+			add_nvplist_numeric(&list, -60000, "server", client, RPL_WHOISSERVER,
+			                    target->name, target->user->server, target->uplink->info);
+		}
 
-			if (target->user->away)
-				sendnumeric(client, RPL_AWAY, name, target->user->away);
+		if (target->user->away && (whois_get_policy(client, target, "away") > WHOIS_CONFIG_DETAILS_NONE))
+		{
+			add_nvplist_numeric(&list, -50000, "away", client, RPL_AWAY,
+			                    target->name, target->user->away);
+		}
 
-			if (IsOper(target) && !hideoper)
+		if (IsOper(target) && !hideoper)
+		{
+			policy = whois_get_policy(client, target, "oper");
+			if (policy == WHOIS_CONFIG_DETAILS_FULL)
 			{
-				buf[0] = '\0';
-				if (IsOper(target))
-					strlcat(buf, "an IRC Operator", sizeof buf);
+				const char *operlogin = get_operlogin(target);
+				const char *operclass = get_operclass(target);
 
-				else
-					strlcat(buf, "a Local IRC Operator", sizeof buf);
-				if (buf[0])
+				if (operlogin && operclass)
 				{
-					if (IsOper(client) && MyUser(target))
-					{
-						char *operclass = "???";
-						ConfigItem_oper *oper = find_oper(target->user->operlogin);
-						if (oper && oper->operclass)
-							operclass = oper->operclass;
-						sendto_one(client, NULL,
-						    ":%s 313 %s %s :is %s (%s) [%s]", me.name,
-						    client->name, name, buf,
-						    target->user->operlogin ? target->user->operlogin : "unknown",
-						    operclass);
-					}
-					else
-						sendnumeric(client, RPL_WHOISOPERATOR, name, buf);
+					add_nvplist_numeric_fmt(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
+					                        "%s :is %s (%s) [%s]",
+					                        target->name, "an IRC Operator", operlogin, operclass);
+				} else
+				if (operlogin)
+				{
+					add_nvplist_numeric_fmt(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
+					                        "%s :is %s (%s)",
+					                        target->name, "an IRC Operator", operlogin);
+				} else
+				{
+					add_nvplist_numeric(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
+							    target->name, "an IRC Operator");
 				}
+			} else
+			if (policy == WHOIS_CONFIG_DETAILS_LIMITED)
+			{
+				add_nvplist_numeric(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
+				                    target->name, "an IRC Operator");
 			}
+		}
 
-			if (target->umodes & UMODE_SECURE)
-				sendnumeric(client, RPL_WHOISSECURE, name,
-					"is using a Secure Connection");
-			
-			RunHook2(HOOKTYPE_WHOIS, client, target);
-
-			if (IsOper(client) && MyUser(target) && IsShunned(target))
+		if (target->umodes & UMODE_SECURE)
+		{
+			policy = whois_get_policy(client, target, "secure");
+			if (policy == WHOIS_CONFIG_DETAILS_LIMITED)
 			{
-				sendto_one(client, NULL, ":%s %d %s %s :is shunned",
-				           me.name, RPL_WHOISSPECIAL, client->name, target->name);
+				add_nvplist_numeric(&list, -30000, "secure", client, RPL_WHOISSECURE,
+				                    target->name, "is using a Secure Connection");
+			} else
+			if (policy == WHOIS_CONFIG_DETAILS_FULL)
+			{
+				const char *ciphers = tls_get_cipher(target);
+				if (ciphers)
+				{
+					add_nvplist_numeric_fmt(&list, -30000, "secure", client, RPL_WHOISSECURE,
+					                        "%s :is using a Secure Connection [%s]",
+					                        target->name, ciphers);
+				} else {
+					add_nvplist_numeric(&list, -30000, "secure", client, RPL_WHOISSECURE,
+							    target->name, "is using a Secure Connection");
+				}
 			}
+		}
+
+		if (MyUser(target) && IsShunned(target) && (whois_get_policy(client, target, "shunned") > WHOIS_CONFIG_DETAILS_NONE))
+		{
+			add_nvplist_numeric(&list, -20000, "shunned", client, RPL_WHOISSPECIAL,
+			                    target->name, "is shunned");
+		}
 
-			if (target->user->swhois && !hideoper)
+		if (target->user->swhois && !hideoper && (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)
 			{
-				SWhois *s;
-				
-				for (s = target->user->swhois; s; s = s->next)
-					sendto_one(client, NULL, ":%s %d %s %s :%s",
-					    me.name, RPL_WHOISSPECIAL, client->name,
-					    name, s->line);
+				add_nvplist_numeric(&list, 100000+swhois_lines, "swhois", client, RPL_WHOISSPECIAL,
+				                    target->name, s->line);
+				swhois_lines++;
 			}
+		}
 
-			/*
-			 * display services account name if it's actually a services account name and
-			 * not a legacy timestamp.  --nenolod
-			 */
-			if (!isdigit(*target->user->svid))
-				sendnumeric(client, RPL_WHOISLOGGEDIN, name, target->user->svid);
+		/* TODO: hmm.. this should be a bit more towards the beginning of the whois, no ? */
+		if (IsLoggedIn(target) && (whois_get_policy(client, target, "account") > WHOIS_CONFIG_DETAILS_NONE))
+		{
+			add_nvplist_numeric(&list, 200000, "account", client, RPL_WHOISLOGGEDIN,
+			                    target->name, target->user->account);
+		}
 
-			/*
-			 * Umode +I hides an oper's idle time from regular users.
-			 * -Nath.
+		if (MyConnect(target))
+		{
+			policy = whois_get_policy(client, target, "idle");
+			/* If the policy is 'full' then show the idle time.
+			 * If the policy is 'limited then show the idle time according to the +I rules
 			 */
-			if (MyConnect(target) && !hide_idle_time(client, target))
+			if ((policy == WHOIS_CONFIG_DETAILS_FULL) ||
+			    ((policy == WHOIS_CONFIG_DETAILS_LIMITED) && !hide_idle_time(client, target)))
 			{
-				sendnumeric(client, RPL_WHOISIDLE, name,
-				    TStime() - target->local->last, target->local->firsttime);
+				add_nvplist_numeric(&list, 500000, "idle", client, RPL_WHOISIDLE,
+				                    target->name,
+				                    (long long)(TStime() - target->local->idle_since),
+				                    (long long)target->local->creationtime);
 			}
 		}
-		if (!found)
-			sendnumeric(client, ERR_NOSUCHNICK, nick);
+
+		RunHook(HOOKTYPE_WHOIS, client, target, &list);
+
+		for (e = list; e; e = e->next)
+			sendto_one(client, NULL, "%s", e->value);
+
+		free_nvplist(list);
 	}
 	sendnumeric(client, RPL_ENDOFWHOIS, querybuf);
 }
diff --git a/src/modules/whowas.c b/src/modules/whowas.c
@@ -32,7 +32,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /whowas", 
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 MOD_INIT()
@@ -53,8 +53,8 @@ MOD_UNLOAD()
 }
 
 /* externally defined functions */
-extern aWhowas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
-extern aWhowas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
+extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
 
 /*
 ** cmd_whowas
@@ -62,7 +62,8 @@ extern aWhowas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
 */
 CMD_FUNC(cmd_whowas)
 {
-	aWhowas *temp;
+	char request[BUFSIZE];
+	WhoWas *temp;
 	int  cur = 0;
 	int  max = -1, found = 0;
 	char *p, *nick;
@@ -75,16 +76,17 @@ CMD_FUNC(cmd_whowas)
 	if (parc > 2)
 		max = atoi(parv[2]);
 	if (parc > 3)
-		if (hunt_server(client, recv_mtags, ":%s WHOWAS %s %s :%s", 3, parc, parv))
+		if (hunt_server(client, recv_mtags, "WHOWAS", 3, parc, parv))
 			return;
 
 	if (!MyConnect(client) && (max > 20))
 		max = 20;
 
-	p = strchr(parv[1], ',');
+	strlcpy(request, parv[1], sizeof(request));
+	p = strchr(request, ',');
 	if (p)
 		*p = '\0';
-	nick = parv[1];
+	nick = request;
 	temp = WHOWASHASH[hash_whowas_name(nick)];
 	found = 0;
 	for (; temp; temp = temp->next)
@@ -109,5 +111,5 @@ CMD_FUNC(cmd_whowas)
 	if (!found)
 		sendnumeric(client, ERR_WASNOSUCHNICK, nick);
 
-	sendnumeric(client, RPL_ENDOFWHOWAS, parv[1]);
+	sendnumeric(client, RPL_ENDOFWHOWAS, request);
 }
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -15,7 +15,7 @@ ModuleHeader MOD_HEADER
 	"5.0",
 	"command /who",
 	"UnrealIRCd Team",
-	"unrealircd-5",
+	"unrealircd-6",
     };
 
 
@@ -80,10 +80,11 @@ static void who_global(Client *client, char *mask, int operspy, struct who_forma
 static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt);
 static void do_who_on_channel(Client *client, Channel *channel,
                               int member, int operspy, struct who_format *fmt);
-static int convert_classical_who_request(Client *client, int *parc, char *parv[], char **orig_mask, struct who_format *fmt);
-char *whox_md_serialize(ModData *m);
-void whox_md_unserialize(char *str, ModData *m);
+static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt);
+const char *whox_md_serialize(ModData *m);
+void whox_md_unserialize(const char *str, ModData *m);
 void whox_md_free(ModData *md);
+static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...) __attribute__((format(printf,4,5)));
 
 MOD_INIT()
 {
@@ -126,7 +127,7 @@ MOD_UNLOAD()
 }
 
 /** whox module data operations: serialize (rare) */
-char *whox_md_serialize(ModData *m)
+const char *whox_md_serialize(ModData *m)
 {
 	static char buf[32];
 	if (m->i == 0)
@@ -136,7 +137,7 @@ char *whox_md_serialize(ModData *m)
 }
 
 /** whox module data operations: unserialize (rare) */
-void whox_md_unserialize(char *str, ModData *m)
+void whox_md_unserialize(const char *str, ModData *m)
 {
 	m->i = atoi(str);
 }
@@ -179,9 +180,9 @@ void whox_md_free(ModData *md)
 CMD_FUNC(cmd_whox)
 {
 	char *mask;
-	char *orig_mask;
+	const char *orig_mask;
 	char ch; /* Scratch char register */
-	char *p; /* Scratch char pointer */
+	const char *p; /* Scratch char pointer */
 	int member;
 	int operspy = 0;
 	struct who_format fmt;
@@ -294,7 +295,7 @@ CMD_FUNC(cmd_whox)
 	
 		while (*s)
 		{
-			int i;
+			Umode *um;
 
 			switch (*s)
 			{
@@ -316,11 +317,11 @@ CMD_FUNC(cmd_whox)
 			else
 				umodes = &fmt.noumodes;
 
-			for (i = 0; i <= Usermode_highest; i++)
+			for (um = usermodes; um; um = um->next)
 			{
-				if (*s == Usermode_Table[i].flag)
+				if (um->letter == *s)
 				{
-					*umodes |= Usermode_Table[i].mode;
+					*umodes |= um->mode;
 					break;
 				}
 			}
@@ -341,7 +342,7 @@ CMD_FUNC(cmd_whox)
 		Channel *channel = NULL;
 
 		/* List all users on a given channel */
-		if ((channel = find_channel(orig_mask, NULL)) != NULL)
+		if ((channel = find_channel(orig_mask)) != NULL)
 		{
 			if (IsMember(client, channel) || operspy)
 				do_who_on_channel(client, channel, 1, operspy, &fmt);
@@ -426,8 +427,7 @@ static int do_match(Client *client, Client *acptr, char *mask, struct who_format
 		return 1;
 
 	/* match account */
-	if (IsMatch(fmt, WMATCH_ACCOUNT) && !BadPtr(acptr->user->svid) &&
-		!isdigit(*acptr->user->svid) && match_simple(mask, acptr->user->svid))
+	if (IsMatch(fmt, WMATCH_ACCOUNT) && IsLoggedIn(acptr) && match_simple(mask, acptr->user->account))
 	{
 		return 1;
 	}
@@ -491,12 +491,12 @@ static void who_common_channel(Client *client, Channel *channel,
 				break;
 		}
 
-		if (i != 0 && !(is_skochanop(client, channel)) && !(is_skochanop(acptr, channel) || has_voice(acptr,channel)))
+		if (i != 0 && !(check_channel_access(client, channel, "hoaq")) && !(check_channel_access(acptr, channel, "hoaq") || check_channel_access(acptr,channel, "v")))
 			continue;
 
 		SetMark(acptr);
 
-		if(*maxmatches > 0)
+		if (*maxmatches > 0)
 		{
 			if (do_match(client, acptr, mask, fmt))
 			{
@@ -530,7 +530,7 @@ static void who_global(Client *client, char *mask, int operspy, struct who_forma
 
 	/* If searching for a nick explicitly, then include it later on in the result: */
 	if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0)))
-		hunted = find_person(mask, NULL);
+		hunted = find_user(mask, NULL);
 
 	/* Initialize the markers to zero */
 	list_for_each_entry(acptr, &client_list, client_node)
@@ -608,10 +608,10 @@ static void do_who_on_channel(Client *client, Channel *channel,
 				break;
 		}
 
-		if (!operspy && (acptr != client) && i != 0 && !(is_skochanop(client, channel)) && !(is_skochanop(acptr, channel) || has_voice(acptr,channel)))
+		if (!operspy && (acptr != client) && i != 0 && !(check_channel_access(client, channel, "hoaq")) && !(check_channel_access(acptr, channel, "hoaq") || check_channel_access(acptr,channel, "v")))
 			continue;
 
-		if(member || !IsInvisible(acptr))
+		if (member || !IsInvisible(acptr))
 			do_who(client, acptr, channel, fmt);
 	}
 }
@@ -688,7 +688,7 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
  	else
  		status[i++] = 'H';
 
-	if (IsARegNick(acptr))
+	if (IsRegNick(acptr))
 		status[i++] = 'r';
 
 	if (IsSecureConnect(acptr))
@@ -715,36 +715,16 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
 		{
 			if (!(fmt->fields || HasCapability(client, "multi-prefix")))
 			{
-				/* Standard NAMES reply */
-#ifdef PREFIX_AQ
-				if (lp->flags & CHFL_CHANOWNER)
-					status[i++] = '~';
-				else if (lp->flags & CHFL_CHANADMIN)
-					status[i++] = '&';
-				else
-#endif
-				if (lp->flags & CHFL_CHANOP)
-					status[i++] = '@';
-				else if (lp->flags & CHFL_HALFOP)
-					status[i++] = '%';
-				else if (lp->flags & CHFL_VOICE)
-					status[i++] = '+';
+				/* Standard NAMES reply (single character) */
+				char c = mode_to_prefix(*lp->member_modes);
+				if (c)
+					status[i++] = c;
 			}
 			else
 			{
 				/* NAMES reply with all rights included (multi-prefix / NAMESX) */
-#ifdef PREFIX_AQ
-				if (lp->flags & CHFL_CHANOWNER)
-					status[i++] = '~';
-				if (lp->flags & CHFL_CHANADMIN)
-					status[i++] = '&';
-#endif
-				if (lp->flags & CHFL_CHANOP)
-					status[i++] = '@';
-				if (lp->flags & CHFL_HALFOP)
-					status[i++] = '%';
-				if (lp->flags & CHFL_VOICE)
-					status[i++] = '+';
+				strcpy(&status[i], modes_to_prefix(lp->member_modes));
+				i += strlen(&status[i]);
 			}
 		}
 	}
@@ -761,7 +741,7 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
 		else
 			host = GetHost(acptr);
 		sendnumeric(client, RPL_WHOREPLY,
-			channel ? channel->chname : "*",
+			channel ? channel->name : "*",
 			acptr->user->username, host,
 			hide ? "*" : acptr->user->server,
 			acptr->name, status, hide ? 0 : acptr->hopcount, acptr->info);
@@ -773,7 +753,7 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
 		if (HasField(fmt, FIELD_QUERYTYPE))
 			append_format(str, sizeof str, &pos, " %s", fmt->querytype);
 		if (HasField(fmt, FIELD_CHANNEL))
-			append_format(str, sizeof str, &pos, " %s", channel ? channel->chname : "*");
+			append_format(str, sizeof str, &pos, " %s", channel ? channel->name : "*");
 		if (HasField(fmt, FIELD_USER))
 			append_format(str, sizeof str, &pos, " %s", acptr->user->username);
 		if (HasField(fmt, FIELD_IP))
@@ -801,21 +781,26 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
 		if (HasField(fmt, FIELD_MODES))
 		{
 			if (IsOper(client))
-				append_format(str, sizeof str, &pos, " %s", strtok(get_usermode_string(acptr), "+"));
-			else
+			{
+				const char *umodes = get_usermode_string(acptr);
+				if (*umodes == '+')
+					umodes++;
+				append_format(str, sizeof str, &pos, " %s", umodes);
+			} else {
 				append_format(str, sizeof str, &pos, " %s", "*");
+			}
 		}
 		if (HasField(fmt, FIELD_HOP))
 			append_format(str, sizeof str, &pos, " %d", hide ? 0 : acptr->hopcount);
 		if (HasField(fmt, FIELD_IDLE))
 		{
 			append_format(str, sizeof str, &pos, " %d",
-				(int)((MyUser(acptr) && !hide_idle_time(client, acptr)) ? (TStime() - acptr->local->last) : 0));
+				(int)((MyUser(acptr) && !hide_idle_time(client, acptr)) ? (TStime() - acptr->local->idle_since) : 0));
 		}
 		if (HasField(fmt, FIELD_ACCOUNT))
-			append_format(str, sizeof str, &pos, " %s", (!isdigit(*acptr->user->svid)) ? acptr->user->svid : "0");
+			append_format(str, sizeof str, &pos, " %s", IsLoggedIn(acptr) ? acptr->user->account : "0");
 		if (HasField(fmt, FIELD_OPLEVEL))
-			append_format(str, sizeof str, &pos, " %s", (channel && is_skochanop(acptr, channel)) ? "999" : "n/a");
+			append_format(str, sizeof str, &pos, " %s", (channel && check_channel_access(acptr, channel, "hoaq")) ? "999" : "n/a");
 		if (HasField(fmt, FIELD_REPUTATION))
 		{
 			if (IsOper(client))
@@ -826,22 +811,15 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
 		if (HasField(fmt, FIELD_INFO))
 			append_format(str, sizeof str, &pos, " :%s", acptr->info);
 
-		if (pos >= sizeof str)
-		{
-			static int warned = 0;
-			if (!warned)
-				sendto_snomask(SNO_JUNK, "*** WHOX overflow while sending information about %s to %s", acptr->name, client->name);
-			warned = 1;
- 		}
 		sendto_one(client, NULL, "%s", str);
 	}
 }
 
 /* Yeah, this is fun. Thank you WHOX !!! */
-static int convert_classical_who_request(Client *client, int *parc, char *parv[], char **orig_mask, struct who_format *fmt)
+static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt)
 {
-	char *p;
-	static char pbuf1[256];
+	const char *p;
+	static char pbuf1[512], pbuf2[512];
 	int points;
 
 	/* Figure out if the user is doing a 'classical' UnrealIRCd request,
@@ -887,7 +865,7 @@ static int convert_classical_who_request(Client *client, int *parc, char *parv[]
 			         parv[1], parv[2] ? " " : "", parv[2] ? parv[2] : "");
 			if (parv[2])
 			{
-				char *swap = parv[1];
+				const char *swap = parv[1];
 				parv[1] = parv[2];
 				parv[2] = swap;
 			} else {
@@ -925,13 +903,19 @@ static int convert_classical_who_request(Client *client, int *parc, char *parv[]
 				sendnotice(client, "WHO request '%s' failed: flag 'c' no longer exists with WHOX.", oldrequest);
 				return 0;
 			}
-			for (p = parv[2]; *p; p++)
+			if (strchr(parv[2], 'g'))
 			{
-				if (*p == 'g')
+				char *w;
+				strlcpy(pbuf2, parv[2], sizeof(pbuf2));
+				for (w = pbuf2; *w; w++)
 				{
-					*p = 'r';
-					break;
+					if (*w == 'g')
+					{
+						*w = 'r';
+						break;
+					}
 				}
+				parv[2] = pbuf2;
 			}
 
 			/* "WHO -m xyz" (now: xyz -m) should become "WHO -xyz m"
diff --git a/src/numeric.c b/src/numeric.c
@@ -1,1056 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/numeric.c
- *   Copyright (C) 1992 Darren Reed
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/** @file
- * @brief Numeric replies in the IRC protocol.
- */
-
-#include "unrealircd.h"
-
-/** Numeric replies */
-static char *replies[] = {
-/* 000 */ NULL,
-/* 001    RPL_WELCOME */  ":Welcome to the %s IRC Network %s!%s@%s",
-/* 002    RPL_YOURHOST */ ":Your host is %s, running version %s",
-/* 003    RPL_CREATED */  ":This server was created %s",
-/* 004    RPL_MYINFO */   "%s %s %s %s",
-/* 005    RPL_ISUPPORT */ "%s :are supported by this server",
-/* 006    RPL_MAP */      ":%s%-*s(%ld) %s",
-/* 007    RPL_MAPEND */   ":End of /MAP",
-/* 008    RPL_SNOMASK */  "%s :Server notice mask",
-/* 009 */ NULL, /* ircu */
-/* 010    RPL_REDIR */	  "%s %d :Please use this Server/Port instead",
-/* 011 */ NULL,
-/* 012 */ NULL,
-/* 013 */ NULL,
-/* 014 */ NULL, /* hybrid */
-/* 015 */ NULL,
-/* 016 */ NULL,
-/* 017 */ NULL,
-/* 018 */ NULL,
-/* 019 */ NULL,
-/* 020 */ NULL,
-/* 021 */ NULL,
-/* 022 */ NULL,
-/* 023 */ NULL,
-/* 024 */ NULL,
-/* 025 */ NULL,
-/* 026 */ NULL,
-/* 027 */ NULL,
-/* 028 */ NULL,
-/* 029 */ NULL,
-/* 030 */ NULL,
-/* 031 */ NULL,
-/* 032 */ NULL,
-/* 033 */ NULL,
-/* 034 */ NULL,
-/* 035 */ NULL,
-/* 036 */ NULL,
-/* 037 */ NULL,
-/* 038 */ NULL,
-/* 039 */ NULL,
-/* 040 */ NULL,
-/* 041 */ NULL,
-/* 042    RPL_YOURID */	"%s :your unique ID",
-/* 043 */ NULL, /* ircnet */
-/* 044 */ NULL,
-/* 045 */ NULL,
-/* 046 */ NULL,
-/* 047 */ NULL,
-/* 048 */ NULL,
-/* 049 */ NULL,
-/* 050 */ NULL, /* aircd */
-/* 051 */ NULL, /* aircd */
-/* 052 */ NULL,
-/* 053 */ NULL,
-/* 054 */ NULL,
-/* 055 */ NULL,
-/* 056 */ NULL,
-/* 057 */ NULL,
-/* 058 */ NULL,
-/* 059 */ NULL,
-/* 060 */ NULL,
-/* 061 */ NULL,
-/* 062 */ NULL,
-/* 063 */ NULL,
-/* 064 */ NULL,
-/* 065 */ NULL,
-/* 066 */ NULL,
-/* 067 */ NULL,
-/* 068 */ NULL,
-/* 069 */ NULL,
-/* 070 */ NULL,
-/* 071 */ NULL,
-/* 072 */ NULL,
-/* 073 */ NULL,
-/* 074 */ NULL,
-/* 075 */ NULL,
-/* 076 */ NULL,
-/* 077 */ NULL,
-/* 078 */ NULL,
-/* 079 */ NULL,
-/* 080 */ NULL,
-/* 081 */ NULL,
-/* 082 */ NULL,
-/* 083 */ NULL,
-/* 084 */ NULL,
-/* 085 */ NULL,
-/* 086 */ NULL,
-/* 087 */ NULL,
-/* 088 */ NULL,
-/* 089 */ NULL,
-/* 090 */ NULL,
-/* 091 */ NULL,
-/* 092 */ NULL,
-/* 093 */ NULL,
-/* 094 */ NULL,
-/* 095 */ NULL,
-/* 096 */ NULL,
-/* 097 */ NULL,
-/* 098 */ NULL,
-/* 099 */ NULL,
-/* 100 */ NULL,
-/* 101 */ NULL,
-/* 102 */ NULL,
-/* 103 */ NULL,
-/* 104 */ NULL,
-/* 105    RPL_REMOTEISUPPORT */ "%s :are supported by this server",
-/* 106 */ NULL,
-/* 107 */ NULL,
-/* 108 */ NULL,
-/* 109 */ NULL,
-/* 110 */ NULL,
-/* 111 */ NULL,
-/* 112 */ NULL,
-/* 113 */ NULL,
-/* 114 */ NULL,
-/* 115 */ NULL,
-/* 116 */ NULL,
-/* 117 */ NULL,
-/* 118 */ NULL,
-/* 119 */ NULL,
-/* 120 */ NULL,
-/* 121 */ NULL,
-/* 122 */ NULL,
-/* 123 */ NULL,
-/* 124 */ NULL,
-/* 125 */ NULL,
-/* 126 */ NULL,
-/* 127 */ NULL,
-/* 128 */ NULL,
-/* 129 */ NULL,
-/* 130 */ NULL,
-/* 131 */ NULL,
-/* 132 */ NULL,
-/* 133 */ NULL,
-/* 134 */ NULL,
-/* 135 */ NULL,
-/* 136 */ NULL,
-/* 137 */ NULL,
-/* 138 */ NULL,
-/* 139 */ NULL,
-/* 140 */ NULL,
-/* 141 */ NULL,
-/* 142 */ NULL,
-/* 143 */ NULL,
-/* 144 */ NULL,
-/* 145 */ NULL,
-/* 146 */ NULL,
-/* 147 */ NULL,
-/* 148 */ NULL,
-/* 149 */ NULL,
-/* 150 */ NULL,
-/* 151 */ NULL,
-/* 152 */ NULL,
-/* 153 */ NULL,
-/* 154 */ NULL,
-/* 155 */ NULL,
-/* 156 */ NULL,
-/* 157 */ NULL,
-/* 158 */ NULL,
-/* 159 */ NULL,
-/* 160 */ NULL,
-/* 161 */ NULL,
-/* 162 */ NULL,
-/* 163 */ NULL,
-/* 164 */ NULL,
-/* 165 */ NULL,
-/* 166 */ NULL,
-/* 167 */ NULL,
-/* 168 */ NULL,
-/* 169 */ NULL,
-/* 170 */ NULL,
-/* 171 */ NULL,
-/* 172 */ NULL,
-/* 173 */ NULL,
-/* 174 */ NULL,
-/* 175 */ NULL,
-/* 176 */ NULL,
-/* 177 */ NULL,
-/* 178 */ NULL,
-/* 179 */ NULL,
-/* 180 */ NULL,
-/* 181 */ NULL,
-/* 182 */ NULL,
-/* 183 */ NULL,
-/* 184 */ NULL,
-/* 185 */ NULL,
-/* 186 */ NULL,
-/* 187 */ NULL,
-/* 188 */ NULL,
-/* 189 */ NULL,
-/* 190 */ NULL,
-/* 191 */ NULL,
-/* 192 */ NULL,
-/* 193 */ NULL,
-/* 194 */ NULL,
-/* 195 */ NULL,
-/* 196 */ NULL,
-/* 197 */ NULL,
-/* 198 */ NULL,
-/* 199 */ NULL,
-/* 200    RPL_TRACELINK */       "Link %s%s %s %s",
-/* 201    RPL_TRACECONNECTING */ "Attempt %s %s",
-/* 202    RPL_TRACEHANDSHAKE */  "Handshaking %s %s",
-/* 203    RPL_TRACEUNKNOWN */    "???? %s %s",
-/* 204    RPL_TRACEOPERATOR */   "Operator %s %s [%s] %ld",
-/* 205    RPL_TRACEUSER */       "User %s %s [%s] %ld",
-/* 206    RPL_TRACESERVER */     "Server %s %dS %dC %s %s!%s@%s %ld",
-/* 207    RPL_TRACESERVICE */    "Service %s %s",
-/* 208    RPL_TRACENEWTYPE */    "%s 0 %s",
-/* 209    RPL_TRACECLASS */      "Class %s %d",
-/* 210    RPL_STATSHELP */       ":%s",
-/* 211 */ NULL, /* Used */
-#ifdef DEBUGMODE
-/* 212    RPL_STATSCOMMANDS */ "%s %u %lu %lu %lu %lu %lu",
-#else
-/* 212    RPL_STATSCOMMANDS */ "%s %u %lu",
-#endif
-/* 213    RPL_STATSCLINE */ "%c %s * %s %d %d %s",
-/* 214    RPL_STATSOLDNLINE */ "%c %s * %s %d %d %s",
-/* 215    RPL_STATSILINE */ "I %s %s %d %d %s %s %d",
-/* 216    RPL_STATSKLINE */ "%s %s %s",
-/* 217    RPL_STATSQLINE */ "%c %s %ld %ld %s :%s",
-/* 218    RPL_STATSYLINE */ "Y %s %d %d %d %d %d",
-/* 219    RPL_ENDOFSTATS */ "%c :End of /STATS report",
-/* 220    RPL_STATSBLINE */ "%c %s %s %s %d %d",
-/* 221    RPL_UMODEIS */ "%s",
-/* 222    RPL_SQLINE_NICK */ "%s :%s",
-/* 223    RPL_STATSGLINE */ "%c %s %li %li %s :%s",
-/* 224    RPL_STATSTLINE */ "T %s %s %s",
-/* 225    RPL_STATSELINE (we use 230 instead) */ NULL,
-/* 226    RPL_STATSNLINE */ "n %s %s",
-/* 227    RPL_STATSVLINE */ "v %s %s %s",
-/* 228    RPL_STATSBANVER */ "%s %s",
-/* 229    RPL_STATSSPAMF */  "%c %s %s %s %li %li %li %s %s :%s",
-/* 230    RPL_STATSEXCEPTTKL */ "%s %s %li %li %s :%s",
-/* 231 */ NULL, /* rfc1459 */
-/* 232    RPL_RULES */ ":- %s",
-/* 233 */ NULL, /* rfc1459 */
-/* 234 */ NULL, /* rfc2812 */
-/* 235 */ NULL, /* rfc2812 */
-/* 236 */ NULL, /* ircu */
-/* 237 */ NULL, /* ircu */
-/* 238 */ NULL, /* ircu, ircnet */
-/* 239 */ NULL, /* ircnet */
-/* 240 */ NULL, /* rfc2812, austhex */
-/* 241    RPL_STATSLLINE */ "%c %s * %s %d %d",
-/* 242    RPL_STATSUPTIME */ ":Server Up %ld days, %ld:%02ld:%02ld",
-/* 243    RPL_STATSOLINE */ "%c %s * %s %s %s",
-/* 244    RPL_STATSHLINE */ "%c %s * %s %d %d",
-/* 245    RPL_STATSSLINE */ "%c %s * %s %d %d",
-/* 246 */ NULL, /* rfc2812 */
-/* 247    RPL_STATSXLINE */ "X %s %d",
-/* 248    RPL_STATSULINE */ "U %s",
-/* 249    RPL_STATSDEBUG */ ":%s",
-/* 250    RPL_STATSCONN */ ":Highest connection count: %d (%d clients)",
-/* 251    RPL_LUSERCLIENT */ ":There are %d users and %d invisible on %d servers",
-/* 252    RPL_LUSEROP */   "%d :operator(s) online",
-/* 253    RPL_LUSERUNKNOWN */ "%d :unknown connection(s)",
-/* 254    RPL_LUSERCHANNELS */ "%d :channels formed",
-/* 255    RPL_LUSERME */    ":I have %d clients and %d servers",
-/* 256    RPL_ADMINME */    ":Administrative info about %s",
-/* 257    RPL_ADMINLOC1 */  ":%s",
-/* 258    RPL_ADMINLOC2 */  ":%s",
-/* 259    RPL_ADMINEMAIL */ ":%s",
-/* 260 */  NULL,
-/* 261    RPL_TRACELOG */   "File %s %d",
-/* 262 */ NULL, /* rfc2812 */
-/* 263    RPL_TRYAGAIN */   "%s :Flooding detected. Please wait a while and try again.",
-/* 264 */ NULL,
-/* 265    RPL_LOCALUSERS */ "%d %d :Current local users %d, max %d",
-/* 266    RPL_GLOBALUSERS */ "%d %d :Current global users %d, max %d",
-/* 267 */ NULL, /* aircd */
-/* 268 */ NULL, /* aircd */
-/* 269 */ NULL, /* aircd */
-/* 270 */ NULL, /* ircu */
-/* 271    RPL_SILELIST */ "%s",
-/* 272    RPL_ENDOFSILELIST */ ":End of Silence List",
-/* 273 */ NULL, /* aircd */
-/* 274 */ NULL, /* ircnet */
-/* 275    RPL_STATSDLINE */ "%c %s %s",
-/* 276    RPL_WHOISCERTFP */ "%s :has client certificate fingerprint %s",
-/* 277 */ NULL, /* hybrid */
-/* 278 */ NULL, /* hybrid */
-/* 279 */ NULL,
-/* 280 */ NULL, /* ircu */
-/* 281 */ NULL, /* ircu, hybrid */
-/* 282 */ NULL, /* ircu, hybrid */
-/* 283 */ NULL, /* ircu, hybrid */
-/* 284 */ NULL, /* hybrid, quakenet */
-/* 285 */ NULL, /* ircu, aircd, quakenet */
-/* 286 */ NULL, /* aircd, quakenet */
-/* 287 */ NULL, /* aircd, quakenet */
-/* 288 */ NULL, /* aircd, quakenet */
-/* 289 */ NULL, /* aircd, quakenet */
-/* 290 */ NULL, /* aircd, quakenet */
-/* 291 */ NULL, /* aircd, quakenet */
-/* 292 */ NULL, /* aircd */
-/* 293 */ NULL, /* aircd */
-/* 294    RPL_HELPFWD */ ":Your help-request has been forwarded to Help Operators",
-/* 295    RPL_HELPIGN */ ":Your address has been ignored from forwarding",
-/* 296 */ NULL, /* aircd */
-/* 297 */ NULL,
-/* 298 */ NULL, /* Used */
-/* 299 */ NULL, /* aircd */
-/* 300 */ NULL, /* rfc1459 */
-/* 301    RPL_AWAY */ "%s :%s",
-/* 302    RPL_USERHOST */ ":%s %s %s %s %s",
-/* 303    RPL_ISON */ ":",
-/* 304 */ NULL, /* RPL_TEXT */
-/* 305    RPL_UNAWAY */ ":You are no longer marked as being away",
-/* 306    RPL_NOWAWAY */ ":You have been marked as being away",
-/* 307    RPL_WHOISREGNICK */ "%s :is identified for this nick",
-/* 308    RPL_RULESSTART */ ":- %s Server Rules - ",
-/* 309    RPL_ENDOFRULES */ ":End of RULES command.",
-/* 310    RPL_WHOISHELPOP */ "%s :is available for help.",
-/* 311    RPL_WHOISUSER */ "%s %s %s * :%s",
-/* 312    RPL_WHOISSERVER */ "%s %s :%s",
-/* 313    RPL_WHOISOPERATOR */ "%s :is %s",
-/* 314    RPL_WHOWASUSER */ "%s %s %s * :%s",
-/* 315    RPL_ENDOFWHO */ "%s :End of /WHO list.",
-/* 316 */ NULL, /* rfc1459 */
-/* 317    RPL_WHOISIDLE */ "%s %ld %ld :seconds idle, signon time",
-/* 318    RPL_ENDOFWHOIS */ "%s :End of /WHOIS list.",
-/* 319    RPL_WHOISCHANNELS */ "%s :%s",
-/* 320    RPL_WHOISSPECIAL */ "%s :%s",
-/* 321    RPL_LISTSTART */ "Channel :Users  Name",
-#ifndef LIST_SHOW_MODES
-/* 322    RPL_LIST */ "%s %d :%s",
-#else
-/* 322    RPL_LIST */ "%s %d :%s %s",
-#endif
-/* 323    RPL_LISTEND */ ":End of /LIST",
-/* 324    RPL_CHANNELMODEIS */ "%s %s %s",
-/* 325 */ NULL, /* rfc2812 */
-/* 326 */ NULL, /* Used */
-/* 327 */ NULL, /* Used */
-/* 328 */ NULL, /* bahamut, austhex */
-/* 329    RPL_CREATIONTIME */ "%s %lu",
-/* 330    RPL_WHOISLOGGEDIN */ "%s %s :is logged in as", 
-/* 331    RPL_NOTOPIC */ "%s :No topic is set.",
-/* 332    RPL_TOPIC */ "%s :%s",
-/* 333    RPL_TOPICWHOTIME */ "%s %s %lu",
-/* 334    RPL_LISTSYNTAX */ ":%s",
-/* 335    RPL_WHOISBOT */ "%s :is a \2Bot\2 on %s",
-/* 336    RPL_INVITELIST */ ":%s",
-/* 337    RPL_ENDOFINVITELIST */ ":End of /INVITE list.",
-/* 338 */ NULL, /* ircu, bahamut */
-/* 339 */ NULL, /* Used */
-/* 340    RPL_USERIP */ ":%s %s %s %s %s",
-/* 341    RPL_INVITING */ "%s %s",
-/* 342    RPL_SUMMONING */ "%s :User summoned to irc",
-/* 343 */ NULL,
-/* 344 */ NULL,
-/* 345 */ NULL, /* gamesurge */
-/* 346    RPL_INVEXLIST */ "%s %s %s %lu",
-/* 347    RPL_ENDOFINVEXLIST */ "%s :End of Channel Invite List",
-/* 348    RPL_EXLIST */ "%s %s %s %lu",
-/* 349    RPL_ENDOFEXLIST */ "%s :End of Channel Exception List",
-/* 350 */ NULL,
-/* 351    RPL_VERSION */ "%s.%s %s :%s%s%s [%s=%d]",
-/* 352    RPL_WHOREPLY */ "%s %s %s %s %s %s :%d %s",
-/* 353    RPL_NAMREPLY */ "%s",
-/* 354 */ NULL, /* ircu */
-/* 355 */ NULL, /* quakenet */
-/* 356 */ NULL,
-/* 357 */ NULL, /* austhex */
-/* 358 */ NULL, /* austhex */
-/* 359 */ NULL, /* austhex */
-/* 360 */ NULL,
-/* 361 */ NULL, /* rfc1459 */
-/* 362    RPL_CLOSING */ "%s :Closed. Status = %d",
-/* 363    RPL_CLOSEEND */ "%d: Connections Closed",
-/* 364    RPL_LINKS */ "%s %s :%d %s",
-/* 365    RPL_ENDOFLINKS */ "%s :End of /LINKS list.",
-/* 366    RPL_ENDOFNAMES */ "%s :End of /NAMES list.",
-/* 367    RPL_BANLIST */ "%s %s %s %lu",
-/* 368    RPL_ENDOFBANLIST  */ "%s :End of Channel Ban List",
-/* 369    RPL_ENDOFWHOWAS */ "%s :End of WHOWAS",
-/* 370 */ NULL,
-/* 371    RPL_INFO */ ":%s",
-/* 372    RPL_MOTD */ ":- %s",
-/* 373    RPL_INFOSTART */ ":Server INFO",
-/* 374    RPL_ENDOFINFO */ ":End of /INFO list.",
-/* 375    RPL_MOTDSTART */ ":- %s Message of the Day - ",
-/* 376    RPL_ENDOFMOTD */ ":End of /MOTD command.",
-/* 377 */ NULL, /* aircd, austhex */
-/* 378    RPL_WHOISHOST */ "%s :is connecting from %s@%s %s",
-/* 379    RPL_WHOISMODES */ "%s :is using modes %s %s",
-/* 380 */ NULL, /* aircd, austhex */
-/* 381    RPL_YOUREOPER */ ":You are now an IRC Operator",
-/* 382    RPL_REHASHING */ "%s :Rehashing",
-/* 383 */ NULL, /* rfc2812 */
-/* 384    RPL_MYPORTIS */ "%d :Port to local server is\r\n",
-/* 385 */ NULL, /* austhex, hybrid */
-/* 386    RPL_QLIST */ "%s %s",
-/* 387    RPL_ENDOFQLIST */ "%s :End of Channel Owner List",
-/* 388    RPL_ALIST */ "%s %s",
-/* 389    RPL_ENDOFALIST */ "%s :End of Protected User List",
-/* 390 */ NULL,
-/* 391    RPL_TIME */ "%s :%s",
-#ifdef	ENABLE_USERS
-/* 392    RPL_USERSSTART */ ":UserID   Terminal  Host",
-/* 393    RPL_USERS */ ":%-8s %-9s %-8s",
-/* 394    RPL_ENDOFUSERS */ ":End of Users",
-/* 395    RPL_NOUSERS */ ":Nobody logged in.",
-#else
-/* 392 */ NULL,
-/* 393 */ NULL,
-/* 394 */ NULL,
-/* 395 */ NULL, 
-#endif
-/* 396    RPL_HOSTHIDDEN */ "%s :is now your displayed host",
-/* 397 */ NULL,
-/* 398 */ NULL,
-/* 399 */ NULL,
-/* 400 */ NULL, /* Used */
-/* 401    ERR_NOSUCHNICK */ "%s :No such nick/channel",
-/* 402    ERR_NOSUCHSERVER */ "%s :No such server",
-/* 403    ERR_NOSUCHCHANNEL */ "%s :No such channel",
-/* 404    ERR_CANNOTSENDTOCHAN */ "%s :%s (%s)",
-/* 405    ERR_TOOMANYCHANNELS */ "%s :You have joined too many channels",
-/* 406    ERR_WASNOSUCHNICK */ "%s :There was no such nickname",
-/* 407    ERR_TOOMANYTARGETS */ "%s :Too many targets. The maximum is %d for %s.",
-/* 408 */ NULL, /* rfc2812, bahamut */
-/* 409    ERR_NOORIGIN */ ":No origin specified",
-/* 410    ERR_INVALIDCAPCMD */ "%s :Invalid CAP subcommand", 
-/* 411    ERR_NORECIPIENT */ ":No recipient given (%s)",
-/* 412    ERR_NOTEXTTOSEND */ ":No text to send",
-/* 413    ERR_NOTOPLEVEL */ "%s :No toplevel domain specified",
-/* 414    ERR_WILDTOPLEVEL */ "%s :Wildcard in toplevel Domain",
-/* 415 */ NULL, /* rfc2812 */
-/* 416    ERR_TOOMANYMATCHES */ "%s :%s",
-/* 417 */ NULL,
-/* 418 */ NULL,
-/* 419 */ NULL, /* aircd */
-/* 420 */ NULL,
-/* 421    ERR_UNKNOWNCOMMAND */ "%s :Unknown command",
-/* 422    ERR_NOMOTD */ ":MOTD File is missing",
-/* 423    ERR_NOADMININFO */ "%s :No administrative info available",
-/* 424    ERR_FILEERROR */ ":File error doing %s on %s",
-/* 425    ERR_NOOPERMOTD */ ":OPERMOTD File is missing",
-/* 426 */ NULL,
-/* 427 */ NULL,
-/* 428 */ NULL,
-/* 429 ERR_TOOMANYAWAY */ ":Too Many aways - Flood Protection activated",
-/* 430 */ NULL, /* austhex */
-/* 431    ERR_NONICKNAMEGIVEN */ ":No nickname given",
-/* 432    ERR_ERRONEUSNICKNAME */ "%s :Nickname is unavailable: %s",
-/* 433    ERR_NICKNAMEINUSE */ "%s :Nickname is already in use.",
-/* 434    ERR_NORULES */ ":RULES File is missing",
-/* 435 */ NULL, /* bahamut */
-/* 436    ERR_NICKCOLLISION */ "%s :Nickname collision KILL",
-/* 437    ERR_BANNICKCHANGE */ "%s :Cannot change nickname while banned on channel",
-/* 438    ERR_NCHANGETOOFAST */ "%s :Nick change too fast. Please try again later.",
-/* 439    ERR_TARGETTOOFAST */ "%s :Message target change too fast. Please wait %ld seconds",
-/* 440    ERR_SERVICESDOWN */  "%s :Services are currently down. Please try again later.",
-/* 441    ERR_USERNOTINCHANNEL */ "%s %s :They aren't on that channel",
-/* 442    ERR_NOTONCHANNEL */ "%s :You're not on that channel",
-/* 443    ERR_USERONCHANNEL */ "%s %s :is already on channel",
-/* 444    ERR_NOLOGIN */ "%s :User not logged in",
-/* 445    ERR_SUMMONDISABLED */ ":SUMMON has been disabled",
-/* 446    ERR_USERSDISABLED */ ":USERS has been disabled",
-/* 447    ERR_NONICKCHANGE */ ":Can not change nickname while on %s (+N)",
-/* 448 	ERR_FORBIDDENCHANNEL */ "%s :Cannot join channel: %s",
-/* 449 */ NULL, /* ircu */
-/* 450 */ NULL,
-/* 451    ERR_NOTREGISTERED */ ":You have not registered",
-/* 452 */ NULL, /* Used */
-/* 453 */ NULL, /* Used */
-/* 454 */ NULL,
-/* 455    ERR_HOSTILENAME */ ":Your username %s contained the invalid "
-	    "character(s) %s and has been changed to %s. "
-	    "Please use only the characters 0-9 a-z A-Z _ - "
-	    "or . in your username. Your username is the part "
-	    "before the @ in your email address.",
-/* 456 */ NULL, /* hybrid */
-/* 457 */ NULL, /* hybrid */
-/* 458 */ NULL, /* hybrid */
-/* 459    ERR_NOHIDING */ "%s :Cannot join channel (+H)",
-/* 460    ERR_NOTFORHALFOPS */ ":Halfops cannot set mode %c",
-/* 461    ERR_NEEDMOREPARAMS */ "%s :Not enough parameters",
-/* 462    ERR_ALREADYREGISTRED */ ":You may not reregister",
-/* 463    ERR_NOPERMFORHOST */ ":Your host isn't among the privileged",
-/* 464    ERR_PASSWDMISMATCH */ ":Password Incorrect",
-/* 465    ERR_YOUREBANNEDCREEP */	":%s",
-/* 466 */ NULL, /* rfc1459 */
-/* 467    ERR_KEYSET */ "%s :Channel key already set",
-/* 468    ERR_ONLYSERVERSCANCHANGE */ "%s :Only servers can change that mode",
-/* 469    ERR_LINKSET */ "%s :Channel link already set",
-/* 470    ERR_LINKCHANNEL */ "%s %s :[Link] %s has become full, so you are automatically being transferred to the linked channel %s",
-/* 471    ERR_CHANNELISFULL */ "%s :Cannot join channel (+l)",
-/* 472    ERR_UNKNOWNMODE */ "%c :is unknown mode char to me",
-/* 473    ERR_INVITEONLYCHAN */ "%s :Cannot join channel (+i)",
-/* 474    ERR_BANNEDFROMCHAN */ "%s :Cannot join channel (+b)",
-/* 475    ERR_BADCHANNELKEY */ "%s :Cannot join channel (+k)",
-/* 476    ERR_BADCHANMASK */ "%s :Bad Channel Mask",
-/* 477    ERR_NEEDREGGEDNICK */ "%s :You need a registered nick to join that channel.",
-/* 478    ERR_BANLISTFULL */ "%s %s :Channel ban/ignore list is full",
-/* 479    ERR_LINKFAIL */ "%s :Sorry, the channel has an invalid channel link set.",
-/* 480    ERR_CANNOTKNOCK */ ":Cannot knock on %s (%s)",
-/* 481    ERR_NOPRIVILEGES */ ":Permission Denied- You do not have the correct IRC operator privileges",
-/* 482    ERR_CHANOPRIVSNEEDED */ "%s :You're not channel operator",
-/* 483    ERR_CANTKILLSERVER */ ":You cant kill a server!",
-/* 484    ERR_ATTACKDENY */ "%s :Cannot kick protected user %s.",
-/* 485    ERR_KILLDENY */ ":Cannot kill protected user %s.",
-/* 486    ERR_NONONREG */ ":You must identify to a registered nick to private message %s",
-/* 487    ERR_NOTFORUSERS */ ":%s is a server only command",
-/* 488 */ NULL,
-/* 489    ERR_SECUREONLYCHAN */ "%s :Cannot join channel (Secure connection is required)",
-/* 490    ERR_NOSWEAR */ ":%s does not accept private messages containing swearing.",
-/* 491    ERR_NOOPERHOST */ ":No O-lines for your host",
-/* 492    ERR_NOCTCP */ ":%s does not accept CTCPs",
-/* 493 */ NULL, /* ircu */
-/* 494 */ NULL, /* ircu */
-/* 495 */ NULL, /* ircu */
-/* 496 */ NULL, /* ircu */
-/* 497 */ NULL, /* ircu */
-/* 498 */ NULL, /* ircu */
-/* 499    ERR_CHANOWNPRIVNEEDED */ "%s :You're not a channel owner",
-/* 500    ERR_TOOMANYJOINS */ "%s :Too many join requests. Please wait a while and try again.",
-/* 501    ERR_UMODEUNKNOWNFLAG */ ":Unknown MODE flag",
-/* 502    ERR_USERSDONTMATCH */ ":Cant change mode for other users",
-/* 503 */ NULL, /* austhex */
-/* 504 */ NULL, /* Used */
-/* 505 */ NULL,
-/* 506 */ NULL,
-/* 507 */ NULL,
-/* 508 */ NULL,
-/* 509 */ NULL,
-/* 510 */ NULL,
-/* 511    ERR_SILELISTFULL */ "%s :Your silence list is full",
-/* 512    ERR_TOOMANYWATCH */ "%s :Maximum size for WATCH-list is 128 entries",
-/* 513    ERR_NEEDPONG */ ":To connect, type /QUOTE PONG %lX",
-/* 514    ERR_TOOMANYDCC */ "%s :Your dcc allow list is full. Maximum size is %d entries",
-/* 515 */ NULL, /* ircu */
-/* 516 */ NULL, /* ircu */
-/* 517    ERR_DISABLED*/ "%s :%s", /* ircu */
-/* 518    518 */ ":Cannot invite (+V) at channel %s",
-/* 519    519 */ ":Cannot join channel %s (Admin only)",
-/* 520    520 */ ":Cannot join channel %s (IRCops only)",
-/* 521    ERR_LISTSYNTAX */ ":Bad list syntax, type /quote list ? or /raw list ?",
-/* 522    ERR_WHOSYNTAX */ ":/WHO Syntax incorrect, use /who ? for help",
-/* 523 	  ERR_WHOLIMEXCEED */ ":Error, /who limit of %d exceeded. Please narrow your search down and try again",
-/* 524    ERR_OPERSPVERIFY */ ":Trying to join +s or +p channel as an oper. Please invite yourself first.",
-/* 525 */ NULL, /* draft-brocklesby-irc-usercmdpfx */
-/* 526 */ NULL, /* draft-brocklesby-irc-usercmdpfx */
-/* 527 */ NULL,
-/* 528 */ NULL,
-/* 529 */ NULL,
-/* 530 */ NULL,
-/* 531    ERR_CANTSENDTOUSER */ "%s :%s",
-/* 532 */ NULL,
-/* 533 */ NULL,
-/* 534 */ NULL,
-/* 535 */ NULL,
-/* 536 */ NULL,
-/* 537 */ NULL,
-/* 538 */ NULL,
-/* 539 */ NULL,
-/* 540 */ NULL,
-/* 541 */ NULL,
-/* 542 */ NULL,
-/* 543 */ NULL,
-/* 544 */ NULL,
-/* 545 */ NULL,
-/* 546 */ NULL,
-/* 547 */ NULL,
-/* 548 */ NULL,
-/* 549 */ NULL,
-/* 550 */ NULL, /* quakenet */
-/* 551 */ NULL, /* quakenet */
-/* 552 */ NULL, /* quakenet */
-/* 553 */ NULL, /* quakenet */
-/* 554 */ NULL,
-/* 555 */ NULL,
-/* 556 */ NULL,
-/* 557 */ NULL,
-/* 558 */ NULL,
-/* 559 */ NULL,
-/* 560 */ NULL,
-/* 561 */ NULL,
-/* 562 */ NULL,
-/* 563 */ NULL,
-/* 564 */ NULL,
-/* 565 */ NULL,
-/* 566 */ NULL,
-/* 567 */ NULL,
-/* 568 */ NULL,
-/* 569 */ NULL,
-/* 570 */ NULL,
-/* 571 */ NULL,
-/* 572 */ NULL,
-/* 573 */ NULL,
-/* 574 */ NULL,
-/* 575 */ NULL,
-/* 576 */ NULL,
-/* 577 */ NULL,
-/* 578 */ NULL,
-/* 579 */ NULL,
-/* 580 */ NULL,
-/* 581 */ NULL,
-/* 582 */ NULL,
-/* 583 */ NULL,
-/* 584 */ NULL,
-/* 585 */ NULL,
-/* 586 */ NULL,
-/* 587 */ NULL,
-/* 588 */ NULL,
-/* 589 */ NULL,
-/* 590 */ NULL,
-/* 591 */ NULL,
-/* 592 */ NULL,
-/* 593 */ NULL,
-/* 594 */ NULL,
-/* 595 */ NULL,
-/* 596 */ NULL,
-/* 597    RPL_REAWAY */ "%s %s %s %d :%s",
-/* 598    RPL_GONEAWAY */ "%s %s %s %d :%s",
-/* 599    RPL_NOTAWAY */ "%s %s %s %d :is no longer away",
-/* 600    RPL_LOGON */ "%s %s %s %d :logged online",
-/* 601    RPL_LOGOFF */ "%s %s %s %d :logged offline",
-/* 602    RPL_WATCHOFF */ "%s %s %s %d :stopped watching",
-/* 603    RPL_WATCHSTAT */ ":You have %d and are on %d WATCH entries",
-/* 604    RPL_NOWON */ "%s %s %s %ld :is online",
-/* 605    RPL_NOWOFF */ "%s %s %s %ld :is offline",
-/* 606    RPL_WATCHLIST */ ":%s",
-/* 607    RPL_ENDOFWATCHLIST */ ":End of WATCH %c",
-/* 608    RPL_CLEARWATCH */ ":Your WATCH list is now empty",
-/* 609    RPL_NOWISAWAY */ "%s %s %s %ld :is away",
-/* 610    RPL_MAPMORE */ ":%s%-*s --> *more*",
-/* 611 */ NULL, /* ultimate */
-/* 612 */ NULL, /* ultimate */
-/* 613 */ NULL, /* ultimate */
-/* 614 */ NULL,
-/* 615 */ NULL, /* ptlink, ultimate */
-/* 616 */ NULL, /* ultimate */
-/* 617    RPL_DCCSTATUS */ ":%s has been %s your DCC allow list",
-/* 618    RPL_DCCLIST */ ":%s",
-/* 619    RPL_ENDOFDCCLIST */ ":End of DCCALLOW %s",
-/* 620    RPL_DCCINFO */ ":%s",
-/* 621 */ NULL, /* ultimate */
-/* 622 */ NULL, /* ultimate */
-/* 623 */ NULL, /* ultimate */
-/* 624 */ NULL, /* ultimate */
-/* 625 */ NULL, /* ultimate */
-/* 626 */ NULL, /* ultimate */
-/* 627 */ NULL,
-/* 628 */ NULL,
-/* 629 */ NULL,
-/* 630 */ NULL, /* ultimate */
-/* 631 */ NULL, /* ultimate */
-/* 632 */ NULL,
-/* 633 */ NULL,
-/* 634 */ NULL,
-/* 635 */ NULL,
-/* 636 */ NULL,
-/* 637 */ NULL,
-/* 638 */ NULL,
-/* 639 */ NULL,
-/* 640 */ NULL,
-/* 641 */ NULL,
-/* 642 */ NULL,
-/* 643 */ NULL,
-/* 644 */ NULL,
-/* 645 */ NULL,
-/* 646 */ NULL,
-/* 647 */ NULL,
-/* 648 */ NULL,
-/* 649 */ NULL,
-/* 650 */ NULL,
-/* 651 */ NULL,
-/* 652 */ NULL,
-/* 653 */ NULL,
-/* 654 */ NULL,
-/* 655 */ NULL,
-/* 656 */ NULL,
-/* 657 */ NULL,
-/* 658 */ NULL,
-/* 659 RPL_SPAMCMDFWD */ "%s :Command processed, but a copy has been sent to ircops for evaluation (anti-spam) purposes. [%s]",
-/* 660 */ NULL, /* kineircd */
-/* 661 */ NULL, /* kineircd */
-/* 662 */ NULL, /* kineircd */
-/* 663 */ NULL, /* kineircd */
-/* 664 */ NULL, /* kineircd */
-/* 665 */ NULL, /* kineircd */
-/* 666 */ NULL, /* kineircd */
-/* 667 */ NULL,
-/* 668 */ NULL,
-/* 669 */ NULL,
-/* 670 RPL_STARTTLS */ ":STARTTLS successful, go ahead with TLS handshake", /* kineircd */
-/* 671 RPL_WHOISSECURE */ "%s :%s", /* our variation on the kineircd numeric */
-/* 672 */ NULL, /* ithildin */
-/* 673 */ NULL, /* ithildin */
-/* 674 */ NULL,
-/* 675 */ NULL,
-/* 676 */ NULL,
-/* 677 */ NULL,
-/* 678 */ NULL, /* kineircd */
-/* 679 */ NULL, /* kineircd */
-/* 680 */ NULL,
-/* 681 */ NULL,
-/* 682 */ NULL, /* kineircd */
-/* 683 */ NULL,
-/* 684 */ NULL,
-/* 685 */ NULL,
-/* 686 */ NULL,
-/* 687 */ NULL, /* kineircd */
-/* 688 */ NULL, /* kineircd */
-/* 689 */ NULL, /* kineircd */
-/* 690 */ NULL, /* kineircd */
-/* 691 ERR_STARTTLS */ ":%s",
-/* 692 */ NULL,
-/* 693 */ NULL,
-/* 694 */ NULL,
-/* 695 */ NULL,
-/* 696 */ NULL,
-/* 697 */ NULL,
-/* 698 */ NULL,
-/* 699 */ NULL,
-/* 700 */ NULL,
-/* 701 */ NULL,
-/* 702 */ NULL,
-/* 703 */ NULL,
-/* 704 */ NULL,
-/* 705 */ NULL,
-/* 706 */ NULL,
-/* 707 */ NULL,
-/* 708 */ NULL,
-/* 709 */ NULL,
-/* 710 */ NULL,
-/* 711 */ NULL,
-/* 712 */ NULL,
-/* 713 */ NULL,
-/* 714 */ NULL,
-/* 715 */ NULL,
-/* 716 */ NULL, /* ratbox */
-/* 717 */ NULL, /* ratbox */
-/* 718 */ NULL, /* ratbox */
-/* 719 */ NULL,
-/* 720 */ NULL,
-/* 721 */ NULL,
-/* 722 */ NULL,
-/* 723 */ NULL,
-/* 724 */ NULL,
-/* 725 */ NULL,
-/* 726 */ NULL,
-/* 727 */ NULL,
-/* 728 */ NULL,
-/* 729 */ NULL,
-/* 730 */ NULL,
-/* 731 */ NULL,
-/* 732 */ NULL,
-/* 733 */ NULL,
-/* 734 */ NULL,
-/* 735 */ NULL,
-/* 736 */ NULL,
-/* 737 */ NULL,
-/* 738 */ NULL,
-/* 739 */ NULL,
-/* 740 */ NULL,
-/* 741 */ NULL,
-/* 742 ERR_MLOCKRESTRICTED */ "%s %c %s :MODE cannot be set due to channel having an active MLOCK restriction policy",
-/* 743 */ NULL,
-/* 744 */ NULL,
-/* 745 */ NULL,
-/* 746 */ NULL,
-/* 747 */ NULL,
-/* 748 */ NULL,
-/* 749 */ NULL,
-/* 750 */ NULL,
-/* 751 */ NULL,
-/* 752 */ NULL,
-/* 753 */ NULL,
-/* 754 */ NULL,
-/* 755 */ NULL,
-/* 756 */ NULL,
-/* 757 */ NULL,
-/* 758 */ NULL,
-/* 759 */ NULL,
-/* 760 */ NULL,
-/* 761 */ NULL,
-/* 762 */ NULL,
-/* 763 */ NULL,
-/* 764 */ NULL,
-/* 765 */ NULL,
-/* 766 */ NULL,
-/* 767 */ NULL,
-/* 768 */ NULL,
-/* 769 */ NULL,
-/* 770 */ NULL,
-/* 771 */ NULL, /* ithildin */
-/* 772 */ NULL,
-/* 773 */ NULL, /* ithildin */
-/* 774 */ NULL, /* ithildin */
-/* 775 */ NULL,
-/* 776 */ NULL,
-/* 777 */ NULL,
-/* 778 */ NULL,
-/* 779 */ NULL,
-/* 780 */ NULL,
-/* 781 */ NULL,
-/* 782 */ NULL,
-/* 783 */ NULL,
-/* 784 */ NULL,
-/* 785 */ NULL,
-/* 786 */ NULL,
-/* 787 */ NULL,
-/* 788 */ NULL,
-/* 789 */ NULL,
-/* 790 */ NULL,
-/* 791 */ NULL,
-/* 792 */ NULL,
-/* 793 */ NULL,
-/* 794 */ NULL,
-/* 795 */ NULL,
-/* 796 */ NULL,
-/* 797 */ NULL,
-/* 798 */ NULL,
-/* 799 */ NULL,
-/* 800 */ NULL,
-/* 801 */ NULL,
-/* 802 */ NULL,
-/* 803 */ NULL,
-/* 804 */ NULL,
-/* 805 */ NULL,
-/* 806 */ NULL,
-/* 807 */ NULL,
-/* 808 */ NULL,
-/* 809 */ NULL,
-/* 810 */ NULL,
-/* 811 */ NULL,
-/* 812 */ NULL,
-/* 813 */ NULL,
-/* 814 */ NULL,
-/* 815 */ NULL,
-/* 816 */ NULL,
-/* 817 */ NULL,
-/* 818 */ NULL,
-/* 819 */ NULL,
-/* 820 */ NULL,
-/* 821 */ NULL,
-/* 822 */ NULL,
-/* 823 */ NULL,
-/* 824 */ NULL,
-/* 825 */ NULL,
-/* 826 */ NULL,
-/* 827 */ NULL,
-/* 828 */ NULL,
-/* 829 */ NULL,
-/* 830 */ NULL,
-/* 831 */ NULL,
-/* 832 */ NULL,
-/* 833 */ NULL,
-/* 834 */ NULL,
-/* 835 */ NULL,
-/* 836 */ NULL,
-/* 837 */ NULL,
-/* 838 */ NULL,
-/* 839 */ NULL,
-/* 840 */ NULL,
-/* 841 */ NULL,
-/* 842 */ NULL,
-/* 843 */ NULL,
-/* 844 */ NULL,
-/* 845 */ NULL,
-/* 846 */ NULL,
-/* 847 */ NULL,
-/* 848 */ NULL,
-/* 849 */ NULL,
-/* 850 */ NULL,
-/* 851 */ NULL,
-/* 852 */ NULL,
-/* 853 */ NULL,
-/* 854 */ NULL,
-/* 855 */ NULL,
-/* 856 */ NULL,
-/* 857 */ NULL,
-/* 858 */ NULL,
-/* 859 */ NULL,
-/* 860 */ NULL,
-/* 861 */ NULL,
-/* 862 */ NULL,
-/* 863 */ NULL,
-/* 864 */ NULL,
-/* 865 */ NULL,
-/* 866 */ NULL,
-/* 867 */ NULL,
-/* 868 */ NULL,
-/* 869 */ NULL,
-/* 870 */ NULL,
-/* 871 */ NULL,
-/* 872 */ NULL,
-/* 873 */ NULL,
-/* 874 */ NULL,
-/* 875 */ NULL,
-/* 876 */ NULL,
-/* 877 */ NULL,
-/* 878 */ NULL,
-/* 879 */ NULL,
-/* 880 */ NULL,
-/* 881 */ NULL,
-/* 882 */ NULL,
-/* 883 */ NULL,
-/* 884 */ NULL,
-/* 885 */ NULL,
-/* 886 */ NULL,
-/* 887 */ NULL,
-/* 888 */ NULL,
-/* 889 */ NULL,
-/* 890 */ NULL,
-/* 891 */ NULL,
-/* 892 */ NULL,
-/* 893 */ NULL,
-/* 894 */ NULL,
-/* 895 */ NULL,
-/* 896 */ NULL,
-/* 897 */ NULL,
-/* 898 */ NULL,
-/* 899 */ NULL,
-/* 900 RPL_LOGGEDIN */          "%s!%s@%s %s :You are now logged in as %s.",
-/* 901 RPL_LOGGEDOUT */         "%s!%s@%s :You are now logged out.",
-/* 902 ERR_NICKLOCKED */        ":You must use a nick assigned to you.",
-/* 903 RPL_SASLSUCCESS */       ":SASL authentication successful",
-/* 904 ERR_SASLFAIL */          ":SASL authentication failed",
-/* 905 ERR_SASLTOOLONG */       ":SASL message too long",
-/* 906 ERR_SASLABORTED */       ":SASL authentication aborted",
-/* 907 ERR_SASLALREADY */       ":You have already completed SASL authentication",
-/* 908 RPL_SASLMECHS */         "%s :are available SASL mechanisms",
-/* 909 */ NULL,
-/* 910 */ NULL,
-/* 911 */ NULL,
-/* 912 */ NULL,
-/* 913 */ NULL,
-/* 914 */ NULL,
-/* 915 */ NULL,
-/* 916 */ NULL,
-/* 917 */ NULL,
-/* 918 */ NULL,
-/* 919 */ NULL,
-/* 920 */ NULL,
-/* 921 */ NULL,
-/* 922 */ NULL,
-/* 923 */ NULL,
-/* 924 */ NULL,
-/* 925 */ NULL,
-/* 926 */ NULL,
-/* 927 */ NULL,
-/* 928 */ NULL,
-/* 929 */ NULL,
-/* 930 */ NULL,
-/* 931 */ NULL,
-/* 932 */ NULL,
-/* 933 */ NULL,
-/* 934 */ NULL,
-/* 935 */ NULL,
-/* 936 */ NULL,
-/* 937 */ NULL,
-/* 938 */ NULL,
-/* 939 */ NULL,
-/* 940 */ NULL,
-/* 941 */ NULL,
-/* 942 */ NULL,
-/* 943 */ NULL,
-/* 944 */ NULL,
-/* 945 */ NULL,
-/* 946 */ NULL,
-/* 947 */ NULL,
-/* 948 */ NULL,
-/* 949 */ NULL,
-/* 950 */ NULL,
-/* 951 */ NULL,
-/* 952 */ NULL,
-/* 953 */ NULL,
-/* 954 */ NULL,
-/* 955 */ NULL,
-/* 956 */ NULL,
-/* 957 */ NULL,
-/* 958 */ NULL,
-/* 959 */ NULL,
-/* 960 */ NULL,
-/* 961 */ NULL,
-/* 962 */ NULL,
-/* 963 */ NULL,
-/* 964 */ NULL,
-/* 965 */ NULL,
-/* 966 */ NULL,
-/* 967 */ NULL,
-/* 968 */ NULL,
-/* 969 */ NULL,
-/* 970 */ NULL,
-/* 971 */ NULL,
-/* 972 ERR_CANNOTDOCOMMAND */ "%s :%s",
-/* 973 */ NULL, /* kineircd */
-/* 974 ERR_CANNOTCHANGECHANMODE */ "%c :%s",
-/* 975 */ NULL, /* kineircd */
-/* 976 */ NULL, /* kineircd */
-/* 977 */ NULL, /* kineircd */
-/* 978 */ NULL, /* kineircd */
-/* 979 */ NULL, /* kineircd */
-/* 980 */ NULL, /* kineircd */
-/* 981 */ NULL, /* kineircd */
-/* 982 */ NULL, /* kineircd */
-/* 983 */ NULL, /* kineircd */
-/* 984 */ NULL,
-/* 985 */ NULL,
-/* 986 */ NULL,
-/* 987 */ NULL,
-/* 988 */ NULL,
-/* 989 */ NULL,
-/* 990 */ NULL,
-/* 991 */ NULL,
-/* 992 */ NULL,
-/* 993 */ NULL,
-/* 994 */ NULL,
-/* 995 */ NULL,
-/* 996 */ NULL,
-/* 997 */ NULL,
-/* 998 */ NULL,
-/* 999    ERR_NUMERICERR */ "Numeric error!",
-/* 1000 */ NULL,
-};
-
-char *getreply(int numeric)
-{
-	if ((numeric < 0) || (numeric > 999) || !replies[numeric])
-		return replies[ERR_NUMERICERR];
-	else
-		return replies[numeric];
-}
diff --git a/src/openssl_hostname_validation.c b/src/openssl_hostname_validation.c
@@ -146,7 +146,7 @@ static char Curl_raw_toupper(char in)
 static int Curl_raw_equal(const char *first, const char *second)
 {
   while(*first && *second) {
-    if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second))
+    if (Curl_raw_toupper(*first) != Curl_raw_toupper(*second))
       /* get out of the loop as soon as they don't match */
       break;
     first++;
@@ -161,14 +161,14 @@ static int Curl_raw_equal(const char *first, const char *second)
 static int Curl_raw_nequal(const char *first, const char *second, size_t max)
 {
   while(*first && *second && max) {
-    if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) {
+    if (Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) {
       break;
     }
     max--;
     first++;
     second++;
   }
-  if(0 == max)
+  if (0 == max)
     return 1; /* they are equal this far */
 
   return Curl_raw_toupper(*first) == Curl_raw_toupper(*second);
@@ -189,7 +189,7 @@ static int hostmatch(const char *hostname, const char *pattern)
   int wildcard_enabled;
   size_t prefixlen, suffixlen;
   pattern_wildcard = strchr(pattern, '*');
-  if(pattern_wildcard == NULL)
+  if (pattern_wildcard == NULL)
     return Curl_raw_equal(pattern, hostname) ?
       CURL_HOST_MATCH : CURL_HOST_NOMATCH;
 
@@ -197,24 +197,24 @@ static int hostmatch(const char *hostname, const char *pattern)
      match. */
   wildcard_enabled = 1;
   pattern_label_end = strchr(pattern, '.');
-  if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL ||
+  if (pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL ||
      pattern_wildcard > pattern_label_end ||
      Curl_raw_nequal(pattern, "xn--", 4)) {
     wildcard_enabled = 0;
   }
-  if(!wildcard_enabled)
+  if (!wildcard_enabled)
     return Curl_raw_equal(pattern, hostname) ?
       CURL_HOST_MATCH : CURL_HOST_NOMATCH;
 
   hostname_label_end = strchr(hostname, '.');
-  if(hostname_label_end == NULL ||
+  if (hostname_label_end == NULL ||
      !Curl_raw_equal(pattern_label_end, hostname_label_end))
     return CURL_HOST_NOMATCH;
 
   /* The wildcard must match at least one character, so the left-most
      label of the hostname is at least as large as the left-most label
      of the pattern. */
-  if(hostname_label_end - hostname < pattern_label_end - pattern)
+  if (hostname_label_end - hostname < pattern_label_end - pattern)
     return CURL_HOST_NOMATCH;
 
   prefixlen = pattern_wildcard - pattern;
@@ -227,14 +227,14 @@ static int hostmatch(const char *hostname, const char *pattern)
 
 int Curl_cert_hostcheck(const char *match_pattern, const char *hostname)
 {
-  if(!match_pattern || !*match_pattern ||
+  if (!match_pattern || !*match_pattern ||
       !hostname || !*hostname) /* sanity check */
     return 0;
 
-  if(Curl_raw_equal(hostname, match_pattern)) /* trivial case */
+  if (Curl_raw_equal(hostname, match_pattern)) /* trivial case */
     return 1;
 
-  if(hostmatch(hostname,match_pattern) == CURL_HOST_MATCH)
+  if (hostmatch(hostname,match_pattern) == CURL_HOST_MATCH)
     return 1;
   return 0;
 }
@@ -388,7 +388,7 @@ static HostnameValidationResult matches_subject_alternative_name(const char *hos
 HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert) {
         HostnameValidationResult result;
 
-        if((hostname == NULL) || (server_cert == NULL))
+        if ((hostname == NULL) || (server_cert == NULL))
                 return Error;
 
         // First try the Subject Alternative Names extension
diff --git a/src/operclass.c b/src/operclass.c
@@ -29,7 +29,7 @@ struct OperClassValidator
 	OperClassCallbackNode *node;
 };
 
-OperClassACLPath *OperClass_parsePath(char *path);
+OperClassACLPath *OperClass_parsePath(const char *path);
 void OperClass_freePath(OperClassACLPath *path);
 OperClassPathNode *OperClass_findPathNodeForIdentifier(char *identifier, OperClassPathNode *head);
 
@@ -111,7 +111,7 @@ void OperClassValidatorDel(OperClassValidator *validator)
 	safe_free(validator);	
 }
 
-OperClassACLPath *OperClass_parsePath(char *path)
+OperClassACLPath *OperClass_parsePath(const char *path)
 {
 	char *pathCopy = raw_strdup(path);
 	OperClassACLPath *pathHead = NULL;
@@ -279,7 +279,7 @@ OperPermission ValidatePermissionsForPathEx(OperClassACL *acl, OperClassACLPath 
 	return OPER_DENY;
 }
 
-OperPermission ValidatePermissionsForPath(char *path, Client *client, Client *victim, Channel *channel, void *extra)
+OperPermission ValidatePermissionsForPath(const char *path, Client *client, Client *victim, Channel *channel, const void *extra)
 {
 	ConfigItem_oper *ce_oper;
 	ConfigItem_operclass *ce_operClass;
diff --git a/src/parse.c b/src/parse.c
@@ -29,11 +29,11 @@ char backupbuf[8192];
 static char *para[MAXPARA + 2];
 
 /* Forward declarations of functions that are local (static) */
-static int do_numeric(int, Client *, MessageTag *, int, char **);
+static int do_numeric(int, Client *, MessageTag *, int, const char **);
 static void cancel_clients(Client *, Client *, char *);
 static void remove_unknown(Client *, char *);
-static void parse2(Client *client, Client **fromptr, MessageTag *mtags, char *ch);
-static void parse_addlag(Client *client, int cmdbytes);
+static void parse2(Client *client, Client **fromptr, MessageTag *mtags, int mtags_bytes, char *ch);
+static void parse_addlag(Client *client, int command_bytes, int mtags_bytes);
 static int client_lagged_up(Client *client);
 static void ban_handshake_data_flooder(Client *client);
 
@@ -63,7 +63,8 @@ int process_packet(Client *client, char *readbuf, int length, int killsafely)
 	/* flood from unknown connection */
 	if (IsUnknown(client) && (DBufLength(&client->local->recvQ) > iConf.handshake_data_flood_amount))
 	{
-		sendto_snomask(SNO_FLOOD, "Handshake data flood from %s detected", client->local->sockhost);
+		unreal_log(ULOG_INFO, "flood", "HANDSHAKE_DATA_FLOOD", client,
+		           "Handshake data flood detected from $client.details [$client.ip]");
 		if (!killsafely)
 			ban_handshake_data_flooder(client);
 		else
@@ -74,12 +75,10 @@ int process_packet(Client *client, char *readbuf, int length, int killsafely)
 	/* excess flood check */
 	if (IsUser(client) && DBufLength(&client->local->recvQ) > get_recvq(client))
 	{
-		sendto_snomask(SNO_FLOOD,
-			"*** Flood -- %s!%s@%s (%d) exceeds %d recvQ",
-			client->name[0] ? client->name : "*",
-			client->user ? client->user->username : "*",
-			client->user ? client->user->realhost : "*",
-			DBufLength(&client->local->recvQ), get_recvq(client));
+		unreal_log(ULOG_INFO, "flood", "RECVQ_EXCEEDED", client,
+		           "Flood from $client.details [$client.ip] exceeds class::recvq ($recvq > $class_recvq) (Client sending too much data)",
+		           log_data_integer("recvq", DBufLength(&client->local->recvQ)),
+		           log_data_integer("class_recvq", get_recvq(client)));
 		if (!killsafely)
 			exit_client(client, NULL, "Excess Flood");
 		else
@@ -105,7 +104,7 @@ void parse_client_queued(Client *client)
 		return; /* we delay processing of data until identd has replied */
 
 	if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) &&
-	    !IsNoHandshakeDelay(client) && (TStime() - client->local->firsttime < iConf.handshake_delay))
+	    !IsNoHandshakeDelay(client) && (TStime() - client->local->creationtime < iConf.handshake_delay))
 	{
 		return; /* we delay processing of data until set::handshake-delay is reached */
 	}
@@ -140,21 +139,11 @@ void parse_client_queued(Client *client)
 */
 void dopacket(Client *client, char *buffer, int length)
 {
-	me.local->receiveB += length;	/* Update bytes received */
-	client->local->receiveB += length;
-	if (client->local->receiveB > 1023)
-	{
-		client->local->receiveK += (client->local->receiveB >> 10);
-		client->local->receiveB &= 0x03ff;	/* 2^10 = 1024, 3ff = 1023 */
-	}
-	if (me.local->receiveB > 1023)
-	{
-		me.local->receiveK += (me.local->receiveB >> 10);
-		me.local->receiveB &= 0x03ff;
-	}
+	client->local->traffic.bytes_received += length;
+	me.local->traffic.bytes_received += length;
 
-	me.local->receiveM += 1;	/* Update messages received */
-	client->local->receiveM += 1;
+	client->local->traffic.messages_received++;
+	me.local->traffic.messages_received++;
 
 	parse(client, buffer, length);
 }
@@ -175,6 +164,7 @@ void parse(Client *cptr, char *buffer, int length)
 	char *ch;
 	int i, ret;
 	MessageTag *mtags = NULL;
+	int mtags_bytes = 0;
 
 	/* Take extreme care in this function, as messages can be up to READBUFSIZE
 	 * in size, which is 8192 at the time of writing.
@@ -184,18 +174,17 @@ void parse(Client *cptr, char *buffer, int length)
 	for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
 	{
 		(*(h->func.intfunc))(from, &me, NULL, &buffer, &length);
-		if(!buffer)
+		if (!buffer)
 			return;
 	}
 
-	Debug((DEBUG_ERROR, "Parsing: %s (from %s)", buffer, (*cptr->name ? cptr->name : "*")));
-
 	if (IsDeadSocket(cptr))
 		return;
 
-	if ((cptr->local->receiveK >= iConf.handshake_data_flood_amount/1024) && IsUnknown(cptr))
+	if ((cptr->local->traffic.bytes_received >= iConf.handshake_data_flood_amount) && IsUnknown(cptr))
 	{
-		sendto_snomask(SNO_FLOOD, "Handshake data flood from %s detected", cptr->local->sockhost);
+		unreal_log(ULOG_INFO, "flood", "HANDSHAKE_DATA_FLOOD", cptr,
+		           "Handshake data flood detected from $client.details [$client.ip]");
 		ban_handshake_data_flooder(cptr);
 		return;
 	}
@@ -203,8 +192,10 @@ void parse(Client *cptr, char *buffer, int length)
 	/* This stores the last executed command in 'backupbuf', useful for debugging crashes */
 	strlcpy(backupbuf, buffer, sizeof(backupbuf));
 
-#if defined(DEBUGMODE) && defined(RAWCMDLOGGING)
-	ircd_log(LOG_ERROR, "<- %s: %s", cptr->name, backupbuf);
+#if defined(RAWCMDLOGGING)
+	unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_IN", cptr,
+		   "<- $client: $data",
+		   log_data_string("data", backupbuf));
 #endif
 
 	/* This poisons unused para elements that code should never access */
@@ -218,31 +209,35 @@ void parse(Client *cptr, char *buffer, int length)
 	/* Now, parse message tags, if any */
 	if (*ch == '@')
 	{
+		char *start = ch;
 		parse_message_tags(cptr, &ch, &mtags);
+		if (ch - start > 0)
+			mtags_bytes = ch - start;
 		/* Skip whitespace again */
 		for (; *ch == ' '; ch++)
 			;
 	}
 
-	parse2(cptr, &from, mtags, ch);
+	parse2(cptr, &from, mtags, mtags_bytes, ch);
 
 	if (IsDead(cptr))
-		RunHook3(HOOKTYPE_POST_COMMAND, NULL, mtags, ch);
+		RunHook(HOOKTYPE_POST_COMMAND, NULL, mtags, ch);
 	else
-		RunHook3(HOOKTYPE_POST_COMMAND, from, mtags, ch);
+		RunHook(HOOKTYPE_POST_COMMAND, from, mtags, ch);
 
 	free_message_tags(mtags);
 	return;
 }
 
 /** Parse the remaining line - helper function for parse().
- * @param cptr   The client from which the message was received
- * @param from   The sender, this may be changed by parse2() when
- *               the message has a sender, eg :xyz PRIVMSG ..
- * @param mtags  Message tags received for this message.
- * @param ch     The incoming line received (buffer), excluding message tags.
+ * @param cptr   	The client from which the message was received
+ * @param from   	The sender, this may be changed by parse2() when
+ *               	the message has a sender, eg :xyz PRIVMSG ..
+ * @param mtags  	Message tags received for this message.
+ * @param mtags_bytes	The length of all message tags.
+ * @param ch		The incoming line received (buffer), excluding message tags.
  */
-static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
+static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, int mtags_bytes, char *ch)
 {
 	Client *from = cptr;
 	char *s;
@@ -328,12 +323,12 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 			ch++;
 	}
 
-	RunHook3(HOOKTYPE_PRE_COMMAND, from, mtags, ch);
+	RunHook(HOOKTYPE_PRE_COMMAND, from, mtags, ch);
 
 	if (*ch == '\0')
 	{
 		if (!IsServer(cptr))
-			cptr->local->since++; /* 1s fake lag */
+			cptr->local->fake_lag++; /* 1s fake lag */
 		return;
 	}
 
@@ -351,7 +346,7 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 		numeric = (*ch - '0') * 100 + (*(ch + 1) - '0') * 10 + (*(ch + 2) - '0');
 		paramcount = MAXPARA;
 		ircstats.is_num++;
-		parse_addlag(cptr, bytes);
+		parse_addlag(cptr, bytes, mtags_bytes);
 	}
 	else
 	{
@@ -377,7 +372,7 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 		if (!cmptr || !(cmptr->flags & CMD_NOLAG))
 		{
 			/* Add fake lag (doing this early in the code, so we don't forget) */
-			parse_addlag(cptr, bytes);
+			parse_addlag(cptr, bytes, mtags_bytes);
 		}
 		if (!cmptr)
 		{
@@ -402,8 +397,6 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 					sendto_one(from, NULL, ":%s %d %s %s :Unknown command",
 					                       me.name, ERR_UNKNOWNCOMMAND,
 					                       from->name, ch);
-					Debug((DEBUG_ERROR, "Unknown (%s) from %s",
-					    ch, get_client_name(cptr, TRUE)));
 				}
 			}
 			ircstats.is_unco++;
@@ -492,34 +485,34 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 
 	if (cmptr == NULL)
 	{
-		do_numeric(numeric, from, mtags, i, para);
+		do_numeric(numeric, from, mtags, i, (const char **)para);
 		return;
 	}
 	cmptr->count++;
 	if (IsUser(cptr) && (cmptr->flags & CMD_RESETIDLE))
-		cptr->local->last = TStime();
+		cptr->local->idle_since = TStime();
 
 	/* Now ready to execute the command */
 #ifndef DEBUGMODE
 	if (cmptr->flags & CMD_ALIAS)
 	{
-		(*cmptr->aliasfunc) (from, mtags, i, para, cmptr->cmd);
+		(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
 	} else {
 		if (!cmptr->overriders)
-			(*cmptr->func) (from, mtags, i, para);
+			(*cmptr->func) (from, mtags, i, (const char **)para);
 		else
-			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, para);
+			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
 	}
 #else
 	then = clock();
 	if (cmptr->flags & CMD_ALIAS)
 	{
-		(*cmptr->aliasfunc) (from, mtags, i, para, cmptr->cmd);
+		(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
 	} else {
 		if (!cmptr->overriders)
-			(*cmptr->func) (from, mtags, i, para);
+			(*cmptr->func) (from, mtags, i, (const char **)para);
 		else
-			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, para);
+			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
 	}
 	if (!IsDead(cptr))
 	{
@@ -528,7 +521,6 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 			cmptr->rticks += ticks;
 		else
 			cmptr->lticks += ticks;
-		cptr->local->cputime += ticks;
 	}
 #endif
 }
@@ -568,21 +560,45 @@ static void ban_handshake_data_flooder(Client *client)
  * be able to flood at full speed causing potentially many Mbits or even
  * GBits of data to be sent out to other clients.
  *
- * @param client    The client.
- * @param cmdbytes  Number of bytes in the command.
+ * @param client	The client.
+ * @param command_bytes	Command length in bytes (excluding message tagss)
+ * @param mtags_bytes	Length of message tags in bytes
  */
-void parse_addlag(Client *client, int cmdbytes)
+void parse_addlag(Client *client, int command_bytes, int mtags_bytes)
 {
+	FloodSettings *settings = get_floodsettings_for_user(client, FLD_LAG_PENALTY);
+
 	if (!IsServer(client) && !IsNoFakeLag(client) &&
 #ifdef FAKELAG_CONFIGURABLE
 	    !(client->local->class && (client->local->class->options & CLASS_OPT_NOFAKELAG)) &&
 #endif
 	    !ValidatePermissionsForPath("immune:lag",client,NULL,NULL,NULL))
 	{
-		client->local->since += (1 + cmdbytes/90);
+		int lag_penalty = settings->period[FLD_LAG_PENALTY];
+		int lag_penalty_bytes = settings->limit[FLD_LAG_PENALTY];
+
+		client->local->fake_lag_msec += (1 + (command_bytes/lag_penalty_bytes) + (mtags_bytes/lag_penalty_bytes)) * lag_penalty;
+
+		/* This code takes into account not only the msecs we just calculated
+		 * but also any leftover msec from previous lagging up.
+		 */
+		client->local->fake_lag += (client->local->fake_lag_msec / 1000);
+		client->local->fake_lag_msec = client->local->fake_lag_msec % 1000;
 	}
 }
 
+/* Add extra fake lag to client, such as after a failed oper attempt.
+ */
+void add_fake_lag(Client *client, long msec)
+{
+	if (!MyConnect(client))
+		return;
+
+	client->local->fake_lag_msec += msec;
+	client->local->fake_lag += (client->local->fake_lag_msec / 1000);
+	client->local->fake_lag_msec = client->local->fake_lag_msec % 1000;
+}
+
 /** Returns 1 if the client is lagged up and data should NOT be parsed.
  * See also parse_addlag() for more information on "fake lag".
  * @param client	The client to check
@@ -596,7 +612,7 @@ static int client_lagged_up(Client *client)
 		return 0;
 	if (ValidatePermissionsForPath("immune:lag",client,NULL,NULL,NULL))
 		return 0;
-	if (client->local->since - TStime() < 10)
+	if (client->local->fake_lag - TStime() < 10)
 		return 0;
 	return 1;
 }
@@ -611,18 +627,19 @@ static int client_lagged_up(Client *client)
  * @note  In general you should NOT send anything back if you receive
  *        a numeric, this to prevent creating loops.
  */
-static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int parc, char *parv[])
+static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int parc, const char *parv[])
 {
 	Client *acptr;
 	Channel *channel;
 	char *nick, *p;
 	int i;
 	char buffer[BUFSIZE];
+	char targets[BUFSIZE];
 
 	if ((numeric < 0) || (numeric > 999))
 		return -1;
 
-	if (MyConnect(client) && !IsServer(client) && !IsUser(client) && IsHandshake(client) && client->serv && !IsServerSent(client))
+	if (MyConnect(client) && !IsServer(client) && !IsUser(client) && IsHandshake(client) && client->server && !IsServerSent(client))
 	{
 		/* This is an outgoing server connect that is currently not yet IsServer() but in 'unknown' state.
 		 * We need to handle a few responses here.
@@ -631,7 +648,7 @@ static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int p
 		/* STARTTLS: unknown command */
 		if ((numeric == 451) && (parc > 2) && strstr(parv[1], "STARTTLS"))
 		{
-			if (client->serv->conf && (client->serv->conf->outgoing.options & CONNECT_INSECURE))
+			if (client->server->conf && (client->server->conf->outgoing.options & CONNECT_INSECURE))
 				start_server_handshake(client);
 			else
 				reject_insecure_server(client);
@@ -641,7 +658,9 @@ static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int p
 		/* STARTTLS failed */
 		if (numeric == 691)
 		{
-			sendto_umode(UMODE_OPER, "STARTTLS failed for link %s. Please check the other side of the link.", client->name);
+			unreal_log(ULOG_WARNING, "link", "STARTTLS_FAILED", client,
+			           "Switching from plaintext to TLS via STARTTLS failed for server $client, this is unusual. "
+			           "Please check the other side of the link for errors.");
 			reject_insecure_server(client);
 			return 0;
 		}
@@ -652,7 +671,8 @@ static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int p
 			int ret = client_starttls(client);
 			if (ret < 0)
 			{
-				sendto_umode(UMODE_OPER, "STARTTLS handshake failed for link %s. Strange.", client->name);
+				unreal_log(ULOG_WARNING, "link", "STARTTLS_FAILED", client,
+					   "Switching from plaintext to TLS via STARTTLS failed for server $client, this is unusual.");
 				reject_insecure_server(client);
 				return ret;
 			}
@@ -680,7 +700,8 @@ static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int p
 	concat_params(buffer, sizeof(buffer), parc, parv);
 
 	/* Now actually process the numeric, IOTW: send it on */
-	for (; (nick = strtoken(&p, parv[1], ",")); parv[1] = NULL)
+	strlcpy(targets, parv[1], sizeof(targets));
+	for (nick = strtoken(&p, targets, ","); nick; nick = strtoken(&p, NULL, ","))
 	{
 		if ((acptr = find_client(nick, NULL)))
 		{
@@ -710,7 +731,7 @@ static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int p
 				sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
 				    client->name, numeric, buffer);
 		}
-		else if ((channel = find_channel(nick, NULL)))
+		else if ((channel = find_channel(nick)))
 		{
 			sendto_channel(channel, client, client->direction,
 			               0, 0, SEND_ALL, recv_mtags,
@@ -739,10 +760,6 @@ static void remove_unknown(Client *client, char *sender)
 	if (!IsServer(client))
 		return;
 
-#ifdef DEVELOP
-	sendto_ops("Killing %s (%s)", sender, backupbuf);
-	return;
-#endif
 	/*
 	 * Do kill if it came from a server because it means there is a ghost
 	 * user on the other server which needs to be removed. -avalon
diff --git a/src/random.c b/src/random.c
@@ -276,7 +276,7 @@ chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
  */
 
 /* Modified for UnrealIRCd by Bram Matthys ("Syzop") in 2019.
- * Things like taking out #if(n)def's for openssl (which we always
+ * Things like taking out #if (n)def's for openssl (which we always
  * compile with), re-indenting, removing various stuff, etc.
  */
 
@@ -308,8 +308,9 @@ static void _rs_stir(void)
 
 	if (RAND_bytes(rnd, sizeof(rnd)) <= 0)
 	{
-		ircd_log(LOG_ERROR, "Couldn't obtain random bytes (error 0x%lx)",
-		    (unsigned long)ERR_get_error());
+		unreal_log(ULOG_FATAL, "random", "RANDOM_OUT_OF_BYTES", NULL,
+		           "Could not obtain random bytes, error $tls_error_code",
+		           log_data_integer("tls_error_code", ERR_get_error()));
 		abort();
 	}
 
@@ -422,12 +423,12 @@ static void arc4_addrandom(void *dat, int datlen)
 
 void add_entropy_configfile(struct stat *st, char *buf)
 {
-	unsigned char mdbuf[16];
+	char sha256buf[SHA256_DIGEST_LENGTH];
 
 	arc4_addrandom(&st->st_size, sizeof(st->st_size));
 	arc4_addrandom(&st->st_mtime, sizeof(st->st_mtime));
-	DoMD5(mdbuf, buf, strlen(buf));
-	arc4_addrandom(&mdbuf, sizeof(mdbuf));
+	sha256hash_binary(sha256buf, buf, strlen(buf));
+	arc4_addrandom(sha256buf, sizeof(sha256buf));
 }
 
 /*
@@ -459,7 +460,6 @@ void init_random()
 	if (fd >= 0)
 	{
 		int n = read(fd, &rdat.rnd, sizeof(rdat.rnd));
-		Debug((DEBUG_INFO, "init_random: read from /dev/urandom returned %d", n));
 		close(fd);
 	}
 #else
diff --git a/src/send.c b/src/send.c
@@ -28,8 +28,8 @@
 
 /* Some forward declarions are needed */
 void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl);
-void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl);
-static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl);
+void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) __attribute__((format(printf,4,0)));
+static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl) __attribute__((format(printf,4,0)));
 
 #define ADD_CRLF(buf, len) { if (len > 510) len = 510; \
                              buf[len++] = '\r'; buf[len++] = '\n'; buf[len] = '\0'; } while(0)
@@ -59,7 +59,7 @@ MODVAR int  current_serial;
  * @param to		Client to mark as dead
  * @param notice	The quit reason to use
  */
-int dead_socket(Client *to, char *notice)
+int dead_socket(Client *to, const char *notice)
 {
 	DBufClear(&to->local->recvQ);
 	DBufClear(&to->local->sendQ);
@@ -76,9 +76,14 @@ int dead_socket(Client *to, char *notice)
 		return -1; /* don't overwrite & don't send multiple times */
 	
 	if (!IsUser(to) && !IsUnknown(to) && !IsClosing(to))
-		sendto_ops_and_log("Link to server %s (%s) closed: %s",
-			to->name, to->ip?to->ip:"<no-ip>", notice);
-	Debug((DEBUG_ERROR, "dead_socket: %s - %s", notice, get_client_name(to, FALSE)));
+	{
+		/* Looks like a duplicate error message to me?
+		 * If so, remove it here.
+		 */
+		unreal_log(ULOG_ERROR, "link", "LINK_CLOSING", to,
+		           "Link to server $client.details closed: $reason",
+		           log_data_string("reason", notice));
+	}
 	safe_strdup(to->local->error_str, notice);
 	return -1;
 }
@@ -123,7 +128,6 @@ int send_queued(Client *to)
 			return dead_socket(to, buf);
 		}
 		dbuf_delete(&to->local->sendQ, rlen);
-		to->local->lastsq = DBufLength(&to->local->sendQ) / 1024;
 		if (want_read)
 		{
 			/* SSL_write indicated that it cannot write data at this
@@ -207,9 +211,17 @@ void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern
  */
 void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl)
 {
-	char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
+	const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
 
+	/* Need to ignore -Wformat-nonliteral here */
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
 	ircvsnprintf(sendbuf, sizeof(sendbuf), pattern, vl);
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
 
 	if (BadPtr(mtags_str))
 	{
@@ -248,8 +260,6 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 	Hook *h;
 	Client *intended_to = to;
 	
-	Debug((DEBUG_ERROR, "Sending [%s] to %s", msg, to->name));
-
 	if (to->direction)
 		to = to->direction;
 	if (IsDeadSocket(to))
@@ -259,11 +269,7 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 	{
 		/* This is normal when 'to' was being closed (via exit_client
 		 *  and close_connection) --Run
-		 * Print the debug message anyway...
 		 */
-		Debug((DEBUG_ERROR,
-		    "Local socket %s with negative fd %d... AARGH!", to->name,
-		    to->local->fd));
 		return;
 	}
 
@@ -289,14 +295,17 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 			p = strchr(msg+1, ' ');
 			if (!p)
 			{
-				ircd_log(LOG_ERROR, "[BUG] sendbufto_one(): Malformed message: %s",
-					msg);
+				unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_MALFORMED_MSG", to,
+				           "Malformed message to $client: $buf",
+				           log_data_string("buf", msg));
 				return;
 			}
-			if (p - msg > 500)
+			if (p - msg > 4094)
 			{
-				ircd_log(LOG_ERROR, "[BUG] sendbufto_one(): Spec-wise legal, but massively oversized message-tag (len %d)",
-				         (int)(p - msg));
+				unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG", to,
+				           "Oversized message to $client (length $length): $buf",
+				           log_data_integer("length", p - msg),
+				           log_data_string("buf", msg));
 				return;
 			}
 			p++; /* skip space character */
@@ -315,10 +324,17 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 		len = quick;
 	}
 
-	if (len >= 1024)
+	if (len >= 10240)
 	{
-		ircd_log(LOG_ERROR, "sendbufto_one: len=%d, quick=%u", len, quick);
+		unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG2", to,
+			   "Oversized message to $client (length $length): $buf",
+			   log_data_integer("length", len),
+			   log_data_string("buf", msg));
+#ifdef DEBUGMODE
 		abort();
+#else
+		return;
+#endif
 	}
 
 	if (IsMe(to))
@@ -327,9 +343,9 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 
 		p = strchr(msg, '\r');
 		if (p) *p = '\0';
-		snprintf(tmp_msg, 500, "Trying to send data to myself! '%s'", msg);
-		ircd_log(LOG_ERROR, "%s", tmp_msg);
-		sendto_ops("%s", tmp_msg); /* recursion? */
+		unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_ME_MESSAGE", to,
+			   "Trying to send data to myself: $buf",
+			   log_data_string("buf", tmp_msg));
 		return;
 	}
 
@@ -340,7 +356,7 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 			return;
 	}
 
-#if defined(DEBUGMODE) && defined(RAWCMDLOGGING)
+#if defined(RAWCMDLOGGING)
 	{
 		char copy[512], *p;
 		strlcpy(copy, msg, len > sizeof(copy) ? sizeof(copy) : len);
@@ -348,16 +364,18 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 		if (p) *p = '\0';
 		p = strchr(copy, '\r');
 		if (p) *p = '\0';
-		ircd_log(LOG_ERROR, "-> %s: %s", to->name, copy);
+		unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_OUT", to,
+		           "-> $client: $data",
+		           log_data_string("data", copy));
 	}
 #endif
 
 	if (DBufLength(&to->local->sendQ) > get_sendq(to))
 	{
-		if (IsServer(to))
-			sendto_ops("Max SendQ limit exceeded for %s: %u > %d",
-			    get_client_name(to, FALSE), DBufLength(&to->local->sendQ),
-			    get_sendq(to));
+		unreal_log(ULOG_INFO, "flood", "SENDQ_EXCEEDED", to,
+		           "Flood of queued data to $client.details [$client.ip] exceeds class::sendq ($sendq > $class_sendq) (Too much data queued to be sent to this client)",
+		           log_data_integer("sendq", DBufLength(&to->local->sendQ)),
+		           log_data_integer("class_sendq", get_sendq(to)));
 		dead_socket(to, "Max SendQ exceeded");
 		return;
 	}
@@ -370,8 +388,8 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 	 * only really sent. Queued bytes get updated in SendQueued.
 	 */
 	// FIXME: something is wrong here, I think we do double counts, either in message or in traffic, I forgot.. CHECK !!!!
-	to->local->sendM += 1;
-	me.local->sendM += 1;
+	to->local->traffic.messages_sent++;
+	me.local->traffic.messages_sent++;
 
 	/* Previously we ran send_queued() here directly, but that is
 	 * a bad idea, CPU-wise. So now we just mark the client indicating
@@ -385,11 +403,11 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
  * now there is 1 single function. This also means that you most
  * likely will pass NULL or 0 as some parameters.
  * @param channel       The channel to send to
- * @param from        The source of the message
- * @param skip        The client to skip (can be NULL).
- *                    Note that if you specify a remote link then
- *                    you usually mean xyz->direction and not xyz.
- * @param prefix      Any combination of PREFIX_* (can be 0 for all)
+ * @param from          The source of the message
+ * @param skip          The client to skip (can be NULL).
+ *                      Note that if you specify a remote link then
+ *                      you usually mean xyz->direction and not xyz.
+ * @param member_modes  Require any of the member_modes to be set (eg: "o"), or NULL to skip this check.
  * @param clicap      Client capability the recipient should have
  *                    (this only works for local clients, we will
  *                     always send the message to remote clients and
@@ -414,14 +432,14 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
  *         sendnumeric(client, ERR_NEEDMOREPARAMS, "SAYHELLO");
  *         return;
  *     }
- *     channel = find_channel(parv[1], NULL);
+ *     channel = find_channel(parv[1]);
  *     if (!channel)
  *     {
  *         sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
  *         return;
  *     }
  *     new_message(client, recv_mtags, &mtags);
- *     sendto_channel(channel, client, client->direction, 0, 0,
+ *     sendto_channel(channel, client, client->direction, NULL, 0,
  *                    SEND_LOCAL|SEND_REMOTE, mtags,
  *                    ":%s PRIVMSG %s :Hello everyone!!!",
  *                    client->name, channel->name);
@@ -430,13 +448,20 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
  * @endcode
  */
 void sendto_channel(Channel *channel, Client *from, Client *skip,
-                    int prefix, long clicap, int sendflags,
+                    char *member_modes, long clicap, int sendflags,
                     MessageTag *mtags,
                     FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
 	Member *lp;
 	Client *acptr;
+	char member_modes_ext[64];
+
+	if (member_modes)
+	{
+		channel_member_modes_generate_equal_or_greater(member_modes, member_modes_ext, sizeof(member_modes_ext));
+		member_modes = member_modes_ext;
+	}
 
 	++current_serial;
 	for (lp = channel->members; lp; lp = lp->next)
@@ -452,23 +477,9 @@ void sendto_channel(Channel *channel, Client *from, Client *skip,
 		/* Don't send to NOCTCP clients */
 		if (has_user_mode(acptr, 'T') && (sendflags & SKIP_CTCP))
 			continue;
-		/* Now deal with 'prefix' (if non-zero) */
-		if (!prefix)
-			goto good;
-		if ((prefix & PREFIX_HALFOP) && (lp->flags & CHFL_HALFOP))
-			goto good;
-		if ((prefix & PREFIX_VOICE) && (lp->flags & CHFL_VOICE))
-			goto good;
-		if ((prefix & PREFIX_OP) && (lp->flags & CHFL_CHANOP))
-			goto good;
-#ifdef PREFIX_AQ
-		if ((prefix & PREFIX_ADMIN) && (lp->flags & CHFL_CHANADMIN))
-			goto good;
-		if ((prefix & PREFIX_OWNER) && (lp->flags & CHFL_CHANOWNER))
-			goto good;
-#endif
-		continue;
-good:
+		/* Now deal with 'member_modes' (if not NULL) */
+		if (member_modes && !check_channel_access_member(lp, member_modes))
+			continue;
 		/* Now deal with 'clicap' (if non-zero) */
 		if (clicap && MyUser(acptr) && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
 			continue;
@@ -629,7 +640,7 @@ void sendto_local_common_channels(Client *user, Client *skip, long clicap, Messa
 ** addition -- Armin, 8jun90 (gruner@informatik.tu-muenchen.de)
 */
 
-static int match_it(Client *one, char *mask, int what)
+static int match_it(Client *one, const char *mask, int what)
 {
 	switch (what)
 	{
@@ -651,7 +662,7 @@ static int match_it(Client *one, char *mask, int what)
  * @param pattern	Format string
  * @param ...		Parameters to the format string
  */
-void sendto_match_butone(Client *one, Client *from, char *mask, int what,
+void sendto_match_butone(Client *one, Client *from, const char *mask, int what,
                          MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
@@ -693,116 +704,6 @@ void sendto_match_butone(Client *one, Client *from, char *mask, int what,
 	}
 }
 
-/** Send a message to all locally connected IRCOps
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_ops(FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	char nbuf[1024];
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-		if (!IsServer(acptr) && !IsMe(acptr) && SendServNotice(acptr))
-		{
-			ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :*** ", me.name, acptr->name);
-			strlcat(nbuf, pattern, sizeof nbuf);
-
-			va_start(vl, pattern);
-			vsendto_one(acptr, NULL, nbuf, vl);
-			va_end(vl);
-		}
-}
-
-/* Hmm.. so local sending is called sendto_ops() and local+remote is sendto_ops_butone(),
- * that is weird naming... (TODO fix some day in a new major series)
- */
-
-/** Send a message to all IRCOps (local and remote), except one.
- * @param one		Skip sending the message to this client/direction
- * @param from		The sender (can not be NULL)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_ops_butone(Client *one, Client *from, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-
-	++current_serial;
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (!SendWallops(acptr))
-			continue;
-		if (acptr->direction->local->serial == current_serial)	/* sent message along it already ? */
-			continue;
-		if (acptr->direction == one)
-			continue;	/* ...was the one I should skip */
-		acptr->direction->local->serial = current_serial;
-
-		va_start(vl, pattern);
-		vsendto_prefix_one(acptr->direction, from, NULL, pattern, vl);
-		va_end(vl);
-	}
-}
-
-/** This function does exactly the same as sendto_ops() in practice in 5.x.
- * There used to be a difference between sendto_ops() and sendto_realops()
- * with regards to user-settable snomasks, but this is no longer the case.
- * TODO: remove this function in some future cleanup
- */
-void sendto_realops(FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	char nbuf[1024];
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :*** ", me.name, acptr->name);
-		strlcat(nbuf, pattern, sizeof nbuf);
-
-		va_start(vl, pattern);
-		vsendto_one(acptr, NULL, nbuf, vl);
-		va_end(vl);
-	}
-}
-
-/** Send a message to all locally connected IRCOps and also log the error.
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_ops_and_log(FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	char buf[1024];
-
-	va_start(vl, pattern);
-	ircvsnprintf(buf, sizeof(buf), pattern, vl);
-	va_end(vl);
-
-	ircd_log(LOG_ERROR, "%s", buf);
-	sendto_umode(UMODE_OPER, "%s", buf);
-}
-
-/** This function does exactly the same as sendto_ops_and_log()
- * TODO: remove this function in some future cleanup
- */
-void sendto_realops_and_log(FORMAT_STRING(const char *fmt), ...)
-{
-	va_list vl;
-	static char buf[2048];
-
-	va_start(vl, fmt);
-	vsnprintf(buf, sizeof(buf), fmt, vl);
-	va_end(vl);
-
-	sendto_realops("%s", buf);
-	ircd_log(LOG_ERROR, "%s", buf);
-}
-
-
 /** Send a message to all locally connected users with specified user mode.
  * @param umodes	The umode that the recipient should have set (one of UMODE_)
  * @param pattern	The format string / pattern to use.
@@ -835,22 +736,13 @@ void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
 	Client *acptr;
+	Umode *um;
 	char nbuf[1024];
-	int i;
 	char modestr[128];
 	char *p;
 
 	/* Convert 'umodes' (int) to 'modestr' (string) */
-	*modestr = '\0';
-	p = modestr;
-	for(i = 0; i <= Usermode_highest; i++)
-	{
-		if (!Usermode_Table[i].flag)
-			continue;
-		if (umodes & Usermode_Table[i].mode)
-			*p++ = Usermode_Table[i].flag;
-	}
-	*p = '\0';
+	get_usermode_string_raw_r(umodes, modestr, sizeof(modestr));
 
 	list_for_each_entry(acptr, &lclient_list, lclient_node)
 	{
@@ -873,66 +765,12 @@ void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
 	}
 }
 
-/** Send a message to all locally connected users with specified snomask.
- * @param snomask	The snomask that the recipient should have set (one of SNO_*)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_snomask(int snomask, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	char nbuf[2048];
-
-	va_start(vl, pattern);
-	ircvsnprintf(nbuf, sizeof(nbuf), pattern, vl);
-	va_end(vl);
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->user->snomask & snomask)
-			sendnotice(acptr, "%s", nbuf);
-	}
-}
-
-/** Send a message to all users with specified snomask (local and remote users).
- * @param snomask	The snomask that the recipient should have set (one of SNO_*)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_snomask_global(int snomask, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	int  i;
-	char nbuf[2048], snobuf[32], *p;
-
-	va_start(vl, pattern);
-	ircvsnprintf(nbuf, sizeof(nbuf), pattern, vl);
-	va_end(vl);
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->user->snomask & snomask)
-			sendnotice(acptr, "%s", nbuf);
-	}
-
-	/* Build snomasks-to-send-to buffer */
-	snobuf[0] = '\0';
-	for (i = 0, p=snobuf; i<= Snomask_highest; i++)
-		if (snomask & Snomask_Table[i].mode)
-			*p++ = Snomask_Table[i].flag;
-	*p = '\0';
-
-	sendto_server(NULL, 0, 0, NULL, ":%s SENDSNO %s :%s", me.id, snobuf, nbuf);
-}
-
 /** Send CAP DEL and CAP NEW notification to clients supporting it.
  * This function is mostly meant to be used by the CAP and SASL modules.
  * @param add		Whether the CAP token is added (1) or removed (0)
  * @param token		The CAP token
  */
-void send_cap_notify(int add, char *token)
+void send_cap_notify(int add, const char *token)
 {
 	Client *client;
 	ClientCapability *clicap = ClientCapabilityFindReal(token);
@@ -944,7 +782,7 @@ void send_cap_notify(int add, char *token)
 		{
 			if (add)
 			{
-				char *args = NULL;
+				const char *args = NULL;
 				if (clicap)
 				{
 					if (clicap->visible && !clicap->visible(client))
@@ -989,7 +827,7 @@ static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, con
 		va_arg(vl, char *); /* eat first parameter */
 
 		*buf = ':';
-		strcpy(buf+1, from->name);
+		strlcpy(buf+1, from->name, buflen-1);
 
 		if (IsUser(from))
 		{
@@ -998,17 +836,24 @@ static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, con
 
 			if (*username)
 			{
-				strcat(buf, "!");
-				strcat(buf, username);
+				strlcat(buf, "!", buflen);
+				strlcat(buf, username, buflen);
 			}
 			if (*host)
 			{
-				strcat(buf, "@");
-				strcat(buf, host);
+				strlcat(buf, "@", buflen);
+				strlcat(buf, host, buflen);
 			}
 		}
 		/* Now build the remaining string */
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
 		ircvsnprintf(buf + strlen(buf), buflen - strlen(buf), &pattern[3], vl);
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
 	}
 	else
 	{
@@ -1048,7 +893,7 @@ void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRIN
  */
 void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl)
 {
-	char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
+	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);
@@ -1066,65 +911,12 @@ void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char 
 	}
 }
 
-void sendto_connectnotice(Client *newuser, int disconnect, char *comment)
-{
-	Client *acptr;
-	char connect[512];
-
-	if (!disconnect)
-	{
-		RunHook(HOOKTYPE_LOCAL_CONNECT, newuser);
-
-		ircsnprintf(connect, sizeof(connect),
-		    "*** Client connecting: %s (%s@%s) [%s] %s", newuser->name,
-		    newuser->user->username, newuser->user->realhost, newuser->ip,
-		    get_connect_extinfo(newuser));
-	}
-	else
-	{
-		ircsnprintf(connect, sizeof(connect), "*** Client exiting: %s (%s@%s) [%s] (%s)",
-			newuser->name, newuser->user->username, newuser->user->realhost, newuser->ip, comment);
-	}
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->user->snomask & SNO_CLIENT)
-			sendnotice(acptr, "%s", connect);
-	}
-}
-
-void sendto_fconnectnotice(Client *newuser, int disconnect, char *comment)
-{
-	Client *acptr;
-	char connect[512];
-
-	if (!disconnect)
-	{
-		ircsnprintf(connect, sizeof(connect),
-		    "*** Client connecting: %s (%s@%s) [%s] %s", newuser->name,
-		    newuser->user->username, newuser->user->realhost, newuser->ip ? newuser->ip : "0",
-		    get_connect_extinfo(newuser));
-	}
-	else
-	{
-		ircsnprintf(connect, sizeof(connect), "*** Client exiting: %s (%s@%s) [%s] (%s)",
-			newuser->name, newuser->user->username, newuser->user->realhost,
-			newuser->ip ? newuser->ip : "0", comment);
-	}
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->user->snomask & SNO_FCLIENT)
-			sendto_one(acptr, NULL, ":%s NOTICE %s :%s", newuser->user->server, acptr->name, connect);
-	}
-}
-
 /** Introduce user to all other servers, except the one to skip.
  * @param one    Server to skip (can be NULL)
  * @param client Client to introduce
  * @param umodes User modes of client
  */
-void sendto_serv_butone_nickcmd(Client *one, Client *client, char *umodes)
+void sendto_serv_butone_nickcmd(Client *one, MessageTag *mtags, Client *client, const char *umodes)
 {
 	Client *acptr;
 
@@ -1133,7 +925,7 @@ void sendto_serv_butone_nickcmd(Client *one, Client *client, char *umodes)
 		if (one && acptr == one->direction)
 			continue;
 		
-		sendto_one_nickcmd(acptr, client, umodes);
+		sendto_one_nickcmd(acptr, mtags, client, umodes);
 	}
 }
 
@@ -1142,9 +934,10 @@ void sendto_serv_butone_nickcmd(Client *one, Client *client, char *umodes)
  * @param client  Client to introduce
  * @param umodes  User modes of client
  */
-void sendto_one_nickcmd(Client *server, Client *client, char *umodes)
+void sendto_one_nickcmd(Client *server, MessageTag *mtags, Client *client, const char *umodes)
 {
 	char *vhost;
+	char mtags_generated = 0;
 
 	if (!*umodes)
 		umodes = "+";
@@ -1164,13 +957,22 @@ void sendto_one_nickcmd(Client *server, Client *client, char *umodes)
 			vhost = "*";
 	}
 
-	sendto_one(server, NULL,
+	if (mtags == NULL)
+	{
+		moddata_add_s2s_mtags(client, &mtags);
+		mtags_generated = 1;
+	}
+
+	sendto_one(server, mtags,
 		":%s UID %s %d %lld %s %s %s %s %s %s %s %s :%s",
-		client->srvptr->id, client->name, client->hopcount,
+		client->uplink->id, client->name, client->hopcount,
 		(long long)client->lastnick,
 		client->user->username, client->user->realhost, client->id,
-		client->user->svid, umodes, vhost, getcloak(client),
+		client->user->account, umodes, vhost, getcloak(client),
 		encode_ip(client->ip), client->info);
+
+	if (mtags_generated)
+		safe_free_message_tags(mtags);
 }
 
 /* sidenote: sendnotice() and sendtxtnumeric() assume no client or server
@@ -1205,36 +1007,6 @@ void sendnotice_multiline(Client *client, MultiLine *m)
 		sendnotice(client, "%s", m->line);
 }
 
-
-/** Send numeric message to a client.
- * @param to		The recipient
- * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
- * @param ...		The parameters for the numeric
- * @note Be sure to provide the correct number and type of parameters that belong to the numeric. Check src/numeric.c when in doubt!
- * @section sendnumeric_examples Examples
- * @subsection sendnumeric_permission_denied Send "Permission Denied" numeric
- * This numeric has no parameter, so is simple:
- * @code
- * sendnumeric(client, ERR_NOPRIVILEGES);
- * @endcode
- * @subsection sendnumeric_notenoughparameters Send "Not enough parameters" numeric
- * This numeric requires 1 parameter: the name of the command.
- * @code
- * sendnumeric(client, ERR_NEEDMOREPARAMS, "SOMECOMMAND");
- * @endcode
- */
-void sendnumeric(Client *to, int numeric, ...)
-{
-	va_list vl;
-	char pattern[512];
-
-	snprintf(pattern, sizeof(pattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", rpl_str(numeric));
-
-	va_start(vl, numeric);
-	vsendto_one(to, NULL, pattern, vl);
-	va_end(vl);
-}
-
 /** 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.
@@ -1276,6 +1048,56 @@ void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
 	va_end(vl);
 }
 
+/** Build buffer in order to send a numeric message to a client - rarely used.
+ * @param buf		The buffer that should be used
+ * @param buflen	The size of the buffer
+ * @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.
+ */
+void buildnumericfmt(char *buf, size_t buflen, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	char realpattern[512];
+
+	snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern);
+
+	va_start(vl, pattern);
+	/* Need to ignore -Wformat-nonliteral here */
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+	vsnprintf(buf, buflen, realpattern, vl);
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+	va_end(vl);
+}
+
+void add_nvplist_numeric_fmt(NameValuePrioList **lst, int priority, const char *name, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	char realpattern[512], buf[512];
+
+	snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern);
+
+	va_start(vl, pattern);
+	/* Need to ignore -Wformat-nonliteral here */
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+	vsnprintf(buf, sizeof(buf), realpattern, vl);
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+	va_end(vl);
+
+	add_nvplist(lst, priority, name, buf);
+}
+
 /* Send raw data directly to socket, bypassing everything.
  * Looks like an interesting function to call? NO! STOP!
  * Don't use this function. It may only be used by the initial
@@ -1291,14 +1113,14 @@ void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
  * By the way, did I already mention that you SHOULD NOT USE THIS
  * FUNCTION? ;)
  */
-void send_raw_direct(Client *user, FORMAT_STRING(FORMAT_STRING(const char *pattern)), ...)
+void send_raw_direct(Client *user, FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
 	int sendlen;
 
 	*sendbuf = '\0';
 	va_start(vl, pattern);
-	sendlen = vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, user, pattern, vl);
+	sendlen = vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf), user, pattern, vl);
 	va_end(vl);
 	(void)send(user->local->fd, sendbuf, sendlen, 0);
 }
diff --git a/src/serv.c b/src/serv.c
@@ -53,14 +53,17 @@ int MODVAR spamf_ugly_vchanoverride = 0;
 
 void read_motd(const char *filename, MOTDFile *motd);
 void do_read_motd(const char *filename, MOTDFile *themotd);
-#ifdef USE_LIBCURL
-void read_motd_async_downloaded(const char *url, const char *filename, const char *errorbuf, int cached, MOTDDownload *motd_download);
-#endif
 
 extern MOTDLine *find_file(char *, short);
 
 void reread_motdsandrules();
 
+#if defined(__GNUC__)
+/* Temporarily ignore for this function. FIXME later!!! */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
 /** Send a message upstream if necessary and check if it's for us.
  * @param client	The sender
  * @param mtags		Message tags associated with this message
@@ -68,13 +71,24 @@ void reread_motdsandrules();
  * @param server	This indicates parv[server] contains the destination
  * @param parc		Parameter count (MAX 8!!)
  * @param parv		Parameter values (MAX 8!!)
- * @note Command can have only max 8 parameters (parv[8])
- * @note parv[server] is replaced with the name of the matched client.
+ * @note While sending parv[server] is replaced with the name of the matched client
+ *       (virtually, as parv[] is not actually written to)
  */
-int hunt_server(Client *client, MessageTag *mtags, char *command, int server, int parc, char *parv[])
+int hunt_server(Client *client, MessageTag *mtags, const char *command, int server, int parc, const char *parv[])
 {
 	Client *acptr;
-	char *saved;
+	const char *saved;
+	int i;
+	char buf[1024];
+
+	if (strchr(command, '%') || strchr(command, ' '))
+	{
+		unreal_log(ULOG_ERROR, "main", "BUG_HUNT_SERVER", client,
+		           "[BUG] hunt_server called with command '$command' but it may not contain "
+		           "spaces or percentage signs nowadays, it must be ONLY the command.",
+		           log_data_string("command", command));
+		abort();
+	}
 
 	/* This would be strange and bad. Previous version assumed "it's for me". Hmm.. okay. */
 	if (parc <= server || BadPtr(parv[server]))
@@ -102,21 +116,46 @@ int hunt_server(Client *client, MessageTag *mtags, char *command, int server, in
 		return HUNTED_NOSUCH;
 	}
 
-	/* Replace "server" part with actual servername (eg: 'User' -> 'x.y.net')
-	 * Ugly. Previous version didn't even restore the state, now we do.
+	/* This puts all parv[] arguments in 'buf'
+	 * Taken from concat_params() but this one is
+	 * with parv[server] magic replacement.
 	 */
-	saved = parv[server];
-	parv[server] = acptr->id;
+	*buf = '\0';
+	for (i = 1; i < parc; i++)
+	{
+		const char *param = parv[i];
 
-	sendto_one(acptr, mtags, command, client->id,
-	    parv[1], parv[2], parv[3], parv[4],
-	    parv[5], parv[6], parv[7], parv[8]);
+		if (!param)
+			break;
+
+		/* The magic parv[server] replacement:
+		 * this replaces eg 'User' with '001' in S2S traffic.
+		 */
+		if (i == server)
+			param = acptr->id;
+
+		if (*buf)
+			strlcat(buf, " ", sizeof(buf));
+
+		if (strchr(param, ' ') || (*param == ':'))
+		{
+			/* Last parameter, with : */
+			strlcat(buf, ":", sizeof(buf));
+			strlcat(buf, parv[i], sizeof(buf));
+			break;
+		}
+		strlcat(buf, parv[i], sizeof(buf));
+	}
 
-	parv[server] = saved;
+	sendto_one(acptr, mtags, ":%s %s %s", client->id, command, buf);
 
 	return HUNTED_PASS;
 }
 
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
 #ifndef _WIN32
 /** Grab operating system name on Windows (outdated) */
 char *getosname(void)
@@ -146,12 +185,17 @@ char *getosname(void)
 #endif
 
 /** Helper function to send version strings */
-void send_version(Client *client, int reply)
+void send_version(Client *client, int remote)
 {
 	int i;
 
 	for (i = 0; ISupportStrings[i]; i++)
-		sendnumeric(client, reply, ISupportStrings[i]);
+	{
+		if (remote)
+			sendnumeric(client, RPL_REMOTEISUPPORT, ISupportStrings[i]);
+		else
+			sendnumeric(client, RPL_ISUPPORT, ISupportStrings[i]);
+	}
 }
 
 /** VERSION command:
@@ -162,11 +206,11 @@ CMD_FUNC(cmd_version)
 	/* Only allow remote VERSIONs if registered -- Syzop */
 	if (!IsUser(client) && !IsServer(client))
 	{
-		send_version(client, RPL_ISUPPORT);
+		send_version(client, 0);
 		return;
 	}
 
-	if (hunt_server(client, recv_mtags, ":%s VERSION :%s", 1, parc, parv) == HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "VERSION", 1, parc, parv) == HUNTED_ISME)
 	{
 		sendnumeric(client, RPL_VERSION, version, debugmode, me.name,
 			    (ValidatePermissionsForPath("server:info",client,NULL,NULL,NULL) ? serveropts : "0"),
@@ -185,9 +229,9 @@ CMD_FUNC(cmd_version)
 			sendnotice(client, "%s", pcre2_version());
 		}
 		if (MyUser(client))
-			send_version(client,RPL_ISUPPORT);
+			send_version(client,0);
 		else
-			send_version(client,RPL_REMOTEISUPPORT);
+			send_version(client,1);
 	}
 }
 
@@ -206,14 +250,14 @@ void send_proto(Client *client, ConfigItem_link *aconf)
 	 */
 
 	/* First line */
-	sendto_one(client, NULL, "PROTOCTL NOQUIT NICKv2 SJOIN SJOIN2 UMODE2 VL SJ3 TKLEXT TKLEXT2 NICKIP ESVID %s %s",
+	sendto_one(client, NULL, "PROTOCTL NOQUIT NICKv2 SJOIN SJOIN2 UMODE2 VL SJ3 TKLEXT TKLEXT2 NICKIP ESVID NEXTBANS %s %s",
 	           iConf.ban_setter_sync ? "SJSBY" : "",
 	           ClientCapabilityFindReal("message-tags") ? "MTAGS" : "");
 
 	/* Second line */
-	sendto_one(client, NULL, "PROTOCTL CHANMODES=%s%s,%s%s,%s%s,%s%s USERMODES=%s BOOTED=%lld PREFIX=%s SID=%s MLOCK TS=%lld EXTSWHOIS",
-		CHPAR1, EXPAR1, CHPAR2, EXPAR2, CHPAR3, EXPAR3, CHPAR4, EXPAR4,
-		umodestring, (long long)me.local->since, prefix->value,
+	sendto_one(client, NULL, "PROTOCTL CHANMODES=%s%s,%s,%s,%s USERMODES=%s BOOTED=%lld PREFIX=%s SID=%s MLOCK TS=%lld EXTSWHOIS",
+		CHPAR1, EXPAR1, EXPAR2, EXPAR3, EXPAR4,
+		umodestring, (long long)me.local->fake_lag, prefix->value,
 		me.id, (long long)TStime());
 
 	/* Third line */
@@ -227,7 +271,7 @@ void send_proto(Client *client, ConfigItem_link *aconf)
 #endif
 
 /** Special filter for remote commands */
-int remotecmdfilter(Client *client, int parc, char *parv[])
+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]))
@@ -252,6 +296,7 @@ char *unrealinfo[] =
 	"* Bram Matthys (Syzop) <syzop@unrealircd.org>",
 	"",
 	"Coders:",
+	"* Krzysztof Beresztant (k4be) <k4be@unrealircd.org>",
 	"* Gottem <gottem@unrealircd.org>",
 	"* i <i@unrealircd.org>",
 	"",
@@ -286,7 +331,7 @@ void cmd_info_send(Client *client)
 	sendnumericfmt(client, RPL_INFO, ":| UnrealIRCd Homepage: https://www.unrealircd.org");
 	sendnumericfmt(client, RPL_INFO, ":============================================");
 	sendnumericfmt(client, RPL_INFO, ":Birth Date: %s, compile # %s", creation, generation);
-	sendnumericfmt(client, RPL_INFO, ":On-line since %s", myctime(me.local->firsttime));
+	sendnumericfmt(client, RPL_INFO, ":On-line since %s", myctime(me.local->creationtime));
 	sendnumericfmt(client, RPL_INFO, ":ReleaseID (%s)", buildid);
 	sendnumeric(client, RPL_ENDOFINFO);
 }
@@ -299,7 +344,7 @@ CMD_FUNC(cmd_info)
 	if (remotecmdfilter(client, parc, parv))
 		return;
 
-	if (hunt_server(client, recv_mtags, ":%s INFO :%s", 1, parc, parv) == HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "INFO", 1, parc, parv) == HUNTED_ISME)
 		cmd_info_send(client);
 }
 
@@ -313,7 +358,7 @@ CMD_FUNC(cmd_license)
 	if (remotecmdfilter(client, parc, parv))
 		return;
 
-	if (hunt_server(client, recv_mtags, ":%s LICENSE :%s", 1, parc, parv) == HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "LICENSE", 1, parc, parv) == HUNTED_ISME)
 	{
 		while (*text)
 			sendnumeric(client, RPL_INFO, *text++);
@@ -333,20 +378,20 @@ CMD_FUNC(cmd_credits)
 	if (remotecmdfilter(client, parc, parv))
 		return;
 
-	if (hunt_server(client, recv_mtags, ":%s CREDITS :%s", 1, parc, parv) == HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, "CREDITS", 1, parc, parv) == HUNTED_ISME)
 	{
 		while (*text)
 			sendnumeric(client, RPL_INFO, *text++);
 
 		sendnumeric(client, RPL_INFO, "");
 		sendnumericfmt(client, RPL_INFO, ":Birth Date: %s, compile # %s", creation, generation);
-		sendnumericfmt(client, RPL_INFO, ":On-line since %s", myctime(me.local->firsttime));
+		sendnumericfmt(client, RPL_INFO, ":On-line since %s", myctime(me.local->creationtime));
 		sendnumeric(client, RPL_ENDOFINFO);
 	}
 }
 
-/** Return flags for a client (connection), eg 's' for SSL/TLS - used in STATS L/l */
-char *get_client_status(Client *client)
+/** Return flags for a client (connection), eg 's' for TLS - used in STATS L/l */
+const char *get_client_status(Client *client)
 {
 	static char buf[10];
 	char *p = buf;
@@ -371,28 +416,7 @@ char *get_client_status(Client *client)
 	}
 	*p++ = ']';
 	*p++ = '\0';
-	return (buf);
-}
-
-/** Used to blank out ports -- Barubary - only used in STATS l/L */
-char *get_client_name2(Client *client, int showports)
-{
-	char *pointer = get_client_name(client, TRUE);
-
-	if (!pointer)
-		return NULL;
-	if (showports)
-		return pointer;
-	if (!strrchr(pointer, '.'))
-		return NULL;
-	/*
-	 * This may seem like wack but remind this is only used 
-	 * in rows of get_client_name2's, so it's perfectly fair
-	 * 
-	*/
-	strcpy(strrchr(pointer, '.'), ".0]");
-
-	return pointer;
+	return buf;
 }
 
 /** ERROR command - used by servers to indicate errors.
@@ -400,29 +424,24 @@ char *get_client_name2(Client *client, int showports)
  */
 CMD_FUNC(cmd_error)
 {
-	char *para;
+	const char *para;
 
 	if (!MyConnect(client))
 		return;
 
 	para = (parc > 1 && *parv[1] != '\0') ? parv[1] : "<>";
 
-	/* Errors from untrusted sources only go to the junk snomask
-	 * (which is only for debugging issues and such).
-	 * This to prevent flooding and confusing IRCOps by
-	 * malicious users.
+	/* Errors from untrusted sources are ignored as any
+	 * malicious user can send these, confusing IRCOps etc.
+	 * One can always see the errors from the other side anyway.
 	 */
-	if (!IsServer(client) && !client->serv)
-	{
-		sendto_snomask(SNO_JUNK, "ERROR from server %s: %s",
-			get_client_name(client, FALSE), para);
+	if (!IsServer(client) && !client->server)
 		return;
-	}
 
-	sendto_umode_global(UMODE_OPER, "ERROR from server %s: %s",
-	                    get_client_name(client, FALSE), para);
-	ircd_log(LOG_ERROR, "ERROR from server %s: %s",
-	                    get_client_name(client, FALSE), para);
+	unreal_log(ULOG_ERROR, "link", "LINK_ERROR_MESSAGE", client,
+	           "Error from $client: $error_message",
+	           log_data_string("error_message", para),
+	           client->server->conf ? log_data_link_block(client->server->conf) : NULL);
 }
 
 /** Save the tunefile (such as: highest seen connection count) */
@@ -433,11 +452,11 @@ EVENT(save_tunefile)
 	tunefile = fopen(conf_files->tune_file, "w");
 	if (!tunefile)
 	{
-#if !defined(_WIN32) && !defined(_AMIGA)
-		sendto_ops("Unable to write tunefile.. %s", strerror(errno));
-#else
-		sendto_ops("Unable to write tunefile..");
-#endif
+		char *errstr = strerror(errno);
+		unreal_log(ULOG_WARNING, "config", "WRITE_TUNE_FILE_FAILED", NULL,
+		           "Unable to write tunefile '$filename': $system_error",
+		           log_data_string("filename", conf_files->tune_file),
+		           log_data_string("system_error", errstr));
 		return;
 	}
 	fprintf(tunefile, "0\n");
@@ -454,14 +473,15 @@ void load_tunefile(void)
 	tunefile = fopen(conf_files->tune_file, "r");
 	if (!tunefile)
 		return;
-	fprintf(stderr, "Loading tunefile..\n");
-	if (!fgets(buf, sizeof(buf), tunefile))
-	    fprintf(stderr, "Warning: error while reading the timestamp offset from the tunefile%s%s\n",
-		errno? ": ": "", errno? strerror(errno): "");
-
-	if (!fgets(buf, sizeof(buf), tunefile))
-	    fprintf(stderr, "Warning: error while reading the peak user count from the tunefile%s%s\n",
-		errno? ": ": "", errno? strerror(errno): "");
+	/* We ignore the first line, hence the weird looking double fgets here... */
+	if (!fgets(buf, sizeof(buf), tunefile) || !fgets(buf, sizeof(buf), tunefile))
+	{
+		char *errstr = strerror(errno);
+		unreal_log(ULOG_WARNING, "config", "READ_TUNE_FILE_FAILED", NULL,
+		           "Unable to read tunefile '$filename': $system_error",
+		           log_data_string("filename", conf_files->tune_file),
+		           log_data_string("system_error", errstr));
+	}
 	irccounts.me_max = atol(buf);
 	fclose(tunefile);
 }
@@ -501,7 +521,7 @@ extern void reinit_resolver(Client *client);
  */
 CMD_FUNC(cmd_rehash)
 {
-	int x = 0;
+	int x;
 
 	/* This is one of the (few) commands that cannot be handled
 	 * by labeled-response accurately in all circumstances.
@@ -521,13 +541,13 @@ CMD_FUNC(cmd_rehash)
 		if (parv[1] && (parv[1][0] == '-'))
 			x = HUNTED_ISME;
 		else
-			x = hunt_server(client, recv_mtags, ":%s REHASH :%s", 1, parc, parv);
+			x = hunt_server(client, recv_mtags, "REHASH", 1, parc, parv);
 	} else {
 		if (match_simple("-glob*", parv[1])) /* This is really ugly... hack to make /rehash -global -something work */
 		{
 			x = HUNTED_ISME;
 		} else {
-			x = hunt_server(client, NULL, ":%s REHASH %s :%s", 1, parc, parv);
+			x = hunt_server(client, NULL, "REHASH", 1, parc, parv);
 		}
 	}
 	if (x != HUNTED_ISME)
@@ -557,17 +577,14 @@ CMD_FUNC(cmd_rehash)
 #endif
 		if (parv[2] == NULL)
 		{
-			if (loop.ircd_rehashing)
+			if (loop.rehashing)
 			{
 				sendnotice(client, "A rehash is already in progress");
 				return;
 			}
-			sendto_umode_global(UMODE_OPER, "%s is remotely rehashing server %s config file", client->name, me.name);
+			unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
 			remote_rehash_client = client;
-			reread_motdsandrules();
-			// TODO: clean this next line up, wtf man.
-			rehash(client, (parc > 1) ? ((*parv[1] == 'q') ? 2 : 0) : 0);
-			return;
+			/* fallthrough... so we deal with this the same way as local rehashes */
 		}
 		parv[1] = parv[2];
 	} else {
@@ -583,16 +600,6 @@ CMD_FUNC(cmd_rehash)
 			parv[1] = parv[2];
 			parv[2] = NULL;
 			parc--;
-			/* Only netadmins may use /REHASH -global, which is because:
-			 * a) it makes sense
-			 * b) remote servers don't support remote rehashes by non-netadmins
-			 */
-			if (!ValidatePermissionsForPath("server:rehash",client,NULL,NULL,NULL))
-			{
-				sendnumeric(client, ERR_NOPRIVILEGES);
-				sendnotice(client, "'/REHASH -global' requires you to have server::rehash permissions");
-				return;
-			}
 			if (parv[1] && *parv[1] != '-')
 			{
 				sendnotice(client, "You cannot specify a server name after /REHASH -global, for obvious reasons");
@@ -614,19 +621,12 @@ CMD_FUNC(cmd_rehash)
 
 	if (!BadPtr(parv[1]) && strcasecmp(parv[1], "-all"))
 	{
-
-		if (!ValidatePermissionsForPath("server:rehash",client,NULL,NULL,NULL))
-		{
-			sendnumeric(client, ERR_NOPRIVILEGES);
-			return;
-		}
-
 		if (*parv[1] == '-')
 		{
 			if (!strncasecmp("-gar", parv[1], 4))
 			{
 				loop.do_garbage_collect = 1;
-				RunHook2(HOOKTYPE_REHASHFLAG, client, parv[1]);
+				RunHook(HOOKTYPE_REHASHFLAG, client, parv[1]);
 				return;
 			}
 			if (!strncasecmp("-dns", parv[1], 4))
@@ -636,58 +636,27 @@ CMD_FUNC(cmd_rehash)
 			}
 			if (match_simple("-ssl*", parv[1]) || match_simple("-tls*", parv[1]))
 			{
-				reinit_ssl(client);
-				return;
-			}
-			if (match_simple("-o*motd", parv[1]))
-			{
-				if (MyUser(client))
-					sendto_ops("Rehashing OPERMOTD on request of %s", client->name);
-				else
-					sendto_umode_global(UMODE_OPER, "Remotely rehashing OPERMOTD on request of %s", client->name);
-				read_motd(conf_files->opermotd_file, &opermotd);
-				RunHook2(HOOKTYPE_REHASHFLAG, client, parv[1]);
-				return;
-			}
-			if (match_simple("-b*motd", parv[1]))
-			{
-				if (MyUser(client))
-					sendto_ops("Rehashing BOTMOTD on request of %s", client->name);
-				else
-					sendto_umode_global(UMODE_OPER, "Remotely rehashing BOTMOTD on request of %s", client->name);
-				read_motd(conf_files->botmotd_file, &botmotd);
-				RunHook2(HOOKTYPE_REHASHFLAG, client, parv[1]);
-				return;
-			}
-			if (!strncasecmp("-motd", parv[1], 5) || !strncasecmp("-rules", parv[1], 6))
-			{
-				if (MyUser(client))
-					sendto_ops("Rehashing all MOTDs and RULES on request of %s", client->name);
-				else
-					sendto_umode_global(UMODE_OPER, "Remotely rehasing all MOTDs and RULES on request of %s", client->name);
-				rehash_motdrules();
-				RunHook2(HOOKTYPE_REHASHFLAG, client, parv[1]);
+				unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", client, "Reloading all TLS related data. [by: $client.details]");
+				reinit_tls();
 				return;
 			}
-			RunHook2(HOOKTYPE_REHASHFLAG, client, parv[1]);
+			RunHook(HOOKTYPE_REHASHFLAG, client, parv[1]);
 			return;
 		}
 	}
 	else
 	{
-		if (loop.ircd_rehashing)
+		if (loop.rehashing)
 		{
-			sendnotice(client, "A rehash is already in progress");
+			sendnotice(client, "ERROR: A rehash is already in progress");
 			return;
 		}
-		sendto_ops("%s is rehashing server config file", client->name);
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
 	}
 
 	/* Normal rehash, rehash motds&rules too, just like the on in the tld block will :p */
 	sendnumeric(client, RPL_REHASHING, configfile);
-	// TODO: fix next line - occurence #2
-	x = rehash(client, (parc > 1) ? ((*parv[1] == 'q') ? 2 : 0) : 0);
-	reread_motdsandrules();
+	request_rehash(client);
 }
 
 /** RESTART command - restart the server (discouraged command)
@@ -696,7 +665,7 @@ CMD_FUNC(cmd_rehash)
  */
 CMD_FUNC(cmd_restart)
 {
-	char *reason = parv[1];
+	const char *reason = parv[1];
 	Client *acptr;
 
 	if (!MyUser(client))
@@ -731,7 +700,6 @@ CMD_FUNC(cmd_restart)
 			reason = parv[2];
 		}
 	}
-	sendto_ops("Server is Restarting by request of %s", client->name);
 
 	list_for_each_entry(acptr, &lclient_list, lclient_node)
 	{
@@ -806,123 +774,12 @@ void short_motd(Client *client)
        sendnumeric(client, RPL_ENDOFMOTD);
 }
 
-/*
- * A merge from ircu and bahamut, and some extra stuff added by codemastr
- * we can now use 1 function for multiple files -- codemastr
- * Merged read_motd/read_rules stuff into this -- Syzop
- */
-
 /** Read motd-like file, used for rules/motd/botmotd/opermotd/etc.
- *  Multiplexes to either directly reading the MOTD or downloading it asynchronously.
  * @param filename Filename of file to read or URL. NULL is accepted and causes the *motd to be free()d.
  * @param motd Reference to motd pointer (used for freeing if needed and for asynchronous remote MOTD support)
  */
 void read_motd(const char *filename, MOTDFile *themotd)
 {
-#ifdef USE_LIBCURL
-	time_t modtime;
-	MOTDDownload *motd_download;
-#endif
-
-	/* TODO: if themotd points to a tld's motd,
-	   could a rehash disrupt this pointer?*/
-#ifdef USE_LIBCURL
-	if(themotd->motd_download)
-	{
-		themotd->motd_download->themotd = NULL;
-		/*
-		 * It is not our job to free() motd_download, the
-		 * read_motd_async_downloaded() function will do that
-		 * when it sees that ->themod == NULL.
-		 */
-		themotd->motd_download = NULL;
-	}
-
-	/* if filename is NULL, do_read_motd will catch it */
-	if(filename && url_is_valid(filename))
-	{
-		/* prepare our payload for read_motd_async_downloaded() */
-		motd_download = safe_alloc(sizeof(MOTDDownload));
-		motd_download->themotd = themotd;
-		themotd->motd_download = motd_download;
-
-		modtime = unreal_getfilemodtime(unreal_mkcache(filename));
-
-		download_file_async(filename, modtime, (vFP)read_motd_async_downloaded, motd_download);
-		return;
-	}
-#endif /* USE_LIBCURL */
-
-	do_read_motd(filename, themotd);
-
-	return;
-}
-
-#ifdef USE_LIBCURL
-/** Callback for download_file_async() called from read_motd() below.
- * @param url the URL curl groked or NULL if the MOTD is stored locally.
- * @param filename the path to the local copy of the MOTD or NULL if either cached=1 or there's an error.
- * @param errorbuf NULL or an errorstring if there was an error while downloading the MOTD.
- * @param cached 0 if the URL was downloaded freshly or 1 if the last download was canceled and the local copy should be used.
- */
-void read_motd_async_downloaded(const char *url, const char *filename, const char *errorbuf, int cached, MOTDDownload *motd_download)
-{
-	MOTDFile *themotd;
-
-	themotd = motd_download->themotd;
-	/*
-	  check if the download was soft-canceled. See struct.h's docs on
-	  struct MOTDDownload for details.
-	*/
-	if(!themotd)
-	{
-		safe_free(motd_download);
-		return;
-	}
-
-	/* errors -- check for specialcached version if applicable */
-	if(!cached && !filename)
-	{
-		if(has_cached_version(url))
-		{
-			config_warn("Error downloading MOTD file from \"%s\": %s -- using cached version instead.", displayurl(url), errorbuf);
-			filename = unreal_mkcache(url);
-		} else {
-			config_error("Error downloading MOTD file from \"%s\": %s", displayurl(url), errorbuf);
-
-			/* remove reference to this chunk of memory about to be freed. */
-			motd_download->themotd->motd_download = NULL;
-			safe_free(motd_download);
-			return;
-		}
-	}
-
-	/*
-	 * We need to move our newly downloaded file to its cache file
-	 * if it isn't there already.
-	 */
-	if(!cached)
-	{
-		/* create specialcached version for later */
-		unreal_copyfileex(filename, unreal_mkcache(url), 1);
-	} else {
-		/*
-		 * The file is cached. Thus we must look for it at the
-		 * cache location where we placed it earlier.
-		 */
-		filename = unreal_mkcache(url);
-	}
-
-	do_read_motd(filename, themotd);
-	safe_free(motd_download);
-}
-#endif /* USE_LIBCURL */
-
-
-/** The actual reading of the MOTD - used by read_motd() and read_motd_async_downloaded()
- */
-void do_read_motd(const char *filename, MOTDFile *themotd)
-{
 	FILE *fd;
 	struct tm *tm_tmp;
 	time_t modtime;
@@ -934,7 +791,7 @@ void do_read_motd(const char *filename, MOTDFile *themotd)
 
 	free_motd(themotd);
 
-	if(!filename)
+	if (!filename)
 		return;
 
 	fd = fopen(filename, "r");
@@ -960,7 +817,7 @@ void do_read_motd(const char *filename, MOTDFile *themotd)
 		temp = safe_alloc(sizeof(MOTDLine));
 		safe_strdup(temp->line, line);
 
-		if(last)
+		if (last)
 			last->next = temp;
 		else
 			/* handle the special case of the first line */
@@ -969,7 +826,7 @@ void do_read_motd(const char *filename, MOTDFile *themotd)
 		last = temp;
 	}
 	/* the file could be zero bytes long? */
-	if(last)
+	if (last)
 		last->next = NULL;
 
 	fclose(fd);
@@ -986,7 +843,7 @@ void free_motd(MOTDFile *themotd)
 {
 	MOTDLine *next, *motdline;
 
-	if(!themotd)
+	if (!themotd)
 		return;
 
 	for (motdline = themotd->lines; motdline; motdline = next)
@@ -998,11 +855,6 @@ void free_motd(MOTDFile *themotd)
 
 	themotd->lines = NULL;
 	memset(&themotd->last_modified, '\0', sizeof(struct tm));
-
-#ifdef USE_LIBCURL
-	/* see struct.h for more information about motd_download */
-	themotd->motd_download = NULL;
-#endif
 }
 
 /** DIE command - terminate the server
@@ -1036,7 +888,8 @@ CMD_FUNC(cmd_die)
 	}
 
 	/* Let the +s know what is going on */
-	sendto_ops("Server Terminating by request of %s", client->name);
+	unreal_log(ULOG_INFO, "main", "UNREALIRCD_STOP", client,
+	           "Terminating server by request of $client.details");
 
 	list_for_each_entry(acptr, &lclient_list, lclient_node)
 	{
@@ -1055,23 +908,29 @@ CMD_FUNC(cmd_die)
 PendingNet *pendingnet = NULL;
 
 /** Add server list (network) from 'client' connection */
-void add_pending_net(Client *client, char *str)
+void add_pending_net(Client *client, const char *str)
 {
 	PendingNet *net;
 	PendingServer *srv;
 	char *p, *name;
+	char buf[512];
 
 	if (BadPtr(str) || !client)
 		return;
 
+	/* Skip any * at the beginning (indicating a reply),
+	 * and work on a copy.
+	 */
+	if (*str == '*')
+		strlcpy(buf, str+1, sizeof(buf));
+	else
+		strlcpy(buf, str, sizeof(buf));
+
 	/* Allocate */
 	net = safe_alloc(sizeof(PendingNet));
 	net->client = client;
 
-	/* Fill in */
-	if (*str == '*')
-		str++;
-	for (name = strtoken(&p, str, ","); name; name = strtoken(&p, NULL, ","))
+	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
 	{
 		if (!*name)
 			continue;
@@ -1108,7 +967,7 @@ void free_pending_net(Client *client)
 }
 
 /** Find SID in any server list (network) that is pending, except 'exempt' */
-PendingNet *find_pending_net_by_sid_butone(char *sid, Client *exempt)
+PendingNet *find_pending_net_by_sid_butone(const char *sid, Client *exempt)
 {
 	PendingNet *net;
 	PendingServer *srv;
@@ -1181,7 +1040,7 @@ Client *find_non_pending_net_duplicates(Client *client)
 }
 
 /** Parse CHANMODES= in PROTOCTL */
-void parse_chanmodes_protoctl(Client *client, char *str)
+void parse_chanmodes_protoctl(Client *client, const char *str)
 {
 	char *modes, *p;
 	char copy[256];
@@ -1191,19 +1050,19 @@ void parse_chanmodes_protoctl(Client *client, char *str)
 	modes = strtoken(&p, copy, ",");
 	if (modes)
 	{
-		safe_strdup(client->serv->features.chanmodes[0], modes);
+		safe_strdup(client->server->features.chanmodes[0], modes);
 		modes = strtoken(&p, NULL, ",");
 		if (modes)
 		{
-			safe_strdup(client->serv->features.chanmodes[1], modes);
+			safe_strdup(client->server->features.chanmodes[1], modes);
 			modes = strtoken(&p, NULL, ",");
 			if (modes)
 			{
-				safe_strdup(client->serv->features.chanmodes[2], modes);
+				safe_strdup(client->server->features.chanmodes[2], modes);
 				modes = strtoken(&p, NULL, ",");
 				if (modes)
 				{
-					safe_strdup(client->serv->features.chanmodes[3], modes);
+					safe_strdup(client->server->features.chanmodes[3], modes);
 				}
 			}
 		}
@@ -1218,9 +1077,9 @@ static int previous_langsinuse_ready = 0;
  */
 void charsys_check_for_changes(void)
 {
-	char *langsinuse = charsys_get_current_languages();
+	const char *langsinuse = charsys_get_current_languages();
 	/* already called by charsys_finish() */
-	safe_strdup(me.serv->features.nickchars, langsinuse);
+	safe_strdup(me.server->features.nickchars, langsinuse);
 
 	if (!previous_langsinuse_ready)
 	{
@@ -1231,10 +1090,10 @@ void charsys_check_for_changes(void)
 
 	if (strcmp(langsinuse, previous_langsinuse))
 	{
-		ircd_log(LOG_ERROR, "Permitted nick characters changed at runtime: %s -> %s",
-			previous_langsinuse, langsinuse);
-		sendto_realops("Permitted nick characters changed at runtime: %s -> %s",
-			previous_langsinuse, langsinuse);
+		unreal_log(ULOG_INFO, "charsys", "NICKCHARS_CHANGED", NULL,
+		           "Permitted nick characters changed at runtime: $old_nickchars -> $new_nickchars",
+		           log_data_string("old_nickchars", previous_langsinuse),
+		           log_data_string("new_nickchars", langsinuse));
 		/* Broadcast change to all (locally connected) servers */
 		sendto_server(NULL, 0, 0, NULL, "PROTOCTL NICKCHARS=%s", langsinuse);
 	}
@@ -1243,25 +1102,21 @@ void charsys_check_for_changes(void)
 }
 
 /** Check if supplied server name is valid, that is: does not contain forbidden characters etc */
-int valid_server_name(char *name)
+int valid_server_name(const char *name)
 {
-	char *p;
+	const char *p;
 
-	if (strlen(name) >= HOSTLEN)
-		return 0;
-
-	for (p = name; *p; p++)
-		if ((*p <= ' ') || (*p > '~'))
-			return 0;
+	if (!valid_host(name, 0))
+		return 0; /* invalid hostname */
 
 	if (!strchr(name, '.'))
-		return 0;
+		return 0; /* no dot */
 
 	return 1;
 }
 
 /** Check if the supplied name is a valid SID, as in: syntax. */
-int valid_sid(char *name)
+int valid_sid(const char *name)
 {
 	if (strlen(name) != 3)
 		return 0;
@@ -1274,6 +1129,31 @@ int valid_sid(char *name)
 	return 1;
 }
 
+/** Check if the supplied name is a valid UID, as in: syntax. */
+int valid_uid(const char *name)
+{
+	const char *p;
+
+	/* Enforce at least some minimum length */
+	if (strlen(name) < 6)
+		return 0;
+
+	/* UID cannot be larger than IDLEN or it would be cut off later */
+	if (strlen(name) > IDLEN)
+		return 0;
+
+	/* Must start with a digit */
+	if (!isdigit(*name))
+		return 0;
+
+	/* For all the remaining characters: digit or uppercase character */
+	for (p = name+1; *p; p++)
+		if (!isdigit(*p) && !isupper(*p))
+			return 0;
+
+	return 1;
+}
+
 /** Initialize the TKL subsystem */
 void tkl_init(void)
 {
@@ -1284,28 +1164,96 @@ void tkl_init(void)
 /** Called when a server link is lost.
  * Used for logging only, API users can use the HOOKTYPE_SERVER_QUIT hook.
  */
-void lost_server_link(Client *serv, FORMAT_STRING(const char *fmt), ...)
+void lost_server_link(Client *client, const char *tls_error_string)
+{
+	if (IsServer(client))
+	{
+		/* An already established link is now lost. */
+		// FIXME: we used to broadcast this GLOBALLY.. not anymore since the U6 rewrite.. is that what we want?
+		if (tls_error_string)
+		{
+			/* TLS */
+			unreal_log(ULOG_ERROR, "link", "LINK_DISCONNECTED", client,
+				   "Lost server link to $client [$client.ip]: $tls_error_string",
+				   log_data_string("tls_error_string", tls_error_string),
+				   client->server->conf ? log_data_link_block(client->server->conf) : NULL);
+		} else {
+			/* NON-TLS */
+			unreal_log(ULOG_ERROR, "link", "LINK_DISCONNECTED", client,
+				   "Lost server link to $client [$client.ip]: $socket_error",
+				   log_data_socket_error(client->local->fd),
+				   client->server->conf ? log_data_link_block(client->server->conf) : NULL);
+		}
+	} else {
+		/* A link attempt failed (it was never a fully connected server) */
+		/* We send these to local ops only */
+		if (tls_error_string)
+		{
+			/* TLS */
+			if (client->server->conf)
+			{
+				unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
+					   "Unable to link with server $client [$link_block.ip:$link_block.port]: $tls_error_string",
+					   log_data_string("tls_error_string", tls_error_string),
+					   log_data_link_block(client->server->conf));
+			} else {
+				unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
+					   "Unable to link with server $client: $tls_error_string",
+					   log_data_string("tls_error_string", tls_error_string));
+			}
+		} else {
+			/* non-TLS */
+			if (client->server->conf)
+			{
+				unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
+					   "Unable to link with server $client [$link_block.ip:$link_block.port]: $socket_error",
+					   log_data_socket_error(client->local->fd),
+					   log_data_link_block(client->server->conf));
+			} else {
+				unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
+					   "Unable to link with server $client: $socket_error",
+					   log_data_socket_error(client->local->fd));
+			}
+		}
+	}
+	SetServerDisconnectLogged(client);
+}
+
+/** Reject an insecure (outgoing) server link that isn't TLS.
+ * This function is void and not int because it can be called from other void functions
+ */
+void reject_insecure_server(Client *client)
 {
-	va_list vl;
-	static char buf[1024], buf2[512];
+	unreal_log(ULOG_ERROR, "link", "SERVER_STARTTLS_FAILED", client,
+	           "Could not link with server $client with TLS enabled. "
+	           "Please check logs on the other side of the link. "
+	           "If you insist with insecure linking then you can set link::options::outgoing::insecure "
+	           "(NOT recommended!).");
+	dead_socket(client, "Rejected server link without TLS");
+}
 
-	va_start(vl, fmt);
-	vsnprintf(buf2, sizeof(buf2), fmt, vl);
-	va_end(vl);
+/** Start server handshake - called after the outgoing connection has been established.
+ * @param client	The remote server
+ */
+void start_server_handshake(Client *client)
+{
+	ConfigItem_link *aconf = client->server ? client->server->conf : NULL;
 
-	if (IsServer(serv))
+	if (!aconf)
 	{
-		/* An already established link is now lost. Broadcast this to all opers. */
-		snprintf(buf, sizeof(buf), "Lost server link to %s: %s",
-			get_client_name(serv, FALSE), buf2);
-		sendto_umode_global(UMODE_OPER, "%s", buf);
-	} else {
-		/* A link attempt failed. Only send this to local opers (can be noisy every xx seconds). */
-		snprintf(buf, sizeof(buf), "Unable to link with server %s: %s",
-			get_client_name(serv, FALSE), buf2);
-		sendto_umode(UMODE_OPER, "%s", buf);
+		/* Should be impossible. */
+		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_HANDSHAKE", client,
+		           "Lost configuration while connecting to $client.details");
+		return;
 	}
 
-	/* Always log! */
-	ircd_log(LOG_ERROR, "%s", buf);
+	RunHook(HOOKTYPE_SERVER_HANDSHAKE_OUT, client);
+
+	sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
+
+	send_protoctl_servers(client, 0);
+	send_proto(client, aconf);
+	/* Sending SERVER message moved to cmd_protoctl, so it's send after the first PROTOCTL
+	 * that we receive from the remote server. -- Syzop
+	 */
 }
diff --git a/src/socket.c b/src/socket.c
@@ -40,10 +40,6 @@ char zlinebuf[BUFSIZE];
 extern char *version;
 MODVAR time_t last_allinuse = 0;
 
-#ifdef USE_LIBCURL
-extern void url_do_transfers_async(void);
-#endif
-
 void start_of_normal_client_handshake(Client *client);
 void proceed_normal_client_handshake(Client *client, struct hostent *he);
 
@@ -85,91 +81,6 @@ void close_connections(void)
 #endif
 }
 
-/** Report an error to the log and also send to all local opers.
- * @param text		Format string for outputting the error.
- *			It must contain only two '%s'. The first
- *			one is replaced by the sockhost from the
- *			client, and the latter will be the error
- *			message from strerror(errno).
- * @param client	The client - ALWAYS locally connected.
- */
-void report_error(char *text, Client *client)
-{
-	int errtmp = ERRNO, origerr = ERRNO;
-	char *host, xbuf[256];
-	int  err, len = sizeof(err), n;
-	
-	host = (client) ? get_client_name(client, FALSE) : "";
-
-	Debug((DEBUG_ERROR, text, host, STRERROR(errtmp)));
-
-	/*
-	 * Get the *real* error from the socket (well try to anyway..).
-	 * This may only work when SO_DEBUG is enabled but its worth the
-	 * gamble anyway.
-	 */
-#ifdef	SO_ERROR
-	if (client && !IsMe(client) && client->local->fd >= 0)
-		if (!getsockopt(client->local->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &len))
-			if (err)
-				errtmp = err;
-#endif
-	if (origerr != errtmp) {
-		/* Socket error is different than original error,
-		 * some tricks are needed because of 2x strerror() (or at least
-		 * according to the man page) -- Syzop.
-		 */
-		snprintf(xbuf, 200, "[syserr='%s'", STRERROR(origerr));
-		n = strlen(xbuf);
-		snprintf(xbuf+n, 256-n, ", sockerr='%s']", STRERROR(errtmp));
-		sendto_snomask(SNO_JUNK, text, host, xbuf);
-		ircd_log(LOG_ERROR, text, host, xbuf);
-	} else {
-		sendto_snomask(SNO_JUNK, text, host, STRERROR(errtmp));
-		ircd_log(LOG_ERROR, text,host,STRERROR(errtmp));
-	}
-	return;
-}
-
-/** Report a BAD error to the log and also send to all local opers.
- * TODO: Document the difference between report_error() and report_baderror()
- * @param text		Format string for outputting the error.
- *			It must contain only two '%s'. The first
- *			one is replaced by the sockhost from the
- *			client, and the latter will be the error
- *			message from strerror(errno).
- * @param client	The client - ALWAYS locally connected.
- */
-void report_baderror(char *text, Client *client)
-{
-#ifndef _WIN32
-	int  errtmp = errno;	/* debug may change 'errno' */
-#else
-	int  errtmp = WSAGetLastError();	/* debug may change 'errno' */
-#endif
-	char *host;
-	int  err, len = sizeof(err);
-
-	host = (client) ? get_client_name(client, FALSE) : "";
-
-	Debug((DEBUG_ERROR, text, host, STRERROR(errtmp)));
-
-	/*
-	 * Get the *real* error from the socket (well try to anyway..).
-	 * This may only work when SO_DEBUG is enabled but its worth the
-	 * gamble anyway.
-	 */
-#ifdef	SO_ERROR
-	if (client && !IsMe(client) && client->local->fd >= 0)
-		if (!getsockopt(client->local->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &len))
-			if (err)
-				errtmp = err;
-#endif
-	sendto_umode(UMODE_OPER, text, host, STRERROR(errtmp));
-	ircd_log(LOG_ERROR, text, host, STRERROR(errtmp));
-	return;
-}
-
 /** Accept an incoming connection.
  * @param listener_fd	The file descriptor of a listen() socket.
  * @param data		The listen { } block configuration data.
@@ -190,8 +101,10 @@ static void listener_accept(int listener_fd, int revents, void *data)
 			 * Of course the underlying cause of this issue should be investigated, as this
 			 * is very much a workaround.
 			 */
-			report_baderror("Cannot accept connections %s:%s", NULL);
-			sendto_realops("[BUG] Restarting listener on %s:%d due to fatal errors (see previous message)", listener->ip, listener->port);
+			unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error",
+				   log_data_socket_error(listener->fd),
+				   log_data_string("listen_ip", listener->ip),
+				   log_data_integer("listen_port", listener->port));
 			close_listener(listener);
 			start_listeners();
 		}
@@ -207,7 +120,9 @@ static void listener_accept(int listener_fd, int revents, void *data)
 		ircstats.is_ref++;
 		if (last_allinuse < TStime() - 15)
 		{
-			sendto_ops_and_log("All connections in use. ([@%s/%u])", listener->ip, listener->port);
+			unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use",
+				   log_data_string("listen_ip", listener->ip),
+				   log_data_integer("listen_port", listener->port));
 			last_allinuse = TStime();
 		}
 
@@ -252,13 +167,20 @@ int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
 	listener->fd = fd_socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket");
 	if (listener->fd < 0)
 	{
-		report_baderror("Cannot open stream socket() %s:%s", NULL);
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
+		           "Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
+			   log_data_socket_error(-1),
+			   log_data_string("listen_ip", ip),
+			   log_data_integer("listen_port", port));
 		return -1;
 	}
 
 	if (++OpenFiles >= maxclients)
 	{
-		sendto_ops_and_log("No more connections allowed (%s)", listener->ip);
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_ERROR_MAXCLIENTS", NULL,
+		           "Could not listen on IP \"$listen_ip\" on port $listen_port: all connections in use",
+		           log_data_string("listen_ip", ip),
+		           log_data_integer("listen_port", port));
 		fd_close(listener->fd);
 		listener->fd = -1;
 		--OpenFiles;
@@ -269,10 +191,11 @@ int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
 
 	if (!unreal_bind(listener->fd, ip, port, ipv6))
 	{
-		char buf[512];
-		ircsnprintf(buf, sizeof(buf), "Error binding stream socket to IP %s port %d", ip, port);
-		strlcat(buf, " - %s:%s", sizeof(buf));
-		report_baderror(buf, NULL);
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
+		           "Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
+		           log_data_socket_error(listener->fd),
+		           log_data_string("listen_ip", ip),
+		           log_data_integer("listen_port", port));
 		fd_close(listener->fd);
 		listener->fd = -1;
 		--OpenFiles;
@@ -281,7 +204,11 @@ int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
 
 	if (listen(listener->fd, LISTEN_SIZE) < 0)
 	{
-		report_error("listen failed for %s:%s", NULL);
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_LISTEN_ERROR", NULL,
+		           "Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
+		           log_data_socket_error(listener->fd),
+		           log_data_string("listen_ip", ip),
+		           log_data_integer("listen_port", port));
 		fd_close(listener->fd);
 		listener->fd = -1;
 		--OpenFiles;
@@ -344,17 +271,17 @@ void close_listener(ConfigItem_listen *listener)
 {
 	if (listener->fd >= 0)
 	{
-		ircd_log(LOG_ERROR, "IRCd no longer listening on %s:%d (%s)%s",
-			listener->ip, listener->port,
-			listener->ipv6 ? "IPv6" : "IPv4",
-			listener->options & LISTENER_TLS ? " (SSL/TLS)" : "");
+		unreal_log(ULOG_INFO, "listen", "LISTEN_REMOVED", NULL,
+			   "UnrealIRCd is now no longer listening on $listen_ip:$listen_port",
+			   log_data_string("listen_ip", listener->ip),
+			   log_data_integer("listen_port", listener->port));
 		fd_close(listener->fd);
 		--OpenFiles;
 	}
 
 	listener->options &= ~LISTENER_BOUND;
 	listener->fd = -1;
-	/* We can already free the SSL/TLS context, since it is only
+	/* We can already free the TLS context, since it is only
 	 * used for new connections, which we no longer accept.
 	 */
 	if (listener->ssl_ctx)
@@ -498,63 +425,6 @@ void close_std_descriptors(void)
 #endif
 }
 
-/** Write PID file */
-void write_pidfile(void)
-{
-#ifdef IRCD_PIDFILE
-	int fd;
-	char buff[20];
-	if ((fd = open(conf_files->pid_file, O_CREAT | O_WRONLY, 0600)) < 0)
-	{
-		ircd_log(LOG_ERROR, "Error writing to pid file %s: %s", conf_files->pid_file, strerror(ERRNO));
-		return;
-	}
-	ircsnprintf(buff, sizeof(buff), "%5d\n", (int)getpid());
-	if (write(fd, buff, strlen(buff)) < 0)
-		ircd_log(LOG_ERROR, "Error writing to pid file %s: %s", conf_files->pid_file, strerror(ERRNO));
-	if (close(fd) < 0)
-		ircd_log(LOG_ERROR, "Error writing to pid file %s: %s", conf_files->pid_file, strerror(ERRNO));
-#endif
-}
-
-/** Reject an insecure (outgoing) server link that isn't SSL/TLS.
- * This function is void and not int because it can be called from other void functions
- */
-void reject_insecure_server(Client *client)
-{
-	sendto_umode(UMODE_OPER, "Could not link with server %s with SSL/TLS enabled. "
-	                         "Please check logs on the other side of the link. "
-	                         "If you insist with insecure linking then you can set link::options::outgoing::insecure "
-	                         "(NOT recommended!).",
-	                         client->name);
-	dead_socket(client, "Rejected link without SSL/TLS");
-}
-
-/** Start server handshake - called after the outgoing connection has been established.
- * @param client	The remote server
- */
-void start_server_handshake(Client *client)
-{
-	ConfigItem_link *aconf = client->serv ? client->serv->conf : NULL;
-
-	if (!aconf)
-	{
-		/* Should be impossible. */
-		sendto_ops_and_log("Lost configuration for %s in start_server_handshake()", get_client_name(client, FALSE));
-		return;
-	}
-
-	RunHook(HOOKTYPE_SERVER_HANDSHAKE_OUT, client);
-
-	sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
-
-	send_protoctl_servers(client, 0);
-	send_proto(client, aconf);
-	/* Sending SERVER message moved to cmd_protoctl, so it's send after the first PROTOCTL
-	 * that we receive from the remote server. -- Syzop
-	 */
-}
-
 /** Do an ident lookup if necessary.
  * @param client	The incoming client
  */
@@ -563,7 +433,7 @@ void consider_ident_lookup(Client *client)
 	char buf[BUFSIZE];
 
 	/* If ident checking is disabled or it's an outgoing connect, then no ident check */
-	if ((IDENT_CHECK == 0) || (client->serv && IsHandshake(client)))
+	if ((IDENT_CHECK == 0) || (client->server && IsHandshake(client)))
 	{
 		ClearIdentLookupSent(client);
 		ClearIdentLookup(client);
@@ -579,11 +449,11 @@ void consider_ident_lookup(Client *client)
 void completed_connection(int fd, int revents, void *data)
 {
 	Client *client = data;
-	ConfigItem_link *aconf = client->serv ? client->serv->conf : NULL;
+	ConfigItem_link *aconf = client->server ? client->server->conf : NULL;
 
 	if (IsHandshake(client))
 	{
-		/* Due to delayed ircd_SSL_connect call */
+		/* Due to delayed unreal_tls_connect call */
 		start_server_handshake(client);
 		fd_setselect(fd, FD_SELECT_READ, read_packet, client);
 		return;
@@ -593,7 +463,8 @@ void completed_connection(int fd, int revents, void *data)
 
 	if (!aconf)
 	{
-		sendto_ops_and_log("Lost configuration for %s", get_client_name(client, FALSE));
+		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_CONNECT", client,
+		           "Lost configuration while connecting to $client.details");
 		return;
 	}
 
@@ -623,40 +494,12 @@ void close_connection(Client *client)
 	if (IsServer(client))
 	{
 		ircstats.is_sv++;
-		ircstats.is_sbs += client->local->sendB;
-		ircstats.is_sbr += client->local->receiveB;
-		ircstats.is_sks += client->local->sendK;
-		ircstats.is_skr += client->local->receiveK;
-		ircstats.is_sti += TStime() - client->local->firsttime;
-		if (ircstats.is_sbs > 1023)
-		{
-			ircstats.is_sks += (ircstats.is_sbs >> 10);
-			ircstats.is_sbs &= 0x3ff;
-		}
-		if (ircstats.is_sbr > 1023)
-		{
-			ircstats.is_skr += (ircstats.is_sbr >> 10);
-			ircstats.is_sbr &= 0x3ff;
-		}
+		ircstats.is_sti += TStime() - client->local->creationtime;
 	}
 	else if (IsUser(client))
 	{
 		ircstats.is_cl++;
-		ircstats.is_cbs += client->local->sendB;
-		ircstats.is_cbr += client->local->receiveB;
-		ircstats.is_cks += client->local->sendK;
-		ircstats.is_ckr += client->local->receiveK;
-		ircstats.is_cti += TStime() - client->local->firsttime;
-		if (ircstats.is_cbs > 1023)
-		{
-			ircstats.is_cks += (ircstats.is_cbs >> 10);
-			ircstats.is_cbs &= 0x3ff;
-		}
-		if (ircstats.is_cbr > 1023)
-		{
-			ircstats.is_ckr += (ircstats.is_cbr >> 10);
-			ircstats.is_cbr &= 0x3ff;
-		}
+		ircstats.is_cti += TStime() - client->local->creationtime;
 	}
 	else
 		ircstats.is_ni++;
@@ -727,13 +570,21 @@ void set_sock_opts(int fd, Client *client, int ipv6)
 #ifdef SO_REUSEADDR
 	opt = 1;
 	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0)
-			report_error("setsockopt(SO_REUSEADDR) %s:%s", client);
+	{
+		unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+		           "Could not setsockopt(SO_REUSEADDR): $socket_error",
+			   log_data_socket_error(-1));
+	}
 #endif
 
 #if defined(SO_USELOOPBACK) && !defined(_WIN32)
 	opt = 1;
 	if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (void *)&opt, sizeof(opt)) < 0)
-		report_error("setsockopt(SO_USELOOPBACK) %s:%s", client);
+	{
+		unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+		           "Could not setsockopt(SO_USELOOPBACK): $socket_error",
+			   log_data_socket_error(-1));
+	}
 #endif
 
 	/* Previously we also called set_socket_buffers() to set some
@@ -747,14 +598,18 @@ void set_sock_opts(int fd, Client *client, int ipv6)
 	{
 		if (client)
 		{
-			report_error("fcntl(fd, F_GETFL) failed for %s:%s", client);
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not get socket options (F_GETFL): $socket_error",
+				   log_data_socket_error(-1));
 		}
 	}
 	else if (fcntl(fd, F_SETFL, opt | O_NONBLOCK) == -1)
 	{
 		if (client)
 		{
-			report_error("fcntl(fd, F_SETL, nonb) failed for %s:%s", client);
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not get socket options (F_SETFL): $socket_error",
+				   log_data_socket_error(-1));
 		}
 	}
 #else
@@ -763,7 +618,9 @@ void set_sock_opts(int fd, Client *client, int ipv6)
 	{
 		if (client)
 		{
-			report_error("ioctlsocket(fd,FIONBIO) failed for %s:%s", client);
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not ioctlsocket FIONBIO: $socket_error",
+				   log_data_socket_error(-1));
 		}
 	}
 #endif
@@ -796,7 +653,7 @@ int is_loopback_ip(char *ip)
  * @param port		Remote port (will be written)
  * @returns The IP address
  */
-char *getpeerip(Client *client, int fd, int *port)
+const char *getpeerip(Client *client, int fd, int *port)
 {
 	static char ret[HOSTLEN+1];
 
@@ -859,7 +716,7 @@ static int check_too_many_unknown_connections(Client *client)
 Client *add_connection(ConfigItem_listen *listener, int fd)
 {
 	Client *client;
-	char *ip;
+	const char *ip;
 	int port = 0;
 	
 	client = make_client(NULL, &me);
@@ -877,7 +734,11 @@ Client *add_connection(ConfigItem_listen *listener, int fd)
 		 */
 		if (ERRNO != P_ENOTCONN)
 		{
-			report_error("Failed to accept new client %s :%s", client);
+			unreal_log(ULOG_ERROR, "listen", "ACCEPT_ERROR", NULL,
+			           "Failed to accept new client: unable to get IP address: $socket_error",
+				   log_data_socket_error(fd),
+				   log_data_string("listen_ip", listener->ip),
+				   log_data_integer("listen_port", listener->port));
 		}
 refuse_client:
 			ircstats.is_ref++;
@@ -932,7 +793,6 @@ refuse_client:
 		if (ctx)
 		{
 			SetTLSAcceptHandshake(client);
-			Debug((DEBUG_DEBUG, "Starting TLS accept handshake for %s", client->local->sockhost));
 			if ((client->local->ssl = SSL_new(ctx)) == NULL)
 			{
 				goto refuse_client;
@@ -940,10 +800,9 @@ refuse_client:
 			SetTLS(client);
 			SSL_set_fd(client->local->ssl, fd);
 			SSL_set_nonblocking(client->local->ssl);
-			SSL_set_ex_data(client->local->ssl, ssl_client_index, client);
-			if (!ircd_SSL_accept(client, fd))
+			SSL_set_ex_data(client->local->ssl, tls_client_index, client);
+			if (!unreal_tls_accept(client, fd))
 			{
-				Debug((DEBUG_DEBUG, "Failed TLS accept handshake in instance 1: %s", client->local->sockhost));
 				SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
 				SSL_smart_shutdown(client->local->ssl);
 				SSL_free(client->local->ssl);
@@ -956,12 +815,10 @@ refuse_client:
 	return client;
 }
 
-static int dns_special_flag = 0; /* This is for an "interesting" race condition  very ugly. */
-
 /** Start of normal client handshake - DNS and ident lookups, etc.
  * @param client	The client
  * @note This is called directly after accept() -> add_connection() for plaintext.
- *       For SSL/TLS connections this is called after the SSL/TLS handshake is completed.
+ *       For TLS connections this is called after the TLS handshake is completed.
  */
 void start_of_normal_client_handshake(Client *client)
 {
@@ -975,9 +832,7 @@ void start_of_normal_client_handshake(Client *client)
 	{
 		if (should_show_connect_info(client))
 			sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_DNS);
-		dns_special_flag = 1;
 		he = unrealdns_doclient(client);
-		dns_special_flag = 0;
 
 		if (client->local->hostp)
 			goto doauth; /* Race condition detected, DNS has been done, continue with auth */
@@ -1040,7 +895,7 @@ void read_packet(int fd, int revents, void *data)
 	fd_setselect(fd, FD_SELECT_READ, read_packet, client);
 	/* Restore handling of writes towards send_queued_cb(), since
 	 * it may be overwritten in an earlier call to read_packet(),
-	 * to handle (SSL) writes by read_packet(), see below under
+	 * to handle (TLS) writes by read_packet(), see below under
 	 * SSL_ERROR_WANT_WRITE.
 	 */
 	fd_setselect(fd, FD_SELECT_WRITE, send_queued_cb, client);
@@ -1090,16 +945,16 @@ void read_packet(int fd, int revents, void *data)
 			if (length < 0 && ((ERRNO == P_EWOULDBLOCK) || (ERRNO == P_EAGAIN) || (ERRNO == P_EINTR)))
 				return;
 
-			if (IsServer(client) || client->serv) /* server or outgoing connection */
-				lost_server_link(client, "Read error or connection closed.");
+			if (IsServer(client) || client->server) /* server or outgoing connection */
+				lost_server_link(client, NULL);
 
-			exit_client(client, NULL, "Read error");
+			exit_client(client, NULL, ERRNO ? "Read error" : "Connection closed");
 			return;
 		}
 
-		client->local->lasttime = now;
-		if (client->local->lasttime > client->local->since)
-			client->local->since = client->local->lasttime;
+		client->local->last_msg_received = now;
+		if (client->local->last_msg_received > client->local->fake_lag)
+			client->local->fake_lag = client->local->last_msg_received;
 		/* FIXME: Is this correct? I have my doubts. */
 		ClearPingSent(client);
 
@@ -1174,191 +1029,28 @@ void process_clients(void)
 	} while(&client->lclient_node != &unknown_list);
 }
 
-/** Returns 4 if 'str' is a valid IPv4 address
- * and 6 if 'str' is a valid IPv6 IP address.
- * Zero (0) is returned in any other case (eg: hostname).
+/** Check if 'ip' is a valid IP address, and if so what type.
+ * @param ip	The IP address
+ * @retval 4	Valid IPv4 address
+ * @retval 6	Valid IPv6 address
+ * @retval 0	Invalid IP address (eg: a hostname)
  */
-int is_valid_ip(char *str)
+int is_valid_ip(const char *ip)
 {
 	char scratch[64];
 	
-	if (inet_pton(AF_INET, str, scratch) == 1)
+	if (BadPtr(ip))
+		return 0;
+
+	if (inet_pton(AF_INET, ip, scratch) == 1)
 		return 4; /* IPv4 */
 	
-	if (inet_pton(AF_INET6, str, scratch) == 1)
+	if (inet_pton(AF_INET6, ip, scratch) == 1)
 		return 6; /* IPv6 */
 	
 	return 0; /* not an IP address */
 }
 
-static int connect_server_helper(ConfigItem_link *, Client *);
-
-/** Start an outgoing connection to a server, for server linking.
- * @param aconf		Configuration attached to this server
- * @param by		The user initiating the connection (can be NULL)
- * @param hp		The address to connect to.
- * @returns <0 on error, 0 on success. Rather confusing.
- */
-int connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp)
-{
-	Client *client;
-
-#ifdef DEBUGMODE
-	sendto_realops("connect_server() called with aconf %p, refcount: %d, TEMP: %s",
-		aconf, aconf->refcount, aconf->flag.temporary ? "YES" : "NO");
-#endif
-
-	if (!aconf->outgoing.hostname)
-		return -1; /* This is an incoming-only link block. Caller shouldn't call us. */
-		
-	if (!hp)
-	{
-		/* Remove "cache" */
-		safe_free(aconf->connect_ip);
-	}
-	/*
-	 * If we dont know the IP# for this host and itis a hostname and
-	 * not a ip# string, then try and find the appropriate host record.
-	 */
-	if (!aconf->connect_ip)
-	{
-		if (is_valid_ip(aconf->outgoing.hostname))
-		{
-			/* link::outgoing::hostname is an IP address. No need to resolve host. */
-			safe_strdup(aconf->connect_ip, aconf->outgoing.hostname);
-		} else
-		{
-			/* It's a hostname, let the resolver look it up. */
-			int ipv4_explicit_bind = 0;
-
-			if (aconf->outgoing.bind_ip && (is_valid_ip(aconf->outgoing.bind_ip) == 4))
-				ipv4_explicit_bind = 1;
-			
-			/* We need this 'aconf->refcount++' or else there's a race condition between
-			 * starting resolving the host and the result of the resolver (we could
-			 * REHASH in that timeframe) leading to an invalid (freed!) 'aconf'.
-			 * -- Syzop, bug #0003689.
-			 */
-			aconf->refcount++;
-			unrealdns_gethostbyname_link(aconf->outgoing.hostname, aconf, ipv4_explicit_bind);
-			return -2;
-		}
-	}
-	client = make_client(NULL, &me);
-	client->local->hostp = hp;
-	/*
-	 * Copy these in so we have something for error detection.
-	 */
-	strlcpy(client->name, aconf->servername, sizeof(client->name));
-	strlcpy(client->local->sockhost, aconf->outgoing.hostname, HOSTLEN + 1);
-
-	if (!connect_server_helper(aconf, client))
-	{
-		int errtmp = ERRNO;
-		report_error("Connect to host %s failed: %s", client);
-		if (by && IsUser(by) && !MyUser(by))
-			sendnotice(by, "*** Connect to host %s failed.", client->name);
-		fd_close(client->local->fd);
-		--OpenFiles;
-		client->local->fd = -2;
-		free_client(client);
-		SET_ERRNO(errtmp);
-		if (ERRNO == P_EINTR)
-			SET_ERRNO(P_ETIMEDOUT);
-		return -1;
-	}
-	/* The socket has been connected or connect is in progress. */
-	make_server(client);
-	client->serv->conf = aconf;
-	client->serv->conf->refcount++;
-#ifdef DEBUGMODE
-	sendto_realops("connect_server() CONTINUED (%s:%d), aconf %p, refcount: %d, TEMP: %s",
-		__FILE__, __LINE__, aconf, aconf->refcount, aconf->flag.temporary ? "YES" : "NO");
-#endif
-	Debug((DEBUG_ERROR, "reference count for %s (%s) is now %d",
-		client->name, client->serv->conf->servername, client->serv->conf->refcount));
-	if (by && IsUser(by))
-		strlcpy(client->serv->by, by->name, sizeof(client->serv->by));
-	else
-		strlcpy(client->serv->by, "AutoConn.", sizeof client->serv->by);
-	client->serv->up = me.name;
-	SetConnecting(client);
-	SetOutgoing(client);
-	irccounts.unknown++;
-	list_add(&client->lclient_node, &unknown_list);
-	set_sockhost(client, aconf->outgoing.hostname);
-	add_client_to_list(client);
-
-	if (aconf->outgoing.options & CONNECT_TLS)
-	{
-		SetTLSConnectHandshake(client);
-		fd_setselect(client->local->fd, FD_SELECT_WRITE, ircd_SSL_client_handshake, client);
-	}
-	else
-		fd_setselect(client->local->fd, FD_SELECT_WRITE, completed_connection, client);
-
-	return 0;
-}
-
-/** Helper function for connect_server() to prepare the actual bind()'ing and connect().
- * @param aconf		Configuration entry of the server.
- * @param client	The client entry that we will use and fill in.
- * @returns 1 on success, 0 on failure.
- */
-static int connect_server_helper(ConfigItem_link *aconf, Client *client)
-{
-	char *bindip;
-	char buf[BUFSIZE];
-
-	if (!aconf->connect_ip)
-		return 0; /* handled upstream or shouldn't happen */
-	
-	if (strchr(aconf->connect_ip, ':'))
-		SetIPV6(client);
-	
-	safe_strdup(client->ip, aconf->connect_ip);
-	
-	snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(client, TRUE));
-	client->local->fd = fd_socket(IsIPV6(client) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf);
-	if (client->local->fd < 0)
-	{
-		if (ERRNO == P_EMFILE)
-		{
-			sendto_realops("opening stream socket to server %s: No more sockets",
-				get_client_name(client, TRUE));
-			return 0;
-		}
-		report_baderror("opening stream socket to server %s:%s", client);
-		return 0;
-	}
-	if (++OpenFiles >= maxclients)
-	{
-		sendto_ops_and_log("No more connections allowed (%s)", client->name);
-		return 0;
-	}
-
-	set_sockhost(client, aconf->outgoing.hostname);
-
-	if (!aconf->outgoing.bind_ip && iConf.link_bindip)
-		bindip = iConf.link_bindip;
-	else
-		bindip = aconf->outgoing.bind_ip;
-
-	if (bindip && strcmp("*", bindip))
-	{
-		if (!unreal_bind(client->local->fd, bindip, 0, IsIPV6(client)))
-		{
-			report_baderror("Error binding to local port for %s:%s -- "
-			                "Your link::outgoing::bind-ip is probably incorrect.", client);
-			return 0;
-		}
-	}
-
-	set_sock_opts(client->local->fd, client, IsIPV6(client));
-
-	return unreal_connect(client->local->fd, client->ip, aconf->outgoing.port, IsIPV6(client));
-}
-
 /** Checks if the system is IPv6 capable.
  * IPv6 is always available at compile time (libs, headers), but the OS may
  * not have IPv6 enabled (or ipv6 kernel module not loaded). So we better check..
@@ -1375,11 +1067,11 @@ int ipv6_capable(void)
 
 /** Attempt to deliver data to a client.
  * This function is only called from send_queued() and will deal
- * with sending to the SSL/TLS or plaintext connection.
+ * with sending to the TLS or plaintext connection.
  * @param cptr The client
  * @param str  The string to send
  * @param len  The length of the string
- * @param want_read In case of SSL/TLS it may happen that SSL_write()
+ * @param want_read In case of TLS it may happen that SSL_write()
  *                  needs to READ data. If this happens then this
  *                  function will set *want_read to 1.
  *                  The upper layer should then call us again when
@@ -1399,16 +1091,10 @@ int deliver_it(Client *client, char *str, int len, int *want_read)
 
 	*want_read = 0;
 
-	if (IsDeadSocket(client) || (!IsServer(client) && !IsUser(client)
-	    && !IsHandshake(client) 
-	    && !IsTLSHandshake(client)
- 
-	    && !IsUnknown(client)))
+	if (IsDeadSocket(client) ||
+	    (!IsServer(client) && !IsUser(client) && !IsHandshake(client) &&
+	     !IsTLSHandshake(client) && !IsUnknown(client)))
 	{
-		str[len] = '\0';
-		sendto_ops
-		    ("* * * DEBUG ERROR * * * !!! Calling deliver_it() for %s, status %d %s, with message: %s",
-		    client->name, client->status, IsDeadSocket(client) ? "DEAD" : "", str);
 		return -1;
 	}
 
@@ -1459,25 +1145,15 @@ int deliver_it(Client *client, char *str, int len, int *want_read)
 
 	if (retval > 0)
 	{
-		client->local->sendB += retval;
-		me.local->sendB += retval;
-		if (client->local->sendB > 1023)
-		{
-			client->local->sendK += (client->local->sendB >> 10);
-			client->local->sendB &= 0x03ff;	/* 2^10 = 1024, 3ff = 1023 */
-		}
-		if (me.local->sendB > 1023)
-		{
-			me.local->sendK += (me.local->sendB >> 10);
-			me.local->sendB &= 0x03ff;
-		}
+		client->local->traffic.bytes_sent += retval;
+		me.local->traffic.bytes_sent += retval;
 	}
 
 	return (retval);
 }
 
 /** Initiate an outgoing connection, the actual connect() call. */
-int unreal_connect(int fd, char *ip, int port, int ipv6)
+int unreal_connect(int fd, const char *ip, int port, int ipv6)
 {
 	int n;
 	
@@ -1513,7 +1189,7 @@ int unreal_connect(int fd, char *ip, int port, int ipv6)
 /** Bind to an IP/port (port may be 0 for auto).
  * @returns 0 on failure, other on success.
  */
-int unreal_bind(int fd, char *ip, int port, int ipv6)
+int unreal_bind(int fd, const char *ip, int port, int ipv6)
 {
 	if (ipv6)
 	{
diff --git a/src/support.c b/src/support.c
@@ -34,7 +34,7 @@ extern void outofmemory();
 #define is_enabled match
 
 /** Convert integer to string */
-char *my_itoa(int i)
+const char *my_itoa(int i)
 {
 	static char buf[128];
 	ircsnprintf(buf, sizeof(buf), "%d", i);
@@ -50,9 +50,7 @@ char *my_itoa(int i)
  * @section Ex1 Example
  * @code
  * for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
- * {
- *      ircd_log(LOG_ERROR, "Got: %s", name);
- * }
+ *      unreal_log(ULOG_INFO, "test", "TEST", "Got: $name", log_data_string(name));
  * @endcode
  */
 char *strtoken(char **save, char *str, char *fs)
@@ -92,7 +90,7 @@ char *strtoken(char **save, char *str, char *fs)
  * @returns IP address as a string (IPv4 or IPv6, in case of the latter:
  *          always the uncompressed form without ::)
  */
-char *inetntop(int af, const void *in, char *out, size_t size)
+const char *inetntop(int af, const void *in, char *out, size_t size)
 {
 	char tmp[MYDUMMY_SIZE];
 
@@ -140,7 +138,6 @@ char *inetntop(int af, const void *in, char *out, size_t size)
 		if (*(op - 1) == ':')
 			*op++ = '0';
 		*op = '\0';
-		Debug((DEBUG_DNS, "Expanding `%s' -> `%s'", tmp, out));
 	}
 	return out;
 }
@@ -180,6 +177,27 @@ size_t strlcpy(char *dst, const char *src, size_t size)
 }
 #endif
 
+#ifndef HAVE_STRLNCPY
+/** BSD'ish strlncpy() - similar to strlcpy but never copies more then n characters.
+ */
+size_t strlncpy(char *dst, const char *src, size_t size, size_t n)
+{
+	size_t len = strlen(src);
+	size_t ret = len;
+
+	if (size <= 0)
+		return 0;
+	if (len > n)
+		len = n;
+	if (len >= size)
+		len = size - 1;
+	memcpy(dst, src, len);
+	dst[len] = 0;
+
+	return ret;
+}
+#endif
+
 #ifndef HAVE_STRLCAT
 /* BSD'ish strlcat().
  * The strlcat() function appends the NUL-terminated string src to the end of
@@ -233,6 +251,16 @@ size_t strlncat(char *dst, const char *src, size_t size, size_t n)
 }
 #endif
 
+/** Like strlcpy but concat one letter */
+void strlcat_letter(char *buf, char c, size_t buflen)
+{
+	int n = strlen(buf);
+	if (!buflen || (n >= buflen-1))
+		return;
+	buf[n] = c;
+	buf[n+1] = '\0';
+}
+
 /** Copies a string and ensure the new buffer is at most 'max' size, including NUL.
  * The syntax is pretty much identical to strlcpy() except that
  * the buffer is newly allocated.
@@ -715,11 +743,11 @@ void outofmemory(size_t bytes)
 
 	if (log_attempt)
 	{
+		/* This will probably fail, but we can try... */
+		unreal_log(ULOG_ERROR, "main", "OUT_OF_MEMORY", NULL,
+		           "Out of memory while trying to allocate $bytes bytes!",
+		           log_data_integer("bytes", bytes));
 		log_attempt = 0;
-		if (bytes)
-			ircd_log(LOG_ERROR, "Out of memory while trying to allocate %lld bytes!", (long long)bytes);
-		else
-			ircd_log(LOG_ERROR, "Out of memory");
 	}
 	exit(7);
 }
@@ -778,9 +806,9 @@ char *unreal_mktemp(const char *dir, const char *suffix)
 /** Returns the path portion of the given path/file
  * in the specified location (must be at least PATH_MAX bytes).
  */
-char *unreal_getpathname(char *filepath, char *path)
+char *unreal_getpathname(const char *filepath, char *path)
 {
-	char *end = filepath+strlen(filepath);
+	const char *end = filepath+strlen(filepath);
 
 	while (*end != '\\' && *end != '/' && end > filepath)
 		end--;
@@ -803,31 +831,35 @@ char *unreal_getpathname(char *filepath, char *path)
 /** Returns the filename portion of the given path.
  * The original string is not modified
  */
-char *unreal_getfilename(char *path)
+const char *unreal_getfilename(const char *path)
 {
-        int len = strlen(path);
-        char *end;
-        if (!len)
-                return NULL;
-        end = path+len-1;
+	int len = strlen(path);
+	const char *end;
+
+	if (!len)
+		return NULL;
+
+	end = path+len-1;
 	if (*end == '\\' || *end == '/')
 		return NULL;
-        while (end > path)
-        {
-                if (*end == '\\' || *end == '/')
-                {
-                        end++;
-                        break;
-                }
-                end--;
-        }
-        return end;
+
+	while (end > path)
+	{
+		if (*end == '\\' || *end == '/')
+		{
+			end++;
+			break;
+		}
+		end--;
+	}
+
+	return end;
 }
 
 /** Returns the special module tmp name for a given path.
  * The original string is not modified.
  */
-char *unreal_getmodfilename(char *path)
+const char *unreal_getmodfilename(const char *path)
 {
 	static char ret[512];
 	char buf[512];
@@ -874,12 +906,12 @@ char *unreal_getmodfilename(char *path)
 /* Returns a consistent filename for the cache/ directory.
  * Returned value will be like: cache/<hash of url>
  */
-char *unreal_mkcache(const char *url)
+const char *unreal_mkcache(const char *url)
 {
 	static char tempbuf[PATH_MAX+1];
-	char tmp2[33];
+	char tmp2[128];
 	
-	snprintf(tempbuf, PATH_MAX, "%s/%s", CACHEDIR, md5hash(tmp2, url, strlen(url)));
+	snprintf(tempbuf, PATH_MAX, "%s/%s", CACHEDIR, sha256hash(tmp2, url, strlen(url)));
 	return tempbuf;
 }
 
@@ -892,9 +924,9 @@ int has_cached_version(const char *url)
 /** Used to blow away result of bad copy or cancel file copy */
 void cancel_copy(int srcfd, int destfd, const char *dest)
 {
-        close(srcfd);
-        close(destfd);
-        unlink(dest);
+	close(srcfd);
+	close(destfd);
+	unlink(dest);
 }
 
 /** Copys the contents of the src file to the dest file.
@@ -1034,7 +1066,7 @@ time_t unreal_getfilemodtime(const char *filename)
 #endif
 
 /** Encode an IP string (eg: "1.2.3.4") to a BASE64 encoded value for S2S traffic */
-char *encode_ip(char *ip)
+const char *encode_ip(const char *ip)
 {
 	static char retbuf[25]; /* returned string */
 	char addrbuf[16];
@@ -1067,7 +1099,7 @@ char *encode_ip(char *ip)
 }
 
 /** Decode a BASE64 encoded string to an IP address string. Used for S2S traffic. */
-char *decode_ip(char *buf)
+const char *decode_ip(const char *buf)
 {
 	int n;
 	char targ[25];
@@ -1161,7 +1193,7 @@ struct u_WSA_errors WSAErrors[] = {
 };
 
 /** Get socket error string */
-char *sock_strerror(int error)
+const char *sock_strerror(int error)
 {
 	static char unkerr[64];
 	int start = 0;
@@ -1277,7 +1309,7 @@ literal:
 }
 
 /** Return the PCRE2 library version in use */
-char *pcre2_version(void)
+const char *pcre2_version(void)
 {
 	static char buf[256];
 
@@ -1322,8 +1354,13 @@ int get_terminal_width(void)
 #endif
 }
 
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
 /** Like strftime() but easier. */
-char *unreal_strftime(char *str)
+char *unreal_strftime(const char *str)
 {
 	time_t t;
 	struct tm *tmp;
@@ -1332,12 +1369,20 @@ char *unreal_strftime(char *str)
 	t = time(NULL);
 	tmp = localtime(&t);
 	if (!tmp || !strftime(buf, sizeof(buf), str, tmp))
-		return str;
+	{
+		/* If anything fails bigtime, then return the format string */
+		strlcpy(buf, str, sizeof(buf));
+		return buf;
+	}
 	return buf;
 }
 
-/** Convert a string to lowercase */
-void strtolower_safe(char *dst, char *src, int size)
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
+/** Convert a string to lowercase - with separate input/output buffer */
+void strtolower_safe(char *dst, const char *src, int size)
 {
 	if (!size)
 		return; /* size of 0 is unworkable */
@@ -1350,3 +1395,32 @@ void strtolower_safe(char *dst, char *src, int size)
 	}
 	*dst = '\0';
 }
+
+/** Convert a string to lowercase - modifying existing string */
+void strtolower(char *str)
+{
+	for (; *str; str++)
+		*str = tolower(*str);
+}
+
+/** Convert a string to uppercase - with separate input/output buffer */
+void strtoupper_safe(char *dst, const char *src, int size)
+{
+	if (!size)
+		return; /* size of 0 is unworkable */
+	size--; /* for \0 */
+
+	for (; *src && size; src++)
+	{
+		*dst++ = toupper(*src);
+		size--;
+	}
+	*dst = '\0';
+}
+
+/** Convert a string to uppercase - modifying existing string */
+void strtoupper(char *str)
+{
+	for (; *str; str++)
+		*str = toupper(*str);
+}
diff --git a/src/tls.c b/src/tls.c
@@ -19,7 +19,7 @@
  */
 
 /** @file
- * @brief SSL/TLS functions
+ * @brief TLS functions
  */
 
 #include "unrealircd.h"
@@ -31,33 +31,32 @@ extern HINSTANCE hInst;
 extern HWND hwIRCDWnd;
 #endif
 
-#define SAFE_SSL_READ 1
-#define SAFE_SSL_WRITE 2
-#define SAFE_SSL_ACCEPT 3
-#define SAFE_SSL_CONNECT 4
+#define FUNC_TLS_READ 1
+#define FUNC_TLS_WRITE 2
+#define FUNC_TLS_ACCEPT 3
+#define FUNC_TLS_CONNECT 4
 
 /* Forward declarations */
-static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *client);
+static int fatal_tls_error(int ssl_error, int where, int my_errno, Client *client);
 int cipher_check(SSL_CTX *ctx, char **errstr);
 int certificate_quality_check(SSL_CTX *ctx, char **errstr);
 
-/* The SSL structures */
+/* The TLS structures */
 SSL_CTX *ctx_server;
 SSL_CTX *ctx_client;
 
-char *SSLKeyPasswd;
+char *TLSKeyPasswd;
 
 typedef struct {
 	int *size;
 	char **buffer;
 } StreamIO;
 
-MODVAR int ssl_client_index = 0;
+MODVAR int tls_client_index = 0;
 
-#define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); }
 #ifdef _WIN32
-/** Ask SSL private key password (Windows GUI mode only) */
-LRESULT SSLPassDLG(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
+/** Ask private key password (Windows GUI mode only) */
+LRESULT TLS_key_passwd_dialog(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
 {
 	static StreamIO *stream;
 	switch (Message) {
@@ -88,7 +87,7 @@ LRESULT SSLPassDLG(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
  * @param my_errno	The value of errno to use in case we want to call strerror().
  * @returns Error string, only valid until next call to this function.
  */
-char *ssl_error_str(int err, int my_errno)
+const char *ssl_error_str(int err, int my_errno)
 {
 	static char ssl_errbuf[256];
 	char *ssl_errstr = NULL;
@@ -96,7 +95,7 @@ char *ssl_error_str(int err, int my_errno)
 	switch(err)
 	{
 		case SSL_ERROR_NONE:
-			ssl_errstr = "SSL: No error";
+			ssl_errstr = "OpenSSL: No error";
 			break;
 		case SSL_ERROR_SSL:
 			ssl_errstr = "Internal OpenSSL error or protocol error";
@@ -126,27 +125,8 @@ char *ssl_error_str(int err, int my_errno)
 	return ssl_errstr;
 }
 
-/** Write official OpenSSL error string to ircd log / sendto_realops, using config_status.
- * Note that you are expected to announce earlier that you actually encountered an SSL error.
- * Also note that multiple error strings may be written out (with a slight chance of including
- * irrelevent ones[?]).
- */
-void config_report_ssl_error()
-{
-unsigned long e;
-char buf[512];
-
-	do {
-		e = ERR_get_error();
-		if (e == 0)
-			break; /* no (more) errors */
-		ERR_error_string_n(e, buf, sizeof(buf));
-		config_status(" %s", buf);
-	} while(e);
-}
-
-/** Ask SSL private key password (rare) */
-int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *password)
+/** Ask certificate private key password (rare) */
+int TLS_key_passwd_cb(char *buf, int size, int rwflag, void *password)
 {
 	char *pass;
 	static int before = 0;
@@ -162,19 +142,19 @@ int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *password)
 		return strlen(buf);
 	}
 #ifndef _WIN32
-	pass = getpass("Password for SSL private key: ");
+	pass = getpass("Password for TLS private key: ");
 #else
 	pass = passbuf;
 	stream.buffer = &pass;
 	stream.size = &passsize;
-	DialogBoxParam(hInst, "SSLPass", hwIRCDWnd, (DLGPROC)SSLPassDLG, (LPARAM)&stream); 
+	DialogBoxParam(hInst, "TLSKey", hwIRCDWnd, (DLGPROC)TLS_key_passwd_dialog, (LPARAM)&stream); 
 #endif
 	if (pass)
 	{
 		strlcpy(buf, pass, size);
 		strlcpy(beforebuf, pass, sizeof(beforebuf));
 		before = 1;
-		SSLKeyPasswd = beforebuf;
+		TLSKeyPasswd = beforebuf;
 		return (strlen(buf));
 	}
 	return 0;
@@ -192,7 +172,7 @@ static int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
 /** Get Client pointer by SSL pointer */
 Client *get_client_by_ssl(SSL *ssl)
 {
-	return SSL_get_ex_data(ssl, ssl_client_index);
+	return SSL_get_ex_data(ssl, tls_client_index);
 }
 
 /** Set requested server name as indicated by SNI */
@@ -218,20 +198,7 @@ static int ssl_hostname_callback(SSL *ssl, int *unk, void *arg)
 	return SSL_TLSEXT_ERR_OK;
 }
 
-/** Special logging function for SSL/TLS (? make more generic?) */
-static void mylog(char *fmt, ...)
-{
-	va_list vl;
-	static char buf[2048];
-
-	va_start(vl, fmt);
-	ircvsnprintf(buf, sizeof(buf), fmt, vl);
-	va_end(vl);
-	sendto_realops("[SSL rehash] %s", buf);
-	ircd_log(LOG_ERROR, "%s", buf);
-}
-
-/** Disable SSL/TLS protocols as set by config */
+/** Disable TLS protocols as set by config */
 void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
 {
 	/* OpenSSL has three mechanisms for protocol version control... */
@@ -255,7 +222,7 @@ void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
 	/* The remaining two mechanisms are:
 	 * The old way, which is most flexible, is to use:
 	 * SSL_CTX_set_options(... SSL_OP_NO_<version>) which allows
-	 * you to disable each and every specific SSL/TLS version.
+	 * you to disable each and every specific TLS version.
 	 *
 	 * And the new way, which only allows setting a
 	 * minimum and maximum protocol version, using:
@@ -305,10 +272,10 @@ void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
 #endif
 }
 
-/** Initialize SSL/TLS context
+/** Initialize TLS context
  * @param tlsoptions	The ::tls-options configuration
  * @param server	Set to 1 if we are initializing a server, 0 for client.
- * @returns The SSL/TLS context (SSL_CTX) or NULL in case of error.
+ * @returns The TLS context (SSL_CTX) or NULL in case of error.
  */
 SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
 {
@@ -322,19 +289,20 @@ SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
 
 	if (!ctx)
 	{
-		config_error("Failed to do SSL CTX new");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_LOAD_FAILED", NULL,
+		           "Failed to do SSL_CTX_new() !?\n$tls_error.all",
+		           log_data_tls_error());
 		return NULL;
 	}
 	disable_ssl_protocols(ctx, tlsoptions);
-	SSL_CTX_set_default_passwd_cb(ctx, ssl_pem_passwd_cb);
+	SSL_CTX_set_default_passwd_cb(ctx, TLS_key_passwd_cb);
 
 	if (server && !(tlsoptions->options & TLSFLAG_DISABLECLIENTCERT))
 	{
 		/* We tell OpenSSL/LibreSSL to verify the certificate and set our callback.
 		 * Our callback will always accept the certificate since actual checking
 		 * will take place elsewhere. Why? Because certificate is (often) delayed
-		 * until after the SSL handshake. Such as in the case of link blocks where
+		 * until after the TLS handshake. Such as in the case of link blocks where
 		 * _verify_link() will take care of it only after we learned what server
 		 * we are dealing with (and if we should verify certificates for that server).
 		 */
@@ -346,71 +314,70 @@ SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
 #endif
 	SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
 
-	if (!tlsoptions->certificate_file)
-	{
-		config_error("No SSL certificate configured (set::options::ssl::certificate or in a listen block)");
-		config_report_ssl_error();
-		goto fail;
-	}
-
 	if (SSL_CTX_use_certificate_chain_file(ctx, tlsoptions->certificate_file) <= 0)
 	{
-		config_error("Failed to load SSL certificate %s", tlsoptions->certificate_file);
-		config_report_ssl_error();
-		goto fail;
-	}
-
-	if (!tlsoptions->key_file)
-	{
-		config_error("No SSL key configured (set::options::ssl::key or in a listen block)");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_LOAD_FAILED", NULL,
+		           "Failed to load TLS certificate $filename\n$tls_error.all",
+		           log_data_string("filename", tlsoptions->certificate_file),
+		           log_data_tls_error());
 		goto fail;
 	}
 
 	if (SSL_CTX_use_PrivateKey_file(ctx, tlsoptions->key_file, SSL_FILETYPE_PEM) <= 0)
 	{
-		config_error("Failed to load SSL private key %s", tlsoptions->key_file);
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_LOAD_FAILED", NULL,
+		           "Failed to load TLS private key $filename\n$tls_error.all",
+		           log_data_string("filename", tlsoptions->key_file),
+		           log_data_tls_error());
 		goto fail;
 	}
 
 	if (!SSL_CTX_check_private_key(ctx))
 	{
-		config_error("Failed to check SSL private key");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_LOAD_FAILED", NULL,
+		           "Check for TLS private key failed $filename\n$tls_error.all",
+		           log_data_string("filename", tlsoptions->key_file),
+		           log_data_tls_error());
 		goto fail;
 	}
 
 	if (SSL_CTX_set_cipher_list(ctx, tlsoptions->ciphers) == 0)
 	{
-		config_error("Failed to set SSL cipher list");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_INVALID_CIPHERS_LIST", NULL,
+		           "Failed to set TLS cipher list '$tls_ciphers_list'\n$tls_error.all",
+		           log_data_string("tls_ciphers_list", tlsoptions->ciphers),
+		           log_data_tls_error());
 		goto fail;
 	}
 
 #ifdef SSL_OP_NO_TLSv1_3
 	if (SSL_CTX_set_ciphersuites(ctx, tlsoptions->ciphersuites) == 0)
 	{
-		config_error("Failed to set SSL ciphersuites list");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_INVALID_CIPHERSUITES_LIST", NULL,
+		           "Failed to set TLS ciphersuites list '$tls_ciphers_list'\n$tls_error.all",
+		           log_data_string("tls_ciphersuites_list", tlsoptions->ciphersuites),
+		           log_data_tls_error());
 		goto fail;
 	}
 #endif
 
 	if (!cipher_check(ctx, &errstr))
 	{
-		config_error("There is a problem with your SSL/TLS 'ciphers' configuration setting: %s", errstr);
-		config_error("Remove the ciphers setting from your configuration file to use safer defaults, or change the cipher setting.");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_CIPHER_CHECK_FAILED", NULL,
+		           "There is a problem with your TLS 'ciphers' configuration setting: $quality_check_error\n"
+		           "Remove the ciphers setting from your configuration file to use safer defaults, or change the cipher setting.",
+		           log_data_string("quality_check_error", errstr));
 		goto fail;
 	}
 
 	if (!certificate_quality_check(ctx, &errstr))
 	{
-		config_error("There is a problem with your SSL/TLS certificate: %s. Please use another certificate/keypair.", errstr);
-		config_error("If you use the standard UnrealIRCd certificates then you can simply run 'make pem' and 'make install' "
-		             "from your UnrealIRCd source directory (eg: ~/unrealircd-5.X.Y/) to create and install new certificates");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_CERTIFICATE_CHECK_FAILED", NULL,
+		           "There is a problem with your TLS certificate '$filename': $quality_check_error\n"
+		           "If you use the standard UnrealIRCd certificates then you can simply run 'make pem' and 'make install' "
+		           "from your UnrealIRCd source directory (eg: ~/unrealircd-6.X.Y/) to create and install new certificates",
+		           log_data_string("filename", tlsoptions->certificate_file),
+		           log_data_string("quality_check_error", errstr));
 		goto fail;
 	}
 
@@ -421,8 +388,10 @@ SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
 	{
 		if (!SSL_CTX_load_verify_locations(ctx, tlsoptions->trusted_ca_file, NULL))
 		{
-			config_error("Failed to load Trusted CA's from %s", tlsoptions->trusted_ca_file);
-			config_report_ssl_error();
+			unreal_log(ULOG_ERROR, "config", "TLS_LOAD_FAILED", NULL,
+				   "Failed to load trusted-ca-file $filename\n$tls_error.all",
+				   log_data_string("filename", tlsoptions->trusted_ca_file),
+				   log_data_tls_error());
 			goto fail;
 		}
 	}
@@ -450,22 +419,22 @@ SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
 #ifdef HAS_SSL_CTX_SET1_CURVES_LIST
 			if (!SSL_CTX_set1_curves_list(ctx, tlsoptions->ecdh_curves))
 			{
-				config_error("Failed to apply ecdh-curves '%s'. "
-				             "To get a list of supported curves with the "
-				             "appropriate names, run "
-				             "'openssl ecparam -list_curves' on the server. "
-				             "Separate multiple curves by colon, "
-				             "for example: ecdh-curves \"secp521r1:secp384r1\".",
-				             tlsoptions->ecdh_curves);
-				config_report_ssl_error();
+				unreal_log(ULOG_ERROR, "config", "TLS_INVALID_ECDH_CURVES_LIST", NULL,
+					   "Failed to set ecdh-curves '$ecdh_curves_list'\n$tls_error.all\n"
+					   "HINT: o get a list of supported curves with the appropriate names, "
+					   "run 'openssl ecparam -list_curves' on the server. "
+					   "Separate multiple curves by colon, for example: "
+					   "ecdh-curves \"secp521r1:secp384r1\".",
+					   log_data_string("ecdh_curves_list", tlsoptions->ecdh_curves),
+					   log_data_tls_error());
 				goto fail;
 			}
 #else
 			/* We try to avoid this in the config code, but better have
 			 * it here too than be sorry if someone screws up:
 			 */
-			config_error("ecdh-curves specified but not supported by library -- BAD!");
-			config_report_ssl_error();
+			unreal_log(ULOG_ERROR, "config", "BUG_ECDH_CURVES", NULL,
+			           "ecdh-curves specified but not supported by library -- BAD!");
 			goto fail;
 #endif
 		}
@@ -487,23 +456,51 @@ fail:
 	return NULL;
 }
 
-/** Early initalization of SSL/TLS subsystem - called on startup */
-int early_init_ssl(void)
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+MODVAR EVP_MD *sha256_function; /**< SHA256 function for EVP_DigestInit_ex() call */
+MODVAR EVP_MD *sha1_function; /**< SHA1 function for EVP_DigestInit_ex() call */
+MODVAR EVP_MD *md5_function; /**< MD5 function for EVP_DigestInit_ex() call */
+#endif
+
+/** Early initalization of TLS subsystem - called on startup */
+int early_init_tls(void)
 {
 	SSL_load_error_strings();
 	SSLeay_add_ssl_algorithms();
 
 	/* This is used to track (SSL *) <--> (Client *) relationships: */
-	ssl_client_index = SSL_get_ex_new_index(0, "ssl_client", NULL, NULL, NULL);
+	tls_client_index = SSL_get_ex_new_index(0, "tls_client", NULL, NULL, NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	sha256_function = EVP_MD_fetch(NULL, "SHA2-256", NULL);
+	if (!sha256_function)
+	{
+		fprintf(stderr, "Could not find SHA256 algorithm in TLS library\n");
+		exit(6);
+	}
+
+	sha1_function = EVP_MD_fetch(NULL, "SHA1", NULL);
+	if (!sha1_function)
+	{
+		fprintf(stderr, "Could not find SHA1 algorithm in TLS library\n");
+		exit(6);
+	}
+
+	md5_function = EVP_MD_fetch(NULL, "MD5", NULL);
+	if (!md5_function)
+	{
+		fprintf(stderr, "Could not find MD5 algorithm in TLS library\n");
+		exit(6);
+	}
+#endif
 	return 1;
 }
 
 /** Initialize the server and client contexts.
  * This is only possible after reading the configuration file.
  */
-int init_ssl(void)
+int init_tls(void)
 {
-	/* SSL preliminaries. We keep the certificate and key with the context. */
 	ctx_server = init_ctx(iConf.tls_options, 1);
 	if (!ctx_server)
 		return 0;
@@ -513,29 +510,20 @@ int init_ssl(void)
 	return 1;
 }
 
-/** Reinitialize SSL/TLS server and client contexts - after REHASH -tls
+/** Reinitialize TLS server and client contexts - after REHASH -tls
  */
-void reinit_ssl(Client *client)
+void reinit_tls(void)
 {
 	SSL_CTX *tmp;
 	ConfigItem_listen *listen;
 	ConfigItem_sni *sni;
 	ConfigItem_link *link;
 
-	if (!client)
-		mylog("Reloading all SSL related data (./unrealircd reloadtls)");
-	else if (IsUser(client))
-		mylog("%s (%s@%s) requested a reload of all SSL related data (/rehash -tls)",
-			client->name, client->user->username, client->user->realhost);
-	else
-		mylog("%s requested a reload of all SSL related data (/rehash -tls)",
-			client->name);
-
 	tmp = init_ctx(iConf.tls_options, 1);
 	if (!tmp)
 	{
-		config_error("SSL Reload failed.");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+		           "TLS Reload failed. See previous errors.");
 		return;
 	}
 	if (ctx_server)
@@ -545,8 +533,8 @@ void reinit_ssl(Client *client)
 	tmp = init_ctx(iConf.tls_options, 0);
 	if (!tmp)
 	{
-		config_error("SSL Reload partially failed. Server context is reloaded, client context failed");
-		config_report_ssl_error();
+		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+		           "TLS Reload failed at client context. See previous errors.");
 		return;
 	}
 	if (ctx_client)
@@ -561,8 +549,8 @@ void reinit_ssl(Client *client)
 			tmp = init_ctx(listen->tls_options, 1);
 			if (!tmp)
 			{
-				config_error("SSL Reload partially failed. listen::tls-options error, see above");
-				config_report_ssl_error();
+				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+					   "TLS Reload failed at listen::tls-options. See previous errors.");
 				return;
 			}
 			if (listen->ssl_ctx)
@@ -579,8 +567,8 @@ void reinit_ssl(Client *client)
 			tmp = init_ctx(sni->tls_options, 1);
 			if (!tmp)
 			{
-				config_error("SSL Reload partially failed. sni::tls-options error, see above");
-				config_report_ssl_error();
+				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+					   "TLS Reload failed at sni::tls-options. See previous errors.");
 				return;
 			}
 			if (sni->ssl_ctx)
@@ -597,9 +585,9 @@ void reinit_ssl(Client *client)
 			tmp = init_ctx(link->tls_options, 0);
 			if (!tmp)
 			{
-				config_error("SSL Reload partially failed. link::outgoing::tls-options error in link %s { }, see above",
-					link->servername);
-				config_report_ssl_error();
+				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+					   "TLS Reload failed at link $servername due to outgoing::tls-options. See previous errors.",
+					   log_data_string("servername", link->servername));
 				return;
 			}
 			if (link->ssl_ctx)
@@ -616,15 +604,23 @@ void SSL_set_nonblocking(SSL *s)
 	BIO_set_nbio(SSL_get_wbio(s),1);
 }
 
-/** Get SSL/TLS ciphersuite */
-char *tls_get_cipher(SSL *ssl)
+/** Get TLS ciphersuite */
+const char *tls_get_cipher(Client *client)
 {
 	static char buf[256];
-	
+	const char *cached;
+
+	cached = moddata_client_get(client, "tls_cipher");
+	if (cached)
+		return cached;
+
+	if (!MyConnect(client) || !client->local->ssl)
+		return NULL;
+
 	buf[0] = '\0';
-	strlcpy(buf, SSL_get_version(ssl), sizeof(buf));
+	strlcpy(buf, SSL_get_version(client->local->ssl), sizeof(buf));
 	strlcat(buf, "-", sizeof(buf));
-	strlcat(buf, SSL_get_cipher(ssl), sizeof(buf));
+	strlcat(buf, SSL_get_cipher(client->local->ssl), sizeof(buf));
 
 	return buf;
 }
@@ -636,30 +632,34 @@ TLSOptions *get_tls_options_for_client(Client *client)
 {
 	if (!client->local)
 		return NULL;
-	if (client->serv && client->serv->conf && client->serv->conf->tls_options)
-		return client->serv->conf->tls_options;
+	if (client->server && client->server->conf && client->server->conf->tls_options)
+		return client->server->conf->tls_options;
 	if (client->local && client->local->listener && client->local->listener->tls_options)
 		return client->local->listener->tls_options;
 	return iConf.tls_options;
 }
 
-/** Outgoing SSL connect (read: handshake) to another server. */
-void ircd_SSL_client_handshake(int fd, int revents, void *data)
+/** Outgoing TLS connect (read: handshake) to another server. */
+void unreal_tls_client_handshake(int fd, int revents, void *data)
 {
 	Client *client = data;
-	SSL_CTX *ctx = (client->serv && client->serv->conf && client->serv->conf->ssl_ctx) ? client->serv->conf->ssl_ctx : ctx_client;
+	SSL_CTX *ctx = (client->server && client->server->conf && client->server->conf->ssl_ctx) ? client->server->conf->ssl_ctx : ctx_client;
 	TLSOptions *tlsoptions = get_tls_options_for_client(client);
 
 	if (!ctx)
 	{
-		sendto_realops("Could not start SSL client handshake: SSL was not loaded correctly on this server (failed to load cert or key)");
+		unreal_log(ULOG_ERROR, "config", "TLS_CREATE_SESSION_FAILED", NULL,
+		           "Could not start TLS client handshake (no ctx?): TLS was possibly not loaded correctly on this server!?\n$tls_error.all",
+		           log_data_tls_error());
 		return;
 	}
 
 	client->local->ssl = SSL_new(ctx);
 	if (!client->local->ssl)
 	{
-		sendto_realops("Failed to SSL_new(ctx)");
+		unreal_log(ULOG_ERROR, "config", "TLS_CREATE_SESSION_FAILED", NULL,
+		           "Could not start TLS client handshake: TLS was possibly not loaded correctly on this server!?\n$tls_error.all",
+		           log_data_tls_error());
 		return;
 	}
 
@@ -679,15 +679,15 @@ void ircd_SSL_client_handshake(int fd, int revents, void *data)
 		BIO_set_ssl_renegotiate_timeout(SSL_get_wbio(client->local->ssl), tlsoptions->renegotiate_timeout);
 	}
 
-	if (client->serv && client->serv->conf)
+	if (client->server && client->server->conf)
 	{
 		/* Client: set hostname for SNI */
-		SSL_set_tlsext_host_name(client->local->ssl, client->serv->conf->servername);
+		SSL_set_tlsext_host_name(client->local->ssl, client->server->conf->servername);
 	}
 
 	SetTLS(client);
 
-	switch (ircd_SSL_connect(client, fd))
+	switch (unreal_tls_connect(client, fd))
 	{
 		case -1:
 			fd_close(fd);
@@ -695,11 +695,9 @@ void ircd_SSL_client_handshake(int fd, int revents, void *data)
 			--OpenFiles;
 			return;
 		case 0: 
-			Debug((DEBUG_DEBUG, "SetTLSConnectHandshake(%s)", get_client_name(client, TRUE)));
 			SetTLSConnectHandshake(client);
 			return;
 		case 1:
-			Debug((DEBUG_DEBUG, "SSL_init_finished should finish this job (%s)", get_client_name(client, TRUE)));
 			return;
 		default:
 			return;
@@ -707,15 +705,15 @@ void ircd_SSL_client_handshake(int fd, int revents, void *data)
 
 }
 
-/** Called by I/O engine to (re)try accepting an SSL/TLS connection */
-static void ircd_SSL_accept_retry(int fd, int revents, void *data)
+/** Called by I/O engine to (re)try accepting an TLS connection */
+static void unreal_tls_accept_retry(int fd, int revents, void *data)
 {
 	Client *client = data;
-	ircd_SSL_accept(client, fd);
+	unreal_tls_accept(client, fd);
 }
 
-/** Accept an SSL/TLS connection - that is: do the TLS handshake */
-int ircd_SSL_accept(Client *client, int fd)
+/** Accept an TLS connection - that is: do the TLS handshake */
+int unreal_tls_accept(Client *client, int fd)
 {
 	int ssl_err;
 
@@ -730,26 +728,26 @@ int ircd_SSL_accept(Client *client, int fd)
 		{
 			char buf[512];
 			snprintf(buf, sizeof(buf),
-				"ERROR :STARTTLS received but this is an SSL-only port. Check your connect settings. "
-				"If this is a server linking in then add 'ssl' in your link::outgoing::options block.\r\n");
+				"ERROR :STARTTLS received but this is a TLS-only port. Check your connect settings. "
+				"If this is a server linking in then add 'tls' in your link::outgoing::options block.\r\n");
 			(void)send(fd, buf, strlen(buf), 0);
-			return fatal_ssl_error(SSL_ERROR_SSL, SAFE_SSL_ACCEPT, ERRNO, client);
+			return fatal_tls_error(SSL_ERROR_SSL, FUNC_TLS_ACCEPT, ERRNO, client);
 		}
 		if ((n >= 4) && (!strncmp(buf, "USER", 4) || !strncmp(buf, "NICK", 4) || !strncmp(buf, "PASS", 4) || !strncmp(buf, "CAP ", 4)))
 		{
 			char buf[512];
 			snprintf(buf, sizeof(buf),
-				"ERROR :NON-SSL command received on SSL-only port. Check your connection settings.\r\n");
+				"ERROR :NON-TLS command received on TLS-only port. Check your connection settings.\r\n");
 			(void)send(fd, buf, strlen(buf), 0);
-			return fatal_ssl_error(SSL_ERROR_SSL, SAFE_SSL_ACCEPT, ERRNO, client);
+			return fatal_tls_error(SSL_ERROR_SSL, FUNC_TLS_ACCEPT, ERRNO, client);
 		}
 		if ((n >= 8) && (!strncmp(buf, "PROTOCTL", 8) || !strncmp(buf, "SERVER", 6)))
 		{
 			char buf[512];
 			snprintf(buf, sizeof(buf),
-				"ERROR :NON-SSL command received on SSL-only port. Check your connection settings.\r\n");
+				"ERROR :NON-TLS command received on TLS-only port. Check your connection settings.\r\n");
 			(void)send(fd, buf, strlen(buf), 0);
-			return fatal_ssl_error(SSL_ERROR_SSL, SAFE_SSL_ACCEPT, ERRNO, client);
+			return fatal_tls_error(SSL_ERROR_SSL, FUNC_TLS_ACCEPT, ERRNO, client);
 		}
 		if (n > 0)
 			SetNextCall(client);
@@ -764,17 +762,17 @@ int ircd_SSL_accept(Client *client, int fd)
 				{
 					return 1;
 				}
-				return fatal_ssl_error(ssl_err, SAFE_SSL_ACCEPT, ERRNO, client);
+				return fatal_tls_error(ssl_err, FUNC_TLS_ACCEPT, ERRNO, client);
 			case SSL_ERROR_WANT_READ:
-				fd_setselect(fd, FD_SELECT_READ, ircd_SSL_accept_retry, client);
+				fd_setselect(fd, FD_SELECT_READ, unreal_tls_accept_retry, client);
 				fd_setselect(fd, FD_SELECT_WRITE, NULL, client);
 				return 1;
 			case SSL_ERROR_WANT_WRITE:
 				fd_setselect(fd, FD_SELECT_READ, NULL, client);
-				fd_setselect(fd, FD_SELECT_WRITE, ircd_SSL_accept_retry, client);
+				fd_setselect(fd, FD_SELECT_WRITE, unreal_tls_accept_retry, client);
 				return 1;
 			default:
-				return fatal_ssl_error(ssl_err, SAFE_SSL_ACCEPT, ERRNO, client);
+				return fatal_tls_error(ssl_err, FUNC_TLS_ACCEPT, ERRNO, client);
 		}
 		/* NOTREACHED */
 		return -1;
@@ -786,14 +784,14 @@ int ircd_SSL_accept(Client *client, int fd)
 }
 
 /** Called by the I/O engine to (re)try to connect to a remote host */
-static void ircd_SSL_connect_retry(int fd, int revents, void *data)
+static void unreal_tls_connect_retry(int fd, int revents, void *data)
 {
 	Client *client = data;
-	ircd_SSL_connect(client, fd);
+	unreal_tls_connect(client, fd);
 }
 
 /** Connect to a remote host - that is: connect and do the TLS handshake */
-int ircd_SSL_connect(Client *client, int fd)
+int unreal_tls_connect(Client *client, int fd)
 {
 	int ssl_err;
 
@@ -805,23 +803,23 @@ int ircd_SSL_connect(Client *client, int fd)
 			case SSL_ERROR_SYSCALL:
 				if (ERRNO == P_EINTR || ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN)
 				{
-					/* Hmmm. This implementation is different than in ircd_SSL_accept().
+					/* Hmmm. This implementation is different than in unreal_tls_accept().
 					 * One of them must be wrong -- better check! (TODO)
 					 */
-					fd_setselect(fd, FD_SELECT_READ|FD_SELECT_WRITE, ircd_SSL_connect_retry, client);
+					fd_setselect(fd, FD_SELECT_READ|FD_SELECT_WRITE, unreal_tls_connect_retry, client);
 					return 0;
 				}
-				return fatal_ssl_error(ssl_err, SAFE_SSL_CONNECT, ERRNO, client);
+				return fatal_tls_error(ssl_err, FUNC_TLS_CONNECT, ERRNO, client);
 			case SSL_ERROR_WANT_READ:
-				fd_setselect(fd, FD_SELECT_READ, ircd_SSL_connect_retry, client);
+				fd_setselect(fd, FD_SELECT_READ, unreal_tls_connect_retry, client);
 				fd_setselect(fd, FD_SELECT_WRITE, NULL, client);
 				return 0;
 			case SSL_ERROR_WANT_WRITE:
 				fd_setselect(fd, FD_SELECT_READ, NULL, client);
-				fd_setselect(fd, FD_SELECT_WRITE, ircd_SSL_connect_retry, client);
+				fd_setselect(fd, FD_SELECT_WRITE, unreal_tls_connect_retry, client);
 				return 0;
 			default:
-				return fatal_ssl_error(ssl_err, SAFE_SSL_CONNECT, ERRNO, client);
+				return fatal_tls_error(ssl_err, FUNC_TLS_CONNECT, ERRNO, client);
 		}
 		/* NOTREACHED */
 		return -1;
@@ -833,7 +831,7 @@ int ircd_SSL_connect(Client *client, int fd)
 	return 1;
 }
 
-/** Shutdown a SSL/TLS connection (gracefully) */
+/** Shutdown a TLS connection (gracefully) */
 int SSL_smart_shutdown(SSL *ssl)
 {
 	char i;
@@ -848,50 +846,54 @@ int SSL_smart_shutdown(SSL *ssl)
 }
 
 /**
- * Report a fatal SSL error and disconnect the associated client.
+ * Report a fatal TLS error and disconnect the associated client.
  *
  * @param ssl_error The error as from OpenSSL.
  * @param where The location, one of the SAFE_SSL_* defines.
  * @param my_errno A preserved value of errno to pass to ssl_error_str().
  * @param client The client the error is associated with.
  */
-static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *client)
+static int fatal_tls_error(int ssl_error, int where, int my_errno, Client *client)
 {
 	/* don`t alter ERRNO */
 	int errtmp = ERRNO;
-	char *ssl_errstr, *ssl_func;
+	const char *ssl_errstr, *ssl_func;
 	unsigned long additional_errno = ERR_get_error();
 	char additional_info[256];
+	char buf[512];
 	const char *one, *two;
 
 	if (IsDeadSocket(client))
-	{
-#ifdef DEBUGMODE
-		/* This is quite possible I guess.. especially if we don't pay attention upstream :p */
-		ircd_log(LOG_ERROR, "Warning: fatal_ssl_error() called for already-dead-socket (%d/%s)",
-			client->local->fd, client->name);
-#endif
 		return -1;
-	}
 
 	switch(where)
 	{
-		case SAFE_SSL_READ:
+		case FUNC_TLS_READ:
 			ssl_func = "SSL_read()";
 			break;
-		case SAFE_SSL_WRITE:
+		case FUNC_TLS_WRITE:
 			ssl_func = "SSL_write()";
 			break;
-		case SAFE_SSL_ACCEPT:
+		case FUNC_TLS_ACCEPT:
 			ssl_func = "SSL_accept()";
 			break;
-		case SAFE_SSL_CONNECT:
+		case FUNC_TLS_CONNECT:
 			ssl_func = "SSL_connect()";
 			break;
 		default:
 			ssl_func = "undefined SSL func";
 	}
 
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	/* Fetch additional error information from OpenSSL 3.0.0+ */
+	two = ERR_reason_error_string(additional_errno);
+	if (two && *two)
+	{
+		snprintf(additional_info, sizeof(additional_info), ": %s", two);
+	} else {
+		*additional_info = '\0';
+	}
+#else
 	/* Fetch additional error information from OpenSSL. This is new as of Nov 2017 (4.0.16+) */
 	one = ERR_func_error_string(additional_errno);
 	two = ERR_reason_error_string(additional_errno);
@@ -901,20 +903,18 @@ static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *clien
 	} else {
 		*additional_info = '\0';
 	}
+#endif
 
 	ssl_errstr = ssl_error_str(ssl_error, my_errno);
 
-	/* if we reply() something here, we might just trigger another
-	 * fatal_ssl_error() call and loop until a stack overflow... 
-	 * the client won`t get the ERROR : ... string, but this is
-	 * the only way to do it.
-	 * IRC protocol wasn`t SSL enabled .. --vejeta
-	 */
 	SetDeadSocket(client);
-	sendto_snomask(SNO_JUNK, "Exiting ssl client %s: %s: %s%s",
-		get_client_name(client, TRUE), ssl_func, ssl_errstr, additional_info);
+	unreal_log(ULOG_DEBUG, "tls", "DEBUG_TLS_FATAL_ERROR", client,
+		   "Exiting TLS client $client.details: $tls_function: $tls_error_string: $tls_additional_info",
+		   log_data_string("tls_function", ssl_func),
+		   log_data_string("tls_error_string", ssl_errstr),
+		   log_data_string("tls_additional_info", additional_info));
 
-	if (where == SAFE_SSL_CONNECT)
+	if (where == FUNC_TLS_CONNECT)
 	{
 		char extra[256];
 		*extra = '\0';
@@ -922,15 +922,17 @@ static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *clien
 		{
 			snprintf(extra, sizeof(extra),
 			         ". Please verify that listen::options::ssl is enabled on port %d in %s's configuration file.",
-			         (client->serv && client->serv->conf) ? client->serv->conf->outgoing.port : -1,
+			         (client->server && client->server->conf) ? client->server->conf->outgoing.port : -1,
 			         client->name);
 		}
-		lost_server_link(client, "%s: %s%s%s", ssl_func, ssl_errstr, additional_info, extra);
+		snprintf(buf, sizeof(buf), "%s: %s%s%s", ssl_func, ssl_errstr, additional_info, extra);
+		lost_server_link(client, buf);
 	} else
-	if (IsServer(client) || (client->serv && client->serv->conf))
+	if (IsServer(client) || (client->server && client->server->conf))
 	{
 		/* Either a trusted fully established server (incoming) or an outgoing server link (established or not) */
-		lost_server_link(client, "%s: %s%s", ssl_func, ssl_errstr, additional_info);
+		snprintf(buf, sizeof(buf), "%s: %s%s", ssl_func, ssl_errstr, additional_info);
+		lost_server_link(client, buf);
 	}
 
 	if (errtmp)
@@ -949,7 +951,7 @@ static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *clien
 	return -1;
 }
 
-/** Do a SSL/TLS handshake after a STARTTLS, as a client */
+/** Do a TLS handshake after a STARTTLS, as a client */
 int client_starttls(Client *client)
 {
 	if ((client->local->ssl = SSL_new(ctx_client)) == NULL)
@@ -960,15 +962,14 @@ int client_starttls(Client *client)
 	SSL_set_fd(client->local->ssl, client->local->fd);
 	SSL_set_nonblocking(client->local->ssl);
 
-	if (client->serv && client->serv->conf)
+	if (client->server && client->server->conf)
 	{
 		/* Client: set hostname for SNI */
-		SSL_set_tlsext_host_name(client->local->ssl, client->serv->conf->servername);
+		SSL_set_tlsext_host_name(client->local->ssl, client->server->conf->servername);
 	}
 
-	if (ircd_SSL_connect(client, client->local->fd) < 0)
+	if (unreal_tls_connect(client, client->local->fd) < 0)
 	{
-		Debug((DEBUG_DEBUG, "Failed SSL connect handshake in instance 1: %s", client->name));
 		SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
 		SSL_smart_shutdown(client->local->ssl);
 		SSL_free(client->local->ssl);
@@ -987,7 +988,7 @@ fail_starttls:
 }
 
 /** Find the appropriate TLSOptions structure for a client.
- * NOTE: The default global SSL options will be returned if not found,
+ * NOTE: The default global TLS options will be returned if not found,
  *       or NULL if no such options are available (unlikely, but possible?).
  */
 TLSOptions *FindTLSOptionsForUser(Client *client)
@@ -1020,7 +1021,7 @@ TLSOptions *FindTLSOptionsForUser(Client *client)
  * @param errstr: Error will be stored in here (optional)
  * @returns Returns 1 on success and 0 on error.
  */
-int verify_certificate(SSL *ssl, char *hostname, char **errstr)
+int verify_certificate(SSL *ssl, const char *hostname, char **errstr)
 {
 	static char buf[512];
 	X509 *cert;
@@ -1033,10 +1034,10 @@ int verify_certificate(SSL *ssl, char *hostname, char **errstr)
 
 	if (!ssl)
 	{
-		strlcpy(buf, "Not using SSL/TLS", sizeof(buf));
+		strlcpy(buf, "Not using TLS", sizeof(buf));
 		if (errstr)
 			*errstr = buf;
-		return 0; /* Cannot verify a non-SSL connection */
+		return 0; /* Cannot verify a non-TLS connection */
 	}
 
 	if (SSL_get_verify_result(ssl) != X509_V_OK)
@@ -1085,7 +1086,7 @@ int verify_certificate(SSL *ssl, char *hostname, char **errstr)
 }
 
 /** Grab the certificate name */
-char *certificate_name(SSL *ssl)
+const char *certificate_name(SSL *ssl)
 {
 	static char buf[384];
 	X509 *cert;
@@ -1128,7 +1129,7 @@ int cipher_check(SSL_CTX *ctx, char **errstr)
 	ssl = SSL_new(ctx);
 	if (!ssl)
 	{
-		snprintf(errbuf, sizeof(errbuf), "Could not create SSL structure");
+		snprintf(errbuf, sizeof(errbuf), "Could not create TLS structure");
 		return 0;
 	}
 
@@ -1169,6 +1170,8 @@ int cipher_check(SSL_CTX *ctx, char **errstr)
 /** Check if a certificate (or actually: key) is weak */
 int certificate_quality_check(SSL_CTX *ctx, char **errstr)
 {
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+	// FIXME: this only works on OpenSSL <3.0.0
 	SSL *ssl;
 	X509 *cert;
 	EVP_PKEY *public_key;
@@ -1185,14 +1188,14 @@ int certificate_quality_check(SSL_CTX *ctx, char **errstr)
 	ssl = SSL_new(ctx);
 	if (!ssl)
 	{
-		snprintf(errbuf, sizeof(errbuf), "Could not create SSL structure");
+		snprintf(errbuf, sizeof(errbuf), "Could not create TLS structure");
 		return 0;
 	}
 
 	cert = SSL_get_certificate(ssl);
 	if (!cert)
 	{
-		snprintf(errbuf, sizeof(errbuf), "Could not retrieve SSL/TLS certificate");
+		snprintf(errbuf, sizeof(errbuf), "Could not retrieve TLS certificate");
 		SSL_free(ssl);
 		return 0;
 	}
@@ -1221,14 +1224,15 @@ int certificate_quality_check(SSL_CTX *ctx, char **errstr)
 
 	if (key_length < 2048)
 	{
-		snprintf(errbuf, sizeof(errbuf), "Your SSL/TLS certificate key is only %d bits, which is insecure", key_length);
+		snprintf(errbuf, sizeof(errbuf), "Your TLS certificate key is only %d bits, which is insecure", key_length);
 		return 0;
 	}
 
+#endif
 	return 1;
 }
 
-char *spki_fingerprint_ex(X509 *x509_cert);
+const char *spki_fingerprint_ex(X509 *x509_cert);
 
 /** Return the SPKI Fingerprint for a client.
  *
@@ -1237,10 +1241,10 @@ char *spki_fingerprint_ex(X509 *x509_cert);
  * openssl dgst -sha256 -binary public.key | openssl enc -base64
  * ( from https://tools.ietf.org/html/draft-ietf-websec-key-pinning-21#appendix-A )
  */
-char *spki_fingerprint(Client *cptr)
+const char *spki_fingerprint(Client *cptr)
 {
 	X509 *x509_cert = NULL;
-	char *ret;
+	const char *ret;
 
 	if (!MyConnect(cptr) || !cptr->local->ssl)
 		return NULL;
@@ -1253,12 +1257,11 @@ char *spki_fingerprint(Client *cptr)
 	return ret;
 }
 
-char *spki_fingerprint_ex(X509 *x509_cert)
+const char *spki_fingerprint_ex(X509 *x509_cert)
 {
 	unsigned char *der_cert = NULL, *p;
 	int der_cert_len, n;
 	static char retbuf[256];
-	SHA256_CTX ckctx;
 	unsigned char checksum[SHA256_DIGEST_LENGTH];
 
 	memset(retbuf, 0, sizeof(retbuf));
@@ -1274,9 +1277,7 @@ char *spki_fingerprint_ex(X509 *x509_cert)
 			/* The DER encoded SPKI is stored in 'der_cert' with length 'der_cert_len'.
 			 * Now we need to create an SHA256 hash out of it.
 			 */
-			SHA256_Init(&ckctx);
-			SHA256_Update(&ckctx, der_cert, der_cert_len);
-			SHA256_Final(checksum, &ckctx);
+			sha256hash_binary(checksum, der_cert, der_cert_len);
 
 			/* And convert the binary to a base64 string... */
 			n = b64_encode(checksum, SHA256_DIGEST_LENGTH, retbuf, sizeof(retbuf));
@@ -1317,7 +1318,7 @@ int outdated_tls_client(Client *client)
 }
 
 /** Returns the expanded string used for set::outdated-tls-policy::user-message etc. */
-char *outdated_tls_client_build_string(char *pattern, Client *client)
+const char *outdated_tls_client_build_string(const char *pattern, Client *client)
 {
 	static char buf[512];
 	const char *name[3], *value[3];
@@ -1404,8 +1405,10 @@ void check_certificate_expiry_tlsoptions_and_warn(TLSOptions *tlsoptions)
 
 	if (check_certificate_expiry_ctx(ctx, &errstr))
 	{
-		sendto_umode_global(UMODE_OPER, "Warning: TLS certificate '%s': %s", tlsoptions->certificate_file, errstr);
-		ircd_log(LOG_ERROR, "[warning] TLS certificate '%s': %s", tlsoptions->certificate_file, errstr);
+		unreal_log(ULOG_ERROR, "tls", "TLS_CERT_EXPIRING", NULL,
+		           "Warning: TLS certificate '$filename': $error_string",
+		           log_data_string("filename", tlsoptions->certificate_file),
+		           log_data_string("error_string", errstr));
 	}
 	SSL_CTX_free(ctx);
 }
diff --git a/src/unrealdb.c b/src/unrealdb.c
@@ -40,15 +40,15 @@
  * and I/O speeds of the underlying hardware.
  */
 
-/* In UnrealIRCd 5.2.0 we don't write the v1 header yet for unencrypted
- * database files, this so users using unencrypted can easily downgrade
- * to 5.0.9 and lower should there be any need to do so.
+/* In UnrealIRCd 5.2.x we didn't write the v1 header yet for unencrypted
+ * database files, this so users using unencrypted could easily downgrade
+ * to version 5.0.9 and older.
  * We DO support READING encypted, unencrypted v1, and unencrypted raw (v0)
- * in 5.2.0, though.
- * Presumably in 2022 or so we will stop writing v0 by default and change
- * this #undef to a #define to write v1.
+ * in 5.2.0 onwards, though.
+ * Starting with UnrealIRCd 6 we now write the header, so people can only
+ * downgrade from UnrealIRCd 6 to 5.2.0 and later (not 5.0.9).
  */
-#undef UNREALDB_WRITE_V1
+#define UNREALDB_WRITE_V1
 
 /* If a key is specified, it must be this size */
 #define UNREALDB_KEY_LEN	crypto_secretstream_xchacha20poly1305_KEYBYTES
@@ -68,6 +68,7 @@
 /* Forward declarations - only used for internal (static) functions, of course */
 static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg);
 static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg);
+static void unrealdb_set_error(UnrealDB *c, UnrealDBError errcode, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,3,4)));
 
 UnrealDBError unrealdb_last_error_code;
 static char *unrealdb_last_error_string = NULL;
@@ -136,7 +137,7 @@ static int unrealdb_kdf(UnrealDB *c, Secret *secr)
  *       For programmatically checking of error conditions
  *       use unrealdb_get_error_code() instead.
  */
-char *unrealdb_get_error_string(void)
+const char *unrealdb_get_error_string(void)
 {
 	return unrealdb_last_error_string;
 }
@@ -321,12 +322,16 @@ UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_bl
 		if (cached)
 		{
 #ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Cache hit for '%s' while writing", secr->name);
+			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_HIT", NULL,
+			           "Cache hit for '$secret_block' while writing",
+			           log_data_string("secret_block", secr->name));
 #endif
 		} else
 		{
 #ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Need to run argon2 '%s' while writing", secr->name);
+			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_MISS", NULL,
+			           "Cache miss for '$secret_block' while writing, need to run argon2",
+			           log_data_string("secret_block", secr->name));
 #endif
 			if (!unrealdb_kdf(c, secr))
 			{
@@ -414,11 +419,15 @@ UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_bl
 			/* Use cached key, no need to run expensive argon2.. */
 			memcpy(c->config->key, dbcache->config->key, c->config->keylen);
 #ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Cache hit for '%s' while reading", secr->name);
+			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_HIT", NULL,
+			           "Cache hit for '$secret_block' while reading",
+			           log_data_string("secret_block", secr->name));
 #endif
 		} else {
 #ifdef DEBUGMODE
-			ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Need to run argon2 for '%s' while reading", secr->name);
+			unreal_log(ULOG_DEBUG, "unrealdb", "DEBUG_UNREALDB_CACHE_MISS", NULL,
+			           "Cache miss for '$secret_block' while reading, need to run argon2",
+			           log_data_string("secret_block", secr->name));
 #endif
 			if (!unrealdb_kdf(c, secr))
 			{
@@ -563,11 +572,11 @@ char *unrealdb_test_db(const char *filename, char *secret_block)
  *       unrealdb_write_int64(), unrealdb_write_int32(), unrealdb_write_int16(),
  *       unrealdb_write_char(), unrealdb_write_str().
  */
-static int unrealdb_write(UnrealDB *c, void *wbuf, int len)
+static int unrealdb_write(UnrealDB *c, const void *wbuf, int len)
 {
 	char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
 	unsigned long long out_len;
-	char *buf = wbuf;
+	const char *buf = wbuf;
 
 	if (c->error_code)
 		return 0;
@@ -643,7 +652,7 @@ static int unrealdb_write(UnrealDB *c, void *wbuf, int len)
  *        Note that 'x' can safely be NULL.
  * @returns 1 on success, 0 on failure.
  */
-int unrealdb_write_str(UnrealDB *c, char *x)
+int unrealdb_write_str(UnrealDB *c, const char *x)
 {
 	uint16_t len;
 
@@ -934,6 +943,7 @@ int unrealdb_read_char(UnrealDB *c, char *t)
 
 /** @} */
 
+#if 0
 void fatal_error(FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
@@ -1045,10 +1055,11 @@ void unrealdb_test(void)
 	fprintf(stderr, "**** TESTING UNENCRYPTED ****\n");
 	unrealdb_test_speed(NULL);
 }
+#endif
 
 /** TODO: document and implement
  */
-char *unrealdb_test_secret(char *name)
+const char *unrealdb_test_secret(const char *name)
 {
 	// FIXME: check if exists, if not then return an error, with a nice FAQ reference etc.
 	return NULL; /* no error */
diff --git a/src/updconf.c b/src/updconf.c
@@ -1,1730 +0,0 @@
-/*
- * Configuration file updater - upgrade from 3.2.x to 4.x
- * (C) Copyright 2015 Bram Matthys and the UnrealIRCd team
- *
- * License: GPLv2
- */
- 
-#include "unrealircd.h"
-
-extern void config_free(ConfigFile *cfptr);
-
-char configfiletmp[512];
-
-struct Upgrade
-{
-	char *locop_host;
-	char *oper_host;
-	char *coadmin_host;
-	char *admin_host;
-	char *sadmin_host;
-	char *netadmin_host;
-	int host_on_oper_up;
-};
-
-struct Upgrade upgrade;
-
-typedef struct FlagMapping FlagMapping;
-struct FlagMapping
-{
-	char shortflag;
-	char *longflag;
-};
-
-static FlagMapping FlagMappingTable[] = {
-	{ 'o', "local" },
-	{ 'O', "global" },
-	{ 'r', "can_rehash" },
-	{ 'D', "can_die" },
-	{ 'R', "can_restart" },
-	{ 'w', "can_wallops" },
-	{ 'g', "can_globops" },
-	{ 'c', "can_localroute" },
-	{ 'L', "can_globalroute" },
-	{ 'K', "can_globalkill" },
-	{ 'b', "can_kline" },
-	{ 'B', "can_unkline" },
-	{ 'n', "can_localnotice" },
-	{ 'G', "can_globalnotice" },
-	{ 'A', "admin" },
-	{ 'a', "services-admin" },
-	{ 'N', "netadmin" },
-	{ 'C', "coadmin" },
-	{ 'z', "can_zline" },
-	{ 'W', "get_umodew" },
-	{ 'H', "get_host" },
-	{ 't', "can_gkline" },
-	{ 'Z', "can_gzline" },
-	{ 'v', "can_override" },
-	{ 'q', "can_setq" },
-	{ 'd', "can_dccdeny" },
-	{ 'T', "can_tsctl" },
-	{ 0, NULL },
-};
-
-int needs_modules_default_conf = 1;
-int needs_operclass_default_conf = 1;
-
-static void die()
-{
-#ifdef _WIN32
-	win_error(); /* ? */
-#endif
-	exit(0);
-}
-
-#define CFGBUFSIZE 1024
-void modify_file(int start, char *ins, int stop)
-{
-	char configfiletmp2[512];
-	FILE *fdi, *fdo;
-	char *rdbuf = NULL, *wbuf;
-	int n;
-	int first = 1;
-		
-	snprintf(configfiletmp2, sizeof(configfiletmp2), "%s.tmp", configfiletmp); // .tmp.tmp :D
-
-#ifndef _WIN32
-	fdi = fopen(configfiletmp, "r");
-	fdo = fopen(configfiletmp2, "w");
-#else
-	fdi = fopen(configfiletmp, "rb");
-	fdo = fopen(configfiletmp2, "wb");
-#endif
-
-	if (!fdi || !fdo)
-	{
-		config_error("could not read/write to %s/%s", configfiletmp, configfiletmp2);
-		die();
-	}
-
-	rdbuf = safe_alloc(start);
-	
-	if ((n = fread(rdbuf, 1, start, fdi)) != start)
-	{
-		config_error("read error in remove_section(%d,%d): %d", start, stop, n);
-		die();
-	}
-	
-	fwrite(rdbuf, 1, start, fdo);
-	
-	safe_free(rdbuf);
-
-	if (ins)
-		fwrite(ins, 1, strlen(ins), fdo); /* insert this piece */
-
-	if (stop > 0)
-	{
-		if (fseek(fdi, stop+1, SEEK_SET) != 0)
-			goto end; /* end of file we hope.. */
-	}
-	
-	// read the remaining stuff
-	rdbuf = safe_alloc(CFGBUFSIZE);
-	
-	while(1)
-	{
-		n = fread(rdbuf, 1, CFGBUFSIZE, fdi);
-		if (n <= 0)
-			break; // done
-		
-		wbuf = rdbuf;
-		
-		if (first && (stop > 0))
-		{
-			if ((n > 0) && (*wbuf == '\r'))
-			{
-				wbuf++;
-				n--;
-			}
-			if ((n > 0) && (*wbuf == '\n'))
-			{
-				wbuf++;
-				n--;
-			}
-			first = 0;
-			if (n <= 0)
-				break; /* we are done (EOF) */
-		}
-		
-		fwrite(wbuf, 1, n, fdo);
-	}
-
-end:
-	fclose(fdi);
-	fclose(fdo);
-	
-	safe_free(rdbuf);
-	// todo: handle write errors and such..
-
-	unlink(configfiletmp);
-	if (rename(configfiletmp2, configfiletmp) < 0)
-	{
-		config_error("Could not rename '%s' to '%s': %s", configfiletmp2, configfiletmp, strerror(errno));
-		die();
-	}
-}
-
-void remove_section(int start, int stop)
-{
-	modify_file(start, NULL, stop);
-}
-
-void insert_section(int start, char *buf)
-{
-#ifdef _WIN32
-static char realbuf[16384];
-char *i, *o;
-
-	if (strlen(buf) > ((sizeof(realbuf)/2)-2))
-		abort(); /* damn lazy you !!! */
-
-	for (i = buf, o = realbuf; *i; i++)
-	{
-		if (*i == '\n')
-		{
-			*o++ = '\r';
-			*o++ = '\n';
-		} else
-		{
-			*o++ = *i;
-		}
-	}
-	*o = '\0';
-	
-	modify_file(start, realbuf, 0);
-#else
-	modify_file(start, buf, 0);
-#endif
-}
-
-void replace_section(ConfigEntry *ce, char *buf)
-{
-	remove_section(ce->ce_fileposstart, ce->ce_fileposend);
-	insert_section(ce->ce_fileposstart, buf);
-}
-
-static char buf[8192];
-
-int upgrade_me_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep;
-	char *name = NULL;
-	char *info = NULL;
-	int numeric = 0;
-
-	char sid[16];
-
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "sid"))
-			return 0; /* no upgrade needed */
-		else if (!cep->ce_vardata)
-		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"me", cep->ce_varname);
-			return 0;
-		}
-		else if (!strcmp(cep->ce_varname, "name"))
-			name = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "info"))
-			info = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "numeric"))
-			numeric = atoi(cep->ce_vardata);
-	}
-	
-	if (!name || !info || !numeric)
-	{
-		/* Invalid block as it does not contain the 3.2.x mandatory items */
-		return 0;
-	}
-
-	snprintf(sid, sizeof(sid), "%.3d", numeric);
-		
-	snprintf(buf, sizeof(buf),
-			 "me {\n"
-			 "\tname %s;\n"
-			 "\tinfo \"%s\";\n"
-			 "\tsid %s;\n"
-			 "};\n",
-			 name,
-			 info,
-			 sid);
-
-	replace_section(ce, buf);
-	
-	config_status("- me block upgraded");
-	return 1;
-}
-
-int upgrade_link_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-	char *bind_ip = NULL;
-	char *username = NULL;
-	char *hostname = NULL;
-	char *port = NULL;
-	char *password_receive = NULL;
-	char *password_connect = NULL;
-	char *class = NULL;
-	int options_ssl = 0;
-	int options_autoconnect = 0;
-	int options_nohostcheck = 0;
-	int options_quarantine = 0;
-	/* options_nodnscache is deprecated, always now.. */
-	char *hub = NULL;
-	char *leaf = NULL;
-	int leaf_depth = -1;
-	char *ciphers = NULL;
-	char *password_receive_authmethod = NULL;
-	int need_incoming = 0, need_outgoing = 0;
-
-	/* ripped from test_link */
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "incoming") || !strcmp(cep->ce_varname, "outgoing"))
-			return 0; /* no upgrade needed */
-		else if (!strcmp(cep->ce_varname, "options"))
-		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-			{
-				if (!strcmp(cepp->ce_varname, "ssl"))
-					options_ssl = 1;
-				if (!strcmp(cepp->ce_varname, "autoconnect"))
-					options_autoconnect = 1;
-				if (!strcmp(cepp->ce_varname, "nohostcheck"))
-					options_nohostcheck = 1;
-				if (!strcmp(cepp->ce_varname, "quarantine"))
-					options_quarantine = 1;
-			}
-		}
-		else if (!cep->ce_vardata)
-		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"link", cep->ce_varname);
-			return 0;
-		}
-		else if (!strcmp(cep->ce_varname, "username"))
-			username = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "hostname"))
-			hostname = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "bind-ip"))
-			bind_ip = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "port"))
-			port = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "password-receive"))
-		{
-			password_receive = cep->ce_vardata;
-			if (cep->ce_entries)
-				password_receive_authmethod = cep->ce_entries->ce_varname;
-		}
-		else if (!strcmp(cep->ce_varname, "password-connect"))
-			password_connect = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "class"))
-			class = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "hub"))
-			hub = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "leaf"))
-			leaf = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "leafdepth"))
-			leaf_depth = atoi(cep->ce_vardata);
-		else if (!strcmp(cep->ce_varname, "ciphers"))
-			ciphers = cep->ce_vardata;
-	}
-	
-	if (!username || !hostname || !class || !password_receive ||
-	    !password_connect || !port)
-	{
-		/* Invalid link block as it does not contain the 3.2.x mandatory items */
-		return 0;
-	}
-	
-	if (strchr(hostname, '?') || strchr(hostname, '*'))
-	{
-		/* Wildcards in hostname: incoming only */
-		need_incoming = 1;
-		need_outgoing = 0;
-	}
-	else
-	{
-		/* IP (or hostname with nohostcheck) */
-		need_incoming = 1;
-		need_outgoing = 1;
-	}
-
-	snprintf(buf, sizeof(buf), "link %s {\n", ce->ce_vardata);
-	
-	if (need_incoming)
-	{
-		char upg_mask[HOSTLEN+USERLEN+8];
-		
-		if (options_nohostcheck)
-		{
-			strlcpy(upg_mask, "*", sizeof(upg_mask));
-		}
-		else
-		{
-			if (!strcmp(username, "*"))
-				strlcpy(upg_mask, hostname, sizeof(upg_mask)); /* just host */
-			else
-				snprintf(upg_mask, sizeof(upg_mask), "%s@%s", username, hostname); /* user@host */
-		}
-
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tincoming {\n\t\tmask %s;\n\t};\n", upg_mask);
-	}
-
-	if (need_outgoing)
-	{
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
-		         "\toutgoing {\n"
-		         "\t\tbind-ip %s;\n"
-		         "\t\thostname %s;\n"
-		         "\t\tport %s;\n"
-		         "\t\toptions { %s%s};\n"
-		         "\t};\n",
-		         bind_ip,
-		         hostname,
-		         port,
-		         options_ssl ? "ssl; " : "",
-		         options_autoconnect ? "autoconnect; " : "");
-	}
-
-	if (strcasecmp(password_connect, password_receive))
-	{
-		if (!password_receive_authmethod)
-		{
-			/* Prompt user ? */
-			config_warn("Link block '%s' has a different connect/receive password. "
-			            "This is no longer supported in UnrealIRCd 4.x",
-			            ce->ce_vardata);
-			
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
-			         "\tpassword \"%s\"; /* WARNING: password changed due to 4.x upgrade */\n",
-			         options_autoconnect ? password_connect : password_receive);
-		} else
-		{
-			/* sslcertificate or sslcertficatefp */
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
-			         "\tpassword \"%s\" { %s; };\n",
-			         password_receive,
-			         password_receive_authmethod);
-		}
-	} else {
-		/* identical connect & receive passwords. easy. */
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
-		         "\tpassword \"%s\";\n", password_receive);
-	}
-
-	if (hub)
-	{
-		if (strcmp(hub, "*")) // only if it's something other than *, as * is the default anyway..
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\thub %s;\n", hub);
-	} else
-	if (leaf)
-	{
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tleaf %s;\n", leaf);
-		if (leaf_depth)
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tleaf-depth %d;\n", leaf_depth);
-	}
-
-	snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tclass %s;\n", class);
-
-	if (ciphers)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tciphers %s;\n", ciphers);
-
-	if (options_quarantine)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toptions { quarantine; };\n");
-
-	snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "};\n"); /* end */
-	
-	replace_section(ce, buf);
-	
-	config_status("- link block '%s' upgraded", ce->ce_vardata);
-	return 1;
-}
-
-/** oper::from::userhost becomes oper::mask & vhost::from::userhost becomes vhost::mask */
-#define MAXFROMENTRIES 100
-int upgrade_from_subblock(ConfigEntry *ce)
-{
-	ConfigEntry *cep;
-	char *list[MAXFROMENTRIES];
-	int listcnt = 0;
-
-	memset(list, 0, sizeof(list));
-	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!cep->ce_vardata)
-			continue;
-		else if (!strcmp(cep->ce_varname, "userhost"))
-		{
-			if (listcnt == MAXFROMENTRIES)
-				break; // no room, sorry.
-			list[listcnt++] = cep->ce_vardata;
-		}
-	}
-	
-	if (listcnt == 0)
-		return 0; /* invalid block. strange. */
-
-	if (listcnt == 1)
-	{
-		char *m = !strncmp(list[0], "*@", 2) ? list[0]+2 : list[0]; /* skip or don't skip the user@ part */
-		snprintf(buf, sizeof(buf), "mask %s;\n", m);
-	} else
-	{
-		/* Multiple (list of masks) */
-		int i;
-		snprintf(buf, sizeof(buf), "mask {\n");
-		
-		for (i=0; i < listcnt; i++)
-		{
-			char *m = !strncmp(list[i], "*@", 2) ? list[i]+2 : list[i]; /* skip or don't skip the user@ part */
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\t%s;\n", m);
-		}
-		
-		strlcat(buf, "\t};\n", sizeof(buf));
-	}
-
-	replace_section(ce, buf);
-	
-	config_status("- %s::from::userhost sub-block upgraded", ce->ce_prevlevel ? ce->ce_prevlevel->ce_varname : "???");
-	return 1;
-}
-
-int upgrade_loadmodule(ConfigEntry *ce)
-{
-	char *file = ce->ce_vardata;
-	char tmp[512], *p, *newfile;
-	
-	if (!file)
-		return 0;
-
-	if (our_strcasestr(file, "commands.dll") || our_strcasestr(file, "/commands.so"))
-	{
-		snprintf(buf, sizeof(buf), "include \"modules.default.conf\";\n");
-		needs_modules_default_conf = 0;
-		if (needs_operclass_default_conf)
-		{
-			/* This is a nice place :) */
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "include \"operclass.default.conf\";\n");
-			needs_operclass_default_conf = 0;
-		}
-		replace_section(ce, buf);
-		config_status("- loadmodule for '%s' replaced with an include \"modules.default.conf\"", file);
-		return 1;
-	}
-	
-	if (our_strcasestr(file, "cloak.dll") || our_strcasestr(file, "/cloak.so"))
-	{
-		replace_section(ce, "/* NOTE: cloaking module is included in modules.default.conf */");
-		config_status("- loadmodule for '%s' removed as this is now in modules.default.conf", file);
-		return 1;
-	}
-
-	/* All other loadmodule commands... */
-	
-	strlcpy(tmp, file, sizeof(tmp));
-	p = strstr(tmp, ".so");
-	if (p)
-		*p = '\0';
-	p = our_strcasestr(tmp, ".dll");
-	if (p)
-		*p = '\0';
-
-	newfile = !strncmp(tmp, "src/", 4) ? tmp+4 : tmp;
-	
-	newfile = !strncmp(newfile, "modules/", 8) ? newfile+8 : newfile;
-	
-	if (!strcmp(newfile, file))
-		return 0; /* no change */
-	
-	snprintf(buf, sizeof(buf), "loadmodule \"%s\";\n", newfile);
-	replace_section(ce, buf);
-	config_status("- loadmodule line converted to new syntax");
-	return 1;
-}
-
-int upgrade_include(ConfigEntry *ce)
-{
-	char *file = ce->ce_vardata;
-	static int badwords_upgraded_already = 0;
-	
-	if (!file)
-		return 0;
-
-	if (!strstr(file, "help/") && match_simple("help*.conf", file))
-	{
-		snprintf(buf, sizeof(buf), "include \"help/%s\";\n", file);
-		replace_section(ce, buf);
-		config_status("- include for '%s' replaced with 'help/%s'", file, file);
-		return 1;
-	}
-	
-	if (!strcmp("badwords.quit.conf", file))
-	{
-		*buf = '\0';
-		replace_section(ce, buf);
-		config_status("- include for '%s' removed (now in badwords.conf)", file);
-		return 1;
-	}
-
-	if (match_simple("badwords.*.conf", file))
-	{
-		if (badwords_upgraded_already)
-		{
-			*buf = '\0';
-			config_status("- include for '%s' removed (now in badwords.conf)", file);
-		} else {
-			strcpy(buf, "/* all badwords are now in badwords.conf */\ninclude \"badwords.conf\";\n");
-			badwords_upgraded_already = 1;
-			config_status("- include for '%s' replaced with 'badwords.conf'", file);
-		}
-		replace_section(ce, buf);
-		return 1;
-	}
-	
-	return 0;
-}
-
-#define MAXSPFTARGETS 32
-int upgrade_spamfilter_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-	char *reason = NULL;
-	char *regex = NULL;
-	char *action = NULL;
-	char *ban_time = NULL;
-	char *target[MAXSPFTARGETS];
-	char targets[512];
-	int targetcnt = 0;
-	char *match_type = NULL;
-	char *p;
-
-	memset(target, 0, sizeof(target));
-		
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "match") || !strcmp(cep->ce_varname, "match-type"))
-			return 0; /* no upgrade needed */
-		else if (!strcmp(cep->ce_varname, "target"))
-		{
-			if (cep->ce_vardata)
-			{
-				target[0] = cep->ce_vardata;
-			}
-			else if (cep->ce_entries)
-			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-				{
-					if (targetcnt == MAXSPFTARGETS)
-						break;
-					target[targetcnt++] = cepp->ce_varname;
-				}
-			}
-		}
-		else if (!cep->ce_vardata)
-			continue; /* invalid */
-		else if (!strcmp(cep->ce_varname, "regex"))
-		{
-			regex = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "action"))
-		{
-			action = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "reason"))
-		{
-			reason = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "ban-time"))
-		{
-			ban_time = cep->ce_vardata;
-		}
-	}
-
-	if (!regex || !target[0] || !action)
-		return 0; /* invalid spamfilter block */
-
-	/* build target(s) list */
-	if (targetcnt > 1)
-	{
-		int i;
-		
-		strlcpy(targets, "{ ", sizeof(targets));
-		
-		for (i=0; i < targetcnt; i++)
-		{
-			snprintf(targets+strlen(targets), sizeof(targets)-strlen(targets),
-			         "%s; ", target[i]);
-		}
-		strlcat(targets, "}", sizeof(target));
-	} else {
-		strlcpy(targets, target[0], sizeof(targets));
-	}
-
-	/* Determine match-type, fallback to 'posix' (=3.2.x regex engine) */
-	
-	match_type = "simple";
-	for (p = regex; *p; p++)
-		if (!isalnum(*p) && !isspace(*p))
-		{
-			match_type = "posix";
-			break;
-		}
-
-	snprintf(buf, sizeof(buf), "spamfilter {\n"
-	                           "\tmatch-type %s;\n"
-	                           "\tmatch \"%s\";\n"
-	                           "\ttarget %s;\n"
-	                           "\taction %s;\n",
-	                           match_type,
-	                           unreal_add_quotes(regex),
-	                           targets,
-	                           action);
-
-	/* optional: reason */
-	if (reason)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\treason \"%s\";\n", unreal_add_quotes(reason));
-	
-	/* optional: ban-time */
-	if (ban_time)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tban-time \"%s\";\n", ban_time);
-
-	strlcat(buf, "};\n", sizeof(buf));
-	
-	replace_section(ce, buf);
-	config_status("- spamfilter block converted to new syntax");
-	return 1;
-}
-
-#define MAXOPTIONS 32
-int upgrade_allow_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-	char *hostname = NULL;
-	char *ip = NULL;
-	char *maxperip = NULL;
-	char *ipv6_clone_mask = NULL;
-	char *password = NULL;
-	char *password_type = NULL;
-	char *class = NULL;
-	char *redirect_server = NULL;
-	int redirect_port = 0;
-	char *options[MAXOPTIONS];
-	int optionscnt = 0;
-	char options_str[512], comment[512];
-
-	memset(options, 0, sizeof(options));
-	*comment = *options_str = '\0';
-		
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "options"))
-		{
-			if (cep->ce_vardata)
-			{
-				options[0] = cep->ce_vardata;
-				optionscnt = 1;
-			}
-			else if (cep->ce_entries)
-			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-				{
-					if (optionscnt == MAXOPTIONS)
-						break;
-					options[optionscnt++] = cepp->ce_varname;
-				}
-			}
-		}
-		else if (!cep->ce_vardata)
-			continue; /* invalid */
-		else if (!strcmp(cep->ce_varname, "hostname"))
-		{
-			hostname = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "ip"))
-		{
-			ip = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "maxperip"))
-		{
-			maxperip = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "ipv6-clone-mask"))
-		{
-			ipv6_clone_mask = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "password"))
-		{
-			password = cep->ce_vardata;
-			if (cep->ce_entries)
-				password_type = cep->ce_entries->ce_varname;
-		}
-		else if (!strcmp(cep->ce_varname, "class"))
-		{
-			class = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "redirect-server"))
-		{
-			redirect_server = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "redirect-port"))
-		{
-			redirect_port = atoi(cep->ce_vardata);
-		}
-	}
-
-	if (!ip || !hostname || !class)
-		return 0; /* missing 3.2.x items in allow block, upgraded already! (or invalid) */
-
-	/* build target(s) list */
-	if (optionscnt == 0)
-	{
-		*options_str = '\0';
-	}
-	else
-	{
-		int i;
-		
-		for (i=0; i < optionscnt; i++)
-		{
-			snprintf(options_str+strlen(options_str), sizeof(options_str)-strlen(options_str),
-			         "%s; ", options[i]);
-		}
-	}
-
-	/* drop either 'ip' or 'hostname' */
-	if (!strcmp(ip, "*@*") && !strcmp(hostname, "*@*"))
-		hostname = NULL; /* just ip */
-	else if (strstr(ip, "NOMATCH"))
-		ip = NULL;
-	else if (strstr(hostname, "NOMATCH"))
-		hostname = NULL;
-	else if (!strchr(hostname, '.') && strcmp(hostname, "localhost"))
-		hostname = NULL;
-	else if (!strchr(ip, '.'))
-		ip = NULL;
-	else
-	{
-		/* very rare case -- let's bet on IP */
-		snprintf(comment, sizeof(comment), "/* CHANGED BY 3.2.x TO 4.x CONF UPGRADE!! Was: ip %s; hostname %s; */\n", ip, hostname);
-		hostname = NULL;
-	}
-
-	snprintf(buf, sizeof(buf), "allow {\n");
-
-	if (*comment)
-		strlcat(buf, comment, sizeof(buf));
-
-	if (ip)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tip \"%s\";\n", ip);
-
-	if (hostname)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\thostname \"%s\";\n", hostname);
-
-	snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tclass %s;\n", class);
-	
-	/* maxperip: optional in 3.2.x, mandatory in 4.x */
-	if (maxperip)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmaxperip %s;\n", maxperip);
-	else
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmaxperip 3; /* CHANGED BY 3.2.x TO 4.x CONF UPGRADE! */\n");
-	
-	if (ipv6_clone_mask)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tipv6-clone-mask %s;\n", ipv6_clone_mask);
-
-	if (password)
-	{
-		if (password_type)
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\" { %s; };\n", password, password_type);
-		else
-			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\";\n", password);
-	}
-
-	if (redirect_server)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tredirect-server %s;\n", redirect_server);
-
-	if (redirect_port)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tredirect-port %d;\n", redirect_port);
-
-	if (*options_str)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toptions { %s};\n", options_str);
-
-	strlcat(buf, "};\n", sizeof(buf));
-	
-	replace_section(ce, buf);
-	config_status("- allow block converted to new syntax");
-	return 1;
-}
-
-/* Pick out the ip address and the port number from a string.
- * The string syntax is:  ip:port.  ip must be enclosed in brackets ([]) if its an ipv6
- * address because they contain colon (:) separators.  The ip part is optional.  If the string
- * contains a single number its assumed to be a port number.
- *
- * Returns with ip pointing to the ip address (if one was specified), a "*" (if only a port
- * was specified), or an empty string if there was an error.  port is returned pointing to the
- * port number if one was specified, otherwise it points to a empty string.
- */
-void ipport_separate(char *string, char **ip, char **port)
-{
-	char *f;
-
-	/* assume failure */
-	*ip = *port = "";
-
-	/* sanity check */
-	if (string && strlen(string) > 0)
-	{
-		/* handle ipv6 type of ip address */
-		if (*string == '[')
-		{
-			if ((f = strrchr(string, ']')))
-			{
-				*ip = string + 1;	/* skip [ */
-				*f = '\0';			/* terminate the ip string */
-				/* next char must be a : if a port was specified */
-				if (*++f == ':')
-				{
-					*port = ++f;
-				}
-			}
-		}
-		/* handle ipv4 and port */
-		else if ((f = strchr(string, ':')))
-		{
-			/* we found a colon... we may have ip:port or just :port */
-			if (f == string)
-			{
-				/* we have just :port */
-				*ip = "*";
-			}
-			else
-			{
-				/* we have ip:port */
-				*ip = string;
-				*f = '\0';
-			}
-			*port = ++f;
-		}
-		/* no ip was specified, just a port number */
-		else if (!strcmp(string, my_itoa(atoi(string))))
-		{
-			*ip = "*";
-			*port = string;
-		}
-	}
-}
-
-int upgrade_listen_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-	char *ip = NULL;
-	char *port = NULL;
-	char *options[MAXOPTIONS];
-	int optionscnt = 0;
-	char options_str[512];
-	char copy[128];
-
-	memset(options, 0, sizeof(options));
-	*options_str = '\0';
-
-	if (!ce->ce_vardata)
-		return 0; /* already upgraded */
-
-	strlcpy(copy, ce->ce_vardata, sizeof(copy));
-	ipport_separate(copy, &ip, &port);
-	if (!ip || !*ip || !port || !*port)
-		return 0; /* invalid conf */
-	
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "options"))
-		{
-			if (cep->ce_entries)
-			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-				{
-					if (optionscnt == MAXOPTIONS)
-						break;
-					options[optionscnt++] = cepp->ce_varname;
-				}
-			}
-		}
-	}
-
-	/* build options list */
-	if (optionscnt == 0)
-	{
-		*options_str = '\0';
-	}
-	else
-	{
-		int i;
-		
-		for (i=0; i < optionscnt; i++)
-		{
-			snprintf(options_str+strlen(options_str), sizeof(options_str)-strlen(options_str),
-			         "%s; ", options[i]);
-		}
-	}
-
-	snprintf(buf, sizeof(buf), "listen {\n"
-	                           "\tip %s;\n"
-	                           "\tport %s;\n",
-	                           ip,
-	                           port);
-
-	if (*options_str)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toptions { %s};\n", options_str);
-
-	strlcat(buf, "};\n", sizeof(buf));
-	
-	replace_section(ce, buf);
-	config_status("- listen block converted to new syntax");
-	return 1;
-}
-
-int upgrade_cgiirc_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep;
-	char *type = NULL;
-	char *username = NULL;
-	char *hostname = NULL;
-	char *password = NULL, *password_type = NULL;
-	char mask[USERLEN+HOSTLEN+8];
-
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!cep->ce_vardata)
-		{
-			config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
-				"cgiirc", cep->ce_varname);
-			return 0;
-		}
-		else if (!strcmp(cep->ce_varname, "type"))
-			type = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "username"))
-			username = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "hostname"))
-			hostname = cep->ce_vardata;
-		else if (!strcmp(cep->ce_varname, "password"))
-		{
-			password = cep->ce_vardata;
-			if (cep->ce_entries)
-				password_type = cep->ce_entries->ce_varname;
-		}
-	}
-	
-	if (!type || !hostname)
-	{
-		/* Invalid block as it does not contain the 3.2.x mandatory items */
-		return 0;
-	}
-
-	if (username)
-		snprintf(mask, sizeof(mask), "%s@%s", username, hostname);
-	else
-		strlcpy(mask, hostname, sizeof(mask));
-
-	if (!strcmp(type, "old"))
-	{
-		snprintf(buf, sizeof(buf),
-		         "webirc {\n"
-		         "\ttype old;\n"
-		         "\tmask %s;\n",
-		         mask);
-	} else
-	{
-		if (password_type)
-		{
-			snprintf(buf, sizeof(buf),
-					 "webirc {\n"
-					 "\tmask %s;\n"
-					 "\tpassword \"%s\" { %s; };\n"
-					 "};\n",
-					 mask,
-					 password,
-					 password_type);
-		} else
-		{
-			snprintf(buf, sizeof(buf),
-					 "webirc {\n"
-					 "\tmask %s;\n"
-					 "\tpassword \"%s\";\n"
-					 "};\n",
-					 mask,
-					 password);
-		}
-	}
-
-	replace_section(ce, buf);
-	
-	config_status("- cgiirc block upgraded and renamed to webirc");
-	return 1;
-}
-
-int contains_flag(char **flags, int flagscnt, char *needle)
-{
-	int i;
-	
-	for (i = 0; i < flagscnt; i++)
-		if (!strcmp(flags[i], needle))
-			return 1;
-
-	return 0;
-}
-
-int upgrade_oper_block(ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-	char *name = NULL;
-	char *password = NULL;
-	char *password_type = NULL;
-	char *require_modes = NULL;
-	char *class = NULL;
-	char *flags[MAXOPTIONS];
-	int flagscnt = 0;
-	char *swhois = NULL;
-	char *snomask = NULL;
-	char *modes = NULL;
-	int maxlogins = -1;
-	char *fromlist[MAXFROMENTRIES];
-	int fromlistcnt = 0;
-	char maskbuf[1024];
-	char *operclass = NULL; /* set by us, not read from conf */
-	char *vhost = NULL; /* set by us, not read from conf */
-	int i;
-	char silly[64];
-
-	memset(flags, 0, sizeof(flags));
-	*maskbuf = '\0';
-
-	name = ce->ce_vardata;
-	
-	if (!name)
-		return 0; /* oper block without a name = invalid */
-
-	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-	{
-		if (!strcmp(cep->ce_varname, "operclass"))
-			return 0; /* already 4.x conf */
-		else if (!strcmp(cep->ce_varname, "flags"))
-		{
-			if (cep->ce_vardata) /* short options (flag letters) */
-			{
-				char *p;
-				for (p = cep->ce_vardata; *p; p++)
-				{
-					if (flagscnt == MAXOPTIONS)
-						break;
-					for (i = 0; FlagMappingTable[i].shortflag; i++)
-					{
-						if (FlagMappingTable[i].shortflag == *p)
-						{
-							flags[flagscnt] = FlagMappingTable[i].longflag;
-							flagscnt++;
-							break;
-						}
-					}
-				}
-			}
-			else if (cep->ce_entries) /* long options (flags written out) */
-			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-				{
-					if (flagscnt == MAXOPTIONS)
-						break;
-					flags[flagscnt++] = cepp->ce_varname;
-				}
-			}
-		}
-		else if (!strcmp(cep->ce_varname, "from"))
-		{
-			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-			{
-				if (!strcmp(cepp->ce_varname, "userhost") && cepp->ce_vardata)
-				{
-					if (fromlistcnt == MAXFROMENTRIES)
-						break; // no room, sorry.
-					fromlist[fromlistcnt++] = cepp->ce_vardata;
-				}
-			}
-		}
-		else if (!strcmp(cep->ce_varname, "mask"))
-		{
-			/* processing mask here means we can also upgrade 3.4-alphaX oper blocks.. */
-			if (cep->ce_vardata)
-			{
-				if (fromlistcnt == MAXFROMENTRIES)
-					break; // no room, sorry.
-				fromlist[fromlistcnt++] = cep->ce_vardata;
-			} else
-			{
-				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-				{
-					if (fromlistcnt == MAXFROMENTRIES)
-						break; // no room, sorry.
-					fromlist[fromlistcnt++] = cepp->ce_varname;
-				}
-			}
-		}
-		else if (!cep->ce_vardata)
-			continue; /* invalid */
-		else if (!strcmp(cep->ce_varname, "password"))
-		{
-			password = cep->ce_vardata;
-			if (cep->ce_entries)
-				password_type = cep->ce_entries->ce_varname;
-		}
-		else if (!strcmp(cep->ce_varname, "require-modes"))
-		{
-			require_modes = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "class"))
-		{
-			class = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "swhois"))
-		{
-			swhois = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "snomasks"))
-		{
-			snomask = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "modes"))
-		{
-			modes = cep->ce_vardata;
-		}
-		else if (!strcmp(cep->ce_varname, "maxlogins"))
-		{
-			maxlogins = atoi(cep->ce_vardata);
-		}
-	}
-
-	if ((fromlistcnt == 0) || !password || !class)
-		return 0; /* missing 3.2.x items in allow block (invalid or upgraded already) */
-
-	/* build oper::mask list in 'maskbuf' (includes variable name) */
-	if (fromlistcnt == 1)
-	{
-		char *m = !strncmp(fromlist[0], "*@", 2) ? fromlist[0]+2 : fromlist[0]; /* skip or don't skip the user@ part */
-		snprintf(maskbuf, sizeof(maskbuf), "mask %s;\n", m);
-	} else
-	{
-		/* Multiple (list of masks) */
-		int i;
-		snprintf(maskbuf, sizeof(maskbuf), "mask {\n");
-		
-		for (i=0; i < fromlistcnt; i++)
-		{
-			char *m = !strncmp(fromlist[i], "*@", 2) ? fromlist[i]+2 : fromlist[i]; /* skip or don't skip the user@ part */
-			snprintf(maskbuf+strlen(maskbuf), sizeof(maskbuf)-strlen(maskbuf), "\t%s;\n", m);
-		}
-		strlcat(maskbuf, "\t};\n", sizeof(maskbuf));
-	}
-	
-	/* Figure out which default operclass looks most suitable (="find highest rank") */
-	if (contains_flag(flags, flagscnt, "netadmin"))
-		operclass = "netadmin";
-	else if (contains_flag(flags, flagscnt, "services-admin"))
-		operclass = "services-admin";
-	else if (contains_flag(flags, flagscnt, "coadmin"))
-		operclass = "coadmin";
-	else if (contains_flag(flags, flagscnt, "admin"))
-		operclass = "admin";
-	else if (contains_flag(flags, flagscnt, "global"))
-		operclass = "globop";
-	else if (contains_flag(flags, flagscnt, "local"))
-		operclass = "locop";
-	else
-	{
-		/* Hmm :) */
-		config_status("WARNING: I have trouble converting oper block '%s' to the new system. "
-		              "I made it use the locop operclass. Feel free to change", name);
-		operclass = "locop";
-	}
-
-	if (contains_flag(flags, flagscnt, "get_host") || upgrade.host_on_oper_up)
-	{
-		if (!strcmp(operclass, "netadmin"))
-			vhost = upgrade.netadmin_host;
-		else if (!strcmp(operclass, "services-admin"))
-			vhost = upgrade.sadmin_host;
-		else if (!strcmp(operclass, "coadmin"))
-			vhost = upgrade.coadmin_host;
-		else if (!strcmp(operclass, "admin"))
-			vhost = upgrade.admin_host;
-		else if (!strcmp(operclass, "globop"))
-			vhost = upgrade.oper_host;
-		else if (!strcmp(operclass, "locop"))
-			vhost = upgrade.locop_host;
-	}
-
-	/* If no swhois is set, then set a title. Just because people are used to it. */
-	if (!swhois)
-	{
-		if (!strcmp(operclass, "netadmin"))
-			swhois = "is a Network Administrator";
-		else if (!strcmp(operclass, "services-admin"))
-			swhois = "is a Services Administrator";
-		else if (!strcmp(operclass, "coadmin"))
-			swhois = "is a Co Administrator";
-		else if (!strcmp(operclass, "admin"))
-			swhois = "is a Server Administrator";
-	}
-
-	/* The 'coadmin' operclass is actually 'admin'. There's no difference in privileges. */
-	if (!strcmp(operclass, "coadmin"))
-		operclass = "admin";
-	
-	/* convert globop and above w/override to operclassname-with-override */
-	if (contains_flag(flags, flagscnt, "can_override") && strcmp(operclass, "locop"))
-	{
-		snprintf(silly, sizeof(silly), "%s-with-override", operclass);
-		operclass = silly;
-	}
-
-	/* Ok, we got everything we need. Now we will write out the actual new oper block! */
-
-	/* oper block header & oper::mask */
-	snprintf(buf, sizeof(buf), "oper %s {\n"
-	                           "\t%s",
-	                           name,
-	                           maskbuf);
-
-	/* oper::password */
-	if (password_type)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\" { %s; };\n", password, password_type);
-	else
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tpassword \"%s\";\n", password);
-
-	/* oper::require-modes */
-	if (require_modes)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\trequire-modes \"%s\";\n", require_modes);
-
-	/* oper::maxlogins */
-	if (maxlogins != -1)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmaxlogins %d;\n", maxlogins);
-
-	/* oper::class */
-	snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tclass %s;\n", class);
-
-	/* oper::operclass */
-	snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\toperclass %s;\n", operclass);
-	
-	/* oper::modes */
-	if (modes)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tmodes \"%s\";\n", modes);
-
-	/* oper::snomask */
-	if (snomask)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tsnomask \"%s\";\n", snomask);
-
-	/* oper::vhost */
-	if (vhost)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tvhost \"%s\";\n", vhost);
-
-	/* oper::swhois */
-	if (swhois)
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "\tswhois \"%s\";\n", swhois);
-
-	strlcat(buf, "};\n", sizeof(buf));
-	
-	replace_section(ce, buf);
-	config_status("- oper block (%s) converted to new syntax", name);
-	return 1;
-}
-
-void update_read_settings(char *cfgfile)
-{
-	ConfigFile *cf = NULL;
-	ConfigEntry *ce = NULL, *cep, *cepp;
-
-	cf = config_load(cfgfile, NULL);
-	if (!cf)
-		return;
-		
-	if (strstr(cfgfile, "modules.default.conf"))
-		needs_modules_default_conf = 0;
-	else if (strstr(cfgfile, "operclass.default.conf"))
-		needs_operclass_default_conf = 0;
-
-	/* This needs to be read early, as the rest may depend on it */
-	for (ce = cf->cf_entries; ce; ce = ce->ce_next)
-	{
-		if (!strcmp(ce->ce_varname, "set"))
-		{
-			for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-			{
-				if (!strcmp(cep->ce_varname, "hosts"))
-				{
-					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					{
-						if (!cepp->ce_vardata)
-							continue;
-						if (!strcmp(cepp->ce_varname, "local")) {
-							safe_strdup(upgrade.locop_host, cepp->ce_vardata);
-						}
-						else if (!strcmp(cepp->ce_varname, "global")) {
-							safe_strdup(upgrade.oper_host, cepp->ce_vardata);
-						}
-						else if (!strcmp(cepp->ce_varname, "coadmin")) {
-							safe_strdup(upgrade.coadmin_host, cepp->ce_vardata);
-						}
-						else if (!strcmp(cepp->ce_varname, "admin")) {
-							safe_strdup(upgrade.admin_host, cepp->ce_vardata);
-						}
-						else if (!strcmp(cepp->ce_varname, "servicesadmin")) {
-							safe_strdup(upgrade.sadmin_host, cepp->ce_vardata);
-						}
-						else if (!strcmp(cepp->ce_varname, "netadmin")) {
-							safe_strdup(upgrade.netadmin_host, cepp->ce_vardata);
-						}
-						else if (!strcmp(cepp->ce_varname, "host-on-oper-up")) {
-							upgrade.host_on_oper_up = config_checkval(cepp->ce_vardata,CFG_YESNO);
-						}
-					}
-				}
-			}
-		}
-	}
-
-	config_free(cf);
-}
-
-
-int update_conf_file(void)
-{
-	ConfigFile *cf = NULL;
-	ConfigEntry *ce = NULL, *cep, *cepp;
-	int update_conf_runs = 0;
-
-again:
-	if (update_conf_runs++ > 100)
-	{
-		config_error("update conf re-run overflow. whoops! upgrade failed! sorry!");
-		return 0;
-	}
-	
-	if (cf)
-	{
-		config_free(cf);
-		cf = NULL;
-	}
-
-	cf = config_load(configfiletmp, NULL);
-	if (!cf)
-	{
-		config_error("could not load configuration file '%s'", configfile);
-		return 0;
-	}
-
-	for (ce = cf->cf_entries; ce; ce = ce->ce_next)
-	{
-		/*printf("%s%s%s\n",
-			ce->ce_varname,
-			ce->ce_vardata ? " " : "",
-			ce->ce_vardata ? ce->ce_vardata : ""); */
-		
-		if (!strcmp(ce->ce_varname, "loadmodule"))
-		{
-			if (upgrade_loadmodule(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "include"))
-		{
-			if (upgrade_include(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "me"))
-		{
-			if (upgrade_me_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "link"))
-		{
-			if (upgrade_link_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "oper"))
-		{
-			if (upgrade_oper_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "vhost"))
-		{
-			for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-			{
-				if (!strcmp(cep->ce_varname, "from"))
-				{
-					if (upgrade_from_subblock(cep))
-						goto again;
-				}
-			}
-		}
-		if (!strcmp(ce->ce_varname, "spamfilter"))
-		{
-			if (upgrade_spamfilter_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "allow") && !ce->ce_vardata) /* 'allow' block for clients, not 'allow channel' etc.. */
-		{
-			if (upgrade_allow_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "listen"))
-		{
-			if (upgrade_listen_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "cgiirc"))
-		{
-			if (upgrade_cgiirc_block(ce))
-				goto again;
-		}
-		if (!strcmp(ce->ce_varname, "set"))
-		{
-			for (cep = ce->ce_entries; cep; cep = cep->ce_next)
-			{
-				if (!strcmp(cep->ce_varname, "throttle"))
-				{
-					int n = 0, t = 0;
-					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-					{
-						if (!cepp->ce_vardata)
-							continue;
-						if (!strcmp(cepp->ce_varname, "period"))
-							t = config_checkval(cepp->ce_vardata, CFG_TIME);
-						else if (!strcmp(cepp->ce_varname, "connections"))
-							n = atoi(cepp->ce_vardata);
-					}
-
-					remove_section(cep->ce_fileposstart, cep->ce_fileposend);
-					snprintf(buf, sizeof(buf), "anti-flood { connect-flood %d:%d; };\n",
-					         n, t);
-						
-					insert_section(cep->ce_fileposstart, buf);
-					goto again;
-				} else
-				if (!strcmp(cep->ce_varname, "hosts"))
-				{
-					config_status("- removed set::hosts. we now use oper::vhost for this.");
-					remove_section(cep->ce_fileposstart, cep->ce_fileposend); /* hmm something is wrong here */
-					goto again;
-				} else
-				if (!strcmp(cep->ce_varname, "dns"))
-				{
-					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
-						if (!strcmp(cepp->ce_varname, "nameserver") ||
-						    !strcmp(cepp->ce_varname, "timeout") ||
-						    !strcmp(cepp->ce_varname, "retries"))
-						{
-							config_status("- removed set::dns::%s. this option is never used.", cepp->ce_varname);
-							remove_section(cepp->ce_fileposstart, cepp->ce_fileposend);
-							goto again;
-						}
-				}
-			}
-		}
-		
-	}	
-
-	if (cf)
-		config_free(cf);
-	
-	return (update_conf_runs > 1) ? 1 : 0;
-}
-
-static int already_included(char *fname, ConfigFile *cf)
-{
-	for (; cf; cf = cf->cf_next)
-		if (!strcmp(cf->cf_filename, fname))
-			return 1;
-
-	return 0;
-}
-
-static void add_include_list(char *fname, ConfigFile **cf)
-{
-	ConfigFile *n = safe_alloc(sizeof(ConfigFile));
-	
-//	config_status("INCLUDE: %s", fname);
-	safe_strdup(n->cf_filename, fname);
-	n->cf_next = *cf;
-	*cf = n;
-}
-
-void build_include_list_ex(char *fname, ConfigFile **cf_list)
-{
-	ConfigFile *cf;
-	ConfigEntry *ce;
-
-	if (strstr(fname, "://"))
-		return; /* Remote include - ignored */
-
-	add_include_list(fname, cf_list);
-
-	cf = config_load(fname, NULL);
-	if (!cf)
-		return;
-
-	for (ce = cf->cf_entries; ce; ce = ce->ce_next)
-		if (!strcmp(ce->ce_varname, "include"))
-		{
-			if ((ce->ce_vardata[0] != '/') && (ce->ce_vardata[0] != '\\') && strcmp(ce->ce_vardata, CPATH))
-			{
-				char *str = safe_alloc(strlen(ce->ce_vardata) + strlen(CONFDIR) + 4);
-				sprintf(str, "%s/%s", CONFDIR, ce->ce_vardata);
-				safe_free(ce->ce_vardata);
-				ce->ce_vardata = str;
-			}
-			if (!already_included(ce->ce_vardata, *cf_list))
-				build_include_list_ex(ce->ce_vardata, cf_list);
-		}
-	
-	config_free(cf);
-}
-
-ConfigFile *build_include_list(char *fname)
-{
-	ConfigFile *cf_list = NULL;
-	
-	build_include_list_ex(fname, &cf_list);
-	return cf_list;
-}
-
-void update_conf(void)
-{
-	ConfigFile *files;
-	ConfigFile *cf;
-	char *mainconf = configfile;
-	int upgraded_files = 0;
-	char answerbuf[128], *answer;
-
-	config_status("You have requested to upgrade your configuration files.");
-	config_status("If you are upgrading from 4.x to 5.x then DO NOT run this script. This script does NOT update config files from 4.x -> 5.x.");
-	config_status("UnrealIRCd 4.2.x configuration files should work OK on 5.x, with only some warnings printed when you boot the IRCd.");
-	config_status("See https://www.unrealircd.org/docs/Upgrading_from_4.x#Configuration_changes");
-	config_status("This upgrade-conf script is only useful if you are upgrading from 3.2.x.");
-	config_status("");
-#ifndef _WIN32
-	do
-	{
-		printf("Continue upgrading 3.2.x to 4.x configuration file format? (Y/N): ");
-		*answerbuf = '\0';
-		answer = fgets(answerbuf, sizeof(answerbuf), stdin);
-		if (answer && (toupper(*answer) == 'N'))
-		{
-			printf("Configuration unchanged.\n");
-			return;
-		}
-		if (answer && (toupper(*answer) == 'Y'))
-		{
-			break;
-		}
-		printf("Invalid response. Please enter either Y or N\n\n");
-	} while(1);
-#endif
-	
-	strlcpy(me.name, "<server>", sizeof(me.name));
-	memset(&upgrade, 0, sizeof(upgrade));
-
-	files = build_include_list(mainconf);
-
-	/* We need to read some original settings first, before we touch anything... */
-	for (cf = files; cf; cf = cf->cf_next)
-	{
-		update_read_settings(cf->cf_filename);
-	}
-	
-	/* Now go upgrade... */
-	for (cf = files; cf; cf = cf->cf_next)
-	{
-		if (!file_exists(cf->cf_filename))
-			continue; /* skip silently. errors were already shown earlier by build_include_list anyway. */
-		configfile = cf->cf_filename;
-		config_status("Checking '%s'...", cf->cf_filename);
-		snprintf(configfiletmp, sizeof(configfiletmp), "%s.tmp", configfile);
-		unlink(configfiletmp);
-		if (!unreal_copyfileex(configfile, configfiletmp, 0))
-		{
-			config_error("Could not create temp file for processing!");
-			die();
-		}
-		if (update_conf_file())
-		{
-			char buf[512];
-			snprintf(buf, sizeof(buf), "%s.old", configfile);
-			if (file_exists(buf))
-			{
-				int i;
-				for (i=0; i<100; i++)
-				{
-					snprintf(buf, sizeof(buf), "%s.old.%d", configfile, i);
-					if (!file_exists(buf))
-						break;
-				}
-			}
-			/* rename original config file to ... */
-			if (rename(configfile, buf) < 0)
-			{
-				config_error("Could not rename original conf '%s' to '%s'", configfile, buf);
-				die();
-			}
-			
-			/* Rename converted conf to config file */
-#ifdef _WIN32
-			/* "If newpath already exists it will be atomically replaced"..
-			 * well.. not on Windows! Error: "File exists"...
-			 */
-			unlink(configfile);
-#endif
-			if (rename(configfiletmp, configfile) < 0)
-			{
-				config_error("Could not rename converted configuration file '%s' to '%s' -- please rename this file yourself!",
-					configfiletmp, configfile);
-				die();
-			}
-			
-			config_status("File '%s' upgrade complete.", configfile);
-			upgraded_files++;
-		} else {
-			unlink(configfiletmp);
-			config_status("File '%s' left unchanged (no upgrade necessary)", configfile);
-		}
-	}
-	configfile = mainconf; /* restore */
-
-	if (needs_operclass_default_conf)
-	{
-		/* There's a slight chance we never added this include, and you get mysterious
-		 * oper permissions errors if you try to use such an operclass and it's missing.
-		 */
-		FILE *fd = fopen(mainconf, "a");
-		if (fd)
-		{
-			fprintf(fd, "\ninclude \"operclass.default.conf\";\n");
-			fclose(fd);
-			config_status("Oh wait, %s needs an include for operclass.default.conf. Added.", mainconf);
-			upgraded_files++;
-		}
-	}	
-	
-	if (upgraded_files > 0)
-	{
-		config_status("");
-		config_status("%d configuration file(s) upgraded. You can now boot UnrealIRCd with your freshly converted conf's!", upgraded_files);
-		config_status("You should probably take a look at the converted configuration files now or at a later time.");
-		config_status("See also https://www.unrealircd.org/docs/Upgrading_from_3.2.x and the sections in there (eg: Oper block)");
-		config_status("");
-	} else {
-		config_status("");
-		config_status("No configuration files were changed. No upgrade was needed. If this is incorrect then please report on https://bugs.unrealircd.org/ !");
-		config_status("");
-	}
-}
-
diff --git a/src/url.c b/src/url.c
@@ -1,458 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/url.c
- *   (C) 2003 Dominick Meglio and the UnrealIRCd Team
- *   (C) 2012 William Pitcock <nenolod@dereferenced.org>
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 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"
-
-extern char *SSLKeyPasswd;
-
-#ifndef _WIN32
-extern uid_t irc_uid;
-extern gid_t irc_gid;
-#endif
-
-CURLM *multihandle;
-
-/* Stores information about the async transfer.
- * Used to maintain information about the transfer
- * to trigger the callback upon completion.
- */
-typedef struct
-{
-	vFP callback;
-	void *callback_data;
-	FILE *fd;
-	char filename[PATH_MAX];
-	char *url; /*< must be free()d by url_do_transfers_async() */
-	char errorbuf[CURL_ERROR_SIZE];
-	time_t cachetime;
-} FileHandle;
-
-/*
- * Determines if the given string is a valid URL. Since libcurl
- * supports telnet, ldap, and dict such strings are treated as
- * invalid URLs here since we don't want them supported in
- * unreal.
- */
-int url_is_valid(const char *string)
-{
-	if (strstr(string, "telnet://") == string ||
-	    strstr(string, "ldap://") == string ||
-	    strstr(string, "dict://") == string)
-	{
-		return 0;
-	}
-	return (strstr(string, "://") != NULL);
-}
-
-/** A displayable URL for in error messages and such.
- * This leaves out any authentication information (user:pass)
- * the URL may contain.
- */
-const char *displayurl(const char *url)
-{
-	static char buf[512];
-	char *proto, *rest;
-
-	/* protocol://user:pass@host/etc.. */
-	rest = strchr(url, '@');
-
-	if (!rest)
-		return url; /* contains no auth information */
-
-	rest++; /* now points to the rest (remainder) of the URL */
-
-	proto = strstr(url, "://");
-	if (!proto || (proto > rest) || (proto == url))
-		return url; /* incorrectly formatted, just show entire URL. */
-
-	/* funny, we don't ship strlncpy.. */
-	*buf = '\0';
-	strlncat(buf, url, sizeof(buf), proto - url);
-	strlcat(buf, "://***:***@", sizeof(buf));
-	strlcat(buf, rest, sizeof(buf));
-
-	return buf;
-}
-
-/*
- * Returns the filename portion of the URL. The returned string
- * is malloc()'ed and must be freed by the caller. If the specified
- * URL does not contain a filename, a '-' is allocated and returned.
- */
-char *url_getfilename(const char *url)
-{
-	const char *c, *start;
-
-	if ((c = strstr(url, "://")))
-		c += 3;
-	else
-		c = url;
-
-	while (*c && *c != '/')
-		c++;
-
-	if (*c == '/')
-	{
-		c++;
-		if (!*c || *c == '?')
-			return raw_strdup("-");
-		start = c;
-		while (*c && *c != '?')
-			c++;
-		if (!*c)
-			return raw_strdup(start);
-		else
-			return raw_strldup(start, c-start+1);
-
-	}
-	return raw_strdup("-");
-}
-
-/*
- * Sets up all of the SSL options necessary to support HTTPS/FTPS
- * transfers.
- */
-static void set_curl_tls_options(CURL *curl)
-{
-	char buf[512];
-	
-#if 0
-	/* This would only be necessary if you use client certificates over HTTPS and such.
-	 * But this information is not known yet since the configuration file has not been
-	 * parsed yet at this point.
-	 */
-	curl_easy_setopt(curl, CURLOPT_SSLCERT, iConf.tls_options->certificate_file);
-	if (SSLKeyPasswd)
-		curl_easy_setopt(curl, CURLOPT_SSLKEYPASSWD, SSLKeyPasswd);
-	curl_easy_setopt(curl, CURLOPT_SSLKEY, iConf.tls_options->key_file);
-#endif
-
-	snprintf(buf, sizeof(buf), "%s/tls/curl-ca-bundle.crt", CONFDIR);
-	curl_easy_setopt(curl, CURLOPT_CAINFO, buf);
-}
-
-/*
- * Used by CURLOPT_WRITEFUNCTION to actually write the data to
- * a stream.
- */
-static size_t do_download(void *ptr, size_t size, size_t nmemb, void *stream)
-{
-	return fwrite(ptr, size, nmemb, (FILE *)stream);
-}
-
-/*
- * Handles synchronous downloading of a file. This function allows
- * a download to be made transparently without the caller having any
- * knowledge of how libcurl works. If the function succeeds, the
- * filename the file was downloaded to is returned. Otherwise NULL
- * is returned and the string pointed to by error contains the error
- * message. The returned filename is malloc'ed and must be freed by
- * the caller.
- */
-char *download_file(const char *url, char **error)
-{
-	static char errorbuf[CURL_ERROR_SIZE];
-	CURL *curl = curl_easy_init();
-	CURLcode res;
-	char *file = url_getfilename(url);
-	char *filename = unreal_getfilename(file);
-	char *tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf");
-	FILE *fd;
-
-
-	if (!curl)
-	{
-		safe_free(file);
-		strlcpy(errorbuf, "curl_easy_init() failed", sizeof(errorbuf));
-		*error = errorbuf;
-		return NULL;
-	}
-
-	fd = fopen(tmp, "wb");
-	if (!fd)
-	{
-		snprintf(errorbuf, CURL_ERROR_SIZE, "Cannot write to %s: %s", tmp, strerror(errno));
-		safe_free(file);
-		*error = errorbuf;
-		return NULL;
-	}
-	curl_easy_setopt(curl, CURLOPT_URL, url);
-	curl_easy_setopt(curl, CURLOPT_WRITEDATA, fd);
-	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, do_download);
-	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
-	curl_easy_setopt(curl, CURLOPT_FILETIME, 1);
- 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
- 	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 45);
- 	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15);
-#if LIBCURL_VERSION_NUM >= 0x070f01
- 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
- 	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);
-#endif
-	/* We need to set CURLOPT_FORBID_REUSE because otherwise libcurl does not
-	 * notify us (or not in time) about FD close/opens, thus we end up closing and
-	 * screwing up another innocent FD, like a listener (BAD!). In my view a bug, but
-	 * mailing list archives seem to indicate curl devs have a different opinion
-	 * on these matters...
-	 * Actually I don't know for sure if this option alone fixes 100% of the cases
-	 * but at least I can't crash my server anymore.
-	 * As a side-effect we also fix useless CLOSE_WAIT connections.
-	 */
-	curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
-
-	set_curl_tls_options(curl);
-	memset(errorbuf, 0, CURL_ERROR_SIZE);
-	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuf);
-	res = curl_easy_perform(curl);
-	fclose(fd);
-	safe_free(file);
-	if (res == CURLE_OK)
-	{
-		long last_mod;
-
-		curl_easy_getinfo(curl, CURLINFO_FILETIME, &last_mod);
-		curl_easy_cleanup(curl);
-
-		if (last_mod != -1)
-			unreal_setfilemodtime(tmp, last_mod);
-		return raw_strdup(tmp);
-	}
-	else
-	{
-		curl_easy_cleanup(curl);
-		remove(tmp);
-		*error = errorbuf;
-		return NULL;
-	}
-}
-
-/*
- * Interface for new-style evented I/O.
- *
- * url_socket_pollcb is the callback from our eventing system into
- * cURL.
- *
- * The other callbacks are for cURL notifying our event system what
- * it wants to do.
- */
-static void url_check_multi_handles(void)
-{
-	CURLMsg *msg;
-	int msgs_left;
-
-	while ((msg = curl_multi_info_read(multihandle, &msgs_left)) != NULL)
-	{
-		if (msg->msg == CURLMSG_DONE)
-		{
-			FileHandle *handle;
-			long code;
-			long last_mod;
-			CURL *easyhand = msg->easy_handle;
-
-			curl_easy_getinfo(easyhand, CURLINFO_RESPONSE_CODE, &code);
-			curl_easy_getinfo(easyhand, CURLINFO_PRIVATE, (char **) &handle);
-			curl_easy_getinfo(easyhand, CURLINFO_FILETIME, &last_mod);
-			fclose(handle->fd);
-
-			if (msg->data.result == CURLE_OK)
-			{
-				if (code == 304 || (last_mod != -1 && last_mod <= handle->cachetime))
-				{
-					handle->callback(handle->url, NULL, NULL, 1, handle->callback_data);
-					remove(handle->filename);
-				}
-				else
-				{
-					if (last_mod != -1)
-						unreal_setfilemodtime(handle->filename, last_mod);
-
-					handle->callback(handle->url, handle->filename, NULL, 0, handle->callback_data);
-					remove(handle->filename);
-				}
-			}
-			else
-			{
-				handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data);
-				remove(handle->filename);
-			}
-
-			safe_free(handle->url);
-			safe_free(handle);
-			curl_multi_remove_handle(multihandle, easyhand);
-
-			/* NOTE: after curl_multi_remove_handle() you cannot use
-			 * 'msg' anymore because it has freed by curl (as of v7.11.0),
-			 * therefore 'easyhand' is used... fun! -- Syzop
-			 */
-			curl_easy_cleanup(easyhand);
-		}
-	}
-}
-
-static void url_socket_pollcb(int fd, int revents, void *data)
-{
-	int flags = 0;
-	int dummy;
-
-	if (revents & FD_SELECT_READ)
-		flags |= CURL_CSELECT_IN;
-	if (revents & FD_SELECT_WRITE)
-		flags |= CURL_CSELECT_OUT;
-
-	curl_multi_socket_action(multihandle, fd, flags, &dummy);
-	url_check_multi_handles();
-}
-
-static int url_socket_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
-{
-	Debug((DEBUG_DEBUG, "url_socket_cb: %d (%s)", (int)s, (what == CURL_POLL_REMOVE)?"remove":"add-or-modify"));
-	if (what == CURL_POLL_REMOVE)
-	{
-		/* Socket is going to be closed *BY CURL*.. so don't call fd_close() but fd_unmap().
-		 * Otherwise we (or actually, they) may end up closing the wrong fd.
-		 */
-		fd_unmap(s);
-	}
-	else
-	{
-		FDEntry *fde = &fd_table[s];
-		int flags = 0;
-		
-		if (!fde->is_open)
-		{
-			fd_open(s, "CURL transfer");
-		}
-
-		if (what == CURL_POLL_IN || what == CURL_POLL_INOUT)
-			flags |= FD_SELECT_READ;
-
-		if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT)
-			flags |= FD_SELECT_WRITE;
-
-		fd_setselect(s, flags, url_socket_pollcb, NULL);
-	}
-
-	return 0;
-}
-
-/* Handle timeouts. */
-static EVENT(curl_socket_timeout)
-{
-	int dummy;
-
-	curl_multi_socket_action(multihandle, CURL_SOCKET_TIMEOUT, 0, &dummy);
-	url_check_multi_handles();
-}
-
-static Event *curl_socket_timeout_hdl = NULL;
-
-/*
- * Initializes the URL system
- */
-void url_init(void)
-{
-	curl_global_init(CURL_GLOBAL_ALL);
-	multihandle = curl_multi_init();
-
-	curl_multi_setopt(multihandle, CURLMOPT_SOCKETFUNCTION, url_socket_cb);
-	curl_socket_timeout_hdl = EventAdd(NULL, "curl_socket_timeout", curl_socket_timeout, NULL, 500, 0);
-}
-
-/*
- * Handles asynchronous downloading of a file. This function allows
- * a download to be made transparently without the caller having any
- * knowledge of how libcurl works. The specified callback function is
- * called when the download completes, or the download fails. The 
- * callback function is defined as:
- *
- * void callback(const char *url, const char *filename, char *errorbuf, int cached, void *data);
- *  - url will contain the original URL used to download the file.
- *  - filename will contain the name of the file (if successful, NULL on error or if cached).
- *    This file will be cleaned up after the callback returns, so save a copy to support caching.
- *  - errorbuf will contain the error message (if failed, NULL otherwise).
- *  - cached 1 if the specified cachetime is >= the current file on the server,
- *    if so, errorbuf will be NULL, filename will contain the path to the file.
- *  - data will be the value of callback_data, allowing you to figure
- *    out how to use the data contained in the downloaded file ;-).
- *    Make sure that if you access the contents of this pointer, you
- *    know that this pointer will persist. A download could take more
- *    than 10 seconds to happen and the config file can be rehashed
- *    multiple times during that time.
- */
-void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data)
-{
-	static char errorbuf[CURL_ERROR_SIZE];
-	CURL *curl = curl_easy_init();
-	if (curl)
-	{
-		char *file = url_getfilename(url);
-		char *filename = unreal_getfilename(file);
-		char *tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf");
-		FileHandle *handle = safe_alloc(sizeof(FileHandle));
-		handle->fd = fopen(tmp, "wb");
-		if (!handle->fd)
-		{
-			snprintf(errorbuf, sizeof(errorbuf), "Cannot create '%s': %s", tmp, strerror(ERRNO));
-			callback(url, NULL, errorbuf, 0, callback_data);
-			safe_free(file);
-			safe_free(handle);
-			return;
-		}
-		handle->callback = callback;
-		handle->callback_data = callback_data;
-		handle->cachetime = cachetime;
-		safe_strdup(handle->url, url);
-		strlcpy(handle->filename, tmp, sizeof(handle->filename));
-		safe_free(file);
-
-		curl_easy_setopt(curl, CURLOPT_URL, url);
-		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, do_download);
-		curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)handle->fd);
-		curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
-		set_curl_tls_options(curl);
-		memset(handle->errorbuf, 0, CURL_ERROR_SIZE);
-		curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, handle->errorbuf);
-		curl_easy_setopt(curl, CURLOPT_PRIVATE, (char *)handle);
-		curl_easy_setopt(curl, CURLOPT_FILETIME, 1);
-		/* We need to set CURLOPT_FORBID_REUSE because otherwise libcurl does not
-		 * notify us (or not in time) about FD close/opens, thus we end up closing and
-		 * screwing up another innocent FD, like a listener (BAD!). In my view a bug, but
-		 * mailing list archives seem to indicate curl devs have a different opinion
-		 * on these matters...
-		 * Actually I don't know for sure if this option alone fixes 100% of the cases
-		 * but at least I can't crash my server anymore.
-		 * As a side-effect we also fix useless CLOSE_WAIT connections.
-		 */
-		curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
-		if (cachetime)
-		{
-			curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
-			curl_easy_setopt(curl, CURLOPT_TIMEVALUE, cachetime);
-		}
-		curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
-		curl_easy_setopt(curl, CURLOPT_TIMEOUT, 45);
-		curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15);
-#if LIBCURL_VERSION_NUM >= 0x070f01
-		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
-		curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);
-#endif
-
-		curl_multi_add_handle(multihandle, curl);
-	}
-}
diff --git a/src/url_curl.c b/src/url_curl.c
@@ -0,0 +1,315 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/url.c
+ *   (C) 2003 Dominick Meglio and the UnrealIRCd Team
+ *   (C) 2004-2021 Bram Matthys <syzop@vulnscan.org>
+ *   (C) 2012 William Pitcock <nenolod@dereferenced.org>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 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"
+#include "dns.h"
+
+extern char *TLSKeyPasswd;
+
+/* Stores information about the async transfer.
+ * Used to maintain information about the transfer
+ * to trigger the callback upon completion.
+ */
+typedef struct Download Download;
+
+struct Download
+{
+	vFP callback;
+	void *callback_data;
+	FILE *file_fd;		/**< File open for writing (otherwise NULL) */
+	char filename[PATH_MAX];
+	char *url; /*< must be free()d by url_do_transfers_async() */
+	char errorbuf[CURL_ERROR_SIZE];
+	time_t cachetime;
+};
+
+CURLM *multihandle = NULL;
+
+void url_free_handle(Download *handle)
+{
+	if (handle->file_fd)
+		fclose(handle->file_fd);
+	safe_free(handle->url);
+	safe_free(handle);
+}
+
+/*
+ * Sets up all of the SSL options necessary to support HTTPS/FTPS
+ * transfers.
+ */
+static void set_curl_tls_options(CURL *curl)
+{
+	char buf[512];
+
+#if 0
+	/* This would only be necessary if you use client certificates over HTTPS and such.
+	 * But this information is not known yet since the configuration file has not been
+	 * parsed yet at this point.
+	 */
+	curl_easy_setopt(curl, CURLOPT_SSLCERT, iConf.tls_options->certificate_file);
+	if (TLSKeyPasswd)
+		curl_easy_setopt(curl, CURLOPT_SSLKEYPASSWD, TLSKeyPasswd);
+	curl_easy_setopt(curl, CURLOPT_SSLKEY, iConf.tls_options->key_file);
+#endif
+
+	snprintf(buf, sizeof(buf), "%s/tls/curl-ca-bundle.crt", CONFDIR);
+	curl_easy_setopt(curl, CURLOPT_CAINFO, buf);
+}
+
+/*
+ * Used by CURLOPT_WRITEFUNCTION to actually write the data to
+ * a stream.
+ */
+static size_t do_download(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+	return fwrite(ptr, size, nmemb, (FILE *)stream);
+}
+
+/*
+ * Interface for new-style evented I/O.
+ *
+ * url_socket_pollcb is the callback from our eventing system into
+ * cURL.
+ *
+ * The other callbacks are for cURL notifying our event system what
+ * it wants to do.
+ */
+static void url_check_multi_handles(void)
+{
+	CURLMsg *msg;
+	int msgs_left;
+
+	while ((msg = curl_multi_info_read(multihandle, &msgs_left)) != NULL)
+	{
+		if (msg->msg == CURLMSG_DONE)
+		{
+			Download *handle;
+			long code;
+			long last_mod;
+			CURL *easyhand = msg->easy_handle;
+
+			curl_easy_getinfo(easyhand, CURLINFO_RESPONSE_CODE, &code);
+			curl_easy_getinfo(easyhand, CURLINFO_PRIVATE, (char **) &handle);
+			curl_easy_getinfo(easyhand, CURLINFO_FILETIME, &last_mod);
+			fclose(handle->file_fd);
+			handle->file_fd = NULL;
+
+			if (msg->data.result == CURLE_OK)
+			{
+				if (code == 304 || (last_mod != -1 && last_mod <= handle->cachetime))
+				{
+					handle->callback(handle->url, NULL, NULL, 1, handle->callback_data);
+					remove(handle->filename);
+				}
+				else
+				{
+					if (last_mod != -1)
+						unreal_setfilemodtime(handle->filename, last_mod);
+
+					handle->callback(handle->url, handle->filename, NULL, 0, handle->callback_data);
+					remove(handle->filename);
+				}
+			}
+			else
+			{
+				handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data);
+				remove(handle->filename);
+			}
+
+			url_free_handle(handle);
+			curl_multi_remove_handle(multihandle, easyhand);
+
+			/* NOTE: after curl_multi_remove_handle() you cannot use
+			 * 'msg' anymore because it has freed by curl (as of v7.11.0),
+			 * therefore 'easyhand' is used... fun! -- Syzop
+			 */
+			curl_easy_cleanup(easyhand);
+		}
+	}
+}
+
+static void url_socket_pollcb(int fd, int revents, void *data)
+{
+	int flags = 0;
+	int dummy;
+
+	if (revents & FD_SELECT_READ)
+		flags |= CURL_CSELECT_IN;
+	if (revents & FD_SELECT_WRITE)
+		flags |= CURL_CSELECT_OUT;
+
+	curl_multi_socket_action(multihandle, fd, flags, &dummy);
+	url_check_multi_handles();
+}
+
+static int url_socket_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
+{
+	if (what == CURL_POLL_REMOVE)
+	{
+		fd_close(s);
+	}
+	else
+	{
+		FDEntry *fde = &fd_table[s];
+		int flags = 0;
+
+		if (!fde->is_open)
+		{
+			/* NOTE: We use FDCLOSE_NONE here because cURL will take
+			 * care of the closing of the socket. So *WE* must never
+			 * close the socket ourselves.
+			 */
+			fd_open(s, "CURL transfer", FDCLOSE_NONE);
+		}
+
+		if (what == CURL_POLL_IN || what == CURL_POLL_INOUT)
+			flags |= FD_SELECT_READ;
+
+		if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT)
+			flags |= FD_SELECT_WRITE;
+
+		fd_setselect(s, flags, url_socket_pollcb, NULL);
+	}
+
+	return 0;
+}
+
+/* Handle timeouts. */
+EVENT(url_socket_timeout)
+{
+	int dummy;
+
+	curl_multi_socket_action(multihandle, CURL_SOCKET_TIMEOUT, 0, &dummy);
+	url_check_multi_handles();
+}
+
+static Event *url_socket_timeout_hdl = NULL;
+
+/*
+ * Initializes the URL system
+ */
+void url_init(void)
+{
+	curl_global_init(CURL_GLOBAL_ALL);
+	multihandle = curl_multi_init();
+
+	curl_multi_setopt(multihandle, CURLMOPT_SOCKETFUNCTION, url_socket_cb);
+	url_socket_timeout_hdl = EventAdd(NULL, "url_socket_timeout", url_socket_timeout, NULL, 500, 0);
+}
+
+/*
+ * Handles asynchronous downloading of a file. This function allows
+ * a download to be made transparently without the caller having any
+ * knowledge of how libcurl works. The specified callback function is
+ * called when the download completes, or the download fails. The 
+ * callback function is defined as:
+ *
+ * void callback(const char *url, const char *filename, char *errorbuf, int cached, void *data);
+ *  - url will contain the original URL used to download the file.
+ *  - filename will contain the name of the file (if successful, NULL on error or if cached).
+ *    This file will be cleaned up after the callback returns, so save a copy to support caching.
+ *  - errorbuf will contain the error message (if failed, NULL otherwise).
+ *  - cached 1 if the specified cachetime is >= the current file on the server,
+ *    if so, errorbuf will be NULL, filename will contain the path to the file.
+ *  - data will be the value of callback_data, allowing you to figure
+ *    out how to use the data contained in the downloaded file ;-).
+ *    Make sure that if you access the contents of this pointer, you
+ *    know that this pointer will persist. A download could take more
+ *    than 10 seconds to happen and the config file can be rehashed
+ *    multiple times during that time.
+ */
+void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects)
+{
+	static char errorbuf[CURL_ERROR_SIZE];
+	char user_agent[256];
+	CURL *curl;
+	char *file;
+	const char *filename;
+	char *tmp;
+	Download *handle;
+
+	curl = curl_easy_init();
+	if (!curl)
+	{
+		unreal_log(ULOG_ERROR, "main", "CURL_INTERNAL_FAILURE", NULL,
+		           "Could not initialize curl handle. Maybe out of memory/resources?");
+		snprintf(errorbuf, sizeof(errorbuf), "Could not initialize curl handle");
+		return;
+	}
+
+	file = url_getfilename(url);
+	filename = unreal_getfilename(file);
+	tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf");
+
+	handle = safe_alloc(sizeof(Download));
+	handle->file_fd = fopen(tmp, "wb");
+	if (!handle->file_fd)
+	{
+		snprintf(errorbuf, sizeof(errorbuf), "Cannot create '%s': %s", tmp, strerror(ERRNO));
+		callback(url, NULL, errorbuf, 0, callback_data);
+		safe_free(file);
+		safe_free(handle);
+		return;
+	}
+
+	handle->callback = callback;
+	handle->callback_data = callback_data;
+	handle->cachetime = cachetime;
+	safe_strdup(handle->url, url);
+	strlcpy(handle->filename, tmp, sizeof(handle->filename));
+	safe_free(file);
+
+	curl_easy_setopt(curl, CURLOPT_URL, url);
+	snprintf(user_agent, sizeof(user_agent), "UnrealIRCd %s", VERSIONONLY);
+	curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, do_download);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)handle->file_fd);
+	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+	set_curl_tls_options(curl);
+	memset(handle->errorbuf, 0, CURL_ERROR_SIZE);
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, handle->errorbuf);
+	curl_easy_setopt(curl, CURLOPT_PRIVATE, (char *)handle);
+	curl_easy_setopt(curl, CURLOPT_FILETIME, 1);
+	/* We need to set CURLOPT_FORBID_REUSE because otherwise libcurl does not
+	 * notify us (or not in time) about FD close/opens, thus we end up closing and
+	 * screwing up another innocent FD, like a listener (BAD!). In my view a bug, but
+	 * mailing list archives seem to indicate curl devs have a different opinion
+	 * on these matters...
+	 * Actually I don't know for sure if this option alone fixes 100% of the cases
+	 * but at least I can't crash my server anymore.
+	 * As a side-effect we also fix useless CLOSE_WAIT connections.
+	 */
+	curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
+	if (cachetime)
+	{
+		curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
+		curl_easy_setopt(curl, CURLOPT_TIMEVALUE, cachetime);
+	}
+	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, DOWNLOAD_TRANSFER_TIMEOUT);
+	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, DOWNLOAD_CONNECT_TIMEOUT);
+#if LIBCURL_VERSION_NUM >= 0x070f01
+	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
+	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, maxredirects);
+#endif
+
+	curl_multi_add_handle(multihandle, curl);
+}
diff --git a/src/url_unreal.c b/src/url_unreal.c
@@ -0,0 +1,1068 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/url.c
+ *   (C) 2021 Bram Matthys 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"
+#include "dns.h"
+
+/* 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.
+ */
+typedef struct Download Download;
+
+struct Download
+{
+	Download *prev, *next;
+	vFP callback;
+	void *callback_data;
+	FILE *file_fd;		/**< File open for writing (otherwise NULL) */
+	char filename[PATH_MAX];
+	char *url; /*< must be free()d by url_do_transfers_async() */
+	char errorbuf[512];
+	time_t cachetime;
+	char *hostname;		/**< Parsed hostname (from 'url') */
+	int port;		/**< Parsed port (from 'url') */
+	char *username;
+	char *password;
+	char *document;		/**< Parsed document (from 'url') */
+	char *ip;		/**< Resolved IP */
+	int ipv6;
+	SSL *ssl;
+	int fd;			/**< Socket */
+	int connected;
+	int got_response;
+	int http_status_code;
+	char *lefttoparse;
+	long long lefttoparselen; /* size of data in lefttoparse (note: not used for first header parsing) */
+	time_t last_modified;
+	time_t download_started;
+	int dns_refcnt;
+	TransferEncoding transfer_encoding;
+	long chunk_remaining;
+	/* for redirects: */
+	int redirects_remaining;
+	char *redirect_new_location;
+	char *redirect_original_url;
+};
+
+/* Variables */
+Download *downloads = NULL;
+SSL_CTX *https_ctx = NULL;
+
+/* Forward declarations */
+void url_resolve_cb(void *arg, int status, int timeouts, struct hostent *he);
+void unreal_https_initiate_connect(Download *handle);
+int url_parse(const char *url, char **host, int *port, char **username, char **password, char **document);
+SSL_CTX *https_new_ctx(void);
+void unreal_https_connect_handshake(int fd, int revents, void *data);
+int https_connect(Download *handle);
+int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle);
+void https_connect_send_header(Download *handle);
+void https_receive_response(int fd, int revents, void *data);
+int https_handle_response_header(Download *handle, char *readbuf, int n);
+int https_handle_response_file(Download *handle, char *readbuf, int n);
+void https_done(Download *handle);
+void https_done_cached(Download *handle);
+void https_redirect(Download *handle);
+int https_parse_header(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request);
+char *url_find_end_of_request(char *header, int totalsize, int *remaining_bytes);
+void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)  __attribute__((format(printf,2,3)));
+
+void url_free_handle(Download *handle)
+{
+	DelListItem(handle, downloads);
+	if (handle->fd > 0)
+	{
+		fd_close(handle->fd);
+		fd_unnotify(handle->fd);
+	}
+	if (handle->file_fd)
+		fclose(handle->file_fd);
+	safe_free(handle->url);
+	safe_free(handle->hostname);
+	safe_free(handle->username);
+	safe_free(handle->password);
+	safe_free(handle->document);
+	safe_free(handle->ip);
+	if (handle->ssl)
+		SSL_free(handle->ssl);
+	safe_free(handle->lefttoparse);
+	safe_free(handle->redirect_new_location);
+	safe_free(handle->redirect_original_url);
+	safe_free(handle);
+}
+
+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);
+	url_free_handle(handle);
+}
+
+void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects)
+{
+	char *file;
+	const char *filename;
+	char *tmp;
+	Download *handle = NULL;
+	int ipv6 = 0;
+	char *host;
+	int port;
+	char *username;
+	char *password;
+	char *document;
+
+	handle = safe_alloc(sizeof(Download));
+	handle->download_started = TStime();
+	handle->callback = callback;
+	handle->callback_data = callback_data;
+	handle->cachetime = cachetime;
+	safe_strdup(handle->url, url);
+	safe_strdup(handle->redirect_original_url, original_url);
+	handle->redirects_remaining = maxredirects;
+	AddListItem(handle, downloads);
+
+	if (strncmp(url, "https://", 8))
+	{
+		https_cancel(handle, "Only https:// is supported (either rebuild UnrealIRCd with curl support or use https)");
+		return;
+	}
+	if (!url_parse(url, &host, &port, &username, &password, &document))
+	{
+		https_cancel(handle, "Failed to parse HTTP url");
+		return;
+	}
+
+	safe_strdup(handle->hostname, host);
+	handle->port = port;
+	safe_strdup(handle->username, username);
+	safe_strdup(handle->password, password);
+	safe_strdup(handle->document, document);
+
+	file = url_getfilename(url);
+	filename = unreal_getfilename(file);
+	tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf");
+
+	handle->file_fd = fopen(tmp, "wb");
+	if (!handle->file_fd)
+	{
+		https_cancel(handle, "Cannot create '%s': %s", tmp, strerror(ERRNO));
+		safe_free(file);
+		return;
+	}
+
+	strlcpy(handle->filename, tmp, sizeof(handle->filename));
+	safe_free(file);
+
+
+	// todo: allocate handle, select en weetikt allemaal
+	// add to some global struct linkedlist, for timeouts
+	// register in i/o
+
+	if (is_valid_ip(handle->hostname))
+	{
+		/* Nothing to resolve, eg https://127.0.0.1/ */
+		safe_strdup(handle->ip, handle->hostname);
+		unreal_https_initiate_connect(handle);
+	} else {
+		/* Hostname, so start resolving... */
+		handle->dns_refcnt++;
+		ares_gethostbyname(resolver_channel, handle->hostname, AF_INET, url_resolve_cb, handle);
+		// TODO: check return value?
+	}
+}
+
+void url_resolve_cb(void *arg, int status, int timeouts, struct hostent *he)
+{
+	Download *handle = (Download *)arg;
+	int n;
+	struct hostent *he2;
+	char ipbuf[HOSTLEN+1];
+	const char *ip = NULL;
+
+	handle->dns_refcnt--;
+
+	if ((status != 0) || !he->h_addr_list || !he->h_addr_list[0])
+	{
+		https_cancel(handle, "Unable to resolve hostname '%s'", handle->hostname);
+		return;
+	}
+
+	if (!he->h_addr_list[0] || (he->h_length != (handle->ipv6 ? 16 : 4)) ||
+	    !(ip = inetntop(handle->ipv6 ? AF_INET6 : AF_INET, he->h_addr_list[0], ipbuf, sizeof(ipbuf))))
+	{
+		/* Illegal response -- fatal */
+		https_cancel(handle, "Unable to resolve hostname '%s'", handle->hostname);
+		return;
+	}
+
+	/* Ok, since we got here, it seems things were actually succesfull */
+
+	safe_strdup(handle->ip, ip);
+
+	unreal_https_initiate_connect(handle);
+}
+
+void unreal_https_initiate_connect(Download *handle)
+{
+	// todo: allocate handle, select en weetikt allemaal
+	// add to some global struct linkedlist, for timeouts
+	// register in i/o
+
+	if (!handle->ip)
+	{
+		https_cancel(handle, "No IP address found to connect to");
+		return;
+	}
+
+	handle->fd = fd_socket(handle->ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "HTTPS");
+	if (handle->fd < 0)
+	{
+		https_cancel(handle, "Could not create socket: %s", strerror(ERRNO));
+		return;
+	}
+	set_sock_opts(handle->fd, NULL, handle->ipv6);
+	if (!unreal_connect(handle->fd, handle->ip, handle->port, handle->ipv6))
+	{
+		https_cancel(handle, "Could not connect: %s", strerror(ERRNO));
+		return;
+	}
+
+	fd_setselect(handle->fd, FD_SELECT_WRITE, unreal_https_connect_handshake, handle);
+}
+
+// based on unreal_tls_client_handshake()
+void unreal_https_connect_handshake(int fd, int revents, void *data)
+{
+	Download *handle = data;
+	handle->ssl = SSL_new(https_ctx);
+	if (!handle->ssl)
+	{
+		https_cancel(handle, "Failed to setup SSL");
+		return;
+	}
+	SSL_set_fd(handle->ssl, handle->fd);
+	SSL_set_connect_state(handle->ssl);
+	SSL_set_nonblocking(handle->ssl);
+	SSL_set_tlsext_host_name(handle->ssl, handle->hostname);
+
+	if (https_connect(handle) < 0)
+	{
+		/* Some fatal error already */
+		https_cancel(handle, "TLS_connect() failed early");
+		return;
+	}
+
+	/* Is now connecting... */
+}
+
+SSL_CTX *https_new_ctx(void)
+{
+	SSL_CTX *ctx_client;
+	char buf1[512], buf2[512];
+	char *curl_ca_bundle = buf1;
+
+	SSL_load_error_strings();
+	SSLeay_add_ssl_algorithms();
+
+	ctx_client = SSL_CTX_new(SSLv23_client_method());
+	if (!ctx_client)
+		return NULL;
+#ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION
+	SSL_CTX_set_min_proto_version(ctx_client, TLS1_2_VERSION);
+#endif
+	SSL_CTX_set_options(ctx_client, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1);
+
+	/* Verify peer certificate */
+	snprintf(buf1, sizeof(buf1), "%s/tls/curl-ca-bundle.crt", CONFDIR);
+	if (!file_exists(buf1))
+	{
+		snprintf(buf2, sizeof(buf2), "%s/doc/conf/tls/curl-ca-bundle.crt", BUILDDIR);
+		if (!file_exists(buf2))
+		{
+			unreal_log(ULOG_ERROR, "url", "CA_BUNDLE_NOT_FOUND", NULL,
+			           "Neither $filename1 nor $filename2 exist.\n"
+			           "Cannot use built-in https client without curl-ca-bundle.crt\n",
+			           log_data_string("filename1", buf1),
+			           log_data_string("filename2", buf2));
+			exit(-1);
+		}
+		curl_ca_bundle = buf2;
+	}
+	SSL_CTX_load_verify_locations(ctx_client, curl_ca_bundle, NULL);
+	SSL_CTX_set_verify(ctx_client, SSL_VERIFY_PEER, NULL);
+
+	/* Limit ciphers as well */
+	SSL_CTX_set_cipher_list(ctx_client, UNREALIRCD_DEFAULT_CIPHERS);
+
+	return ctx_client;
+}
+
+// Based on unreal_tls_connect_retry
+void https_connect_retry(int fd, int revents, void *data)
+{
+	Download *handle = data;
+	https_connect(handle);
+}
+
+// Based on unreal_tls_connect()
+int https_connect(Download *handle)
+{
+	int ssl_err;
+	char *errstr;
+
+	if ((ssl_err = SSL_connect(handle->ssl)) <= 0)
+	{
+		ssl_err = SSL_get_error(handle->ssl, ssl_err);
+		switch(ssl_err)
+		{
+			case SSL_ERROR_SYSCALL:
+				if (ERRNO == P_EINTR || ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN)
+				{
+					/* Hmmm. This implementation is different than in unreal_tls_accept().
+					 * One of them must be wrong -- better check! (TODO)
+					 */
+					fd_setselect(handle->fd, FD_SELECT_READ|FD_SELECT_WRITE, https_connect_retry, handle);
+					return 0;
+				}
+				return https_fatal_tls_error(ssl_err, ERRNO, handle);
+			case SSL_ERROR_WANT_READ:
+				fd_setselect(handle->fd, FD_SELECT_READ, https_connect_retry, handle);
+				fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle);
+				return 0;
+			case SSL_ERROR_WANT_WRITE:
+				fd_setselect(handle->fd, FD_SELECT_READ, NULL, handle);
+				fd_setselect(handle->fd, FD_SELECT_WRITE, https_connect_retry, handle);
+				return 0;
+			default:
+				return https_fatal_tls_error(ssl_err, ERRNO, handle);
+		}
+		/* NOTREACHED */
+		return -1;
+	}
+
+	/* We are connected now. */
+
+	if (!verify_certificate(handle->ssl, handle->hostname, &errstr))
+	{
+		https_cancel(handle, "TLS Certificate error for server: %s", errstr);
+		return -1;
+	}
+	https_connect_send_header(handle);
+	return 1;
+}
+
+/**
+ * Report a fatal TLS error and terminate the download.
+ *
+ * @param ssl_error The error as from OpenSSL.
+ * @param where The location, one of the SAFE_SSL_* defines.
+ * @param my_errno A preserved value of errno to pass to ssl_error_str().
+ * @param client The client the error is associated with.
+ */
+int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle)
+{
+	const char *ssl_errstr;
+	unsigned long additional_errno = ERR_get_error();
+	char additional_info[256];
+	const char *one, *two;
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	/* Fetch additional error information from OpenSSL 3.0.0+ */
+	two = ERR_reason_error_string(additional_errno);
+	if (two && *two)
+	{
+		snprintf(additional_info, sizeof(additional_info), ": %s", two);
+	} else {
+		*additional_info = '\0';
+	}
+#else
+	/* Fetch additional error information from OpenSSL. This is new as of Nov 2017 (4.0.16+) */
+	one = ERR_func_error_string(additional_errno);
+	two = ERR_reason_error_string(additional_errno);
+	if (one && *one && two && *two)
+	{
+		snprintf(additional_info, sizeof(additional_info), ": %s: %s", one, two);
+	} else {
+		*additional_info = '\0';
+	}
+#endif
+
+	ssl_errstr = ssl_error_str(ssl_error, my_errno);
+
+	https_cancel(handle, "%s [%s]", ssl_errstr, additional_info);
+	return -1;
+}
+
+// copied 100% from modulemanager parse_url()
+int url_parse(const char *url, char **hostname, int *port, char **username, char **password, char **document)
+{
+	char *p, *p2;
+	static char hostbuf[256];
+	static char documentbuf[512];
+
+	*hostname = *username = *password = *document = NULL;
+	*port = 443;
+
+	if (strncmp(url, "https://", 8))
+		return 0;
+	url += 8; /* skip over https:// part */
+
+	p = strchr(url, '/');
+	if (!p)
+		return 0;
+
+	strlncpy(hostbuf, url, sizeof(hostbuf), p - url);
+
+	strlcpy(documentbuf, p, sizeof(documentbuf));
+
+	*hostname = hostbuf;
+	*document = documentbuf;
+
+	/* Actually we may still need to extract the port */
+	p = strchr(hostbuf, '@');
+	if (p)
+	{
+		*p++ = '\0';
+
+		*username = hostbuf;
+		p2 = strchr(hostbuf, ':');
+		if (p2)
+		{
+			*p2++ = '\0';
+			*password = p2;
+		}
+		*hostname = p;
+	}
+	p = strchr(*hostname, ':');
+	if (p)
+	{
+		*p++ = '\0';
+		*port = atoi(p);
+	}
+
+	return 1;
+}
+
+void https_connect_send_header(Download *handle)
+{
+	char buf[1024];
+	char hostandport[512];
+	int ssl_err;
+	char *host;
+	int port;
+	char *document;
+
+	handle->connected = 1;
+	snprintf(hostandport, sizeof(hostandport), "%s:%d", handle->hostname, handle->port);
+
+	/* Prepare the header */
+	snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\n"
+	                    "User-Agent: UnrealIRCd %s\r\n"
+	                    "Host: %s\r\n"
+	                    "Connection: close\r\n",
+	                    handle->document,
+	                    VERSIONONLY,
+	                    hostandport);
+	if (handle->username && handle->password)
+	{
+		char wbuf[128];
+		char obuf[256];
+		char header[512];
+
+		snprintf(wbuf, sizeof(wbuf), "%s:%s", handle->username, handle->password);
+		if (b64_encode(wbuf, strlen(wbuf), obuf, sizeof(obuf)-1) > 0)
+		{
+			snprintf(header, sizeof(header), "Authorization: Basic %s\r\n", obuf);
+			strlcat(buf, header, sizeof(buf));
+		}
+	}
+	if (handle->cachetime > 0)
+	{
+		const char *datestr = rfc2616_time(handle->cachetime);
+		if (datestr)
+		{
+			// snprintf_append...
+			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
+				 "If-Modified-Since: %s\r\n", datestr);
+		}
+	}
+	strlcat(buf, "\r\n", sizeof(buf));
+
+	ssl_err = SSL_write(handle->ssl, buf, strlen(buf));
+	if (ssl_err < 0)
+	{
+		https_fatal_tls_error(ssl_err, ERRNO, handle);
+		return;
+	}
+	fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle);
+	fd_setselect(handle->fd, FD_SELECT_READ, https_receive_response, handle);
+}
+
+void https_receive_response(int fd, int revents, void *data)
+{
+	Download *handle = data;
+	int n;
+	char readbuf[2048];
+
+	n = SSL_read(handle->ssl, readbuf, sizeof(readbuf)-1);
+	if (n == 0)
+	{
+		/* Graceful close */
+		https_done(handle);
+		return;
+	}
+	if (n < 0)
+	{
+		int ssl_err = SSL_get_error(handle->ssl, n);
+		switch (ssl_err)
+		{
+			case SSL_ERROR_WANT_WRITE:
+				fd_setselect(fd, FD_SELECT_READ, NULL, handle);
+				fd_setselect(fd, FD_SELECT_WRITE, https_receive_response, handle);
+				return;
+			case SSL_ERROR_WANT_READ:
+				/* Wants to read more data; let it call us next time again */
+				return;
+			case SSL_ERROR_SYSCALL:
+			case SSL_ERROR_SSL:
+			default:
+				https_fatal_tls_error(ssl_err, ERRNO, handle);
+				return;
+		}
+		return;
+	}
+	readbuf[n] = '\0';
+
+	//fprintf(stderr, "Got: '%s'\n", readbuf);
+
+	if (!handle->got_response)
+	{
+		https_handle_response_header(handle, readbuf, n);
+		return;
+	} else
+	if (handle->got_response)
+	{
+		if (!https_handle_response_file(handle, readbuf, n))
+			return; /* handle is already freed! */
+	}
+}
+
+// Based on websocket_handle_handshake()
+int https_handle_response_header(Download *handle, char *readbuf, int n)
+{
+	char *key, *value;
+	int r, end_of_request;
+	char netbuf[4096], netbuf2[4096];
+	char *lastloc = NULL;
+	int maxcopy, nprefix=0;
+	int totalsize;
+
+	/* Yeah, totally paranoid: */
+	memset(netbuf, 0, sizeof(netbuf));
+	memset(netbuf2, 0, sizeof(netbuf2));
+
+	/** Frame re-assembling starts here **/
+	*netbuf = '\0';
+	if (handle->lefttoparse)
+	{
+		strlcpy(netbuf, handle->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().)
+	 */
+	if (n > maxcopy)
+		n = maxcopy;
+	if (n <= 0)
+	{
+		https_cancel(handle, "Oversized line in HTTP response");
+		return 0;
+	}
+	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(handle->lefttoparse);
+
+	/** Now step through the lines.. **/
+	for (r = https_parse_header(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
+	     r;
+	     r = https_parse_header(NULL, 0, &key, &value, &lastloc, &end_of_request))
+	{
+		// do something actually with the header here ;)
+		if (!strcasecmp(key, "RESPONSE"))
+		{
+			handle->http_status_code = atoi(value);
+			if (handle->http_status_code == 304)
+			{
+				/* 304 Not Modified: cache hit */
+				https_done_cached(handle);
+				return 0;
+			}
+			else if ((handle->http_status_code >= 301) && (handle->http_status_code <= 308))
+			{
+				/* Redirect */
+				if (handle->redirects_remaining == 0)
+				{
+					https_cancel(handle, "Too many HTTP redirects (%d)", DOWNLOAD_MAX_REDIRECTS);
+					return 0;
+				}
+				/* Let it continue.. we handle it later, as we need to
+				 * receive the "Location" header as well.
+				 */
+			}
+			else if (handle->http_status_code != 200)
+			{
+				/* HTTP Failure code */
+				https_cancel(handle, "HTTP Error: %s", value);
+				return 0;
+			}
+		} else
+		if (!strcasecmp(key, "Last-Modified") && value)
+		{
+			handle->last_modified = rfc2616_time_to_unix_time(value);
+		} else
+		if (!strcasecmp(key, "Location") && value)
+		{
+			safe_strdup(handle->redirect_new_location, value);
+		} else
+		if (!strcasecmp(key, "Transfer-Encoding") && value)
+		{
+			if (value && !strcasecmp(value, "chunked"))
+				handle->transfer_encoding = TRANSFER_ENCODING_CHUNKED;
+		}
+		//fprintf(stderr, "\nHEADER '%s'\n\n", key);
+	}
+
+	if (end_of_request)
+	{
+		int remaining_bytes = 0;
+		char *nextframe;
+
+		safe_free(handle->lefttoparse);
+		handle->got_response = 1;
+
+		if (handle->http_status_code == 0)
+		{
+			https_cancel(handle, "Invalid HTTP response");
+			return 0;
+		}
+		if (handle->http_status_code != 200)
+		{
+			if (handle->redirect_new_location)
+			{
+				https_redirect(handle);
+				return 0; /* this old request dies */
+			} else {
+				https_cancel(handle, "HTTP Redirect encountered but no URL specified!?");
+				return 0;
+			}
+		}
+
+		nextframe = url_find_end_of_request(netbuf2, totalsize, &remaining_bytes);
+		if (nextframe)
+		{
+			if (!https_handle_response_file(handle, nextframe, remaining_bytes))
+				return 0;
+		}
+	}
+
+	if (lastloc)
+	{
+		/* Last line was cut somewhere, save it for next round. */
+		safe_strdup(handle->lefttoparse, lastloc);
+	}
+
+	return 1;
+}
+
+int https_handle_response_file(Download *handle, char *readbuf, int pktsize)
+{
+	char *buf;
+	long long n;
+	char *free_this_buffer = NULL;
+
+	// TODO we fail to check for write errors ;)
+	// TODO: Makes sense to track if we got everything? :D
+
+	if (handle->transfer_encoding == TRANSFER_ENCODING_NONE)
+	{
+		/* Ohh.. so easy! */
+		fwrite(readbuf, 1, pktsize, handle->file_fd);
+		return 1;
+	}
+
+	/* Fill 'buf' nd set 'buflen' with what we had + what we have now.
+	 * Makes things easy.
+	 */
+	if (handle->lefttoparse)
+	{
+		n = handle->lefttoparselen + pktsize;
+		free_this_buffer = buf = safe_alloc(n);
+		memcpy(buf, handle->lefttoparse, handle->lefttoparselen);
+		memcpy(buf+handle->lefttoparselen, readbuf, pktsize);
+		safe_free(handle->lefttoparse);
+		handle->lefttoparselen = 0;
+	} else {
+		n = pktsize;
+		buf = readbuf;
+	}
+
+	/* Chunked transfers.. yayyyy.. */
+	while (n > 0)
+	{
+		if (handle->chunk_remaining > 0)
+		{
+			/* Eat it */
+			int eat = MIN(handle->chunk_remaining, n);
+			fwrite(buf, 1, eat, handle->file_fd);
+			n -= eat;
+			buf += eat;
+			handle->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.. */
+					handle->lefttoparselen = n;
+					handle->lefttoparse = safe_alloc(n);
+					memcpy(handle->lefttoparse, buf, n);
+				}
+				safe_free(free_this_buffer);
+				return 1; /* WE WANT MORE! */
+			}
+			buf[i] = '\0'; /* cut at LF */
+			i++; /* point to next data */
+			handle->chunk_remaining = strtol(buf, NULL, 16);
+			if (handle->chunk_remaining < 0)
+			{
+				https_cancel(handle, "Negative chunk encountered (%ld)", handle->chunk_remaining);
+				safe_free(free_this_buffer);
+				return 0;
+			}
+			if (handle->chunk_remaining == 0)
+			{
+				https_done(handle);
+				safe_free(free_this_buffer);
+				return 0;
+			}
+			buf += i;
+			n -= i;
+		}
+	}
+
+	safe_free(free_this_buffer);
+	return 1;
+}
+
+void https_done(Download *handle)
+{
+	char *url = handle->redirect_original_url ? handle->redirect_original_url : handle->url;
+
+	fclose(handle->file_fd);
+	handle->file_fd = NULL;
+
+	if (!handle->got_response)
+		handle->callback(url, NULL, "HTTPS response not received", 0, handle->callback_data);
+	else
+	{
+		if (handle->last_modified > 0)
+			unreal_setfilemodtime(handle->filename, handle->last_modified);
+		handle->callback(url, handle->filename, NULL, 0, handle->callback_data);
+	}
+	url_free_handle(handle);
+	return;
+}
+
+void https_done_cached(Download *handle)
+{
+	char *url = handle->redirect_original_url ? handle->redirect_original_url : handle->url;
+
+	fclose(handle->file_fd);
+	handle->file_fd = NULL;
+	handle->callback(url, NULL, NULL, 1, handle->callback_data);
+	url_free_handle(handle);
+}
+
+void https_redirect(Download *handle)
+{
+	if (handle->redirects_remaining == 0)
+	{
+		https_cancel(handle, "Too many HTTP redirects (%d)", DOWNLOAD_MAX_REDIRECTS);
+		return;
+	}
+	handle->redirects_remaining--;
+
+	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);
+}
+
+/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
+int https_parse_header(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;
+		// new compared to websocket handling:
+		if (*p == '\n')
+			*lastloc = p + 1;
+		else
+			*lastloc = p + 2;
+		return 0;
+	}
+
+	/* Note: p *could* point to the NUL byte ('\0') */
+
+	/* Special handling for response line itself. */
+	if (!strncmp(p, "HTTP/1", 6) && (strlen(p)>=13))
+	{
+		k = "RESPONSE";
+		p += 9;
+		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;
+			}
+		}
+		*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 *url_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 timeouts. */
+EVENT(url_socket_timeout)
+{
+	Download *d, *d_next;
+	for (d = downloads; d; d = d_next)
+	{
+		d_next = d->next;
+		if (d->dns_refcnt)
+			continue; /* can't touch this... */
+		if (!d->connected && (TStime() - d->download_started > DOWNLOAD_CONNECT_TIMEOUT))
+		{
+			https_cancel(d, "Connect or DNS timeout after %ld seconds", (long)DOWNLOAD_CONNECT_TIMEOUT);
+			continue;
+		}
+		if (d->connected && (TStime() - d->download_started > DOWNLOAD_TRANSFER_TIMEOUT))
+		{
+			https_cancel(d, "Download timeout after %ld seconds", (long)DOWNLOAD_TRANSFER_TIMEOUT);
+			continue;
+		}
+	}
+}
+
+void url_init(void)
+{
+	https_ctx = https_new_ctx();
+	if (!https_ctx)
+	{
+		unreal_log(ULOG_ERROR, "url", "HTTPS_NEW_CTX_FAILED", NULL,
+			   "Unable to initialize SSL context");
+		exit(-1);
+	}
+	EventAdd(NULL, "url_socket_timeout", url_socket_timeout, NULL, 500, 0);
+}
diff --git a/src/user.c b/src/user.c
@@ -59,7 +59,7 @@ MODVAR int non_utf8_nick_chars_in_use = 0;
  * @param client	The client (user)
  * @param host		The new vhost
  */
-void iNAH_host(Client *client, char *host)
+void iNAH_host(Client *client, const char *host)
 {
 	if (!client->user)
 		return;
@@ -80,45 +80,45 @@ void iNAH_host(Client *client, char *host)
  * @param umode		The user mode string
  * @returns the user mode value (long)
  */
-long set_usermode(char *umode)
+long set_usermode(const char *umode)
 {
-	int  newumode;
-	int  what;
-	char *m;
-	int i;
+	Umode *um;
+	int newumode;
+	int what;
+	const char *m;
 
 	newumode = 0;
 	what = MODE_ADD;
 	for (m = umode; *m; m++)
+	{
 		switch (*m)
 		{
-		  case '+':
-			  what = MODE_ADD;
-			  break;
-		  case '-':
-			  what = MODE_DEL;
-			  break;
-		  case ' ':
-		  case '\n':
-		  case '\r':
-		  case '\t':
-			  break;
-		  default:
-		 	 for (i = 0; i <= Usermode_highest; i++)
-		 	 {
-		 	 	if (!Usermode_Table[i].flag)
-		 	 		continue;
-		 	 	if (*m == Usermode_Table[i].flag)
-		 	 	{
-		 	 		if (what == MODE_ADD)
-			 	 		newumode |= Usermode_Table[i].mode;
-			 	 	else
-			 	 		newumode &= ~Usermode_Table[i].mode;
-		 	 	}
-		 	 } 	  
+			case '+':
+				what = MODE_ADD;
+				break;
+			case '-':
+				what = MODE_DEL;
+				break;
+			case ' ':
+			case '\n':
+			case '\r':
+			case '\t':
+				break;
+			default:
+				for (um = usermodes; um; um = um->next)
+				{
+					if (um->letter == *m)
+					{
+						if (what == MODE_ADD)
+							newumode |= um->mode;
+						else
+							newumode &= ~um->mode;
+					}
+				}
 		}
+	}
 
-	return (newumode);
+	return newumode;
 }
 
 /** Convert a target pointer to an 8 bit hash, used for target limiting. */
@@ -177,10 +177,10 @@ int target_limit_exceeded(Client *client, void *target, const char *name)
 	{
 		/* Target limit reached */
 		client->local->nexttarget += 2; /* punish them some more */
-		client->local->since += 2; /* lag them up as well */
+		add_fake_lag(client, 2000); /* lag them up as well */
 
 		flood_limit_exceeded_log(client, "max-concurrent-conversations");
-		sendnumeric(client, ERR_TARGETTOOFAST, name, client->local->nexttarget - TStime());
+		sendnumeric(client, ERR_TARGETTOOFAST, name, (long long)(client->local->nexttarget - TStime()));
 
 		return 1;
 	}
@@ -207,9 +207,10 @@ int target_limit_exceeded(Client *client, void *target, const char *name)
  * @param buffer	Input string
  * @returns The new de-duplicated buffer (temporary storage, only valid until next canonize call)
  */
-char *canonize(char *buffer)
+char *canonize(const char *buffer)
 {
 	static char cbuf[2048];
+	char tbuf[2048];
 	char *s, *t, *cp = cbuf;
 	int  l = 0;
 	char *p = NULL, *p2;
@@ -219,11 +220,8 @@ char *canonize(char *buffer)
 	if (!buffer)
 		return NULL;
 
-	/* Ohh.. so lazy. But then again, this should never happen with a 2K buffer anyway. */
-	if (strlen(buffer) >= sizeof(cbuf))
-		buffer[sizeof(cbuf)-1] = '\0';
-
-	for (s = strtoken(&p, buffer, ","); s; s = strtoken(&p, NULL, ","))
+	strlcpy(tbuf, buffer, sizeof(tbuf));
+	for (s = strtoken(&p, tbuf, ","); s; s = strtoken(&p, NULL, ","))
 	{
 		if (l)
 		{
@@ -252,99 +250,98 @@ char *canonize(char *buffer)
 	return cbuf;
 }
 
-/** Get snomasks as a string.
+/** Get user modes as a string.
  * @param client	The client
- * @returns string of snomasks (temporary storage)
+ * @returns string of user modes (temporary storage)
  */
-char *get_snomask_string(Client *client)
+const char *get_usermode_string(Client *client)
 {
-	int i;
-	char *m;
+	static char buf[128];
+	Umode *um;
 
-	m = buf;
+	strlcpy(buf, "+", sizeof(buf));
+	for (um = usermodes; um; um = um->next)
+		if (client->umodes & um->mode)
+			strlcat_letter(buf, um->letter, sizeof(buf));
 
-	*m++ = '+';
-	for (i = 0; i <= Snomask_highest && (m - buf < BUFSIZE - 4); i++)
-		if (Snomask_Table[i].flag && client->user->snomask & Snomask_Table[i].mode)
-			*m++ = Snomask_Table[i].flag;
-	*m = 0;
 	return buf;
 }
 
-/** Get user modes as a string.
+/** Get user modes as a string - buffer is specified.
  * @param client	The client
- * @returns string of user modes (temporary storage)
+ * @param buf		The buffer to write to
+ * @param buflen	The size of the buffer
+ * @returns string of user modes (buf)
  */
-char *get_usermode_string(Client *client)
+const char *get_usermode_string_r(Client *client, char *buf, size_t buflen)
 {
-	int  i;
-	char *m;
+	Umode *um;
+
+	strlcpy(buf, "+", buflen);
+	for (um = usermodes; um; um = um->next)
+		if (client->umodes & um->mode)
+			strlcat_letter(buf, um->letter, buflen);
 
-	m = buf;
-	*m++ = '+';
-	for (i = 0; (i <= Usermode_highest) && (m - buf < BUFSIZE - 4); i++)
-		if (Usermode_Table[i].flag && (client->umodes & Usermode_Table[i].mode))
-			*m++ = Usermode_Table[i].flag;
-	*m = '\0';
 	return buf;
 }
 
-
 /** Get user modes as a string - this one does not work on 'client' but directly on 'umodes'.
  * @param umodes	The user modes that are set
  * @returns string of user modes (temporary storage)
  */
-char *get_usermode_string_raw(long umodes)
+const char *get_usermode_string_raw(long umodes)
 {
-	int  i;
-	char *m;
+	static char buf[128];
+	Umode *um;
+
+	strlcpy(buf, "+", sizeof(buf));
+	for (um = usermodes; um; um = um->next)
+		if (umodes & um->mode)
+			strlcat_letter(buf, um->letter, sizeof(buf));
 
-	m = buf;
-	*m++ = '+';
-	for (i = 0; (i <= Usermode_highest) && (m - buf < BUFSIZE - 4); i++)
-		
-		if (Usermode_Table[i].flag && (umodes & Usermode_Table[i].mode))
-			*m++ = Usermode_Table[i].flag;
-	*m = '\0';
 	return buf;
 }
 
-/** Get snomasks as a string - this one does not work on 'client' but directly on 'sno'.
- * @param sno	The snomasks that are set
- * @returns string of snomasks (temporary storage)
+/** Get user modes as a string - this one does not work on 'client' but directly on 'umodes'.
+ * @param umodes	The user modes that are set
+ * @param buf		The buffer to write to
+ * @param buflen	The size of the buffer
+ * @returns string of user modes (buf)
  */
-char *get_snomask_string_raw(long sno)
+const char *get_usermode_string_raw_r(long umodes, char *buf, size_t buflen)
 {
-	int i;
-	char *m;
+	Umode *um;
 
-	m = buf;
+	strlcpy(buf, "+", buflen);
+	for (um = usermodes; um; um = um->next)
+		if (umodes & um->mode)
+			strlcat_letter(buf, um->letter, buflen);
 
-	*m++ = '+';
-	for (i = 0; i <= Snomask_highest && (m - buf < BUFSIZE - 4); i++)
-		if (Snomask_Table[i].flag && sno & Snomask_Table[i].mode)
-			*m++ = Snomask_Table[i].flag;
-	*m = 0;
 	return buf;
 }
 
+
 /** Set a new snomask on the user.
  * The user is not informed of the change by this function.
  * @param client	The client
  * @param snomask	The snomask to add or delete (eg: "+k-c")
  */
-void set_snomask(Client *client, char *snomask)
+void set_snomask(Client *client, const char *snomask)
 {
 	int what = MODE_ADD; /* keep this an int. -- Syzop */
-	char *p;
+	const char *p;
 	int i;
-	if (snomask == NULL) {
-		client->user->snomask = 0;
+
+	if (snomask == NULL)
+	{
+		remove_all_snomasks(client);
 		return;
 	}
 	
-	for (p = snomask; p && *p; p++) {
-		switch (*p) {
+	for (p = snomask; p && *p; p++)
+	{
+		switch (*p)
+		{
 			case '+':
 				what = MODE_ADD;
 				break;
@@ -352,22 +349,20 @@ void set_snomask(Client *client, char *snomask)
 				what = MODE_DEL;
 				break;
 			default:
-		 	 for (i = 0; i <= Snomask_highest; i++)
-		 	 {
-		 	 	if (!Snomask_Table[i].flag)
-		 	 		continue;
-		 	 	if (*p == Snomask_Table[i].flag)
-		 	 	{
-					if (Snomask_Table[i].allowed && !Snomask_Table[i].allowed(client,what))
+				if (what == MODE_ADD)
+				{
+					if (!isalpha(*p) || !is_valid_snomask(*p))
 						continue;
-		 	 		if (what == MODE_ADD)
-			 	 		client->user->snomask |= Snomask_Table[i].mode;
-			 	 	else
-			 	 		client->user->snomask &= ~Snomask_Table[i].mode;
-		 	 	}
-		 	 }				
+					addlettertodynamicstringsorted(&client->user->snomask, *p);
+				} else {
+					delletterfromstring(client->user->snomask, *p);
+				}
+				break;
 		}
 	}
+	/* If the snomask becomes empty ("") then set it to NULL and user mode -s */
+	if (client->user->snomask && !*client->user->snomask)
+		remove_all_snomasks(client);
 }
 
 /** Build the MODE line with (modified) user modes for this user.
@@ -375,7 +370,7 @@ void set_snomask(Client *client, char *snomask)
  */
 void build_umode_string(Client *client, long old, long sendmask, char *umode_buf)
 {
-	int i;
+	Umode *um;
 	long flag;
 	char *m;
 	int what = MODE_NULL;
@@ -386,33 +381,31 @@ void build_umode_string(Client *client, long old, long sendmask, char *umode_buf
 	 */
 	m = umode_buf;
 	*m = '\0';
-	for (i = 0; i <= Usermode_highest; i++)
+	for (um = usermodes; um; um = um->next)
 	{
-		if (!Usermode_Table[i].flag)
-			continue;
-		flag = Usermode_Table[i].mode;
+		flag = um->mode;
 		if (MyUser(client) && !(flag & sendmask))
 			continue;
 		if ((flag & old) && !(client->umodes & flag))
 		{
 			if (what == MODE_DEL)
-				*m++ = Usermode_Table[i].flag;
+				*m++ = um->letter;
 			else
 			{
 				what = MODE_DEL;
 				*m++ = '-';
-				*m++ = Usermode_Table[i].flag;
+				*m++ = um->letter;
 			}
 		}
 		else if (!(flag & old) && (client->umodes & flag))
 		{
 			if (what == MODE_ADD)
-				*m++ = Usermode_Table[i].flag;
+				*m++ = um->letter;
 			else
 			{
 				what = MODE_ADD;
 				*m++ = '+';
-				*m++ = Usermode_Table[i].flag;
+				*m++ = um->letter;
 			}
 		}
 	}
@@ -485,7 +478,7 @@ static void maxtarget_add_sorted(MaxTarget *n)
 }
 
 /** Find a maxtarget structure for a cmd (internal) */
-MaxTarget *findmaxtarget(char *cmd)
+MaxTarget *findmaxtarget(const char *cmd)
 {
 	MaxTarget *m;
 
@@ -496,17 +489,14 @@ MaxTarget *findmaxtarget(char *cmd)
 }
 
 /** Set a maximum targets per command restriction */
-void setmaxtargets(char *cmd, int limit)
+void setmaxtargets(const char *cmd, int limit)
 {
 	MaxTarget *m = findmaxtarget(cmd);
 	if (!m)
 	{
-		char cmdupper[64], *i, *o;
-		if (strlen(cmd) > 63)
-			cmd[63] = '\0';
-		for (i=cmd,o=cmdupper; *i; i++)
-			*o++ = toupper(*i);
-		*o = '\0';
+		char cmdupper[64];
+		strlcpy(cmdupper, cmd, sizeof(cmdupper));
+		strtoupper(cmdupper);
 		m = safe_alloc(sizeof(MaxTarget));
 		safe_strdup(m->cmd, cmdupper);
 		maxtarget_add_sorted(m);
@@ -529,7 +519,7 @@ void freemaxtargets(void)
 }
 
 /** Return the maximum number of targets permitted for a command */
-int max_targets_for_command(char *cmd)
+int max_targets_for_command(const char *cmd)
 {
 	MaxTarget *m = findmaxtarget(cmd);
 	if (m)
@@ -626,7 +616,7 @@ int is_handshake_finished(Client *client)
 int should_show_connect_info(Client *client)
 {
 	if (SHOWCONNECTINFO &&
-	    !client->serv &&
+	    !client->server &&
 	    !IsServersOnlyListener(client->local->listener) &&
 	    !client->local->listener->websocket_options)
 	{
@@ -672,7 +662,7 @@ const char *uid_get(void)
 }
 
 /** Get cloaked host for user */
-char *getcloak(Client *client)
+const char *getcloak(Client *client)
 {
 	if (!*client->user->cloakedhost)
 	{
@@ -689,9 +679,11 @@ char *getcloak(Client *client)
  * @param buf		Buffer to store the new cloaked host in
  * @param buflen	Length of the buffer (should be HOSTLEN+1)
  */
-void make_cloakedhost(Client *client, char *curr, char *buf, size_t buflen)
+void make_cloakedhost(Client *client, const char *curr, char *buf, size_t buflen)
 {
-	char host[256], *mask, *p, *q;
+	const char *p;
+	char host[256], *q;
+	const char *mask;
 
 	/* Convert host to lowercase and cut off at 255 bytes just to be sure */
 	for (p = curr, q = host; *p && (q < host+sizeof(host)-1); p++, q++)
@@ -700,9 +692,9 @@ void make_cloakedhost(Client *client, char *curr, char *buf, size_t buflen)
 
 	/* Call the cloaking layer */
 	if (RCallbacks[CALLBACKTYPE_CLOAK_EX] != NULL)
-		mask = RCallbacks[CALLBACKTYPE_CLOAK_EX]->func.pcharfunc(client, host);
+		mask = RCallbacks[CALLBACKTYPE_CLOAK_EX]->func.stringfunc(client, host);
 	else if (RCallbacks[CALLBACKTYPE_CLOAK] != NULL)
-		mask = RCallbacks[CALLBACKTYPE_CLOAK]->func.pcharfunc(host);
+		mask = RCallbacks[CALLBACKTYPE_CLOAK]->func.stringfunc(host);
 	else
 		mask = curr;
 
@@ -718,7 +710,7 @@ void user_account_login(MessageTag *recv_mtags, Client *client)
 		if (find_tkline_match(client, 0) && IsDead(client))
 			return;
 	}
-	RunHook2(HOOKTYPE_ACCOUNT_LOGIN, client, recv_mtags);
+	RunHook(HOOKTYPE_ACCOUNT_LOGIN, client, recv_mtags);
 }
 
 /** Should we hide the idle time of 'target' to user 'client'?
@@ -751,11 +743,13 @@ int hide_idle_time(Client *client, Client *target)
  * @param name	The name of the group
  * @returns 1 if name is valid, 0 if not (eg: illegal characters)
  */
-int security_group_valid_name(char *name)
+int security_group_valid_name(const char *name)
 {
-	char *p;
+	const char *p;
+
 	if (strlen(name) > SECURITYGROUPLEN)
 		return 0; /* Too long */
+
 	for (p = name; *p; p++)
 	{
 		if (!isalnum(*p) && !strchr("_-", *p))
@@ -768,7 +762,7 @@ int security_group_valid_name(char *name)
  * @param name	The name of the security group
  * @returns A SecurityGroup struct, or NULL if not found.
  */
-SecurityGroup *find_security_group(char *name)
+SecurityGroup *find_security_group(const char *name)
 {
 	SecurityGroup *s;
 	for (s = securitygroups; s; s = s->next)
@@ -782,7 +776,7 @@ SecurityGroup *find_security_group(char *name)
  * @param name	The name of the security group
  * @returns 1 if it exists, 0 if not
  */
-int security_group_exists(char *name)
+int security_group_exists(const char *name)
 {
 	if (!strcmp(name, "unknown-users") || find_security_group(name))
 		return 1;
@@ -793,7 +787,7 @@ int security_group_exists(char *name)
  * @param name	The name of the security group
  * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list)
  */
-SecurityGroup *add_security_group(char *name, int priority)
+SecurityGroup *add_security_group(const char *name, int priority)
 {
 	SecurityGroup *s = find_security_group(name);
 
@@ -812,9 +806,7 @@ SecurityGroup *add_security_group(char *name, int priority)
 /** Free a SecurityGroup struct */
 void free_security_group(SecurityGroup *s)
 {
-	/* atm there is nothing else to free,
-	 * but who knows this may change in the future
-	 */
+	unreal_delete_masks(s->include_mask);
 	safe_free(s);
 }
 
@@ -866,7 +858,9 @@ int user_allowed_by_security_group(Client *client, SecurityGroup *s)
 		return 1;
 	if (s->reputation_score && (GetReputation(client) >= s->reputation_score))
 		return 1;
-	if (s->tls && (IsSecureConnect(client) || IsSecure(client)))
+	if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
+		return 1;
+	if (s->include_mask && unreal_mask_match(client, s->include_mask))
 		return 1;
 	return 0;
 }
@@ -876,7 +870,7 @@ int user_allowed_by_security_group(Client *client, SecurityGroup *s)
  * @param secgroupname	The name of the security-group to check against
  * @retval 1 if user is allowed by security-group, 0 if not.
  */
-int user_allowed_by_security_group_name(Client *client, char *secgroupname)
+int user_allowed_by_security_group_name(Client *client, const char *secgroupname)
 {
 	SecurityGroup *s;
 
@@ -897,33 +891,81 @@ int user_allowed_by_security_group_name(Client *client, char *secgroupname)
 	return user_allowed_by_security_group(client, s);
 }
 
+/** Get comma separated list of matching security groups for 'client'.
+ * This is usually only used for displaying purposes.
+ * @returns string like "unknown-users,tls-users" from a static buffer.
+ */
+const char *get_security_groups(Client *client)
+{
+	SecurityGroup *s;
+	static char buf[512];
+
+	*buf = '\0';
+
+	/* 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"))
+		strlcat(buf, "known-users,", sizeof(buf));
+	else
+		strlcat(buf, "unknown-users,", sizeof(buf));
+
+	for (s = securitygroups; s; s = s->next)
+	{
+		if (strcmp(s->name, "known-users") &&
+		    user_allowed_by_security_group(client, s))
+		{
+			strlcat(buf, s->name, sizeof(buf));
+			strlcat(buf, ",", sizeof(buf));
+		}
+	}
+
+	if (*buf)
+		buf[strlen(buf)-1] = '\0';
+	return buf;
+}
+
 /** Return extended information about user for the "Client connecting" line.
  * @returns A string such as "[secure] [reputation: 5]", never returns NULL.
  */
-char *get_connect_extinfo(Client *client)
+const char *get_connect_extinfo(Client *client)
 {
 	static char retbuf[512];
 	char tmp[512];
+	const char *s;
+	const char *secgroups;
 	NameValuePrioList *list = NULL, *e;
 
 	/* From modules... */
-	RunHook2(HOOKTYPE_CONNECT_EXTINFO, client, &list);
+	RunHook(HOOKTYPE_CONNECT_EXTINFO, client, &list);
 
 	/* And some built-in: */
 
-	/* "class": this should be first */
+	/* "vhost": this should be first */
+	if (IsHidden(client))
+		add_nvplist(&list, -1000000, "vhost", client->user->virthost);
+
+	/* "class": second */
 	if (MyUser(client) && client->local->class)
 		add_nvplist(&list, -100000, "class", client->local->class->name);
 
-	/* "secure": SSL/TLS */
-	if (MyUser(client) && IsSecure(client))
-		add_nvplist(&list, -1000, "secure", tls_get_cipher(client->local->ssl));
-	else if (!MyUser(client) && IsSecureConnect(client))
-		add_nvplist(&list, -1000, "secure", NULL);
+	/* "secure": TLS */
+	s = tls_get_cipher(client);
+	if (s)
+		add_nvplist(&list, -1000, "secure", s);
+	else if (IsSecure(client) || IsSecureConnect(client))
+		add_nvplist(&list, -1000, "secure", NULL); /* old server or otherwise no details (eg: fake secure) */
 
 	/* services account? */
 	if (IsLoggedIn(client))
-		add_nvplist(&list, -500, "account", client->user->svid);
+		add_nvplist(&list, -500, "account", client->user->account);
+
+	/* security groups */
+	secgroups = get_security_groups(client);
+	if (secgroups)
+		add_nvplist(&list, 100, "security-groups", secgroups);
 
 	*retbuf = '\0';
 	for (e = list; e; e = e->next)
@@ -949,18 +991,13 @@ char *get_connect_extinfo(Client *client)
  * @param client	The client to check flood for (local user)
  * @param opt		The flood option (eg FLD_AWAY)
  */
-void flood_limit_exceeded_log(Client *client, char *floodname)
+void flood_limit_exceeded_log(Client *client, const char *floodname)
 {
 	char buf[1024];
 
-	snprintf(buf, sizeof(buf), "Flood blocked (%s) from %s!%s@%s [%s]",
-		floodname,
-		client->name,
-		client->user->username,
-		client->user->realhost,
-		GetIP(client));
-	ircd_log(LOG_FLOOD, "%s", buf);
-	sendto_snomask_global(SNO_FLOOD, "%s", buf);
+	unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
+	           "Flood blocked ($flood_type) from $client.details [$client.ip]",
+	           log_data_string("flood_type", floodname));
 }
 
 /** Is the flood limit exceeded for an option? eg for away-flood.
@@ -980,9 +1017,6 @@ int flood_limit_exceeded(Client *client, FloodOption opt)
 	if (f->limit[opt] <= 0)
 		return 0; /* No limit set or unlimited */
 
-	ircd_log(LOG_ERROR, "Checking flood_limit_exceeded() for '%s', type %d with max %d:%ld...",
-		client->name, (int)opt, (int)f->limit[opt], (long)f->period[opt]);
-
 	/* Ok, let's do the flood check */
 	if ((client->local->flood[opt].t + f->period[opt]) <= timeofday)
 	{
@@ -1041,12 +1075,76 @@ FloodSettings *get_floodsettings_for_user(Client *client, FloodOption opt)
 	return f;
 }
 
-MODVAR char *floodoption_names[] = {
+MODVAR const char *floodoption_names[] = {
 	"nick-flood",
 	"join-flood",
 	"away-flood",
 	"invite-flood",
 	"knock-flood",
 	"max-concurrent-conversations",
+	"lag-penalty",
 	NULL
 };
+
+/** Lookup GEO information for an IP address.
+ * @param ip	The IP to lookup
+ * @returns A struct containing all the details. Must be freed by caller!
+ */
+GeoIPResult *geoip_lookup(const char *ip)
+{
+	if (!RCallbacks[CALLBACKTYPE_GEOIP_LOOKUP])
+		return NULL;
+	return RCallbacks[CALLBACKTYPE_GEOIP_LOOKUP]->func.pvoidfunc(ip);
+}
+
+void free_geoip_result(GeoIPResult *r)
+{
+	if (!r)
+		return;
+	safe_free(r->country_code);
+	safe_free(r->country_name);
+	safe_free(r);
+}
+
+/** Grab geoip information for client */
+GeoIPResult *geoip_client(Client *client)
+{
+	ModData *m = moddata_client_get_raw(client, "geoip");
+	if (!m)
+		return NULL;
+	return m->ptr; /* can still be NULL */
+}
+
+/** Get the oper block that was used to become OPER.
+ * @param client	The client to fetch the info for
+ * @returns the oper block name (eg: "OpEr") or NULL.
+ */
+const char *get_operlogin(Client *client)
+{
+	if (client->user->operlogin)
+		return client->user->operlogin;
+	return moddata_client_get(client, "operlogin");
+}
+
+/** Get the operclass of the IRCOp.
+ * @param client	The client to fetch the info for
+ * @returns the operclass name or NULL
+ */
+const char *get_operclass(Client *client)
+{
+	const char *operlogin = NULL;
+
+	if (MyUser(client) && client->user->operlogin)
+	{
+		ConfigItem_oper *oper;
+		operlogin = client->user->operlogin;
+		oper = find_oper(operlogin);
+		if (oper && oper->operclass)
+			return oper->operclass;
+	}
+
+	/* Remote user or locally no longer available
+	 * (eg oper block removed but user is still oper)
+	 */
+	return moddata_client_get(client, "operclass");
+}
diff --git a/src/utf8.c b/src/utf8.c
@@ -140,38 +140,51 @@ char *unrl_utf8_find_prev_char (const char *begin, const char *p)
 }
 
 /** Return a valid UTF8 string based on the input.
- * @param str The input string, with a maximum of 1024 bytes.
- * @retval Returns a valid UTF8 string (which may be sanitized
- *         or simply the original string if it was OK already)
+ * @param str		The input string
+ * @param outputbuf	The output buffer
+ * @param outputbuflen	Length of the output buffer
+ * @param strictlen	If set to 1 we never return more than
+ *                      outputbuflen-1 characters.
+ *                      If set to 0, we may do that, if the
+ *                      input string was already 100% valid UTF8.
+ * @retval Returns a valid UTF8 string, either the input buffer
+ *         (if it was already valid UTF8) or the output buffer.
+ *         NULL is returned if either 'str' was NULL or outputlen is zero.
+ * @notes The 'outputbuf' is unused if the string is already valid UTF8.
+ *        So don't rely on it being always set, use the returned string.
  */
-char *unrl_utf8_make_valid(const char *str)
+char *unrl_utf8_make_valid(const char *str, char *outputbuf, size_t outputbuflen, int strictlen)
 {
-	static char string[4096]; /* crazy, but lazy, max amplification is x3, so x4 is safe. */
 	const char *remainder, *invalid;
 	int remaining_bytes, valid_bytes, len;
 	int replaced = 0; /**< UTF8 string needed replacement (was invalid) */
 
-	if (!str)
+	if (!str || !outputbuflen)
 		return NULL;
 
 	len = strlen(str);
 
-	if (len >= 1024)
-		abort(); /* better safe than sorry */
-
-	*string = '\0';
+	*outputbuf = '\0';
 	remainder = str;
 	remaining_bytes = len;
 
 	while (remaining_bytes != 0)
 	{
 		if (unrl_utf8_validate(remainder, &invalid))
+		{
+			if (!replaced && strictlen)
+			{
+				/* Caller wants us to go through the 'replaced' branch */
+				strlcpy(outputbuf, str, outputbuflen);
+				replaced = 1;
+			}
 			break;
+		}
 		replaced = 1;
 		valid_bytes = invalid - remainder;
 
-		strlncat(string, remainder, sizeof(string), valid_bytes); /*g_string_append_len(string, remainder, valid_bytes);*/
-		strlcat(string, "\357\277\275", sizeof(string));
+		strlncat(outputbuf, remainder, outputbuflen, valid_bytes); /*g_string_append_len(string, remainder, valid_bytes);*/
+		strlcat(outputbuf, "\357\277\275", outputbuflen);
 
 		remaining_bytes -= valid_bytes + 1;
 		remainder = invalid + 1;
@@ -180,21 +193,25 @@ char *unrl_utf8_make_valid(const char *str)
 	if (!replaced)
 		return (char *)str; /* return original string (no changes needed) */
 
-	/* If output size is too much for an IRC message then cut the string at
-	 * the appropriate place (as in: not to cause invalid UTF8 due to
-	 * cutting half-way a byte sequence).
+	/* If we took up all the space, then backtrack one character and cut
+	 * things off from there. This to ensure that we don't end up with
+	 * invalid UTF8 due to cutting half-way a UTF8 byte sequence.
+	 * NOTE: This may cause us to remove 1 character needlessly at the
+	 *       end even though there was still (some) space. So be it.
 	 */
-	if (strlen(string) >= 510)
+	if (strlen(outputbuf) == outputbuflen-1)
 	{
-		char *cut_at = unrl_utf8_find_prev_char(string, string+509);
+		char *cut_at = unrl_utf8_find_prev_char(outputbuf, outputbuf+outputbuflen-1);
 		if (cut_at)
 			*cut_at = '\0';
 	}
 
-	if (!unrl_utf8_validate(string, NULL))
+#ifdef DEBUGMODE
+	if (!unrl_utf8_validate(outputbuf, NULL))
 		abort(); /* this should never happen, it means our conversion resulted in an invalid UTF8 string */
+#endif
 
-	return string;
+	return outputbuf;
 }
 
 /**************** END OF UTF8 HELPER FUNCTIONS *****************/
@@ -206,12 +223,14 @@ void utf8_test(void)
 	char *res;
 	int cnt = 0;
 	char *heapbuf; /* for strict OOB testing with ASan */
+	char *workbuf = safe_alloc(500);
+	size_t workbuflen = 500;
 
 	while ((fgets(buf, sizeof(buf), stdin)))
 	{
 		stripcrlf(buf);
 		heapbuf = strdup(buf);
-		res = unrl_utf8_make_valid(heapbuf);
+		res = unrl_utf8_make_valid(heapbuf, workbuf, workbuflen, 1);
 		if (heapbuf == res)
 		{
 			printf("    %s\n", res);
@@ -220,4 +239,5 @@ void utf8_test(void)
 		}
 		free(heapbuf);
 	}
+	safe_free(workbuf);
 }
diff --git a/src/version.c.SH b/src/version.c.SH
@@ -4,7 +4,10 @@ echo "Extracting src/version.c..."
 
 #id=`grep '$Id: Changes,v' ../Changes`
 #id=`echo $id |sed 's/.* Changes\,v \(.*\) .* Exp .*/\1/'`
-id="5.2.0.1"
+if [ -d ../.git ]; then
+	SUFFIX="-$(git rev-parse --short HEAD)"
+fi
+id="6.0.1.1$SUFFIX"
 echo "$id"
 
 if test -r version.c
@@ -19,7 +22,9 @@ generation=`expr $generation + 1`
 export LANG=C
 export LC_TIME=C
 export LC_ALL=C
-creation=`date | \
+SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(date +%s)}"
+BUILD_DATE=$(date -u -d "@$SOURCE_DATE_EPOCH" 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" 2>/dev/null || date -u)
+creation=`echo "$BUILD_DATE" | \
 awk '{if (NF == 6) \
          { print $1 " "  $2 " " $3 " "  $6 " at " $4 " " $5 } \
 else \
@@ -143,7 +148,8 @@ char *unrealcredits[] =
 	"officially joining the staff.",
 	" ",
 	"===========================[ Coders ]=============================",
-	"Syzop <syzop@unrealircd.org> - Head Coder / Project leader",
+	"Bram Matthys (Syzop) <syzop@unrealircd.org> - Head Coder / Project leader",
+	"Krzysztof Beresztant (k4be) <k4be@unrealircd.org> - Coder",
 	"Gottem <gottem@unrealircd.org> - Coder",
 	"i <i@unrealircd.org> - Coder",
 	" ",
diff --git a/src/whowas.c b/src/whowas.c
@@ -24,19 +24,19 @@
 // Some users may not want to load cmd_whowas at all.
 
 /* internally defined function */
-static void add_whowas_to_clist(aWhowas **, aWhowas *);
-static void del_whowas_from_clist(aWhowas **, aWhowas *);
-static void add_whowas_to_list(aWhowas **, aWhowas *);
-static void del_whowas_from_list(aWhowas **, aWhowas *);
+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 *);
 
-aWhowas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
-aWhowas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
+WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
 
 MODVAR int whowas_next = 0;
 
 void add_history(Client *client, int online)
 {
-	aWhowas *new;
+	WhoWas *new;
 
 	new = &WHOWAS[whowas_next];
 
@@ -87,7 +87,7 @@ void add_history(Client *client, int online)
 
 void off_history(Client *client)
 {
-	aWhowas *temp, *next;
+	WhoWas *temp, *next;
 
 	for (temp = client->user->whowas; temp; temp = next)
 	{
@@ -97,9 +97,9 @@ void off_history(Client *client)
 	}
 }
 
-Client *get_history(char *nick, time_t timelimit)
+Client *get_history(const char *nick, time_t timelimit)
 {
-	aWhowas *temp;
+	WhoWas *temp;
 	int  blah;
 
 	timelimit = TStime() - timelimit;
@@ -118,7 +118,7 @@ Client *get_history(char *nick, time_t timelimit)
 
 void count_whowas_memory(int *wwu, u_long *wwum)
 {
-	aWhowas *tmp;
+	WhoWas *tmp;
 	int  i;
 	int  u = 0;
 	u_long um = 0;
@@ -129,7 +129,7 @@ void count_whowas_memory(int *wwu, u_long *wwum)
 		if (tmp->hashv != -1)
 		{
 			u++;
-			um += sizeof(aWhowas);
+			um += sizeof(WhoWas);
 		}
 	*wwu = u;
 	*wwum = um;
@@ -142,14 +142,14 @@ void initwhowas()
 
 	for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
 	{
-		memset(&WHOWAS[i], 0, sizeof(aWhowas));
+		memset(&WHOWAS[i], 0, sizeof(WhoWas));
 		WHOWAS[i].hashv = -1;
 	}
 	for (i = 0; i < WHOWAS_HASH_TABLE_SIZE; i++)
 		WHOWASHASH[i] = NULL;
 }
 
-static void add_whowas_to_clist(aWhowas ** bucket, aWhowas * whowas)
+static void add_whowas_to_clist(WhoWas ** bucket, WhoWas * whowas)
 {
 	whowas->cprev = NULL;
 	if ((whowas->cnext = *bucket) != NULL)
@@ -157,7 +157,7 @@ static void add_whowas_to_clist(aWhowas ** bucket, aWhowas * whowas)
 	*bucket = whowas;
 }
 
-static void del_whowas_from_clist(aWhowas ** bucket, aWhowas * whowas)
+static void del_whowas_from_clist(WhoWas ** bucket, WhoWas * whowas)
 {
 	if (whowas->cprev)
 		whowas->cprev->cnext = whowas->cnext;
@@ -167,7 +167,7 @@ static void del_whowas_from_clist(aWhowas ** bucket, aWhowas * whowas)
 		whowas->cnext->cprev = whowas->cprev;
 }
 
-static void add_whowas_to_list(aWhowas ** bucket, aWhowas * whowas)
+static void add_whowas_to_list(WhoWas ** bucket, WhoWas * whowas)
 {
 	whowas->prev = NULL;
 	if ((whowas->next = *bucket) != NULL)
@@ -175,7 +175,7 @@ static void add_whowas_to_list(aWhowas ** bucket, aWhowas * whowas)
 	*bucket = whowas;
 }
 
-static void del_whowas_from_list(aWhowas ** bucket, aWhowas * whowas)
+static 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
@@ -2,8 +2,8 @@
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
 <assemblyIdentity
     processorArchitecture="amd64"
-    name="UnrealIRCd.UnrealIRCd.5"
-    version="5.2.0.1"
+    name="UnrealIRCd.UnrealIRCd.6"
+    version="6.0.1.0"
     type="win32"
 />
 <description>Internet Relay Chat Daemon</description>
@@ -12,7 +12,7 @@
         <assemblyIdentity
             type="win32"
             name="Microsoft.Windows.Common-Controls"
-            version="6.0.0.0"
+            version="6.0.1.0"
             processorArchitecture="amd64"
             publicKeyToken="6595b64144ccf1df"
             language="*"
diff --git a/src/windows/gui.c b/src/windows/gui.c
@@ -85,9 +85,6 @@ UINT WM_TASKBARCREATED, WM_FINDMSGSTRING;
 FARPROC lpfnOldWndProc;
 HMENU hContext;
 char OSName[OSVER_SIZE];
-#ifdef USE_LIBCURL
-extern char *find_loaded_remote_include(char *url);
-#endif 
 
 void TaskBarCreated() 
 {
@@ -167,16 +164,23 @@ LRESULT RESubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
 	return CallWindowProc((WNDPROC)lpfnOldWndProc, hWnd, Message, wParam, lParam);
 }
 
-int CloseUnreal(HWND hWnd)
+int DoCloseUnreal(HWND hWnd)
+{
+	unreal_log(ULOG_INFO, "main", "UNREALIRCD_STOP", NULL,
+	           "Terminating server (process termination requested or GUI window closed)");
+	loop.terminating = 1;
+	unload_all_modules();
+	DestroyWindow(hWnd);
+	TerminateProcess(GetCurrentProcess(), 0);
+	exit(0); /* in case previous fails (possible?) */
+}
+
+int AskCloseUnreal(HWND hWnd)
 {
 	if (MessageBox(hWnd, "Close UnrealIRCd?", "Are you sure?", MB_YESNO|MB_ICONQUESTION) == IDNO)
 		 return 0;
-	else 
-	{
-		DestroyWindow(hWnd);
-		TerminateProcess(GetCurrentProcess(), 0);
-		exit(0); /* in case previous fails (possible?) */
-	}
+	DoCloseUnreal(hWnd);
+	exit(0);
 }
 
 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
@@ -195,7 +199,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
 	DWORD need;
 	
 	/* Go one level up, since we are currently in the bin\ subdir
-	 * and we want to be in (f.e.) "C:\Program Files\UnrealIRCd 5"
+	 * and we want to be in (f.e.) "C:\Program Files\UnrealIRCd 6"
 	 */
 	chdir("..");
 
@@ -243,7 +247,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
 	WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated");
 	WM_FINDMSGSTRING = RegisterWindowMessage(FINDMSGSTRING);
 	atexit(CleanUp);
-	if(!LoadLibrary("riched20.dll"))
+	if (!LoadLibrary("riched20.dll"))
 		LoadLibrary("riched32.dll");
 	InitDebug();
 
@@ -328,7 +332,7 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 			return 0;
 		}
 		case WM_CLOSE: 
-			return CloseUnreal(hDlg);
+			return DoCloseUnreal(hDlg);
 		case WM_USER: 
 		{
 			switch(LOWORD(lParam)) 
@@ -350,6 +354,7 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 					DestroyMenu(hLogs);
 					hLogs = CreatePopupMenu();
 					AppendMenu(hConfig, MF_STRING, IDM_CONF, CPATH);
+#if 0
 					if (conf_log) 
 					{
 						ConfigItem_log *logs;
@@ -360,22 +365,7 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 						}
 					}
 					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-					if (conf_include) 
-					{
-						ConfigItem_include *inc;
-						for (inc = conf_include; inc; inc = inc->next)
-						{
-							if (inc->flag.type & INCLUDE_NOTLOADED)
-								continue;
-#ifdef USE_LIBCURL
-							if (inc->flag.type & INCLUDE_REMOTE)
-								AppendMenu(hConfig, MF_STRING, i++, inc->url);
-							else
 #endif
-							AppendMenu(hConfig, MF_STRING, i++, inc->file);
-						}
-						AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-					}
 					if (conf_files)
 					{
 						AppendMenu(hConfig, MF_STRING, IDM_MOTD, conf_files->motd_file);
@@ -458,6 +448,7 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 				hLogs = CreatePopupMenu();
 
 				AppendMenu(hConfig, MF_STRING, IDM_CONF, CPATH);
+#if 0
 				if (conf_log) 
 				{
 					ConfigItem_log *logs;
@@ -468,22 +459,7 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 					}
 				}
 				AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-
-				if (conf_include) 
-				{
-					ConfigItem_include *inc;
-					for (inc = conf_include; inc; inc = inc->next)
-					{
-#ifdef USE_LIBCURL
-						if (inc->flag.type & INCLUDE_REMOTE)
-							AppendMenu(hConfig, MF_STRING, i++, inc->url);
-						else
 #endif
-						AppendMenu(hConfig, MF_STRING, i++, inc->file);
-					}
-					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-				}
-
 				if (conf_files)
 				{
 					AppendMenu(hConfig, MF_STRING, IDM_MOTD, conf_files->motd_file);
@@ -520,7 +496,16 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 				return 0;
 			}
 			else if ((p.x >= 336) && (p.x <= 411) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				return CloseUnreal(hDlg);
+				return AskCloseUnreal(hDlg);
+		}
+		case WM_SYSCOMMAND:
+		{
+			if (wParam == SC_CLOSE)
+			{
+				AskCloseUnreal(hDlg);
+				return 1;
+			}
+			break;
 		}
 		case WM_COMMAND: 
 		{
@@ -533,20 +518,13 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 				else 
 				{
 					GetMenuString(hConfig,LOWORD(wParam), path, MAX_PATH, MF_BYCOMMAND);
-#ifdef USE_LIBCURL
-					if (url_is_valid(path))
-					{
-						char *file = find_loaded_remote_include(path);
-						DialogBoxParam(hInst, "FromVar", hDlg, (DLGPROC)FromFileReadDLG, (LPARAM)file);
-					}
-					else
-#endif
+					if (!url_is_valid(path))
 						DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, (LPARAM)path);
 				}
 				return FALSE;
 			}
 
-			if (!loop.ircd_booted)
+			if (!loop.booted)
 			{
 				MessageBox(NULL, "UnrealIRCd not booted due to configuration errors. "
 				                 "Check other window for error details. Then close that window, "
@@ -561,34 +539,28 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 					ShowDialog(&hStatusWnd, hInst, "Status", hDlg,StatusDLG);
 					break;
 				case IDM_SHUTDOWN:
-					return CloseUnreal(hDlg);
+					return AskCloseUnreal(hDlg);
 				case IDM_RHALL:
 					MessageBox(NULL, "Rehashing all files", "Rehashing", MB_OK);
-					sendto_realops("Rehashing all files via the console");
-					rehash(&me,0);
-					reread_motdsandrules();
+					request_rehash(NULL);
 					break;
 				case IDM_RHCONF:
 					MessageBox(NULL, "Rehashing the Config file", "Rehashing", MB_OK);
-					sendto_realops("Rehashing the Config file via the console");
-					rehash(&me,0);
+					request_rehash(NULL);
 					break;
 				case IDM_RHMOTD: 
 				{
 					MessageBox(NULL, "Rehashing all MOTD and Rules files", "Rehashing", MB_OK);
 					rehash_motdrules();
-					sendto_realops("Rehashing all MOTD and Rules files via the console");
 					break;
 				}
 				case IDM_RHOMOTD:
 					MessageBox(NULL, "Rehashing the OperMOTD", "Rehashing", MB_OK);
 					read_motd(conf_files->opermotd_file, &opermotd);
-					sendto_realops("Rehashing the OperMOTD via the console");
 					break;
 				case IDM_RHBMOTD:
 					MessageBox(NULL, "Rehashing the BotMOTD", "Rehashing", MB_OK);
 					read_motd(conf_files->botmotd_file, &botmotd);
-					sendto_realops("Rehashing the BotMOTD via the console");
 					break;
 				case IDM_LICENSE: 
 					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)LicenseDLG);
@@ -1016,7 +988,7 @@ void win_map(Client *server, HWND hwTreeView, short remap)
 	for (lp = Servers; lp; lp = lp->next)
         {
                 acptr = lp->value.client;
-                if (acptr->srvptr != server)
+                if (acptr->uplink != server)
                         continue;
                 win_map(acptr, hwTreeView, 0);
         }
@@ -1076,27 +1048,6 @@ void win_error()
 {
 	if (errors && !IsService)
 		DialogBox(hInst, "ConfigError", hwIRCDWnd, (DLGPROC)ConfigErrorDLG);
-	if (need_34_upgrade)
-	{
-		need_34_upgrade = 0; /* anti-recursion. yes, is needed. */
-		if (MessageBox(NULL, 
-		               "Shall I try to upgrade your configuration files to UnrealIRCd 4 format?",
-		               "3.2.x configuration detected",
-		               MB_YESNO|MB_ICONQUESTION) == IDNO)
-		{
-			 return;
-		}
-		else 
-		{
-			update_conf();
-			MessageBox(NULL,
-			           "Configuration file(s) upgraded! In next screen you can see what I did (just for reference). "
-			           "After that, simply try to start UnrealIRCd again and see if it loads.",
-			           "Configuration upgrade",
-			           MB_OK);
-			DialogBox(hInst, "ConfigError", hwIRCDWnd, (DLGPROC)ConfigErrorDLG);
-		}
-	}
 }
 
 LRESULT CALLBACK ConfigErrorDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
diff --git a/src/windows/service.c b/src/windows/service.c
@@ -77,8 +77,7 @@ VOID WINAPI IRCDCtrlHandler(DWORD opcode)
 	/* Rehash */
 	else if (opcode == IRCD_SERVICE_CONTROL_REHASH) 
 	{
-		rehash(&me,0);
-		reread_motdsandrules();
+		request_rehash(NULL);
 	}
 
 	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
@@ -112,7 +111,7 @@ VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
 	chdir(path);
 
 	/* Go one level up, since we are currently in the bin\ subdir
-	 * and we want to be in (f.e.) "C:\Program Files\UnrealIRCd 5"
+	 * and we want to be in (f.e.) "C:\Program Files\UnrealIRCd 6"
 	 */
 	chdir("..");
 
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
@@ -5,15 +5,15 @@
 #define USE_CURL
 
 [Setup]
-AppName=UnrealIRCd 5
-AppVerName=UnrealIRCd 5.2.0.1
+AppName=UnrealIRCd 6
+AppVerName=UnrealIRCd 6.0.1.1
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org
 AppUpdatesURL=https://www.unrealircd.org
 AppMutex=UnrealMutex,Global\UnrealMutex
-DefaultDirName={pf}\UnrealIRCd 5
-DefaultGroupName=UnrealIRCd 5
+DefaultDirName={pf}\UnrealIRCd 6
+DefaultGroupName=UnrealIRCd 6
 AllowNoIcons=yes
 LicenseFile=src\windows\gplplusssl.rtf
 Compression=lzma
@@ -26,8 +26,11 @@ UninstallFilesDir={app}\bin\uninstaller
 DisableWelcomePage=no
 ArchitecturesInstallIn64BitMode=x64
 ArchitecturesAllowed=x64
+;These are set only on release:
+;SignedUninstaller=yes
+;SignTool=signtool
 
-; !!! Make sure to update SSL/TLS validation (WizardForm.TasksList.Checked[9]) if tasks are added/removed !!!
+; !!! Make sure to update TLS validation (WizardForm.TasksList.Checked[9]) if tasks are added/removed !!!
 [Tasks]
 Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Additional icons:"
 Name: "quicklaunchicon"; Description: "Create a &Quick Launch icon"; GroupDescription: "Additional icons:"; Flags: unchecked
@@ -35,13 +38,42 @@ Name: "installservice"; Description: "Install as a &service (not for beginners)"
 Name: "installservice/startboot"; Description: "S&tart UnrealIRCd when Windows starts"; GroupDescription: "Service support:"; MinVersion: 0,4.0; Flags: exclusive unchecked
 Name: "installservice/startdemand"; Description: "Start UnrealIRCd on &request"; GroupDescription: "Service support:"; MinVersion: 0,4.0; Flags: exclusive unchecked
 Name: "installservice/crashrestart"; Description: "Restart UnrealIRCd if it &crashes"; GroupDescription: "Service support:"; Flags: unchecked; MinVersion: 0,5.0;
-Name: "makecert"; Description: "&Create certificate"; GroupDescription: "SSL/TLS options:";
+Name: "makecert"; Description: "&Create certificate"; GroupDescription: "TLS options:";
 Name: "fixperm"; Description: "Make UnrealIRCd folder writable by current user";
 
 [Files]
-Source: "UnrealIRCd.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
+; UnrealIRCd binaries
+Source: "UnrealIRCd.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
 Source: "UnrealIRCd.pdb"; DestDir: "{app}\bin"; Flags: ignoreversion
+Source: "unrealsvc.exe";  DestDir: "{app}\bin"; Flags: ignoreversion signonce
 
+; TLS certificate generation helpers
+Source: "src\windows\makecert.bat"; DestDir: "{app}\bin"; Flags: ignoreversion
+Source: "extras\tls.cnf"; DestDir: "{app}\bin"; Flags: ignoreversion
+
+; UnrealIRCd modules
+Source: "src\modules\*.dll"; DestDir: "{app}\modules"; Flags: ignoreversion signonce
+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\third\*.dll"; DestDir: "{app}\modules\third"; Flags: ignoreversion skipifsourcedoesntexist signonce
+
+; Libraries
+Source: "c:\dev\unrealircd-6-libs\pcre2\bin\pcre*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\argon2\vs2015\build\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\libsodium\bin\x64\Release\v142\dynamic\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\jansson\bin\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\c-ares\msvc\cares\dll-release\cares.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\libressl\bin\openssl.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\libressl\bin\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\GeoIP\libGeoIP\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "c:\dev\unrealircd-6-libs\setacl.exe"; DestDir: "{app}\tmp"; Flags: ignoreversion signonce
+#ifdef USE_CURL
+Source: "c:\dev\unrealircd-6-libs\curl\builds\libcurl-vc-x64-release-dll-ssl-dll-cares-dll-ipv6-obj-lib\libcurl.dll"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+#endif
+Source: "doc\conf\tls\curl-ca-bundle.crt"; DestDir: "{app}\conf\tls"; Flags: ignoreversion
+
+; Config files
 Source: "doc\conf\*.default.conf"; DestDir: "{app}\conf"; Flags: ignoreversion
 Source: "doc\conf\*.optional.conf"; DestDir: "{app}\conf"; Flags: ignoreversion
 Source: "doc\conf\spamfilter.conf"; DestDir: "{app}\conf"; Flags: onlyifdoesntexist
@@ -50,38 +82,13 @@ Source: "doc\conf\dccallow.conf"; DestDir: "{app}\conf"; Flags: onlyifdoesntexis
 Source: "doc\conf\aliases\*.conf"; DestDir: "{app}\conf\aliases"; Flags: ignoreversion
 Source: "doc\conf\help\*.conf"; DestDir: "{app}\conf\help"; Flags: ignoreversion
 Source: "doc\conf\examples\*.conf"; DestDir: "{app}\conf\examples"; Flags: ignoreversion
+Source: "doc\conf\aliases\*"; DestDir: "{app}\conf\aliases"; Flags: ignoreversion
 
+; Documentation etc.
 Source: "doc\Donation"; DestDir: "{app}\doc"; DestName: "Donation.txt"; Flags: ignoreversion
 Source: "LICENSE"; DestDir: "{app}\doc"; DestName: "LICENSE.txt"; Flags: ignoreversion
-
 Source: "doc\*.*"; DestDir: "{app}\doc"; Flags: ignoreversion
 Source: "doc\technical\*.*"; DestDir: "{app}\doc\technical"; Flags: ignoreversion
-Source: "doc\conf\aliases\*"; DestDir: "{app}\conf\aliases"; Flags: ignoreversion
-
-Source: "unrealsvc.exe"; DestDir: "{app}\bin"; Flags: ignoreversion; MinVersion: 0,4.0
-
-Source: "src\windows\makecert.bat"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "extras\tls.cnf"; DestDir: "{app}\bin"; Flags: ignoreversion
-
-Source: "src\modules\*.dll"; DestDir: "{app}\modules"; Flags: ignoreversion
-Source: "src\modules\chanmodes\*.dll"; DestDir: "{app}\modules\chanmodes"; Flags: ignoreversion
-Source: "src\modules\usermodes\*.dll"; DestDir: "{app}\modules\usermodes"; Flags: ignoreversion
-Source: "src\modules\snomasks\*.dll"; DestDir: "{app}\modules\snomasks"; Flags: ignoreversion
-Source: "src\modules\extbans\*.dll"; DestDir: "{app}\modules\extbans"; Flags: ignoreversion
-Source: "src\modules\third\*.dll"; DestDir: "{app}\modules\third"; Flags: ignoreversion skipifsourcedoesntexist
-
-Source: "c:\dev\unrealircd-5-libs\pcre2\bin\pcre*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "c:\dev\unrealircd-5-libs\argon2\vs2015\build\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "c:\dev\unrealircd-5-libs\libsodium\bin\x64\Release\v142\dynamic\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "c:\dev\unrealircd-5-libs\c-ares\msvc\cares\dll-release\cares.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "c:\dev\unrealircd-5-libs\libressl\bin\openssl.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "c:\dev\unrealircd-5-libs\libressl\bin\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "c:\dev\unrealircd-5-libs\setacl.exe"; DestDir: "{app}\tmp"; Flags: ignoreversion
-
-#ifdef USE_CURL
-Source: "c:\dev\unrealircd-5-libs\curl\builds\libcurl-vc-x64-release-dll-ssl-dll-cares-dll-ipv6-obj-lib\libcurl.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "doc\conf\tls\curl-ca-bundle.crt"; DestDir: "{app}\conf\tls"; Flags: ignoreversion
-#endif
 
 [Dirs]
 Name: "{app}\tmp"
@@ -156,7 +163,7 @@ if CurStep = ssPostInstall then
 end;
 
 //*********************************************************************************
-// Checks if SSL/TLS cert file exists
+// Checks if TLS cert file exists
 //*********************************************************************************
 
 procedure CurPageChanged(CurPage: Integer);
diff --git a/src/windows/unrealsvc.c b/src/windows/unrealsvc.c
@@ -83,11 +83,11 @@ int main(int argc, char *argv[]) {
 			uChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &info);
 			CloseServiceHandle(hService);
 			printf("\n[!!!] IMPORTANT: By default the network service user cannot write to the \n"
-			       "UnrealIRCd 5 folder and this will make UnrealIRCd fail to boot without\n"
+			       "UnrealIRCd 6 folder and this will make UnrealIRCd fail to boot without\n"
 				   "writing any meaningful error to the log files.\n"
 				   "You have two options:\n"
 				   "1) Manually grant FULL permissions to NT AUTHORITY\\NetworkService\n"
-				   "   for the UnrealIRCd 5 folder, all its subfolders and files.\n"
+				   "   for the UnrealIRCd 6 folder, all its subfolders and files.\n"
 				   "OR, easier and recommended:\n"
 				   "2) just re-run the UnrealIRCd installer and select 'Install as a service',\n"
 				   "   which sets all the necessary permissions automatically.\n");
diff --git a/src/windows/win.c b/src/windows/win.c
@@ -143,7 +143,7 @@ int GetOSName(char *pszOS)
 
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
 
-   if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
+   if ( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
       return -1;
 
    // Call GetNativeSystemInfo if supported or GetSystemInfo otherwise.
@@ -151,7 +151,7 @@ int GetOSName(char *pszOS)
    pGNSI = (PGNSI) GetProcAddress(
       GetModuleHandle(TEXT("kernel32.dll")), 
       "GetNativeSystemInfo");
-   if(NULL != pGNSI)
+   if (NULL != pGNSI)
       pGNSI(&si);
    else GetSystemInfo(&si);
 
@@ -164,14 +164,14 @@ int GetOSName(char *pszOS)
 
       if ( osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1 )
       {
-         if( osvi.wProductType == VER_NT_WORKSTATION )
+         if ( osvi.wProductType == VER_NT_WORKSTATION )
              StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows 7 "));
          else StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Server 2008 R2 " ));
       }
       
       if ( osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0 )
       {
-         if( osvi.wProductType == VER_NT_WORKSTATION )
+         if ( osvi.wProductType == VER_NT_WORKSTATION )
              StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Vista "));
          else StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Server 2008 " ));
 
@@ -243,13 +243,13 @@ int GetOSName(char *pszOS)
 
       if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2 )
       {
-         if( GetSystemMetrics(SM_SERVERR2) )
+         if ( GetSystemMetrics(SM_SERVERR2) )
             StringCchCat(pszOS, OSVER_SIZE, TEXT( "Windows Server 2003 R2, "));
          else if ( osvi.wSuiteMask==VER_SUITE_STORAGE_SERVER )
             StringCchCat(pszOS, OSVER_SIZE, TEXT( "Windows Storage Server 2003"));
          else if ( osvi.wSuiteMask==VER_SUITE_WH_SERVER )
             StringCchCat(pszOS, OSVER_SIZE, TEXT( "Windows Home Server"));
-         else if( osvi.wProductType == VER_NT_WORKSTATION &&
+         else if ( osvi.wProductType == VER_NT_WORKSTATION &&
                   si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
          {
             StringCchCat(pszOS, OSVER_SIZE, TEXT( "Windows XP Professional x64 Edition"));
@@ -261,17 +261,17 @@ int GetOSName(char *pszOS)
          {
             if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_IA64 )
             {
-                if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+                if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Edition for Itanium-based Systems" ));
-                else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
+                else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Enterprise Edition for Itanium-based Systems" ));
             }
 
             else if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 )
             {
-                if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+                if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter x64 Edition" ));
-                else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
+                else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Enterprise x64 Edition" ));
                 else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Standard x64 Edition" ));
             }
@@ -280,9 +280,9 @@ int GetOSName(char *pszOS)
             {
                 if ( osvi.wSuiteMask & VER_SUITE_COMPUTE_SERVER )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Compute Cluster Edition" ));
-                else if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+                else if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Edition" ));
-                else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
+                else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Enterprise Edition" ));
                 else if ( osvi.wSuiteMask & VER_SUITE_BLADE )
                    StringCchCat(pszOS, OSVER_SIZE, TEXT( "Web Edition" ));
@@ -294,7 +294,7 @@ int GetOSName(char *pszOS)
       if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )
       {
          StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows XP "));
-         if( osvi.wSuiteMask & VER_SUITE_PERSONAL )
+         if ( osvi.wSuiteMask & VER_SUITE_PERSONAL )
             StringCchCat(pszOS, OSVER_SIZE, TEXT( "Home Edition" ));
          else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Professional" ));
       }
@@ -309,9 +309,9 @@ int GetOSName(char *pszOS)
          }
          else 
          {
-            if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+            if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
               StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Server" ));
-            else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
+            else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                StringCchCat(pszOS, OSVER_SIZE, TEXT( "Advanced Server" ));
             else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Server" ));
          }
@@ -319,7 +319,7 @@ int GetOSName(char *pszOS)
 
        // Include service pack (if any) and build number.
 
-      if( _tcslen(osvi.szCSDVersion) > 0 )
+      if ( _tcslen(osvi.szCSDVersion) > 0 )
       {
           StringCchCat(pszOS, OSVER_SIZE, TEXT(" ") );
           StringCchCat(pszOS, OSVER_SIZE, osvi.szCSDVersion);
diff --git a/src/windows/wingui.rc b/src/windows/wingui.rc
@@ -143,9 +143,9 @@ BEGIN
     CONTROL         "",IDC_GRAY,"Button",BS_OWNERDRAW | WS_TABSTOP,137,21,13,12
 END
 
-SSLPASS DIALOG 0, 0, 174, 57
+TLSKEY DIALOG 0, 0, 174, 57
 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "SSL Private Key Password"
+CAPTION "TLS Private Key Password"
 FONT 8, "MS Sans Serif"
 BEGIN
     DEFPUSHBUTTON   "OK",IDOK,33,35,50,14
@@ -261,7 +261,7 @@ BEGIN
         BOTTOMMARGIN, 30
     END
 
-    "SSLPASS", DIALOG
+    "TLSKEY", DIALOG
     BEGIN
         LEFTMARGIN, 7
         RIGHTMARGIN, 167
diff --git a/unrealircd.in b/unrealircd.in
@@ -2,6 +2,11 @@
 
 PID_FILE="@PIDFILE@"
 PID_BACKUP="@PIDFILE@.bak"
+
+# 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"
+
 if [ ! -f @BINDIR@/unrealircd ]; then
 	echo "ERROR: Could not find the IRCd binary (@BINDIR@/unrealircd)"
 	echo "This could mean two things:"
@@ -14,9 +19,6 @@ if [ "$1" = "start" ] ; then
 	if [ -r $PID_FILE ] ; then
 		mv -f $PID_FILE $PID_BACKUP
 	fi
-	# 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"
 
 	# Check if ~/Unrealxxx/unrealircd.conf exists but the file
 	# ~/unrealircd/conf/unrealircd.conf does not.
@@ -132,8 +134,6 @@ elif [ "$1" = "version" ] ; then
 	@BINDIR@/unrealircd -v
 elif [ "$1" = "gencloak" ] ; then
 	@BINDIR@/unrealircd -k
-elif [ "$1" = "upgrade-conf" ] ; then
-	@BINDIR@/unrealircd -U
 elif [ "$1" = "backtrace" ] ; then
 	cd @TMPDIR@
 
@@ -227,9 +227,15 @@ elif [ "$1" = "spki" -o "$1" = "spkifp" ] ; then
 	CERT="@CONFDIR@/tls/server.cert.pem"
 	if [ "$2" != "" ]; then
 		CERT="$2"
+	else
+		echo "NOTE: This script uses the default certificate location (any set::tls settings"
+		echo "are ignored). If this is not what you want then specify a certificate"
+		echo "explicitly like this: ./unrealircd spkifp conf/tls/example.pem"
+		echo ""
 	fi
 	if [ ! -f "$CERT" ]; then
 		echo "Could not open certificate: $CERT"
+		echo "You can specify a certificate like this: ./unrealircd spkifp conf/tls/example.pem"
 		exit 1
 	fi
 	openssl x509 -noout -in "$CERT" -pubkey | openssl asn1parse -noout -inform pem -out @TMPDIR@/tmp.public.key
@@ -295,6 +301,7 @@ elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 	fi
 elif [ "$1" = "upgrade" ] ; then
 	@BINDIR@/unrealircd-upgrade-script $*
+	exit
 elif [ "$1" = "genlinkblock" ] ; then
 	@BINDIR@/unrealircd -L
 else