unrealircd

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

commit 845b48a7563c2feedb5f6446cf75cf7594027cbc
parent 25498230b79a0dd1cdfb58a8d80240023eadc5ff
Author: acidvegas <acid.vegas@acid.vegas>
Date: Sun, 20 Apr 2025 20:57:52 -0400

STREET GANG MOTHEA FUCKAAA

Diffstat:
A.env.example | 6++++++
M.gitignore | 86+++++++++++--------------------------------------------------------------------
DBSDmakefile | 4----
DCONTRIBUTING.md | 5-----
DConfig | 919-------------------------------------------------------------------------------
ADockerfile | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DLICENSE | 340-------------------------------------------------------------------------------
DMakefile.windows | 1408-------------------------------------------------------------------------------
DREADME.md | 10----------
DSECURITY.md | 22----------------------
Aansible/deploy_ircd.yml | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aansible/inventory.example.ini | 12++++++++++++
Ddoc/conf/ircd.motd | 57---------------------------------------------------------
Ddoc/conf/unrealircd.link.conf | 40----------------------------------------
Ddoc/conf/unrealircd.remote.conf | 281-------------------------------------------------------------------------------
Dextras/c-ares.tar.gz | 0
Dextras/pcre2.tar.gz | 0
C.gitignore -> ircd/.gitignore | 0
Aircd/Config | 919+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
RMakefile.in -> ircd/Makefile.in | 0
Rautoconf/Makefile -> ircd/autoconf/Makefile | 0
Rautoconf/config.guess -> ircd/autoconf/config.guess | 0
Rautoconf/config.sub -> ircd/autoconf/config.sub | 0
Rautoconf/install-sh -> ircd/autoconf/install-sh | 0
Rautoconf/m4/ax_check_compile_flag.m4 -> ircd/autoconf/m4/ax_check_compile_flag.m4 | 0
Rautoconf/m4/ax_check_link_flag.m4 -> ircd/autoconf/m4/ax_check_link_flag.m4 | 0
Rautoconf/m4/ax_pthread.m4 -> ircd/autoconf/m4/ax_pthread.m4 | 0
Rautoconf/m4/unreal.m4 -> ircd/autoconf/m4/unreal.m4 | 0
Rautogen.sh -> ircd/autogen.sh | 0
Rconfigure -> ircd/configure | 0
Rconfigure.ac -> ircd/configure.ac | 0
Rdoc/Authors -> ircd/doc/Authors | 0
Rdoc/Config.header -> ircd/doc/Config.header | 0
Rdoc/Donation -> ircd/doc/Donation | 0
Rdoc/KEYS -> ircd/doc/KEYS | 0
Rdoc/RELEASE-NOTES.md -> ircd/doc/RELEASE-NOTES.md | 0
Rdoc/coding-guidelines -> ircd/doc/coding-guidelines | 0
Rdoc/compiling_win32.txt -> ircd/doc/compiling_win32.txt | 0
Rdoc/conf/badwords.conf -> ircd/doc/conf/badwords.conf | 0
Rdoc/conf/except.conf -> ircd/doc/conf/except.conf | 0
Aircd/doc/conf/ircd.motd | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rdoc/conf/ircd.rules -> ircd/doc/conf/ircd.rules | 0
Rdoc/conf/links.conf -> ircd/doc/conf/links.conf | 0
Rdoc/conf/modules.conf -> ircd/doc/conf/modules.conf | 0
Rdoc/conf/opers.conf -> ircd/doc/conf/opers.conf | 0
Rdoc/conf/remote.motd -> ircd/doc/conf/remote.motd | 0
Rdoc/conf/snomasks.conf -> ircd/doc/conf/snomasks.conf | 0
Rdoc/conf/spamfilter.conf -> ircd/doc/conf/spamfilter.conf | 0
Rdoc/conf/tls/curl-ca-bundle.crt -> ircd/doc/conf/tls/curl-ca-bundle.crt | 0
Rdoc/conf/unrealircd.hub.conf -> ircd/doc/conf/unrealircd.hub.conf | 0
Aircd/doc/conf/unrealircd.link.conf | 9+++++++++
Aircd/doc/conf/unrealircd.remote.conf | 278+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rdoc/tao.of.irc -> ircd/doc/tao.of.irc | 0
Rdoc/technical/005.txt -> ircd/doc/technical/005.txt | 0
Rdoc/technical/base64.txt -> ircd/doc/technical/base64.txt | 0
Rdoc/technical/serverprotocol.txt -> ircd/doc/technical/serverprotocol.txt | 0
Rdoc/translations.txt -> ircd/doc/translations.txt | 0
Rextras/.indent.pro -> ircd/extras/.indent.pro | 0
Rextras/VStudioAnalyze.ruleset -> ircd/extras/VStudioAnalyze.ruleset | 0
Rextras/argon2.tar.gz -> ircd/extras/argon2.tar.gz | 0
Rextras/build-tests/nix/build -> ircd/extras/build-tests/nix/build | 0
Rextras/build-tests/nix/configs/default -> ircd/extras/build-tests/nix/configs/default | 0
Rextras/build-tests/nix/run-tests -> ircd/extras/build-tests/nix/run-tests | 0
Rextras/build-tests/nix/run-tests.bbwrapper -> ircd/extras/build-tests/nix/run-tests.bbwrapper | 0
Rextras/build-tests/nix/select-config -> ircd/extras/build-tests/nix/select-config | 0
Rextras/build-tests/windows/build.bat -> ircd/extras/build-tests/windows/build.bat | 0
Rextras/build-tests/windows/compilecmd/vs2019.bat -> ircd/extras/build-tests/windows/compilecmd/vs2019.bat | 0
Rextras/curlinstall -> ircd/extras/curlinstall | 0
Rextras/doxygen/Developers.md -> ircd/extras/doxygen/Developers.md | 0
Rextras/doxygen/Doxyfile -> ircd/extras/doxygen/Doxyfile | 0
Rextras/doxygen/doxygen_custom.css -> ircd/extras/doxygen/doxygen_custom.css | 0
Rextras/doxygen/header.html -> ircd/extras/doxygen/header.html | 0
Rextras/geoip-classic.tar.gz -> ircd/extras/geoip-classic.tar.gz | 0
Rextras/jansson.tar.gz -> ircd/extras/jansson.tar.gz | 0
Rextras/libsodium.tar.gz -> ircd/extras/libsodium.tar.gz | 0
Rextras/patches/patch_spamfilter_conf -> ircd/extras/patches/patch_spamfilter_conf | 0
Rextras/patches/spamfilter.conf.patch -> ircd/extras/patches/spamfilter.conf.patch | 0
Rextras/security/apparmor/unrealircd -> ircd/extras/security/apparmor/unrealircd | 0
Rextras/tests/tls/cipherscan_profiles/baseline.txt -> ircd/extras/tests/tls/cipherscan_profiles/baseline.txt | 0
Rextras/tests/tls/tls-tests -> ircd/extras/tests/tls/tls-tests | 0
Rextras/tls.cnf -> ircd/extras/tls.cnf | 0
Rextras/unreal.supp -> ircd/extras/unreal.supp | 0
Rextras/unrealircd-upgrade-script.in -> ircd/extras/unrealircd-upgrade-script.in | 0
Rextras/wrap-compiler-for-flag-check -> ircd/extras/wrap-compiler-for-flag-check | 0
Rinclude/channel.h -> ircd/include/channel.h | 0
Rinclude/common.h -> ircd/include/common.h | 0
Rinclude/config.h -> ircd/include/config.h | 0
Rinclude/crypt_blowfish.h -> ircd/include/crypt_blowfish.h | 0
Rinclude/dbuf.h -> ircd/include/dbuf.h | 0
Rinclude/dns.h -> ircd/include/dns.h | 0
Rinclude/dynconf.h -> ircd/include/dynconf.h | 0
Rinclude/fdlist.h -> ircd/include/fdlist.h | 0
Rinclude/h.h -> ircd/include/h.h | 0
Rinclude/ircsprintf.h -> ircd/include/ircsprintf.h | 0
Rinclude/license.h -> ircd/include/license.h | 0
Rinclude/list.h -> ircd/include/list.h | 0
Rinclude/mempool.h -> ircd/include/mempool.h | 0
Rinclude/modules.h -> ircd/include/modules.h | 0
Rinclude/modversion.h -> ircd/include/modversion.h | 0
Rinclude/msg.h -> ircd/include/msg.h | 0
Rinclude/numeric.h -> ircd/include/numeric.h | 0
Rinclude/openssl_hostname_validation.h -> ircd/include/openssl_hostname_validation.h | 0
Rinclude/resource.h -> ircd/include/resource.h | 0
Rinclude/setup.h.in -> ircd/include/setup.h.in | 0
Rinclude/struct.h -> ircd/include/struct.h | 0
Rinclude/sys.h -> ircd/include/sys.h | 0
Rinclude/types.h -> ircd/include/types.h | 0
Rinclude/unrealircd.h -> ircd/include/unrealircd.h | 0
Rinclude/version.h -> ircd/include/version.h | 0
Rinclude/whowas.h -> ircd/include/whowas.h | 0
Rinclude/windows/setup.h -> ircd/include/windows/setup.h | 0
Rsrc/Makefile.in -> ircd/src/Makefile.in | 0
Rsrc/aliases.c -> ircd/src/aliases.c | 0
Rsrc/api-channelmode.c -> ircd/src/api-channelmode.c | 0
Rsrc/api-clicap.c -> ircd/src/api-clicap.c | 0
Rsrc/api-command.c -> ircd/src/api-command.c | 0
Rsrc/api-efunctions.c -> ircd/src/api-efunctions.c | 0
Rsrc/api-event.c -> ircd/src/api-event.c | 0
Rsrc/api-extban.c -> ircd/src/api-extban.c | 0
Rsrc/api-history-backend.c -> ircd/src/api-history-backend.c | 0
Rsrc/api-isupport.c -> ircd/src/api-isupport.c | 0
Rsrc/api-messagetag.c -> ircd/src/api-messagetag.c | 0
Rsrc/api-moddata.c -> ircd/src/api-moddata.c | 0
Rsrc/api-rpc.c -> ircd/src/api-rpc.c | 0
Rsrc/api-usermode.c -> ircd/src/api-usermode.c | 0
Rsrc/auth.c -> ircd/src/auth.c | 0
Rsrc/buildmod -> ircd/src/buildmod | 0
Rsrc/channel.c -> ircd/src/channel.c | 0
Rsrc/conf.c -> ircd/src/conf.c | 0
Rsrc/conf_preprocessor.c -> ircd/src/conf_preprocessor.c | 0
Rsrc/crashreport.c -> ircd/src/crashreport.c | 0
Rsrc/crule.c -> ircd/src/crule.c | 0
Rsrc/crypt_blowfish.c -> ircd/src/crypt_blowfish.c | 0
Rsrc/dbuf.c -> ircd/src/dbuf.c | 0
Rsrc/debug.c -> ircd/src/debug.c | 0
Rsrc/dispatch.c -> ircd/src/dispatch.c | 0
Rsrc/dns.c -> ircd/src/dns.c | 0
Rsrc/fdlist.c -> ircd/src/fdlist.c | 0
Rsrc/hash.c -> ircd/src/hash.c | 0
Rsrc/ircd.c -> ircd/src/ircd.c | 0
Rsrc/ircd_vars.c -> ircd/src/ircd_vars.c | 0
Rsrc/ircsprintf.c -> ircd/src/ircsprintf.c | 0
Rsrc/json.c -> ircd/src/json.c | 0
Rsrc/list.c -> ircd/src/list.c | 0
Rsrc/log.c -> ircd/src/log.c | 0
Rsrc/macosx/UnrealIRCd.xcodeproj/project.pbxproj -> ircd/src/macosx/UnrealIRCd.xcodeproj/project.pbxproj | 0
Rsrc/macosx/UnrealIRCd.xcodeproj/project.xcworkspace/contents.xcworkspacedata -> ircd/src/macosx/UnrealIRCd.xcodeproj/project.xcworkspace/contents.xcworkspacedata | 0
Rsrc/macosx/UnrealIRCd/AppDelegate.swift -> ircd/src/macosx/UnrealIRCd/AppDelegate.swift | 0
Rsrc/macosx/UnrealIRCd/AppModel.swift -> ircd/src/macosx/UnrealIRCd/AppModel.swift | 0
Rsrc/macosx/UnrealIRCd/Base.lproj/Main.storyboard -> ircd/src/macosx/UnrealIRCd/Base.lproj/Main.storyboard | 0
Rsrc/macosx/UnrealIRCd/ChangeNotifier.swift -> ircd/src/macosx/UnrealIRCd/ChangeNotifier.swift | 0
Rsrc/macosx/UnrealIRCd/ConfigurationModel.swift -> ircd/src/macosx/UnrealIRCd/ConfigurationModel.swift | 0
Rsrc/macosx/UnrealIRCd/DaemonModel.swift -> ircd/src/macosx/UnrealIRCd/DaemonModel.swift | 0
Rsrc/macosx/UnrealIRCd/Images.xcassets/AppIcon.appiconset/Contents.json -> ircd/src/macosx/UnrealIRCd/Images.xcassets/AppIcon.appiconset/Contents.json | 0
Rsrc/macosx/UnrealIRCd/Info.plist -> ircd/src/macosx/UnrealIRCd/Info.plist | 0
Rsrc/macosx/UnrealIRCd/ViewController.swift -> ircd/src/macosx/UnrealIRCd/ViewController.swift | 0
Rsrc/macosx/UnrealIRCd/WindowController.swift -> ircd/src/macosx/UnrealIRCd/WindowController.swift | 0
Rsrc/macosx/UnrealIRCd/logo.png -> ircd/src/macosx/UnrealIRCd/logo.png | 0
Rsrc/macosx/UnrealIRCd/logo@2x.png -> ircd/src/macosx/UnrealIRCd/logo@2x.png | 0
Rsrc/macosx/UnrealIRCdTests/Info.plist -> ircd/src/macosx/UnrealIRCdTests/Info.plist | 0
Rsrc/macosx/UnrealIRCdTests/UnrealIRCdTests.swift -> ircd/src/macosx/UnrealIRCdTests/UnrealIRCdTests.swift | 0
Rsrc/match.c -> ircd/src/match.c | 0
Rsrc/mempool.c -> ircd/src/mempool.c | 0
Rsrc/misc.c -> ircd/src/misc.c | 0
Rsrc/modulemanager.c -> ircd/src/modulemanager.c | 0
Rsrc/modules.c -> ircd/src/modules.c | 0
Rsrc/modules/Makefile.in -> ircd/src/modules/Makefile.in | 0
Rsrc/modules/account-notify.c -> ircd/src/modules/account-notify.c | 0
Rsrc/modules/account-tag.c -> ircd/src/modules/account-tag.c | 0
Rsrc/modules/addmotd.c -> ircd/src/modules/addmotd.c | 0
Rsrc/modules/addomotd.c -> ircd/src/modules/addomotd.c | 0
Rsrc/modules/admin.c -> ircd/src/modules/admin.c | 0
Rsrc/modules/antimixedutf8.c -> ircd/src/modules/antimixedutf8.c | 0
Rsrc/modules/antirandom.c -> ircd/src/modules/antirandom.c | 0
Rsrc/modules/authprompt.c -> ircd/src/modules/authprompt.c | 0
Rsrc/modules/away.c -> ircd/src/modules/away.c | 0
Rsrc/modules/batch.c -> ircd/src/modules/batch.c | 0
Rsrc/modules/blacklist.c -> ircd/src/modules/blacklist.c | 0
Rsrc/modules/bot-tag.c -> ircd/src/modules/bot-tag.c | 0
Rsrc/modules/botmotd.c -> ircd/src/modules/botmotd.c | 0
Rsrc/modules/cap.c -> ircd/src/modules/cap.c | 0
Rsrc/modules/certfp.c -> ircd/src/modules/certfp.c | 0
Rsrc/modules/chanmodes/Makefile.in -> ircd/src/modules/chanmodes/Makefile.in | 0
Rsrc/modules/chanmodes/censor.c -> ircd/src/modules/chanmodes/censor.c | 0
Rsrc/modules/chanmodes/chanadmin.c -> ircd/src/modules/chanmodes/chanadmin.c | 0
Rsrc/modules/chanmodes/chanop.c -> ircd/src/modules/chanmodes/chanop.c | 0
Rsrc/modules/chanmodes/chanowner.c -> ircd/src/modules/chanmodes/chanowner.c | 0
Rsrc/modules/chanmodes/delayjoin.c -> ircd/src/modules/chanmodes/delayjoin.c | 0
Rsrc/modules/chanmodes/floodprot.c -> ircd/src/modules/chanmodes/floodprot.c | 0
Rsrc/modules/chanmodes/halfop.c -> ircd/src/modules/chanmodes/halfop.c | 0
Rsrc/modules/chanmodes/history.c -> ircd/src/modules/chanmodes/history.c | 0
Rsrc/modules/chanmodes/inviteonly.c -> ircd/src/modules/chanmodes/inviteonly.c | 0
Rsrc/modules/chanmodes/isregistered.c -> ircd/src/modules/chanmodes/isregistered.c | 0
Rsrc/modules/chanmodes/issecure.c -> ircd/src/modules/chanmodes/issecure.c | 0
Rsrc/modules/chanmodes/key.c -> ircd/src/modules/chanmodes/key.c | 0
Rsrc/modules/chanmodes/limit.c -> ircd/src/modules/chanmodes/limit.c | 0
Rsrc/modules/chanmodes/link.c -> ircd/src/modules/chanmodes/link.c | 0
Rsrc/modules/chanmodes/moderated.c -> ircd/src/modules/chanmodes/moderated.c | 0
Rsrc/modules/chanmodes/nocolor.c -> ircd/src/modules/chanmodes/nocolor.c | 0
Rsrc/modules/chanmodes/noctcp.c -> ircd/src/modules/chanmodes/noctcp.c | 0
Rsrc/modules/chanmodes/noexternalmsgs.c -> ircd/src/modules/chanmodes/noexternalmsgs.c | 0
Rsrc/modules/chanmodes/noinvite.c -> ircd/src/modules/chanmodes/noinvite.c | 0
Rsrc/modules/chanmodes/nokick.c -> ircd/src/modules/chanmodes/nokick.c | 0
Rsrc/modules/chanmodes/noknock.c -> ircd/src/modules/chanmodes/noknock.c | 0
Rsrc/modules/chanmodes/nonickchange.c -> ircd/src/modules/chanmodes/nonickchange.c | 0
Rsrc/modules/chanmodes/nonotice.c -> ircd/src/modules/chanmodes/nonotice.c | 0
Rsrc/modules/chanmodes/operonly.c -> ircd/src/modules/chanmodes/operonly.c | 0
Rsrc/modules/chanmodes/permanent.c -> ircd/src/modules/chanmodes/permanent.c | 0
Rsrc/modules/chanmodes/private.c -> ircd/src/modules/chanmodes/private.c | 0
Rsrc/modules/chanmodes/regonly.c -> ircd/src/modules/chanmodes/regonly.c | 0
Aircd/src/modules/chanmodes/regonlyspeak.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chanmodes/secret.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chanmodes/secureonly.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chanmodes/stripcolor.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chanmodes/topiclimit.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chanmodes/voice.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/channel-context.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/channeldb.c | 567+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/charsys.c | 1295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chathistory.c | 403+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chghost.c | 372+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chgident.c | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/chgname.c | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/clienttagdeny.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/cloak_md5.c | 422+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/cloak_none.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/cloak_sha256.c | 411+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/close.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/connect-flood.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/connect.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/connthrottle.c | 600+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/creationtime.c | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/cycle.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/dccallow.c | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/dccdeny.c | 882+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/echo-message.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/eos.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/Makefile.in | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/account.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/certfp.c | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/country.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/flood.c | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/inchannel.c | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/join.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/msgbypass.c | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/nickchange.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/operclass.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/partmsg.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/quiet.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/realname.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/securitygroup.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/textban.c | 533+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extbans/timedban.c | 506+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extended-monitor.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/extjwt.c | 1151+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/geoip-tag.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/geoip_base.c | 351+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/geoip_classic.c | 297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/geoip_csv.c | 838+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/geoip_maxmind.c | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/globops.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/help.c | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/hideserver.c | 467+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/history.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/history_backend_mem.c | 1618+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/history_backend_null.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/ident_lookup.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/invite.c | 542+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/ircops.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/ison.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/issued-by-tag.c | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/join.c | 615+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/jointhrottle.c | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/json-log-tag.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/jumpserver.c | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/kick.c | 369+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/kill.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/knock.c | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/labeled-response.c | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/lag.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/link-security.c | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/links.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/list.c | 468+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/locops.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/lusers.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/map.c | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/max-unknown-connections-per-ip.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/md.c | 527+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/message-ids.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/message-tags.c | 311+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/message.c | 710+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/mkpasswd.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/mode.c | 1569+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/module.def | 5+++++
Aircd/src/modules/monitor.c | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/motd.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/names.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/netinfo.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/nick.c | 1321+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/nocodes.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/oper.c | 381+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/operinfo.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/opermotd.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/part.c | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/pass.c | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/pingpong.c | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/plaintext-policy.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/protoctl.c | 409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/quit.c | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/real-quit-reason.c | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/reply-tag.c | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/reputation.c | 1379+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/require-module.c | 576+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/restrict-commands.c | 409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rmtkl.c | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/Makefile.in | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/channel.c | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/log.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/name_ban.c | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/rpc.c | 1922+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/server.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/server_ban.c | 342+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/server_ban_exception.c | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/spamfilter.c | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/stats.c | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/user.c | 697+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rpc/whowas.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/rules.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sajoin.c | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/samode.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sapart.c | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sasl.c | 412+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sdesc.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sendsno.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sendumode.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/server-time.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/server.c | 2259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sethost.c | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/setident.c | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/setname.c | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/silence.c | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sinfo.c | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sjoin.c | 821+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/slog.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sqline.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/squit.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sreply.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/staff.c | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/standard-replies.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/starttls.c | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/stats.c | 1057+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/sts.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsjoin.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svskill.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svslogin.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svslusers.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsmode.c | 640+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsmotd.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsnick.c | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsnline.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsnolag.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svsnoop.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svso.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svspart.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svssilence.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svssno.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/svswatch.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/swhois.c | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/targetfloodprot.c | 323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/third/Makefile.in | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/time.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/tkl.c | 5437+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/tkldb.c | 761+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/tline.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/tls_antidos.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/tls_cipher.c | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/topic.c | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/trace.c | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/tsctl.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/typing-indicator.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/umode2.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/unreal_server_compat.c | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/unsqline.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/user.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/userhost-tag.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/userhost.c | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/userip-tag.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/userip.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/Makefile.in | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/bot.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/censor.c | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/noctcp.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/nokick.c | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/privacy.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/privdeaf.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/regonlymsg.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/secureonlymsg.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/servicebot.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/showwhois.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/usermodes/wallops.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/vhost.c | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/watch-backend.c | 392+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/watch.c | 429+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/webirc.c | 482+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/webredir.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/webserver.c | 675+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/websocket.c | 688+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/websocket_common.c | 523+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/who_old.c | 842+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/whois.c | 663+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/whowas.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/whowasdb.c | 641+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/modules/whox.c | 988+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/openssl_hostname_validation.c | 402+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/operclass.c | 348+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/parse.c | 782+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/proc_io_client.c | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/proc_io_server.c | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/random.c | 520+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/scache.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/securitygroup.c | 864+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/send.c | 1193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/serv.c | 1264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/socket.c | 1390+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/support.c | 1534+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/tls.c | 1452+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/unrealdb.c | 1174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/unrealircdctl.c | 268+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/url_curl.c | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/url_unreal.c | 1086+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/user.c | 1007+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/utf8.c | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/version.c.SH | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/whowas.c | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/Icon1.ico | 0
Aircd/src/windows/UnrealIRCd.exe.manifest | 22++++++++++++++++++++++
Aircd/src/windows/bar.bmp | 0
Aircd/src/windows/compilerhelp.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/config.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/def-clean.c | 39+++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/editor.c | 769+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/gpl.rtf | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/gplplusssl.rtf | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/gui.c | 1051+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/hand.CUR | 0
Aircd/src/windows/makecert.bat | 6++++++
Aircd/src/windows/resource.h | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/rtf.c | 878+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/service.c | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/toolbar.bmp | 0
Aircd/src/windows/unreal.bmp | 0
Aircd/src/windows/unrealinst.iss | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/unrealircdctl.exe.manifest | 10++++++++++
Aircd/src/windows/unrealsvc.c | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/unrealsvc.rc | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/win.c | 341+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/win.h | 43+++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/windebug.c | 370+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/src/windows/wingui.rc | 410+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aircd/unrealircd.in | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asetup.sh | 45+++++++++++++++++++++++++++++++++++++++++++++
Dsrc/modules/chanmodes/regonlyspeak.c | 108-------------------------------------------------------------------------------
Dsrc/modules/chanmodes/secret.c | 73-------------------------------------------------------------------------
Dsrc/modules/chanmodes/secureonly.c | 190-------------------------------------------------------------------------------
Dsrc/modules/chanmodes/stripcolor.c | 131-------------------------------------------------------------------------------
Dsrc/modules/chanmodes/topiclimit.c | 82-------------------------------------------------------------------------------
Dsrc/modules/chanmodes/voice.c | 88-------------------------------------------------------------------------------
Dsrc/modules/channel-context.c | 95-------------------------------------------------------------------------------
Dsrc/modules/channeldb.c | 567-------------------------------------------------------------------------------
Dsrc/modules/charsys.c | 1295-------------------------------------------------------------------------------
Dsrc/modules/chathistory.c | 403-------------------------------------------------------------------------------
Dsrc/modules/chghost.c | 372-------------------------------------------------------------------------------
Dsrc/modules/chgident.c | 164-------------------------------------------------------------------------------
Dsrc/modules/chgname.c | 134-------------------------------------------------------------------------------
Dsrc/modules/clienttagdeny.c | 77-----------------------------------------------------------------------------
Dsrc/modules/cloak_md5.c | 422-------------------------------------------------------------------------------
Dsrc/modules/cloak_none.c | 87-------------------------------------------------------------------------------
Dsrc/modules/cloak_sha256.c | 411-------------------------------------------------------------------------------
Dsrc/modules/close.c | 82-------------------------------------------------------------------------------
Dsrc/modules/connect-flood.c | 89-------------------------------------------------------------------------------
Dsrc/modules/connect.c | 123-------------------------------------------------------------------------------
Dsrc/modules/connthrottle.c | 600-------------------------------------------------------------------------------
Dsrc/modules/creationtime.c | 100-------------------------------------------------------------------------------
Dsrc/modules/cycle.c | 83-------------------------------------------------------------------------------
Dsrc/modules/dccallow.c | 257-------------------------------------------------------------------------------
Dsrc/modules/dccdeny.c | 882-------------------------------------------------------------------------------
Dsrc/modules/echo-message.c | 107-------------------------------------------------------------------------------
Dsrc/modules/eos.c | 71-----------------------------------------------------------------------
Dsrc/modules/extbans/Makefile.in | 56--------------------------------------------------------
Dsrc/modules/extbans/account.c | 119-------------------------------------------------------------------------------
Dsrc/modules/extbans/certfp.c | 142-------------------------------------------------------------------------------
Dsrc/modules/extbans/country.c | 141-------------------------------------------------------------------------------
Dsrc/modules/extbans/flood.c | 187-------------------------------------------------------------------------------
Dsrc/modules/extbans/inchannel.c | 168-------------------------------------------------------------------------------
Dsrc/modules/extbans/join.c | 73-------------------------------------------------------------------------
Dsrc/modules/extbans/msgbypass.c | 224-------------------------------------------------------------------------------
Dsrc/modules/extbans/nickchange.c | 76----------------------------------------------------------------------------
Dsrc/modules/extbans/operclass.c | 101-------------------------------------------------------------------------------
Dsrc/modules/extbans/partmsg.c | 74--------------------------------------------------------------------------
Dsrc/modules/extbans/quiet.c | 73-------------------------------------------------------------------------
Dsrc/modules/extbans/realname.c | 114-------------------------------------------------------------------------------
Dsrc/modules/extbans/securitygroup.c | 152-------------------------------------------------------------------------------
Dsrc/modules/extbans/textban.c | 533-------------------------------------------------------------------------------
Dsrc/modules/extbans/timedban.c | 506-------------------------------------------------------------------------------
Dsrc/modules/extended-monitor.c | 153-------------------------------------------------------------------------------
Dsrc/modules/extjwt.c | 1151-------------------------------------------------------------------------------
Dsrc/modules/geoip-tag.c | 108-------------------------------------------------------------------------------
Dsrc/modules/geoip_base.c | 351-------------------------------------------------------------------------------
Dsrc/modules/geoip_classic.c | 297-------------------------------------------------------------------------------
Dsrc/modules/geoip_csv.c | 838-------------------------------------------------------------------------------
Dsrc/modules/geoip_maxmind.c | 239-------------------------------------------------------------------------------
Dsrc/modules/globops.c | 85-------------------------------------------------------------------------------
Dsrc/modules/help.c | 145-------------------------------------------------------------------------------
Dsrc/modules/hideserver.c | 467-------------------------------------------------------------------------------
Dsrc/modules/history.c | 136-------------------------------------------------------------------------------
Dsrc/modules/history_backend_mem.c | 1618-------------------------------------------------------------------------------
Dsrc/modules/history_backend_null.c | 74--------------------------------------------------------------------------
Dsrc/modules/ident_lookup.c | 256-------------------------------------------------------------------------------
Dsrc/modules/invite.c | 542-------------------------------------------------------------------------------
Dsrc/modules/ircops.c | 141-------------------------------------------------------------------------------
Dsrc/modules/ison.c | 105-------------------------------------------------------------------------------
Dsrc/modules/issued-by-tag.c | 155-------------------------------------------------------------------------------
Dsrc/modules/join.c | 615-------------------------------------------------------------------------------
Dsrc/modules/jointhrottle.c | 248-------------------------------------------------------------------------------
Dsrc/modules/json-log-tag.c | 97-------------------------------------------------------------------------------
Dsrc/modules/jumpserver.c | 271-------------------------------------------------------------------------------
Dsrc/modules/kick.c | 369-------------------------------------------------------------------------------
Dsrc/modules/kill.c | 184-------------------------------------------------------------------------------
Dsrc/modules/knock.c | 160-------------------------------------------------------------------------------
Dsrc/modules/labeled-response.c | 399-------------------------------------------------------------------------------
Dsrc/modules/lag.c | 85-------------------------------------------------------------------------------
Dsrc/modules/link-security.c | 281-------------------------------------------------------------------------------
Dsrc/modules/links.c | 77-----------------------------------------------------------------------------
Dsrc/modules/list.c | 468-------------------------------------------------------------------------------
Dsrc/modules/locops.c | 74--------------------------------------------------------------------------
Dsrc/modules/lusers.c | 96-------------------------------------------------------------------------------
Dsrc/modules/map.c | 243-------------------------------------------------------------------------------
Dsrc/modules/max-unknown-connections-per-ip.c | 96-------------------------------------------------------------------------------
Dsrc/modules/md.c | 527-------------------------------------------------------------------------------
Dsrc/modules/message-ids.c | 156-------------------------------------------------------------------------------
Dsrc/modules/message-tags.c | 311-------------------------------------------------------------------------------
Dsrc/modules/message.c | 710-------------------------------------------------------------------------------
Dsrc/modules/mkpasswd.c | 106-------------------------------------------------------------------------------
Dsrc/modules/mode.c | 1569-------------------------------------------------------------------------------
Dsrc/modules/module.def | 5-----
Dsrc/modules/monitor.c | 232-------------------------------------------------------------------------------
Dsrc/modules/motd.c | 123-------------------------------------------------------------------------------
Dsrc/modules/names.c | 190-------------------------------------------------------------------------------
Dsrc/modules/netinfo.c | 141-------------------------------------------------------------------------------
Dsrc/modules/nick.c | 1321-------------------------------------------------------------------------------
Dsrc/modules/nocodes.c | 102-------------------------------------------------------------------------------
Dsrc/modules/oper.c | 381-------------------------------------------------------------------------------
Dsrc/modules/operinfo.c | 99-------------------------------------------------------------------------------
Dsrc/modules/opermotd.c | 90-------------------------------------------------------------------------------
Dsrc/modules/part.c | 216-------------------------------------------------------------------------------
Dsrc/modules/pass.c | 84-------------------------------------------------------------------------------
Dsrc/modules/pingpong.c | 208-------------------------------------------------------------------------------
Dsrc/modules/plaintext-policy.c | 75---------------------------------------------------------------------------
Dsrc/modules/protoctl.c | 409-------------------------------------------------------------------------------
Dsrc/modules/quit.c | 177-------------------------------------------------------------------------------
Dsrc/modules/real-quit-reason.c | 81-------------------------------------------------------------------------------
Dsrc/modules/reply-tag.c | 116-------------------------------------------------------------------------------
Dsrc/modules/reputation.c | 1379-------------------------------------------------------------------------------
Dsrc/modules/require-module.c | 576-------------------------------------------------------------------------------
Dsrc/modules/restrict-commands.c | 409-------------------------------------------------------------------------------
Dsrc/modules/rmtkl.c | 295-------------------------------------------------------------------------------
Dsrc/modules/rpc/Makefile.in | 55-------------------------------------------------------
Dsrc/modules/rpc/channel.c | 230-------------------------------------------------------------------------------
Dsrc/modules/rpc/log.c | 139-------------------------------------------------------------------------------
Dsrc/modules/rpc/name_ban.c | 241-------------------------------------------------------------------------------
Dsrc/modules/rpc/rpc.c | 1922-------------------------------------------------------------------------------
Dsrc/modules/rpc/server.c | 416-------------------------------------------------------------------------------
Dsrc/modules/rpc/server_ban.c | 342-------------------------------------------------------------------------------
Dsrc/modules/rpc/server_ban_exception.c | 314-------------------------------------------------------------------------------
Dsrc/modules/rpc/spamfilter.c | 326-------------------------------------------------------------------------------
Dsrc/modules/rpc/stats.c | 214-------------------------------------------------------------------------------
Dsrc/modules/rpc/user.c | 697-------------------------------------------------------------------------------
Dsrc/modules/rpc/whowas.c | 148-------------------------------------------------------------------------------
Dsrc/modules/rules.c | 89-------------------------------------------------------------------------------
Dsrc/modules/sajoin.c | 294-------------------------------------------------------------------------------
Dsrc/modules/samode.c | 89-------------------------------------------------------------------------------
Dsrc/modules/sapart.c | 210-------------------------------------------------------------------------------
Dsrc/modules/sasl.c | 412-------------------------------------------------------------------------------
Dsrc/modules/sdesc.c | 92-------------------------------------------------------------------------------
Dsrc/modules/sendsno.c | 101-------------------------------------------------------------------------------
Dsrc/modules/sendumode.c | 95-------------------------------------------------------------------------------
Dsrc/modules/server-time.c | 99-------------------------------------------------------------------------------
Dsrc/modules/server.c | 2259-------------------------------------------------------------------------------
Dsrc/modules/sethost.c | 151------------------------------------------------------------------------------
Dsrc/modules/setident.c | 125-------------------------------------------------------------------------------
Dsrc/modules/setname.c | 162-------------------------------------------------------------------------------
Dsrc/modules/silence.c | 241-------------------------------------------------------------------------------
Dsrc/modules/sinfo.c | 171-------------------------------------------------------------------------------
Dsrc/modules/sjoin.c | 821-------------------------------------------------------------------------------
Dsrc/modules/slog.c | 190-------------------------------------------------------------------------------
Dsrc/modules/sqline.c | 87-------------------------------------------------------------------------------
Dsrc/modules/squit.c | 153-------------------------------------------------------------------------------
Dsrc/modules/sreply.c | 87-------------------------------------------------------------------------------
Dsrc/modules/staff.c | 178-------------------------------------------------------------------------------
Dsrc/modules/standard-replies.c | 58----------------------------------------------------------
Dsrc/modules/starttls.c | 121-------------------------------------------------------------------------------
Dsrc/modules/stats.c | 1057-------------------------------------------------------------------------------
Dsrc/modules/sts.c | 111-------------------------------------------------------------------------------
Dsrc/modules/svsjoin.c | 97-------------------------------------------------------------------------------
Dsrc/modules/svskill.c | 88-------------------------------------------------------------------------------
Dsrc/modules/svslogin.c | 102-------------------------------------------------------------------------------
Dsrc/modules/svslusers.c | 79-------------------------------------------------------------------------------
Dsrc/modules/svsmode.c | 640-------------------------------------------------------------------------------
Dsrc/modules/svsmotd.c | 112-------------------------------------------------------------------------------
Dsrc/modules/svsnick.c | 122-------------------------------------------------------------------------------
Dsrc/modules/svsnline.c | 157-------------------------------------------------------------------------------
Dsrc/modules/svsnolag.c | 105-------------------------------------------------------------------------------
Dsrc/modules/svsnoop.c | 97-------------------------------------------------------------------------------
Dsrc/modules/svso.c | 141-------------------------------------------------------------------------------
Dsrc/modules/svspart.c | 90-------------------------------------------------------------------------------
Dsrc/modules/svssilence.c | 98-------------------------------------------------------------------------------
Dsrc/modules/svssno.c | 111-------------------------------------------------------------------------------
Dsrc/modules/svswatch.c | 80-------------------------------------------------------------------------------
Dsrc/modules/swhois.c | 110-------------------------------------------------------------------------------
Dsrc/modules/targetfloodprot.c | 323-------------------------------------------------------------------------------
Dsrc/modules/third/Makefile.in | 50--------------------------------------------------
Dsrc/modules/time.c | 66------------------------------------------------------------------
Dsrc/modules/tkl.c | 5437-------------------------------------------------------------------------------
Dsrc/modules/tkldb.c | 761-------------------------------------------------------------------------------
Dsrc/modules/tline.c | 87-------------------------------------------------------------------------------
Dsrc/modules/tls_antidos.c | 111-------------------------------------------------------------------------------
Dsrc/modules/tls_cipher.c | 118-------------------------------------------------------------------------------
Dsrc/modules/topic.c | 317-------------------------------------------------------------------------------
Dsrc/modules/trace.c | 234-------------------------------------------------------------------------------
Dsrc/modules/tsctl.c | 74--------------------------------------------------------------------------
Dsrc/modules/typing-indicator.c | 105-------------------------------------------------------------------------------
Dsrc/modules/umode2.c | 74--------------------------------------------------------------------------
Dsrc/modules/unreal_server_compat.c | 319-------------------------------------------------------------------------------
Dsrc/modules/unsqline.c | 74--------------------------------------------------------------------------
Dsrc/modules/user.c | 113-------------------------------------------------------------------------------
Dsrc/modules/userhost-tag.c | 111-------------------------------------------------------------------------------
Dsrc/modules/userhost.c | 115-------------------------------------------------------------------------------
Dsrc/modules/userip-tag.c | 111-------------------------------------------------------------------------------
Dsrc/modules/userip.c | 126-------------------------------------------------------------------------------
Dsrc/modules/usermodes/Makefile.in | 54------------------------------------------------------
Dsrc/modules/usermodes/bot.c | 105-------------------------------------------------------------------------------
Dsrc/modules/usermodes/censor.c | 271-------------------------------------------------------------------------------
Dsrc/modules/usermodes/noctcp.c | 86-------------------------------------------------------------------------------
Dsrc/modules/usermodes/nokick.c | 100-------------------------------------------------------------------------------
Dsrc/modules/usermodes/privacy.c | 69---------------------------------------------------------------------
Dsrc/modules/usermodes/privdeaf.c | 59-----------------------------------------------------------
Dsrc/modules/usermodes/regonlymsg.c | 72------------------------------------------------------------------------
Dsrc/modules/usermodes/secureonlymsg.c | 86-------------------------------------------------------------------------------
Dsrc/modules/usermodes/servicebot.c | 144-------------------------------------------------------------------------------
Dsrc/modules/usermodes/showwhois.c | 76----------------------------------------------------------------------------
Dsrc/modules/usermodes/wallops.c | 111-------------------------------------------------------------------------------
Dsrc/modules/vhost.c | 182-------------------------------------------------------------------------------
Dsrc/modules/watch-backend.c | 392-------------------------------------------------------------------------------
Dsrc/modules/watch.c | 429-------------------------------------------------------------------------------
Dsrc/modules/webirc.c | 482-------------------------------------------------------------------------------
Dsrc/modules/webredir.c | 201-------------------------------------------------------------------------------
Dsrc/modules/webserver.c | 675-------------------------------------------------------------------------------
Dsrc/modules/websocket.c | 688-------------------------------------------------------------------------------
Dsrc/modules/websocket_common.c | 523-------------------------------------------------------------------------------
Dsrc/modules/who_old.c | 842-------------------------------------------------------------------------------
Dsrc/modules/whois.c | 663-------------------------------------------------------------------------------
Dsrc/modules/whowas.c | 133-------------------------------------------------------------------------------
Dsrc/modules/whowasdb.c | 641-------------------------------------------------------------------------------
Dsrc/modules/whox.c | 988-------------------------------------------------------------------------------
Dsrc/openssl_hostname_validation.c | 402-------------------------------------------------------------------------------
Dsrc/operclass.c | 348-------------------------------------------------------------------------------
Dsrc/parse.c | 782-------------------------------------------------------------------------------
Dsrc/proc_io_client.c | 199-------------------------------------------------------------------------------
Dsrc/proc_io_server.c | 191-------------------------------------------------------------------------------
Dsrc/random.c | 520-------------------------------------------------------------------------------
Dsrc/scache.c | 99-------------------------------------------------------------------------------
Dsrc/securitygroup.c | 864-------------------------------------------------------------------------------
Dsrc/send.c | 1193-------------------------------------------------------------------------------
Dsrc/serv.c | 1264-------------------------------------------------------------------------------
Dsrc/socket.c | 1390-------------------------------------------------------------------------------
Dsrc/support.c | 1534-------------------------------------------------------------------------------
Dsrc/tls.c | 1452-------------------------------------------------------------------------------
Dsrc/unrealdb.c | 1174-------------------------------------------------------------------------------
Dsrc/unrealircdctl.c | 268-------------------------------------------------------------------------------
Dsrc/url_curl.c | 340-------------------------------------------------------------------------------
Dsrc/url_unreal.c | 1086-------------------------------------------------------------------------------
Dsrc/user.c | 1007-------------------------------------------------------------------------------
Dsrc/utf8.c | 250-------------------------------------------------------------------------------
Dsrc/version.c.SH | 282-------------------------------------------------------------------------------
Dsrc/whowas.c | 208-------------------------------------------------------------------------------
Dsrc/windows/Icon1.ico | 0
Dsrc/windows/UnrealIRCd.exe.manifest | 22----------------------
Dsrc/windows/bar.bmp | 0
Dsrc/windows/compilerhelp.c | 55-------------------------------------------------------
Dsrc/windows/config.c | 57---------------------------------------------------------
Dsrc/windows/def-clean.c | 39---------------------------------------
Dsrc/windows/editor.c | 769-------------------------------------------------------------------------------
Dsrc/windows/gpl.rtf | 98-------------------------------------------------------------------------------
Dsrc/windows/gplplusssl.rtf | 197-------------------------------------------------------------------------------
Dsrc/windows/gui.c | 1051-------------------------------------------------------------------------------
Dsrc/windows/hand.CUR | 0
Dsrc/windows/makecert.bat | 6------
Dsrc/windows/resource.h | 108-------------------------------------------------------------------------------
Dsrc/windows/rtf.c | 878-------------------------------------------------------------------------------
Dsrc/windows/service.c | 140-------------------------------------------------------------------------------
Dsrc/windows/toolbar.bmp | 0
Dsrc/windows/unreal.bmp | 0
Dsrc/windows/unrealinst.iss | 204-------------------------------------------------------------------------------
Dsrc/windows/unrealircdctl.exe.manifest | 10----------
Dsrc/windows/unrealsvc.c | 213-------------------------------------------------------------------------------
Dsrc/windows/unrealsvc.rc | 99-------------------------------------------------------------------------------
Dsrc/windows/win.c | 341-------------------------------------------------------------------------------
Dsrc/windows/win.h | 43-------------------------------------------
Dsrc/windows/windebug.c | 370-------------------------------------------------------------------------------
Dsrc/windows/wingui.rc | 410-------------------------------------------------------------------------------
Dunrealircd.in | 340-------------------------------------------------------------------------------

711 files changed, 83913 insertions(+), 85531 deletions(-)

diff --git a/.env.example b/.env.example
@@ -0,0 +1,6 @@
+# SuperNETS UnrealIRCd - Developed by acidvegas (https://github.com/supernets/unrealircd)
+# unrealircd/.env.example
+
+LEAF_NAME="wildwest"
+LEAF_PORT="5000"
+REMOTE_INCLUDE_URL="https://hub.supernets.org:5000"
diff --git a/.gitignore b/.gitignore
@@ -1,74 +1,11 @@
-# Ignore configure step
-aclocal.m4
-autom4te.cache
-config.log
-conftest.*
-config.settings
-extras/pcre2*
-extras/c-ares*
-config.status
-extras/ircdcron/ircd.cron
-extras/ircdcron/ircdchk
-src/modules/chanmodes/Makefile
-src/modules/extbans/Makefile
-src/modules/usermodes/Makefile
-src/modules/Makefile
-src/modules/third/Makefile
-/Makefile
-/src/Makefile
-/unrealircd
-include/setup.h
-
-# Ignore tags file
-tags
-
-# Ignore editor files
-*\#*
-*~
-
-# Ignore SSL Stuff
-server.cert.pem
-server.key.pem
-server.req.pem
-tls.rnd
-
-# Ignores for platform stuff
-.DS_Store
-
-# Ignores for build artifacts
-*.so
-*.o
-*.dSYM
-*.dylib
-src/ircd
-src/version.c
-src/include
-
-# Ignores for mac stuff
-## Various settings
-*.pbxuser
-!default.pbxuser
-*.mode1v3
-!default.mode1v3
-*.mode2v3
-!default.mode2v3
-*.perspectivev3
-!default.perspectivev3
-xcuserdata
-
-## Other
-*.xccheckout
-*.moved-aside
-*.xcuserstate
-*.xcscmblueprint
-
-## Obj-C/Swift specific
-*.hmap
-*.ipa
-
-src/macosx/build/
-DerivedData
-src/macosx/pods/
-
-# Doxygen generated files
-doc/doxygen/
+# SuperNETS UnrealIRCd - Developed by acidvegas (https://github.com/supernets/unrealircd)
+# unrealircd/.gitignore
+
+.env
+assets/*
+__pycache__
+*.retry
+inventory.ini
+*.log
+.log
+ansible/*.private
+\ No newline at end of file
diff --git a/BSDmakefile b/BSDmakefile
@@ -1,4 +0,0 @@
-.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
@@ -1,5 +0,0 @@
-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,919 +0,0 @@
-#!/bin/sh
-#
-# Config script for UnrealIRCd
-# (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
-# copyright headers stay intact
-#
-#
-# Rewritten completely to be an interface to autoconf by codemastr
-# This was inspired by the config by Michael Graff (explorer@flame.org)
-# but was written from scratch
-
-# In order to be faster than the old Config, this assumes that all information
-# in the cache file is valid and therefore doesn't check it, so if you messed with
-# default values thats your problem :P
-
-# some bits edited by baafie on March 17 2004, every change marked.
-
-# Remove trailing slash in paths (if any)
-FIX_PATHNAMES () {
-	BASEPATH="${BASEPATH%/}"
-	BINDIR="${BINDIR%/}"
-	DATADIR="${DATADIR%/}"
-	CONFDIR="${CONFDIR%/}"
-	MODULESDIR="${MODULESDIR%/}"
-	LOGDIR="${LOGDIR%/}"
-	CACHEDIR="${CACHEDIR%/}"
-	DOCDIR="${DOCDIR%/}"
-	TMPDIR="${TMPDIR%/}"
-	PRIVATELIBDIR="${PRIVATELIBDIR%/}"
-	SSLDIR="${SSLDIR%/}"
-	CURLDIR="${CURLDIR%/}"
-}
-
-# Create and run the ./configure command with the appropriate
-# options based on the users settings.
-RUN_CONFIGURE () {
-ARG=" "
-
-if [ -z "$BINDIR" -o -z "$DATADIR" -o -z "$CONFDIR" -o -z "$MODULESDIR" -o -z "$LOGDIR" -o -z "$CACHEDIR" -o -z "$DOCDIR" -o -z "$TMPDIR" -o -z "$PRIVATELIBDIR" ]; then
-	echo "Sorry './Config -quick' cannot be used because your 'config.settings'"
-	echo "file either does not exist or is from an old UnrealIRCd version"
-	echo "(older than UnrealIRCd 5.0.0)."
-	echo ""
-	echo "Please run './Config' without -quick and answer all questions."
-	echo ""
-	exit
-fi
-
-
-mkdir -p $TMPDIR
-mkdir -p $PRIVATELIBDIR
-
-# Do this even if we're not in advanced mode
-if [ "$ADVANCED" = "1" ] ; then
-if [ "$NOOPEROVERRIDE" = "1" ] ; then
-	ARG="$ARG--with-no-operoverride "
-fi
-if [ "$OPEROVERRIDEVERIFY" = "1" ] ; then
-	ARG="$ARG--with-operoverride-verify "
-fi
-fi
-if test x"$SSLDIR" = "x" ; then
-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 [ "$MAXCONNECTIONS_REQUEST" != "auto" ]; then
-	ARG="$ARG--with-maxconnections=$MAXCONNECTIONS_REQUEST "
-fi
-
-ARG="$ARG--with-bindir=$BINDIR "
-ARG="$ARG--with-datadir=$DATADIR "
-ARG="$ARG--with-pidfile=$DATADIR/unrealircd.pid "
-ARG="$ARG--with-controlfile=$DATADIR/unrealircd.ctl "
-ARG="$ARG--with-confdir=$CONFDIR "
-ARG="$ARG--with-modulesdir=$MODULESDIR "
-ARG="$ARG--with-logdir=$LOGDIR "
-ARG="$ARG--with-cachedir=$CACHEDIR "
-ARG="$ARG--with-docdir=$DOCDIR "
-ARG="$ARG--with-tmpdir=$TMPDIR "
-ARG="$ARG--with-privatelibdir=$PRIVATELIBDIR "
-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
-if [ "x$INSTALLCURL" = "x1" ]; then
-	extras/curlinstall "$PRIVATELIBDIR" || exit 1
-fi
-# At least do SOME parallel compiling by default, IF:
-# - the MAKE environment variable is not set
-# - the MAKEFLAGS environment variable is not set
-# - we are using GNU Make
-if [ "x$MAKE" = "x" ]; then
-	if [ "x$MAKEFLAGS" = "x" ]; then
-		if make --version 2>&1|grep -q "GNU Make"; then
-			LOWMEM=0
-			if [ -f /proc/meminfo ]; then
-				FREEKB="`cat /proc/meminfo |grep MemAvailable|awk '{ print $2 }'`"
-				if [ "$FREEKB" != "" -a "$FREEKB" -lt 768000 ]; then
-					LOWMEM=1
-				fi
-			fi
-			if [ "$LOWMEM" = 0 ]; then
-				echo "Running with 4 concurrent build processes by default (make -j4)."
-				export MAKE='make -j4'
-			else
-				echo "System detected with less than 750MB available memory, not forcing parallel build."
-			fi
-		fi
-	fi
-fi
-
-echo $CONF
-$CONF || exit 1
-cd "$UNREALCWD"
-
-if [ "$QUICK" != "1" ] ; then
-	if [ ! -f $CONFDIR/tls/server.cert.pem -a ! -f $CONFDIR/ssl/server.cert.pem ]; then
-		export OPENSSLPATH
-		TEST=""
-		while [ -z "$TEST" ] ; do
-			if [ "$GENCERTIFICATE" = "1" ] ; then
-				TEST="Yes"
-			else
-				TEST="No"
-			fi
-			echo ""
-			echo "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
-			if [ -z "$cc" ] ; then
-			cc=$TEST
-			fi
-			case "$cc" in
-			[Yy]*)
-				GENCERTIFICATE="1"
-				;;
-			[Nn]*)
-				GENCERTIFICATE=""
-				;;
-			*)
-				echo ""
-				echo "You must enter either Yes or No"
-				TEST=""
-				;;
-			esac
-		done
-		if [ "$GENCERTIFICATE" = 1 ]; then
-			echo
-			echo "*******************************************************************************"
-			echo "Next you will be asked some questions in order to generate the 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
-			echo "Certificate created successfully."
-			sleep 1
-		else
-			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 "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
-}
-
-RUN_ADVANCED () {
-TEST=""
-while [ -z "$TEST" ] ; do
-	if [ "$NOOPEROVERRIDE" = "1" ] ; then
-		TEST="Yes"
-	else
-		TEST="No"
-	fi
-	echo ""
-	echo "Do you want to disable oper override?"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		cc=$TEST
-	fi
-	case "$cc" in
-	[Yy]*)
-		NOOPEROVERRIDE="1"
-		;;
-	[Nn]*)
-		NOOPEROVERRIDE=""
-		;;
-	*)
-		echo ""
-		echo "You must enter either Yes or No"
-		TEST=""
-		;;
-	esac
-done
-
-TEST=""
-while [ -z "$TEST" ] ; do
-	if [ "$OPEROVERRIDEVERIFY" = "1" ] ; then
-		TEST="Yes"
-	else
-		TEST="No"
-	fi
-	echo ""
-	echo "Do you want to require opers to /invite themselves into a +s or +p channel?"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		cc=$TEST
-	fi
-	case "$cc" in
-	[Yy]*)
-		OPEROVERRIDEVERIFY="1"
-		;;
-	[Nn]*)
-		OPEROVERRIDEVERIFY=""
-		;;
-	*)
-		echo ""
-		echo "You must enter either Yes or No"
-		TEST=""
-		;;
-	esac
-done
-
-}
-c=""
-n=""
-UNREALCWD="`pwd`"
-BASEPATH="$HOME/unrealircd"
-DEFPERM="0600"
-SSLDIR=""
-NICKNAMEHISTORYLENGTH="100"
-MAXCONNECTIONS_REQUEST="auto"
-REMOTEINC="1"
-CURLDIR=""
-NOOPEROVERRIDE=""
-OPEROVERRIDEVERIFY=""
-GENCERTIFICATE="1"
-EXTRAPARA=""
-SANITIZER=""
-GEOIP="none"
-if [ "`eval echo -n 'a'`" = "-n a" ] ; then
-	c="\c"
-else
-	n="-n"
-fi
-
-
-#parse arguments
-IMPORTEDSETTINGS=""
-NOINTRO=""
-QUICK=""
-ADVANCED=""
-while [ $# -ge 1 ] ; do
-	if [ $1 = "--help" ] ; then
-		echo "Config utility for UnrealIRCd"
-		echo "-----------------------------"
-		echo "Syntax: ./Config [options]"
-		echo "-nointro     Skip intro (release notes, etc)"
-		echo "-quick       Skip questions, go straight to configure"
-		echo "-advanced    Include additional advanced questions"
-		exit 0
-	elif [ $1 = "-nointro" ] ; then
-		NOINTRO="1"
-	elif [ $1 = "-quick" -o $1 = "-q" ] ; then
-		QUICK="1"
-		echo "running quick config"
-		if [ -f "config.settings" ] ; then
-			. ./config.settings
-		fi
-		FIX_PATHNAMES
-		RUN_CONFIGURE
-		cd "$UNREALCWD"
-		exit 0
-	elif [ $1 = "-advanced" ] ; then
-		PREADVANCED="1"
-	fi
-	shift 1
-done
-
-if [ "$PREADVANCED" = "1" ] ; then
-	ADVANCED="1"
-elif [ "$ADVANCED" = "1" ]; then
-	ADVANCED=""
-fi
-
-if [ "`id -u`" = "0" ]; then
-	echo "ERROR: You cannot build or run UnrealIRCd as root"
-	echo ""
-	echo "Please create a separate account for building and running UnrealIRCd."
-	echo "See https://www.unrealircd.org/docs/Do_not_run_as_root"
-	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
-	cat doc/Config.header
-	echo ""
-	echo $n "[Press Enter to continue]"
-	read cc
-	clear
-fi
-
-echo "We will now ask you a number of questions. You can just press ENTER to accept the defaults!"
-echo ""
-
-# This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-UNREALRELEASES="unrealircd-6.1.0-rc2 unrealircd-6.1.0-rc1 unrealircd-6.0.7 unrealircd-6.0.6 unrealircd-6.0.5 unrealircd-6.0.5-rc2 unrealircd-6.0.5-rc1 unrealircd-6.0.4.2 unrealircd-6.0.4.1 unrealircd-6.0.4 unrealircd-6.0.4-rc2 unrealircd-6.0.4-rc1 unrealircd-6.0.3 unrealircd-6.0.2 unrealircd-6.0.1.1 unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6"
-if [ -f "config.settings" ]; then
-	. ./config.settings
-else
-	# Try to load a previous config.settings
-	for x in $UNREALRELEASES
-	do
-		if [ -f ../$x/config.settings ]; then
-			IMPORTEDSETTINGS="../$x"
-			break
-		fi
-	done
-	echo "If you have previously installed UnrealIRCd on this shell then you can specify a"
-	echo "directory here so I can import the build settings and third party modules"
-	echo "to make your life a little easier."
-	if [ ! -z "$IMPORTEDSETTINGS" ]; then
-		echo "Found previous installation in: $IMPORTEDSETTINGS."
-		echo "You can enter a different path or type 'none' if you don't want to use it."
-		echo "Just press Enter to accept the default settings."
-	else
-		echo "If you install UnrealIRCd for the first time on this shell, then just hit Enter";
-	fi
-
-	TEST="$IMPORTEDSETTINGS"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ]; then
-		IMPORTEDSETTINGS="$TEST"
-	else
-		IMPORTEDSETTINGS="$cc"
-	fi
-	if [ "$IMPORTEDSETTINGS" = "none" ]; then
-		IMPORTEDSETTINGS=""
-	fi
-	if [ "$IMPORTEDSETTINGS" != "" ]; then
-		if [ -d $IMPORTEDSETTINGS/conf ]; then
-			echo "ERROR: Directory $IMPORTEDSETTINGS is an INSTALLATION directory (eg /home/irc/unrealircd)."
-			echo "This question was about a SOURCE directory (eg /home/irc/unrealircd-5.0.0)."
-			exit
-		fi
-		if [ ! -f $IMPORTEDSETTINGS/config.settings ]; then
-			echo "Directory $IMPORTEDSETTINGS does not exist or does not contain a config.settings file"
-			exit
-		fi
-		COPYMODULES="1"
-		if grep -q TOPICNICKISNUH $IMPORTEDSETTINGS/config.settings; then
-			echo "Directory $IMPORTEDSETTINGS seems to be UnrealIRCd 4.x (or older)."
-			echo "I will copy the settings but not any 3rd party modules, as they are incompatible with 5.x."
-			COPYMODULES="0"
-		fi
-		# Actually load the settings
-		. $IMPORTEDSETTINGS/config.settings
-		if [ "$COPYMODULES" = "1" ]; then
-			# Copy over 3rd party modules (also deals with 0 file cases, hence the silly looking code)
-			for f in $IMPORTEDSETTINGS/src/modules/third/*.c
-			do
-				[ -e "$f" ] && cp $f src/modules/third/
-			done
-		fi
-	fi
-fi
-# If we just imported settings and the curl dir is set to
-# something like /home/xxx/unrealircd-5.x.y/extras/curl/
-# (what we call 'local-curl') then remove this setting as
-# it would refer to the old UnrealIRCd installation.
-if [ ! -z "$IMPORTEDSETTINGS" ]; then
-	if echo "$CURLDIR"|grep -qi unrealircd; then
-		CURLDIR=""
-	fi
-fi
-
-TEST="$BASEPATH"
-echo ""
-echo "In what directory do you want to install UnrealIRCd?"
-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
-if [ -z "$cc" ] ; then
-	BASEPATH=$TEST
-else
-	BASEPATH=`eval echo $cc` # modified
-fi
-if [ "$BASEPATH" = "$UNREALCWD" ]; then
-	echo ""
-	echo "ERROR: The installation directory cannot be the same as the directory"
-	echo "       containing the source code ($UNREALCWD)."
-	echo " HINT: Usually the directory containing the source is $HOME/unrealircd-5.x.y"
-	echo "       and the installation directory you would need to enter is $HOME/unrealircd"
-	exit 1
-fi
-
-# TODO: For -advanced we could prompt the user.
-BINDIR="$BASEPATH/bin"
-DATADIR="$BASEPATH/data"
-CONFDIR="$BASEPATH/conf"
-MODULESDIR="$BASEPATH/modules"
-LOGDIR="$BASEPATH/logs"
-CACHEDIR="$BASEPATH/cache"
-DOCDIR="$BASEPATH/doc"
-TMPDIR="$BASEPATH/tmp"
-PRIVATELIBDIR="$BASEPATH/lib"
-
-TEST=""
-while [ -z "$TEST" ] ; do
-	TEST="$DEFPERM"
-	echo ""
-	echo "What should the default permissions for your configuration files be? (Set this to 0 to disable)"
-	echo "It is strongly recommended that you use 0600 to prevent unwanted reading of the file"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		DEFPERM=$TEST
-		break
-	fi
-	case "$cc" in
-	[0-9]*)
-		DEFPERM="$cc"
-		;;
-	*)
-		echo ""
-		echo "You must enter a number"
-		TEST=""
-		;;
-	esac
-done
-
-echo ""
-echo "If you want, you can manually enter the path to OpenSSL/LibreSSL here."
-echo "In most cases you can leave this blank and it will be detected automatically."
-
-if [ -z "$SSLDIR" ]; then
-	uname|grep -q Darwin
-	if [ "$?" = 0 ]; then
-		echo "Looks like you're on a Mac - El Capitan and higher require"
-		echo "a 3rd party OpenSSL installation. We recommend using homebrew"
-		echo "to install OpenSSL, but you may install it any other way as well."
-		echo "We are selecting the default homebrew OpenSSL path - but you can"
-		echo "change it to another path if you have installed OpenSSL another way"
-		SSLDIR="/usr/local/opt/openssl/"
-	fi
-fi
-
-TEST="$SSLDIR"
-echo $n "[$TEST] -> $c"
-read cc
-if [ -z "$cc" ] ; then
-	SSLDIR="$TEST"
-else 
-	SSLDIR=`eval echo $cc` # modified
-fi
-
-if [ "$SSLDIR" != "" -a "$SSLDIR" != "/usr" ]; then
-	echo ""
-	echo "You answered previous question manually. Just note that if the library is not"
-	echo "in your default library path then UnrealIRCd may fail to start with an error"
-	echo "that it cannot find certain .so files (libraries). In that case you would have"
-	echo "to either set the LD_LIBARY_PATH environment variable, or you could update the"
-	echo "Makefile to link with -Wl,-rpath,$SSLDIR or similar."
-	echo ""
-	if [ "$SSLDIR" = "/usr/local" ]; then
-		echo "**** CAUTION ****"
-		echo "You have chosen to use OpenSSL from /usr/local. Just be aware that if you"
-		echo "use the LD_LIBRARY_PATH or -Wl,-rpath,$SSLDIR from above,"
-		echo "that you are diverting OTHER libraries to that path as well."
-		echo "It's not only loading OpenSSL from /usr/local but also potentially other"
-		echo "libraries like PCRE2, Jansson, or any of the other libraries that"
-		echo "UnrealIRCd uses (including dependencies about 40 libs in total!)"
-		echo "All that can result in weird issues and crashes!"
-		echo ""
-	fi
-	echo "Press enter to continue with the rest of the questions, or CTRL+C to abort."
-	read cc
-fi
-
-TEST=""
-while [ -z "$TEST" ] ; do
-	if [ "$REMOTEINC" = "1" ] ; then
-		TEST="Yes"
-	else
-		TEST="No"
-	fi
-	echo ""
-	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
-		cc=$TEST
-	fi
-	case "$cc" in
-	[Yy]*)
-		REMOTEINC="1"
-		;;
-	[Nn]*)
-		REMOTEINC=""
-		CURLDIR=""
-		;;
-	*)
-		echo ""
-		echo "You must enter either Yes or No"
-		TEST=""
-		;;
-	esac
-done
-
-if [ "$REMOTEINC" = "1" ] ; then
-	if [ ! -d "$CURLDIR" ]; then
-		# Reset any previous CURLDIR if it doesn't exist (anymore)
-		CURLDIR=""
-	fi
-
-	INSTALLCURL="0"
-	SUGGESTCURLDIR=""
-
-	if [ -d "/usr/local/include/curl" ]; then
-		SUGGESTCURLDIR="/usr/local"
-	fi
-	if [ -d "/usr/include/curl" ]; then
-		SUGGESTCURLDIR="/usr"
-	fi
-	# This one also works for /usr/include/x86_64-linux-gnu and friends:
-	if [ -f "/usr/bin/curl-config" ]; then
-		SUGGESTCURLDIR="/usr"
-	fi
-
-	GOTASYNC=0
-	if [ "x$SUGGESTCURLDIR" != "x" ]; then
-		# Check if it's of any use: a curl without async dns (cares) hangs the entire ircd..
-		# normally this is done in ./configure but now we're forced to do it also here..
-		if "$SUGGESTCURLDIR"/bin/curl-config --features | grep -q -e AsynchDNS; then
-			GOTASYNC="1"
-		fi
-		if [ "$GOTASYNC" != "1" ]; then
-			SUGGESTCURLDIRBAD="$CURLDIR"
-			SUGGESTCURLDIR=""
-		fi
-	fi
-
-	if [ "x$CURLDIR" = "x$HOME/curl" ]; then
-		if [ "x$SUGGESTCURLDIR" != "x" ]; then
-			# I guess some people will complain about this, but if system wide cURL is available
-			# and many people have old defaults then this is much preferred:
-			echo ""
-			echo "WARNING: Your previous (potentially old) setting is to use cURL from $HOME/curl."
-			echo "However, your operating system also provides a working cURL."
-			echo "I am therefore changing the setting to: $SUGGESTCURLDIR"
-			CURLDIR="$SUGGESTCURLDIR"
-		else
-			echo ""
-			echo "WARNING: We no longer use $HOME/curl nowadays."
-			echo "Use the automatic download and install feature below."
-			CURLDIR=""
-		fi
-	fi
-
-	if [ "x$CURLDIR" = "x" ]; then
-		CURLDIR="$SUGGESTCURLDIR"
-		# NOTE: CURLDIR may still be empty after this
-
-		# System curl has no asyncdns, so install our own.
-		if [ "$GOTASYNC" != "1" ]; then
-			CURLDIR=""
-		fi
-
-		# Need to output it here, as the HOME check from above may cause this to be no longer relevant.
-		if [ "x$CURLDIR" = "x" -a "x$SUGGESTCURLDIRBAD" != "x" ]; then
-			echo "Curl library was found in $SUGGESTCURLDIRBAD, but it does not support Asynchronous DNS (not compiled with c-ares)"
-			echo "so it's of no use to us as it would stall the IRCd on REHASH."
-		fi
-	fi
-
-	# Final check
-	if [ "x$CURLDIR" != "x" ]; then
-		"$CURLDIR/bin/curl-config" --features 2>/dev/null | grep -q -e AsynchDNS
-		if [ "$?" != 0 ]; then
-			echo "Curl from $CURLDIR seems unusable ($CURLDIR/bin/curl-config does not exist)"
-			CURLDIR=""
-		fi
-	fi
-
-	if [ "x$CURLDIR" = "x" ]; then
-		# Still empty?
-		TEST=""
-		while [ -z "$TEST" ] ; do
-			TEST="Yes"
-			echo ""
-			echo "Do you want me to automatically download and install curl for you?"
-			echo $n "[$TEST] -> $c"
-			read cc
-			if [ -z "$cc" ] ; then
-				cc=$TEST
-			fi
-			case "$cc" in
-			[Yy]*)
-				INSTALLCURL="1"
-				CURLDIR="$UNREALCWD/extras/curl"
-				;;
-			[Nn]*)
-				INSTALLCURL="0"
-				;;
-			*)
-				echo ""
-				echo "You must enter either Yes or No"
-				TEST=""
-				;;
-			esac
-		done
-	fi
-
-	if [ "$INSTALLCURL" != "1" ]; then
-		TEST=""
-		while [ -z "$TEST" ] ; do
-			TEST="$CURLDIR"
-			echo ""
-			echo "Specify the directory you installed libcurl to"
-			echo $n "[$TEST] -> $c"
-			read cc
-			if [ -z "$cc" ] ; then
-				cc=$TEST
-			else
-				TEST=$cc
-				CURLDIR=`eval echo $cc` # modified
-			fi
-		done
-		if [ "x$CURLDIR" != "x" ]; then
-			"$CURLDIR/bin/curl-config" --libs 1>/dev/null 2>&1
-			if [ "$?" != 0 ]; then
-				echo "Curl from $CURLDIR seems unusable ($CURLDIR/bin/curl-config does not exist)"
-				CURLDIR=""
-			fi
-		fi
-	fi
-fi
-
-
-TEST=""
-while [ -z "$TEST" ] ; do
-	TEST="$NICKNAMEHISTORYLENGTH"
-	echo ""
-	echo "How far back do you want to keep the nickname history?"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		NICKNAMEHISTORYLENGTH=$TEST
-		break
-	fi
-	case "$cc" in
-	[1-9]*)
-		NICKNAMEHISTORYLENGTH="$cc"
-		;;
-	*)
-		echo ""
-		echo "You must enter a number"
-		TEST=""
-		;;
-	esac
-done
-
-TEST=""
-while [ -z "$TEST" ] ; do
-	TEST="$GEOIP"
-	echo ""
-	echo "GeoIP is a feature that allows converting an IP address to a location (country)"
-	echo "Possible build options:"
-	echo "     classic: This is the DEFAULT geoip engine. It should work on all systems"
-	echo "              and receives automatic updates."
-	echo "libmaxminddb: This uses the libmaxminddb library. If you want to use this, then"
-	echo "              you need to install the libmaxminddb library on your system first"
-	echo "        none: Don't build with any geoip library (geoip-csv is still built)"
-	echo "Choose one of: classic, libmaxminddb, none"
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		GEOIP=$TEST
-		break
-	fi
-	case "$cc" in
-	classic)
-		GEOIP="$cc"
-		;;
-	libmaxminddb)
-		GEOIP="$cc"
-		;;
-	none)
-		GEOIP="$cc"
-		;;
-	*)
-		echo ""
-		echo "Invalid choice: $cc"
-		TEST=""
-		;;
-	esac
-done
-
-echo ""
-TEST=""
-while [ -z "$TEST" ] ; do
-	TEST="$MAXCONNECTIONS_REQUEST"
-	echo ""
-	echo "What is the maximum number of sockets (and file descriptors) that"
-	echo "UnrealIRCd may use?"
-	echo "It is recommended to leave this at the default setting 'auto',"
-	echo "which at present results in a limit of up to 16384, depending on"
-	echo "the system. When you boot UnrealIRCd later you will always see"
-	echo "the effective limit."
-	echo $n "[$TEST] -> $c"
-	read cc
-	if [ -z "$cc" ] ; then
-		MAXCONNECTIONS_REQUEST=$TEST
-		break
-	fi
-	case "$cc" in
-		auto)
-			MAXCONNECTIONS_REQUEST="$cc"
-			;;
-		[1-9][0-9][0-9]*)
-			MAXCONNECTIONS_REQUEST="$cc"
-			;;
-		*)
-			echo ""
-			echo "You must to enter a number greater than or equal to 100."
-			echo "Or enter 'auto' to leave it at automatic, which is recommended."
-			TEST=""
-			;;
-	esac
-done
-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?"
-echo "Most people don't need this and can just press ENTER."
-echo "Otherwise, see \`./configure --help' and write them here:"
-echo $n "[$TEST] -> $c"
-read EXTRAPARA
-if [ -z "$EXTRAPARA" ]; then
-	EXTRAPARA="$TEST"
-fi
-
-FIX_PATHNAMES
-
-rm -f config.settings
-cat > config.settings << __EOF__
-#
-# 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="$BASEPATH"
-BINDIR="$BINDIR"
-DATADIR="$DATADIR"
-CONFDIR="$CONFDIR"
-MODULESDIR="$MODULESDIR"
-LOGDIR="$LOGDIR"
-CACHEDIR="$CACHEDIR"
-DOCDIR="$DOCDIR"
-TMPDIR="$TMPDIR"
-PRIVATELIBDIR="$PRIVATELIBDIR"
-MAXCONNECTIONS_REQUEST="$MAXCONNECTIONS_REQUEST"
-NICKNAMEHISTORYLENGTH="$NICKNAMEHISTORYLENGTH"
-GEOIP="$GEOIP"
-DEFPERM="$DEFPERM"
-SSLDIR="$SSLDIR"
-REMOTEINC="$REMOTEINC"
-CURLDIR="$CURLDIR"
-NOOPEROVERRIDE="$NOOPEROVERRIDE"
-OPEROVERRIDEVERIFY="$OPEROVERRIDEVERIFY"
-GENCERTIFICATE="$GENCERTIFICATE"
-SANITIZER="$SANITIZER"
-EXTRAPARA="$EXTRAPARA"
-ADVANCED="$ADVANCED"
-__EOF__
-RUN_CONFIGURE
-cd "$UNREALCWD"
-cat << __EOF__
-
- _______________________________________________________________________
-|                                                                       |
-|                    UnrealIRCd Compile-Time Config                     |
-|_______________________________________________________________________|
-|_______________________________________________________________________|
-|                                                                       |
-|                        - The UnrealIRCd Team -                        |
-|                                                                       |
-|              Bram Matthys (Syzop) - syzop@unrealircd.org              |
-|       Krzysztof Beresztant (k4be) - k4be@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/Dockerfile b/Dockerfile
@@ -0,0 +1,90 @@
+# SuperNETS UnrealIRCd - Developed by acidvegas (https://github.com/supernets/unrealircd)
+# unrealircd/Dockerfile
+
+# Define the source and build directories
+ENV SOURCE_DIR="/tmp/ircd"
+ENV BUILD_DIR="/opt/ircd"
+
+# Use debian-based image for consistency
+FROM debian:bullseye-slim
+
+# Install required packages
+RUN apt-get update && apt-get install -y \
+    build-essential \
+    pkg-config \
+    gdb \
+    libssl-dev \
+    libpcre2-dev \
+    libargon2-0-dev \
+    libsodium-dev \
+    libc-ares-dev \
+    libcurl4-openssl-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+# Copy the .env file
+COPY .env /tmp/.env
+
+# Parse .env file and set environment variables
+ENV LEAF_NAME=$(grep LEAF_NAME /tmp/.env | cut -d'"' -f2)
+ENV LEAF_PORT=$(grep LEAF_PORT /tmp/.env | cut -d'"' -f2)
+ENV REMOTE_INCLUDE_URL=$(grep REMOTE_INCLUDE_URL /tmp/.env | cut -d'"' -f2)
+
+# Remove the temporary .env file after parsing
+RUN rm /tmp/.env
+
+# Copy source files
+COPY ircd ${SOURCE_DIR}
+
+# Enter the source directory
+WORKDIR ${SOURCE_DIR}
+
+# Build UnrealIRCd
+RUN echo -e "\n" | ./Config -nointro && make && make install
+
+# Switch to the build directory
+WORKDIR ${BUILD_DIR}
+
+# Nuke the source directory
+RUN rm -rf ${SOURCE_DIR}
+
+# Nuke the default configuration files
+RUN find ${BUILD_DIR}/doc/conf/ -maxdepth 1 -type f ! -name 'unrealircd.link.conf' ! -name 'remote.motd' -exec rm -f {} +
+RUN mv ${BUILD_DIR}/doc/conf/unrealircd.link.conf ${BUILD_DIR}/doc/conf/unrealircd.conf
+
+# Replace the remote include URL placeholder
+RUN sed -i "s|https://USERNAME:PASSWORD@hub.supernets.org:PORT|${REMOTE_INCLUDE_URL}|g" ${BUILD_DIR}/doc/conf/unrealircd.conf
+
+# Replace the server name placeholder
+RUN sed -i "s|example.supernets.org|${LEAF_NAME}.supernets.org|g" ${BUILD_DIR}/doc/conf/unrealircd.conf
+
+# Replace the SID placeholder
+# Generate a random SID
+ENV SID=$(cat /dev/urandom | tr -dc '0-9' | fold -w 256 | head -n 1 | head --bytes 1)$(cat /dev/urandom | tr -dc 'A-Z0-9' | fold -w 2 | head -n 1)
+RUN sed -i "s|XXX|${SID}|g" ${BUILD_DIR}/doc/conf/unrealircd.conf
+
+# Copy over the assets
+COPY assets/* ${BUILD_DIR}/assets/
+RUN  mv       ${BUILD_DIR}/assets/*.db    ${BUILD_DIR}/data/
+RUN  mv       ${BUILD_DIR}/assets/tls.crt ${BUILD_DIR}/conf/tls.crt
+RUN  mv       ${BUILD_DIR}/assets/tls.key ${BUILD_DIR}/conf/tls.key
+
+# Create ircd user and group
+RUN groupadd -r ircd && useradd -r -g ircd ircd
+
+# Set permissions
+RUN chown -R ircd:ircd ${BUILD_DIR}
+
+# Expose standard IRC client ports
+EXPOSE 6660-6669 7000
+
+# Expose TLS client ports
+EXPOSE 6697 9000
+
+# Expose server-only port
+EXPOSE ${LEAF_PORT}
+
+# Switch to ircd user
+USER ircd
+
+# Set entrypoint
+ENTRYPOINT ["/opt/unrealircd/bin/unrealircd", "start"]
+\ No newline at end of file
diff --git a/LICENSE b/LICENSE
@@ -1,340 +0,0 @@
-		    GNU GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-			    Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-		    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-			    NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-		     END OF TERMS AND CONDITIONS
-
-	    How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    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 2 of the License, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
-    Gnomovision version 69, Copyright (C) year  name of author
-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
-  `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
-  <signature of Ty Coon>, 1 April 1989
-  Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs.  If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Library General
-Public License instead of this License.
diff --git a/Makefile.windows b/Makefile.windows
@@ -1,1408 +0,0 @@
-#
-# UnrealIRCd Makefile - codemastr
-#
-CC=cl
-LINK=link
-RC=rc
-MT=mt
-
-############################ USER CONFIGURATION ############################
-
-# You are encouraged NOT to set these values here, but instead make a batch file
-# which passes all these arguments to nmake, like:
-# nmake -f makefile.windows LIBRESSL_INC_DIR="c:\dev\libressl" etc etc...
-# Both ways will work, but if you use a batch file it's easier with
-# upgrading UnrealIRCd as you won't have to edit this makefile again.
-
-### PCRE2 ###
-#PCRE2_LIB_DIR="C:\dev\pcre2\build\release"
-#PCRE2_INC_DIR="C:\dev\pcre2"
-#PCRE2LIB="pcre2-8.lib"
-
-### ARGON2 ###
-#ARGON2_LIB_DIR="C:\dev\argon2\vs2015\build"
-#ARGON2_INC_DIR="C:\dev\argon2\include"
-#ARGON2LIB="Argon2RefDll.lib"
-
-### SODIUM ###
-#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.
-#
-#
-#To enable remote includes uncomment the next line:
-#USE_REMOTEINC=1
-#
-#If your libcurl library and include files are not in your compiler's
-#default locations, specify the locations here:
-#LIBCURL_INC_DIR="c:\dev\curl\include"
-#LIBCURL_LIB_DIR="c:\dev\curl\lib"
-#
-#
-### END REMOTE INCLUDES ##
-
-####### SSL/TLS SUPPORT (MANDATORY) ######
-#Use LibreSSL or OpenSSL. Define paths and libraries:
-#LIBRESSL_INC_DIR="c:\dev\libressl\include"
-#LIBRESSL_LIB_DIR="c:\dev\libressl\lib"
-#SSLLIB=libcrypto-38.lib libssl-39.lib libtls-11.lib
-#The version numbers of the 3 libraries in the last line change
-#every libressl release. So be sure to update after any libressl upgrade.
-######### END SSL/TLS ########
-
-###### _EXTRA_ DEBUGGING #####
-# We always build releases with debugging information, since otherwise
-# we cannot trace the source of a crash. Plus we do not mind the extra
-# performance hit caused by not enabling super-optimization, tracing
-# crashes properly is more important.
-# You can choose (at your own risk) to enable EVEN MORE debugging,
-# note that this causes /MDd to be used instead of /MD which can make
-# libraries incompatible, plus all the other side-effects such as
-# requiring a different dll we do not ship (and maybe you are not even
-# allowed to ship due to license agreements), etc...
-# In any case, this probably should not be used, unless debugging a
-# problem locally, in which case it can be useful.
-#DEBUGEXTRA=1
-#
-#
-#### END RELEASE BUILD ###
-
-############################# END CONFIGURATION ############################
-
-!IFDEF CARES_INC_DIR
-CARES_INC=/I "$(CARES_INC_DIR)"
-!ENDIF
-!IFDEF CARES_LIB_DIR
-CARES_LIB=/LIBPATH:"$(CARES_LIB_DIR)"
-!ENDIF
-
-!IFDEF PCRE2_INC_DIR
-PCRE2_INC=/I "$(PCRE2_INC_DIR)"
-!ENDIF
-!IFDEF PCRE2_LIB_DIR
-PCRE2_LIB=/LIBPATH:"$(PCRE2_LIB_DIR)"
-!ENDIF
-
-!IFDEF ARGON2_INC_DIR
-ARGON2_INC=/I "$(ARGON2_INC_DIR)"
-!ENDIF
-!IFDEF ARGON2_LIB_DIR
-ARGON2_LIB=/LIBPATH:"$(ARGON2_LIB_DIR)"
-!ENDIF
-
-!IFDEF SODIUM_INC_DIR
-SODIUM_INC=/I "$(SODIUM_INC_DIR)"
-!ENDIF
-!IFDEF SODIUM_LIB_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_CURL.OBJ
-CURLLIB=libcurl.lib
-!IFDEF LIBCURL_INC_DIR
-LIBCURL_INC=/I "$(LIBCURL_INC_DIR)"
-!ENDIF
-!IFDEF LIBCURL_LIB_DIR
-LIBCURL_LIB=/LIBPATH:"$(LIBCURL_LIB_DIR)"
-!ENDIF
-!ENDIF
-
-!IFDEF LIBRESSL_INC_DIR
-LIBRESSL_INC=/I "$(LIBRESSL_INC_DIR)"
-!ENDIF
-!IFDEF LIBRESSL_LIB_DIR
-LIBRESSL_LIB=/LIBPATH:"$(LIBRESSL_LIB_DIR)"
-!ENDIF
-
-!IFDEF DEBUGEXTRA
-DBGCFLAG=/MDd /Zi /Od
-DBGCFLAGST=/MTd /Zi /Od
-DBGLFLAG=/debug
-MODDBGCFLAG=/LDd /MDd /Zi
-!ELSE
-DBGCFLAG=/MD /Zi
-DBGCFLAGST=/MT /Zi
-DBGLFLAG=/debug
-MODDBGCFLAG=/LDd /MD /Zi
-!ENDIF 
-
-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
-STDLIBS=$(CARES_LIB) $(CARESLIB) $(PCRE2_LIB) $(PCRE2LIB) $(ARGON2_LIB) $(ARGON2LIB) \
- $(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) \
- /nologo $(DBGLFLAG)
-MODCFLAGS=$(MODDBGCFLAG) $(STDOPTIONS) /D DYNAMIC_LINKING /D MODULE_COMPILE
-MODLFLAGS=/link /def:src/modules/module.def UnrealIRCd.lib ws2_32.lib $(STDLIBS)
-
-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/ircd_vars.obj src/channel.obj src/send.obj src/socket.obj \
- src/conf.obj src/proc_io_server.obj src/conf_preprocessor.obj \
- src/fdlist.obj src/dbuf.obj  \
- src/hash.obj src/parse.obj \
- src/whowas.obj \
- src/securitygroup.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/api-rpc.obj src/mempool.obj \
- src/dispatch.obj src/api-isupport.obj src/api-command.obj \
- src/api-clicap.obj src/api-messagetag.obj src/api-history-backend.obj \
- src/api-extban.obj src/api-efunctions.obj src/crypt_blowfish.obj \
- src/operclass.obj src/crashreport.obj src/unrealdb.obj \
- src/openssl_hostname_validation.obj \
- src/utf8.obj src/json.obj src/log.obj $(CURLOBJ)
-
-OBJ_FILES=$(EXP_OBJ_FILES) src/gui.obj src/service.obj src/windebug.obj src/rtf.obj \
- src/editor.obj src/win.obj src/ircd.obj src/proc_io_client.obj
-
-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/connect-flood.dll \
- src/modules/connthrottle.dll \
- src/modules/creationtime.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/flood.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/geoip-tag.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/max-unknown-connections-per-ip.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/real-quit-reason.dll \
- src/modules/reply-tag.dll \
- src/modules/reputation.dll \
- src/modules/require-module.dll \
- src/modules/restrict-commands.dll \
- src/modules/rmtkl.dll \
- src/modules/rpc/channel.dll \
- src/modules/rpc/log.dll \
- src/modules/rpc/name_ban.dll \
- src/modules/rpc/rpc.dll \
- src/modules/rpc/stats.dll \
- src/modules/rpc/server.dll \
- src/modules/rpc/server_ban.dll \
- src/modules/rpc/server_ban_exception.dll \
- src/modules/rpc/spamfilter.dll \
- src/modules/rpc/whowas.dll \
- src/modules/rpc/user.dll \
- src/modules/rules.dll \
- src/modules/sajoin.dll \
- src/modules/samode.dll \
- 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/sreply.dll \
- src/modules/staff.dll \
- src/modules/standard-replies.dll \
- src/modules/starttls.dll \
- src/modules/stats.dll \
- src/modules/sts.dll \
- src/modules/svsjoin.dll \
- src/modules/svskill.dll \
- src/modules/svslogin.dll \
- src/modules/svslusers.dll \
- src/modules/svsmode.dll \
- src/modules/svsmotd.dll \
- src/modules/svsnick.dll \
- src/modules/svsnline.dll \
- src/modules/svsnolag.dll \
- src/modules/svsnoop.dll \
- src/modules/svso.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/tline.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/channel-context.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/webserver.dll \
- src/modules/websocket.dll \
- src/modules/websocket_common.dll \
- src/modules/whois.dll \
- src/modules/who_old.dll \
- src/modules/whowas.dll \
- src/modules/whowasdb.dll \
- src/modules/whox.dll
-
-
-ALL: CONF unrealircdctl.exe UNREALSVC.EXE UnrealIRCd.exe MODULES
-
-CLEAN:
-	-@del /Q /S *.dll *.exe *.obj *.pdb *.res *.lib  *.exp *.ilk src\version.c >NUL
-
-UNREALSVC.EXE: SRC/UNREALSVC.OBJ SRC/WINDOWS/UNREALSVC.RES
-	$(LINK) $(DBGLFLAGST) advapi32.lib src/unrealsvc.obj src/windows/unrealsvc.res
-
-CONF:
-	-@copy include\windows\setup.h include\setup.h >NUL
-	$(CC) src/windows/config.c
-	-@config.exe
-
-UnrealIRCd.exe: $(OBJ_FILES) src/ircd.obj src/windows/win.res
-        $(LINK) $(LFLAGS) /out:UnrealIRCd.exe /def:UnrealIRCd.def /implib:UnrealIRCd.lib $(OBJ_FILES) src/windows/win.res /MAP
-	-@erase src\windows\win.res
-	$(MT) -manifest src\windows\UnrealIRCd.exe.manifest -outputresource:UnrealIRCd.exe;1
-
-unrealircdctl.exe: $(OBJ_FILES) src/unrealircdctl.obj src/proc_io_client.obj
-	$(LINK) $(LFLAGS) /SUBSYSTEM:CONSOLE /out:unrealircdctl.exe $(OBJ_FILES) src/unrealircdctl.obj
-	$(MT) -manifest src\windows\unrealircdctl.exe.manifest -outputresource:unrealircdctl.exe;1
-
-# alternative option -- FIXME: REMOVE / CHOOSE
-#unrealircdctl.exe: $(OBJ_FILES) src/unrealircdctl.obj src/proc_io_client.obj src/windows/unrealircdctl.res
-#	$(LINK) $(LFLAGS) /out:unrealircdctl.exe $(OBJ_FILES) src/unrealircdctl.obj src/windows/unrealircdctl.res
-
-#Source files
-
-src/version.obj: src/version.c
-        $(CC) $(CFLAGS) src/version.c
-
-src/ircd_vars.obj: src/ircd_vars.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/ircd_vars.c
-
-src/parse.obj: src/parse.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/parse.c
-
-src/socket.obj: src/socket.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/socket.c
-
-src/dbuf.obj: src/dbuf.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/dbuf.c
-
-src/ircsprintf.obj: src/ircsprintf.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/ircsprintf.c
-
-src/fdlist.obj: src/fdlist.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/fdlist.c
-
-src/dynconf.obj: src/dynconf.c $(INCLUDES) ./include/dbuf.h \
-                ./include/channel.h ./include/whowas.h
-        $(CC) $(CFLAGS) src/dynconf.c
-
-src/send.obj: src/send.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/send.c
-
-src/match.obj: src/match.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/match.c
-
-src/support.obj: src/support.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/support.c
-
-src/channel.obj: src/channel.c $(INCLUDES) ./include/channel.h
-        $(CC) $(CFLAGS) src/channel.c
-
-src/class.obj: src/class.c $(INCLUDES) ./include/class.h
-        $(CC) $(CFLAGS) src/class.c
-
-src/ircd.obj: src/ircd.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/ircd.c
-
-src/list.obj: src/list.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/list.c
-
-src/dns.obj: src/dns.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/dns.c
-
-src/conf.obj: src/conf.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/conf.c
-
-src/proc_io_server.obj: src/proc_io_server.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/proc_io_server.c
-
-src/proc_io_client.obj: src/proc_io_client.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/proc_io_client.c
-
-src/conf_preprocessor.obj: src/conf_preprocessor.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/conf_preprocessor.c
-
-src/debug.obj: src/debug.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/debug.c
-
-src/securitygroup.obj: src/securitygroup.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/securitygroup.c
-
-src/misc.obj: src/misc.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/misc.c
-
-src/scache.obj: src/scache.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/scache.c
-
-src/socks.obj: src/socks.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/socks.c
-
-src/aliases.obj: src/aliases.c $(INCLUDES)
-	  $(CC) $(CFLAGS) src/aliases.c
-
-src/user.obj: src/user.c $(INCLUDES) ./include/dbuf.h \
-                ./include/channel.h ./include/whowas.h
-        $(CC) $(CFLAGS) src/user.c
-
-src/serv.obj: src/serv.c $(INCLUDES) ./include/dbuf.h ./include/whowas.h
-        $(CC) $(CFLAGS) src/serv.c
-
-src/whowas.obj: src/whowas.c $(INCLUDES) ./include/dbuf.h ./include/whowas.h
-        $(CC) $(CFLAGS) src/whowas.c
-
-src/hash.obj: src/hash.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/hash.c
-
-src/crule.obj: src/crule.c $(INCLUDES)
-        $(CC) $(CFLAGS) src/crule.c
-
-src/gui.obj: src/windows/gui.c $(INCLUDES) ./include/resource.h
-        $(CC) $(CFLAGS) src/windows/gui.c
-
-src/rtf.obj: src/windows/rtf.c $(INCLUDES) ./src/windows/win.h
-	$(CC) $(CFLAGS) src/windows/rtf.c
-
-src/editor.obj: src/windows/editor.c $(INCLUDES) ./include/resource.h ./src/windows/win.h
-        $(CC) $(CFLAGS) src/windows/editor.c
-
-src/service.obj: src/windows/service.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/windows/service.c
-
-src/windebug.obj: src/windows/windebug.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/windows/windebug.c
-
-src/win.obj: src/windows/win.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/windows/win.c
-
-src/unrealsvc.obj: src/windows/unrealsvc.c $(INCLUDES)
-	$(CC) $(CFLAGSST) src/windows/unrealsvc.c
-
-src/unrealircdctl.obj: src/unrealircdctl.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/unrealircdctl.c
-
-src/modules.obj: src/modules.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/modules.c
-
-src/api-event.obj: src/api-event.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-event.c
-
-src/api-usermode.obj: src/api-usermode.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-usermode.c
-
-src/auth.obj: src/auth.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/auth.c
-
-src/random.obj: src/random.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/random.c
-
-src/api-channelmode.obj: src/api-channelmode.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-channelmode.c
-
-src/api-moddata.obj: src/api-moddata.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-moddata.c
-
-src/api-rpc.obj: src/api-rpc.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-rpc.c
-
-src/mempool.obj: src/mempool.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/mempool.c
-
-src/dispatch.obj: src/dispatch.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/dispatch.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
-
-src/api-efunctions.obj: src/api-efunctions.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-efunctions.c
-
-src/api-isupport.obj: src/api-isupport.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-isupport.c
-
-src/api-command.obj: src/api-command.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-command.c
-
-src/api-clicap.obj: src/api-clicap.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-clicap.c
-
-src/api-messagetag.obj: src/api-messagetag.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-messagetag.c
-
-src/api-history-backend.obj: src/api-history-backend.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/api-history-backend.c
-
-src/tls.obj: src/tls.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/tls.c
-
-src/crypt_blowfish.obj: src/crypt_blowfish.c $(INCLUDES)
-	$(CC) $(CFLAGS) src/crypt_blowfish.c
-
-src/operclass.obj: src/operclass.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/operclass.c
-
-src/crashreport.obj: src/crashreport.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/crashreport.c
-
-src/unrealdb.obj: src/unrealdb.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/unrealdb.c
-
-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/json.obj: src/json.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/json.c
-
-src/log.obj: src/log.c $(INCLUDES) ./include/dbuf.h
-        $(CC) $(CFLAGS) src/log.c
-
-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
-
-src/windows/unrealsvc.res: src/windows/unrealsvc.rc
-        $(RC) /l 0x409 /fosrc/windows/unrealsvc.res /i ./include /i ./src \
-              /d NDEBUG src/windows/unrealsvc.rc
-
-src/windows/unrealircdctl.res: src/windows/unrealircdctl.rc
-        $(RC) /l 0x409 /fosrc/windows/unrealircdctl.res /i ./include /i ./src \
-              /d NDEBUG src/windows/unrealircdctl.rc
-
-################# Modules #################
-
-CUSTOMMODULE: src/modules/third/$(MODULEFILE).c
-	$(CC) $(MODCFLAGS) src/modules/third/$(MODULEFILE).c /Fesrc/modules/third/ /Fosrc/modules/third/ $(MODLFLAGS) \
-	      /OUT:src/modules/third/$(MODULEFILE).dll $(EXLIBS)
-
-SYMBOLFILE: 
-	$(CC) src/windows/def-clean.c
-	dlltool --output-def UnrealIRCd.def.in --export-all-symbols $(EXP_OBJ_FILES)
-	def-clean UnrealIRCd.def.in UnrealIRCd.def
-
-MODULES: $(DLL_FILES)
-
-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/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/addmotd.dll: src/modules/addmotd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/addmotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/addmotd.pdb $(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/admin.dll: src/modules/admin.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/admin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/admin.pdb $(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/antirandom.dll: src/modules/antirandom.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/antirandom.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/antirandom.pdb $(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/away.dll: src/modules/away.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/away.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/away.pdb $(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/blacklist.dll: src/modules/blacklist.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/blacklist.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/blacklist.pdb $(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/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/cap.dll: src/modules/cap.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/cap.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cap.pdb $(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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/channeldb.dll: src/modules/channeldb.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/channeldb.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/channeldb.pdb $(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/chathistory.dll: src/modules/chathistory.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/chathistory.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/chathistory.pdb $(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/chgident.dll: src/modules/chgident.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/chgident.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/chgident.pdb $(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/clienttagdeny.dll: src/modules/clienttagdeny.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/clienttagdeny.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/clienttagdeny.pdb $(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/connect.dll: src/modules/connect.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/connect.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connect.pdb $(MODLFLAGS)
-
-src/modules/connect-flood.dll: src/modules/connect-flood.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/connect-flood.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connect-flood.pdb $(MODLFLAGS)
-
-src/modules/connthrottle.dll: src/modules/connthrottle.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/connthrottle.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connthrottle.pdb $(MODLFLAGS)
-
-src/modules/creationtime.dll: src/modules/creationtime.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/creationtime.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/creationtime.pdb $(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/dccallow.dll: src/modules/dccallow.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/dccallow.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/dccallow.pdb $(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/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/eos.dll: src/modules/eos.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/eos.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/eos.pdb $(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/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/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/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/extbans/join.dll: src/modules/extbans/join.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/extbans/join.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/join.pdb $(MODLFLAGS)
-
-src/modules/extbans/flood.dll: src/modules/extbans/flood.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/extbans/flood.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/flood.pdb $(MODLFLAGS)
-
-src/modules/extbans/msgbypass.dll: src/modules/extbans/msgbypass.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/extbans/msgbypass.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/msgbypass.pdb $(MODLFLAGS)
-
-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/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/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/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/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/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/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/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/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/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/extjwt.dll: src/modules/extjwt.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/extjwt.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/extjwt.pdb $(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/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-tag.dll: src/modules/geoip-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/geoip-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip-tag.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/ /Fdsrc/modules/join.pdb $(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/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/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/ /Fdsrc/modules/lusers.pdb $(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/max-unknown-connections-per-ip.dll: src/modules/max-unknown-connections-per-ip.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/max-unknown-connections-per-ip.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/max-unknown-connections-per-ip.pdb $(MODLFLAGS)
-
-src/modules/md.dll: src/modules/md.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/md.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/md.pdb $(MODLFLAGS)
-
-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/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/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/mkpasswd.dll: src/modules/mkpasswd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/mkpasswd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/mkpasswd.pdb $(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/monitor.dll: src/modules/monitor.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/monitor.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/monitor.pdb $(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/names.dll: src/modules/names.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/names.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/names.pdb $(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/nick.dll: src/modules/nick.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/nick.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/nick.pdb $(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/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/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/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/oper.dll: src/modules/oper.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/oper.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/oper.pdb $(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/opermotd.dll: src/modules/opermotd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/opermotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/opermotd.pdb $(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/pass.dll: src/modules/pass.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/pass.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/pass.pdb $(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/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/real-quit-reason.dll: src/modules/real-quit-reason.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/real-quit-reason.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/real-quit-reason.pdb $(MODLFLAGS)
-
-src/modules/reply-tag.dll: src/modules/reply-tag.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/reply-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/reply-tag.pdb $(MODLFLAGS)
-
-src/modules/reputation.dll: src/modules/reputation.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/reputation.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/reputation.pdb $(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/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/rmtkl.dll: src/modules/rmtkl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rmtkl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/rmtkl.pdb $(MODLFLAGS)
-
-src/modules/rpc/channel.dll: src/modules/rpc/channel.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/channel.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/channel.pdb $(MODLFLAGS)
-
-src/modules/rpc/log.dll: src/modules/rpc/log.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/log.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/log.pdb $(MODLFLAGS)
-
-src/modules/rpc/name_ban.dll: src/modules/rpc/name_ban.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/name_ban.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/name_ban.pdb $(MODLFLAGS)
-
-src/modules/rpc/rpc.dll: src/modules/rpc/rpc.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/rpc.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/rpc.pdb $(MODLFLAGS)
-
-src/modules/rpc/stats.dll: src/modules/rpc/stats.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/stats.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/stats.pdb $(MODLFLAGS)
-
-src/modules/rpc/server.dll: src/modules/rpc/server.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/server.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/server.pdb $(MODLFLAGS)
-
-src/modules/rpc/server_ban.dll: src/modules/rpc/server_ban.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/server_ban.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/server_ban.pdb $(MODLFLAGS)
-
-src/modules/rpc/server_ban_exception.dll: src/modules/rpc/server_ban_exception.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/server_ban_exception.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/server_ban_exception.pdb $(MODLFLAGS)
-
-src/modules/rpc/spamfilter.dll: src/modules/rpc/spamfilter.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/spamfilter.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/spamfilter.pdb $(MODLFLAGS)
-
-src/modules/rpc/user.dll: src/modules/rpc/user.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/user.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/user.pdb $(MODLFLAGS)
-
-src/modules/rpc/whowas.dll: src/modules/rpc/whowas.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rpc/whowas.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/whowas.pdb $(MODLFLAGS)
-
-src/modules/rules.dll: src/modules/rules.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/rules.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/rules.pdb $(MODLFLAGS)
-
-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/samode.dll: src/modules/samode.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/samode.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/samode.pdb $(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/sasl.dll: src/modules/sasl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sasl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sasl.pdb $(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/sendsno.dll: src/modules/sendsno.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sendsno.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sendsno.pdb $(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/server.dll: src/modules/server.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/server.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/server.pdb $(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/sethost.dll: src/modules/sethost.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sethost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sethost.pdb $(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/setname.dll: src/modules/setname.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/setname.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/setname.pdb $(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/sinfo.dll: src/modules/sinfo.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sinfo.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sinfo.pdb $(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/slog.dll: src/modules/slog.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/slog.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/slog.pdb $(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/squit.dll: src/modules/squit.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/squit.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/squit.pdb $(MODLFLAGS)
-
-src/modules/sreply.dll: src/modules/sreply.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sreply.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sreply.pdb $(MODLFLAGS)
-
-src/modules/staff.dll: src/modules/staff.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/staff.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/staff.pdb $(MODLFLAGS)
-
-src/modules/standard-replies.dll: src/modules/standard-replies.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/standard-replies.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/standard-replies.pdb $(MODLFLAGS)
-
-src/modules/starttls.dll: src/modules/starttls.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/starttls.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/starttls.pdb $(MODLFLAGS)
-
-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/sts.dll: src/modules/sts.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/sts.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sts.pdb $(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/svskill.dll: src/modules/svskill.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svskill.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svskill.pdb $(MODLFLAGS)
-
-src/modules/svslogin.dll: src/modules/svslogin.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svslogin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svslogin.pdb $(MODLFLAGS)
-
-src/modules/svslusers.dll: src/modules/svslusers.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svslusers.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svslusers.pdb $(MODLFLAGS)
-
-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/svsmotd.dll: src/modules/svsmotd.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svsmotd.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsmotd.pdb $(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/svsnline.dll: src/modules/svsnline.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svsnline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnline.pdb $(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/svsnoop.dll: src/modules/svsnoop.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svsnoop.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnoop.pdb $(MODLFLAGS)
-
-src/modules/svso.dll: src/modules/svso.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svso.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svso.pdb $(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/svssilence.dll: src/modules/svssilence.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svssilence.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svssilence.pdb $(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/svswatch.dll: src/modules/svswatch.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/svswatch.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svswatch.pdb $(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/targetfloodprot.dll: src/modules/targetfloodprot.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/targetfloodprot.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/targetfloodprot.pdb $(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/tkl.dll: src/modules/tkl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/tkl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tkl.pdb $(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/tline.dll: src/modules/tline.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/tline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tline.pdb $(MODLFLAGS)
-
-src/modules/tls_antidos.dll: src/modules/tls_antidos.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/tls_antidos.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tls_antidos.pdb $(MODLFLAGS)
-
-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/topic.dll: src/modules/topic.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/topic.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/topic.pdb $(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/tsctl.dll: src/modules/tsctl.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/tsctl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tsctl.pdb $(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/channel-context.dll: src/modules/channel-context.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/channel-context.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/channel-context.pdb $(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/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/unsqline.dll: src/modules/unsqline.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/unsqline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/unsqline.pdb $(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/userhost.dll: src/modules/userhost.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/userhost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/userhost.pdb $(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/userip.dll: src/modules/userip.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/userip.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/userip.pdb $(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/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/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/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/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/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/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/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/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/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/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/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/vhost.dll: src/modules/vhost.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/vhost.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/vhost.pdb $(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/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/webserver.dll: src/modules/webserver.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/webserver.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/webserver.pdb $(MODLFLAGS)
-
-src/modules/websocket.dll: src/modules/websocket.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/websocket.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/websocket.pdb $(MODLFLAGS)
-
-src/modules/websocket_common.dll: src/modules/websocket_common.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/websocket_common.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/websocket_common.pdb $(MODLFLAGS)
-
-src/modules/whois.dll: src/modules/whois.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/whois.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whois.pdb $(MODLFLAGS)
-
-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/whowasdb.dll: src/modules/whowasdb.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/whowasdb.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whowasdb.pdb $(MODLFLAGS)
-
-src/modules/whox.dll: src/modules/whox.c $(INCLUDES)
-	$(CC) $(MODCFLAGS) src/modules/whox.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whox.pdb $(MODLFLAGS)
-
diff --git a/README.md b/README.md
@@ -1,10 +0,0 @@
-# WARNING
-UnrealIRCd has proven to be quite an unstable and poorly coded IRCd choice for our network. It has also raise quite a bit of security concern.
-
-After using it for about 10 years now, we have finally determined that there **way** better options in modern times.
-
-[Ergo](https://ergo.chat/about) will soon be powering SuperNETs IRC network behind a **MASSIVE** kubernetes cluster of 100's of nodes.
-
-Fork this repository at your own risk. No longer will updates be applied to this repository.
-
-2024 starts a new revolution in SuperNETs history.
diff --git a/SECURITY.md b/SECURITY.md
@@ -1,22 +0,0 @@
-# Security Policy
-
-## Supported Versions
-* 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.
-
-## Reporting a Vulnerability
-
-Please report issues on the [bug tracker](https://bugs.unrealircd.org) and in the bug submit form **set the 'View Status' to 'private'**.
-
-Do not report security issues on the forums or in a public IRC channel such as #unreal-support.
-If you insist on e-mail then you can use syzop@unrealircd.org or security@unrealircd.org. Again, the bug tracker is preferred.
-
-If you are *unsure* if something is a security issue, then report it at the bug tracker as a 'private' bug anyway. Better safe than sorry.
-Do not ask around in public channels or forums.
-
-You should get a response or at least an acknowledgement soon. If you don't hear back within 24 hours, then please try to contact us again.
-
-## Full policy
-See https://www.unrealircd.org/docs/Policy:_Handling_of_security_issues for full information.
diff --git a/ansible/deploy_ircd.yml b/ansible/deploy_ircd.yml
@@ -0,0 +1,106 @@
+---
+- name: Deploy UnrealIRCd
+  hosts: ircd_servers
+  become: true
+  gather_facts: true
+
+  vars:
+    ircd_user: ircd
+    install_dir: /opt/ircd
+
+  tasks:
+    - name: Install required packages
+      package:
+        name:
+          - git
+          - make
+          - gcc
+          - curl
+        state: present
+
+    - name: Check if Docker is installed
+      stat:
+        path: /usr/bin/docker
+      register: docker_check
+
+    - name: Install Docker using convenience script
+      block:
+        - name: Download Docker installation script
+          get_url:
+            url: https://get.docker.com
+            dest: /tmp/get-docker.sh
+            mode: '0755'
+            force: yes
+
+        - name: Execute Docker installation script
+          command: /tmp/get-docker.sh
+          args:
+            creates: /usr/bin/docker
+
+        - name: Clean up installation script
+          file:
+            path: /tmp/get-docker.sh
+            state: absent
+      when: not docker_check.stat.exists
+
+    - name: Create ircd user
+      user:
+        name: "{{ ircd_user }}"
+        groups: docker
+        append: yes
+        system: yes
+
+    - name: Enable and start Docker service
+      systemd:
+        name: docker
+        state: started
+        enabled: yes
+        
+    - name: Check if repository exists
+      stat:
+        path: "{{ install_dir }}/.git"
+      register: repo_check
+
+    - name: Clone UnrealIRCd repository
+      git:
+        repo: 'https://git.supernets.org/supernets/ircd'
+        dest: "{{ install_dir }}"
+        force: true
+        version: master
+      when: not repo_check.stat.exists
+
+    - name: Update existing repository
+      command:
+        cmd: git pull
+        chdir: "{{ install_dir }}"
+      when: repo_check.stat.exists
+      register: git_pull
+      changed_when: git_pull.stdout != 'Already up to date.'
+
+    - name: Set ircd ownership recursively
+      file:
+        path: "{{ install_dir }}"
+        state: directory
+        owner: "{{ ircd_user }}"
+        group: "{{ ircd_user }}"
+        recurse: yes
+
+    - name: Set execute permission on setup.sh
+      file:
+        path: "{{ install_dir }}/setup.sh"
+        mode: '0755'
+
+    - name: Run setup script
+      command:
+        cmd: ./setup.sh
+        chdir: "{{ install_dir }}"
+      become_user: "{{ ircd_user }}"
+      register: setup_result
+      changed_when: false
+      failed_when: setup_result.rc != 0
+      ignore_errors: true
+
+    - name: Show setup output
+      debug:
+        var: setup_result.stdout_lines
+      when: setup_result is defined
+\ No newline at end of file
diff --git a/ansible/inventory.example.ini b/ansible/inventory.example.ini
@@ -0,0 +1,11 @@
+[all:vars]
+ansible_user=root
+ansible_ssh_private_key_file=supernets.private # Assumed to be in the same directory as the inventory file
+
+[ircd_servers]
+# Add your servers below in the format:
+# hostname ansible_host=ip_address ansible_port=ssh_port leaf_name=server_leaf_name
+# Examples:
+# ircd01 ansible_host=192.168.1.10 ansible_port=22    leaf_name=us-east
+# ircd02 ansible_host=192.168.1.11 ansible_port=2222  leaf_name=eu-west
+# ircd03 ansible_host=192.168.1.12 ansible_port=42022 leaf_name=asia-east 
+\ No newline at end of file
diff --git a/doc/conf/ircd.motd b/doc/conf/ircd.motd
@@ -1,57 +0,0 @@
-   0,0     0â•—  0,0  0â•—  0,0  0â•— 0,0      0â•—  0,0       0â•— 0,0      0â•—
-  0,0  0â•”â•â•0,0  0â•— 0,0  0â•‘  0,0  0â•‘ 0,0  0â•”â•â•0,0  0â•— 0,0  0â•”â•â•â•â•â• 0,0  0â•”â•â•0,0  0â•—
-  0,0  0â•‘  ╚â•â• 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
-  0╚0,0     0â•—  0,0  0â•‘  0,0  0â•‘ 0,0      0╔╠0,0       0â•— 0,0      0â•”â•
-   0╚â•â•â•0,0  0â•— 0,0  0â•‘  0,0  0â•‘ 0,0  0â•”â•â•â•â•  0,0  0â•”â•â•â•â•â• 0,0  0â•”â•â•0,0  0â•—
-  0,0  0â•—  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
-  0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
-  0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
-  0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
-  0╚0,0     0╔╠╚0,0     0╔╠0,0  0║      0,0       0╗ 0,0  0║  0,0  0║
-0   ╚â•â•â•â•â•   ╚â•â•â•â•â•  ╚â•â•      ╚â•â•â•â•â•â•╠╚â•â•  ╚â•â•
-
-      0,12                                          
-    0,12    0,4                              0,12          
-  0,12    0,4      0,8  0,4    0,8        0,4    0,8  0,4        0,12        
-0,12    0,4      0,8  0,4    0,8            0,4      0,8  0,4      0,12      
-0,12    0,4    0,8    0,4    0,8                      0,4    0,12      
-  0,12    0,4    0,8  0,4                      0,8    0,4  0,12        
-    0,12    0,4                              0,12          
-      0,12    0,4    0,8              0,4        0,12            
-        0,12    0,4      0,8  0,4  0,8      0,4      0,12              
-          0,12    0,4                  0,12                
-            0,12    0,4    0,8      0,4    0,12                  
-              0,12    0,4    0,8  0,4    0,12                    
-                0,12    0,4      0,12                      
-                  0,12    0,4  0,12                        
-                    0,12                            
-                      0,12  
-
-     0,0     0â•—  0,0  0â•— 0,0       0â•— 0,0      0â•—  0,0     0â•—
-     0,0  0â•”â•0,0  0â•— 0,0  0â•‘ 0,0  0â•”â•â•â•â•â•   0,0  0â•”â•â• 0,0  0â•”â•â•0,0  0â•—
-     0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘        0,0  0â•‘   0,0  0â•‘  ╚â•â•
-     0,0  0║ 0,0  0║ 0,0  0║ 0,0       0╗   0,0  0║   ╚0,0     0╗
-     0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘ 0,0  0â•”â•â•â•â•â•   0,0  0â•‘    ╚â•â•â•0,0  0â•—
-     0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘        0,0  0â•‘   0,0     0,0  0â•‘
-     0,0  0â•‘ ╚0,0     0â•‘ 0,0       0â•—   0,0  0â•‘   ╚0,0     0â•”â•
-     0╚â•â•  ╚â•â•â•â•╠╚â•â•â•â•â•â•â•   ╚â•â•    ╚â•â•â•â•â•
-
-4─────────┤ 0THE WILD WILD WEST OF IRC 4├─────────
-
-14• 7  Round-robin   irc.supernets.org 14(IPv4/IPv6)
-14• 7        Onion   14removed until further notice
-
-14• 7        Ports   6660-6669 & 7000
-14• 7SSL/TLS Ports   6697      & 9000
-
-14• 7         Mail   12admin@supernets.org
-14• 7          Git   12https://git.supernets.org
-14• 7      Twitter   12https://twitter.com/super_nets
-14• 7      Website   12https://supernets.org/
-
-4─────────┤ 0MOST DANGEROUS IRC NETWORK 4├────────
-
-14• 7This is a hostile chat environment
-14• 7Do not disrupt the orderly operation of the network
-14• 7No distribution of child pornography
-14• 7See /RULES for a list of network rules
diff --git a/doc/conf/unrealircd.link.conf b/doc/conf/unrealircd.link.conf
@@ -1,40 +0,0 @@
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/badwords.conf";
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/except.conf";
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/ircd.conf";
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/modules.conf";
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/opers.conf";
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/snomasks.conf";
-include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/spamfilter.conf";
-
-me { name "example.supernets.org"; info "SuperNETS IRC Network"; sid XXX; }
-
-listen {
-	ip *;
-	port 6697;
-	options { clientsonly; tls; }
-	tls-options {
-		certificate "tls/irc.crt";
-		key         "tls/irc.key";
-	}
-}
-listen {
-	ip *;
-	port 9000;
-	options { clientsonly; tls; }
-	tls-options {
-		certificate "tls/irc.crt";
-		key         "tls/irc.key";
-	}
-}
-
-listen {        
-	file "/etc/tor/unrealircd/tor_ircd.socket";
-	mode 0777;
-	spoof-ip 127.0.0.2;
-}
-listen {
-	file "/etc/tor/unrealircd/tor_tls_ircd.socket";
-	mode 0777;
-	spoof-ip 127.0.0.2;
-	options { tls; }
-}
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
@@ -1,281 +0,0 @@
-@define $VOID "8,4   E N T E R   T H E   V O I D   ";
-
-admin {
-	"4Administrator: Brandon Brown      14(aka MRCHATS)  6branbran89@supernets.org";
-	"    4Moderator: Bristopher Manning 14(aka delorean) 6simpsonsfan95@supernets.org";
-	"        4Sales: Branthony Bronson  14(aka pyrex)    6showercaphandgun@supernets.org";
-	"";
-	"Feel free to chat with us in #5000 for network help & support!";
-}
-
-alias botserv { type services; }
-alias bs { target botserv; type services; }
-alias chanserv { type services; }
-alias cs { target chanserv; type services; }
-alias hostserv { type services; }
-alias hs { target hostserv; type services; }
-alias nickserv { type services; }
-alias ns { target nickserv; type services; }
-alias operserv { type services; }
-alias os { target operserv; type services; }
-
-class clients { pingfreq 120; maxclients  100; sendq  25M; recvq 32k; }
-class known   { pingfreq 120; maxclients  250; sendq  50M; recvq 32k; }
-class local   { pingfreq 300; maxclients 1000; sendq  50M; options { nofakelag; } }
-class tor     { pingfreq 300; maxclients  100; sendq  25M; }
-class servers { pingfreq 300; maxclients   20; sendq 100M; connfreq 15; }
-
-allow { mask *;                              class clients; maxperip 2;    global-maxperip 2; }
-allow { mask { security-group known-users; } class known;   maxperip 3;    global-maxperip 3; }
-allow { mask { 127.0.0.1; ::1;             } class local;   maxperip 1000; global-maxperip 1000; password "simpsonsfan"; }
-allow { mask { 127.0.0.2;                  } class tor;     maxperip 100;  global-maxperip 100; }
-
-listen { ip *; port 6660–6669; options { clientsonly;      } }
-listen { ip *; port 7000;      options { clientsonly;      } }
-listen { ip *; port REDACTED;  options { serversonly; tls; } }
-
-#require authentication {
-#	mask { *@127.0.0.2; }
-#	reason "$VOID";
-#}
-
-deny channel { channel "#help";     reason "This channel has moved to #superbowl"; redirect "#superbowl"; }
-deny channel { channel "#pumpcoin"; reason "This channel has moved to #exchange";  redirect "#exchange";  }
-
-link irc.supernets.org {
-	incoming { mask REDACTED; }
-	outgoing {
-		bind-ip *;
-		hostname REDACTED;
-		port REDACTED;
-		options { tls; autoconnect; }
-	}
-	password "REDACTED" { spkifp; }
-	class servers;
-}
-
-log {
-	source { error; fatal; warn; }
-	destination { file "ircd.log" { maxsize 5M; } }
-}
-
-log {
-	source { !debug; all; }
-	destination { channel "#syslog"; }
-}
-
-tld { mask *@*; motd remote.motd; rules remote.motd; options { remote; } }
-
-ulines { services.supernets.org; }
-
-blacklist dronebl {
-	dns {
-		name dnsbl.dronebl.org;
-		type record;
-		reply { 3; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; }
-	}
-	action gzline;
-	ban-time 30d;
-	reason "$VOID";
-}
-
-blacklist efnetrbl {
-	dns {
-		name rbl.efnetrbl.org;
-		type record;
-		reply { 1; 4; 5; }
-	}
-	action gzline;
-	ban-time 30d;
-	reason "$VOID";
-}
-
-blacklist torbl {
-	dns {
-		name torexit.dan.me.uk;
-		type record;
-		reply { 100; }
-	}
-	action gzline;
-	ban-time 30d;
-	reason "$VOID";
-}
-
-set {
-	kline-address "enterthevoid@supernets.org";
-	gline-address "enterthevoid@supernets.org";
-	modes-on-connect "+iIpTx";
-	modes-on-oper "+Hq";
-	snomask-on-oper "+o";
-	modes-on-join "+ns";
-	level-on-join "op";
-	restrict-usermodes "ips";
-	restrict-channelmodes "nLpPs";
-	restrict-commands {
-		channel-message { except { connect-time 5;   identified yes; reputation-score 100; } }
-		channel-notice  { except { connect-time 15;  identified yes; reputation-score 100; } }
-		invite          { except { connect-time 300; identified yes; reputation-score 100; } }
-		join            { except { connect-time 5;   identified yes; reputation-score 100; } }
-		list            { except { connect-time 5;   identified yes; reputation-score 100; } }
-		private-message { except { connect-time 300; identified yes; reputation-score 100; } }
-		private-notice  { except { connect-time 300; identified yes; reputation-score 100; } }
-	}
-	oper-auto-join "#syslog";
-	who-limit 1;
-	nick-length 20;
-	maxchannelsperuser 10;
-	channel-command-prefix "`!@$.";
-	topic-setter nick;
-	ban-setter nick;
-	options { hide-ulines; flat-map; identd-check; }
-	network-name "SuperNETs";
-	default-server "irc.supernets.org";
-	services-server "services.supernets.org";
-	sasl-server "services.supernets.org";
-	help-channel "#superbowl";
-	cloak-method ip;
-	cloak-keys {
-		"REDACTED";
-		"REDACTED";
-		"REDACTED";
-	}
-	cloak-prefix "SUPER";
-	plaintext-policy {
-		user warn;
-		oper deny;
-		server deny;
-		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 TLS protocol or cipher";
-		oper-message "Network operators must be using an up-to-date TLS protocol & cipher";
-	}
-	anti-flood {
-		channel {
-			profile defcon { flood-mode "[10j#R5,500m#M5,10n#N5,10k#K5]:15"; }
-			boot-delay 75;
-			split-delay 75;
-		}
-		everyone {
-			connect-flood 3:60;
-			handshake-data-flood {
-				amount 4k;
-				ban-action kill;
-			}
-		}
-		known-users {
-			away-flood    10:300;
-			invite-flood  10:300;
-			knock-flood   10:300;
-			join-flood	  10:300;
-			nick-flood    10:300;
-			max-concurrent-conversations { users 5; new-user-every 60s; }
-			lag-penalty 10; # update?
-			lag-penalty-bytes 0;
-		}
-		unknown-users {
-			away-flood    3:300;
-			invite-flood  3:300;
-			knock-flood   3:300;
-			join-flood    3:300;
-			nick-flood    3:300;
-			max-concurrent-conversations { users 2; new-user-every 120s; }
-			lag-penalty 25;
-			lag-penalty-bytes 90;
-		}
-	}
-	default-bantime 30d;
-	modef-default-unsettime 5;
-	spamfilter {
-		ban-time 30d;
-		ban-reason "$VOID";
-		utf8 yes;
-		except "#anythinggoes";
-	}
-	max-targets-per-command { kick 1; part 1; privmsg 1; }
-	hide-ban-reason yes;
-	reject-message {
-		gline                "$VOID";
-		kline                "$VOID";
-		password-mismatch    "$VOID";
-		server-full          "$VOID";
-		too-many-connections "$VOID";
-		unauthorized         "$VOID";
-	}
-	antimixedutf8 {
-		score 8;
-		ban-action block;
-		ban-reason "$VOID";
-	}
-	connthrottle {
-		except        { reputation-score 100; identified yes; webirc yes; }
-		new-users     { local-throttle 20:60; global-throttle 30:60;      }
-		disabled-when { reputation-gathering 1w; start-delay 3m;          }
-		reason "$VOID";
-	}
-	history {
-		channel {
-			playback-on-join { lines 1000; time 1d; }
-			max-storage-per-channel {
-				registered   { lines 1000; time 1d; } 
-				unregistered { lines 100;  time 1h; } 
-			}
-		}
-	}
-	manual-ban-target ip;
-	hide-idle-time { policy always; }
-	whois-details {
-		bot         { everyone none; self full; oper full; } 
-		channels    { everyone none; self full; oper full; }
-		oper        { everyone none; self full; oper full; } 
-		reputation  { everyone full; self full; oper full; }
-		server      { everyone none; self full; oper full; }
-		swhois      { everyone full; self full; oper full; }
-	}
-}
-
-hideserver {
-	disable-map yes;
-	disable-links yes;
-	map-deny-message "$VOID";
-	links-deny-message "$VOID";
-}
-
-security-group known-users {
-	identified yes;
-	reputation-score 10000;
-}
-
-security-group tor {
-	ip 127.0.0.2;
-}
-
-set known-users {
-	auto-join "#superbowl";
-}
-
-set unknown-users {
-	auto-join "#blackhole";
-	static-quit "EMO-QUIT";
-	static-part "EMO-PART";
-}
-
-set tor {
-	auto-join "#tor";
-	static-quit "EMO-QUIT";
-	static-part "EMO-PART";
-}
-
-ban nick {
-	mask "*ac*d*v*ga*";
-	reason "$VOID"	
-}
-
-ban nick {
-	mask "MemoServ"
-	reason "$VOID";
-}
diff --git a/extras/c-ares.tar.gz b/extras/c-ares.tar.gz
Binary files differ.
diff --git a/extras/pcre2.tar.gz b/extras/pcre2.tar.gz
Binary files differ.
diff --git a/.gitignore b/ircd/.gitignore
diff --git a/ircd/Config b/ircd/Config
@@ -0,0 +1,919 @@
+#!/bin/sh
+#
+# Config script for UnrealIRCd
+# (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
+# copyright headers stay intact
+#
+#
+# Rewritten completely to be an interface to autoconf by codemastr
+# This was inspired by the config by Michael Graff (explorer@flame.org)
+# but was written from scratch
+
+# In order to be faster than the old Config, this assumes that all information
+# in the cache file is valid and therefore doesn't check it, so if you messed with
+# default values thats your problem :P
+
+# some bits edited by baafie on March 17 2004, every change marked.
+
+# Remove trailing slash in paths (if any)
+FIX_PATHNAMES () {
+	BASEPATH="${BASEPATH%/}"
+	BINDIR="${BINDIR%/}"
+	DATADIR="${DATADIR%/}"
+	CONFDIR="${CONFDIR%/}"
+	MODULESDIR="${MODULESDIR%/}"
+	LOGDIR="${LOGDIR%/}"
+	CACHEDIR="${CACHEDIR%/}"
+	DOCDIR="${DOCDIR%/}"
+	TMPDIR="${TMPDIR%/}"
+	PRIVATELIBDIR="${PRIVATELIBDIR%/}"
+	SSLDIR="${SSLDIR%/}"
+	CURLDIR="${CURLDIR%/}"
+}
+
+# Create and run the ./configure command with the appropriate
+# options based on the users settings.
+RUN_CONFIGURE () {
+ARG=" "
+
+if [ -z "$BINDIR" -o -z "$DATADIR" -o -z "$CONFDIR" -o -z "$MODULESDIR" -o -z "$LOGDIR" -o -z "$CACHEDIR" -o -z "$DOCDIR" -o -z "$TMPDIR" -o -z "$PRIVATELIBDIR" ]; then
+	echo "Sorry './Config -quick' cannot be used because your 'config.settings'"
+	echo "file either does not exist or is from an old UnrealIRCd version"
+	echo "(older than UnrealIRCd 5.0.0)."
+	echo ""
+	echo "Please run './Config' without -quick and answer all questions."
+	echo ""
+	exit
+fi
+
+
+mkdir -p $TMPDIR
+mkdir -p $PRIVATELIBDIR
+
+# Do this even if we're not in advanced mode
+if [ "$ADVANCED" = "1" ] ; then
+if [ "$NOOPEROVERRIDE" = "1" ] ; then
+	ARG="$ARG--with-no-operoverride "
+fi
+if [ "$OPEROVERRIDEVERIFY" = "1" ] ; then
+	ARG="$ARG--with-operoverride-verify "
+fi
+fi
+if test x"$SSLDIR" = "x" ; then
+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 [ "$MAXCONNECTIONS_REQUEST" != "auto" ]; then
+	ARG="$ARG--with-maxconnections=$MAXCONNECTIONS_REQUEST "
+fi
+
+ARG="$ARG--with-bindir=$BINDIR "
+ARG="$ARG--with-datadir=$DATADIR "
+ARG="$ARG--with-pidfile=$DATADIR/unrealircd.pid "
+ARG="$ARG--with-controlfile=$DATADIR/unrealircd.ctl "
+ARG="$ARG--with-confdir=$CONFDIR "
+ARG="$ARG--with-modulesdir=$MODULESDIR "
+ARG="$ARG--with-logdir=$LOGDIR "
+ARG="$ARG--with-cachedir=$CACHEDIR "
+ARG="$ARG--with-docdir=$DOCDIR "
+ARG="$ARG--with-tmpdir=$TMPDIR "
+ARG="$ARG--with-privatelibdir=$PRIVATELIBDIR "
+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
+if [ "x$INSTALLCURL" = "x1" ]; then
+	extras/curlinstall "$PRIVATELIBDIR" || exit 1
+fi
+# At least do SOME parallel compiling by default, IF:
+# - the MAKE environment variable is not set
+# - the MAKEFLAGS environment variable is not set
+# - we are using GNU Make
+if [ "x$MAKE" = "x" ]; then
+	if [ "x$MAKEFLAGS" = "x" ]; then
+		if make --version 2>&1|grep -q "GNU Make"; then
+			LOWMEM=0
+			if [ -f /proc/meminfo ]; then
+				FREEKB="`cat /proc/meminfo |grep MemAvailable|awk '{ print $2 }'`"
+				if [ "$FREEKB" != "" -a "$FREEKB" -lt 768000 ]; then
+					LOWMEM=1
+				fi
+			fi
+			if [ "$LOWMEM" = 0 ]; then
+				echo "Running with 4 concurrent build processes by default (make -j4)."
+				export MAKE='make -j4'
+			else
+				echo "System detected with less than 750MB available memory, not forcing parallel build."
+			fi
+		fi
+	fi
+fi
+
+echo $CONF
+$CONF || exit 1
+cd "$UNREALCWD"
+
+if [ "$QUICK" != "1" ] ; then
+	if [ ! -f $CONFDIR/tls/server.cert.pem -a ! -f $CONFDIR/ssl/server.cert.pem ]; then
+		export OPENSSLPATH
+		TEST=""
+		while [ -z "$TEST" ] ; do
+			if [ "$GENCERTIFICATE" = "1" ] ; then
+				TEST="Yes"
+			else
+				TEST="No"
+			fi
+			echo ""
+			echo "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
+			if [ -z "$cc" ] ; then
+			cc=$TEST
+			fi
+			case "$cc" in
+			[Yy]*)
+				GENCERTIFICATE="1"
+				;;
+			[Nn]*)
+				GENCERTIFICATE=""
+				;;
+			*)
+				echo ""
+				echo "You must enter either Yes or No"
+				TEST=""
+				;;
+			esac
+		done
+		if [ "$GENCERTIFICATE" = 1 ]; then
+			echo
+			echo "*******************************************************************************"
+			echo "Next you will be asked some questions in order to generate the 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
+			echo "Certificate created successfully."
+			sleep 1
+		else
+			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 "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
+}
+
+RUN_ADVANCED () {
+TEST=""
+while [ -z "$TEST" ] ; do
+	if [ "$NOOPEROVERRIDE" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to disable oper override?"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
+	[Yy]*)
+		NOOPEROVERRIDE="1"
+		;;
+	[Nn]*)
+		NOOPEROVERRIDE=""
+		;;
+	*)
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
+done
+
+TEST=""
+while [ -z "$TEST" ] ; do
+	if [ "$OPEROVERRIDEVERIFY" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to require opers to /invite themselves into a +s or +p channel?"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
+	[Yy]*)
+		OPEROVERRIDEVERIFY="1"
+		;;
+	[Nn]*)
+		OPEROVERRIDEVERIFY=""
+		;;
+	*)
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
+done
+
+}
+c=""
+n=""
+UNREALCWD="`pwd`"
+BASEPATH="/opt/ircd"
+DEFPERM="0600"
+SSLDIR=""
+NICKNAMEHISTORYLENGTH="100"
+MAXCONNECTIONS_REQUEST="auto"
+REMOTEINC="1"
+CURLDIR=""
+NOOPEROVERRIDE=""
+OPEROVERRIDEVERIFY=""
+GENCERTIFICATE="1"
+EXTRAPARA=""
+SANITIZER=""
+GEOIP="none"
+if [ "`eval echo -n 'a'`" = "-n a" ] ; then
+	c="\c"
+else
+	n="-n"
+fi
+
+
+#parse arguments
+IMPORTEDSETTINGS=""
+NOINTRO=""
+QUICK=""
+ADVANCED=""
+while [ $# -ge 1 ] ; do
+	if [ $1 = "--help" ] ; then
+		echo "Config utility for UnrealIRCd"
+		echo "-----------------------------"
+		echo "Syntax: ./Config [options]"
+		echo "-nointro     Skip intro (release notes, etc)"
+		echo "-quick       Skip questions, go straight to configure"
+		echo "-advanced    Include additional advanced questions"
+		exit 0
+	elif [ $1 = "-nointro" ] ; then
+		NOINTRO="1"
+	elif [ $1 = "-quick" -o $1 = "-q" ] ; then
+		QUICK="1"
+		echo "running quick config"
+		if [ -f "config.settings" ] ; then
+			. ./config.settings
+		fi
+		FIX_PATHNAMES
+		RUN_CONFIGURE
+		cd "$UNREALCWD"
+		exit 0
+	elif [ $1 = "-advanced" ] ; then
+		PREADVANCED="1"
+	fi
+	shift 1
+done
+
+if [ "$PREADVANCED" = "1" ] ; then
+	ADVANCED="1"
+elif [ "$ADVANCED" = "1" ]; then
+	ADVANCED=""
+fi
+
+if [ "`id -u`" = "0" ]; then
+	echo "ERROR: You cannot build or run UnrealIRCd as root"
+	echo ""
+	echo "Please create a separate account for building and running UnrealIRCd."
+	echo "See https://www.unrealircd.org/docs/Do_not_run_as_root"
+	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
+	cat doc/Config.header
+	echo ""
+	echo $n "[Press Enter to continue]"
+	read cc
+	clear
+fi
+
+echo "We will now ask you a number of questions. You can just press ENTER to accept the defaults!"
+echo ""
+
+# This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
+UNREALRELEASES="unrealircd-6.1.0-rc2 unrealircd-6.1.0-rc1 unrealircd-6.0.7 unrealircd-6.0.6 unrealircd-6.0.5 unrealircd-6.0.5-rc2 unrealircd-6.0.5-rc1 unrealircd-6.0.4.2 unrealircd-6.0.4.1 unrealircd-6.0.4 unrealircd-6.0.4-rc2 unrealircd-6.0.4-rc1 unrealircd-6.0.3 unrealircd-6.0.2 unrealircd-6.0.1.1 unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6"
+if [ -f "config.settings" ]; then
+	. ./config.settings
+else
+	# Try to load a previous config.settings
+	for x in $UNREALRELEASES
+	do
+		if [ -f ../$x/config.settings ]; then
+			IMPORTEDSETTINGS="../$x"
+			break
+		fi
+	done
+	echo "If you have previously installed UnrealIRCd on this shell then you can specify a"
+	echo "directory here so I can import the build settings and third party modules"
+	echo "to make your life a little easier."
+	if [ ! -z "$IMPORTEDSETTINGS" ]; then
+		echo "Found previous installation in: $IMPORTEDSETTINGS."
+		echo "You can enter a different path or type 'none' if you don't want to use it."
+		echo "Just press Enter to accept the default settings."
+	else
+		echo "If you install UnrealIRCd for the first time on this shell, then just hit Enter";
+	fi
+
+	TEST="$IMPORTEDSETTINGS"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ]; then
+		IMPORTEDSETTINGS="$TEST"
+	else
+		IMPORTEDSETTINGS="$cc"
+	fi
+	if [ "$IMPORTEDSETTINGS" = "none" ]; then
+		IMPORTEDSETTINGS=""
+	fi
+	if [ "$IMPORTEDSETTINGS" != "" ]; then
+		if [ -d $IMPORTEDSETTINGS/conf ]; then
+			echo "ERROR: Directory $IMPORTEDSETTINGS is an INSTALLATION directory (eg /home/irc/unrealircd)."
+			echo "This question was about a SOURCE directory (eg /home/irc/unrealircd-5.0.0)."
+			exit
+		fi
+		if [ ! -f $IMPORTEDSETTINGS/config.settings ]; then
+			echo "Directory $IMPORTEDSETTINGS does not exist or does not contain a config.settings file"
+			exit
+		fi
+		COPYMODULES="1"
+		if grep -q TOPICNICKISNUH $IMPORTEDSETTINGS/config.settings; then
+			echo "Directory $IMPORTEDSETTINGS seems to be UnrealIRCd 4.x (or older)."
+			echo "I will copy the settings but not any 3rd party modules, as they are incompatible with 5.x."
+			COPYMODULES="0"
+		fi
+		# Actually load the settings
+		. $IMPORTEDSETTINGS/config.settings
+		if [ "$COPYMODULES" = "1" ]; then
+			# Copy over 3rd party modules (also deals with 0 file cases, hence the silly looking code)
+			for f in $IMPORTEDSETTINGS/src/modules/third/*.c
+			do
+				[ -e "$f" ] && cp $f src/modules/third/
+			done
+		fi
+	fi
+fi
+# If we just imported settings and the curl dir is set to
+# something like /home/xxx/unrealircd-5.x.y/extras/curl/
+# (what we call 'local-curl') then remove this setting as
+# it would refer to the old UnrealIRCd installation.
+if [ ! -z "$IMPORTEDSETTINGS" ]; then
+	if echo "$CURLDIR"|grep -qi unrealircd; then
+		CURLDIR=""
+	fi
+fi
+
+TEST="$BASEPATH"
+echo ""
+echo "In what directory do you want to install UnrealIRCd?"
+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
+if [ -z "$cc" ] ; then
+	BASEPATH=$TEST
+else
+	BASEPATH=`eval echo $cc` # modified
+fi
+if [ "$BASEPATH" = "$UNREALCWD" ]; then
+	echo ""
+	echo "ERROR: The installation directory cannot be the same as the directory"
+	echo "       containing the source code ($UNREALCWD)."
+	echo " HINT: Usually the directory containing the source is $HOME/unrealircd-5.x.y"
+	echo "       and the installation directory you would need to enter is $HOME/unrealircd"
+	exit 1
+fi
+
+# TODO: For -advanced we could prompt the user.
+BINDIR="$BASEPATH/bin"
+DATADIR="$BASEPATH/data"
+CONFDIR="$BASEPATH/conf"
+MODULESDIR="$BASEPATH/modules"
+LOGDIR="$BASEPATH/logs"
+CACHEDIR="$BASEPATH/cache"
+DOCDIR="$BASEPATH/doc"
+TMPDIR="$BASEPATH/tmp"
+PRIVATELIBDIR="$BASEPATH/lib"
+
+TEST=""
+while [ -z "$TEST" ] ; do
+	TEST="$DEFPERM"
+	echo ""
+	echo "What should the default permissions for your configuration files be? (Set this to 0 to disable)"
+	echo "It is strongly recommended that you use 0600 to prevent unwanted reading of the file"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		DEFPERM=$TEST
+		break
+	fi
+	case "$cc" in
+	[0-9]*)
+		DEFPERM="$cc"
+		;;
+	*)
+		echo ""
+		echo "You must enter a number"
+		TEST=""
+		;;
+	esac
+done
+
+echo ""
+echo "If you want, you can manually enter the path to OpenSSL/LibreSSL here."
+echo "In most cases you can leave this blank and it will be detected automatically."
+
+if [ -z "$SSLDIR" ]; then
+	uname|grep -q Darwin
+	if [ "$?" = 0 ]; then
+		echo "Looks like you're on a Mac - El Capitan and higher require"
+		echo "a 3rd party OpenSSL installation. We recommend using homebrew"
+		echo "to install OpenSSL, but you may install it any other way as well."
+		echo "We are selecting the default homebrew OpenSSL path - but you can"
+		echo "change it to another path if you have installed OpenSSL another way"
+		SSLDIR="/usr/local/opt/openssl/"
+	fi
+fi
+
+TEST="$SSLDIR"
+echo $n "[$TEST] -> $c"
+read cc
+if [ -z "$cc" ] ; then
+	SSLDIR="$TEST"
+else 
+	SSLDIR=`eval echo $cc` # modified
+fi
+
+if [ "$SSLDIR" != "" -a "$SSLDIR" != "/usr" ]; then
+	echo ""
+	echo "You answered previous question manually. Just note that if the library is not"
+	echo "in your default library path then UnrealIRCd may fail to start with an error"
+	echo "that it cannot find certain .so files (libraries). In that case you would have"
+	echo "to either set the LD_LIBARY_PATH environment variable, or you could update the"
+	echo "Makefile to link with -Wl,-rpath,$SSLDIR or similar."
+	echo ""
+	if [ "$SSLDIR" = "/usr/local" ]; then
+		echo "**** CAUTION ****"
+		echo "You have chosen to use OpenSSL from /usr/local. Just be aware that if you"
+		echo "use the LD_LIBRARY_PATH or -Wl,-rpath,$SSLDIR from above,"
+		echo "that you are diverting OTHER libraries to that path as well."
+		echo "It's not only loading OpenSSL from /usr/local but also potentially other"
+		echo "libraries like PCRE2, Jansson, or any of the other libraries that"
+		echo "UnrealIRCd uses (including dependencies about 40 libs in total!)"
+		echo "All that can result in weird issues and crashes!"
+		echo ""
+	fi
+	echo "Press enter to continue with the rest of the questions, or CTRL+C to abort."
+	read cc
+fi
+
+TEST=""
+while [ -z "$TEST" ] ; do
+	if [ "$REMOTEINC" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	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
+		cc=$TEST
+	fi
+	case "$cc" in
+	[Yy]*)
+		REMOTEINC="1"
+		;;
+	[Nn]*)
+		REMOTEINC=""
+		CURLDIR=""
+		;;
+	*)
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
+done
+
+if [ "$REMOTEINC" = "1" ] ; then
+	if [ ! -d "$CURLDIR" ]; then
+		# Reset any previous CURLDIR if it doesn't exist (anymore)
+		CURLDIR=""
+	fi
+
+	INSTALLCURL="0"
+	SUGGESTCURLDIR=""
+
+	if [ -d "/usr/local/include/curl" ]; then
+		SUGGESTCURLDIR="/usr/local"
+	fi
+	if [ -d "/usr/include/curl" ]; then
+		SUGGESTCURLDIR="/usr"
+	fi
+	# This one also works for /usr/include/x86_64-linux-gnu and friends:
+	if [ -f "/usr/bin/curl-config" ]; then
+		SUGGESTCURLDIR="/usr"
+	fi
+
+	GOTASYNC=0
+	if [ "x$SUGGESTCURLDIR" != "x" ]; then
+		# Check if it's of any use: a curl without async dns (cares) hangs the entire ircd..
+		# normally this is done in ./configure but now we're forced to do it also here..
+		if "$SUGGESTCURLDIR"/bin/curl-config --features | grep -q -e AsynchDNS; then
+			GOTASYNC="1"
+		fi
+		if [ "$GOTASYNC" != "1" ]; then
+			SUGGESTCURLDIRBAD="$CURLDIR"
+			SUGGESTCURLDIR=""
+		fi
+	fi
+
+	if [ "x$CURLDIR" = "x$HOME/curl" ]; then
+		if [ "x$SUGGESTCURLDIR" != "x" ]; then
+			# I guess some people will complain about this, but if system wide cURL is available
+			# and many people have old defaults then this is much preferred:
+			echo ""
+			echo "WARNING: Your previous (potentially old) setting is to use cURL from $HOME/curl."
+			echo "However, your operating system also provides a working cURL."
+			echo "I am therefore changing the setting to: $SUGGESTCURLDIR"
+			CURLDIR="$SUGGESTCURLDIR"
+		else
+			echo ""
+			echo "WARNING: We no longer use $HOME/curl nowadays."
+			echo "Use the automatic download and install feature below."
+			CURLDIR=""
+		fi
+	fi
+
+	if [ "x$CURLDIR" = "x" ]; then
+		CURLDIR="$SUGGESTCURLDIR"
+		# NOTE: CURLDIR may still be empty after this
+
+		# System curl has no asyncdns, so install our own.
+		if [ "$GOTASYNC" != "1" ]; then
+			CURLDIR=""
+		fi
+
+		# Need to output it here, as the HOME check from above may cause this to be no longer relevant.
+		if [ "x$CURLDIR" = "x" -a "x$SUGGESTCURLDIRBAD" != "x" ]; then
+			echo "Curl library was found in $SUGGESTCURLDIRBAD, but it does not support Asynchronous DNS (not compiled with c-ares)"
+			echo "so it's of no use to us as it would stall the IRCd on REHASH."
+		fi
+	fi
+
+	# Final check
+	if [ "x$CURLDIR" != "x" ]; then
+		"$CURLDIR/bin/curl-config" --features 2>/dev/null | grep -q -e AsynchDNS
+		if [ "$?" != 0 ]; then
+			echo "Curl from $CURLDIR seems unusable ($CURLDIR/bin/curl-config does not exist)"
+			CURLDIR=""
+		fi
+	fi
+
+	if [ "x$CURLDIR" = "x" ]; then
+		# Still empty?
+		TEST=""
+		while [ -z "$TEST" ] ; do
+			TEST="Yes"
+			echo ""
+			echo "Do you want me to automatically download and install curl for you?"
+			echo $n "[$TEST] -> $c"
+			read cc
+			if [ -z "$cc" ] ; then
+				cc=$TEST
+			fi
+			case "$cc" in
+			[Yy]*)
+				INSTALLCURL="1"
+				CURLDIR="$UNREALCWD/extras/curl"
+				;;
+			[Nn]*)
+				INSTALLCURL="0"
+				;;
+			*)
+				echo ""
+				echo "You must enter either Yes or No"
+				TEST=""
+				;;
+			esac
+		done
+	fi
+
+	if [ "$INSTALLCURL" != "1" ]; then
+		TEST=""
+		while [ -z "$TEST" ] ; do
+			TEST="$CURLDIR"
+			echo ""
+			echo "Specify the directory you installed libcurl to"
+			echo $n "[$TEST] -> $c"
+			read cc
+			if [ -z "$cc" ] ; then
+				cc=$TEST
+			else
+				TEST=$cc
+				CURLDIR=`eval echo $cc` # modified
+			fi
+		done
+		if [ "x$CURLDIR" != "x" ]; then
+			"$CURLDIR/bin/curl-config" --libs 1>/dev/null 2>&1
+			if [ "$?" != 0 ]; then
+				echo "Curl from $CURLDIR seems unusable ($CURLDIR/bin/curl-config does not exist)"
+				CURLDIR=""
+			fi
+		fi
+	fi
+fi
+
+
+TEST=""
+while [ -z "$TEST" ] ; do
+	TEST="$NICKNAMEHISTORYLENGTH"
+	echo ""
+	echo "How far back do you want to keep the nickname history?"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		NICKNAMEHISTORYLENGTH=$TEST
+		break
+	fi
+	case "$cc" in
+	[1-9]*)
+		NICKNAMEHISTORYLENGTH="$cc"
+		;;
+	*)
+		echo ""
+		echo "You must enter a number"
+		TEST=""
+		;;
+	esac
+done
+
+TEST=""
+while [ -z "$TEST" ] ; do
+	TEST="$GEOIP"
+	echo ""
+	echo "GeoIP is a feature that allows converting an IP address to a location (country)"
+	echo "Possible build options:"
+	echo "     classic: This is the DEFAULT geoip engine. It should work on all systems"
+	echo "              and receives automatic updates."
+	echo "libmaxminddb: This uses the libmaxminddb library. If you want to use this, then"
+	echo "              you need to install the libmaxminddb library on your system first"
+	echo "        none: Don't build with any geoip library (geoip-csv is still built)"
+	echo "Choose one of: classic, libmaxminddb, none"
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		GEOIP=$TEST
+		break
+	fi
+	case "$cc" in
+	classic)
+		GEOIP="$cc"
+		;;
+	libmaxminddb)
+		GEOIP="$cc"
+		;;
+	none)
+		GEOIP="$cc"
+		;;
+	*)
+		echo ""
+		echo "Invalid choice: $cc"
+		TEST=""
+		;;
+	esac
+done
+
+echo ""
+TEST=""
+while [ -z "$TEST" ] ; do
+	TEST="$MAXCONNECTIONS_REQUEST"
+	echo ""
+	echo "What is the maximum number of sockets (and file descriptors) that"
+	echo "UnrealIRCd may use?"
+	echo "It is recommended to leave this at the default setting 'auto',"
+	echo "which at present results in a limit of up to 16384, depending on"
+	echo "the system. When you boot UnrealIRCd later you will always see"
+	echo "the effective limit."
+	echo $n "[$TEST] -> $c"
+	read cc
+	if [ -z "$cc" ] ; then
+		MAXCONNECTIONS_REQUEST=$TEST
+		break
+	fi
+	case "$cc" in
+		auto)
+			MAXCONNECTIONS_REQUEST="$cc"
+			;;
+		[1-9][0-9][0-9]*)
+			MAXCONNECTIONS_REQUEST="$cc"
+			;;
+		*)
+			echo ""
+			echo "You must to enter a number greater than or equal to 100."
+			echo "Or enter 'auto' to leave it at automatic, which is recommended."
+			TEST=""
+			;;
+	esac
+done
+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?"
+echo "Most people don't need this and can just press ENTER."
+echo "Otherwise, see \`./configure --help' and write them here:"
+echo $n "[$TEST] -> $c"
+read EXTRAPARA
+if [ -z "$EXTRAPARA" ]; then
+	EXTRAPARA="$TEST"
+fi
+
+FIX_PATHNAMES
+
+rm -f config.settings
+cat > config.settings << __EOF__
+#
+# 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="$BASEPATH"
+BINDIR="$BINDIR"
+DATADIR="$DATADIR"
+CONFDIR="$CONFDIR"
+MODULESDIR="$MODULESDIR"
+LOGDIR="$LOGDIR"
+CACHEDIR="$CACHEDIR"
+DOCDIR="$DOCDIR"
+TMPDIR="$TMPDIR"
+PRIVATELIBDIR="$PRIVATELIBDIR"
+MAXCONNECTIONS_REQUEST="$MAXCONNECTIONS_REQUEST"
+NICKNAMEHISTORYLENGTH="$NICKNAMEHISTORYLENGTH"
+GEOIP="$GEOIP"
+DEFPERM="$DEFPERM"
+SSLDIR="$SSLDIR"
+REMOTEINC="$REMOTEINC"
+CURLDIR="$CURLDIR"
+NOOPEROVERRIDE="$NOOPEROVERRIDE"
+OPEROVERRIDEVERIFY="$OPEROVERRIDEVERIFY"
+GENCERTIFICATE="$GENCERTIFICATE"
+SANITIZER="$SANITIZER"
+EXTRAPARA="$EXTRAPARA"
+ADVANCED="$ADVANCED"
+__EOF__
+RUN_CONFIGURE
+cd "$UNREALCWD"
+cat << __EOF__
+
+ _______________________________________________________________________
+|                                                                       |
+|                    UnrealIRCd Compile-Time Config                     |
+|_______________________________________________________________________|
+|_______________________________________________________________________|
+|                                                                       |
+|                        - The UnrealIRCd Team -                        |
+|                                                                       |
+|              Bram Matthys (Syzop) - syzop@unrealircd.org              |
+|       Krzysztof Beresztant (k4be) - k4be@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/ircd/Makefile.in
diff --git a/autoconf/Makefile b/ircd/autoconf/Makefile
diff --git a/autoconf/config.guess b/ircd/autoconf/config.guess
diff --git a/autoconf/config.sub b/ircd/autoconf/config.sub
diff --git a/autoconf/install-sh b/ircd/autoconf/install-sh
diff --git a/autoconf/m4/ax_check_compile_flag.m4 b/ircd/autoconf/m4/ax_check_compile_flag.m4
diff --git a/autoconf/m4/ax_check_link_flag.m4 b/ircd/autoconf/m4/ax_check_link_flag.m4
diff --git a/autoconf/m4/ax_pthread.m4 b/ircd/autoconf/m4/ax_pthread.m4
diff --git a/autoconf/m4/unreal.m4 b/ircd/autoconf/m4/unreal.m4
diff --git a/autogen.sh b/ircd/autogen.sh
diff --git a/configure b/ircd/configure
diff --git a/configure.ac b/ircd/configure.ac
diff --git a/doc/Authors b/ircd/doc/Authors
diff --git a/doc/Config.header b/ircd/doc/Config.header
diff --git a/doc/Donation b/ircd/doc/Donation
diff --git a/doc/KEYS b/ircd/doc/KEYS
diff --git a/doc/RELEASE-NOTES.md b/ircd/doc/RELEASE-NOTES.md
diff --git a/doc/coding-guidelines b/ircd/doc/coding-guidelines
diff --git a/doc/compiling_win32.txt b/ircd/doc/compiling_win32.txt
diff --git a/doc/conf/badwords.conf b/ircd/doc/conf/badwords.conf
diff --git a/doc/conf/except.conf b/ircd/doc/conf/except.conf
diff --git a/ircd/doc/conf/ircd.motd b/ircd/doc/conf/ircd.motd
@@ -0,0 +1,63 @@
+   0,0     0â•—  0,0  0â•—  0,0  0â•— 0,0      0â•—  0,0       0â•— 0,0      0â•—
+  0,0  0â•”â•â•0,0  0â•— 0,0  0â•‘  0,0  0â•‘ 0,0  0â•”â•â•0,0  0â•— 0,0  0â•”â•â•â•â•â• 0,0  0â•”â•â•0,0  0â•—
+  0,0  0â•‘  ╚â•â• 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
+  0╚0,0     0â•—  0,0  0â•‘  0,0  0â•‘ 0,0      0╔╠0,0       0â•— 0,0      0â•”â•
+   0╚â•â•â•0,0  0â•— 0,0  0â•‘  0,0  0â•‘ 0,0  0â•”â•â•â•â•  0,0  0â•”â•â•â•â•â• 0,0  0â•”â•â•0,0  0â•—
+  0,0  0â•—  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
+  0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
+  0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
+  0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘  0,0  0â•‘ 0,0  0â•‘      0,0  0â•‘      0,0  0â•‘  0,0  0â•‘
+  0╚0,0     0╔╠╚0,0     0╔╠0,0  0║      0,0       0╗ 0,0  0║  0,0  0║
+0   ╚â•â•â•â•â•   ╚â•â•â•â•â•  ╚â•â•      ╚â•â•â•â•â•â•╠╚â•â•  ╚â•â•
+
+      0,12                                          
+    0,12    0,4                              0,12          
+  0,12    0,4      0,8  0,4    0,8        0,4    0,8  0,4        0,12        
+0,12    0,4      0,8  0,4    0,8            0,4      0,8  0,4      0,12      
+0,12    0,4    0,8    0,4    0,8                      0,4    0,12      
+  0,12    0,4    0,8  0,4                      0,8    0,4  0,12        
+    0,12    0,4                              0,12          
+      0,12    0,4    0,8              0,4        0,12            
+        0,12    0,4      0,8  0,4  0,8      0,4      0,12              
+          0,12    0,4                  0,12                
+            0,12    0,4    0,8      0,4    0,12                  
+              0,12    0,4    0,8  0,4    0,12                    
+                0,12    0,4      0,12                      
+                  0,12    0,4  0,12                        
+                    0,12                            
+                      0,12  
+
+     0,0     0â•—  0,0  0â•— 0,0       0â•— 0,0      0â•—  0,0     0â•—
+     0,0  0â•”â•0,0  0â•— 0,0  0â•‘ 0,0  0â•”â•â•â•â•â•   0,0  0â•”â•â• 0,0  0â•”â•â•0,0  0â•—
+     0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘        0,0  0â•‘   0,0  0â•‘  ╚â•â•
+     0,0  0║ 0,0  0║ 0,0  0║ 0,0       0╗   0,0  0║   ╚0,0     0╗
+     0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘ 0,0  0â•”â•â•â•â•â•   0,0  0â•‘    ╚â•â•â•0,0  0â•—
+     0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘ 0,0  0â•‘        0,0  0â•‘   0,0     0,0  0â•‘
+     0,0  0â•‘ ╚0,0     0â•‘ 0,0       0â•—   0,0  0â•‘   ╚0,0     0â•”â•
+     0╚â•â•  ╚â•â•â•â•╠╚â•â•â•â•â•â•â•   ╚â•â•    ╚â•â•â•â•â•
+
+4─────────┤ 0THE WILD WILD WEST OF IRC 4├─────────
+
+14• 7          Hub   hub.supernets.org
+14• 7  Round-robin   irc.supernets.org 14(IPv4/IPv6)
+14• 7        Onion   x6uqarww3ldvovqgkor3n3ixx6sbyclasfckem2konk7izzcaumcxrad.onion
+14• 7        Webchat https://irc.supernets.org
+
+14• 7        Ports   6660-6669 & 7000
+14• 7SSL/TLS Ports   6697      & 9000
+
+4─────────────────┤ 0SERVICES 4├─────────────────
+
+14• 7         Mail   12admin@supernets.org
+14• 7          Git   12https://git.supernets.org
+14• 7      Twitter   12https://twitter.com/super_nets
+14• 7      Website   12https://supernets.org
+14• 7        Jitsi   12https://hardchats.com
+14• 7         XMPP   12xmpp.supernets.org
+
+4─────────┤ 0MOST DANGEROUS IRC NETWORK 4├────────
+
+14• 7This is a hostile chat environment
+14• 7Do not disrupt the orderly operation of the network
+14• 7No distribution of child pornography
+14• 7See /RULES for a list of network rules
diff --git a/doc/conf/ircd.rules b/ircd/doc/conf/ircd.rules
diff --git a/doc/conf/links.conf b/ircd/doc/conf/links.conf
diff --git a/doc/conf/modules.conf b/ircd/doc/conf/modules.conf
diff --git a/doc/conf/opers.conf b/ircd/doc/conf/opers.conf
diff --git a/doc/conf/remote.motd b/ircd/doc/conf/remote.motd
diff --git a/doc/conf/snomasks.conf b/ircd/doc/conf/snomasks.conf
diff --git a/doc/conf/spamfilter.conf b/ircd/doc/conf/spamfilter.conf
diff --git a/doc/conf/tls/curl-ca-bundle.crt b/ircd/doc/conf/tls/curl-ca-bundle.crt
diff --git a/doc/conf/unrealircd.hub.conf b/ircd/doc/conf/unrealircd.hub.conf
diff --git a/ircd/doc/conf/unrealircd.link.conf b/ircd/doc/conf/unrealircd.link.conf
@@ -0,0 +1,8 @@
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/badwords.conf";
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/except.conf";
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/ircd.conf";
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/modules.conf";
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/opers.conf";
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/snomasks.conf";
+include "https://USERNAME:PASSWORD@hub.supernets.org:PORT/spamfilter.conf";
+me { name "example.supernets.org"; info "SuperNETS IRC Network"; sid XXX; }
+\ No newline at end of file
diff --git a/ircd/doc/conf/unrealircd.remote.conf b/ircd/doc/conf/unrealircd.remote.conf
@@ -0,0 +1,277 @@
+@define $VOID "8,4   E N T E R   T H E   V O I D   ";
+
+admin {
+	"         4Administrator: Wyatt Deere        14(aka chrono)        6chrono@digitalgangsta.com";
+	"           4Retaliation: Brandon McDubai    14(aka MRCHATS)       6branbran89@supernets.org";
+	"             4Moderator: Bristopher Manning 14(aka delorean)      6simpsonsfan420@supernets.org";
+	"                 4Sales: Branthony Bronson  14(aka pyrex)         6showercaphandgun@supernets.org";
+	"      4Public Relations: Bradshaw Wiggins   14(aka Baron Fortuna) 6lovemyrace@dailystormer.in";
+	"4Research & Development: Tim Allen-key      14(aka e)             6highschoolmusical@carltonbanksclub.edu";
+	"";
+	"Feel free to chat with us in #5000 for network help & support!";
+}
+
+alias botserv { type services; }
+alias bs { target botserv; type services; }
+alias chanserv { type services; }
+alias cs { target chanserv; type services; }
+alias hostserv { type services; }
+alias hs { target hostserv; type services; }
+alias nickserv { type services; }
+alias ns { target nickserv; type services; }
+alias operserv { type services; }
+alias os { target operserv; type services; }
+
+class clients { pingfreq 120; maxclients  100; sendq  25M; recvq 32k; }
+class known   { pingfreq 120; maxclients  250; sendq  50M; recvq 32k; }
+class local   { pingfreq 300; maxclients 1000; sendq  50M; options { nofakelag; } }
+class tor     { pingfreq 300; maxclients  100; sendq  25M; }
+class servers { pingfreq 300; maxclients   20; sendq 100M; connfreq 15; }
+
+allow { mask *;                              class clients; maxperip 2;    global-maxperip 2; }
+allow { mask { security-group known-users; } class known;   maxperip 3;    global-maxperip 3; }
+allow { mask { 127.0.0.1; ::1;             } class local;   maxperip 1000; global-maxperip 1000; password "simpsonsfan"; }
+#allow { mask { 127.0.0.2;                  } class tor;    maxperip 100;  global-maxperip 100; }
+
+listen { ip *; port 6660–6669; options { clientsonly;      } }
+listen { ip *; port 7000;      options { clientsonly;      } }
+listen { ip *; port REDACTED;  options { serversonly; tls; } }
+
+listen { ip *; port 6697; options { clientsonly; tls; } tls-options { certificate "tls/irc.crt"; key "tls/irc.key"; } }
+listen { ip *; port 9000; options { clientsonly; tls; } tls-options { certificate "tls/irc.crt"; key "tls/irc.key"; } }
+
+#listen { file "/etc/tor/unrealircd/tor_ircd.socket"; mode 0777; spoof-ip 127.0.0.2; }
+#listen { file "/etc/tor/unrealircd/tor_tls_ircd.socket"; mode 0777; spoof-ip 127.0.0.2; options { tls; } }
+
+#require authentication { mask { *@127.0.0.2; } reason "$VOID"; }
+
+deny channel { channel "#help";     reason "This channel has moved to #superbowl"; redirect "#superbowl"; }
+deny channel { channel "#pumpcoin"; reason "This channel has moved to #exchange";  redirect "#exchange";  }
+
+link irc.supernets.org {
+	incoming { mask REDACTED; }
+	outgoing {
+		bind-ip *;
+		hostname REDACTED;
+		port REDACTED;
+		options { tls; autoconnect; }
+	}
+	password "REDACTED" { spkifp; }
+	class servers;
+}
+
+log {
+	source { error; fatal; warn; }
+	destination { file "ircd.log" { maxsize 5M; } }
+}
+
+log {
+	source { !debug; all; }
+	destination { channel "#syslog"; }
+}
+
+tld { mask *@*; motd remote.motd; rules remote.motd; options { remote; } }
+
+ulines { services.supernets.org; }
+
+blacklist dronebl {
+	dns {
+		name dnsbl.dronebl.org;
+		type record;
+		reply { 3; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; }
+	}
+	action gzline;
+	ban-time 30d;
+	reason "$VOID";
+}
+
+blacklist efnetrbl {
+	dns {
+		name rbl.efnetrbl.org;
+		type record;
+		reply { 1; 4; 5; }
+	}
+	action gzline;
+	ban-time 30d;
+	reason "$VOID";
+}
+
+blacklist torbl {
+	dns {
+		name torexit.dan.me.uk;
+		type record;
+		reply { 100; }
+	}
+	action gzline;
+	ban-time 30d;
+	reason "$VOID";
+}
+
+set {
+	kline-address "enterthevoid@supernets.org";
+	gline-address "enterthevoid@supernets.org";
+	modes-on-connect "+iIpTx";
+	modes-on-oper "+Hq";
+	snomask-on-oper "+o";
+	modes-on-join "+ns";
+	level-on-join "op";
+	restrict-usermodes "ips";
+	restrict-channelmodes "nLpPs";
+	restrict-commands {
+		channel-message { except { connect-time 5;   identified yes; reputation-score 100; } }
+		channel-notice  { except { connect-time 15;  identified yes; reputation-score 100; } }
+		invite          { except { connect-time 300; identified yes; reputation-score 100; } }
+		join            { except { connect-time 5;   identified yes; reputation-score 100; } }
+		list            { except { connect-time 5;   identified yes; reputation-score 100; } }
+		private-message { except { connect-time 300; identified yes; reputation-score 100; } }
+		private-notice  { except { connect-time 300; identified yes; reputation-score 100; } }
+	}
+	oper-auto-join "#syslog";
+	who-limit 1;
+	nick-length 20;
+	maxchannelsperuser 10;
+	channel-command-prefix "`!@$.";
+	topic-setter nick;
+	ban-setter nick;
+	options { hide-ulines; flat-map; identd-check; }
+	network-name "SuperNETs";
+	default-server "irc.supernets.org";
+	services-server "services.supernets.org";
+	sasl-server "services.supernets.org";
+	help-channel "#superbowl";
+	cloak-method ip;
+	cloak-keys {
+		"REDACTED";
+		"REDACTED";
+		"REDACTED";
+	}
+	cloak-prefix "SUPER";
+	plaintext-policy {
+		user warn;
+		oper deny;
+		server deny;
+		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 TLS protocol or cipher";
+		oper-message "Network operators must be using an up-to-date TLS protocol & cipher";
+	}
+	anti-flood {
+		channel {
+			profile defcon { flood-mode "[10j#R5,500m#M5,10n#N5,10k#K5]:15"; }
+			boot-delay 75;
+			split-delay 75;
+		}
+		everyone {
+			connect-flood 3:60;
+			handshake-data-flood {
+				amount 4k;
+				ban-action kill;
+			}
+		}
+		known-users {
+			away-flood    10:300;
+			invite-flood  10:300;
+			knock-flood   10:300;
+			join-flood	  10:300;
+			nick-flood    10:300;
+			max-concurrent-conversations { users 5; new-user-every 60s; }
+			lag-penalty 10; # update?
+			lag-penalty-bytes 0;
+		}
+		unknown-users {
+			away-flood    3:300;
+			invite-flood  3:300;
+			knock-flood   3:300;
+			join-flood    3:300;
+			nick-flood    3:300;
+			max-concurrent-conversations { users 2; new-user-every 120s; }
+			lag-penalty 25;
+			lag-penalty-bytes 90;
+		}
+	}
+	default-bantime 30d;
+	modef-default-unsettime 5;
+	spamfilter {
+		ban-time 30d;
+		ban-reason "$VOID";
+		utf8 yes;
+		except "#anythinggoes";
+	}
+	max-targets-per-command { kick 1; part 1; privmsg 1; }
+	hide-ban-reason yes;
+	reject-message {
+		gline                "$VOID";
+		kline                "$VOID";
+		password-mismatch    "$VOID";
+		server-full          "$VOID";
+		too-many-connections "$VOID";
+		unauthorized         "$VOID";
+	}
+	antimixedutf8 {
+		score 8;
+		ban-action block;
+		ban-reason "$VOID";
+	}
+	connthrottle {
+		except        { reputation-score 100; identified yes; webirc yes; }
+		new-users     { local-throttle 20:60; global-throttle 30:60;      }
+		disabled-when { reputation-gathering 1w; start-delay 3m;          }
+		reason "$VOID";
+	}
+	history {
+		channel {
+			playback-on-join { lines 1000; time 1d; }
+			max-storage-per-channel {
+				registered   { lines 1000; time 1d; } 
+				unregistered { lines 100;  time 1h; } 
+			}
+		}
+	}
+	manual-ban-target ip;
+	hide-idle-time { policy always; }
+	whois-details {
+		bot         { everyone none; self full; oper full; } 
+		channels    { everyone none; self full; oper full; }
+		oper        { everyone none; self full; oper full; } 
+		reputation  { everyone full; self full; oper full; }
+		server      { everyone none; self full; oper full; }
+		swhois      { everyone full; self full; oper full; }
+	}
+}
+
+hideserver {
+	disable-map yes;
+	disable-links yes;
+	map-deny-message "$VOID";
+	links-deny-message "$VOID";
+}
+
+security-group known-users {
+	identified yes;
+	reputation-score 10000;
+}
+
+security-group tor {
+	ip 127.0.0.2;
+}
+
+set known-users {
+	auto-join "#superbowl";
+}
+
+set unknown-users {
+	auto-join "#blackhole";
+	static-quit "EMO-QUIT";
+	static-part "EMO-PART";
+}
+
+set tor {
+	auto-join "#tor";
+	static-quit "EMO-QUIT";
+	static-part "EMO-PART";
+}
+\ No newline at end of file
diff --git a/doc/tao.of.irc b/ircd/doc/tao.of.irc
diff --git a/doc/technical/005.txt b/ircd/doc/technical/005.txt
diff --git a/doc/technical/base64.txt b/ircd/doc/technical/base64.txt
diff --git a/doc/technical/serverprotocol.txt b/ircd/doc/technical/serverprotocol.txt
diff --git a/doc/translations.txt b/ircd/doc/translations.txt
diff --git a/extras/.indent.pro b/ircd/extras/.indent.pro
diff --git a/extras/VStudioAnalyze.ruleset b/ircd/extras/VStudioAnalyze.ruleset
diff --git a/extras/argon2.tar.gz b/ircd/extras/argon2.tar.gz
Binary files differ.
diff --git a/extras/build-tests/nix/build b/ircd/extras/build-tests/nix/build
diff --git a/extras/build-tests/nix/configs/default b/ircd/extras/build-tests/nix/configs/default
diff --git a/extras/build-tests/nix/run-tests b/ircd/extras/build-tests/nix/run-tests
diff --git a/extras/build-tests/nix/run-tests.bbwrapper b/ircd/extras/build-tests/nix/run-tests.bbwrapper
diff --git a/extras/build-tests/nix/select-config b/ircd/extras/build-tests/nix/select-config
diff --git a/extras/build-tests/windows/build.bat b/ircd/extras/build-tests/windows/build.bat
diff --git a/extras/build-tests/windows/compilecmd/vs2019.bat b/ircd/extras/build-tests/windows/compilecmd/vs2019.bat
diff --git a/extras/curlinstall b/ircd/extras/curlinstall
diff --git a/extras/doxygen/Developers.md b/ircd/extras/doxygen/Developers.md
diff --git a/extras/doxygen/Doxyfile b/ircd/extras/doxygen/Doxyfile
diff --git a/extras/doxygen/doxygen_custom.css b/ircd/extras/doxygen/doxygen_custom.css
diff --git a/extras/doxygen/header.html b/ircd/extras/doxygen/header.html
diff --git a/extras/geoip-classic.tar.gz b/ircd/extras/geoip-classic.tar.gz
Binary files differ.
diff --git a/extras/jansson.tar.gz b/ircd/extras/jansson.tar.gz
Binary files differ.
diff --git a/extras/libsodium.tar.gz b/ircd/extras/libsodium.tar.gz
Binary files differ.
diff --git a/extras/patches/patch_spamfilter_conf b/ircd/extras/patches/patch_spamfilter_conf
diff --git a/extras/patches/spamfilter.conf.patch b/ircd/extras/patches/spamfilter.conf.patch
diff --git a/extras/security/apparmor/unrealircd b/ircd/extras/security/apparmor/unrealircd
diff --git a/extras/tests/tls/cipherscan_profiles/baseline.txt b/ircd/extras/tests/tls/cipherscan_profiles/baseline.txt
diff --git a/extras/tests/tls/tls-tests b/ircd/extras/tests/tls/tls-tests
diff --git a/extras/tls.cnf b/ircd/extras/tls.cnf
diff --git a/extras/unreal.supp b/ircd/extras/unreal.supp
diff --git a/extras/unrealircd-upgrade-script.in b/ircd/extras/unrealircd-upgrade-script.in
diff --git a/extras/wrap-compiler-for-flag-check b/ircd/extras/wrap-compiler-for-flag-check
diff --git a/include/channel.h b/ircd/include/channel.h
diff --git a/include/common.h b/ircd/include/common.h
diff --git a/include/config.h b/ircd/include/config.h
diff --git a/include/crypt_blowfish.h b/ircd/include/crypt_blowfish.h
diff --git a/include/dbuf.h b/ircd/include/dbuf.h
diff --git a/include/dns.h b/ircd/include/dns.h
diff --git a/include/dynconf.h b/ircd/include/dynconf.h
diff --git a/include/fdlist.h b/ircd/include/fdlist.h
diff --git a/include/h.h b/ircd/include/h.h
diff --git a/include/ircsprintf.h b/ircd/include/ircsprintf.h
diff --git a/include/license.h b/ircd/include/license.h
diff --git a/include/list.h b/ircd/include/list.h
diff --git a/include/mempool.h b/ircd/include/mempool.h
diff --git a/include/modules.h b/ircd/include/modules.h
diff --git a/include/modversion.h b/ircd/include/modversion.h
diff --git a/include/msg.h b/ircd/include/msg.h
diff --git a/include/numeric.h b/ircd/include/numeric.h
diff --git a/include/openssl_hostname_validation.h b/ircd/include/openssl_hostname_validation.h
diff --git a/include/resource.h b/ircd/include/resource.h
diff --git a/include/setup.h.in b/ircd/include/setup.h.in
diff --git a/include/struct.h b/ircd/include/struct.h
diff --git a/include/sys.h b/ircd/include/sys.h
diff --git a/include/types.h b/ircd/include/types.h
diff --git a/include/unrealircd.h b/ircd/include/unrealircd.h
diff --git a/include/version.h b/ircd/include/version.h
diff --git a/include/whowas.h b/ircd/include/whowas.h
diff --git a/include/windows/setup.h b/ircd/include/windows/setup.h
diff --git a/src/Makefile.in b/ircd/src/Makefile.in
diff --git a/src/aliases.c b/ircd/src/aliases.c
diff --git a/src/api-channelmode.c b/ircd/src/api-channelmode.c
diff --git a/src/api-clicap.c b/ircd/src/api-clicap.c
diff --git a/src/api-command.c b/ircd/src/api-command.c
diff --git a/src/api-efunctions.c b/ircd/src/api-efunctions.c
diff --git a/src/api-event.c b/ircd/src/api-event.c
diff --git a/src/api-extban.c b/ircd/src/api-extban.c
diff --git a/src/api-history-backend.c b/ircd/src/api-history-backend.c
diff --git a/src/api-isupport.c b/ircd/src/api-isupport.c
diff --git a/src/api-messagetag.c b/ircd/src/api-messagetag.c
diff --git a/src/api-moddata.c b/ircd/src/api-moddata.c
diff --git a/src/api-rpc.c b/ircd/src/api-rpc.c
diff --git a/src/api-usermode.c b/ircd/src/api-usermode.c
diff --git a/src/auth.c b/ircd/src/auth.c
diff --git a/src/buildmod b/ircd/src/buildmod
diff --git a/src/channel.c b/ircd/src/channel.c
diff --git a/src/conf.c b/ircd/src/conf.c
diff --git a/src/conf_preprocessor.c b/ircd/src/conf_preprocessor.c
diff --git a/src/crashreport.c b/ircd/src/crashreport.c
diff --git a/src/crule.c b/ircd/src/crule.c
diff --git a/src/crypt_blowfish.c b/ircd/src/crypt_blowfish.c
diff --git a/src/dbuf.c b/ircd/src/dbuf.c
diff --git a/src/debug.c b/ircd/src/debug.c
diff --git a/src/dispatch.c b/ircd/src/dispatch.c
diff --git a/src/dns.c b/ircd/src/dns.c
diff --git a/src/fdlist.c b/ircd/src/fdlist.c
diff --git a/src/hash.c b/ircd/src/hash.c
diff --git a/src/ircd.c b/ircd/src/ircd.c
diff --git a/src/ircd_vars.c b/ircd/src/ircd_vars.c
diff --git a/src/ircsprintf.c b/ircd/src/ircsprintf.c
diff --git a/src/json.c b/ircd/src/json.c
diff --git a/src/list.c b/ircd/src/list.c
diff --git a/src/log.c b/ircd/src/log.c
diff --git a/src/macosx/UnrealIRCd.xcodeproj/project.pbxproj b/ircd/src/macosx/UnrealIRCd.xcodeproj/project.pbxproj
diff --git a/src/macosx/UnrealIRCd.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ircd/src/macosx/UnrealIRCd.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/src/macosx/UnrealIRCd/AppDelegate.swift b/ircd/src/macosx/UnrealIRCd/AppDelegate.swift
diff --git a/src/macosx/UnrealIRCd/AppModel.swift b/ircd/src/macosx/UnrealIRCd/AppModel.swift
diff --git a/src/macosx/UnrealIRCd/Base.lproj/Main.storyboard b/ircd/src/macosx/UnrealIRCd/Base.lproj/Main.storyboard
diff --git a/src/macosx/UnrealIRCd/ChangeNotifier.swift b/ircd/src/macosx/UnrealIRCd/ChangeNotifier.swift
diff --git a/src/macosx/UnrealIRCd/ConfigurationModel.swift b/ircd/src/macosx/UnrealIRCd/ConfigurationModel.swift
diff --git a/src/macosx/UnrealIRCd/DaemonModel.swift b/ircd/src/macosx/UnrealIRCd/DaemonModel.swift
diff --git a/src/macosx/UnrealIRCd/Images.xcassets/AppIcon.appiconset/Contents.json b/ircd/src/macosx/UnrealIRCd/Images.xcassets/AppIcon.appiconset/Contents.json
diff --git a/src/macosx/UnrealIRCd/Info.plist b/ircd/src/macosx/UnrealIRCd/Info.plist
diff --git a/src/macosx/UnrealIRCd/ViewController.swift b/ircd/src/macosx/UnrealIRCd/ViewController.swift
diff --git a/src/macosx/UnrealIRCd/WindowController.swift b/ircd/src/macosx/UnrealIRCd/WindowController.swift
diff --git a/src/macosx/UnrealIRCd/logo.png b/ircd/src/macosx/UnrealIRCd/logo.png
Binary files differ.
diff --git a/src/macosx/UnrealIRCd/logo@2x.png b/ircd/src/macosx/UnrealIRCd/logo@2x.png
Binary files differ.
diff --git a/src/macosx/UnrealIRCdTests/Info.plist b/ircd/src/macosx/UnrealIRCdTests/Info.plist
diff --git a/src/macosx/UnrealIRCdTests/UnrealIRCdTests.swift b/ircd/src/macosx/UnrealIRCdTests/UnrealIRCdTests.swift
diff --git a/src/match.c b/ircd/src/match.c
diff --git a/src/mempool.c b/ircd/src/mempool.c
diff --git a/src/misc.c b/ircd/src/misc.c
diff --git a/src/modulemanager.c b/ircd/src/modulemanager.c
diff --git a/src/modules.c b/ircd/src/modules.c
diff --git a/src/modules/Makefile.in b/ircd/src/modules/Makefile.in
diff --git a/src/modules/account-notify.c b/ircd/src/modules/account-notify.c
diff --git a/src/modules/account-tag.c b/ircd/src/modules/account-tag.c
diff --git a/src/modules/addmotd.c b/ircd/src/modules/addmotd.c
diff --git a/src/modules/addomotd.c b/ircd/src/modules/addomotd.c
diff --git a/src/modules/admin.c b/ircd/src/modules/admin.c
diff --git a/src/modules/antimixedutf8.c b/ircd/src/modules/antimixedutf8.c
diff --git a/src/modules/antirandom.c b/ircd/src/modules/antirandom.c
diff --git a/src/modules/authprompt.c b/ircd/src/modules/authprompt.c
diff --git a/src/modules/away.c b/ircd/src/modules/away.c
diff --git a/src/modules/batch.c b/ircd/src/modules/batch.c
diff --git a/src/modules/blacklist.c b/ircd/src/modules/blacklist.c
diff --git a/src/modules/bot-tag.c b/ircd/src/modules/bot-tag.c
diff --git a/src/modules/botmotd.c b/ircd/src/modules/botmotd.c
diff --git a/src/modules/cap.c b/ircd/src/modules/cap.c
diff --git a/src/modules/certfp.c b/ircd/src/modules/certfp.c
diff --git a/src/modules/chanmodes/Makefile.in b/ircd/src/modules/chanmodes/Makefile.in
diff --git a/src/modules/chanmodes/censor.c b/ircd/src/modules/chanmodes/censor.c
diff --git a/src/modules/chanmodes/chanadmin.c b/ircd/src/modules/chanmodes/chanadmin.c
diff --git a/src/modules/chanmodes/chanop.c b/ircd/src/modules/chanmodes/chanop.c
diff --git a/src/modules/chanmodes/chanowner.c b/ircd/src/modules/chanmodes/chanowner.c
diff --git a/src/modules/chanmodes/delayjoin.c b/ircd/src/modules/chanmodes/delayjoin.c
diff --git a/src/modules/chanmodes/floodprot.c b/ircd/src/modules/chanmodes/floodprot.c
diff --git a/src/modules/chanmodes/halfop.c b/ircd/src/modules/chanmodes/halfop.c
diff --git a/src/modules/chanmodes/history.c b/ircd/src/modules/chanmodes/history.c
diff --git a/src/modules/chanmodes/inviteonly.c b/ircd/src/modules/chanmodes/inviteonly.c
diff --git a/src/modules/chanmodes/isregistered.c b/ircd/src/modules/chanmodes/isregistered.c
diff --git a/src/modules/chanmodes/issecure.c b/ircd/src/modules/chanmodes/issecure.c
diff --git a/src/modules/chanmodes/key.c b/ircd/src/modules/chanmodes/key.c
diff --git a/src/modules/chanmodes/limit.c b/ircd/src/modules/chanmodes/limit.c
diff --git a/src/modules/chanmodes/link.c b/ircd/src/modules/chanmodes/link.c
diff --git a/src/modules/chanmodes/moderated.c b/ircd/src/modules/chanmodes/moderated.c
diff --git a/src/modules/chanmodes/nocolor.c b/ircd/src/modules/chanmodes/nocolor.c
diff --git a/src/modules/chanmodes/noctcp.c b/ircd/src/modules/chanmodes/noctcp.c
diff --git a/src/modules/chanmodes/noexternalmsgs.c b/ircd/src/modules/chanmodes/noexternalmsgs.c
diff --git a/src/modules/chanmodes/noinvite.c b/ircd/src/modules/chanmodes/noinvite.c
diff --git a/src/modules/chanmodes/nokick.c b/ircd/src/modules/chanmodes/nokick.c
diff --git a/src/modules/chanmodes/noknock.c b/ircd/src/modules/chanmodes/noknock.c
diff --git a/src/modules/chanmodes/nonickchange.c b/ircd/src/modules/chanmodes/nonickchange.c
diff --git a/src/modules/chanmodes/nonotice.c b/ircd/src/modules/chanmodes/nonotice.c
diff --git a/src/modules/chanmodes/operonly.c b/ircd/src/modules/chanmodes/operonly.c
diff --git a/src/modules/chanmodes/permanent.c b/ircd/src/modules/chanmodes/permanent.c
diff --git a/src/modules/chanmodes/private.c b/ircd/src/modules/chanmodes/private.c
diff --git a/src/modules/chanmodes/regonly.c b/ircd/src/modules/chanmodes/regonly.c
diff --git a/ircd/src/modules/chanmodes/regonlyspeak.c b/ircd/src/modules/chanmodes/regonlyspeak.c
@@ -0,0 +1,108 @@
+/*
+ * Only registered users can speak UnrealIRCd Module (Channel Mode +M)
+ * (C) Copyright 2014 Travis McArthur (Heero) 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/regonlyspeak",
+	"4.2",
+	"Channel Mode +M",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_REGONLYSPEAK;
+static char errMsg[2048];
+
+#define IsRegOnlySpeak(channel)    (channel->mode.mode & EXTCMODE_REGONLYSPEAK)
+
+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()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	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);
+	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, regonlyspeak_part_message);
+
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+const char *regonlyspeak_part_message (Client *client, Channel *channel, const char *comment)
+{
+	if (!comment)
+		return NULL;
+
+	if (IsRegOnlySpeak(channel) && !IsLoggedIn(client) && !ValidatePermissionsForPath("channel:override:message:regonlyspeak",client,NULL,NULL,NULL))
+		return NULL;
+
+	return comment;
+}
+
+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) &&
+	    !check_channel_access_membership(lp, "vhoaq"))
+	{
+		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_ALLOW)
+				return HOOK_CONTINUE; /* bypass +M restriction */
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		*errmsg = "You must have a registered nick (+r) to talk on this channel";
+		return HOOK_DENY; /* BLOCK message */
+	}
+
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/chanmodes/secret.c b/ircd/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/ircd/src/modules/chanmodes/secureonly.c b/ircd/src/modules/chanmodes/secureonly.c
@@ -0,0 +1,190 @@
+/*
+ * Only allow secure users to join UnrealIRCd Module (Channel Mode +z)
+ * (C) Copyright 2014 Travis McArthur (Heero) 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/secureonly",
+	"4.2",
+	"Channel Mode +z",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_SECUREONLY;
+
+#define IsSecureOnly(channel)    (channel->mode.mode & EXTCMODE_SECUREONLY)
+
+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_pre_local_join(Client *client, Channel *channel, const char *key);
+
+MOD_TEST()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CmodeInfo req;
+
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	req.letter = 'z';
+	req.is_ok = extcmode_default_requirechop;
+	CmodeAdd(modinfo->handle, req, &EXTCMODE_SECUREONLY);
+
+	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);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SAJOIN, 0, secureonly_check_sajoin);
+
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/** Kicks all insecure users on a +z channel
+ * Returns 1 if the channel was destroyed as a result of this.
+ */
+static int secureonly_kick_insecure_users(Channel *channel)
+{
+	Member *member, *mb2;
+	Client *client;
+	int i = 0;
+	Hook *h;
+	char *comment = "Insecure user not allowed on secure channel (+z)";
+
+	if (!IsSecureOnly(channel))
+		return 0;
+
+	for (member = channel->members; member; member = mb2)
+	{
+		mb2 = member->next;
+		client = member->client;
+		if (MyUser(client) && !IsSecureConnect(client) && !IsULine(client))
+		{
+			char *prefix = NULL;
+			MessageTag *mtags = NULL;
+
+			if (invisible_user_in_channel(client, channel))
+			{
+				/* Send only to chanops */
+				prefix = "ho";
+			}
+
+			new_message(&me, NULL, &mtags);
+
+			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->name, 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->name, client->id, comment);
+
+			free_message_tags(mtags);
+
+			if (remove_user_from_channel(client, channel, 0) == 1)
+				return 1; /* channel was destroyed */
+		}
+	}
+	return 0;
+}
+
+int secureonly_check_join(Client *client, Channel *channel, const char *key, char **errmsg)
+{
+	Link *lp;
+
+	if (IsSecureOnly(channel) && !(client->umodes & UMODE_SECURE))
+	{
+		if (ValidatePermissionsForPath("channel:override:secureonly",client,NULL,channel,NULL))
+		{
+			/* if the channel is +z we still allow an ircop to bypass it
+			 * if they are invited.
+			 */
+			if (is_invited(client, channel))
+				return HOOK_CONTINUE;
+		}
+		*errmsg = STR_ERR_SECUREONLYCHAN;
+		return ERR_SECUREONLYCHAN;
+	}
+	return 0;
+}
+
+int secureonly_check_secure(Channel *channel)
+{
+	if (IsSecureOnly(channel))
+	{
+		return 1;
+	}
+
+	return 0;
+}
+
+int secureonly_channel_sync(Channel *channel, int merge, int removetheirs, int nomode)
+{
+	if (!merge && !removetheirs && !nomode)
+		return secureonly_kick_insecure_users(channel); /* may return 1, meaning channel is destroyed */
+	return 0;
+}
+
+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 TLS",
+			target->name, channel->name);
+		return HOOK_DENY;
+	}
+
+	return HOOK_CONTINUE;
+}
+
+/* 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_pre_local_join(Client *client, Channel *channel, const char *key)
+{
+	if ((channel->users == 0) && (MODES_ON_JOIN & EXTCMODE_SECUREONLY) && !IsSecure(client) && !IsOper(client))
+	{
+		sendnumeric(client, ERR_SECUREONLYCHAN, channel->name);
+		return HOOK_DENY;
+	}
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/chanmodes/stripcolor.c b/ircd/src/modules/chanmodes/stripcolor.c
@@ -0,0 +1,131 @@
+/*
+ * Strip Color UnrealIRCd Module (Channel Mode +S)
+ * (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"
+
+CMD_FUNC(stripcolor);
+
+ModuleHeader MOD_HEADER
+  = {
+	"chanmodes/stripcolor",
+	"4.2",
+	"Channel Mode +S",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+Cmode_t EXTCMODE_STRIPCOLOR;
+
+#define IsStripColor(channel)    (channel->mode.mode & EXTCMODE_STRIPCOLOR)
+
+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()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+CmodeInfo req;
+
+	/* Channel mode */
+	memset(&req, 0, sizeof(req));
+	req.paracount = 0;
+	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);
+	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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
+{
+	Hook *h;
+	int i;
+
+	if (MyUser(client) && IsStripColor(channel))
+	{
+		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_COLOR);
+			if (i == HOOK_ALLOW)
+				return HOOK_CONTINUE; /* bypass this restriction */
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		*msg = StripColors(*msg);
+	}
+
+	return HOOK_CONTINUE;
+}
+
+const char *stripcolor_prelocalpart(Client *client, Channel *channel, const char *comment)
+{
+	if (!comment)
+		return NULL;
+
+	if (MyUser(client) && IsStripColor(channel))
+		comment = StripColors(comment);
+
+	return comment;
+}
+
+/** Is any channel where the user is in +S? */
+static int IsAnyChannelStripColor(Client *client)
+{
+	Membership *lp;
+
+	for (lp = client->user->channel; lp; lp = lp->next)
+		if (IsStripColor(lp->channel))
+			return 1;
+	return 0;
+}
+
+
+const char *stripcolor_prelocalquit(Client *client, const char *comment)
+{
+	if (!comment)
+		return NULL;
+
+	if (MyUser(client) && !BadPtr(comment) && IsAnyChannelStripColor(client))
+		comment = StripColors(comment);
+        
+	return comment;
+}
+
diff --git a/ircd/src/modules/chanmodes/topiclimit.c b/ircd/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/ircd/src/modules/chanmodes/voice.c b/ircd/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/ircd/src/modules/channel-context.c b/ircd/src/modules/channel-context.c
@@ -0,0 +1,95 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/channel-context.c
+ *   (C) 2022 Valware & 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
+  = {
+	"channel-context",
+	"1.0",
+	"Channel Context (IRCv3)",
+	"UnrealIRCd team",
+	"unrealircd-6",
+	};
+
+int chancontext_mtag_is_ok(Client *client, const char *name, const char *value);
+void mtag_add_chancontext(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.is_ok = chancontext_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	mtag.name = "+draft/channel-context";
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_chancontext);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int chancontext_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (BadPtr(value))
+		return 0;
+
+	/* Validate a bit further, but only for local users.. */
+	if (MyUser(client))
+	{
+		Channel *channel = find_channel(value);
+		if (!channel)
+			return 0;
+		if (!IsMember(client, channel))
+			return 0;
+	}
+
+	return 1;
+}
+
+void mtag_add_chancontext(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m;
+
+	if (IsUser(client))
+	{
+		m = find_mtag(recv_mtags, "+draft/channel-context");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+	}
+}
diff --git a/ircd/src/modules/channeldb.c b/ircd/src/modules/channeldb.c
@@ -0,0 +1,567 @@
+/*
+ * Stores channel settings for +P channels in a .db file
+ * (C) Copyright 2019 Syzop, Gottem and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER = {
+	"channeldb",
+	"1.0",
+	"Stores and retrieves channel settings for persistent (+P) channels",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Database version */
+#define CHANNELDB_VERSION 100
+/* Save channels to file every <this> seconds */
+#define CHANNELDB_SAVE_EVERY 300
+/* The very first save after boot, apply this delta, this
+ * so we don't coincide with other (potentially) expensive
+ * I/O events like saving tkldb.
+ */
+#define CHANNELDB_SAVE_EVERY_DELTA -15
+
+#define MAGIC_CHANNEL_START	0x11111111
+#define MAGIC_CHANNEL_END	0x22222222
+
+// #undef BENCHMARK
+
+#define WARN_WRITE_ERROR(fname) \
+	do { \
+		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) \
+	do { \
+		if (!(x)) { \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+#define IsMDErr(x, y, z) \
+	do { \
+		if (!(x)) { \
+			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
+			return MOD_FAILED; \
+		} \
+	} while(0)
+
+/* Structs */
+struct cfgstruct {
+	char *database;
+	char *db_secret;
+};
+
+/* Forward declarations */
+void channeldb_moddata_free(ModData *md);
+void setcfg(struct cfgstruct *cfg);
+void freecfg(struct cfgstruct *cfg);
+int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int channeldb_config_posttest(int *errs);
+int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(write_channeldb_evt);
+int write_channeldb(void);
+int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel);
+int read_channeldb(void);
+
+/* Global variables */
+static uint32_t channeldb_version = CHANNELDB_VERSION;
+static struct cfgstruct cfg;
+static struct cfgstruct test;
+
+static long channeldb_next_event = 0;
+
+MOD_TEST()
+{
+	memset(&cfg, 0, sizeof(cfg));
+	memset(&test, 0, sizeof(test));
+	setcfg(&test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, channeldb_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, channeldb_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	/* We must unload early, when all channel modes and such are still in place: */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
+
+	LoadPersistentLong(modinfo, channeldb_next_event);
+
+	setcfg(&cfg);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (!channeldb_next_event)
+	{
+		/* If this is the first time that our module is loaded, then read the database. */
+		if (!read_channeldb())
+		{
+			char fname[512];
+			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
+			if (rename(cfg.database, fname) == 0)
+				config_warn("[channeldb] Existing database renamed to %s and starting a new one...", fname);
+			else
+				config_warn("[channeldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
+		}
+		channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY + CHANNELDB_SAVE_EVERY_DELTA;
+	}
+	EventAdd(modinfo->handle, "channeldb_write_channeldb", write_channeldb_evt, NULL, 1000, 0);
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (loop.terminating)
+		write_channeldb();
+	freecfg(&test);
+	freecfg(&cfg);
+	SavePersistentLong(modinfo, channeldb_next_event);
+	return MOD_SUCCESS;
+}
+
+void channeldb_moddata_free(ModData *md)
+{
+	if (md->i)
+		md->i = 0;
+}
+
+void setcfg(struct cfgstruct *cfg)
+{
+	// Default: data/channel.db
+	safe_strdup(cfg->database, "channel.db");
+	convert_to_absolute_path(&cfg->database, PERMDATADIR);
+}
+
+void freecfg(struct cfgstruct *cfg)
+{
+	safe_free(cfg->database);
+	safe_free(cfg->db_secret);
+}
+
+int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	// We are only interested in set::channeldb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "channeldb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: blank set::channeldb::%s without value", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		} else
+		if (!strcmp(cep->name, "database"))
+		{
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
+		} else
+		if (!strcmp(cep->name, "db-secret"))
+		{
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
+			{
+				config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
+				errors++;
+				continue;
+			}
+			safe_strdup(test.db_secret, cep->value);
+		} else
+		{
+			config_error("%s:%i: unknown directive set::channeldb::%s", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int channeldb_config_posttest(int *errs)
+{
+	int errors = 0;
+	char *errstr;
+
+	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+	{
+		config_error("[channeldb] %s", errstr);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	// We are only interested in set::channeldb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "channeldb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database"))
+			safe_strdup(cfg.database, cep->value);
+		else if (!strcmp(cep->name, "db-secret"))
+			safe_strdup(cfg.db_secret, cep->value);
+	}
+	return 1;
+}
+
+EVENT(write_channeldb_evt)
+{
+	if (channeldb_next_event > TStime())
+		return;
+	channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY;
+	write_channeldb();
+}
+
+int write_channeldb(void)
+{
+	char tmpfname[512];
+	UnrealDB *db;
+	Channel *channel;
+	int cnt = 0;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	// Write to a tempfile first, then rename it if everything succeeded
+	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+	if (!db)
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	W_SAFE(unrealdb_write_int32(db, channeldb_version));
+
+	/* First, count +P channels and write the count to the database */
+	for (channel = channels; channel; channel=channel->nextch)
+		if (has_channel_mode(channel, 'P'))
+			cnt++;
+	W_SAFE(unrealdb_write_int64(db, cnt));
+
+	for (channel = channels; channel; channel=channel->nextch)
+	{
+		/* We only care about +P (persistent) channels */
+		if (has_channel_mode(channel, 'P'))
+		{
+			if (!write_channel_entry(db, tmpfname, channel))
+				return 0;
+		}
+	}
+
+	// Everything seems to have gone well, attempt to close and rename the tempfile
+	if (!unrealdb_close(db))
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(cfg.database);
+#endif
+	if (rename(tmpfname, cfg.database) < 0)
+	{
+		config_error("[channeldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
+		return 0;
+	}
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	config_status("[channeldb] Benchmark: SAVE DB: %ld microseconds",
+		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
+#endif
+	return 1;
+}
+
+int write_listmode(UnrealDB *db, const char *tmpfname, Ban *lst)
+{
+	Ban *l;
+	int cnt = 0;
+
+	/* First count and write the list count */
+	for (l = lst; l; l = l->next)
+		cnt++;
+	W_SAFE(unrealdb_write_int32(db, cnt));
+
+	for (l = lst; l; l = l->next)
+	{
+		/* The entry, setby, seton */
+		W_SAFE(unrealdb_write_str(db, l->banstr));
+		W_SAFE(unrealdb_write_str(db, l->who));
+		W_SAFE(unrealdb_write_int64(db, l->when));
+	}
+	return 1;
+}
+
+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->name));
+	/* Channel creation time */
+	W_SAFE(unrealdb_write_int64(db, channel->creationtime));
+	/* Topic (topic, setby, seton) */
+	W_SAFE(unrealdb_write_str(db, channel->topic));
+	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, 1);
+	W_SAFE(unrealdb_write_str(db, modebuf));
+	W_SAFE(unrealdb_write_str(db, parabuf));
+	/* Mode lock */
+	W_SAFE(unrealdb_write_str(db, channel->mode_lock));
+	/* List modes (bans, exempts, invex) */
+	if (!write_listmode(db, tmpfname, channel->banlist))
+		return 0;
+	if (!write_listmode(db, tmpfname, channel->exlist))
+		return 0;
+	if (!write_listmode(db, tmpfname, channel->invexlist))
+		return 0;
+	W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_END));
+	return 1;
+}
+
+#define R_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+			if (e) \
+			{ \
+				safe_free(e->banstr); \
+				safe_free(e->who); \
+				safe_free(e); \
+			} \
+			return 0; \
+		} \
+	} while(0)
+
+int read_listmode(UnrealDB *db, Ban **lst)
+{
+	uint32_t total;
+	uint64_t when;
+	int i;
+	Ban *e = NULL;
+
+	R_SAFE(unrealdb_read_int32(db, &total));
+
+	for (i = 0; i < total; i++)
+	{
+		const char *str;
+		e = safe_alloc(sizeof(Ban));
+		R_SAFE(unrealdb_read_str(db, &e->banstr));
+		R_SAFE(unrealdb_read_str(db, &e->who));
+		R_SAFE(unrealdb_read_int64(db, &when));
+		str = clean_ban_mask(e->banstr, MODE_ADD, &me, 0);
+		if (str == NULL)
+		{
+			/* Skip this item */
+			config_warn("[channeldb] listmode skipped (no longer valid?): %s", e->banstr);
+			safe_free(e->banstr);
+			safe_free(e->who);
+			safe_free(e);
+			continue;
+		}
+		safe_strdup(e->banstr, str);
+
+		if (ban_exists(*lst, e->banstr))
+		{
+			/* Free again - duplicate item */
+			safe_free(e->banstr);
+			safe_free(e->who);
+			safe_free(e);
+		} else {
+			/* Add to list */
+			e->when = when;
+			e->next = *lst;
+			*lst = e;
+		}
+	}
+
+	return 1;
+}
+#undef R_SAFE
+
+#define FreeChannelEntry() \
+ 	do { \
+		/* Some of these might be NULL */ \
+		safe_free(chname); \
+		safe_free(topic); \
+		safe_free(topic_nick); \
+		safe_free(modes1); \
+		safe_free(modes2); \
+		safe_free(mode_lock); \
+	} while(0)
+
+#define R_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+			unrealdb_close(db); \
+			FreeChannelEntry(); \
+			return 0; \
+		} \
+	} while(0)
+
+int read_channeldb(void)
+{
+	UnrealDB *db;
+	uint32_t version;
+	int added = 0;
+	int i;
+	uint64_t count = 0;
+	uint32_t magic;
+	// Variables for the channels
+	// Some of them need to be declared and NULL initialised early due to the macro FreeChannelEntry() being used by R_SAFE() on error
+	char *chname = NULL;
+	uint64_t creationtime = 0;
+	char *topic = NULL;
+	char *topic_nick = NULL;
+	uint64_t topic_time = 0;
+	char *modes1 = NULL;
+	char *modes2 = NULL;
+	char *mode_lock = NULL;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+		{
+			/* Database does not exist. Could be first boot */
+			config_warn("[channeldb] No database present at '%s', will start a new one", cfg.database);
+			return 1;
+		} else
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+		{
+			/* Re-open as unencrypted */
+			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+			if (!db)
+			{
+				/* This should actually never happen, unless some weird I/O error */
+				config_warn("[channeldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
+				return 0;
+			}
+		} else
+		{
+			config_warn("[channeldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
+			return 0;
+		}
+	}
+
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version > channeldb_version)
+	{
+		config_warn("[channeldb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, channeldb_version, version);
+		unrealdb_close(db);
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_int64(db, &count));
+
+	for (i=1; i <= count; i++)
+	{
+		// Variables
+		chname = NULL;
+		creationtime = 0;
+		topic = NULL;
+		topic_nick = NULL;
+		topic_time = 0;
+		modes1 = NULL;
+		modes2 = NULL;
+		mode_lock = NULL;
+		
+		Channel *channel;
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic != MAGIC_CHANNEL_START)
+		{
+			config_error("[channeldb] Corrupt database (%s) - channel magic start is 0x%x. Further reading aborted.", cfg.database, magic);
+			break;
+		}
+		R_SAFE(unrealdb_read_str(db, &chname));
+		R_SAFE(unrealdb_read_int64(db, &creationtime));
+		R_SAFE(unrealdb_read_str(db, &topic));
+		R_SAFE(unrealdb_read_str(db, &topic_nick));
+		R_SAFE(unrealdb_read_int64(db, &topic_time));
+		R_SAFE(unrealdb_read_str(db, &modes1));
+		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 = 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;
+		safe_strdup(channel->mode_lock, mode_lock);
+		set_channel_mode(channel, NULL, modes1, modes2);
+		R_SAFE(read_listmode(db, &channel->banlist));
+		R_SAFE(read_listmode(db, &channel->exlist));
+		R_SAFE(read_listmode(db, &channel->invexlist));
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		FreeChannelEntry();
+		added++;
+		if (magic != MAGIC_CHANNEL_END)
+		{
+			config_error("[channeldb] Corrupt database (%s) - channel magic end is 0x%x. Further reading aborted.", cfg.database, magic);
+			break;
+		}
+	}
+
+	unrealdb_close(db);
+
+	if (added)
+		config_status("[channeldb] Added %d persistent channels (+P)", added);
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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
diff --git a/ircd/src/modules/charsys.c b/ircd/src/modules/charsys.c
@@ -0,0 +1,1295 @@
+/*
+ * Unreal Internet Relay Chat Daemon, src/charsys.c
+ * (C) Copyright 2005-2017 Bram Matthys and The UnrealIRCd Team.
+ *
+ * Character system: This subsystem deals with finding out wheter a
+ * character should be allowed or not in nicks (nicks only for now).
+ *
+ * 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"
+
+#ifndef ARRAY_SIZEOF
+ #define ARRAY_SIZEOF(x) (sizeof((x))/sizeof((x)[0]))
+#endif
+
+ModuleHeader MOD_HEADER
+= {
+	"charsys",	/* Name of module */
+	"5.0", /* Version */
+	"Character System (set::allowed-nickchars)", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* NOTE: it is guaranteed that char is unsigned by compiling options
+ *       (-funsigned-char @ gcc, /J @ MSVC)
+ * NOTE2: Original credit for supplying the correct chinese
+ *        coderanges goes to: RexHsu, Mr.WebBar and Xuefer
+ */
+
+/** Our multibyte structure */
+typedef struct MBList MBList;
+struct MBList
+{
+	MBList *next;
+	char s1, e1, s2, e2;
+};
+MBList *mblist = NULL, *mblist_tail = NULL;
+
+/* Use this to prevent mixing of certain combinations
+ * (such as GBK & high-ascii, etc)
+ */
+static int langav = 0;
+char langsinuse[4096];
+
+/* bitmasks: */
+#define LANGAV_ASCII			0x000001 /* 8 bit ascii */
+#define LANGAV_LATIN1			0x000002 /* latin1 (western europe) */
+#define LANGAV_LATIN2			0x000004 /* latin2 (eastern europe, eg: polish) */
+#define LANGAV_ISO8859_7		0x000008 /* greek */
+#define LANGAV_ISO8859_8I		0x000010 /* hebrew */
+#define LANGAV_ISO8859_9		0x000020 /* turkish */
+#define LANGAV_W1250			0x000040 /* windows-1250 (eg: polish-w1250) */
+#define LANGAV_W1251			0x000080 /* windows-1251 (eg: russian) */
+#define LANGAV_LATIN2W1250		0x000100 /* Compatible with both latin2 AND windows-1250 (eg: hungarian) */
+#define LANGAV_ISO8859_6		0x000200 /* arabic */
+#define LANGAV_GBK			0x001000 /* (Chinese) GBK encoding */
+#define LANGAV_UTF8			0x002000 /* any UTF8 encoding */
+#define LANGAV_LATIN_UTF8		0x004000 /* UTF8: latin script */
+#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
+{
+	char *directive;
+	char *code;
+	int setflags;
+};
+
+/* MUST be alphabetized (first column) */
+static LangList langlist[] = {
+	{ "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 },
+	{ "catalan-utf8", "cat-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "chinese",      "chi-j,chi-s,chi-t", LANGAV_GBK },
+	{ "chinese-ja",   "chi-j", LANGAV_GBK },
+	{ "chinese-simp", "chi-s", LANGAV_GBK },
+	{ "chinese-trad", "chi-t", LANGAV_GBK },
+	{ "cyrillic-utf8", "blr-utf8,rus-utf8,ukr-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
+	{ "czech",        "cze-m", LANGAV_ASCII|LANGAV_W1250 },
+	{ "czech-utf8",   "cze-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "danish",       "dan", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "danish-utf8",  "dan-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "dutch",        "dut", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "dutch-utf8",   "dut-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "estonian-utf8","est-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "french",       "fre", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "french-utf8",  "fre-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "gbk",          "chi-s,chi-t,chi-j", LANGAV_GBK },
+	{ "german",       "ger", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "german-utf8",  "ger-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "greek",        "gre", LANGAV_ASCII|LANGAV_ISO8859_7 },
+	{ "greek-utf8",   "gre-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_GREEK_UTF8 },
+	{ "hebrew",       "heb", LANGAV_ASCII|LANGAV_ISO8859_8I },
+	{ "hebrew-utf8",  "heb-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_HEBREW_UTF8 },
+	{ "hungarian",    "hun", LANGAV_ASCII|LANGAV_LATIN2W1250 },
+	{ "hungarian-utf8","hun-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "icelandic",    "ice", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "icelandic-utf8","ice-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "italian",      "ita", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "italian-utf8", "ita-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "latin-utf8",   "cat-utf8,cze-utf8,dan-utf8,dut-utf8,fre-utf8,ger-utf8,hun-utf8,ice-utf8,ita-utf8,pol-utf8,rum-utf8,slo-utf8,spa-utf8,swe-utf8,tur-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "latin1",       "cat,dut,fre,ger,ita,spa,swe", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "latin2",       "hun,pol,rum", LANGAV_ASCII|LANGAV_LATIN2 },
+	{ "latvian-utf8", "lav-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "lithuanian-utf8","lit-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "polish",       "pol", LANGAV_ASCII|LANGAV_LATIN2 },
+	{ "polish-utf8",  "pol-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "polish-w1250", "pol-m", LANGAV_ASCII|LANGAV_W1250 },
+	{ "romanian",     "rum", LANGAV_ASCII|LANGAV_LATIN2W1250 },
+	{ "romanian-utf8","rum-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "russian-utf8", "rus-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
+	{ "russian-w1251","rus", LANGAV_ASCII|LANGAV_W1251 },
+	{ "slovak",       "slo-m", LANGAV_ASCII|LANGAV_W1250 },
+	{ "slovak-utf8",  "slo-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "spanish",      "spa", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "spanish-utf8", "spa-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "swedish",      "swe", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "swedish-utf8", "swe-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "swiss-german", "swg", LANGAV_ASCII|LANGAV_LATIN1 },
+	{ "swiss-german-utf8", "swg-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "turkish",      "tur", LANGAV_ASCII|LANGAV_ISO8859_9 },
+	{ "turkish-utf8", "tur-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
+	{ "ukrainian-utf8", "ukr-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
+	{ "ukrainian-w1251", "ukr", LANGAV_ASCII|LANGAV_W1251 },
+	{ "windows-1250", "cze-m,pol-m,rum,slo-m,hun",  LANGAV_ASCII|LANGAV_W1250 },
+	{ "windows-1251", "rus,ukr,blr", LANGAV_ASCII|LANGAV_W1251 },
+	{ NULL, NULL, 0 }
+};
+
+/* For temporary use during config_run */
+typedef struct ILangList ILangList;
+struct ILangList
+{
+	ILangList *prev, *next;
+	char *name;
+};
+ILangList *ilanglist = NULL;
+
+/* These characters are ALWAYS disallowed... from remote, in
+ * multibyte, etc.. even though this might mean a certain
+ * (legit) character cannot be used (eg: in chinese GBK).
+ * - ! (nick!user seperator)
+ * - prefix chars: +, %, @, &, ~
+ * - channel chars: #
+ * - scary chars: $, :, ', ", ?, *, ',', '.'
+ * NOTE: the caller should also check for ascii <= 32.
+ * [CHANGING THIS WILL CAUSE SECURITY/SYNCH PROBLEMS AND WILL
+ *  VIOLATE YOUR ""RIGHT"" ON SUPPORT IMMEDIATELY]
+ */
+const char *illegalnickchars = "!+%@&~#$:'\"?*,.";
+
+/* Forward declarations */
+int _do_nick_name(char *nick);
+int _do_remote_nick_name(char *nick);
+static int do_nick_name_multibyte(char *nick);
+static int do_nick_name_standard(char *nick);
+void charsys_reset(void);
+void charsys_reset_pretest(void);
+void charsys_finish(void);
+void charsys_addmultibyterange(char s1, char e1, char s2, char e2);
+void charsys_addallowed(char *s);
+int charsys_test_language(char *name);
+void charsys_add_language(char *name);
+static void charsys_doadd_language(char *name);
+int charsys_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int charsys_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+int charsys_config_posttest(int *errs);
+char *_charsys_get_current_languages(void);
+
+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);
+	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);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, charsys_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, charsys_config_run);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	charsys_finish();
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int charsys_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::allowed-nickchars... */
+	if (!ce || !ce->name || strcmp(ce->name, "allowed-nickchars"))
+		return 0;
+
+	if (ce->value)
+	{
+		config_error("%s:%i: set::allowed-nickchars: please use 'allowed-nickchars { name; };' "
+					 "and not 'allowed-nickchars name;'",
+					 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->items; cep; cep=cep->next)
+	{
+		if (!charsys_test_language(cep->name))
+		{
+			config_error("%s:%i: set::allowed-nickchars: Unknown (sub)language '%s'",
+				ce->file->filename, ce->line_number, cep->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int charsys_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	/* We are only interrested in set::allowed-nickchars... */
+	if (!ce || !ce->name || strcmp(ce->name, "allowed-nickchars"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+		charsys_add_language(cep->name);
+
+	return 1;
+}
+
+/** Check if the specified charsets during the TESTING phase can be
+ * premitted without getting into problems.
+ * RETURNS: -1 in case of failure, 1 if ok
+ */
+int charsys_config_posttest(int *errs)
+{
+	int errors = 0;
+	int x=0;
+
+	if ((langav & LANGAV_ASCII) && (langav & LANGAV_GBK))
+	{
+		config_error("ERROR: set::allowed-nickchars specifies incorrect combination "
+		             "of languages: high-ascii languages (such as german, french, etc) "
+		             "cannot be mixed with chinese/..");
+		return -1;
+	}
+	if (langav & LANGAV_LATIN_UTF8)
+		x++;
+	if (langav & LANGAV_GREEK_UTF8)
+		x++;
+	if (langav & LANGAV_CYRILLIC_UTF8)
+		x++;
+	if (langav & LANGAV_HEBREW_UTF8)
+		x++;
+	if (langav & LANGAV_LATIN1)
+		x++;
+	if (langav & LANGAV_LATIN2)
+		x++;
+	if (langav & LANGAV_ISO8859_6)
+		x++;
+	if (langav & LANGAV_ISO8859_7)
+		x++;
+	if (langav & LANGAV_ISO8859_9)
+		x++;
+	if (langav & LANGAV_W1250)
+		x++;
+	if (langav & LANGAV_W1251)
+		x++;
+	if ((langav & LANGAV_LATIN2W1250) && !(langav & LANGAV_LATIN2) && !(langav & LANGAV_W1250))
+	    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");
+			errors++;
+		}
+		if (langav & LANGAV_GREEK_UTF8)
+		{
+			config_error("ERROR: set::allowed-nickchars: you cannot combine 'greek-utf8' with any other character set");
+			errors++;
+		}
+		if (langav & LANGAV_CYRILLIC_UTF8)
+		{
+			config_error("ERROR: set::allowed-nickchars: you cannot combine 'cyrillic-utf8' with any other character set");
+			errors++;
+		}
+		if (langav & LANGAV_HEBREW_UTF8)
+		{
+			config_error("ERROR: set::allowed-nickchars: you cannot combine 'hebrew-utf8' with any other character set");
+			errors++;
+		}
+		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;
+	return errors ? -1 : 1;
+}
+
+/** Called on boot and just before config run */
+void charsys_reset(void)
+{
+	int i;
+	MBList *m, *m_next;
+
+	/* First, reset everything */
+	for (i=0; i < 256; i++)
+		char_atribs[i] &= ~ALLOWN;
+	for (m=mblist; m; m=m_next)
+	{
+		m_next = m->next;
+		safe_free(m);
+	}
+	mblist=mblist_tail=NULL;
+	/* Then add the default which will always be allowed */
+	charsys_addallowed("0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyzy{|}");
+	langav = 0;
+	langsinuse[0] = '\0';
+#ifdef DEBUGMODE
+	if (ilanglist)
+		abort();
+#endif
+}
+
+void charsys_reset_pretest(void)
+{
+	langav = 0;
+	non_utf8_nick_chars_in_use = 0;
+}
+
+static inline void ilang_swap(ILangList *one, ILangList *two)
+{
+	char *tmp = one->name;
+	one->name = two->name;
+	two->name = tmp;
+}
+
+static void ilang_sort(void)
+{
+	ILangList *outer, *inner;
+
+	/* Selection sort -- perhaps optimize to qsort/whatever if
+     * possible? ;)
+     */
+	for (outer=ilanglist; outer; outer=outer->next)
+	{
+		for (inner=outer->next; inner; inner=inner->next)
+		{
+			if (strcmp(outer->name, inner->name) > 0)
+				ilang_swap(outer, inner);
+		}
+	}
+}
+
+void charsys_finish(void)
+{
+	ILangList *e, *e_next;
+
+	/* Sort alphabetically */
+	ilang_sort();
+
+	/* [note: this can be optimized] */
+	langsinuse[0] = '\0';
+	for (e=ilanglist; e; e=e->next)
+	{
+		strlcat(langsinuse, e->name, sizeof(langsinuse));
+		if (e->next)
+			strlcat(langsinuse, ",", sizeof(langsinuse));
+	}
+
+	/* Free everything */
+	for (e=ilanglist; e; e=e_next)
+	{
+		e_next=e->next;
+		safe_free(e->name);
+		safe_free(e);
+	}
+	ilanglist = NULL;
+#ifdef DEBUGMODE
+	if (strlen(langsinuse) > 490)
+		abort();
+#endif
+	charsys_check_for_changes();
+}
+
+/** Add a character range to the multibyte list.
+ * Eg: charsys_addmultibyterange(0xaa, 0xbb, 0x00, 0xff) for 0xaa00-0xbbff.
+ * @param s1 Start of highest byte
+ * @param e1 End of highest byte
+ * @param s2 Start of lowest byte
+ * @param e2 End of lowest byte
+ */
+void charsys_addmultibyterange(char s1, char e1, char s2, char e2)
+{
+MBList *m = safe_alloc(sizeof(MBList));
+
+	m->s1 = s1;
+	m->e1 = e1;
+	m->s2 = s2;
+	m->e2 = e2;
+
+	if (mblist_tail)
+		mblist_tail->next = m;
+	else
+		mblist = m;
+	mblist_tail = m;
+}
+
+/** Adds all characters in the specified string to the allowed list. */
+void charsys_addallowed(char *s)
+{
+	for (; *s; s++)
+	{
+		if ((*s <= 32) || strchr(illegalnickchars, *s))
+		{
+			config_error("INTERNAL ERROR: charsys_addallowed() called for illegal characters: %s", s);
+#ifdef DEBUGMODE
+			abort();
+#endif
+		}
+		char_atribs[(unsigned char)*s] |= ALLOWN;
+	}
+}
+
+void charsys_addallowed_range(unsigned char from, unsigned char to)
+{
+	unsigned char i;
+
+	for (i = from; i != to; i++)
+		char_atribs[i] |= ALLOWN;
+}
+
+int _do_nick_name(char *nick)
+{
+	if (mblist)
+		return do_nick_name_multibyte(nick);
+	else
+		return do_nick_name_standard(nick);
+}
+
+static int do_nick_name_standard(char *nick)
+{
+	int len;
+	char *ch;
+
+	if ((*nick == '-') || isdigit(*nick))
+		return 0;
+
+	for (ch=nick,len=0; *ch && len <= NICKLEN; ch++, len++)
+		if (!isvalid(*ch))
+			return 0; /* reject the full nick */
+	*ch = '\0';
+	return len;
+}
+
+static int isvalidmbyte(unsigned char c1, unsigned char c2)
+{
+	MBList *m;
+
+	for (m=mblist; m; m=m->next)
+	{
+		if ((c1 >= m->s1) && (c1 <= m->e1) &&
+		    (c2 >= m->s2) && (c2 <= m->e2))
+		    return 1;
+	}
+	return 0;
+}
+
+/* hmmm.. there must be some problems with multibyte &
+ * other high ascii characters I think (such as german etc).
+ * Not sure if this can be solved? I don't think so... -- Syzop.
+ */
+static int do_nick_name_multibyte(char *nick)
+{
+	int len;
+	char *ch;
+	int firstmbchar = 0;
+
+	if ((*nick == '-') || isdigit(*nick))
+		return 0;
+
+	for (ch=nick,len=0; *ch && len <= NICKLEN; ch++, len++)
+	{
+		/* Some characters are ALWAYS illegal, so they have to be disallowed here */
+		if ((*ch <= 32) || strchr(illegalnickchars, *ch))
+			return 0;
+		if (firstmbchar)
+		{
+			if (!isvalidmbyte(ch[-1], *ch))
+				return 0;
+			firstmbchar = 0;
+		} else if ((*ch) & 0x80)
+			firstmbchar = 1;
+		else if (!isvalid(*ch))
+			return 0;
+	}
+	if (firstmbchar)
+	{
+		ch--;
+		len--;
+	}
+	*ch = '\0';
+	return len;
+}
+
+/** Does some very basic checking on remote nickname.
+ * It's only purpose is not to cause the whole network
+ * to fall down in pieces, that's all. Display problems
+ * are not really handled here. They are assumed to have been
+ * checked by PROTOCTL NICKCHARS= -- Syzop.
+ */
+int _do_remote_nick_name(char *nick)
+{
+	char *c;
+
+	/* Don't allow nicks to start with a digit, ever. */
+	if ((*nick == '-') || isdigit(*nick))
+		return 0;
+
+	/* Now the other, more relaxed checks.. */
+	for (c=nick; *c; c++)
+		if ((*c <= 32) || strchr(illegalnickchars, *c))
+			return 0;
+
+	return (c - nick);
+}
+
+static LangList *charsys_find_language(char *name)
+{
+	int start = 0;
+	int stop = ARRAY_SIZEOF(langlist)-1;
+	int mid;
+
+	while (start <= stop)
+	{
+		mid = (start+stop)/2;
+		if (!langlist[mid].directive || smycmp(name, langlist[mid].directive) < 0)
+			stop = mid-1;
+		else if (strcmp(name, langlist[mid].directive) == 0)
+			return &langlist[mid];
+		else
+			start = mid+1;
+	}
+	return NULL;
+}
+
+static LangList *charsys_find_language_code(char *code)
+{
+	int i;
+	for (i = 0; langlist[i].code; i++)
+		if (!strcasecmp(langlist[i].code, code))
+			return &langlist[i];
+	return NULL;
+}
+
+/** Check if language is available. */
+int charsys_test_language(char *name)
+{
+	LangList *l = charsys_find_language(name);
+
+	if (l)
+	{
+		langav |= l->setflags;
+		if (!(l->setflags & LANGAV_UTF8))
+			non_utf8_nick_chars_in_use = 1;
+		return 1;
+	}
+	if (!strcmp(name, "euro-west"))
+	{
+		config_error("set::allowed-nickchars: ERROR: 'euro-west' got renamed to 'latin1'");
+		return 0;
+	}
+	return 0;
+}
+
+static void charsys_doadd_language(char *name)
+{
+LangList *l;
+ILangList *li;
+int found;
+char tmp[512], *lang, *p;
+
+	l = charsys_find_language(name);
+	if (!l)
+	{
+#ifdef DEBUGMODE
+		abort();
+#endif
+		return;
+	}
+
+	strlcpy(tmp, l->code, sizeof(tmp));
+	for (lang = strtoken(&p, tmp, ","); lang; lang = strtoken(&p, NULL, ","))
+	{
+		/* Check if present... */
+		found=0;
+		for (li=ilanglist; li; li=li->next)
+			if (!strcmp(li->name, lang))
+			{
+				found = 1;
+				break;
+			}
+		if (!found)
+		{
+			/* Add... */
+			li = safe_alloc(sizeof(ILangList));
+			safe_strdup(li->name, lang);
+			AddListItem(li, ilanglist);
+		}
+	}
+}
+
+void charsys_add_language(char *name)
+{
+	char latin1=0, latin2=0, w1250=0, w1251=0, chinese=0;
+	char latin_utf8=0, cyrillic_utf8=0;
+
+	/** Note: there could well be some characters missing in the lists below.
+	 *        While I've seen other altnernatives that just allow pretty much
+	 *        every accent that exists even for dutch (where we rarely use
+	 *        accents except for like 3 types), I rather prefer to use a bit more
+	 *        reasonable aproach ;). That said, anyone is welcome to make
+	 *        suggestions about characters that should be added (or removed)
+	 *        of course. -- Syzop
+	 */
+
+	/* Add our language to our list */
+	charsys_doadd_language(name);
+
+	/* GROUPS */
+	if (!strcmp(name, "latin-utf8"))
+		latin_utf8 = 1;
+	else if (!strcmp(name, "cyrillic-utf8"))
+		cyrillic_utf8 = 1;
+	else if (!strcmp(name, "latin1"))
+		latin1 = 1;
+	else if (!strcmp(name, "latin2"))
+		latin2 = 1;
+	else if (!strcmp(name, "windows-1250"))
+		w1250 = 1;
+	else if (!strcmp(name, "windows-1251"))
+		w1251 = 1;
+	else if (!strcmp(name, "chinese") || !strcmp(name, "gbk"))
+		chinese = 1;
+
+	/* INDIVIDUAL CHARSETS */
+
+	/* [LATIN1] and [LATIN-UTF8] */
+	if (latin1 || !strcmp(name, "german"))
+	{
+		/* a", A", o", O", u", U" and es-zett */
+		charsys_addallowed("äÄöÖüÜß");
+	}
+	if (latin_utf8 || !strcmp(name, "german-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9f, 0x9f);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+	}
+	if (latin1 || !strcmp(name, "swiss-german"))
+	{
+		/* a", A", o", O", u", U"  */
+		charsys_addallowed("äÄöÖüÜ");
+	}
+	if (latin_utf8 || !strcmp(name, "swiss-german-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+	}
+	if (latin1 || !strcmp(name, "dutch"))
+	{
+		/* Ok, even though I'm Dutch myself, I've trouble getting
+		 * a proper list of this ;). I think I got them all now, but
+		 * I did not include "borrow-words" like words we use in Dutch
+		 * that are literal French. So if you really want to use them all,
+		 * I suggest you to use just latin1 :P.
+		 */
+		/* e', e", o", i", u", e`. */
+		charsys_addallowed("éëöïüè");
+	}
+	if (latin_utf8 || !strcmp(name, "dutch-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa8, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xab, 0xab);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xaf, 0xaf);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+	}
+	if (latin1 || !strcmp(name, "danish"))
+	{
+		/* supplied by klaus:
+		 * <ae>, <AE>, ao, Ao, o/, O/ */
+		charsys_addallowed("æÆåÅøØ");
+	}
+	if (latin_utf8 || !strcmp(name, "danish-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x85, 0x86);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x98, 0x98);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa5, 0xa6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb8, 0xb8);
+	}
+	if (latin1 || !strcmp(name, "french"))
+	{
+		/* A`, A^, a`, a^, weird-C, weird-c, E`, E', E^, E", e`, e', e^, e",
+		 * I^, I", i^, i", O^, o^, U`, U^, U", u`, u", u`, y" [not in that order, sry]
+		 * Hmm.. there might be more, but I'm not sure how common they are
+		 * and I don't think they are always displayed correctly (?).
+		 */
+		charsys_addallowed("ÀÂàâÇçÈÉÊËèéêëÎÏîïÔôÙÛÜùûüÿ");
+	}
+	if (latin_utf8 || !strcmp(name, "french-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x80, 0x80);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x82, 0x82);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x87, 0x8b);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8e, 0x8f);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x94, 0x94);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x99, 0x99);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9b, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa0, 0xa0);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa2, 0xa2);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa7, 0xab);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xae, 0xaf);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb4, 0xb4);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb9, 0xb9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbb, 0xbc);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbf, 0xbf);
+	}
+	if (latin1 || !strcmp(name, "spanish"))
+	{
+		/* a', A', e', E', i', I', o', O', u', U', u", U", n~, N~ */
+		charsys_addallowed("áÁéÉíÍóÓúÚüÜñÑ");
+	}
+	if (latin_utf8 || !strcmp(name, "spanish-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x91, 0x91);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb1, 0xb1);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+	}
+	if (latin1 || !strcmp(name, "italian"))
+	{
+		/* A`, E`, E', I`, I', O`, O', U`, U', a`, e`, e', i`, i', o`, o', u`, u' */
+		charsys_addallowed("ÀÈÉÌÍÒÓÙÚàèéìíòóùú");
+	}
+	if (latin_utf8 || !strcmp(name, "italian-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x80, 0x80);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x88, 0x89);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8c, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x92, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x99, 0x9a);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa0, 0xa0);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa8, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xac, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb2, 0xb3);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb9, 0xba);
+	}
+	if (latin1 || !strcmp(name, "catalan"))
+	{
+		/* supplied by Trocotronic */
+		/* a`, A`, e`, weird-c, weird-C, E`, e', E', i', I', o`, O`, o', O', u', U', i", I", u", U", weird-dot */
+		charsys_addallowed("àÀçÇèÈéÉíÍòÒóÓúÚïÏüÜ");
+	}
+	if (latin_utf8 || !strcmp(name, "catalan-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x80, 0x80);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x87, 0x89);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8f, 0x8f);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x92, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa0, 0xa0);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa7, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xaf, 0xaf);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb2, 0xb3);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
+	}
+	if (latin1 || !strcmp(name, "swedish"))
+	{
+		/* supplied by Tank */
+		/* ao, Ao, a", A", o", O" */
+		charsys_addallowed("åÅäÄöÖ");
+	}
+	if (latin_utf8 || !strcmp(name, "swedish-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x85);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa5);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+	}
+	if (latin1 || !strcmp(name, "icelandic"))
+	{
+		/* supplied by Saevar */
+		charsys_addallowed("ÆæÖöÁáÍíÐðÚúÓóÝýÞþ");
+	}
+	if (latin_utf8 || !strcmp(name, "icelandic-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x86, 0x86);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x90, 0x90);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9d, 0x9e);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa6, 0xa6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb0, 0xb0);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbe);
+	}
+
+	/* [LATIN2] and rest of [LATIN-UTF8] */
+	/* actually hungarian is a special case, include it in both w1250 and latin2 ;p */
+	if (latin2 || w1250 || !strcmp(name, "hungarian"))
+	{
+		/* supplied by AngryWolf */
+		/* a', e', i', o', o", o~, u', u", u~, A', E', I', O', O", O~, U', U", U~ */
+		charsys_addallowed("áéíóöõúüûÁÉÍÓÖÕÚÜÛ");
+	}
+	if (latin_utf8 || !strcmp(name, "hungarian-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x90, 0x91);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xb0, 0xb1);
+	}
+	/* same is true for romanian: latin2 & w1250 compatible */
+	if (latin2 || w1250 || !strcmp(name, "romanian"))
+	{
+		/* With some help from crazytoon */
+		/* 'S,' 's,' 'A^' 'A<' 'I^' 'T,' 'a^' 'a<' 'i^' 't,' */
+		charsys_addallowed("ªºÂÃÎÞâãîþ");
+	}
+	if (latin_utf8 || !strcmp(name, "romanian-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x82, 0x82);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8e, 0x8e);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa2, 0xa2);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xae, 0xae);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x82, 0x83);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x9e, 0x9f);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa2, 0xa3);
+	}
+
+	if (latin2 || !strcmp(name, "polish"))
+	{
+		/* supplied by k4be */
+		charsys_addallowed("±æê³ñó¶¿¼¡ÆÊ£ÑÓ¦¯¬");
+	}
+	if (latin_utf8 || !strcmp(name, "polish-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x84, 0x87);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x98, 0x99);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x81, 0x84);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x9a, 0x9b);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xb9, 0xbc);
+	}
+	/* [windows 1250] */
+	if (w1250 || !strcmp(name, "polish-w1250"))
+	{
+		/* supplied by k4be */
+		charsys_addallowed("¹æê³ñ󜿟¥ÆÊ£ÑÓŒ¯");
+	}
+	if (w1250 || !strcmp(name, "czech-w1250"))
+	{
+		/* Syzop [probably incomplete] */
+		charsys_addallowed("ŠŽšžÁÈÉÌÍÏÒÓØÙÚÝáèéìíïòóøùúý");
+	}
+	if (latin_utf8 || !strcmp(name, "czech-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9d, 0x9d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbd);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8f);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x9a, 0x9b);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x87, 0x88);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x98, 0x99);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa4, 0xa5);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xae, 0xaf);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
+	}
+	if (w1250 || !strcmp(name, "slovak-w1250"))
+	{
+		/* Syzop [probably incomplete] */
+		charsys_addallowed("ŠŽšž¼¾ÀÁÄÅÈÉÍÏàáäåèéíïòóôúý");
+	}
+	if (latin_utf8 || !strcmp(name, "slovak-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb4);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbd);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8f);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xb9, 0xba);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xbd, 0xbe);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x88, 0x88);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x94, 0x95);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa4, 0xa5);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
+	}
+
+	/* [windows 1251] */
+	if (w1251 || !strcmp(name, "russian-w1251"))
+	{
+		/* supplied by Roman Parkin:
+		 * 128-159 and 223-254
+		 */
+		charsys_addallowed("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ¨¸");
+	}
+	if (cyrillic_utf8 || !strcmp(name, "russian-utf8"))
+	{
+		charsys_addmultibyterange(0xd0, 0xd0, 0x81, 0x81);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x90, 0xbf);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x8f);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x91, 0x91);
+	}
+
+	if (w1251 || !strcmp(name, "belarussian-w1251"))
+	{
+		/* supplied by Bock (Samets Anton) & ss:
+		 * 128-159, 161, 162, 178, 179 and 223-254
+		 * Corrected 01.11.2006 to more "correct" behavior by Bock
+		 */
+		charsys_addallowed("ÀÁÂÃÄŨÆÇ²ÉÊËÌÍÎÏÐÑÒÓ¡ÔÕÖרÛÜÝÞßàáâãä叿ç³éêëìíîïðñòó¢ôõö÷øûüýþÿ");
+	}
+	if (cyrillic_utf8 || !strcmp(name, "belarussian-utf8"))
+	{
+		charsys_addmultibyterange(0xd0, 0xd0, 0x81, 0x81);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x86, 0x86);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x8e, 0x8e);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x90, 0x97);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x99, 0xa8);
+		charsys_addmultibyterange(0xd0, 0xd0, 0xab, 0xb7);
+		charsys_addmultibyterange(0xd0, 0xd0, 0xb9, 0xbf);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x88);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x8b, 0x8f);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x91, 0x91);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x96, 0x96);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x9e, 0x9e);
+	}
+
+	if (w1251 || !strcmp(name, "ukrainian-w1251"))
+	{
+		/* supplied by Anton Samets & ss:
+		 * 128-159, 170, 175, 178, 179, 186, 191 and 223-254
+		 * Corrected 01.11.2006 to more "correct" behavior by core
+		 */
+		charsys_addallowed("ÀÁÂÃ¥ÄŪÆÇȲ¯ÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÜÞßàáâã´äåºæç賿éêëìíîïðñòóôõö÷øùüþÿ");
+	}
+	if (cyrillic_utf8 || !strcmp(name, "ukrainian-utf8"))
+	{
+		charsys_addmultibyterange(0xd0, 0xd0, 0x84, 0x84);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x86, 0x87);
+		charsys_addmultibyterange(0xd0, 0xd0, 0x90, 0xa9);
+		charsys_addmultibyterange(0xd0, 0xd0, 0xac, 0xac);
+		charsys_addmultibyterange(0xd0, 0xd0, 0xae, 0xbf);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x89);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x8c, 0x8c);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x8e, 0x8f);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x94, 0x94);
+		charsys_addmultibyterange(0xd1, 0xd1, 0x96, 0x97);
+		charsys_addmultibyterange(0xd2, 0xd2, 0x90, 0x91);
+	}
+
+	/* [GREEK] */
+	if (!strcmp(name, "greek"))
+	{
+		/* supplied by GSF */
+		/* ranges from rfc1947 / iso 8859-7 */
+		charsys_addallowed("¶¸¹º¼¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóô");
+	}
+	if (!strcmp(name, "greek-utf8"))
+	{
+		charsys_addmultibyterange(0xce, 0xce, 0x86, 0x86);
+		charsys_addmultibyterange(0xce, 0xce, 0x88, 0x8a);
+		charsys_addmultibyterange(0xce, 0xce, 0x8c, 0x8c);
+		charsys_addmultibyterange(0xce, 0xce, 0x8e, 0xa1);
+		charsys_addmultibyterange(0xce, 0xce, 0xa3, 0xbf);
+		charsys_addmultibyterange(0xcf, 0xcf, 0x80, 0x84);
+	}
+
+	/* [TURKISH] */
+	if (!strcmp(name, "turkish"))
+	{
+		/* Supplied by Ayberk Yancatoral */
+		charsys_addallowed("öÖçÇþÞüÜðÐý");
+	}
+	if (!strcmp(name, "turkish-utf8"))
+	{
+		charsys_addmultibyterange(0xc3, 0xc3, 0x87, 0x87);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa7, 0xa7);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x9e, 0x9f);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xb1, 0xb1);
+		charsys_addmultibyterange(0xc5, 0xc5, 0x9e, 0x9f);
+	}
+
+	/* [HEBREW] */
+	if (!strcmp(name, "hebrew"))
+	{
+		/* Supplied by PHANTOm. */
+		/* 0xE0 - 0xFE */
+		charsys_addallowed("àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ");
+	}
+	if (!strcmp(name, "hebrew-utf8"))
+	{
+		/* Supplied by Lion-O */
+		charsys_addmultibyterange(0xd7, 0xd7, 0x90, 0xaa);
+	}
+
+	/* [CHINESE] */
+	if (chinese || !strcmp(name, "chinese-ja"))
+	{
+		charsys_addmultibyterange(0xa4, 0xa4, 0xa1, 0xf3); /* JIS_PIN */
+		charsys_addmultibyterange(0xa5, 0xa5, 0xa1, 0xf6); /* JIS_PIN */
+	}
+	if (chinese || !strcmp(name, "chinese-simp"))
+	{
+		charsys_addmultibyterange(0xb0, 0xd6, 0xa1, 0xfe); /* GBK/2 BC with GB2312 */
+		charsys_addmultibyterange(0xd7, 0xd7, 0xa1, 0xf9); /* GBK/2 BC with GB2312 */
+		charsys_addmultibyterange(0xd8, 0xf7, 0xa1, 0xfe); /* GBK/2 BC with GB2312 */
+	}
+	if (chinese || !strcmp(name, "chinese-trad"))
+	{
+		charsys_addmultibyterange(0x81, 0xa0, 0x40, 0x7e); /* GBK/3 - lower half */
+		charsys_addmultibyterange(0x81, 0xa0, 0x80, 0xfe); /* GBK/3 - upper half */
+		charsys_addmultibyterange(0xaa, 0xfe, 0x40, 0x7e); /* GBK/4 - lower half */
+		charsys_addmultibyterange(0xaa, 0xfe, 0x80, 0xa0); /* GBK/4 - upper half */
+	}
+
+	/* [LATVIAN] */
+	if (latin_utf8 || !strcmp(name, "latvian-utf8"))
+	{
+		/* A a, C c, E e, G g, I i, K k, Š š, U u, Ž ž */
+		charsys_addmultibyterange(0xc4, 0xc4, 0x80, 0x81);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x92, 0x93);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8d);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x92, 0x93);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xa2, 0xa3);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xaa, 0xab);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xb6, 0xb7);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xaa, 0xab);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
+	}
+
+	/* [ESTONIAN] */
+	if (latin_utf8 || !strcmp(name, "estonian-utf8"))
+	{
+		/* õ, ä, ö, ü,  Õ, Ä, Ö, Ü */
+		charsys_addmultibyterange(0xc3, 0xc3, 0xb5, 0xb6);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
+		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x95, 0x96);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
+		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
+	}
+
+	/* [LITHUANIAN] */
+	if (latin_utf8 || !strcmp(name, "lithuanian-utf8"))
+	{
+		/* a, c, e, e, i, š, u, u, ž, A, C, E, E, I, Š, U, U, Ž */
+		charsys_addmultibyterange(0xc4, 0xc4, 0x84, 0x85);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8d);
+		charsys_addmultibyterange(0xc4, 0xc4, 0x96, 0x99);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xae, 0xaf);
+		charsys_addmultibyterange(0xc4, 0xc4, 0xae, 0xaf);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
+		charsys_addmultibyterange(0xc5, 0xc5, 0xb2, 0xb3);
+		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 */
+char *charsys_displaychars(void)
+{
+#if 0
+	MBList *m;
+	unsigned char hibyte, lobyte;
+#endif
+	static char buf[512];
+	int n = 0;
+	int i, j;
+
+	// 		char_atribs[(unsigned char)*s] |= ALLOWN;
+	for (i = 0; i <= 255; i++)
+	{
+		if (char_atribs[i] & ALLOWN)
+			buf[n++] = i;
+		/* (no bounds checking: first 255 characters always fit a 512 byte buffer) */
+	}
+
+#if 0
+	for (m=mblist; m; m=m->next)
+	{
+		for (hibyte = m->s1; hibyte <= m->e1; hibyte++)
+		{
+			for (lobyte = m->s2; lobyte <= m->e2; lobyte++)
+			{
+				if (n >= sizeof(buf) - 3)
+					break; // break, or an attempt anyway
+				buf[n++] = hibyte;
+				buf[n++] = lobyte;
+			}
+		}
+	}
+#endif
+	/* above didn't work due to multiple overlapping ranges permitted.
+	 * try this instead (lazy).. this is only used in DEBUGMODE
+	 * via a command line option anyway:
+	 */
+	for (i=0; i <= 255; i++)
+	{
+		for (j=0; j <= 255; j++)
+		{
+			if (isvalidmbyte(i, j))
+			{
+				if (n >= sizeof(buf) - 3)
+					break; // break, or an attempt anyway
+				buf[n++] = i;
+				buf[n++] = j;
+			}
+		}
+	}
+
+	buf[n] = '\0'; /* there's always room for a NUL */
+
+	return buf;
+}
+
+char *charsys_group(int v)
+{
+	if (v & LANGAV_LATIN_UTF8)
+		return "Latin script";
+	if (v & LANGAV_CYRILLIC_UTF8)
+		return "Cyrillic script";
+	if (v & LANGAV_GREEK_UTF8)
+		return "Greek script";
+	if (v & LANGAV_HEBREW_UTF8)
+		return "Hebrew script";
+	if (v & LANGAV_ARABIC_UTF8)
+		return "Arabic script";
+
+	return "Other";
+}
+
+void charsys_dump_table(char *filter)
+{
+	int i = 0;
+
+	for (i = 0; langlist[i].directive; i++)
+	{
+		char *charset = langlist[i].directive;
+
+		if (!match_simple(filter, charset))
+			continue; /* skip */
+
+		charsys_reset();
+		charsys_add_language(charset);
+		charsys_finish();
+		printf("%s;%s;%s\n", charset, charsys_group(langlist[i].setflags), charsys_displaychars());
+	}
+}
+
+/** Get current languages (the 'langsinuse' variable) */
+char *_charsys_get_current_languages(void)
+{
+	return langsinuse;
+}
diff --git a/ircd/src/modules/chathistory.c b/ircd/src/modules/chathistory.c
@@ -0,0 +1,403 @@
+/* src/modules/chathistory.c - IRCv3 CHATHISTORY command.
+ * (C) Copyright 2021 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ *
+ * This implements the "CHATHISTORY" command, the CAP and 005 token.
+ * https://ircv3.net/specs/extensions/chathistory
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"chathistory",
+	"1.0",
+	"IRCv3 CHATHISTORY command",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Structs */
+typedef struct ChatHistoryTarget ChatHistoryTarget;
+struct ChatHistoryTarget {
+	ChatHistoryTarget *prev, *next;
+	char *datetime;
+	char *object;
+};
+
+/* Forward declarations */
+CMD_FUNC(cmd_chathistory);
+
+/* Global variables */
+long CAP_CHATHISTORY = 0L;
+
+#define CHATHISTORY_LIMIT 50
+
+MOD_INIT()
+{
+	ClientCapabilityInfo c;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, "CHATHISTORY", cmd_chathistory, MAXPARA, CMD_USER);
+
+	memset(&c, 0, sizeof(c));
+	c.name = "draft/chathistory";
+	ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHATHISTORY);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	ISupportSetFmt(modinfo->handle, "CHATHISTORY", "%d", CHATHISTORY_LIMIT);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int chathistory_token(const char *str, char *token, char **store)
+{
+	char request[BUFSIZE];
+	char *p;
+
+	strlcpy(request, str, sizeof(request));
+
+	p = strchr(request, '=');
+	if (!p)
+		return 0;
+	*p = '\0'; // frag
+	if (!strcmp(request, token))
+	{
+		*p = '='; // restore
+		*store = strdup(p + 1); // can be \0
+		return 1;
+	}
+	*p = '='; // restore
+	return 0;
+}
+
+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 *m;
+	time_t ts;
+	char *datetime;
+	ChatHistoryTarget *e;
+
+	if (!r->log || !((m = find_mtag(r->log->mtags, "time"))) || !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))
+	{
+		mtags = safe_alloc(sizeof(MessageTag));
+		mtags->name = strdup("batch");
+		mtags->value = strdup(batchid);
+	}
+
+	sendto_one(client, mtags, ":%s CHATHISTORY TARGETS %s %s",
+		me.name, r->object, r->datetime);
+
+	if (mtags)
+		free_message_tags(mtags);
+}
+
+void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
+{
+	Membership *mp;
+	HistoryResult *r;
+	char batch[BATCHLEN+1];
+	int sent = 0;
+	ChatHistoryTarget *targets = NULL, *targets_next;
+
+	/* 1. Grab all information we need */
+
+	filter->cmd = HFC_BEFORE;
+	if (strcmp(filter->timestamp_a, filter->timestamp_b) < 0)
+	{
+		/* Swap if needed */
+		char *swap = filter->timestamp_a;
+		filter->timestamp_a = filter->timestamp_b;
+		filter->timestamp_b = swap;
+	}
+	filter->limit = 1;
+
+	for (mp = client->user->channel; mp; mp = mp->next)
+	{
+		Channel *channel = mp->channel;
+		r = history_request(channel->name, filter);
+		if (r)
+		{
+			add_chathistory_target(&targets, r);
+			free_history_result(r);
+		}
+	}
+
+	/* 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 */
+	if (*batch)
+		sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
+}
+
+void send_empty_batch(Client *client, const char *target)
+{
+	char batch[BATCHLEN+1];
+
+	if (HasCapability(client, "batch"))
+	{
+		generate_batch_id(batch);
+		sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, target);
+		sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
+	}
+}
+
+CMD_FUNC(cmd_chathistory)
+{
+	HistoryFilter *filter = NULL;
+	HistoryResult *r = NULL;
+	Channel *channel;
+
+	memset(&filter, 0, sizeof(filter));
+
+	/* This command is only for local users */
+	if (!MyUser(client))
+		return;
+
+	if ((parc < 5) || BadPtr(parv[4]))
+	{
+		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS :Insufficient parameters", me.name);
+		return;
+	}
+
+	if (!HasCapability(client, "server-time"))
+	{
+		sendnotice(client, "Your IRC client does not support the 'server-time' capability");
+		sendnotice(client, "https://ircv3.net/specs/extensions/server-time");
+		sendnotice(client, "History request refused.");
+		return;
+	}
+
+	if (!strcasecmp(parv[1], "TARGETS"))
+	{
+		Membership *mp;
+		int limit;
+
+		filter = safe_alloc(sizeof(HistoryFilter));
+		/* Below this point, instead of 'return', use 'goto end' */
+
+		if (!chathistory_token(parv[2], "timestamp", &filter->timestamp_a))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx",
+				me.name, parv[1], parv[3]);
+			goto end;
+		}
+		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_b))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx",
+				me.name, parv[1], parv[4]);
+			goto end;
+		}
+		limit = atoi(parv[4]);
+		chathistory_targets(client, filter, limit);
+		goto end;
+	}
+
+	/* We don't support retrieving chathistory for PM's. Send empty response/batch, similar to channels without +H. */
+	if (parv[2][0] != '#')
+	{
+		send_empty_batch(client, parv[2]);
+		return;
+	}
+
+	channel = find_channel(parv[2]);
+	if (!channel)
+	{
+		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, 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'))
+	{
+		send_empty_batch(client, channel->name);
+		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 (!strcasecmp(parv[1], "BEFORE"))
+	{
+		filter->cmd = HFC_BEFORE;
+		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+				me.name, parv[1], parv[3]);
+			goto end;
+		}
+		filter->limit = atoi(parv[4]);
+	} else
+	if (!strcasecmp(parv[1], "AFTER"))
+	{
+		filter->cmd = HFC_AFTER;
+		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+				me.name, parv[1], parv[3]);
+			goto end;
+		}
+		filter->limit = atoi(parv[4]);
+	} else
+	if (!strcasecmp(parv[1], "LATEST"))
+	{
+		filter->cmd = HFC_LATEST;
+		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+		    !chathistory_token(parv[3], "msgid", &filter->msgid_a) &&
+		    strcmp(parv[3], "*"))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx or *",
+				me.name, parv[1], parv[3]);
+			goto end;
+		}
+		filter->limit = atoi(parv[4]);
+	} else
+	if (!strcasecmp(parv[1], "AROUND"))
+	{
+		filter->cmd = HFC_AROUND;
+		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+				me.name, parv[1], parv[3]);
+			goto end;
+		}
+		filter->limit = atoi(parv[4]);
+	} else
+	if (!strcasecmp(parv[1], "BETWEEN"))
+	{
+		filter->cmd = HFC_BETWEEN;
+		if (BadPtr(parv[5]))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s :Insufficient parameters", parv[1], me.name);
+			goto end;
+		}
+		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+				me.name, parv[1], parv[3]);
+			goto end;
+		}
+		if (!chathistory_token(parv[4], "timestamp", &filter->timestamp_b) &&
+		    !chathistory_token(parv[4], "msgid", &filter->msgid_b))
+		{
+			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+				me.name, parv[1], parv[4]);
+			goto end;
+		}
+		filter->limit = atoi(parv[5]);
+	} else {
+		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s :Invalid subcommand", me.name, parv[1]);
+		goto end;
+	}
+
+	if (filter->limit <= 0)
+	{
+		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %d :Specified limit is =<0",
+			me.name, parv[1], filter->limit);
+		goto end;
+	}
+
+	if (filter->limit > CHATHISTORY_LIMIT)
+		filter->limit = CHATHISTORY_LIMIT;
+
+	if ((r = history_request(channel->name, filter)))
+		history_send_result(client, r);
+
+end:
+	if (filter)
+		free_history_filter(filter);
+	if (r)
+		free_history_result(r);
+}
diff --git a/ircd/src/modules/chghost.c b/ircd/src/modules/chghost.c
@@ -0,0 +1,372 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/chghost.c
+ *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
+ *
+ *   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_CHGHOST 	"CHGHOST"
+
+CMD_FUNC(cmd_chghost);
+void _userhost_save_current(Client *client);
+void _userhost_changed(Client *client);
+
+long CAP_CHGHOST = 0L;
+
+ModuleHeader MOD_HEADER
+  = {
+	"chghost",	/* Name of module */
+	"5.0", /* Version */
+	"/chghost", /* Short description of module */
+	"UnrealIRCd Team",
+	"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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+	
+}
+
+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_CHANGE, 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);
+
+		if (MyUser(client))
+			sendnumeric(client, RPL_HOSTHIDDEN, GetHost(client));
+
+		/* 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>
+ * parv[1] - target user
+ * parv[2] - hostname
+ *
+*/
+
+CMD_FUNC(cmd_chghost)
+{
+	Client *target;
+
+	if (MyUser(client) && !ValidatePermissionsForPath("client:set:host",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 3) || BadPtr(parv[2]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGHOST");
+		return;
+	}
+
+	if (strlen(parv[2]) > (HOSTLEN))
+	{
+		sendnotice(client, "*** ChgName Error: Requested hostname too long -- rejected.");
+		return;
+	}
+
+	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;
+	}
+
+	if (parv[2][0] == ':')
+	{
+		sendnotice(client, "*** A hostname cannot start with ':'");
+		return;
+	}
+
+	target = find_client(parv[1], NULL);
+	if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
+	{
+		/* CHGHOST for a UID that is not online.
+		 * Let's assume it may not YET be online and forward the message to
+		 * the remote server and stop processing ourselves.
+		 * That server will then handle pre-registered processing of the
+		 * CHGHOST and later communicate the host when the user actually
+		 * comes online in the UID message.
+		 */
+		sendto_one(target, recv_mtags, ":%s CHGHOST %s %s", client->id, parv[1], parv[2]);
+		return;
+	}
+
+	if (!target || !target->user)
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		return;
+	}
+
+	if (!strcmp(GetHost(target), parv[2]))
+	{
+		sendnotice(client, "*** /ChgHost Error: requested host is same as current host.");
+		return;
+	}
+
+	userhost_save_current(target);
+
+	switch (UHOST_ALLOWED)
+	{
+		case UHALLOW_NEVER:
+			if (MyUser(client))
+			{
+				sendnumeric(client, ERR_DISABLED, "CHGHOST",
+					"This command is disabled on this server");
+				return;
+			}
+			break;
+		case UHALLOW_ALWAYS:
+			break;
+		case UHALLOW_NOCHANS:
+			if (IsUser(target) && MyUser(client) && target->user->joined)
+			{
+				sendnotice(client, "*** /ChgHost can not be used while %s is on a channel", target->name);
+				return;
+			}
+			break;
+		case UHALLOW_REJOIN:
+			/* rejoin sent later when the host has been changed */
+			break;
+	}
+
+	if (!IsULine(client))
+	{
+		const char *issuer = command_issued_by_rpc(recv_mtags);
+		if (issuer)
+		{
+			unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
+				   "CHGHOST: $issuer changed the virtual hostname of $target.details to be $new_hostname",
+				   log_data_string("issuer", issuer),
+				   log_data_string("change_type", "hostname"),
+				   log_data_client("target", target),
+				   log_data_string("new_hostname", parv[2]));
+		} else {
+			unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
+				   "CHGHOST: $client changed the virtual hostname of $target.details to be $new_hostname",
+				   log_data_string("change_type", "hostname"),
+				   log_data_client("target", target),
+				   log_data_string("new_hostname", parv[2]));
+		}
+	}
+
+	target->umodes |= UMODE_HIDE;
+	target->umodes |= UMODE_SETHOST;
+
+	/* Send to other servers too, unless the client is still in the registration phase (SASL) */
+	if (IsUser(target))
+		sendto_server(client, 0, 0, recv_mtags, ":%s CHGHOST %s %s", client->id, target->id, parv[2]);
+
+	safe_strdup(target->user->virthost, parv[2]);
+	userhost_changed(target);
+}
diff --git a/ircd/src/modules/chgident.c b/ircd/src/modules/chgident.c
@@ -0,0 +1,164 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/chgident.c
+ *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
+ *
+ *   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_CHGIDENT 	"CHGIDENT"
+
+CMD_FUNC(cmd_chgident);
+
+ModuleHeader MOD_HEADER
+  = {
+	"chgident",	/* Name of module */
+	"5.0", /* Version */
+	"/chgident", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_CHGIDENT, cmd_chgident, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* 
+ * cmd_chgident - 12/07/1999 (two months after I made SETIDENT) - Stskeeps
+ * :prefix CHGIDENT <nick> <new identname>
+ * parv[1] - nickname
+ * parv[2] - identname
+ *
+*/
+
+CMD_FUNC(cmd_chgident)
+{
+	Client *target;
+	const char *s;
+	int legalident = 1;
+
+	if (!ValidatePermissionsForPath("client:set:ident",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+
+	if ((parc < 3) || !*parv[2])
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGIDENT");
+		return;
+	}
+
+	if (strlen(parv[2]) > (USERLEN))
+	{
+		sendnotice(client, "*** ChgIdent Error: Requested ident too long -- rejected.");
+		return;
+	}
+
+	if (!valid_username(parv[2]))
+	{
+		sendnotice(client, "*** /ChgIdent Error: A ident may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them");
+		return;
+	}
+
+	target = find_client(parv[1], NULL);
+	if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
+	{
+		/* CHGIDENT for a UID that is not online.
+		 * Let's assume it may not YET be online and forward the message to
+		 * the remote server and stop processing ourselves.
+		 * That server will then handle pre-registered processing of the
+		 * CHGIDENT and later communicate the host when the user actually
+		 * comes online in the UID message.
+		 */
+		sendto_one(target, recv_mtags, ":%s CHGIDENT %s %s", client->id, parv[1], parv[2]);
+		return;
+	}
+
+	if (!target || !target->user)
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		return;
+	}
+	userhost_save_current(target);
+
+	switch (UHOST_ALLOWED)
+	{
+		case UHALLOW_NEVER:
+			if (MyUser(client))
+			{
+				sendnumeric(client, ERR_DISABLED, "CHGIDENT",
+					"This command is disabled on this server");
+				return;
+			}
+			break;
+		case UHALLOW_ALWAYS:
+			break;
+		case UHALLOW_NOCHANS:
+			if (IsUser(target) && MyUser(client) && target->user->joined)
+			{
+				sendnotice(client, "*** /ChgIdent can not be used while %s is on a channel", target->name);
+				return;
+			}
+			break;
+		case UHALLOW_REJOIN:
+			/* join sent later when the ident has been changed */
+			break;
+	}
+	if (!IsULine(client))
+	{
+		const char *issuer = command_issued_by_rpc(recv_mtags);
+		if (issuer)
+		{
+			unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
+				   "CHGIDENT: $issuer changed the username of $target.details to be $new_username",
+				   log_data_string("issuer", issuer),
+				   log_data_string("change_type", "username"),
+				   log_data_client("target", target),
+				   log_data_string("new_username", parv[2]));
+		} else {
+			unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
+				   "CHGIDENT: $client changed the username of $target.details to be $new_username",
+				   log_data_string("change_type", "username"),
+				   log_data_client("target", target),
+				   log_data_string("new_username", parv[2]));
+		}
+	}
+
+	/* Send to other servers too, unless the client is still in the registration phase (SASL) */
+	if (IsUser(target))
+		sendto_server(client, 0, 0, recv_mtags, ":%s CHGIDENT %s %s", client->id, target->id, parv[2]);
+
+	ircsnprintf(target->user->username, sizeof(target->user->username), "%s", parv[2]);
+	userhost_changed(target);
+}
diff --git a/ircd/src/modules/chgname.c b/ircd/src/modules/chgname.c
@@ -0,0 +1,134 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/chgname.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_CHGNAME     "CHGNAME"
+#define MSG_SVSNAME     "SVSNAME"
+
+CMD_FUNC(cmd_chgname);
+
+ModuleHeader MOD_HEADER
+  = {
+	"chgname",	/* Name of module */
+	"5.0", /* Version */
+	"command /chgname", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_CHGNAME, cmd_chgname, 2, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, MSG_SVSNAME, cmd_chgname, 2, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/*
+ * cmd_chgname - Tue May 23 13:06:35 BST 200 (almost a year after I made CHGIDENT) - Stskeeps
+ * :prefix CHGNAME <nick> <new realname>
+ * parv[1] - nickname
+ * parv[2] - realname
+ *
+*/
+CMD_FUNC(cmd_chgname)
+{
+	Client *target;
+	ConfigItem_ban *bconf;
+
+	if (!ValidatePermissionsForPath("client:set:name",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 3) || !*parv[2])
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGNAME");
+		return;
+	}
+
+	if (strlen(parv[2]) > (REALLEN))
+	{
+		sendnotice(client, "*** ChgName Error: Requested realname too long -- rejected.");
+		return;
+	}
+
+	if (!(target = find_user(parv[1], NULL)))
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		return;
+	}
+
+	/* Let's log this first */
+	if (!IsULine(client))
+	{
+		const char *issuer = command_issued_by_rpc(recv_mtags);
+		if (issuer)
+		{
+			unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
+				   "CHGNAME: $issuer changed the realname of $target.details to be $new_realname",
+				   log_data_string("issuer", issuer),
+				   log_data_string("change_type", "realname"),
+				   log_data_client("target", target),
+				   log_data_string("new_realname", parv[2]));
+		} else {
+			unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
+				   "CHGNAME: $client changed the realname of $target.details to be $new_realname",
+				   log_data_string("change_type", "realname"),
+				   log_data_client("target", target),
+				   log_data_string("new_realname", parv[2]));
+		}
+	}
+
+	/* set the realname to make ban checking work */
+	ircsnprintf(target->info, sizeof(target->info), "%s", parv[2]);
+
+	if (MyUser(target))
+	{
+		/* only check for realname bans if the person who's name is being changed is NOT an oper */
+		if (!ValidatePermissionsForPath("immune:server-ban:ban-realname",target,NULL,NULL,NULL) &&
+		    ((bconf = find_ban(NULL, target->info, CONF_BAN_REALNAME))))
+		{
+			banned_client(target, "realname", bconf->reason?bconf->reason:"", 0, 0);
+			return;
+		}
+	}
+
+	sendto_server(client, 0, 0, recv_mtags, ":%s CHGNAME %s :%s",
+	    client->id, target->name, parv[2]);
+}
diff --git a/ircd/src/modules/clienttagdeny.c b/ircd/src/modules/clienttagdeny.c
@@ -0,0 +1,77 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/echo-message.c
+ *   (C) 2020 k4be for The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+char *ct_isupport_param(void);
+int tags_rehash_complete(void);
+
+Module *module;
+
+ModuleHeader MOD_HEADER = {
+	"clienttagdeny",
+	"5.0",
+	"Informs clients about supported client tags",
+	"k4be",
+	"unrealircd-6",
+};
+
+MOD_INIT(){
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD(){
+	module = modinfo->handle;
+	ISupportAdd(module, "CLIENTTAGDENY", ct_isupport_param());
+	HookAdd(module, HOOKTYPE_REHASH_COMPLETE, 0, tags_rehash_complete);
+
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD(){
+	return MOD_SUCCESS;
+}
+
+#define BUFLEN 500
+
+char *ct_isupport_param(void){
+	static char buf[BUFLEN];
+	MessageTagHandler *m;
+	
+	strlcpy(buf, "*", sizeof(buf));
+
+	for (m = mtaghandlers; m; m = m->next) {
+		if (!m->unloaded && m->name[0] == '+'){
+			strlcat(buf, ",-", sizeof(buf));
+			strlcat(buf, m->name+1, sizeof(buf));
+		}
+	}
+	return buf;
+}
+
+int tags_rehash_complete(void){
+	ISupportSet(module, "CLIENTTAGDENY", ct_isupport_param());
+	return HOOK_CONTINUE;
+}
+
diff --git a/ircd/src/modules/cloak_md5.c b/ircd/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/ircd/src/modules/cloak_none.c b/ircd/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/ircd/src/modules/cloak_sha256.c b/ircd/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/ircd/src/modules/close.c b/ircd/src/modules/close.c
@@ -0,0 +1,82 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_close);
+
+#define MSG_CLOSE 	"CLOSE"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"close",
+	"5.0",
+	"command /close", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_CLOSE, cmd_close, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_close - added by Darren Reed Jul 13 1992.
+*/
+CMD_FUNC(cmd_close)
+{
+	Client *target, *next;
+	int  closed = 0;
+
+	if (!ValidatePermissionsForPath("server:close",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	list_for_each_entry_safe(target, next, &unknown_list, lclient_node)
+	{
+		sendnumeric(client, RPL_CLOSING,
+		    get_client_name(target, TRUE), target->status);
+		exit_client(target, NULL, "Oper Closing");
+		closed++;
+	}
+
+	sendnumeric(client, RPL_CLOSEEND, 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/ircd/src/modules/connect-flood.c b/ircd/src/modules/connect-flood.c
@@ -0,0 +1,89 @@
+/*
+ * Connection throttling (set::anti-flood::connect-flood)
+ * (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"connect-flood",
+	"6.0.0",
+	"set::anti-flood::connect-flood",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declaration */
+int connect_flood_accept(Client *client);
+int connect_flood_ip_change(Client *client, const char *oldip);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -3000, connect_flood_accept);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -3000, connect_flood_ip_change);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int connect_flood_throttle(Client *client, int exitflags)
+{
+	int val;
+	char zlinebuf[512];
+
+	if (!(val = throttle_can_connect(client)))
+	{
+		if (exitflags & NO_EXIT_CLIENT)
+		{
+			ircsnprintf(zlinebuf, sizeof(zlinebuf),
+				"ERROR :Closing Link: [%s] (Throttled: Reconnecting too fast) - "
+				"Email %s for more information.\r\n",
+				client->ip, KLINE_ADDRESS);
+			(void)send(client->local->fd, zlinebuf, strlen(zlinebuf), 0);
+			return HOOK_DENY;
+		} else {
+			ircsnprintf(zlinebuf, sizeof(zlinebuf),
+				    "Throttled: Reconnecting too fast - "
+				    "Email %s for more information.",
+				    KLINE_ADDRESS);
+			/* WAS: exit_client(client, NULL, zlinebuf);
+			 * Can't use exit_client() here because HOOKTYPE_IP_CHANGE call
+			 * may be too deep. Eg: read_packet -> webserver_packet_in ->
+			 * webserver_handle_request_header -> webserver_handle_request ->
+			 * RunHook().... and then returning without touching anything
+			 * after an exit_client() would not be feasible.
+			 */
+			dead_socket(client, zlinebuf);
+			return HOOK_DENY;
+		}
+	}
+	else if (val == 1)
+		add_throttling_bucket(client);
+
+	return 0;
+}
+
+int connect_flood_accept(Client *client)
+{
+	if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
+		return 0;
+	return connect_flood_throttle(client, NO_EXIT_CLIENT);
+}
+
+int connect_flood_ip_change(Client *client, const char *oldip)
+{
+	return connect_flood_throttle(client, 0);
+}
diff --git a/ircd/src/modules/connect.c b/ircd/src/modules/connect.c
@@ -0,0 +1,123 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_connect);
+
+#define MSG_CONNECT 	"CONNECT"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"connect",
+	"5.0",
+	"command /connect", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_CONNECT, cmd_connect, MAXPARA, CMD_USER|CMD_SERVER); /* hmm.. server.. really? */
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/***********************************************************************
+ * cmd_connect() - Added by Jto 11 Feb 1989
+ ***********************************************************************//*
+   ** cmd_connect
+   **  parv[1] = servername
+ */
+CMD_FUNC(cmd_connect)
+{
+	int  retval;
+	ConfigItem_link	*aconf;
+	Client *server;
+	const char *str;
+
+	if (!IsServer(client) && MyConnect(client) && !ValidatePermissionsForPath("route:global",client,NULL,NULL,NULL) && parc > 3)
+	{			/* Only allow LocOps to make */
+		/* local CONNECTS --SRB      */
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	if (!IsServer(client) && MyUser(client) && !ValidatePermissionsForPath("route:local",client,NULL,NULL,NULL) && parc <= 3)
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	if (hunt_server(client, recv_mtags, "CONNECT", 3, parc, parv) != HUNTED_ISME)
+		return;
+
+	if (parc < 2 || *parv[1] == '\0')
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "CONNECT");
+		return;
+	}
+
+	if ((server = find_server_quick(parv[1])))
+	{
+		sendnotice(client, "*** Connect: Server %s already exists from %s.",
+		    parv[1], server->direction->name);
+		return;
+	}
+
+	aconf = find_link(parv[1]);
+	if (!aconf)
+	{
+		sendnotice(client,
+		    "*** Connect: Server %s is not configured for linking",
+		    parv[1]);
+		return;
+	}
+
+	if (!aconf->outgoing.hostname && !aconf->outgoing.file)
+	{
+		sendnotice(client,
+		    "*** Connect: Server %s is not configured to be an outgoing link (has a link block, but no link::outgoing::hostname or link::outgoing::file)",
+		    parv[1]);
+		return;
+	}
+
+	if ((str = check_deny_link(aconf, 0)))
+	{
+		sendnotice(client, "*** Connect: Disallowed by connection rule: %s", str);
+		return;
+	}
+
+	unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
+		   "CONNECT: Link to $link_block requested by $client",
+		   log_data_link_block(aconf));
+
+	connect_server(aconf, client, NULL);
+}
diff --git a/ircd/src/modules/connthrottle.c b/ircd/src/modules/connthrottle.c
@@ -0,0 +1,600 @@
+/*
+ * connthrottle - Connection throttler
+ * (C) Copyright 2004-2020 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ * See https://www.unrealircd.org/docs/Connthrottle
+ */
+
+#include "unrealircd.h"
+
+#define CONNTHROTTLE_VERSION "1.3"
+
+#ifndef CALLBACKTYPE_REPUTATION_STARTTIME
+ #define CALLBACKTYPE_REPUTATION_STARTTIME 5
+#endif
+
+ModuleHeader MOD_HEADER
+  = {
+	"connthrottle",
+	CONNTHROTTLE_VERSION,
+	"Connection throttler - by Syzop",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+typedef struct {
+	int count;
+	int period;
+} ThrottleSetting;
+
+struct cfgstruct {
+	/* set::connthrottle::known-users: */
+	ThrottleSetting local;
+	ThrottleSetting global;
+	/* set::connthrottle::except: */
+	SecurityGroup *except;
+	/* set::connthrottle::disabled-when: */
+	long reputation_gathering;
+	int start_delay;
+	/* set::connthrottle (generic): */
+	char *reason;
+};
+static struct cfgstruct cfg;
+
+typedef struct {
+	int count;
+	long t;
+} ThrottleCounter;
+
+typedef struct UCounter UCounter;
+struct UCounter {
+	ThrottleCounter local;		/**< Local counter */
+	ThrottleCounter global;		/**< Global counter */
+	int rejected_clients;		/**< Number of rejected clients this minute */
+	int allowed_except;		/**< Number of allowed clients - on except list */
+	int allowed_unknown_users;	/**< Number of allowed clients - not on except list */
+	char disabled;			/**< Module disabled by oper? */
+	int throttling_this_minute;	/**< Did we do any throttling this minute? */
+	int throttling_previous_minute;	/**< Did we do any throttling previous minute? */
+	int throttling_banner_displayed;/**< Big we-are-now-throttling banner displayed? */
+	time_t next_event;		/**< When is next event? (for "last 60 seconds" stats) */
+};
+UCounter *ucounter = NULL;
+
+#define MSG_THROTTLE "THROTTLE"
+
+/* Forward declarations */
+int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int ct_config_posttest(int *errs);
+int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+int ct_pre_lconnect(Client *client);
+int ct_lconnect(Client *);
+int ct_rconnect(Client *);
+CMD_FUNC(ct_throttle);
+EVENT(connthrottle_evt);
+void ucounter_free(ModData *m);
+
+MOD_TEST()
+{
+	memset(&cfg, 0, sizeof(cfg));
+	
+	/* Defaults: */
+	cfg.local.count = 20; cfg.local.period = 60;
+	cfg.global.count = 30; cfg.global.period = 60;
+	cfg.start_delay = 180;		/* 3 minutes */
+	safe_strdup(cfg.reason, "Throttled: Too many users trying to connect, please wait a while and try again");
+	cfg.except = safe_alloc(sizeof(SecurityGroup));
+	cfg.except->reputation_score = 24;
+	cfg.except->identified = 1;
+	cfg.except->webirc = 0;
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, ct_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, ct_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	LoadPersistentPointer(modinfo, ucounter, ucounter_free);
+	if (!ucounter)
+		ucounter = safe_alloc(sizeof(UCounter));
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, ct_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, ct_pre_lconnect);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, ct_lconnect);
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, ct_rconnect);
+	CommandAdd(modinfo->handle, MSG_THROTTLE, ct_throttle, MAXPARA, CMD_USER|CMD_SERVER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	EventAdd(modinfo->handle, "connthrottle_evt", connthrottle_evt, NULL, 1000, 0);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	SavePersistentPointer(modinfo, ucounter);
+	safe_free(cfg.reason);
+	free_security_group(cfg.except);
+	return MOD_SUCCESS;
+}
+
+/** This function checks if the reputation module is loaded.
+ * If not, then the module will error, since we depend on it.
+ */
+int ct_config_posttest(int *errs)
+{
+	int errors = 0;
+
+	/* Note: we use Callbacks[] here, but this is only for checking. Don't
+	 * let this confuse you. At any other place you must use RCallbacks[].
+	 */
+	if (Callbacks[CALLBACKTYPE_REPUTATION_STARTTIME] == NULL)
+	{
+		config_error("The 'connthrottle' module requires the 'reputation' "
+		             "module to be loaded as well.");
+		config_error("Add the following to your configuration file: "
+		             "loadmodule \"reputation\";");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/** Test the set::connthrottle configuration */
+int ct_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::connthrottle.. */
+	if (!ce || !ce->name || strcmp(ce->name, "connthrottle"))
+		return 0;
+	
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "except"))
+		{
+			test_match_block(cf, cep, &errors);
+		} else
+		if (!strcmp(cep->name, "known-users"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				CheckNull(cepp);
+				if (!strcmp(cepp->name, "minimum-reputation-score"))
+				{
+					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->file->filename, cepp->line_number);
+						errors++;
+						continue;
+					}
+				} else
+				if (!strcmp(cepp->name, "sasl-bypass"))
+				{
+				} else
+				if (!strcmp(cepp->name, "webirc-bypass"))
+				{
+				} else
+				{
+					config_error_unknown(cepp->file->filename, cepp->line_number,
+					                     "set::connthrottle::known-users", cepp->name);
+					errors++;
+				}
+			}
+		} else
+		if (!strcmp(cep->name, "new-users"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				CheckNull(cepp);
+				if (!strcmp(cepp->name, "local-throttle"))
+				{
+					int 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->file->filename, cepp->line_number);
+						errors++;
+						continue;
+					}
+				} else
+				if (!strcmp(cepp->name, "global-throttle"))
+				{
+					int 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->file->filename, cepp->line_number);
+						errors++;
+						continue;
+					}
+				} else
+				{
+					config_error_unknown(cepp->file->filename, cepp->line_number,
+					                     "set::connthrottle::new-users", cepp->name);
+					errors++;
+				}
+			}
+		} else
+		if (!strcmp(cep->name, "disabled-when"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				CheckNull(cepp);
+				if (!strcmp(cepp->name, "start-delay"))
+				{
+					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->file->filename, cepp->line_number);
+						errors++;
+						continue;
+					}
+				} else
+				if (!strcmp(cepp->name, "reputation-gathering"))
+				{
+				} else
+				{
+					config_error_unknown(cepp->file->filename, cepp->line_number,
+					                     "set::connthrottle::disabled-when", cepp->name);
+					errors++;
+				}
+			}
+		} else
+		if (!strcmp(cep->name, "reason"))
+		{
+			CheckNull(cep);
+		} else
+		{
+			config_error("%s:%i: unknown directive set::connthrottle::%s",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		}
+	}
+	
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/* Configure ourselves based on the set::connthrottle settings */
+int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep, *cepp;
+
+	if (type != CONFIG_SET)
+		return 0;
+	
+	/* We are only interrested in set::connthrottle.. */
+	if (!ce || !ce->name || strcmp(ce->name, "connthrottle"))
+		return 0;
+	
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "except"))
+		{
+			conf_match_block(cf, cep, &cfg.except);
+		} else
+		if (!strcmp(cep->name, "known-users"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				if (!strcmp(cepp->name, "minimum-reputation-score"))
+					cfg.except->reputation_score = atoi(cepp->value);
+				else if (!strcmp(cepp->name, "sasl-bypass"))
+					cfg.except->identified = config_checkval(cepp->value, CFG_YESNO);
+				else if (!strcmp(cepp->name, "webirc-bypass"))
+					cfg.except->webirc = config_checkval(cepp->value, CFG_YESNO);
+			}
+		} else
+		if (!strcmp(cep->name, "new-users"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				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->name, "disabled-when"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				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->name, "reason"))
+		{
+			safe_free(cfg.reason);
+			cfg.reason = safe_alloc(strlen(cep->value)+16);
+			sprintf(cfg.reason, "Throttled: %s", cep->value);
+		}
+	}
+	return 1;
+}
+
+/** Returns 1 if the 'reputation' module is still gathering
+ * data, such as in the first week of when it is loaded.
+ * This behavior is configured via set::disabled-when::reputation-gathering
+ */
+int still_reputation_gathering(void)
+{
+	int v;
+
+	if (RCallbacks[CALLBACKTYPE_REPUTATION_STARTTIME] == NULL)
+		return 1; /* Reputation module not loaded, disable us */
+
+	v = RCallbacks[CALLBACKTYPE_REPUTATION_STARTTIME]->func.intfunc();
+
+	if (TStime() - v < cfg.reputation_gathering)
+		return 1; /* Still gathering reputation data (eg: first week) */
+
+	return 0;
+}
+
+EVENT(connthrottle_evt)
+{
+	char buf[512];
+
+	if (ucounter->next_event > TStime())
+		return;
+	ucounter->next_event = TStime() + 60;
+
+	if (ucounter->rejected_clients)
+	{
+		unreal_log(ULOG_INFO, "connthrottle", "CONNTHROTLE_REPORT", NULL,
+		           "ConnThrottle] Stats for this server past 60 secs: "
+		           "Connections rejected: $num_rejected. "
+		           "Accepted: $num_accepted_except except user(s) and "
+		           "$num_accepted_unknown_users new user(s).",
+		           log_data_integer("num_rejected", ucounter->rejected_clients),
+		           log_data_integer("num_accepted_except", ucounter->allowed_except),
+		           log_data_integer("num_accepted_unknown_users", ucounter->allowed_unknown_users));
+	}
+
+	/* Reset stats for next message */
+	ucounter->rejected_clients = 0;
+	ucounter->allowed_except = 0;
+	ucounter->allowed_unknown_users = 0;
+
+	ucounter->throttling_previous_minute = ucounter->throttling_this_minute;
+	ucounter->throttling_this_minute = 0; /* reset */
+	ucounter->throttling_banner_displayed = 0; /* reset */
+}
+
+#define THROT_LOCAL 1
+#define THROT_GLOBAL 2
+int ct_pre_lconnect(Client *client)
+{
+	int throttle=0;
+	int score;
+
+	if (me.local->creationtime + cfg.start_delay > TStime())
+		return HOOK_CONTINUE; /* no throttle: start delay */
+
+	if (ucounter->disabled)
+		return HOOK_CONTINUE; /* protection disabled: allow user */
+
+	if (still_reputation_gathering())
+		return HOOK_CONTINUE; /* still gathering reputation data */
+
+	if (user_allowed_by_security_group(client, cfg.except))
+		return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */
+
+	/* If we reach this then the user is NEW */
+
+	/* +1 global client would reach global limit? */
+	if ((TStime() - ucounter->global.t < cfg.global.period) && (ucounter->global.count+1 > cfg.global.count))
+		throttle |= THROT_GLOBAL;
+
+	/* +1 local client would reach local limit? */
+	if ((TStime() - ucounter->local.t < cfg.local.period) && (ucounter->local.count+1 > cfg.local.count))
+		throttle |= THROT_LOCAL;
+
+	if (throttle)
+	{
+		ucounter->throttling_this_minute = 1;
+		ucounter->rejected_clients++;
+		/* We send the LARGE banner if throttling was activated */
+		if (!ucounter->throttling_previous_minute && !ucounter->throttling_banner_displayed)
+		{
+			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);
+		return HOOK_DENY;
+	}
+
+	return HOOK_CONTINUE;
+}
+
+/** Increase the connect counter(s), nothing else. */
+void bump_connect_counter(int local_connect)
+{
+	if (local_connect)
+	{
+		/* Bump local connect counter */
+		if (TStime() - ucounter->local.t >= cfg.local.period)
+		{
+			ucounter->local.t = TStime();
+			ucounter->local.count = 1;
+		} else {
+			ucounter->local.count++;
+		}
+	}
+
+	/* Bump global connect counter */
+	if (TStime() - ucounter->global.t >= cfg.global.period)
+	{
+		ucounter->global.t = TStime();
+		ucounter->global.count = 1;
+	} else {
+		ucounter->global.count++;
+	}
+}
+
+int ct_lconnect(Client *client)
+{
+	int score;
+
+	if (me.local->creationtime + cfg.start_delay > TStime())
+		return 0; /* no throttle: start delay */
+
+	if (ucounter->disabled)
+		return 0; /* protection disabled: allow user */
+
+	if (still_reputation_gathering())
+		return 0; /* still gathering reputation data */
+
+	if (user_allowed_by_security_group(client, cfg.except))
+	{
+		ucounter->allowed_except++;
+		return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */
+	}
+
+	/* Allowed NEW user */
+	ucounter->allowed_unknown_users++;
+
+	bump_connect_counter(1);
+
+	return 0;
+}
+
+int ct_rconnect(Client *client)
+{
+	int score;
+
+	if (client->uplink && !IsSynched(client->uplink))
+		return 0; /* Netmerge: skip */
+
+	if (IsULine(client))
+		return 0; /* U:lined, such as services: skip */
+
+#if UNREAL_VERSION_TIME >= 201915
+	/* On UnrealIRCd 4.2.3+ we can see the boot time (start time)
+	 * of the remote server. This way we can apply the
+	 * set::disabled-when::start-delay restriction on remote
+	 * servers as well.
+	 */
+	if (client->uplink && client->uplink->server && client->uplink->server->boottime &&
+	    (TStime() - client->uplink->server->boottime < cfg.start_delay))
+	{
+		return 0;
+	}
+#endif
+
+	if (user_allowed_by_security_group(client, cfg.except))
+		return 0; /* user is on except list (known user or otherwise) */
+
+	bump_connect_counter(0);
+
+	return 0;
+}
+
+static void ct_throttle_usage(Client *client)
+{
+	sendnotice(client, "Usage: /THROTTLE [ON|OFF|STATUS|RESET]");
+	sendnotice(client, " ON:     Enabled protection");
+	sendnotice(client, " OFF:    Disables protection");
+	sendnotice(client, " STATUS: Status report");
+	sendnotice(client, " RESET:  Resets all counters(&more)");
+	sendnotice(client, "NOTE: All commands only affect this server. Remote servers are not affected.");
+}
+
+CMD_FUNC(ct_throttle)
+{
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		ct_throttle_usage(client);
+		return;
+	}
+
+	if (!strcasecmp(parv[1], "STATS") || !strcasecmp(parv[1], "STATUS"))
+	{
+		sendnotice(client, "STATUS:");
+		if (ucounter->disabled)
+		{
+			sendnotice(client, "Module DISABLED on oper request. To re-enable, type: /THROTTLE ON");
+		} else {
+			if (still_reputation_gathering())
+			{
+				sendnotice(client, "Module DISABLED because the 'reputation' module has not gathered enough data yet (set::connthrottle::disabled-when::reputation-gathering).");
+			} else
+			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->creationtime + cfg.start_delay) - TStime()));
+			} else
+			{
+				sendnotice(client, "Module ENABLED");
+			}
+		}
+	} else 
+	if (!strcasecmp(parv[1], "OFF"))
+	{
+		if (ucounter->disabled == 1)
+		{
+			sendnotice(client, "Already OFF");
+			return;
+		}
+		ucounter->disabled = 1;
+		unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_MODULE_DISABLED", client,
+			   "[ConnThrottle] $client.details DISABLED the connthrottle module.");
+	} else
+	if (!strcasecmp(parv[1], "ON"))
+	{
+		if (ucounter->disabled == 0)
+		{
+			sendnotice(client, "Already ON");
+			return;
+		}
+		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));
+		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]);
+		ct_throttle_usage(client);
+	}
+}
+
+void ucounter_free(ModData *m)
+{
+	safe_free(ucounter);
+}
diff --git a/ircd/src/modules/creationtime.c b/ircd/src/modules/creationtime.c
@@ -0,0 +1,100 @@
+/*
+ * Store creationtime in ModData
+ * (C) Copyright 2022-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"creationtime",
+	"6.1",
+	"Store and retrieve creation time of clients",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+void creationtime_free(ModData *m);
+const char *creationtime_serialize(ModData *m);
+void creationtime_unserialize(const char *str, ModData *m);
+int creationtime_handshake(Client *client);
+int creationtime_welcome_user(Client *client, int numeric);
+int creationtime_whois(Client *client, Client *target);
+
+ModDataInfo *creationtime_md; /* Module Data structure which we acquire */
+
+#define SetCreationTime(x,y)	do { moddata_client(x, creationtime_md).ll = y; } while(0)
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "creationtime";
+	mreq.free = creationtime_free;
+	mreq.serialize = creationtime_serialize;
+	mreq.unserialize = creationtime_unserialize;
+	mreq.self_write = 1;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	creationtime_md = ModDataAdd(modinfo->handle, mreq);
+	if (!creationtime_md)
+		abort();
+
+	/* This event sets creationtime very early: on handshake in and out */
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, creationtime_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, creationtime_handshake);
+
+	/* And this event (re)sets it because that also happens in
+	 * welcome_user() in nick.c regarding #2174
+	 */
+	HookAdd(modinfo->handle, HOOKTYPE_WELCOME, 0, creationtime_welcome_user);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int creationtime_handshake(Client *client)
+{
+	SetCreationTime(client, client->local->creationtime);
+	return 0;
+}
+
+int creationtime_welcome_user(Client *client, int numeric)
+{
+	if (numeric == 0)
+		SetCreationTime(client, client->local->creationtime);
+	return 0;
+}
+
+void creationtime_free(ModData *m)
+{
+	m->ll = 0;
+}
+
+const char *creationtime_serialize(ModData *m)
+{
+	static char buf[64];
+
+	snprintf(buf, sizeof(buf), "%lld", (long long)m->ll);
+	return buf;
+}
+
+void creationtime_unserialize(const char *str, ModData *m)
+{
+	m->ll = atoll(str);
+}
diff --git a/ircd/src/modules/cycle.c b/ircd/src/modules/cycle.c
@@ -0,0 +1,83 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/cycle.c
+ *   (C) 2000-2001 Carsten V. Munk 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"
+
+CMD_FUNC(cmd_cycle);
+
+/* Place includes here */
+#define MSG_CYCLE       "CYCLE"
+
+ModuleHeader MOD_HEADER
+  = {
+	"cycle",	/* Name of module */
+	"5.0", /* Version */
+	"command /cycle", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_CYCLE, cmd_cycle, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+	
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/*
+ * cmd_cycle() - Stskeeps
+ * parv[1] = channels
+*/
+
+CMD_FUNC(cmd_cycle)
+{
+	char channels[BUFSIZE];
+	const char *parx[3];
+	int n;
+	
+	if (parc < 2)
+		return;
+
+	/* First PART the user... */
+	parv[2] = "cycling";
+	strlcpy(channels, parv[1], sizeof(channels));
+	do_cmd(client, recv_mtags, "PART", 3, parv);
+	if (IsDead(client))
+		return;
+
+	/* Then JOIN the user again... */
+	parx[0] = NULL;
+	parx[1] = channels;
+	parx[2] = NULL;
+	do_cmd(client, recv_mtags, "JOIN", 2, parx);
+}
diff --git a/ircd/src/modules/dccallow.c b/ircd/src/modules/dccallow.c
@@ -0,0 +1,257 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/m_dccallow
+ *   (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_dccallow);
+
+#define MSG_DCCALLOW 	"DCCALLOW"
+
+ModuleHeader MOD_HEADER
+  = {
+	"dccallow",
+	"5.0",
+	"command /dccallow", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_DCCALLOW, cmd_dccallow, 1, CMD_USER);
+	ISupportAdd(modinfo->handle, "USERIP", NULL);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* cmd_dccallow:
+ * HISTORY:
+ * Taken from bahamut 1.8.1
+ */
+CMD_FUNC(cmd_dccallow)
+{
+	char request[BUFSIZE];
+	Link *lp;
+	char *p, *s;
+	Client *friend;
+	int didlist = 0, didhelp = 0, didanything = 0;
+	char **ptr;
+	int ntargets = 0;
+	int maxtargets = max_targets_for_command("WHOIS");
+	static char *dcc_help[] =
+	{
+		"/DCCALLOW [<+|->nick[,<+|->nick, ...]] [list] [help]",
+		"You may allow DCCs of files which are otherwise blocked by the IRC server",
+		"by specifying a DCC allow for the user you want to recieve files from.",
+		"For instance, to allow the user Bob to send you file.exe, you would type:",
+		"/DCCALLOW +bob",
+		"and Bob would then be able to send you files. Bob will have to resend the file",
+		"if the server gave him an error message before you added him to your allow list.",
+		"/DCCALLOW -bob",
+		"Will do the exact opposite, removing him from your dcc allow list.",
+		"/dccallow list",
+		"Will list the users currently on your dcc allow list.",
+		NULL
+	};
+
+	if (!MyUser(client))
+		return;
+	
+	if (parc < 2)
+	{
+		sendnotice(client, "No command specified for DCCALLOW. "
+			"Type '/DCCALLOW HELP' for more information.");
+		return;
+	}
+
+	strlcpy(request, parv[1], sizeof(request));
+	for (p = NULL, s = strtoken(&p, request, ", "); s; s = strtoken(&p, NULL, ", "))
+	{
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, s, maxtargets, "DCCALLOW");
+			break;
+		}
+		if (*s == '+')
+		{
+			didanything = 1;
+			if (!*++s)
+				continue;
+			
+			friend = find_user(s, NULL);
+			
+			if (friend == client)
+				continue;
+			
+			if (!friend)
+			{
+				sendnumeric(client, ERR_NOSUCHNICK, s);
+				continue;
+			}
+			add_dccallow(client, friend);
+		} else
+		if (*s == '-')
+		{
+			didanything = 1;
+			if (!*++s)
+				continue;
+			
+			friend = find_user(s, NULL);
+			if (friend == client)
+				continue;
+			if (!friend)
+			{
+				sendnumeric(client, ERR_NOSUCHNICK, s);
+				continue;
+			}
+			del_dccallow(client, friend);
+		} else
+		if (!didlist && !strncasecmp(s, "list", 4))
+		{
+			didanything = didlist = 1;
+			sendnumeric(client, RPL_DCCINFO, "The following users are on your dcc allow list:");
+			for(lp = client->user->dccallow; lp; lp = lp->next)
+			{
+				if (lp->flags == DCC_LINK_REMOTE)
+					continue;
+				sendnumericfmt(client, RPL_DCCLIST, ":%s (%s@%s)", lp->value.client->name,
+					lp->value.client->user->username,
+					GetHost(lp->value.client));
+			}
+			sendnumeric(client, RPL_ENDOFDCCLIST, s);
+		} else
+		if (!didhelp && !strncasecmp(s, "help", 4))
+		{
+			didanything = didhelp = 1;
+			for(ptr = dcc_help; *ptr; ptr++)
+				sendnumeric(client, RPL_DCCINFO, *ptr);
+			sendnumeric(client, RPL_ENDOFDCCLIST, s);
+		}
+	}
+	if (!didanything)
+	{
+		sendnotice(client, "Invalid syntax for DCCALLOW. Type '/DCCALLOW HELP' for more information.");
+		return;
+	}
+}
+
+/* The dccallow functions below are all taken from bahamut (1.8.1).
+ * Well, with some small modifications of course. -- Syzop
+ */
+
+/** Adds 'optr' to the DCCALLOW list of 'client' */
+int add_dccallow(Client *client, Client *optr)
+{
+	Link *lp;
+	int cnt = 0;
+
+	for (lp = client->user->dccallow; lp; lp = lp->next)
+	{
+		if (lp->flags != DCC_LINK_ME)
+			continue;
+		cnt++;
+		if (lp->value.client == optr)
+			return 0;
+	}
+
+	if (cnt >= MAXDCCALLOW)
+	{
+		sendnumeric(client, ERR_TOOMANYDCC,
+			optr->name, MAXDCCALLOW);
+		return 0;
+	}
+
+	lp = make_link();
+	lp->value.client = optr;
+	lp->flags = DCC_LINK_ME;
+	lp->next = client->user->dccallow;
+	client->user->dccallow = lp;
+
+	lp = make_link();
+	lp->value.client = client;
+	lp->flags = DCC_LINK_REMOTE;
+	lp->next = optr->user->dccallow;
+	optr->user->dccallow = lp;
+
+	sendnumeric(client, RPL_DCCSTATUS, optr->name, "added to");
+	return 0;
+}
+
+/** Removes 'optr' from the DCCALLOW list of 'client' */
+int del_dccallow(Client *client, Client *optr)
+{
+	Link **lpp, *lp;
+	int found = 0;
+
+	for (lpp = &(client->user->dccallow); *lpp; lpp=&((*lpp)->next))
+	{
+		if ((*lpp)->flags != DCC_LINK_ME)
+			continue;
+		if ((*lpp)->value.client == optr)
+		{
+			lp = *lpp;
+			*lpp = lp->next;
+			free_link(lp);
+			found++;
+			break;
+		}
+	}
+	if (!found)
+	{
+		sendnumericfmt(client, RPL_DCCINFO, ":%s is not in your DCC allow list", optr->name);
+		return 0;
+	}
+
+	for (found = 0, lpp = &(optr->user->dccallow); *lpp; lpp=&((*lpp)->next))
+	{
+		if ((*lpp)->flags != DCC_LINK_REMOTE)
+			continue;
+		if ((*lpp)->value.client == client)
+		{
+			lp = *lpp;
+			*lpp = lp->next;
+			free_link(lp);
+			found++;
+			break;
+		}
+	}
+	if (!found)
+	{
+		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");
+
+	return 0;
+}
diff --git a/ircd/src/modules/dccdeny.c b/ircd/src/modules/dccdeny.c
@@ -0,0 +1,882 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/dccdeny.c
+ *   (C) 2004-2019 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
+  = {
+	"dccdeny",
+	"6.0.2",
+	"command /dccdeny", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Variables */
+ConfigItem_deny_dcc     *conf_deny_dcc = NULL;
+ConfigItem_allow_dcc    *conf_allow_dcc = NULL;
+
+/* Forward declarions */
+int dccdeny_configtest_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+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, 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, 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, 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 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()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, dccdeny_configtest_deny_dcc);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, dccdeny_configtest_allow_dcc);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	LoadPersistentPointer(modinfo, conf_deny_dcc, dccdeny_unload_free_all_conf_deny_dcc);
+	LoadPersistentPointer(modinfo, conf_allow_dcc, dccdeny_unload_free_all_conf_allow_dcc);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, dccdeny_configrun_deny_dcc);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, dccdeny_configrun_allow_dcc);
+	CommandAdd(modinfo->handle, "DCCDENY", cmd_dccdeny, 2, CMD_USER);
+	CommandAdd(modinfo->handle, "UNDCCDENY", cmd_undccdeny, MAXPARA, CMD_USER);
+	CommandAdd(modinfo->handle, "SVSFLINE", cmd_svsfline, MAXPARA, CMD_SERVER);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, dccdeny_stats);
+	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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	free_dcc_config_blocks();
+	SavePersistentPointer(modinfo, conf_deny_dcc);
+	SavePersistentPointer(modinfo, conf_allow_dcc);
+
+	return MOD_SUCCESS;
+}
+
+int dccdeny_configtest_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	char has_filename = 0, has_reason = 0, has_soft = 0;
+
+	/* We are only interested in deny dcc { } */
+	if ((type != CONFIG_DENY) || strcmp(ce->value, "dcc"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (config_is_blankorempty(cep, "deny dcc"))
+		{
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "filename"))
+		{
+			if (has_filename)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "deny dcc::filename");
+				continue;
+			}
+			has_filename = 1;
+		}
+		else if (!strcmp(cep->name, "reason"))
+		{
+			if (has_reason)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "deny dcc::reason");
+				continue;
+			}
+			has_reason = 1;
+		}
+		else if (!strcmp(cep->name, "soft"))
+		{
+			if (has_soft)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "deny dcc::soft");
+				continue;
+			}
+			has_soft = 1;
+		}
+		else
+		{
+			config_error_unknown(cep->file->filename,
+				cep->line_number, "deny dcc", cep->name);
+			errors++;
+		}
+	}
+	if (!has_filename)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny dcc::filename");
+		errors++;
+	}
+	if (!has_reason)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny dcc::reason");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int dccdeny_configtest_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0, has_filename = 0, has_soft = 0;
+
+	/* We are only interested in allow dcc { } */
+	if ((type != CONFIG_ALLOW) || strcmp(ce->value, "dcc"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (config_is_blankorempty(cep, "allow dcc"))
+		{
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "filename"))
+		{
+			if (has_filename)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow dcc::filename");
+				continue;
+			}
+			has_filename = 1;
+		}
+		else if (!strcmp(cep->name, "soft"))
+		{
+			if (has_soft)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "allow dcc::soft");
+				continue;
+			}
+			has_soft = 1;
+		}
+		else
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"allow dcc", cep->name);
+			errors++;
+		}
+	}
+	if (!has_filename)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"allow dcc::filename");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int dccdeny_configrun_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigItem_deny_dcc 	*deny = NULL;
+	ConfigEntry 	    	*cep;
+
+	/* We are only interested in deny dcc { } */
+	if ((type != CONFIG_DENY) || strcmp(ce->value, "dcc"))
+		return 0;
+
+	deny = safe_alloc(sizeof(ConfigItem_deny_dcc));
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "filename"))
+		{
+			safe_strdup(deny->filename, cep->value);
+		}
+		else if (!strcmp(cep->name, "reason"))
+		{
+			safe_strdup(deny->reason, cep->value);
+		}
+		else if (!strcmp(cep->name, "soft"))
+		{
+			int x = config_checkval(cep->value,CFG_YESNO);
+			if (x == 1)
+				deny->flag.type = DCCDENY_SOFT;
+		}
+	}
+	if (!deny->reason)
+	{
+		if (deny->flag.type == DCCDENY_HARD)
+			safe_strdup(deny->reason, "Possible infected virus file");
+		else
+			safe_strdup(deny->reason, "Possible executable content");
+	}
+	AddListItem(deny, conf_deny_dcc);
+	return 0;
+}
+
+int dccdeny_configrun_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigItem_allow_dcc *allow = NULL;
+	ConfigEntry *cep;
+
+	/* We are only interested in allow dcc { } */
+	if ((type != CONFIG_ALLOW) || strcmp(ce->value, "dcc"))
+		return 0;
+
+	allow = safe_alloc(sizeof(ConfigItem_allow_dcc));
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "filename"))
+			safe_strdup(allow->filename, cep->value);
+		else if (!strcmp(cep->name, "soft"))
+		{
+			int x = config_checkval(cep->value,CFG_YESNO);
+			if (x)
+				allow->flag.type = DCCDENY_SOFT;
+		}
+	}
+	AddListItem(allow, conf_allow_dcc);
+	return 1;
+}
+
+/** Free allow dcc { } and deny dcc { } blocks, called on REHASH */
+void free_dcc_config_blocks(void)
+{
+	ConfigItem_deny_dcc *d, *d_next;
+	ConfigItem_allow_dcc *a, *a_next;
+
+	for (d = conf_deny_dcc; d; d = d_next)
+	{
+		d_next = d->next;
+		if (d->flag.type2 == CONF_BAN_TYPE_CONF)
+		{
+			safe_free(d->filename);
+			safe_free(d->reason);
+			DelListItem(d, conf_deny_dcc);
+			safe_free(d);
+		}
+	}
+	for (a = conf_allow_dcc; a; a = a_next)
+	{
+		a_next = a->next;
+		if (a->flag.type2 == CONF_BAN_TYPE_CONF)
+		{
+			safe_free(a->filename);
+			DelListItem(a, conf_allow_dcc);
+			safe_free(a);
+		}
+	}
+}
+
+/** Free all deny dcc { } blocks - only called on permanent module unload (rare) */
+void dccdeny_unload_free_all_conf_deny_dcc(ModData *m)
+{
+	ConfigItem_deny_dcc *d, *d_next;
+
+	for (d = conf_deny_dcc; d; d = d_next)
+	{
+		d_next = d->next;
+		safe_free(d->filename);
+		safe_free(d->reason);
+		DelListItem(d, conf_deny_dcc);
+		safe_free(d);
+	}
+	conf_deny_dcc = NULL;
+}
+
+/** Free all allow dcc { } blocks - only called on permanent module unload (rare) */
+void dccdeny_unload_free_all_conf_allow_dcc(ModData *m)
+{
+	ConfigItem_allow_dcc *a, *a_next;
+
+	for (a = conf_allow_dcc; a; a = a_next)
+	{
+		a_next = a->next;
+		safe_free(a->filename);
+		DelListItem(a, conf_allow_dcc);
+		safe_free(a);
+	}
+	conf_allow_dcc = NULL;
+}
+
+
+/* Add a temporary dccdeny line
+ *
+ * parv[1] - file
+ * parv[2] - reason
+ */
+CMD_FUNC(cmd_dccdeny)
+{
+	if (!MyUser(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:dccdeny",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[2]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "DCCDENY");
+		return;
+	}
+
+	if (!find_deny_dcc(parv[1]))
+	{
+		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
+	{
+		sendnotice(client, "*** %s already has a dccdeny", parv[1]);
+	}
+}
+
+/* Remove a temporary dccdeny line
+ * parv[1] - file/mask
+ */
+CMD_FUNC(cmd_undccdeny)
+{
+	ConfigItem_deny_dcc *d;
+
+	if (!MyUser(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:dccdeny",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "UNDCCDENY");
+		return;
+	}
+
+	if ((d = find_deny_dcc(parv[1])) && d->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
+	{
+		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
+	{
+		sendnotice(client, "*** Unable to find a temp dccdeny matching %s", parv[1]);
+	}
+}
+
+CMD_FUNC(cmd_svsfline)
+{
+	if (parc < 2)
+		return;
+
+	switch (*parv[1])
+	{
+		/* Allow non-U-Lines to send ONLY SVSFLINE +, but don't send it out
+		 * unless it is from a U-Line -- codemastr
+		 */
+		case '+':
+		{
+			if (parc < 4)
+				return;
+
+			if (!find_deny_dcc(parv[2]))
+				DCCdeny_add(parv[2], parv[3], DCCDENY_HARD, CONF_BAN_TYPE_AKILL);
+
+			if (IsULine(client))
+			{
+				sendto_server(client, 0, 0, NULL, ":%s SVSFLINE + %s :%s",
+				    client->id, parv[2], parv[3]);
+			}
+
+			break;
+		}
+
+		case '-':
+		{
+			ConfigItem_deny_dcc *deny;
+
+			if (!IsULine(client))
+				return;
+
+			if (parc < 3)
+				return;
+
+			if (!(deny = find_deny_dcc(parv[2])))
+				break;
+
+			DCCdeny_del(deny);
+
+			sendto_server(client, 0, 0, NULL, ":%s SVSFLINE %s", client->id, parv[2]);
+
+			break;
+		}
+
+		case '*':
+		{
+			if (!IsULine(client))
+				return;
+
+			dcc_wipe_services();
+
+			sendto_server(client, 0, 0, NULL, ":%s SVSFLINE *", client->id);
+
+			break;
+		}
+	}
+}
+
+/** Sync the DCC entries on server connect */
+int dccdeny_server_sync(Client *client)
+{
+	ConfigItem_deny_dcc *p;
+	for (p = conf_deny_dcc; p; p = p->next)
+	{
+		if (p->flag.type2 == CONF_BAN_TYPE_AKILL)
+			sendto_one(client, NULL, ":%s SVSFLINE + %s :%s", me.id,
+			    p->filename, p->reason);
+	}
+	return 0;
+}
+
+/** Check if a DCC should be blocked (user-to-user) */
+int dccdeny_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
+{
+	if (**text == '\001')
+	{
+		const char *filename = get_dcc_filename(*text);
+		if (filename)
+		{
+			if (MyUser(client) && !can_dcc(client, target->name, target, filename, errmsg))
+				return HOOK_DENY;
+			if (MyUser(target) && !can_dcc_soft(client, target, filename, errmsg))
+				return HOOK_DENY;
+		}
+	}
+
+	return HOOK_CONTINUE;
+}
+
+/** Check if a DCC should be blocked (user-to-channel, unusual) */
+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'))
+	{
+		const char *err = NULL;
+		const char *filename = get_dcc_filename(*msg);
+		if (filename && !can_dcc(client, channel->name, NULL, filename, &err))
+		{
+			strlcpy(errbuf, err, sizeof(errbuf));
+			*errmsg = errbuf;
+			return HOOK_DENY;
+		}
+	}
+	return HOOK_CONTINUE;
+}
+
+/* e->flag.type2:
+ * CONF_BAN_TYPE_AKILL		banned by SVSFLINE ('global')
+ * CONF_BAN_TYPE_CONF		banned by conf
+ * CONF_BAN_TYPE_TEMPORARY	banned by /DCCDENY ('temporary')
+ * e->flag.type:
+ * DCCDENY_HARD				Fully denied
+ * DCCDENY_SOFT				Denied, but allowed to override via /DCCALLOW
+ */
+
+/** Make a viewable dcc filename.
+ * This is to protect a bit against tricks like 'flood-it-off-the-buffer'
+ * and color 1,1 etc...
+ */
+static const char *dcc_displayfile(const char *f)
+{
+	static char buf[512];
+	const char *i;
+	char *o = buf;
+	size_t n = strlen(f);
+
+	if (n < 300)
+	{
+		for (i = f; *i; i++)
+			if (*i < 32)
+				*o++ = '?';
+			else
+				*o++ = *i;
+		*o = '\0';
+		return buf;
+	}
+
+	/* Else, we show it as: [first 256 chars]+"[..TRUNCATED..]"+[last 20 chars] */
+	for (i = f; i < f+256; i++)
+		if (*i < 32)
+			*o++ = '?';
+		else
+			*o++ = *i;
+	strcpy(o, "[..TRUNCATED..]");
+	o += sizeof("[..TRUNCATED..]");
+	for (i = f+n-20; *i; i++)
+		if (*i < 32)
+			*o++ = '?';
+		else
+			*o++ = *i;
+	*o = '\0';
+	return buf;
+}
+
+static const char *get_dcc_filename(const char *text)
+{
+	static char filename[BUFSIZE+1];
+	char *end;
+	int size_string;
+
+	if (*text != '\001')
+		return 0;
+
+	if (!strncasecmp(text+1, "DCC SEND ", 9))
+		text = text + 10;
+	else if (!strncasecmp(text+1, "DCC RESUME ", 11))
+		text = text + 12;
+	else
+		return 0;
+
+	for (; *text == ' '; text++); /* skip leading spaces */
+
+	if (*text == '"' && *(text+1))
+		end = strchr(text+1, '"');
+	else
+		end = strchr(text, ' ');
+
+	if (!end || (end < text))
+		return 0;
+
+	size_string = (int)(end - text);
+
+	if (!size_string || (size_string > (BUFSIZE - 1)))
+		return 0;
+
+	strlcpy(filename, text, size_string+1);
+	return filename;
+}
+
+/** Checks if a DCC SEND is allowed.
+ * @param client      Sending client
+ * @param target      Target name (user or channel)
+ * @param targetcli   Target client (NULL in case of channel!)
+ * @param text        The entire message
+ * @returns 1 if DCC SEND allowed, 0 if rejected
+ */
+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];
+	int size_string, ret;
+
+	/* User (IRCOp) may bypass send restrictions */
+	if (ValidatePermissionsForPath("immune:dcc",client,targetcli,NULL,NULL))
+		return 1;
+
+	/* User (IRCOp) likes to receive bad dcc's */
+	if (targetcli && ValidatePermissionsForPath("self:getbaddcc",targetcli,NULL,NULL,NULL))
+		return 1;
+
+	/* Check if user is already blocked (from the past) */
+	if (IsDCCBlock(client))
+	{
+		*errmsg = "*** You are blocked from sending files as you have tried to "
+		          "send a forbidden file - reconnect to regain ability to send";
+		return 0;
+	}
+
+	if (match_spamfilter(client, filename, SPAMF_DCC, "PRIVMSG", target, 0, NULL))
+	{
+		/* Dirty hack, yeah spamfilter already sent the error message :( */
+		*errmsg = "";
+		return 0;
+	}
+
+	if ((fl = dcc_isforbidden(client, filename)))
+	{
+		const char *displayfile = dcc_displayfile(filename);
+
+		RunHook(HOOKTYPE_DCC_DENIED, client, target, filename, displayfile, fl);
+
+		ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
+		*errmsg = errbuf;
+		SetDCCBlock(client);
+		return 0;
+	}
+
+	/* Channel dcc (???) and discouraged? just block */
+	if (!targetcli && ((fl = dcc_isdiscouraged(client, filename))))
+	{
+		ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
+		*errmsg = errbuf;
+		return 0;
+	}
+
+	/* If we get here, the file is allowed */
+	return 1;
+}
+
+/** Checks if a DCC is allowed by DCCALLOW rules (only SOFT bans are checked).
+ * PARAMETERS:
+ * from:		the sender client (possibly remote)
+ * to:			the target client (always local)
+ * text:		the whole msg
+ * RETURNS:
+ * 1:			allowed
+ * 0:			block
+ */
+static int can_dcc_soft(Client *from, Client *to, const char *filename, const char **errmsg)
+{
+	ConfigItem_deny_dcc *fl;
+	const char *displayfile;
+	static char errbuf[256];
+
+	/* User (IRCOp) may bypass send restrictions */
+	if (ValidatePermissionsForPath("immune:dcc",from,to,NULL,NULL))
+		return 1;
+
+	/* User (IRCOp) likes to receive bad dcc's */
+	if (ValidatePermissionsForPath("self:getbaddcc",to,NULL,NULL,NULL))
+		return 1;
+
+	/* On the 'soft' blocklist ? */
+	if (!(fl = dcc_isdiscouraged(from, filename)))
+		return 1; /* No, so is OK */
+
+	/* If on DCCALLOW list then the user is OK with it */
+	if (on_dccallow_list(to, from))
+		return 1;
+
+	/* Soft-blocked */
+	displayfile = dcc_displayfile(filename);
+
+	ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
+	*errmsg = errbuf;
+
+	/* Inform target ('to') about the /DCCALLOW functionality */
+	sendnotice(to, "%s (%s@%s) tried to DCC SEND you a file named '%s', the request has been blocked.",
+		from->name, from->user->username, GetHost(from), displayfile);
+	if (!IsDCCNotice(to))
+	{
+		SetDCCNotice(to);
+		sendnotice(to, "Files like these might contain malicious content (viruses, trojans). "
+			"Therefore, you must explicitly allow anyone that tries to send you such files.");
+		sendnotice(to, "If you trust %s, and want him/her to send you this file, you may obtain "
+			"more information on using the dccallow system by typing '/DCCALLOW HELP'", from->name);
+	}
+	return 0;
+}
+
+/** Checks if the dcc is blacklisted. */
+static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, const char *filename)
+{
+	ConfigItem_deny_dcc *d;
+	ConfigItem_allow_dcc *a;
+
+	if (!conf_deny_dcc || !filename)
+		return NULL;
+
+	for (d = conf_deny_dcc; d; d = d->next)
+	{
+		if ((d->flag.type == DCCDENY_HARD) && match_simple(d->filename, filename))
+		{
+			for (a = conf_allow_dcc; a; a = a->next)
+			{
+				if ((a->flag.type == DCCDENY_HARD) && match_simple(a->filename, filename))
+					return NULL;
+			}
+			return d;
+		}
+	}
+
+	return NULL;
+}
+
+/** checks if the dcc is discouraged ('soft bans'). */
+static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, const char *filename)
+{
+	ConfigItem_deny_dcc *d;
+	ConfigItem_allow_dcc *a;
+
+	if (!conf_deny_dcc || !filename)
+		return NULL;
+
+	for (d = conf_deny_dcc; d; d = d->next)
+	{
+		if ((d->flag.type == DCCDENY_SOFT) && match_simple(d->filename, filename))
+		{
+			for (a = conf_allow_dcc; a; a = a->next)
+			{
+				if ((a->flag.type == DCCDENY_SOFT) && match_simple(a->filename, filename))
+					return NULL;
+			}
+			return d;
+		}
+	}
+
+	return NULL;
+}
+
+static void DCCdeny_add(const char *filename, const char *reason, int type, int type2)
+{
+	ConfigItem_deny_dcc *deny = NULL;
+
+	deny = safe_alloc(sizeof(ConfigItem_deny_dcc));
+	safe_strdup(deny->filename, filename);
+	safe_strdup(deny->reason, reason);
+	deny->flag.type = type;
+	deny->flag.type2 = type2;
+	AddListItem(deny, conf_deny_dcc);
+}
+
+static void DCCdeny_del(ConfigItem_deny_dcc *deny)
+{
+	DelListItem(deny, conf_deny_dcc);
+	safe_free(deny->filename);
+	safe_free(deny->reason);
+	safe_free(deny);
+}
+
+ConfigItem_deny_dcc *find_deny_dcc(const char *name)
+{
+	ConfigItem_deny_dcc	*e;
+
+	if (!name)
+		return NULL;
+
+	for (e = conf_deny_dcc; e; e = e->next)
+	{
+		if (match_simple(name, e->filename))
+			return e;
+	}
+	return NULL;
+}
+
+static void dcc_wipe_services(void)
+{
+	ConfigItem_deny_dcc *dconf, *next;
+
+	for (dconf = conf_deny_dcc; dconf; dconf = next)
+	{
+		next = dconf->next;
+		if (dconf->flag.type2 == CONF_BAN_TYPE_AKILL)
+		{
+			DelListItem(dconf, conf_deny_dcc);
+			safe_free(dconf->filename);
+			safe_free(dconf->reason);
+			safe_free(dconf);
+		}
+	}
+
+}
+
+int dccdeny_stats(Client *client, const char *para)
+{
+	ConfigItem_deny_dcc *denytmp;
+	ConfigItem_allow_dcc *allowtmp;
+	char *filemask, *reason;
+	char a = 0;
+
+	/* '/STATS F' or '/STATS denydcc' is for us... */
+	if (strcmp(para, "F") && strcasecmp(para, "denydcc"))
+		return 0;
+
+	for (denytmp = conf_deny_dcc; denytmp; denytmp = denytmp->next)
+	{
+		filemask = BadPtr(denytmp->filename) ? "<NULL>" : denytmp->filename;
+		reason = BadPtr(denytmp->reason) ? "<NULL>" : denytmp->reason;
+		if (denytmp->flag.type2 == CONF_BAN_TYPE_CONF)
+			a = 'c';
+		if (denytmp->flag.type2 == CONF_BAN_TYPE_AKILL)
+			a = 's';
+		if (denytmp->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
+			a = 'o';
+		/* <d> <s|h> <howadded> <filemask> <reason> */
+		sendtxtnumeric(client, "d %c %c %s %s", (denytmp->flag.type == DCCDENY_SOFT) ? 's' : 'h',
+			a, filemask, reason);
+	}
+	for (allowtmp = conf_allow_dcc; allowtmp; allowtmp = allowtmp->next)
+	{
+		filemask = BadPtr(allowtmp->filename) ? "<NULL>" : allowtmp->filename;
+		if (allowtmp->flag.type2 == CONF_BAN_TYPE_CONF)
+			a = 'c';
+		if (allowtmp->flag.type2 == CONF_BAN_TYPE_AKILL)
+			a = 's';
+		if (allowtmp->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
+			a = 'o';
+		/* <a> <s|h> <howadded> <filemask> */
+		sendtxtnumeric(client, "a %c %c %s", (allowtmp->flag.type == DCCDENY_SOFT) ? 's' : 'h',
+			a, filemask);
+	}
+	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/ircd/src/modules/echo-message.c b/ircd/src/modules/echo-message.c
@@ -0,0 +1,107 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/echo-message.c
+ *   (C) 2019 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"echo-message",
+	"5.0",
+	"echo-message CAP",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_ECHO_MESSAGE = 0L;
+
+/* Forward declarations */
+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()
+{
+	ClientCapabilityInfo cap;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "echo-message";
+	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_ECHO_MESSAGE);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CHANMSG, 0, em_chanmsg);
+	HookAdd(modinfo->handle, HOOKTYPE_USERMSG, 0, em_usermsg);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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))
+	{
+		if (sendtype != SEND_TYPE_TAGMSG)
+		{
+			sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				target,
+				text);
+		} else {
+			sendto_prefix_one(client, client, mtags, ":%s %s %s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				target);
+		}
+	}
+	return 0;
+}
+
+int em_usermsg(Client *client, Client *to, MessageTag *mtags, const char *text, SendType sendtype)
+{
+	if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
+	{
+		if (sendtype != SEND_TYPE_TAGMSG)
+		{
+			sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				to->name,
+				text);
+		} else {
+			sendto_prefix_one(client, client, mtags, ":%s %s %s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				to->name);
+		}
+	}
+	return 0;
+}
diff --git a/ircd/src/modules/eos.c b/ircd/src/modules/eos.c
@@ -0,0 +1,71 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_eos);
+
+#define MSG_EOS 	"EOS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"eos",
+	"5.0",
+	"command /eos", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_EOS, cmd_eos, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * EOS (End Of Sync) command.
+ * Type: Broadcast
+ * Purpose: Broadcasted over a network if a server is synced (after the users, channels,
+ *          etc are introduced). Makes us able to know if a server is linked.
+ * History: Added in beta18 (in cvs since 2003-08-11) by Syzop
+ */
+CMD_FUNC(cmd_eos)
+{
+	if (!IsServer(client))
+		return;
+	client->server->flags.synced = 1;
+	/* pass it on ^_- */
+	sendto_server(client, 0, 0, NULL, ":%s EOS", client->id);
+
+	RunHook(HOOKTYPE_SERVER_SYNCED, client);
+}
diff --git a/ircd/src/modules/extbans/Makefile.in b/ircd/src/modules/extbans/Makefile.in
@@ -0,0 +1,56 @@
+#************************************************************************
+#*   IRC - Internet Relay Chat, src/modules/chanmodes/Makefile
+#*   Copyright (C) Carsten V. Munk 2001 & The UnrealIRCd Team
+#*
+#*   This program is free software; you can redistribute it and/or modify
+#*   it under the terms of the GNU General Public License as published by
+#*   the Free Software Foundation; either version 1, or (at your option)
+#*   any later version.
+#*
+#*   This program is distributed in the hope that it will be useful,
+#*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#*   GNU General Public License for more details.
+#*
+#*   You should have received a copy of the GNU General Public License
+#*   along with this program; if not, write to the Free Software
+#*   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#*/
+
+CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
+
+INCLUDES = ../../include/channel.h \
+	../../include/common.h ../../include/config.h ../../include/dbuf.h \
+	../../include/dynconf.h ../../include/fdlist.h ../../include/h.h \
+	../../include/ircsprintf.h \
+	../../include/license.h \
+	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
+	../../include/numeric.h ../../include/dns.h \
+	../../include/resource.h ../../include/setup.h \
+	../../include/struct.h ../../include/sys.h \
+	../../include/types.h \
+	../../include/version.h ../../include/whowas.h
+
+R_MODULES= \
+	join.so quiet.so nickchange.so inchannel.so realname.so \
+	account.so operclass.so certfp.so textban.so msgbypass.so \
+	timedban.so partmsg.so securitygroup.so \
+	country.so flood.so
+
+MODULES=$(R_MODULES)
+MODULEFLAGS=@MODULEFLAGS@
+RM=@RM@
+
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
+all: build
+
+build: $(MODULES)
+
+clean:
+	$(RM) -f *.o *.so *~ core
+
+%.so: %.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o $@ $<
diff --git a/ircd/src/modules/extbans/account.c b/ircd/src/modules/extbans/account.c
@@ -0,0 +1,119 @@
+/*
+ * Extended ban to ban/exempt by services account (~b ~a:accountname)
+ * (C) Copyright 2011-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/account",
+	"4.2",
+	"ExtBan ~a - Ban/exempt by services account name",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+const char *extban_account_conv_param(BanContext *b, Extban *extban);
+int extban_account_is_banned(BanContext *b);
+
+Extban *register_account_extban(ModuleInfo *modinfo)
+{
+	ExtbanInfo req;
+
+	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;
+	return ExtbanAdd(modinfo->handle, req);
+}
+
+/** Called upon module test */
+MOD_TEST()
+{
+	if (!register_account_extban(modinfo))
+	{
+		config_error("could not register extended ban type");
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+/** Called upon module init */
+MOD_INIT()
+{
+	if (!register_account_extban(modinfo))
+	{
+		config_error("could not register extended ban type");
+		return MOD_FAILED;
+	}
+
+	ISupportAdd(modinfo->handle, "ACCOUNTEXTBAN", "account,a");
+
+	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;
+}
+
+/** Account bans */
+const char *extban_account_conv_param(BanContext *b, Extban *extban)
+{
+	char *mask, *acc;
+	static char retbuf[NICKLEN + 4];
+
+	strlcpy(retbuf, b->banstr, sizeof(retbuf)); /* truncate */
+
+	acc = retbuf;
+	if (!*acc)
+		return NULL; /* don't allow "~a:" */
+
+	return retbuf;
+}
+
+int extban_account_is_banned(BanContext *b)
+{
+	/* ~a:0 is special and matches all unauthenticated users */
+	if (!strcmp(b->banstr, "0"))
+		return IsLoggedIn(b->client) ? 0 : 1;
+
+	/* ~a:* matches all authenticated users
+	 * (Yes this special code is needed because account
+	 *  is 0 or * for unauthenticated users)
+	 */
+	if (!strcmp(b->banstr, "*"))
+		return IsLoggedIn(b->client) ? 1 : 0;
+
+	if (b->client->user && match_simple(b->banstr, b->client->user->account))
+		return 1;
+
+	return 0;
+}
diff --git a/ircd/src/modules/extbans/certfp.c b/ircd/src/modules/extbans/certfp.c
@@ -0,0 +1,142 @@
+/*
+ * Extended ban to ban/exempt by certificate fingerprint (+b ~S:certfp)
+ * (C) Copyright 2015 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/certfp",
+	"4.2",
+	"ExtBan ~S - Ban/exempt by SHA256 TLS certificate fingerprint",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int extban_certfp_is_ok(BanContext *b);
+const char *extban_certfp_conv_param(BanContext *b, Extban *extban);
+int extban_certfp_is_banned(BanContext *b);
+
+Extban *register_certfp_extban(ModuleInfo *modinfo)
+{
+	ExtbanInfo req;
+
+	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;
+	return ExtbanAdd(modinfo->handle, req);
+}
+
+/* Called upon module test */
+MOD_TEST()
+{
+	if (!register_certfp_extban(modinfo))
+	{
+		config_error("could not register extended ban type");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+/* Called upon module init */
+MOD_INIT()
+{
+	if (!register_certfp_extban(modinfo))
+	{
+		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;
+}
+
+#define CERT_FP_LEN 64
+
+int extban_certfp_usage(Client *client)
+{
+	sendnotice(client, "ERROR: ExtBan ~S expects an SHA256 fingerprint in hexadecimal format (no colons). "
+					 "For example: +e ~S:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)");
+	return EX_DENY;
+}
+
+int extban_certfp_is_ok(BanContext *b)
+{
+	if (b->is_ok_check == EXCHK_PARAM)
+	{
+		const char *p;
+
+		if (strlen(b->banstr) != CERT_FP_LEN)
+			return extban_certfp_usage(b->client);
+
+		for (p = b->banstr; *p; p++)
+			if (!isxdigit(*p))
+				return extban_certfp_usage(b->client);
+
+		return EX_ALLOW;
+	}
+	return EX_ALLOW;
+}
+
+/* Obtain targeted certfp from the ban string */
+const char *extban_certfp_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 = tolower(*p);
+	}
+
+	return retbuf;
+}
+
+int extban_certfp_is_banned(BanContext *b)
+{
+	const char *fp = moddata_client_get(b->client, "certfp");
+
+	if (!fp)
+		return 0; /* not using TLS */
+
+	if (!strcmp(b->banstr, fp))
+		return 1;
+
+	return 0;
+}
diff --git a/ircd/src/modules/extbans/country.c b/ircd/src/modules/extbans/country.c
@@ -0,0 +1,141 @@
+/*
+ * 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);
+
+Extban *register_country_extban(ModuleInfo *modinfo)
+{
+	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;
+	return ExtbanAdd(modinfo->handle, req);
+}
+
+/* Called upon module test */
+MOD_TEST()
+{
+	if (!register_country_extban(modinfo))
+	{
+		config_error("could not register extended ban type");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+/* Called upon module init */
+MOD_INIT()
+{
+	if (!register_country_extban(modinfo))
+	{
+		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/ircd/src/modules/extbans/flood.c b/ircd/src/modules/extbans/flood.c
@@ -0,0 +1,187 @@
+/*
+ * Extended ban to exempt from +f/+F checking.
+ * Eg: +e ~flood:*:~account:TrustedBot
+ * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/flood",
+	"1.0",
+	"Extban ~flood - exempt from +f/+F checks",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/** Maximum length of the ~flood ban exemption */
+#define MAX_FLOODBAN_LENGTH 128
+
+/* Forward declarations */
+int extban_flood_is_banned(BanContext *b);
+int flood_extban_is_ok(BanContext *b);
+const char *flood_extban_conv_param(BanContext *b, Extban *extban);
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+
+	memset(&req, 0, sizeof(req));
+	req.letter = 'F';
+	req.name = "flood";
+	req.is_ok = flood_extban_is_ok;
+	req.conv_param = flood_extban_conv_param;
+	req.options = EXTBOPT_ACTMODIFIER;
+	if (!ExtbanAdd(modinfo->handle, req))
+	{
+		config_error("could not register extended ban type 'flood'");
+		return MOD_FAILED;
+	}
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	return MOD_SUCCESS;
+}
+
+/** Called upon module load */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Called upon unload */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Check if letters in 'str' are valid flood-types.
+ * TODO: ideally this would call a function in chanmode +F module!!
+ */
+static int flood_type_ok(char *str)
+{
+	char *p;
+
+	/* The * (asterisk) simply means ALL. */
+	if (!strcmp(str, "*"))
+		return 1;
+
+	for (p = str; *p; p++)
+		if (!strchr("cjkmntr", *p))
+			return 0;
+
+	return 1;
+}
+
+const char *flood_extban_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[MAX_FLOODBAN_LENGTH+1];
+	char para[MAX_FLOODBAN_LENGTH+1];
+	char tmpmask[MAX_FLOODBAN_LENGTH+1];
+	char *type; /**< Type(s), such as 'j' */
+	char *matchby; /**< Matching method, such as 'n!u@h' */
+	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
+
+	/* ~flood:type:n!u@h   for direct matching
+	 * ~flood:type:~x:.... when calling another bantype
+	 */
+
+	type = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return NULL;
+	*matchby++ = '\0';
+
+	/* don't verify type(s), already done in is_ok for local clients */
+	//if (!flood_type_ok(type))
+	//	return NULL;
+	/* ... but limit them to a reasonable value :D */
+	if (strlen(type) > 16)
+		return NULL;
+
+	b->banstr = matchby;
+	newmask = extban_conv_param_nuh_or_extban(b, extban);
+	if (BadPtr(newmask))
+		return NULL;
+
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", type, newmask);
+	return retbuf;
+}
+
+int flood_extban_syntax(Client *client, int checkt, char *reason)
+{
+	if (MyUser(client) && (checkt == EXBCHK_PARAM))
+	{
+		sendnotice(client, "Error when setting ban exception: %s", reason);
+		sendnotice(client, " Syntax: +e ~flood:floodtype(s):mask");
+		sendnotice(client, "Example: +e ~flood:*:~account:TrustedUser");
+		sendnotice(client, "Valid flood types are: c, j, k, m, n, t, r, and * for all");
+		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~account, ~certfp, etc.");
+	}
+	return 0; /* FAIL: ban rejected */
+}
+
+int flood_extban_is_ok(BanContext *b)
+{
+	static char para[MAX_FLOODBAN_LENGTH+1];
+	char *type; /**< Type(s), such as 'j' */
+	char *matchby; /**< Matching method, such as 'n!u@h' */
+	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+
+	/* Always permit deletion */
+	if (b->what == MODE_DEL)
+		return 1;
+
+	if (b->ban_type != EXBTYPE_EXCEPT)
+	{
+		if (b->is_ok_check == EXBCHK_PARAM)
+			sendnotice(b->client, "Ban type ~flood only works with exceptions (+e) and not with bans or invex (+b/+I)");
+		return 0; /* reject */
+	}
+
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
+
+	/* ~flood:type:n!u@h   for direct matching
+	 * ~flood:type:~x:.... when calling another bantype
+	 */
+
+	type = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return flood_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
+	*matchby++ = '\0';
+
+	if (!flood_type_ok(type))
+		return flood_extban_syntax(b->client, b->is_ok_check, "Unknown flood type");
+	if (strlen(type) > 16)
+		return flood_extban_syntax(b->client, b->is_ok_check, "Too many flood types specified");
+
+	b->banstr = matchby;
+	if (extban_is_ok_nuh_extban(b) == 0)
+	{
+		/* This could be anything ranging from:
+		 * invalid n!u@h syntax, unknown (sub)extbantype,
+		 * disabled extban type in conf, too much recursion, etc.
+		 */
+		return flood_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
+	}
+
+	return 1; /* OK */
+}
diff --git a/ircd/src/modules/extbans/inchannel.c b/ircd/src/modules/extbans/inchannel.c
@@ -0,0 +1,168 @@
+/*
+ * Extended ban: "in channel?" (+b ~c:#chan)
+ * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/inchannel",
+	"4.2",
+	"ExtBan ~c - banned when in specified channel",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+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;
+	
+	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))
+	{
+		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;
+}
+
+const char *extban_inchannel_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[CHANNELLEN+6];
+	char *chan, *p, symbol='\0';
+
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
+	chan = retbuf;
+
+	if ((*chan == '+') || (*chan == '%') || (*chan == '%') ||
+	    (*chan == '@') || (*chan == '&') || (*chan == '~'))
+	    chan++;
+
+	if ((*chan != '#') && (*chan != '*') && (*chan != '?'))
+		return NULL;
+
+	if (!valid_channelname(chan))
+		return NULL;
+
+	p = strchr(chan, ':'); /* ~r:#chan:*.blah.net is not allowed (for now) */
+	if (p)
+		*p = '\0';
+
+	/* on a sidenote '#' is allowed because it's a valid channel (atm) */
+	return retbuf;
+}
+
+/* The only purpose of this function is a temporary workaround to prevent a desync.. pfff */
+int extban_inchannel_is_ok(BanContext *b)
+{
+	const char *p = b->banstr;
+
+	if ((b->is_ok_check == EXBCHK_PARAM) && MyUser(b->client) && (b->what == MODE_ADD) && (strlen(b->banstr) > 3))
+	{
+		if ((*p == '+') || (*p == '%') || (*p == '%') ||
+		    (*p == '@') || (*p == '&') || (*p == '~'))
+		    p++;
+
+		if (*p != '#')
+		{
+			sendnotice(b->client, "Please use a # in the channelname (eg: ~c:#*blah*)");
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static int extban_inchannel_compareflags(char symbol, const char *member_modes)
+{
+	const char *required_modes = NULL;
+
+	if (symbol == '+')
+		required_modes = "vhoaq";
+	else if (symbol == '%')
+		required_modes = "hoaq";
+	else if (symbol == '@')
+		required_modes = "oaq";
+	else if (symbol == '&')
+		required_modes = "aq";
+	else if (symbol == '~')
+		required_modes = "q";
+	else
+		return 0; /* unknown prefix character */
+
+	if (check_channel_access_string(member_modes, required_modes))
+		return 1;
+
+	return 0;
+}
+
+int extban_inchannel_is_banned(BanContext *b)
+{
+	Membership *lp;
+	const char *p = b->banstr;
+	char symbol = '\0';
+
+	if (*p != '#')
+	{
+		symbol = *p;
+		p++;
+	}
+
+	for (lp = b->client->user->channel; lp; lp = lp->next)
+	{
+		if (match_esc(p, lp->channel->name))
+		{
+			/* Channel matched, check symbol if needed (+/%/@/etc) */
+			if (symbol)
+			{
+				if (extban_inchannel_compareflags(symbol, lp->member_modes))
+					return 1;
+			} else
+				return 1;
+		}
+	}
+
+	return 0;
+}
+
diff --git a/ircd/src/modules/extbans/join.c b/ircd/src/modules/extbans/join.c
@@ -0,0 +1,73 @@
+/*
+ * Extended ban that affects JOIN only (+b ~j)
+ * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/join",
+	"4.2",
+	"Extban ~j - prevent join only",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int extban_modej_is_banned(BanContext *b);
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+	
+	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))
+	{
+		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;
+}
+
+/** This ban that affects JOINs only */
+int extban_modej_is_banned(BanContext *b)
+{
+	return ban_check_mask(b);
+}
diff --git a/ircd/src/modules/extbans/msgbypass.c b/ircd/src/modules/extbans/msgbypass.c
@@ -0,0 +1,224 @@
+/*
+ * Extended ban that allows user to bypass message restrictions
+ * (C) Copyright 2017-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/msgbypass",
+	"4.2",
+	"ExtBan ~m - bypass +m/+n/+c/+S/+T (msgbypass)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int msgbypass_can_bypass(Client *client, Channel *channel, BypassChannelMessageRestrictionType bypass_type);
+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;
+	
+	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.options = EXTBOPT_ACTMODIFIER;
+	if (!ExtbanAdd(modinfo->handle, req))
+	{
+		config_error("could not register extended ban type ~m");
+		return MOD_FAILED;
+	}
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	return MOD_SUCCESS;
+}
+
+/** Called upon module load */
+MOD_LOAD()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION, 0, msgbypass_can_bypass);
+	return MOD_SUCCESS;
+}
+
+/** Called upon unload */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** 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)))
+		{
+			matchby = strchr(type, ':');
+			if (!matchby)
+				continue;
+			matchby++;
+			
+			b->banstr = matchby;
+			if (ban_check_mask(b))
+			{
+				safe_free(b);
+				return HOOK_ALLOW; /* Yes, user may bypass */
+			}
+		}
+	}
+
+	safe_free(b);
+	return HOOK_CONTINUE; /* No, may NOT bypass. */
+}
+
+/** Does this bypass type exist? (eg: 'external') */
+int msgbypass_extban_type_ok(char *type)
+{
+	if (!strcmp(type, "external") ||
+	    !strcmp(type, "moderated") ||
+	    !strcmp(type, "censor") ||
+	    !strcmp(type, "color") ||
+	    !strcmp(type, "notice"))
+	{
+		return 1; /* Yes, OK type */
+	}
+	return 0; /* NOMATCH */
+}
+
+#define MAX_LENGTH 128
+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' */
+	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+
+	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
+	 */
+
+	type = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return NULL;
+	*matchby++ = '\0';
+
+	if (!msgbypass_extban_type_ok(type))
+		return NULL;
+
+	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);
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", type, newmask);
+	return retbuf;
+}
+
+int msgbypass_extban_syntax(Client *client, int checkt, char *reason)
+{
+	if (MyUser(client) && (checkt == EXBCHK_PARAM))
+	{
+		sendnotice(client, "Error when setting ban exception: %s", reason);
+		sendnotice(client, " Syntax: +e ~m:type:mask");
+		sendnotice(client, "Example: +e ~m:moderated:~a:TrustedUser");
+		sendnotice(client, "Valid types are: external, moderated, color, notice");
+		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
+	}
+	return 0; /* FAIL: ban rejected */
+}
+
+int msgbypass_extban_is_ok(BanContext *b)
+{
+	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 (b->what == MODE_DEL)
+		return 1;
+	
+	if (b->ban_type != EXBTYPE_EXCEPT)
+	{
+		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, 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
+	 */
+
+	type = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
+	*matchby++ = '\0';
+
+	if (!msgbypass_extban_type_ok(type))
+		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Unknown type");
+
+	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(b->client, b->is_ok_check, "Invalid matcher");
+	}
+
+	return 1; /* OK */
+}
+
diff --git a/ircd/src/modules/extbans/nickchange.c b/ircd/src/modules/extbans/nickchange.c
@@ -0,0 +1,76 @@
+/*
+ * Extended ban that affects nick-changes only (+b ~n)
+ * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/nickchange",
+	"4.2",
+	"ExtBan ~n - prevent nick-changes only",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int extban_nickchange_is_banned(BanContext *b);
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+	
+	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))
+	{
+		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;
+}
+
+/** This ban that affects nick-changes only */
+int extban_nickchange_is_banned(BanContext *b)
+{
+	if (check_channel_access(b->client, b->channel, "v"))
+		return 0;
+
+	return ban_check_mask(b);
+}
diff --git a/ircd/src/modules/extbans/operclass.c b/ircd/src/modules/extbans/operclass.c
@@ -0,0 +1,101 @@
+/*
+ * Extended ban type: ban (or rather: exempt or invex) an operclass.
+ * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/operclass",
+	"4.2",
+	"ExtBan ~O - Ban/exempt operclass",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+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;
+	
+	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))
+	{
+		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;
+}
+
+
+#define OPERCLASSLEN 64
+
+const char *extban_operclass_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[OPERCLASSLEN + 4];
+	char *p;
+
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
+
+	/* allow alpha, numeric, -, _, * and ? wildcards */
+	for (p = retbuf; *p; p++)
+		if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_?*", *p))
+			*p = '\0';
+
+	if (retbuf[3] == '\0')
+		return NULL; /* just "~O:" is invalid */
+
+	return retbuf;
+}
+
+int extban_operclass_is_banned(BanContext *b)
+{
+	if (MyUser(b->client) && IsOper(b->client))
+	{
+		const char *operclass = get_operclass(b->client);
+		if (operclass && match_simple(b->banstr, operclass))
+			return 1;
+	}
+
+	return 0;
+}
diff --git a/ircd/src/modules/extbans/partmsg.c b/ircd/src/modules/extbans/partmsg.c
@@ -0,0 +1,74 @@
+/*
+ * Hide Part/Quit message extended ban (+b ~p:nick!user@host)
+ * (C) Copyright i <info@servx.org> and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/partmsg",
+	"4.2",
+	"ExtBan ~p - Ban/exempt Part/Quit message",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+int extban_partmsg_is_banned(BanContext *b);
+
+MOD_INIT()
+{
+	ExtbanInfo req;
+
+	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");
+		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_partmsg_is_banned(BanContext *b)
+{
+	b->msg = NULL;
+	// Uh.. there is no attempt to match.... anything.......?
+
+	return 0;
+}
diff --git a/ircd/src/modules/extbans/quiet.c b/ircd/src/modules/extbans/quiet.c
@@ -0,0 +1,73 @@
+/*
+ * Extended ban that affects messages/notices only (+b ~q)
+ * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/quiet",
+	"4.2",
+	"ExtBan ~q - prevent messages only (quiet)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int extban_quiet_is_banned(BanContext *b);
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+	
+	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))
+	{
+		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;
+}
+
+/** This ban that affects messages/notices only */
+int extban_quiet_is_banned(BanContext *b)
+{
+	return ban_check_mask(b);
+}
diff --git a/ircd/src/modules/extbans/realname.c b/ircd/src/modules/extbans/realname.c
@@ -0,0 +1,114 @@
+/*
+ * Extended ban to ban based on real name / gecos field (+b ~r)
+ * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/realname",
+	"4.2",
+	"ExtBan ~r - Ban based on realname/gecos field",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+const char *extban_realname_conv_param(BanContext *b, Extban *extban);
+int extban_realname_is_banned(BanContext *b);
+
+Extban *register_realname_extban(ModuleInfo *modinfo)
+{
+	ExtbanInfo req;
+
+	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_INVEX|EXTBOPT_TKL;
+	return ExtbanAdd(modinfo->handle, req);
+}
+
+/** Called upon module test */
+MOD_TEST()
+{
+	if (!register_realname_extban(modinfo))
+	{
+		config_error("could not register extended ban type");
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+/** Called upon module init */
+MOD_INIT()
+{
+	if (!register_realname_extban(modinfo))
+	{
+		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;
+}
+
+/** Realname bans - conv_param */
+const char *extban_realname_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[REALLEN + 8];
+	char *mask;
+
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
+
+	mask = retbuf;
+
+	if (!*mask)
+		return NULL; /* don't allow "~r:" */
+
+	if (strlen(mask) > REALLEN)
+		mask[REALLEN] = '\0';
+
+	/* Prevent otherwise confusing extban relationship */
+	if (*mask == '~')
+		*mask = '?';
+
+	return retbuf;
+}
+
+int extban_realname_is_banned(BanContext *b)
+{
+	if (match_esc(b->banstr, b->client->info))
+		return 1;
+
+	return 0;
+}
diff --git a/ircd/src/modules/extbans/securitygroup.c b/ircd/src/modules/extbans/securitygroup.c
@@ -0,0 +1,152 @@
+/*
+ * Extended ban to ban based on security groups such as "unknown-users"
+ * (C) Copyright 2020 Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/securitygroup",
+	"4.2",
+	"ExtBan ~G - Ban based on security-group",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban);
+int extban_securitygroup_is_ok(BanContext *b);
+int extban_securitygroup_is_banned(BanContext *b);
+
+Extban *register_securitygroup_extban(ModuleInfo *modinfo)
+{
+	ExtbanInfo req;
+
+	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;
+	return ExtbanAdd(modinfo->handle, req);
+}
+
+/** Called upon module test */
+MOD_TEST()
+{
+	if (!register_securitygroup_extban(modinfo))
+	{
+		config_error("could not register extended ban type ~G");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+/** Called upon module init */
+MOD_INIT()
+{
+	if (!register_securitygroup_extban(modinfo))
+	{
+		config_error("could not register extended ban type ~G");
+		return MOD_FAILED;
+	}
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	return MOD_SUCCESS;
+}
+
+/** Called upon module load */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Called upon unload */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Helper function for extban_securitygroup_is_ok() and extban_securitygroup_conv_param()
+ * to do ban validation.
+ */
+int extban_securitygroup_generic(char *mask, int strict)
+{
+	/* ! at the start means negative match */
+	if (*mask == '!')
+		mask++;
+
+	/* Check if the rest of the security group name is valid */
+	if (strict)
+	{
+		if (!security_group_exists(mask))
+			return 0; /* security group does not exist */
+	} else {
+		if (!security_group_valid_name(mask))
+			return 0; /* invalid characters or too long */
+	}
+
+	if (!*mask)
+		return 0; /* don't allow "~G:" nor "~G:!" */
+
+	return 1;
+}
+
+int extban_securitygroup_is_ok(BanContext *b)
+{
+	if (MyUser(b->client) && (b->what == MODE_ADD) && (b->is_ok_check == EXBCHK_PARAM))
+	{
+		char banbuf[SECURITYGROUPLEN+8];
+		strlcpy(banbuf, b->banstr, sizeof(banbuf));
+		if (!extban_securitygroup_generic(banbuf, 1))
+		{
+			SecurityGroup *s;
+			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(b->client, "%s", s->name);
+			sendnotice(b->client, "unknown-users");
+			sendnotice(b->client, "End of security group list.");
+			return 0;
+		}
+	}
+	return 1;
+}
+
+/** Security group extban - conv_param */
+const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[SECURITYGROUPLEN + 8];
+
+	strlcpy(retbuf, b->banstr, sizeof(retbuf));
+	if (!extban_securitygroup_generic(retbuf, 0))
+		return NULL;
+
+	return retbuf;
+}
+
+/** Is the user banned by ~G:something ? */
+int extban_securitygroup_is_banned(BanContext *b)
+{
+	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/ircd/src/modules/extbans/textban.c b/ircd/src/modules/extbans/textban.c
@@ -0,0 +1,533 @@
+/*
+ * Text ban. (C) Copyright 2004-2016 Bram Matthys.
+ * 
+ * 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 2
+ * of the License, 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "unrealircd.h"
+
+/** Max number of text bans per channel.
+ * This is basically the most important setting. It directly affects
+ * how much CPU you want to spend on text processing.
+ * For comparison: with 10 textbans of max length (150), and messages said in
+ * the channel with max length (~500 bytes), on an A1800+ (1.53GHz) machine
+ * this consumes 30 usec per-channel message PEAK/MAX (usec = 1/1000000 of a
+ * second), and in normal (non-supersize messages) count on 10-15 usec.
+ * Basically this means this allows for like >25000 messages per second at
+ * 100% CPU usage in a worth case scenario. Which seems by far sufficient to me.
+ * Also note that (naturally) only local clients are processed, only people
+ * that do not have halfops or higher, and only channels that have any
+ * textbans set.
+ * UPDATE: The speed impact for 15 bans per channel is 42 usec PEAK.
+ * HINT: If you are hitting the "normal banlimit" before you actually hit this
+ *       one, then you might want to tweak the #define MAXBANS and #define
+ *       MAXBANLENGTH in include/struct.h. Doubling MAXBANLENGTH is usually
+ *       a good idea, and then you can enlarge MAXBANS too a bit if you want to.
+ */
+#define MAX_EXTBANT_PER_CHAN     15 /* Max number of ~T bans in a channel. */
+
+/** Max length of a ban.
+ * NOTE: This is mainly for 'cosmetic' purposes. Lowering it does not
+ *       decrease CPU usage for text processing.
+ */
+#define MAX_LENGTH               150 /* Max length of a ban */
+
+/** Allow user@host in the textban? This changes the syntax! */
+#undef UHOSTFEATURE
+
+/** Enable 'censor' support. What this type will do is replace the
+ * matched word with "<censored>" (or another word, see later)
+ * Like:
+ * <Idiot> hey check out my fucking new car
+ * will become:
+ * <Idiot> hey check out my <censored> new car
+ *
+ * SPEED: See README
+ */
+#define CENSORFEATURE
+
+/** Which censor replace word to use when CENSORFEATURE is enabled. */
+#define CENSORWORD "<censored>"
+
+ModuleHeader MOD_HEADER
+  = {
+	"extbans/textban",
+	"2.2",
+	"ExtBan ~T (textban) by Syzop",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+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()
+{
+	ExtbanInfo req;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&req, 0, sizeof(ExtbanInfo));
+	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_ok = extban_modeT_is_ok;
+
+	if (!ExtbanAdd(modinfo->handle, req))
+	{
+		config_error("textban module: adding extban ~T failed! module NOT loaded");
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, textban_can_send_to_channel);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+#if defined(CENSORFEATURE) || defined(STRIPFEATURE)
+static char *my_strcasestr(char *haystack, char *needle)
+{
+	int i;
+	int nlength = strlen (needle);
+	int hlength = strlen (haystack);
+
+	if (nlength > hlength)
+		return NULL;
+	if (hlength <= 0)
+		return NULL;
+	if (nlength <= 0)
+		return haystack;
+	for (i = 0; i <= (hlength - nlength); i++)
+	{
+		if (strncasecmp (haystack + i, needle, nlength) == 0)
+			return haystack + i;
+	}
+	return NULL; /* not found */
+}
+
+#define TEXTBAN_WORD_LEFT	0x1
+#define TEXTBAN_WORD_RIGHT	0x2
+
+/* textban_replace:
+ * a fast replace routine written by Syzop used for replacing.
+ * searches in line for huntw and replaces it with replacew,
+ * buf is used for the result and max is sizeof(buf).
+ * (Internal assumptions: size of 'buf' is 512 characters or more)
+ */
+int textban_replace(int type, char *badword, char *line, char *buf)
+{
+	char *replacew;
+	char *pold = line, *pnew = buf; /* Pointers to old string and new string */
+	char *poldx = line;
+	int replacen;
+	int searchn = -1;
+	char *startw, *endw;
+	char *c_eol = buf + 510 - 1; /* Cached end of (new) line */
+	int cleaned = 0;
+
+	replacew = CENSORWORD;
+	replacen = sizeof(CENSORWORD)-1;
+
+	while (1)
+	{
+		pold = my_strcasestr(pold, badword);
+		if (!pold)
+			break;
+		if (searchn == -1)
+			searchn = strlen(badword);
+		/* Hunt for start of word */
+ 		if (pold > line)
+ 		{
+			for (startw = pold; (!iswseperator(*startw) && (startw != line)); startw--);
+			if (iswseperator(*startw))
+				startw++; /* Don't point at the space/seperator but at the word! */
+		} else {
+			startw = pold;
+		}
+
+		if (!(type & TEXTBAN_WORD_LEFT) && (pold != startw))
+		{
+			/* not matched */
+			pold++;
+			continue;
+		}
+
+		/* Hunt for end of word
+		 * Fix for bug #4909: word will be at least 'searchn' long so we can skip
+		 * 'searchn' bytes and avoid stopping half-way the badword.
+		 */
+		for (endw = pold+searchn; ((*endw != '\0') && (!iswseperator(*endw))); endw++);
+
+		if (!(type & TEXTBAN_WORD_RIGHT) && (pold+searchn != endw))
+		{
+			/* not matched */
+			pold++;
+			continue;
+		}
+
+		cleaned = 1; /* still too soon? Syzop/20050227 */
+
+		/* Do we have any not-copied-yet data? */
+		if (poldx != startw)
+		{
+			int tmp_n = startw - poldx;
+			if (pnew + tmp_n >= c_eol)
+			{
+				/* Partial copy and return... */
+				memcpy(pnew, poldx, c_eol - pnew);
+				*c_eol = '\0';
+				return 1;
+			}
+
+			memcpy(pnew, poldx, tmp_n);
+			pnew += tmp_n;
+		}
+		/* Now update the word in buf (pnew is now something like startw-in-new-buffer */
+
+		if (replacen)
+		{
+			if ((pnew + replacen) >= c_eol)
+			{
+				/* Partial copy and return... */
+				memcpy(pnew, replacew, c_eol - pnew);
+				*c_eol = '\0';
+				return 1;
+			}
+			memcpy(pnew, replacew, replacen);
+			pnew += replacen;
+		}
+		poldx = pold = endw;
+	}
+	/* Copy the last part */
+	if (*poldx)
+	{
+		strncpy(pnew, poldx, c_eol - pnew);
+		*(c_eol) = '\0';
+	} else {
+		*pnew = '\0';
+	}
+	return cleaned;
+}
+#endif
+
+unsigned int counttextbans(Channel *channel)
+{
+	Ban *ban;
+	unsigned int cnt = 0;
+
+	for (ban = channel->banlist; ban; ban=ban->next)
+		if ((ban->banstr[0] == '~') && (ban->banstr[1] == 'T') && (ban->banstr[2] == ':'))
+			cnt++;
+	for (ban = channel->exlist; ban; ban=ban->next)
+		if ((ban->banstr[0] == '~') && (ban->banstr[1] == 'T') && (ban->banstr[2] == ':'))
+			cnt++;
+	return cnt;
+}
+
+
+int extban_modeT_is_ok(BanContext *b)
+{
+	int n;
+
+	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 ((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(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;
+}
+
+char *conv_pattern_asterisks(const char *pattern)
+{
+	static char buf[512];
+	char missing_prefix = 0, missing_suffix = 0;
+	if (*pattern != '*')
+		missing_prefix = 1;
+	if (*pattern && (pattern[strlen(pattern)-1] != '*'))
+		missing_suffix = 1;
+	snprintf(buf, sizeof(buf), "%s%s%s",
+		missing_prefix ? "*" : "",
+		pattern,
+		missing_suffix ? "*" : "");
+	return buf;
+}
+
+/** Ban callbacks */
+const char *extban_modeT_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[MAX_LENGTH+1];
+	char para[MAX_LENGTH+1], *action, *text, *p;
+#ifdef UHOSTFEATURE
+	char *uhost;
+	int ap = 0;
+#endif
+
+	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
+	 */
+
+#ifdef UHOSTFEATURE
+	action = strchr(para, ':');
+	if (!action)
+		return NULL;
+	*action++ = '\0';
+	if (!*action)
+		return NULL;
+	text = strchr(action, ':');
+	if (!text || !text[1])
+		return NULL;
+	*text++ = '\0';
+	uhost = para;
+
+	for (p = uhost; *p; p++)
+	{
+		if (*p == '@')
+			ap++;
+		else if ((*p <= ' ') || (*p > 128))
+			return NULL; /* cannot be in a username/host */
+	}
+	if (ap != 1)
+		return NULL; /* no @ */
+#else
+	text = strchr(para, ':');
+	if (!text)
+		return NULL;
+	*text++ = '\0';
+	/* para=action, text=text */
+	if (!*text)
+		return NULL; /* empty text */
+	action = para;
+#endif
+
+	/* ~T:<action>:<text> */
+	if (!strcasecmp(action, "block"))
+	{
+		action = "block"; /* ok */
+		text = conv_pattern_asterisks(text);
+	}
+#ifdef CENSORFEATURE
+	else if (!strcasecmp(action, "censor"))
+	{
+		char *p;
+		action = "censor";
+		for (p = text; *p; p++)
+			if ((*p == '*') && !(p == text) && !(p[1] == '\0'))
+				return NULL; /* can only be *word, word* or *word* or word */
+		if (!strcmp(p, "*") || !strcmp(p, "**"))
+			return NULL; /* cannot match everything ;p */
+	}
+#endif
+	else
+		return NULL; /* unknown action */
+
+	/* check the string.. */
+	for (p=text; *p; p++)
+	{
+		if ((*p == '\003') || (*p == '\002') || 
+		    (*p == '\037') || (*p == '\026') ||
+		    (*p == ' '))
+		{
+			return NULL; /* codes not permitted, would be confusing since they are stripped */
+		}
+	}
+
+	/* Rebuild the string.. can be cut off if too long. */
+#ifdef UHOSTFEATURE
+	snprintf(retbuf, sizeof(retbuf), "%s:%s:%s", uhost, action, text);
+#else
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", action, text);
+#endif
+	return retbuf;
+}
+
+/** Check for text bans (censor and block) */
+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 (check_channel_access(client, channel, "hoaq"))
+		return HOOK_CONTINUE;
+
+	/* IRCOps with these privileges bypass textbans too */
+	if (op_can_override("channel:override:message:ban", client, channel, NULL))
+		return HOOK_CONTINUE;
+
+	/* Now we have to manually walk the banlist and check if things match */
+	for (ban = channel->banlist; ban; ban=ban->next)
+	{
+		char *banstr = ban->banstr;
+
+		/* Pretend time does not exist... */
+		if (!strncmp(banstr, "~t:", 3))
+		{
+			banstr = strchr(banstr+3, ':');
+			if (!banstr)
+				continue;
+			banstr++;
+		}
+		else if (!strncmp(banstr, "~time:", 6))
+		{
+			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;
+		}
+	}
+
+	return HOOK_CONTINUE;
+}
+
+
+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;
+	const char *p;
+#ifdef UHOSTFEATURE
+	char buf[512], uhost[USERLEN + HOSTLEN + 16];
+#endif
+	char tmp[1024], *word;
+	int type;
+
+	/* We can only filter on non-NULL text of course */
+	if ((msg == NULL) || (*msg == NULL))
+		return 0;
+
+	filtered[0] = '\0'; /* NOT needed, but... :P */
+
+#ifdef UHOSTFEATURE
+	ircsprintf(uhost, "%s@%s", client->user->username, GetHost(client));
+#endif
+	strlcpy(filtered, StripControlCodes(*msg), sizeof(filtered));
+
+	p = strchr(ban, ':');
+	if (!p)
+		return 0; /* "impossible" */
+	p++;
+#ifdef UHOSTFEATURE
+	/* First.. deal with userhost... */
+	strcpy(buf, p);
+	p = strchr(buf, ':');
+	if (!p)
+		return 0; /* invalid format */
+	*p++ = '\0';
+
+	if (match_simple(buf, uhost))
+#else
+	if (1)
+#endif
+	{
+		if (!strncasecmp(p, "block:", 6))
+		{
+			if (match_simple(p+6, filtered))
+			{
+				if (errmsg)
+					*errmsg = "Message blocked due to a text ban";
+				return 1; /* BLOCK */
+			}
+		}
+#ifdef CENSORFEATURE
+		else if (!strncasecmp(p, "censor:", 7))
+		{
+			parse_word(p+7, &word, &type);
+			if (textban_replace(type, word, filtered, tmp))
+			{
+				strlcpy(filtered, tmp, sizeof(filtered));
+				cleaned = 1;
+			}
+		}
+#endif
+	}
+
+	if (cleaned)
+	{
+		/* check for null string */
+		char *p;
+		for (p = filtered; *p; p++)
+		{
+			if (*p != ' ')
+			{
+				strlcpy(retbuf, filtered, sizeof(retbuf));
+				*msg = retbuf;
+				return 0; /* allow through, but filtered */
+			}
+		}
+		return 1; /* nothing but spaces found.. */
+	}
+	return 0; /* nothing blocked */
+}
+
+#ifdef CENSORFEATURE
+void parse_word(const char *s, char **word, int *type)
+{
+	static char buf[512];
+	const char *tmp;
+	int len;
+	int tpe = 0;
+	char *o = buf;
+
+	for (tmp = s; *tmp; tmp++)
+	{
+		if (*tmp != '*')
+			*o++ = *tmp;
+		else
+		{
+			if (s == tmp)
+				tpe |= TEXTBAN_WORD_LEFT;
+			if (*(tmp + 1) == '\0')
+				tpe |= TEXTBAN_WORD_RIGHT;
+		}
+	}
+	*o = '\0';
+
+	*word = buf;
+	*type = tpe;
+}
+#endif
diff --git a/ircd/src/modules/extbans/timedban.c b/ircd/src/modules/extbans/timedban.c
@@ -0,0 +1,506 @@
+/*
+ * timedban - Timed bans that are automatically unset.
+ * (C) Copyright 2009-2017 Bram Matthys (Syzop) and the UnrealIRCd team.
+ * License: GPLv2 or later
+ *
+ * This module adds an extended ban ~t:time:mask
+ * Where 'time' is the time in minutes after which the ban will be removed.
+ * Where 'mask' is any banmask that is normally valid.
+ *
+ * Note that this extended ban is rather special in the sense that
+ * it permits (crazy) triple-extbans to be set, such as:
+ * +b ~t:1:~q:~a:Account
+ * (=a temporary 1min ban to mute a user with services account Account)
+ * +e ~t:1440:~m:moderated:*!*@host
+ * (=user with *!*@host may speak through +m for the next 1440m / 24h)
+ *
+ * The triple-extbans / double-stacking requires special routines that
+ * are based on parts of the core and special recursion checks.
+ * If you are looking for inspiration of coding your own extended ban
+ * then look at another extended ban * module as this module is not a
+ * good starting point ;)
+ */
+   
+#include "unrealircd.h"
+
+/* Maximum time (in minutes) for a ban */
+#define TIMEDBAN_MAX_TIME	9999
+
+/* Maximum length of a ban */
+#define MAX_LENGTH 128
+
+/* Split timeout event in <this> amount of iterations */
+#define TIMEDBAN_TIMER_ITERATION_SPLIT 4
+
+/* Call timeout event every <this> seconds.
+ * NOTE: until all channels are processed it takes
+ *       TIMEDBAN_TIMER_ITERATION_SPLIT * TIMEDBAN_TIMER.
+ */
+#define TIMEDBAN_TIMER	2
+
+/* We allow a ban to (potentially) expire slightly before the deadline.
+ * For example with TIMEDBAN_TIMER_ITERATION_SPLIT=4 and TIMEDBAN_TIMER=2
+ * a 1 minute ban would expire at 56-63 seconds, rather than 60-67 seconds.
+ * This is usually preferred.
+ */
+#define TIMEDBAN_TIMER_DELTA ((TIMEDBAN_TIMER_ITERATION_SPLIT*TIMEDBAN_TIMER)/2)
+
+ModuleHeader MOD_HEADER
+  = {
+	"extbans/timedban",
+	"1.0",
+	"ExtBan ~t: automatically removed timed bans",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+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);
+
+EVENT(timedban_timeout);
+
+MOD_TEST()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ExtbanInfo extban;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&extban, 0, sizeof(ExtbanInfo));
+	extban.letter = 't';
+	extban.name = "time";
+	extban.options |= EXTBOPT_ACTMODIFIER; /* not really, but ours shouldn't be stacked from group 1 */
+	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))
+	{
+		config_error("timedban: unable to register 't' extban type!!");
+		return MOD_FAILED;
+	}
+                
+	EventAdd(modinfo->handle, "timedban_timeout", timedban_timeout, NULL, TIMEDBAN_TIMER*1000, 0);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** 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? ;)
+ */
+const char *generic_clean_ban_mask(BanContext *b, Extban *extban)
+{
+	char *cp, *x;
+	static char maskbuf[512];
+	char *mask;
+
+	/* Work on a copy */
+	strlcpy(maskbuf, b->banstr, sizeof(maskbuf));
+	mask = maskbuf;
+
+	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 <= ' ')
+			return NULL;
+
+	/* Extended ban? */
+	if (is_extended_ban(mask))
+	{
+		const char *nextbanstr;
+		Extban *extban = findmod_by_bantype(mask, &nextbanstr);
+		if (!extban)
+			return NULL; /* reject unknown extban */
+		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 */
+		if (strlen(mask) > 80)
+			mask[80] = '\0';
+		return mask;
+	}
+
+	return convert_regular_ban(mask, NULL, 0);
+}
+
+/** Convert ban to an acceptable format (or return NULL to fully reject it) */
+const char *timedban_extban_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[MAX_LENGTH+1];
+	char para[MAX_LENGTH+1];
+	char tmpmask[MAX_LENGTH+1];
+	char *durationstr; /**< Duration, such as '5' */
+	int duration;
+	char *matchby; /**< 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, 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
+	 */
+
+	durationstr = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return NULL;
+	*matchby++ = '\0';
+	
+	duration = atoi(durationstr);
+
+	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
+		return NULL;
+
+	strlcpy(tmpmask, matchby, sizeof(tmpmask));
+	timedban_extban_conv_param_recursion++;
+	//newmask = extban_conv_param_nuh_or_extban(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), "%d:%s", duration, newmask);
+	return retbuf;
+}
+
+int timedban_extban_syntax(Client *client, int checkt, char *reason)
+{
+	if (MyUser(client) && (checkt == EXBCHK_PARAM))
+	{
+		sendnotice(client, "Error when setting timed ban: %s", reason);
+		sendnotice(client, " Syntax: +b ~t:duration:mask");
+		sendnotice(client, "Example: +b ~t:5:nick!user@host");
+		sendnotice(client, "Duration is the time in minutes after which the ban is removed (1-9999)");
+		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
+	}
+	return 0; /* FAIL: ban rejected */
+}
+
+/** Generic helper for sub-bans, used by our "is this ban ok?" function */
+int generic_ban_is_ok(BanContext *b)
+{
+	if ((b->banstr[0] == '~') && MyUser(b->client))
+	{
+		Extban *extban;
+		const char *nextbanstr;
+
+		/* This portion is copied from clean_ban_mask() */
+		if (is_extended_ban(b->banstr) && MyUser(b->client))
+		{
+			if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL))
+			{
+				if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
+				{
+					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, b->banstr[1]))
+				{
+					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 */
+			extban = findmod_by_bantype(b->banstr, &nextbanstr);
+			if (extban && extban->is_ok)
+			{
+				b->banstr = nextbanstr;
+				if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR))
+				{
+					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)
+				{
+					if (!extban->is_ok(b))
+					{
+						return 0; /* REJECT */
+					}
+				}
+			}
+		}
+	}
+	
+	/* ACCEPT:
+	 * - not an extban; OR
+	 * - extban with NULL is_ok; OR
+	 * - non-existing extban character (handled by conv_param?)
+	 */
+	return 1;
+}
+
+/** Validate ban ("is this ban ok?") */
+int timedban_extban_is_ok(BanContext *b)
+{
+	char para[MAX_LENGTH+1];
+	char tmpmask[MAX_LENGTH+1];
+	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' */
+	static int timedban_extban_is_ok_recursion = 0;
+	int res;
+
+	/* Always permit deletion */
+	if (b->what == MODE_DEL)
+		return 1;
+
+	if (timedban_extban_is_ok_recursion)
+		return 0; /* Recursion detected (~t:1:~t:....) */
+
+	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
+	 */
+
+	durationstr = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		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(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(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)
+	{
+		/* This could be anything ranging from:
+		 * invalid n!u@h syntax, unknown (sub)extbantype,
+		 * disabled extban type in conf, too much recursion, etc.
+		 */
+		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(BanContext *b)
+{
+	b->banstr = strchr(b->banstr, ':'); /* skip time argument */
+	if (!b->banstr)
+		return 0; /* invalid fmt */
+	b->banstr++; /* skip over final semicolon */
+
+	return ban_check_mask(b);
+}
+
+/** Helper to check if the ban has been expired.
+ */
+int timedban_has_ban_expired(Ban *ban)
+{
+	char *banstr = ban->banstr;
+	char *p1, *p2;
+	int t;
+	time_t expire_on;
+
+	/* 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 */
+	p2 = strchr(p1+1, ':'); /* skip time argument */
+	if (!p2)
+		return 0; /* invalid fmt */
+	*p2 = '\0'; /* danger.. must restore!! */
+	t = atoi(p1);
+	*p2 = ':'; /* restored.. */
+	
+	expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA;
+	
+	if (expire_on < TStime())
+		return 1;
+	return 0;
+}
+
+static char mbuf[512];
+static char pbuf[512];
+
+/** This removes any expired timedbans */
+EVENT(timedban_timeout)
+{
+	Channel *channel;
+	Ban *ban, *nextban;
+	static int current_iteration = 0;
+
+	if (++current_iteration >= TIMEDBAN_TIMER_ITERATION_SPLIT)
+		current_iteration = 0;
+
+	for (channel = channels; channel; channel = channel->nextch)
+	{
+		/* This is a very quick check, at the cost of it being
+		 * biased since there's always a tendency of more channel
+		 * names to start with one specific letter. But hashing
+		 * is too costly. So we stick with this. It should be
+		 * good enough. Alternative would be some channel->id value.
+		 */
+		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", 2) && timedban_has_ban_expired(ban))
+			{
+				add_send_mode_param(channel, &me, '-',  'b', ban->banstr);
+				del_listmode(&channel->banlist, channel, ban->banstr);
+			}
+		}
+		for (ban = channel->exlist; ban; ban=nextban)
+		{
+			nextban = ban->next;
+			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);
+			}
+		}
+		for (ban = channel->invexlist; ban; ban=nextban)
+		{
+			nextban = ban->next;
+			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);
+			}
+		}
+		if (*pbuf)
+		{
+			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->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;
+		}
+	}
+}
+
+#if MODEBUFLEN > 512
+ #error "add_send_mode_param() is not made for MODEBUFLEN > 512"
+#endif
+
+void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param) {
+	static char *modes = NULL, lastwhat;
+	static short count = 0;
+	short send = 0;
+	
+	if (!modes) modes = mbuf;
+	
+	if (!mbuf[0]) {
+		modes = mbuf;
+		*modes++ = what;
+		*modes = 0;
+		lastwhat = what;
+		*pbuf = 0;
+		count = 0;
+	}
+	if (lastwhat != what) {
+		*modes++ = what;
+		*modes = 0;
+		lastwhat = what;
+	}
+	if (strlen(pbuf) + strlen(param) + 11 < MODEBUFLEN) {
+		if (*pbuf) 
+			strcat(pbuf, " ");
+		strcat(pbuf, param);
+		*modes++ = mode;
+		*modes = 0;
+		count++;
+	}
+	else if (*pbuf) 
+		send = 1;
+
+	if (count == MAXMODEPARAMS)
+		send = 1;
+
+	if (send)
+	{
+		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->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;
+		modes = mbuf;
+		*modes++ = what;
+		lastwhat = what;
+		if (count != MAXMODEPARAMS)
+		{
+			strlcpy(pbuf, param, sizeof(pbuf));
+			*modes++ = mode;
+			count = 1;
+		} else {
+			count = 0;
+		}
+		*modes = 0;
+	}
+}
diff --git a/ircd/src/modules/extended-monitor.c b/ircd/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_change(Client *client, const char *olduser, const char *oldhost);
+int extended_monitor_realname_change(Client *client, const char *oldinfo);
+int extended_monitor_notification(Client *client, Watch *watch, Link *lp, int event);
+
+ModuleHeader MOD_HEADER
+  = {
+	"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 = "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_CHANGE, 0, extended_monitor_userhost_change);
+	HookAdd(modinfo->handle, HOOKTYPE_REALNAME_CHANGE, 0, extended_monitor_realname_change);
+
+	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_change(Client *client, const char *olduser, const char *oldhost)
+{
+	watch_check(client, WATCH_EVENT_USERHOST, extended_monitor_notification);
+	return 0;
+}
+
+int extended_monitor_realname_change(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/ircd/src/modules/extjwt.c b/ircd/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/ircd/src/modules/geoip-tag.c b/ircd/src/modules/geoip-tag.c
@@ -0,0 +1,108 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/geoip-tag.c
+ *   (C) 2022 westor, 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.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"geoip-tag",
+	"6.0",
+	"geoip message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Forward declarations */
+int geoip_mtag_is_ok(Client *client, const char *name, const char *value);
+int geoip_mtag_should_send_to_client(Client *target);
+void mtag_add_geoip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/geoip";
+	mtag.is_ok = geoip_mtag_is_ok;
+	mtag.should_send_to_client = geoip_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_geoip);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending
+ * 'geoip-tag' is permitted to do so and uses a permitted
+ * syntax.
+ * We simply allow geoip-tag ONLY from servers and with any syntax.
+ */
+int geoip_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_add_geoip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m;
+	
+	GeoIPResult *geoip;
+
+	if (IsUser(client) && ((geoip = geoip_client(client))))
+	{
+		MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/geoip");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+		} else {
+			m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, "unrealircd.org/geoip");
+			safe_strdup(m->value, geoip->country_code);
+		}
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int geoip_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target) || IsOper(target))
+		return 1;
+
+	return 0;
+}
diff --git a/ircd/src/modules/geoip_base.c b/ircd/src/modules/geoip_base.c
@@ -0,0 +1,351 @@
+/*
+ * 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 or later
+ */
+
+#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_ip_change(Client *client, const char *oldip);
+int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list);
+int geoip_connect_extinfo(Client *client, NameValuePrioList **list);
+int geoip_json_expand_client(Client *client, int detail, json_t *j);
+int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(geoip_base_set_existing_users_evt);
+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_IP_CHANGE, 0, geoip_base_ip_change);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, geoip_base_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 1, geoip_connect_extinfo); /* (prio: near-first) */
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0,geoip_base_handshake); /* in case the IP changed in registration phase (WEBIRC, HTTP Forwarded) */
+	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois);
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, geoip_json_expand_client);
+
+	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;
+}
+
+int geoip_base_ip_change(Client *client, const char *oldip)
+{
+	geoip_base_handshake(client);
+	return 0;
+}
+
+void geoip_base_free(ModData *m)
+{
+	if (m->ptr)
+	{
+		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)
+	{
+		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 (MyUser(client))
+			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_json_expand_client(Client *client, int detail, json_t *j)
+{
+	GeoIPResult *geo = GEOIPDATA(client);
+	json_t *geoip;
+
+	if (!geo)
+		return 0;
+
+	geoip = json_object();
+	json_object_set_new(j, "geoip", geoip);
+	json_object_set_new(geoip, "country_code", json_string_unreal(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/ircd/src/modules/geoip_classic.c b/ircd/src/modules/geoip_classic.c
@@ -0,0 +1,297 @@
+/* GEOIP Classic module
+ * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#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/ircd/src/modules/geoip_csv.c b/ircd/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/ircd/src/modules/geoip_maxmind.c b/ircd/src/modules/geoip_maxmind.c
@@ -0,0 +1,239 @@
+/* GEOIP maxmind module
+ * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#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/ircd/src/modules/globops.c b/ircd/src/modules/globops.c
@@ -0,0 +1,85 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_globops);
+
+#define MSG_GLOBOPS 	"GLOBOPS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"globops",
+	"5.0",
+	"command /globops", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_GLOBOPS, cmd_globops, 1, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Write message to IRCOps.
+ * parv[1] = message text
+ */
+CMD_FUNC(cmd_globops)
+{
+	const char *message = parc > 1 ? parv[1] : NULL;
+
+	if (BadPtr(message))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "GLOBOPS");
+		return;
+	}
+
+	if (MyUser(client) && !ValidatePermissionsForPath("chat:globops",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (MyUser(client))
+	{
+		/* Easy */
+		sendto_umode_global(UMODE_OPER, "from %s: %s", client->name, message);
+	} else
+	{
+		/* Backward-compatible (3.2.x) */
+		sendto_umode(UMODE_OPER, "from %s: %s", client->name, message);
+		sendto_server(client, 0, 0, NULL, ":%s SENDUMODE o :from %s: %s",
+		    me.id, client->name, message);
+	}
+}
diff --git a/ircd/src/modules/help.c b/ircd/src/modules/help.c
@@ -0,0 +1,145 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_help);
+
+#define MSG_HELP 	"HELP"	
+#define MSG_HELPOP	"HELPOP"
+
+ModuleHeader MOD_HEADER
+  = {
+	"help",
+	"5.0",
+	"command /help", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_HELP, cmd_help, 1, CMD_USER);
+	CommandAdd(modinfo->handle, MSG_HELPOP, cmd_help, 1, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+#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(const char *command)
+{
+	ConfigItem_help *help;
+
+	if (!command)
+	{
+		for (help = conf_help; help; help = help->next)
+		{
+			if (help->command == NULL)
+				return help;
+		}
+		return NULL;
+	}
+	for (help = conf_help; help; help = help->next)
+	{
+		if (help->command == NULL)
+			continue;
+		else if (!strcasecmp(command,help->command))
+			return help;
+	}
+	return NULL;
+}
+
+void parse_help(Client *client, const char *help)
+{
+	ConfigItem_help *helpitem;
+	MOTDLine *text;
+	if (BadPtr(help))
+	{
+		helpitem = find_Help(NULL);
+		if (!helpitem)
+			return;
+		SND(" -");
+		HDR("        ***** UnrealIRCd Help System *****");
+		SND(" -");
+		text = helpitem->text;
+		while (text) {
+			SND(text->line);
+			text = text->next;
+		}
+		SND(" -");
+		return;
+		
+	}
+	helpitem = find_Help(help);
+	if (!helpitem) {
+		SND(" -");
+		HDR("        ***** No Help Available *****");
+		SND(" -");
+		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, HELP_CHANNEL);
+		SND(" -");
+		return;
+	}
+	text = helpitem->text;
+	SND(" -");
+	sendto_one(client, NULL, ":%s 290 %s :***** %s *****",
+	    me.name, client->name, helpitem->command);
+	SND(" -");
+	while (text) {
+		SND(text->line);
+		text = text->next;
+	}
+	SND(" -");
+}
+
+/*
+** cmd_help (help/write to +h currently online) -Donwulff
+**	parv[1] = optional message text
+*/
+CMD_FUNC(cmd_help)
+{
+	const char *helptopic;
+
+	if (!MyUser(client))
+		return; /* never remote */
+
+	helptopic = parc > 1 ? parv[1] : NULL;
+	
+	if (helptopic && (*helptopic == '?'))
+		helptopic++;
+
+	parse_help(client, BadPtr(helptopic) ? NULL : helptopic);
+}
diff --git a/ircd/src/modules/hideserver.c b/ircd/src/modules/hideserver.c
@@ -0,0 +1,467 @@
+/*
+ * hideserver.c - Hide certain or all servers from /MAP & /LINKS
+ *
+ * Note that this module simple hides servers. It does not truly
+ * increase security. Use as you wish.
+ *
+ * (C) Copyright 2003-2004 AngryWolf <angrywolf@flashmail.com>
+ * (C) Copyright 2016 Bram Matthys <syzop@vulnscan.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"
+
+CMD_OVERRIDE_FUNC(override_map);
+CMD_OVERRIDE_FUNC(override_links);
+static int cb_test(ConfigFile *, ConfigEntry *, int, int *);
+static int cb_conf(ConfigFile *, ConfigEntry *, int);
+
+ConfigItem_ulines *HiddenServers;
+
+static struct
+{
+	unsigned	disable_map : 1;
+	unsigned	disable_links : 1;
+	char		*map_deny_message;
+	char		*links_deny_message;
+} Settings;
+
+static ModuleInfo	*MyModInfo;
+#define MyMod		MyModInfo->handle
+#define SAVE_MODINFO	MyModInfo = modinfo;
+
+static int lmax = 0;
+static int umax = 0;
+
+static int dcount(int n)
+{
+   int cnt = 0;
+
+   while (n != 0)
+   {
+	   n = n/10;
+	   cnt++;
+   }
+
+   return cnt;
+}
+
+ModuleHeader MOD_HEADER
+  = {
+	"hideserver",
+	"5.0",
+	"Hide servers from /MAP & /LINKS",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+static void InitConf()
+{
+	memset(&Settings, 0, sizeof Settings);
+}
+
+static void FreeConf()
+{
+	ConfigItem_ulines	*h, *next;
+
+	safe_free(Settings.map_deny_message);
+	safe_free(Settings.links_deny_message);
+
+	for (h = HiddenServers; h; h = next)
+	{
+		next = h->next;
+		DelListItem(h, HiddenServers);
+		safe_free(h->servername);
+		safe_free(h);
+	}
+}
+
+MOD_TEST()
+{
+	SAVE_MODINFO
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cb_test);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	SAVE_MODINFO
+	HiddenServers = NULL;
+	InitConf();
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cb_conf);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (!CommandOverrideAdd(MyMod, "MAP", 0, override_map))
+		return MOD_FAILED;
+
+	if (!CommandOverrideAdd(MyMod, "LINKS", 0, override_links))
+		return MOD_FAILED;
+
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	FreeConf();
+
+	return MOD_SUCCESS;
+}
+
+static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+
+	if (type == CONFIG_MAIN)
+	{
+		if (!strcmp(ce->name, "hideserver"))
+		{
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				if (!strcmp(cep->name, "hide"))
+				{
+					/* No checking needed */
+				}
+				else if (!cep->value)
+				{
+					config_error("%s:%i: %s::%s without value",
+						cep->file->filename,
+						cep->line_number,
+						ce->name, cep->name);
+					errors++;
+					continue;
+				}
+				else if (!strcmp(cep->name, "disable-map"))
+					;
+				else if (!strcmp(cep->name, "disable-links"))
+					;
+				else if (!strcmp(cep->name, "map-deny-message"))
+					;
+				else if (!strcmp(cep->name, "links-deny-message"))
+					;
+				else
+				{
+					config_error("%s:%i: unknown directive hideserver::%s",
+						cep->file->filename, cep->line_number, cep->name);
+					errors++;
+				}
+			}
+			*errs = errors;
+			return errors ? -1 : 1;
+		}
+	}
+
+	return 0;
+}
+
+static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry		*cep, *cepp;
+	ConfigItem_ulines	*ca;
+
+	if (type == CONFIG_MAIN)
+	{
+		if (!strcmp(ce->name, "hideserver"))
+		{
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				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->value);
+				}
+				else if (!strcmp(cep->name, "links-deny-message"))
+				{
+					safe_strdup(Settings.links_deny_message, cep->value);
+				}
+				else if (!strcmp(cep->name, "hide"))
+				{
+					for (cepp = cep->items; cepp; cepp = cepp->next)
+					{
+						if (!strcasecmp(cepp->name, me.name))
+							continue;
+
+						ca = safe_alloc(sizeof(ConfigItem_ulines));
+						safe_strdup(ca->servername, cepp->name);
+						AddListItem(ca, HiddenServers);
+					}
+				}
+			}
+
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+ConfigItem_ulines *FindHiddenServer(char *servername)
+{
+	ConfigItem_ulines *h;
+
+	for (h = HiddenServers; h; h = h->next)
+		if (!strcasecmp(servername, h->servername))
+			break;
+
+	return h;
+}
+
+/*
+ * New /MAP format -Potvin
+ * dump_map function.
+ */
+static void dump_map(Client *client, Client *server, char *mask, int prompt_length, int length)
+{
+	static char prompt[64];
+	char *p = &prompt[prompt_length];
+	int  cnt = 0;
+	Client *acptr;
+
+	*p = '\0';
+
+	if (prompt_length > 60)
+		sendnumeric(client, RPL_MAPMORE, prompt, length, server->name);
+	else
+	{
+		char tbuf[256];
+		char sid[10];
+		int len = length - strlen(server->name) + 1;
+
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+		if (IsOper(client))
+			snprintf(sid, sizeof(sid), " [%s]", server->id);
+		sendnumeric(client, RPL_MAP, prompt, server->name, tbuf, umax,
+			server->server->users, (double)(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(server->server->users * 100.0 / irccounts.clients),
+			IsOper(client) ? sid : "");
+		cnt = 0;
+	}
+
+	if (prompt_length > 0)
+	{
+		p[-1] = ' ';
+		if (p[-2] == '`')
+			p[-2] = ' ';
+	}
+	if (prompt_length > 60)
+		return;
+
+	strcpy(p, "|-");
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if (acptr->uplink != server ||
+ 		    (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)))
+			continue;
+		if (FindHiddenServer(acptr->name))
+			break;
+		SetMap(acptr);
+		cnt++;
+	}
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
+			continue;
+		if (FindHiddenServer(acptr->name))
+			break;
+		if (acptr->uplink != server)
+			continue;
+		if (!IsMap(acptr))
+			continue;
+		if (--cnt == 0)
+			*p = '`';
+		dump_map(client, acptr, mask, prompt_length + 2, length - 2);
+	}
+
+	if (prompt_length > 0)
+		p[-1] = '-';
+}
+
+void dump_flat_map(Client *client, Client *server, int length)
+{
+	char buf[4];
+	char tbuf[256];
+	Client *acptr;
+	int cnt = 0, len = 0, hide_ulines;
+
+	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
+
+	len = length - strlen(server->name) + 3;
+	if (len < 0)
+		len = 0;
+	if (len > 255)
+		len = 255;
+
+	tbuf[len--] = '\0';
+	while (len >= 0)
+		tbuf[len--] = '-';
+
+	sendnumeric(client, RPL_MAP, "", server->name, tbuf, umax, server->server->users,
+		(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+		(server->server->users * 100.0 / irccounts.clients), "");
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
+			continue;
+		if (FindHiddenServer(acptr->name))
+			break;
+		cnt++;
+	}
+
+	strcpy(buf, "|-");
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
+			continue;
+		if (FindHiddenServer(acptr->name))
+			break;
+		if (--cnt == 0)
+			*buf = '`';
+
+		len = length - strlen(acptr->name) + 1;
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+
+		sendnumeric(client, RPL_MAP, buf, acptr->name, tbuf, umax, acptr->server->users,
+			(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(acptr->server->users * 100.0 / irccounts.clients), "");
+	}
+}
+
+/*
+** New /MAP format. -Potvin
+** cmd_map (NEW)
+**
+**      parv[1] = server mask
+**/
+CMD_OVERRIDE_FUNC(override_map)
+{
+	Client *acptr;
+	int longest = strlen(me.name);
+	float avg_users = 0.0;
+
+	umax = 0;
+	lmax = 0;
+
+	if (parc < 2)
+		parv[1] = "*";
+	
+	if (IsOper(client))
+	{
+		CALL_NEXT_COMMAND_OVERRIDE();
+		return;
+	}
+
+	if (Settings.disable_map)
+	{
+		if (Settings.map_deny_message)
+			sendnotice(client, "%s", Settings.map_deny_message);
+		else
+			sendnumeric(client, RPL_MAPEND);
+		return;
+	}
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		int perc = 0;
+		if (FindHiddenServer(acptr->name))
+			break;
+		perc = (acptr->server->users * 100 / irccounts.clients);
+		if ((strlen(acptr->name) + acptr->hopcount * 2) > longest)
+			longest = strlen(acptr->name) + acptr->hopcount * 2;
+		if (lmax < perc)
+			lmax = perc;
+		if (umax < dcount(acptr->server->users))
+			umax = dcount(acptr->server->users);
+	}
+
+	if (longest > 60)
+		longest = 60;
+	longest += 2;
+
+	if (FLAT_MAP && !ValidatePermissionsForPath("server:info:map:real-map",client,NULL,NULL,NULL))
+		dump_flat_map(client, &me, longest);
+	else
+		dump_map(client, &me, "*", 0, longest);
+
+	avg_users = irccounts.clients * 1.0 / irccounts.servers;
+	sendnumeric(client, RPL_MAPUSERS, irccounts.servers, (irccounts.servers > 1 ? "s" : ""), irccounts.clients,
+		(irccounts.clients > 1 ? "s" : ""), avg_users);
+	sendnumeric(client, RPL_MAPEND);
+}
+
+CMD_OVERRIDE_FUNC(override_links)
+{
+	Client *acptr;
+	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
+
+	if (IsOper(client))
+	{
+		CALL_NEXT_COMMAND_OVERRIDE();
+		return;
+	}
+
+	if (Settings.disable_links)
+	{
+		if (Settings.links_deny_message)
+			sendnotice(client, "%s", Settings.links_deny_message);
+		else
+			sendnumeric(client, RPL_ENDOFLINKS, "*");
+		return;
+	}
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		/* Some checks */
+		if (HIDE_ULINES && IsULine(acptr) && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
+			continue;
+		if (FindHiddenServer(acptr->name))
+			continue;
+		if (flat)
+			sendnumeric(client, RPL_LINKS, acptr->name, me.name,
+			    1, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
+		else
+			sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink ? acptr->uplink->name : me.name,
+			    acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
+	}
+
+	sendnumeric(client, RPL_ENDOFLINKS, "*");
+}
diff --git a/ircd/src/modules/history.c b/ircd/src/modules/history.c
@@ -0,0 +1,136 @@
+/* src/modules/history.c - (C) 2020 Bram Matthys (Syzop) & The UnrealIRCd Team
+ *
+ * Simple HISTORY command to fetch channel history.
+ * This is one of the interfaces to the channel history backend.
+ * The other, most prominent, being history-on-join at the moment.
+ * In the future there will likely be an IRCv3 standard which
+ * will allow clients to fetch history automatically, which will
+ * be implemented under a different command and not really meant
+ * for end users. However, it will take a while until such a spec is
+ * finalized, let alone when major clients will finally support it.
+ *
+ * See doc/Authors and git history 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
+  = {
+	"history",
+	"5.0",
+	"Simple history command for end-users",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+#define HISTORY_LINES_DEFAULT 100
+#define HISTORY_LINES_MAX 100
+
+CMD_FUNC(cmd_history);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, "HISTORY", cmd_history, MAXPARA, CMD_USER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void history_usage(Client *client)
+{
+	sendnotice(client, " Use: /HISTORY #channel [lines-to-display]");
+	sendnotice(client, "  Ex: /HISTORY #lobby");
+	sendnotice(client, "  Ex: /HISTORY #lobby 50");
+	sendnotice(client, "The lines-to-display value must be 1-%d, the default is %d",
+		HISTORY_LINES_MAX, HISTORY_LINES_DEFAULT);
+	sendnotice(client, "Naturally, the line count and time limits in channel mode +H are obeyed");
+}
+
+CMD_FUNC(cmd_history)
+{
+	HistoryFilter filter;
+	HistoryResult *r;
+	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]);
+	if (!channel)
+	{
+		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+		return;
+	}
+
+	if (!IsMember(client, channel))
+	{
+		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->name);
+		return;
+	}
+
+	if (parv[2])
+	{
+		lines = atoi(parv[2]);
+		if (lines < 1)
+		{
+			history_usage(client);
+			return;
+		}
+		if (lines > HISTORY_LINES_MAX)
+			lines = HISTORY_LINES_MAX;
+	}
+
+	if (!HasCapability(client, "server-time"))
+	{
+		sendnotice(client, "Your IRC client does not support the 'server-time' capability");
+		sendnotice(client, "https://ircv3.net/specs/extensions/server-time");
+		sendnotice(client, "History request refused.");
+		return;
+	}
+
+	memset(&filter, 0, sizeof(filter));
+	filter.cmd = HFC_SIMPLE;
+	filter.last_lines = lines;
+
+	if ((r = history_request(channel->name, &filter)))
+	{
+		history_send_result(client, r);
+		free_history_result(r);
+	}
+}
diff --git a/ircd/src/modules/history_backend_mem.c b/ircd/src/modules/history_backend_mem.c
@@ -0,0 +1,1618 @@
+/* src/modules/history_backend_mem.c - History Backend: memory
+ * (C) Copyright 2019-2021 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+#include "unrealircd.h"
+
+/* This is the memory type backend. It is optimized for speed.
+ * For example, per-channel, it caches the field "number of lines"
+ * and "oldest record", so frequent cleaning operations such as
+ * "delete any record older than time T" or "keep only N lines"
+ * are executed as fast as possible.
+ */
+
+ModuleHeader MOD_HEADER
+= {
+	"history_backend_mem",
+	"2.0",
+	"History backend: memory",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Defines */
+#define OBJECTLEN	((NICKLEN > CHANNELLEN) ? NICKLEN : CHANNELLEN)
+#define HISTORY_BACKEND_MEM_HASH_TABLE_SIZE 1019
+
+/* The regular history cleaning (by timer) is spread out
+ * a bit, rather than doing ALL channels every T time.
+ * HISTORY_SPREAD: how much to spread the "cleaning", eg 1 would be
+ *  to clean everything in 1 go, 2 would mean the first event would
+ *  clean half of the channels, and the 2nd event would clean the rest.
+ *  Obviously more = better to spread the load, but doing a reasonable
+ *  amount of work is also benefitial for performance (think: CPU cache).
+ * HISTORY_MAX_OFF_SECS: how many seconds may the history be 'off',
+ *  that is: how much may we store the history longer than required.
+ * The other 2 macros are calculated based on that target.
+ *
+ * Update April 2021: these values are now also used for saving the
+ * history if the persistent option is enabled. Therefore changed the
+ * values to spread it even more out: from 16/128 to 60/300 so
+ * in case of persistent it will save every 5 minutes.
+ */
+#if 0 //was: DEBUGMODE
+#define HISTORY_CLEAN_PER_LOOP HISTORY_BACKEND_MEM_HASH_TABLE_SIZE
+#define HISTORY_TIMER_EVERY 5
+#else
+#define HISTORY_SPREAD	60
+#define HISTORY_MAX_OFF_SECS	300
+#define HISTORY_CLEAN_PER_LOOP	(HISTORY_BACKEND_MEM_HASH_TABLE_SIZE/HISTORY_SPREAD)
+#define HISTORY_TIMER_EVERY	(HISTORY_MAX_OFF_SECS/HISTORY_SPREAD)
+#endif
+
+/* Some magic numbers used in the database format */
+#define HISTORYDB_MAGIC_FILE_START	0xFEFEFEFE
+#define HISTORYDB_MAGIC_FILE_END	0xEFEFEFEF
+#define HISTORYDB_MAGIC_ENTRY_START	0xFFFFFFFF
+#define HISTORYDB_MAGIC_ENTRY_END	0xEEEEEEEE
+
+/* Definitions (structs, etc.) -- all for persistent history */
+struct cfgstruct {
+	int persist;
+	char *directory;
+	char *masterdb; /* Autogenerated for convenience, not a real config item */
+	char *db_secret;
+};
+
+typedef struct HistoryLogObject HistoryLogObject;
+struct HistoryLogObject {
+	HistoryLogObject *prev, *next;
+	HistoryLogLine *head; /**< Start of the log (the earliest entry) */
+	HistoryLogLine *tail; /**< End of the log (the latest entry) */
+	int num_lines; /**< Number of lines of log */
+	time_t oldest_t; /**< Oldest time in log */
+	int max_lines; /**< Maximum number of lines permitted */
+	long max_time; /**< Maximum number of seconds to retain history */
+	int dirty; /**< Dirty flag, used for disk writing */
+	char name[OBJECTLEN+1];
+};
+
+/* Global variables */
+struct cfgstruct cfg;
+struct cfgstruct test;
+static char *siphashkey_history_backend_mem = NULL;
+HistoryLogObject **history_hash_table;
+static long already_loaded = 0;
+static char *hbm_prehash = NULL;
+static char *hbm_posthash = NULL;
+
+/* Forward declarations */
+int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int hbm_config_posttest(int *errs);
+int hbm_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+int hbm_rehash(void);
+int hbm_rehash_complete(void);
+static void setcfg(struct cfgstruct *cfg);
+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(const char *object, MessageTag *mtags, const char *line);
+int hbm_history_cleanup(HistoryLogObject *h);
+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(const char *fname);
+static int hbm_write_masterdb(void);
+static int hbm_write_db(HistoryLogObject *h);
+static void hbm_delete_db(HistoryLogObject *h);
+static void hbm_flush(void);
+void hbm_generic_free(ModData *m);
+void hbm_free_all_history(ModData *m);
+
+MOD_TEST()
+{
+	hbm_init_hashes(modinfo);
+	memset(&cfg, 0, sizeof(cfg));
+	memset(&test, 0, sizeof(test));
+	setcfg(&test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, hbm_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, hbm_config_posttest);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	HistoryBackendInfo hbi;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	/* We must unload early, when all channel modes and such are still in place: */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
+
+	setcfg(&cfg);
+
+	LoadPersistentLong(modinfo, already_loaded);
+	LoadPersistentPointer(modinfo, siphashkey_history_backend_mem, hbm_generic_free);
+	LoadPersistentPointer(modinfo, history_hash_table, hbm_free_all_history);
+	if (history_hash_table == NULL)
+		history_hash_table = safe_alloc(sizeof(HistoryLogObject *) * HISTORY_BACKEND_MEM_HASH_TABLE_SIZE);
+	/* hbm_prehash & hbm_posthash already loaded in MOD_TEST through hbm_init_hashes() */
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, hbm_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_DEL, 0, hbm_modechar_del);
+	HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, hbm_rehash);
+	HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, hbm_rehash_complete);
+
+	if (siphashkey_history_backend_mem == NULL)
+	{
+		siphashkey_history_backend_mem = safe_alloc(SIPHASH_KEY_LENGTH);
+		siphash_generate_key(siphashkey_history_backend_mem);
+	}
+
+	memset(&hbi, 0, sizeof(hbi));
+	hbi.name = "mem";
+	hbi.history_add = hbm_history_add;
+	hbi.history_request = hbm_history_request;
+	hbi.history_destroy = hbm_history_destroy;
+	hbi.history_set_limit = hbm_history_set_limit;
+	if (!HistoryBackendAdd(modinfo->handle, &hbi))
+		return MOD_FAILED;
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	/* Need to save these here already (after conf reading these are set),
+	 * as on next round the module reads it in TEST which happens before
+	 * the saving in MOD_UNLOAD:
+	 */
+	SavePersistentPointer(modinfo, hbm_prehash);
+	SavePersistentPointer(modinfo, hbm_posthash);
+
+	EventAdd(modinfo->handle, "history_mem_init", history_mem_init, NULL, 1, 1);
+	EventAdd(modinfo->handle, "history_mem_clean", history_mem_clean, NULL, HISTORY_TIMER_EVERY*1000, 0);
+	init_history_storage(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Read the .db if 'persist' mode is enabled.
+ * Normally this would be in MOD_LOAD, but the load order always
+ * must be: channeldb first, this module second, and since we
+ * cannot influence the load order we do this silly trick
+ * with a one-time 1msec event.
+ */
+EVENT(history_mem_init)
+{
+	if (!already_loaded)
+	{
+		/* Initial boot / load of the module... */
+		already_loaded = 1;
+		if (cfg.persist)
+			hbm_read_dbs();
+	}
+}
+
+MOD_UNLOAD()
+{
+	if (loop.terminating)
+		hbm_flush();
+	freecfg(&test);
+	freecfg(&cfg);
+	SavePersistentPointer(modinfo, hbm_prehash);
+	SavePersistentPointer(modinfo, hbm_posthash);
+	SavePersistentPointer(modinfo, history_hash_table);
+	SavePersistentPointer(modinfo, siphashkey_history_backend_mem);
+	SavePersistentLong(modinfo, already_loaded);
+	return MOD_SUCCESS;
+}
+
+/** Set cfg->masterdb based on cfg->directory, for convenience */
+static void hbm_set_masterdb_filename(struct cfgstruct *cfg)
+{
+	char buf[512];
+
+	safe_free(cfg->masterdb);
+	if (cfg->directory)
+	{
+		snprintf(buf, sizeof(buf), "%s/master.db", cfg->directory);
+		safe_strdup(cfg->masterdb, buf);
+	}
+}
+
+/** Default configuration for set::history::channel */
+static void setcfg(struct cfgstruct *cfg)
+{
+	safe_strdup(cfg->directory, "history");
+	convert_to_absolute_path(&cfg->directory, PERMDATADIR);
+	hbm_set_masterdb_filename(cfg);
+}
+
+static void freecfg(struct cfgstruct *cfg)
+{
+	safe_free(cfg->masterdb);
+	safe_free(cfg->directory);
+	safe_free(cfg->db_secret);
+}
+
+static void hbm_init_hashes(ModuleInfo *modinfo)
+{
+	char buf[256];
+
+	LoadPersistentPointer(modinfo, hbm_prehash, hbm_generic_free);
+	LoadPersistentPointer(modinfo, hbm_posthash, hbm_generic_free);
+
+	if (!hbm_prehash)
+	{
+		gen_random_alnum(buf, 128);
+		safe_strdup(hbm_prehash, buf);
+	}
+
+	if (!hbm_posthash)
+	{
+		gen_random_alnum(buf, 128);
+		safe_strdup(hbm_posthash, buf);
+	}
+}
+
+/** Test the set::history::channel configuration */
+int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+
+	if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->name)
+		return 0;
+
+	if (!strcmp(ce->name, "persist"))
+	{
+		if (!ce->value)
+		{
+			config_error("%s:%i: missing parameter",
+				ce->file->filename, ce->line_number);
+			errors++;
+		} else {
+			test.persist = config_checkval(ce->value, CFG_YESNO);
+		}
+	} else
+	if (!strcmp(ce->name, "db-secret"))
+	{
+		const char *err;
+		if ((err = unrealdb_test_secret(ce->value)))
+		{
+			config_error("%s:%i: set::history::channel::db-secret: %s", ce->file->filename, ce->line_number, err);
+			errors++;
+		}
+		safe_strdup(test.db_secret, ce->value);
+	} else
+	if (!strcmp(ce->name, "directory")) // or "path" ?
+	{
+		if (!ce->value)
+		{
+			config_error("%s:%i: missing parameter",
+				ce->file->filename, ce->line_number);
+			errors++;
+		} else
+		{
+			safe_strdup(test.directory, ce->value);
+			hbm_set_masterdb_filename(&test);
+		}
+	} else
+	{
+		return 0; /* unknown option to us, let another module handle it */
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/** Post-configuration test on set::history::channel */
+int hbm_config_posttest(int *errs)
+{
+	int errors = 0;
+
+	if (test.db_secret && !test.persist)
+	{
+		config_error("set::history::channel::db-secret is set but set::history::channel::persist is disabled, this makes no sense. "
+			     "Either use 'persist yes' or comment out / delete 'db-secret'.");
+		errors++;
+	} else
+	if (!test.db_secret && test.persist)
+	{
+		config_error("set::history::channel::db-secret needs to be set.");
+		errors++;
+	} else
+	if (test.db_secret && test.persist)
+	{
+		/* Configuration is good, now check if the password is correct
+		 * (if we can check at all, that is)...
+		 */
+		char *errstr = NULL;
+		if (test.masterdb && ((errstr = unrealdb_test_db(test.masterdb, test.db_secret))))
+		{
+			config_error("[history] %s", errstr);
+			errors++;
+			goto hbm_config_posttest_end;
+		}
+
+		/* Ensure directory exists and is writable */
+#ifdef _WIN32
+		(void)mkdir(test.directory); /* (errors ignored) */
+#else
+		(void)mkdir(test.directory, S_IRUSR|S_IWUSR|S_IXUSR); /* (errors ignored) */
+#endif
+		if (!file_exists(test.directory))
+		{
+			config_error("[history] Directory %s does not exist and could not be created",
+				test.directory);
+			errors++;
+		} else
+		{
+			/* Only do this if directory actually exists, hence in the 'else' block */
+			if (!hbm_read_masterdb())
+				errors++;
+		}
+	}
+
+hbm_config_posttest_end:
+	freecfg(&test);
+	setcfg(&test);
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/** 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->name)
+		return 0;
+
+	if (!strcmp(ce->name, "persist"))
+	{
+		cfg.persist = config_checkval(ce->value, CFG_YESNO);
+	} else
+	if (!strcmp(ce->name, "directory")) // or "path" ?
+	{
+		safe_strdup(cfg.directory, ce->value);
+		convert_to_absolute_path(&cfg.directory, PERMDATADIR);
+		hbm_set_masterdb_filename(&cfg);
+	} else
+	if (!strcmp(ce->name, "db-secret"))
+	{
+		safe_strdup(cfg.db_secret, ce->value);
+	} else
+	{
+		return 0; /* unknown option to us, let another module handle it */
+	}
+
+	return 1; /* handled by us */
+}
+
+int hbm_rehash(void)
+{
+	freecfg(&cfg);
+	setcfg(&cfg);
+	return 0;
+}
+
+int hbm_rehash_complete(void)
+{
+	return 0;
+}
+
+const char *history_storage_capability_parameter(Client *client)
+{
+	static char buf[128];
+
+	if (cfg.persist)
+		strlcpy(buf, "memory,disk=encrypted", sizeof(buf));
+	else
+		strlcpy(buf, "memory", sizeof(buf));
+
+	return buf;
+}
+
+static void init_history_storage(ModuleInfo *modinfo)
+{
+	ClientCapabilityInfo cap;
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "unrealircd.org/history-storage";
+	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
+	cap.parameter = history_storage_capability_parameter;
+	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
+}
+
+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(const char *object)
+{
+	int hashv = hbm_hash(object);
+	HistoryLogObject *h;
+
+	for (h = history_hash_table[hashv]; h; h = h->next)
+	{
+		if (!strcasecmp(object, h->name))
+			return h;
+	}
+	return NULL;
+}
+
+HistoryLogObject *hbm_find_or_add_object(const char *object)
+{
+	int hashv = hbm_hash(object);
+	HistoryLogObject *h;
+
+	for (h = history_hash_table[hashv]; h; h = h->next)
+	{
+		if (!strcasecmp(object, h->name))
+			return h;
+	}
+	/* Create new one */
+	h = safe_alloc(sizeof(HistoryLogObject));
+	strlcpy(h->name, object, sizeof(h->name));
+	AddListItem(h, history_hash_table[hashv]);
+	return h;
+}
+
+void hbm_delete_object_hlo(HistoryLogObject *h)
+{
+	int hashv;
+
+	if (cfg.persist)
+		hbm_delete_db(h);
+
+	hashv = hbm_hash(h->name);
+	DelListItem(h, history_hash_table[hashv]);
+	safe_free(h);
+}
+
+int hbm_modechar_del(Channel *channel, int modechar)
+{
+	HistoryLogObject *h;
+
+	if (!cfg.persist)
+		return 0;
+
+	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);
+
+		h->dirty = 1;
+		/* The reason for marking the entry as 'dirty' is that someone may later
+		 * set the channel +P again. If we would not set the h->dirty=1 then this
+		 * would mean the history log would not get rewritten until someone speaks.
+		 */
+	}
+
+	return 0;
+}
+
+void hbm_duplicate_mtags(HistoryLogLine *l, MessageTag *m)
+{
+	MessageTag *n;
+
+	/* Duplicate all message tags */
+	for (; m; m = m->next)
+	{
+		n = duplicate_mtag(m);
+		AppendListItem(n, l->mtags);
+	}
+	n = find_mtag(l->mtags, "time");
+	if (!n)
+	{
+		/* This is duplicate code from src/modules/server-time.c
+		 * which seems silly.
+		 */
+		struct timeval t;
+		struct tm *tm;
+		time_t sec;
+		char buf[64];
+
+		gettimeofday(&t, NULL);
+		sec = t.tv_sec;
+		tm = gmtime(&sec);
+		snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
+			tm->tm_year + 1900,
+			tm->tm_mon + 1,
+			tm->tm_mday,
+			tm->tm_hour,
+			tm->tm_min,
+			tm->tm_sec,
+			(int)(t.tv_usec / 1000));
+
+		n = safe_alloc(sizeof(MessageTag));
+		safe_strdup(n->name, "time");
+		safe_strdup(n->value, buf);
+		AddListItem(n, l->mtags);
+	}
+	/* Now convert the "time" message tag to something we can use in l->t */
+	l->t = server_time_to_unix_time(n->value);
+}
+
+/** Add a line to a history object */
+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 ^ */
+	hbm_duplicate_mtags(l, mtags);
+	if (h->tail)
+	{
+		/* append to tail */
+		h->tail->next = l;
+		l->prev = h->tail;
+		h->tail = l;
+	} else {
+		/* no tail, no head */
+		h->head = h->tail = l;
+	}
+	h->dirty = 1;
+	h->num_lines++;
+	if ((l->t < h->oldest_t) || (h->oldest_t == 0))
+		h->oldest_t = l->t;
+}
+
+/** Delete a line from a history object */
+void hbm_history_del_line(HistoryLogObject *h, HistoryLogLine *l)
+{
+	if (l->prev)
+		l->prev->next = l->next;
+	if (l->next)
+		l->next->prev = l->prev;
+	if (h->head == l)
+	{
+		/* New head */
+		h->head = l->next;
+	}
+	if (h->tail == l)
+	{
+		/* New tail */
+		h->tail = l->prev; /* could be NULL now */
+	}
+
+	free_message_tags(l->mtags);
+	safe_free(l);
+
+	h->dirty = 1;
+	h->num_lines--;
+
+	/* IMPORTANT: updating h->oldest_t takes place at the caller
+	 * because it is in a better position to optimize the process
+	 */
+}
+
+/** Add history entry */
+int hbm_history_add(const char *object, MessageTag *mtags, const char *line)
+{
+	HistoryLogObject *h = hbm_find_or_add_object(object);
+	if (!h->max_lines)
+	{
+		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
+		h->max_lines = 50;
+		h->max_time = 86400;
+#endif
+	}
+	if (h->num_lines >= h->max_lines)
+	{
+		/* Delete previous line */
+		hbm_history_del_line(h, h->head);
+	}
+	hbm_history_add_line(h, mtags, line);
+	return 0;
+}
+
+HistoryLogLine *duplicate_log_line(HistoryLogLine *l)
+{
+	HistoryLogLine *n = safe_alloc(sizeof(HistoryLogLine) + strlen(l->line));
+	strcpy(n->line, l->line); /* safe, see memory allocation above ^ */
+	hbm_duplicate_mtags(n, l->mtags);
+	return n;
+}
+
+/** Quickly append a new line 'n' to result 'r' */
+static void hbm_result_append_line(HistoryResult *r, HistoryLogLine *n)
+{
+	if (!r->log)
+	{
+		/* First item */
+		r->log = r->log_tail = n;
+	} else
+	{
+		/* Quick append to tail */
+		r->log_tail->next = n;
+		n->prev = r->log_tail;
+		r->log_tail = n; /* we are the new tail */
+	}
+}
+
+/** Quickly prepend a new line 'n' to result 'r' */
+static void hbm_result_prepend_line(HistoryResult *r, HistoryLogLine *n)
+{
+	if (!r->log)
+		r->log_tail = n;
+	AddListItem(n, r->log);
+}
+
+/** Put lines in HistoryResult that are after a certain msgid or
+ *  timestamp (excluding said msgid/timestamp).
+ * @param r		The history result set that we will use
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ *          which is a perfectly valid result.
+ */
+static int hbm_return_after(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+	HistoryLogLine *l, *n;
+	int written = 0;
+	int started = 0;
+	MessageTag *m;
+
+	for (l = h->head; l; l = l->next)
+	{
+		/* Not started yet? Check if this is the starting point... */
+		if (!started)
+		{
+			if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) > 0))
+			{
+				started = 1;
+			} else
+			if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+			{
+				started = 1;
+				continue;
+			}
+		}
+		if (started)
+		{
+			/* Check if we need to stop */
+			if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) >= 0))
+			{
+				break;
+			} else
+			if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
+			{
+				break;
+			}
+
+			/* Add line to the return buffer */
+			n = duplicate_log_line(l);
+			hbm_result_append_line(r, n);
+			if (++written >= filter->limit)
+				break;
+		}
+	}
+
+	return written;
+}
+
+/** Put lines in HistoryResult that before after a certain msgid or
+ *  timestamp (excluding said msgid/timestamp).
+ * @param r		The history result set that we will use
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ *          which is a perfectly valid result.
+ */
+static int hbm_return_before(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+	HistoryLogLine *l, *n;
+	int written = 0;
+	int started = 0;
+	MessageTag *m;
+
+	for (l = h->tail; l; l = l->prev)
+	{
+		/* Not started yet? Check if this is the starting point... */
+		if (!started)
+		{
+			if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) < 0))
+			{
+				started = 1;
+			} else
+			if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+			{
+				started = 1;
+				continue;
+			}
+		}
+		if (started)
+		{
+			/* Check if we need to stop */
+			if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) < 0))
+			{
+				break;
+			} else
+			if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
+			{
+				break;
+			}
+
+			/* Add line to the return buffer */
+			n = duplicate_log_line(l);
+			hbm_result_prepend_line(r, n);
+			if (++written >= filter->limit)
+				break;
+		}
+	}
+
+	return written;
+}
+
+/** Put lines in HistoryResult that are 'latest'
+ * @param r		The history result set that we will use
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ *          which is a perfectly valid result.
+ */
+static int hbm_return_latest(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+	HistoryLogLine *l, *n;
+	int written = 0;
+	MessageTag *m;
+
+	for (l = h->tail; l; l = l->prev)
+	{
+		if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) <= 0))
+			break; /* Stop now */
+		else
+		if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+			break; /* Stop now */
+
+		n = duplicate_log_line(l);
+		hbm_result_prepend_line(r, n);
+		if (++written >= filter->limit)
+			break;
+	}
+
+	return written;
+}
+
+/** Put lines in HistoryResult based on a 'simple' request, that is: maximum lines or time
+ * @param r		The history result set that we will use
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ *          which is a perfectly valid result.
+ */
+static int hbm_return_simple(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+	HistoryLogLine *l;
+	int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
+	long redline;
+	int written = 0;
+
+	/* Decide on red line, under this the history is too old.
+	 * Filter can be more strict than history object (but not the other way around):
+	 */
+	if (filter && filter->last_seconds && (filter->last_seconds < h->max_time))
+		redline = TStime() - filter->last_seconds;
+	else
+		redline = TStime() - h->max_time;
+
+	/* Once the filter API expands, the following will change too.
+	 * For now, this is sufficient, since requests are only about lines:
+	 */
+	lines_sendable = 0;
+	for (l = h->head; l; l = l->next)
+		if (l->t >= redline)
+			lines_sendable++;
+	if (filter && (lines_sendable > filter->last_lines))
+		lines_to_skip = lines_sendable - filter->last_lines;
+
+	for (l = h->head; l; l = l->next)
+	{
+		/* Make sure we don't send too old entries:
+		 * We only have to check for time here, as line count is already
+		 * taken into account in hbm_history_add.
+		 */
+		if (l->t >= redline && (++cnt > lines_to_skip))
+		{
+			/* Add to result */
+			HistoryLogLine *n = duplicate_log_line(l);
+			hbm_result_append_line(r, n);
+			written++;
+		}
+	}
+
+	return written;
+}
+
+/** Put lines in HistoryResult that are 'around' a certain point.
+ * @param r		The history result set that we will use
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ *          which is a perfectly valid result.
+ */
+static int hbm_return_around(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+	int n = 0;
+	int orig_limit = filter->limit;
+
+	/* First request 50% above the search term */
+	if (filter->limit > 1)
+		filter->limit = filter->limit / 2;
+	n = hbm_return_before(r, h, filter);
+	/* Then the remainder (50% or more) below the search term.
+	 *
+	 * Ok, well, unless the original limit was 1 and we already
+	 * sent 1 line, then we may not send anything anymore..
+	 */
+	filter->limit = orig_limit - n;
+	if (filter->limit > 0)
+		n += hbm_return_after(r, h, filter);
+
+	return n;
+}
+
+/** Figure out the direction (forwards or backwards) for CHATHISTORY BETWEEN request
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns 0 for backward searching, 1 for forward searching, -1 for invalid / not found
+ */
+static int hbm_return_between_figure_out_direction(HistoryLogObject *h, HistoryFilter *filter)
+{
+	HistoryLogLine *l;
+	int found_a = 0;
+	int found_b = 0;
+	MessageTag *m;
+
+	/* Two timestamps? Then we can easily tell the direction. */
+	if (filter->timestamp_a && filter->timestamp_b)
+		return (strcmp(filter->timestamp_a, filter->timestamp_b) <= 0) ? 1 : 0;
+
+	for (l = h->head; l; l = l->next)
+	{
+		if (!found_a)
+		{
+			if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) >= 0))
+			{
+				found_a = 1;
+			} else
+			if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+			{
+				found_a = 1;
+			}
+			if (found_a)
+			{
+				if (found_b)
+				{
+					/* B was found before A? Then the result is: backwards */
+					return 0;
+				}
+				if (filter->timestamp_b && (m = find_mtag(l->mtags, "time")) && m->value)
+				{
+					/* We can already resolve the direction now: */
+					char *timestamp_a = m->value;
+					return (strcmp(timestamp_a, filter->timestamp_b) <= 0) ? 1 : 0;
+				}
+			}
+		}
+		if (!found_b)
+		{
+			if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) >= 0))
+			{
+				found_b = 1;
+			} else
+			if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
+			{
+				found_b = 1;
+			}
+			if (found_b)
+			{
+				if (found_a)
+				{
+					/* A was found before B? Then the result is: forwards */
+					return 1;
+				}
+				if (filter->timestamp_a && (m = find_mtag(l->mtags, "time")) && m->value)
+				{
+					/* We can already resolve the direction now: */
+					char *timestamp_b = m->value;
+					return (strcmp(filter->timestamp_a, timestamp_b) <= 0) ? 1 : 0;
+				}
+			}
+		}
+	}
+
+	/* Neither points were found OR
+	 * one of the point is a msgid that could not be found.
+	 */
+	return -1; /* Result: invalid */
+}
+
+/** Put lines in HistoryResult that are 'between' two points.
+ * @param r		The history result set that we will use
+ * @param h		The history log object
+ * @param filter	The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ *          which is a perfectly valid result.
+ */
+static int hbm_return_between(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+	int direction;
+
+	direction = hbm_return_between_figure_out_direction(h, filter);
+
+	if (direction == 1)
+		return hbm_return_after(r, h, filter);
+	else if (direction == 0)
+		return hbm_return_before(r, h, filter);
+	/* else direction is -1 which means not found / invalid */
+
+	return 0;
+}
+
+HistoryResult *hbm_history_request(const char *object, HistoryFilter *filter)
+{
+	HistoryResult *r;
+	HistoryLogObject *h = hbm_find_object(object);
+	HistoryLogLine *l;
+	int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
+	long redline;
+
+	if (!h)
+		return NULL; /* nothing found */
+
+	/* Check if we need to remove some history entries due to 'time'.
+	 * No need to worry about 'count' as that is being taken care off
+	 * by hbm_history_add().
+	 */
+	if (h->oldest_t < TStime() - h->max_time)
+		hbm_history_cleanup(h);
+
+	r = safe_alloc(sizeof(HistoryResult));
+	safe_strdup(r->object, object);
+
+	switch(filter->cmd)
+	{
+		case HFC_BEFORE:
+			hbm_return_before(r, h, filter);
+			break;
+		case HFC_AFTER:
+			hbm_return_after(r, h, filter);
+			break;
+		case HFC_LATEST:
+			hbm_return_latest(r, h, filter);
+			break;
+		case HFC_AROUND:
+			hbm_return_around(r, h, filter);
+			break;
+		case HFC_BETWEEN:
+			hbm_return_between(r, h, filter);
+			break;
+		case HFC_SIMPLE:
+			hbm_return_simple(r, h, filter);
+			break;
+		default:
+			// unhandled
+			break;
+	}
+
+	return r;
+}
+
+/** Clean up expired entries */
+int hbm_history_cleanup(HistoryLogObject *h)
+{
+	HistoryLogLine *l, *l_next = NULL;
+	long redline = TStime() - h->max_time;
+
+	/* First enforce 'h->max_time', after that enforce 'h->max_lines' */
+
+	/* Checking for time */
+	if (h->oldest_t < redline)
+	{
+		h->oldest_t = 0; /* recalculate in next loop */
+
+		for (l = h->head; l; l = l_next)
+		{
+			l_next = l->next;
+			if (l->t < redline)
+			{
+				hbm_history_del_line(h, l); /* too old, delete it */
+				continue;
+			}
+			if ((h->oldest_t == 0) || (l->t < h->oldest_t))
+				h->oldest_t = l->t;
+		}
+	}
+
+	if (h->num_lines > h->max_lines)
+	{
+		h->oldest_t = 0; /* recalculate in next loop */
+
+		for (l = h->head; l; l = l_next)
+		{
+			l_next = l->next;
+			if (h->num_lines > h->max_lines)
+			{
+				hbm_history_del_line(h, l);
+				continue;
+			}
+			if ((h->oldest_t == 0) || (l->t < h->oldest_t))
+				h->oldest_t = l->t;
+		}
+	}
+
+	return 1;
+}
+
+int hbm_history_destroy(const char *object)
+{
+	HistoryLogObject *h = hbm_find_object(object);
+	HistoryLogLine *l, *l_next;
+
+	if (!h)
+		return 0;
+
+	for (l = h->head; l; l = l_next)
+	{
+		l_next = l->next;
+		/* We could use hbm_history_del_line() here but
+		 * it does unnecessary work, this is quicker.
+		 * The only danger is that we may forget to free some
+		 * fields that are added later there but not here.
+		 */
+		free_message_tags(l->mtags);
+		safe_free(l);
+	}
+
+	hbm_delete_object_hlo(h);
+	return 1;
+}
+
+/** Set new limit on history object */
+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;
+	h->max_time = max_time;
+	hbm_history_cleanup(h); /* impose new restrictions */
+	return 1;
+}
+
+/** Read the master.db file, this is done at the INIT stage so we can still
+ * reject the configuration / boot attempt.
+ *
+ * IMPORTANT: Because we run at INIT you must use test.xyz values and not cfg.xyz!
+ */
+static int hbm_read_masterdb(void)
+{
+	UnrealDB *db;
+	uint32_t mdb_version;
+	char *prehash = NULL;
+	char *posthash = NULL;
+
+	db = unrealdb_open(test.masterdb, UNREALDB_MODE_READ, test.db_secret);
+
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+		{
+			/* Database does not exist. Could be first boot */
+			config_warn("[history] No database present at '%s', will start a new one", test.masterdb);
+			if (!hbm_write_masterdb())
+				return 0; /* fatal error */
+			return 1;
+		} else
+		{
+			config_warn("[history] Unable to open the database file '%s' for reading: %s", test.masterdb, unrealdb_get_error_string());
+			return 0;
+		}
+	}
+
+	/* Master db has an easy format:
+	 * 64 bits: version number
+	 * string:  pre hash
+	 * string:  post hash
+	 */
+	if (!unrealdb_read_int32(db, &mdb_version) ||
+	    !unrealdb_read_str(db, &prehash) ||
+	    !unrealdb_read_str(db, &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;
+	}
+	unrealdb_close(db);
+
+	if (!prehash || !posthash)
+	{
+		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.. */
+	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;
+}
+
+/** Write the master.db file. Only call this if it does not exist yet! */
+static int hbm_write_masterdb(void)
+{
+	UnrealDB *db;
+	uint32_t mdb_version;
+
+	if (!test.db_secret)
+		abort();
+
+	db = unrealdb_open(test.masterdb, UNREALDB_MODE_WRITE, test.db_secret);
+	if (!db)
+	{
+		config_error("[history] Unable to write to '%s': %s",
+			test.masterdb, unrealdb_get_error_string());
+		return 0;
+	}
+
+	if (!hbm_prehash || !hbm_posthash)
+		abort(); /* impossible */
+
+	mdb_version = 5000;
+	if (!unrealdb_write_int32(db, mdb_version) ||
+	    !unrealdb_write_str(db, hbm_prehash) ||
+	    !unrealdb_write_str(db, hbm_posthash))
+	{
+		config_error("[history] Unable to write to '%s': %s",
+			test.masterdb, unrealdb_get_error_string());
+		return 0;
+	}
+	unrealdb_close(db);
+	return 1;
+}
+
+/** Read all database files (except master.db, which is already loaded) */
+static void hbm_read_dbs(void)
+{
+	char buf[512];
+#ifndef _WIN32
+	struct dirent *dir;
+	DIR *fd = opendir(cfg.directory);
+
+	if (!fd)
+		return;
+
+	while ((dir = readdir(fd)))
+	{
+		char *fname = dir->d_name;
+#else
+	/* Windows */
+	WIN32_FIND_DATA hData;
+	HANDLE hFile;
+	char xbuf[512];
+
+	snprintf(xbuf, sizeof(xbuf), "%s/*.db", cfg.directory);
+
+	hFile = FindFirstFile(xbuf, &hData);
+	if (hFile == INVALID_HANDLE_VALUE)
+		return;
+
+	do
+	{
+		char *fname = hData.cFileName;
+#endif
+
+		/* Common section for both *NIX and Windows */
+
+		snprintf(buf, sizeof(buf), "%s/%s", cfg.directory, fname);
+		if (filename_has_suffix(fname, ".db") && strcmp(fname, "master.db"))
+		{
+			if (!hbm_read_db(buf))
+			{
+				/* On error, we move the file to the 'bad' subdirectory,
+				 * eg data/history/bad/xyz.db
+				 */
+				char buf2[512];
+				snprintf(buf2, sizeof(buf2), "%s/bad", cfg.directory);
+#ifdef _WIN32
+				(void)mkdir(buf2); /* (errors ignored) */
+#else
+				(void)mkdir(buf2, S_IRUSR|S_IWUSR|S_IXUSR); /* (errors ignored) */
+#endif
+				snprintf(buf2, sizeof(buf2), "%s/bad/%s", cfg.directory, fname);
+				unlink(buf2);
+				(void)rename(buf, buf2);
+			}
+		}
+
+		/* End of common section */
+#ifndef _WIN32
+	}
+	closedir(fd);
+#else
+	} while (FindNextFile(hFile, &hData));
+	FindClose(hFile);
+#endif
+}
+
+#define RESET_VALUES_LOOP()	do { \
+					safe_free(mtag_name); \
+					safe_free(mtag_value); \
+					safe_free(line); \
+					free_message_tags(mtags); \
+					mtags = NULL; \
+					magic = 0; \
+					line_ts = 0; \
+				} while(0)
+
+#define R_SAFE_CLEANUP()	do { \
+					unrealdb_close(db); \
+					RESET_VALUES_LOOP(); \
+					safe_free(prehash); \
+					safe_free(posthash); \
+					safe_free(object); \
+				} while(0)
+#define R_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			config_warn("[history] Read error from database file '%s' (possible corruption): %s", fname, unrealdb_get_error_string()); \
+			R_SAFE_CLEANUP(); \
+			return 0; \
+		} \
+	} while(0)
+
+
+/** Read a channel history db file */
+static int hbm_read_db(const char *fname)
+{
+	UnrealDB *db = NULL;
+	// header
+	uint32_t magic = 0;
+	uint32_t version = 0;
+	char *prehash = NULL;
+	char *posthash = NULL;
+	char *object = NULL;
+	uint64_t max_lines = 0;
+	uint64_t max_time = 0;
+	// then, for each entry:
+	// (magic)
+	uint64_t line_ts;
+	char *mtag_name = NULL;
+	char *mtag_value = NULL;
+	MessageTag *mtags = NULL, *m;
+	char *line = NULL;
+	HistoryLogObject *h;
+
+	db = unrealdb_open(fname, UNREALDB_MODE_READ, cfg.db_secret);
+	if (!db)
+	{
+		config_warn("[history] Unable to open the database file '%s' for reading: %s", fname, unrealdb_get_error_string());
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_int32(db, &magic));
+	if (magic != HISTORYDB_MAGIC_FILE_START)
+	{
+		config_warn("[history] Database '%s' has wrong magic value, possibly corrupt (0x%lx), expected HISTORYDB_MAGIC_FILE_START.",
+			fname, (long)magic);
+		unrealdb_close(db);
+		return 0;
+	}
+
+	/* Now do a version check */
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version < 4999)
+	{
+		config_warn("[history] Database '%s' uses an unsupported - possibly old - format (%ld).", fname, (long)version);
+		unrealdb_close(db);
+		return 0;
+	}
+	if (version > 5000)
+	{
+		config_warn("[history] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
+			fname, (unsigned long)version, (unsigned long)5000);
+		unrealdb_close(db);
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_str(db, &prehash));
+	R_SAFE(unrealdb_read_str(db, &posthash));
+
+	if (!prehash || !posthash || strcmp(prehash, hbm_prehash) || strcmp(posthash, hbm_posthash))
+	{
+		config_warn("[history] Database '%s' does not belong to our 'master.db'. Are you mixing old with new .db files perhaps? This is not supported. File ignored.",
+			fname);
+		R_SAFE_CLEANUP();
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_str(db, &object));
+	R_SAFE(unrealdb_read_int64(db, &max_lines));
+	R_SAFE(unrealdb_read_int64(db, &max_time));
+	h = hbm_find_object(object);
+	if (!h)
+	{
+		config_warn("Channel %s does not have +H set, deleting history", object);
+		R_SAFE_CLEANUP();
+		unlink(fname);
+		return 1; /* No problem */
+	}
+
+	while(1)
+	{
+		RESET_VALUES_LOOP();
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic == HISTORYDB_MAGIC_FILE_END)
+			break; /* We're done, end gracefully */
+		if (magic != HISTORYDB_MAGIC_ENTRY_START)
+		{
+			config_warn("[history] Read error from database file '%s': wrong magic value in entry (0x%lx), expected HISTORYDB_MAGIC_ENTRY_START",
+				fname, (long)magic);
+			R_SAFE_CLEANUP();
+			return 0;
+		}
+
+		R_SAFE(unrealdb_read_int64(db, &line_ts));
+		while(1)
+		{
+			R_SAFE(unrealdb_read_str(db, &mtag_name));
+			R_SAFE(unrealdb_read_str(db, &mtag_value));
+			if (!mtag_name && !mtag_value)
+				break; /* We're done reading mtags for this particular line */
+			m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, mtag_name);
+			safe_strdup(m->value, mtag_value);
+			AppendListItem(m, mtags);
+			safe_free(mtag_name);
+			safe_free(mtag_value);
+		}
+		R_SAFE(unrealdb_read_str(db, &line));
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic != HISTORYDB_MAGIC_ENTRY_END)
+		{
+			config_warn("[history] Read error from database file '%s': wrong magic value in entry (0x%lx), expected HISTORYDB_MAGIC_ENTRY_END",
+				fname, (long)magic);
+			R_SAFE_CLEANUP();
+			return 0;
+		}
+		hbm_history_add(object, mtags, line);
+	}
+
+	/* Prevent directly rewriting the channel, now that we have just read it.
+	 * This could cause things not to fire in case of corner issues like
+	 * hot-loading but that should be acceptable. The alternative is that
+	 * all log files are written again with identical contents for no reason,
+	 * which is a waste of resources.
+	 */
+	h->dirty = 0;
+
+	R_SAFE_CLEANUP();
+	return 1;
+}
+
+/** Flush all dirty logs to disk on UnrealIRCd stop */
+static void hbm_flush(void)
+{
+	int hashnum;
+	HistoryLogObject *h;
+
+	if (!cfg.persist)
+		return; /* nothing to flush anyway */
+
+	for (hashnum = 0; hashnum < HISTORY_BACKEND_MEM_HASH_TABLE_SIZE; hashnum++)
+	{
+		for (h = history_hash_table[hashnum]; h; h = h->next)
+		{
+			hbm_history_cleanup(h);
+			if (cfg.persist && h->dirty)
+				hbm_write_db(h);
+		}
+	}
+}
+
+/** Free all history.
+ * This is only called when the module is unloaded for good, so
+ * when UnrealIRCd is terminating or someone comments the module out
+ * and/or switches history backends.
+ */
+void hbm_free_all_history(ModData *m)
+{
+	int hashnum;
+	HistoryLogObject *h, *h_next;
+
+	for (hashnum = 0; hashnum < HISTORY_BACKEND_MEM_HASH_TABLE_SIZE; hashnum++)
+	{
+		for (h = history_hash_table[hashnum]; h; h = h_next)
+		{
+			h_next = h->next;
+			hbm_history_destroy(h->name);
+		}
+	}
+
+	/* And free the hash table pointer */
+	safe_free(m->ptr);
+}
+
+/** Periodically clean the history.
+ * Instead of doing all channels in 1 go, we do a limited number
+ * of channels each call, hence the 'static int' and the do { } while
+ * rather than a regular for loop.
+ * Note that we already impose the line limit in hbm_history_add,
+ * so this history_mem_clean is for removals due to max_time limits.
+ */
+EVENT(history_mem_clean)
+{
+	static int hashnum = 0;
+	int loopcnt = 0;
+	Channel *channel;
+	HistoryLogObject *h;
+
+	do
+	{
+		for (h = history_hash_table[hashnum]; h; h = h->next)
+		{
+			hbm_history_cleanup(h);
+			if (cfg.persist && h->dirty)
+				hbm_write_db(h);
+		}
+
+		hashnum++;
+
+		if (hashnum >= HISTORY_BACKEND_MEM_HASH_TABLE_SIZE)
+			hashnum = 0;
+	} while(loopcnt++ < HISTORY_CLEAN_PER_LOOP);
+}
+
+const char *hbm_history_filename(HistoryLogObject *h)
+{
+	static char fname[512];
+	char oname[OBJECTLEN+1];
+	char hashdata[512];
+	char hash[128];
+
+	if (!hbm_prehash || !hbm_posthash)
+		abort(); /* impossible */
+
+	strtolower_safe(oname, h->name, sizeof(oname));
+	snprintf(hashdata, sizeof(hashdata), "%s %s %s", hbm_prehash, oname, hbm_posthash);
+	sha256hash(hash, hashdata, strlen(hashdata));
+
+	snprintf(fname, sizeof(fname), "%s/%s.db", cfg.directory, hash);
+	return fname;
+}
+
+#define WARN_WRITE_ERROR(fname) \
+	do { \
+		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) \
+	do { \
+		if (!(x)) { \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+
+// FIXME: the code below will cause massive floods on disk or I/O errors if hundreds of
+// channel logs fail to write... fun.
+static int hbm_write_db(HistoryLogObject *h)
+{
+	UnrealDB *db;
+	const char *realfname;
+	char tmpfname[512];
+	HistoryLogLine *l;
+	MessageTag *m;
+	Channel *channel;
+
+	if (!cfg.db_secret)
+		abort();
+
+	channel = find_channel(h->name);
+	if (!channel || !has_channel_mode(channel, 'P'))
+		return 1; /* Don't save this channel, pretend success */
+
+	realfname = hbm_history_filename(h);
+	snprintf(tmpfname, sizeof(tmpfname), "%s.tmp", realfname);
+
+	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+	if (!db)
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_FILE_START));
+	W_SAFE(unrealdb_write_int32(db, 5000)); /* VERSION */
+	W_SAFE(unrealdb_write_str(db, hbm_prehash));
+	W_SAFE(unrealdb_write_str(db, hbm_posthash));
+	W_SAFE(unrealdb_write_str(db, h->name));
+
+	W_SAFE(unrealdb_write_int64(db, h->max_lines));
+	W_SAFE(unrealdb_write_int64(db, h->max_time));
+
+	for (l = h->head; l; l = l->next)
+	{
+		W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_ENTRY_START));
+		W_SAFE(unrealdb_write_int64(db, l->t));
+		for (m = l->mtags; m; m = m->next)
+		{
+			W_SAFE(unrealdb_write_str(db, m->name));
+			W_SAFE(unrealdb_write_str(db, m->value)); /* can be NULL */
+		}
+		W_SAFE(unrealdb_write_str(db, NULL));
+		W_SAFE(unrealdb_write_str(db, NULL));
+		W_SAFE(unrealdb_write_str(db, l->line));
+		W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_ENTRY_END));
+	}
+	W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_FILE_END));
+
+	if (!unrealdb_close(db))
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(realfname);
+#endif
+	if (rename(tmpfname, realfname) < 0)
+	{
+		config_error("[history] Error renaming '%s' to '%s': %s (HISTORY NOT SAVED)",
+			tmpfname, realfname, strerror(errno));
+		return 0;
+	}
+
+	/* Now that everything was successful, clear the dirty flag */
+	h->dirty = 0;
+	return 1;
+}
+
+static void hbm_delete_db(HistoryLogObject *h)
+{
+	UnrealDB *db;
+	const char *fname;
+	if (!cfg.persist || !hbm_prehash || !hbm_posthash)
+	{
+#ifdef DEBUGMODE
+		abort(); /* we should not be called, so debug this */
+#endif
+		return;
+	}
+	fname = hbm_history_filename(h);
+	unlink(fname);
+}
+
+void hbm_generic_free(ModData *m)
+{
+	safe_free(m->ptr);
+}
diff --git a/ircd/src/modules/history_backend_null.c b/ircd/src/modules/history_backend_null.c
@@ -0,0 +1,74 @@
+/* src/modules/history_backend_null.c - History Backend: null / none
+ * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+#include "unrealircd.h"
+
+/* This is the null backend type. It does not store anything at all.
+ * This can be useful on a hub server where you don't need channel
+ * history but still need to have a backend loaded to use the
+ * channel mode +H.
+ */
+
+ModuleHeader MOD_HEADER
+= {
+	"history_backend_null",
+	"2.0",
+	"History backend: null/none",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+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()
+{
+	HistoryBackendInfo hbi;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&hbi, 0, sizeof(hbi));
+	hbi.name = "mem";
+	hbi.history_set_limit = hbn_history_set_limit;
+	hbi.history_add = hbn_history_add;
+	hbi.history_request = hbn_history_request;
+	hbi.history_destroy = hbn_history_destroy;
+	if (!HistoryBackendAdd(modinfo->handle, &hbi))
+		return MOD_FAILED;
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int hbn_history_add(const char *object, MessageTag *mtags, const char *line)
+{
+	return 1;
+}
+
+HistoryResult *hbn_history_request(const char *object, HistoryFilter *filter)
+{
+	return NULL;
+}
+
+int hbn_history_set_limit(const char *object, int max_lines, long max_time)
+{
+	return 1;
+}
+
+int hbn_history_destroy(const char *object)
+{
+	return 1;
+}
diff --git a/ircd/src/modules/ident_lookup.c b/ircd/src/modules/ident_lookup.c
@@ -0,0 +1,256 @@
+/* src/modules/ident_lookup.c - Ident lookups (RFC1413)
+ * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"ident_lookup",
+	"1.0",
+	"Ident lookups (RFC1413)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+static EVENT(check_ident_timeout);
+static int ident_lookup_connect(Client *client);
+static void ident_lookup_send(int fd, int revents, void *data);
+static void ident_lookup_receive(int fd, int revents, void *data);
+static char *ident_lookup_parse(Client *client, char *buf);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1); /* needed? or not? */
+	EventAdd(NULL, "check_ident_timeout", check_ident_timeout, NULL, 1000, 0);
+	HookAdd(modinfo->handle, HOOKTYPE_IDENT_LOOKUP, 0, ident_lookup_connect);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+static void ident_lookup_failed(Client *client)
+{
+	ircstats.is_abad++;
+	if (client->local->authfd != -1)
+	{
+		fd_close(client->local->authfd);
+		--OpenFiles;
+		client->local->authfd = -1;
+	}
+	ClearIdentLookupSent(client);
+	ClearIdentLookup(client);
+	if (should_show_connect_info(client))
+		sendto_one(client, NULL, ":%s %s", me.name, REPORT_FAIL_ID);
+}
+
+static EVENT(check_ident_timeout)
+{
+	Client *client, *next;
+
+	list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
+	{
+		if (IsIdentLookup(client))
+		{
+			if (IsIdentLookupSent(client))
+			{
+				/* set::ident::connect-timeout */
+				if ((TStime() - client->local->creationtime) > IDENT_CONNECT_TIMEOUT)
+					ident_lookup_failed(client);
+			} else
+			{
+				/* set::ident::read-timeout */
+				if ((TStime() - client->local->creationtime) > IDENT_READ_TIMEOUT)
+					ident_lookup_failed(client);
+			}
+		}
+	}
+}
+
+/** Start the ident lookup for this user */
+static int ident_lookup_connect(Client *client)
+{
+	char buf[BUFSIZE];
+
+	snprintf(buf, sizeof buf, "identd: %s", get_client_name(client, TRUE));
+	if ((client->local->authfd = fd_socket(IsIPV6(client) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf)) == -1)
+	{
+		ident_lookup_failed(client);
+		return 0;
+	}
+	if (++OpenFiles >= maxclients+1)
+	{
+		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;
+		return 0;
+	}
+
+	if (should_show_connect_info(client))
+		sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_ID);
+
+	set_sock_opts(client->local->authfd, client, IsIPV6(client));
+
+	/* Bind to the IP the user got in */
+	unreal_bind(client->local->authfd, client->local->listener->ip, 0, IsIPV6(client));
+
+	/* And connect... */
+	if (!unreal_connect(client->local->authfd, client->ip, 113, IsIPV6(client)))
+	{
+		ident_lookup_failed(client);
+		return 0;
+	}
+	SetIdentLookupSent(client);
+	SetIdentLookup(client);
+
+	fd_setselect(client->local->authfd, FD_SELECT_WRITE, ident_lookup_send, client);
+
+	return 0;
+}
+
+/** Send the request to the ident server */
+static void ident_lookup_send(int fd, int revents, void *data)
+{
+	char authbuf[32];
+	Client *client = data;
+
+	ircsnprintf(authbuf, sizeof(authbuf), "%d , %d\r\n",
+		client->local->port,
+		client->local->listener->port);
+
+	if (WRITE_SOCK(client->local->authfd, authbuf, strlen(authbuf)) != strlen(authbuf))
+	{
+		if (ERRNO == P_EAGAIN)
+			return; /* Not connected yet, try again later */
+		ident_lookup_failed(client);
+		return;
+	}
+	ClearIdentLookupSent(client);
+
+	fd_setselect(client->local->authfd, FD_SELECT_READ, ident_lookup_receive, client);
+	fd_setselect(client->local->authfd, FD_SELECT_WRITE, NULL, client);
+
+	return;
+}
+
+/** Receive the ident response */
+static void ident_lookup_receive(int fd, int revents, void *userdata)
+{
+	Client *client = userdata;
+	char *ident = NULL;
+	char buf[512];
+	int len;
+
+	len = READ_SOCK(client->local->authfd, buf, sizeof(buf)-1);
+
+	/* We received a response. We don't bother with fragmentation
+	 * since that is not going to happen for such a short string.
+	 * Other IRCd's think the same and this simplifies things a lot.
+	 */
+
+	/* Before we continue, we can already tear down the connection
+	 * and set the appropriate flags that we are finished.
+	 */
+	fd_close(client->local->authfd);
+	--OpenFiles;
+	client->local->authfd = -1;
+	client->local->identbufcnt = 0;
+	ClearIdentLookup(client);
+
+	if (should_show_connect_info(client))
+		sendto_one(client, NULL, ":%s %s", me.name, REPORT_FIN_ID);
+
+	if (len > 0)
+	{
+		buf[len] = '\0'; /* safe, due to the READ_SOCK() being on sizeof(buf)-1 */
+		ident = ident_lookup_parse(client, buf);
+	}
+	if (ident)
+	{
+		strlcpy(client->ident, ident, USERLEN + 1);
+		SetIdentSuccess(client);
+		ircstats.is_asuc++;
+	} else {
+		ircstats.is_abad++;
+	}
+	return;
+}
+
+static char *ident_lookup_parse(Client *client, char *buf)
+{
+	/* <port> , <port> : USERID : <OSTYPE>: <username>
+	 * Actually the only thing we care about is <username>
+	 */
+	int port1 = 0, port2 = 0;
+	char *ostype = NULL;
+	char *username = NULL;
+	char *p, *p2;
+
+	skip_whitespace(&buf);
+	p = strchr(buf, ',');
+	if (!p)
+		return NULL;
+	*p = '\0';
+	port1 = atoi(buf); /* port1 is set */
+
+	/*  <port> : USERID : <OSTYPE>: <username> */
+	buf = p + 1;
+	p = strchr(buf, ':');
+	if (!p)
+		return NULL;
+	*p = '\0';
+	port2 = atoi(buf); /* port2 is set */
+
+	/*  USERID : <OSTYPE>: <username> */
+	buf = p + 1;
+	skip_whitespace(&buf);
+	if (strncmp(buf, "USERID", 6))
+		return NULL;
+	buf += 6; /* skip over strlen("USERID") */
+	skip_whitespace(&buf);
+	if (*buf != ':')
+		return NULL;
+	buf++;
+	skip_whitespace(&buf);
+
+	/*  <OSTYPE>: <username> */
+	p = strchr(buf, ':');
+	if (!p)
+		return NULL;
+
+	/*  <username> */
+	buf = p+1;
+	skip_whitespace(&buf);
+
+	/* Username */
+	// A) Skip any ~ or ^ at the start
+	for (; *buf; buf++)
+		if (!strchr("~^", *buf) && (*buf > 32))
+			break;
+	// B) Stop at the end, IOTW stop at newline, space, etc.
+	for (p=buf; *p; p++)
+	{
+		if (strchr("\n\r@:", *p) || (*p <= 32))
+		{
+			*p = '\0';
+			break;
+		}
+	}
+	if (*buf == '\0')
+		return NULL;
+	return buf;
+}
diff --git a/ircd/src/modules/invite.c b/ircd/src/modules/invite.c
@@ -0,0 +1,542 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/invite.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"
+
+#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);
+
+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
+  = {
+	"invite",
+	"5.0",
+	"command /invite", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, invite_config_test);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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_INVITES(client); inv; inv = inv->next)
+	{
+		sendnumeric(client, RPL_INVITELIST,
+			   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 name
+**  parv[3] - override (S2S only)
+*/
+CMD_FUNC(cmd_invite)
+{
+	Client *target = NULL;
+	Channel *channel = NULL;
+	int override = 0;
+	int i = 0;
+	int params_ok = 0;
+	Hook *h;
+
+	if (parc >= 3 && *parv[1] != '\0')
+	{
+		params_ok = 1;
+		target = find_user(parv[1], NULL);
+		channel = find_channel(parv[2]);
+	}
+	
+	if (!MyConnect(client))
+	/*** remote 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;
+	}
+
+	/*** local invite ***/
+
+	/* the client requested own invite list */
+	if (parc == 1)
+	{
+		send_invite_list(client);
+		return;
+	}
+
+	/* notify user about bad parameters */
+	if (!params_ok)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "INVITE");
+		return;
+	}
+
+	if (!target)
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		return;
+	}
+
+	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);
+		if (i == HOOK_DENY)
+			return;
+		if (i == HOOK_ALLOW)
+			break;
+	}
+
+	if (!IsMember(client, channel) && !IsULine(client))
+	{
+		if (ValidatePermissionsForPath("channel:override:invite:notinchannel",client,NULL,channel,NULL) && client == target)
+		{
+			override = 1;
+		} else {
+			sendnumeric(client, ERR_NOTONCHANNEL, parv[2]);
+			return;
+		}
+	}
+
+	if (IsMember(target, channel))
+	{
+		sendnumeric(client, ERR_USERONCHANNEL, parv[1], parv[2]);
+		return;
+	}
+
+	if (has_channel_mode(channel, 'i'))
+	{
+		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->name);
+				return;
+			}
+		}
+		else if (!IsMember(client, channel) && !IsULine(client))
+		{
+			if (ValidatePermissionsForPath("channel:override:invite:invite-only",client,NULL,channel,NULL) && client == target)
+			{
+				override = 1;
+			} else {
+				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+				return;
+			}
+		}
+	}
+
+	if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN &&
+	    !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->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 (!override)
+	{
+		sendnumeric(client, RPL_INVITING, target->name, channel->name);
+		if (target->user->away)
+		{
+			sendnumeric(client, RPL_AWAY, target->name, target->user->away);
+		}
+	}
+	else
+	{
+		/* Send OperOverride messages */
+		char override_what = '\0';
+		if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
+			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'))
+			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;
+#endif
+		else
+			return;
+	}
+
+	/* 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)
+	{
+		for (tmp = CLIENT_INVITES(to); tmp->next; tmp = tmp->next)
+			;
+		del_invite(to, tmp->value.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 (link_list_length(CHANNEL_INVITES(channel)) >= MAXCHANNELSPERUSER)
+	{
+		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/ircd/src/modules/ircops.c b/ircd/src/modules/ircops.c
@@ -0,0 +1,141 @@
+/*
+ *   cmd_ircops - /IRCOPS command that lists IRC Operators
+ *   (C) Copyright 2004-2016 Syzop <syzop@vulnscan.org>
+ *   (C) Copyright 2003-2004 AngryWolf <angrywolf@flashmail.com>
+ *
+ *   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_IRCOPS        "IRCOPS"
+#define IsAway(x)         (x)->user->away
+
+CMD_FUNC(cmd_ircops);
+
+ModuleHeader MOD_HEADER
+  = {
+	"ircops",
+	"3.71",
+	"/IRCOPS command that lists IRC Operators",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (CommandExists(MSG_IRCOPS))
+	{
+		config_error("Command " MSG_IRCOPS " already exists");
+		return MOD_FAILED;
+	}
+	CommandAdd(modinfo->handle, MSG_IRCOPS, cmd_ircops, MAXPARA, CMD_USER);
+
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("Error adding command " MSG_IRCOPS ": %s",
+			ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/*
+ * cmd_ircops
+ *
+ *     parv[0]: sender prefix
+ *
+ *     Originally comes from TR-IRCD, but I changed it in several places.
+ *     In addition, I didn't like to display network name. In addition,
+ *     instead of realname, servername is shown. See the original
+ *     header below.
+ */
+
+/************************************************************************
+ * IRC - Internet Relay Chat, modules/ircops.c
+ *
+ *   Copyright (C) 2000-2002 TR-IRCD Development
+ *
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *                      University of Oulu, Co Center
+ *
+ * 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 2, 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.
+ */
+
+CMD_FUNC(cmd_ircops)
+{
+	Client *acptr;
+	char buf[512];
+	int opers = 0, total = 0, aways = 0;
+
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		/* List only real IRC Operators */
+		if (IsULine(acptr) || !IsUser(acptr) || !IsOper(acptr))
+			continue;
+		/* Don't list +H users */
+		if (!IsOper(client) && IsHideOper(acptr))
+			continue;
+
+		sendto_one(client, NULL, ":%s %d %s :\2%s\2 is %s on %s" "%s",
+			me.name, RPL_TEXT, client->name,
+			acptr->name,
+			"an IRC Operator", /* find_otype(acptr->umodes), */
+			acptr->user->server,
+			(IsAway(acptr) ? " [Away]" : ""));
+
+		if (IsAway(acptr))
+			aways++;
+		else
+			opers++;
+
+	}
+
+	total = opers + aways;
+
+	snprintf(buf, sizeof(buf),
+		"Total: \2%d\2 IRCOP%s online - \2%d\2 Oper%s available and \2%d\2 Away",
+		total, (total) != 1 ? "s" : "",
+		opers, opers != 1 ? "s" : "",
+		aways);
+
+	sendnumericfmt(client, RPL_TEXT, ":%s", buf);
+	sendnumericfmt(client, RPL_TEXT, ":End of /IRCOPS list");
+}
diff --git a/ircd/src/modules/ison.c b/ircd/src/modules/ison.c
@@ -0,0 +1,105 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/ison.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_ison);
+
+#define MSG_ISON 	"ISON"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"ison",
+	"5.0",
+	"command /ison", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_ISON, cmd_ison, 1, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * cmd_ison added by Darren Reed 13/8/91 to act as an efficent user indicator
+ * with respect to cpu/bandwidth used. Implemented for NOTIFY feature in
+ * clients. Designed to reduce number of whois requests. Can process
+ * nicknames in batches as long as the maximum buffer length.
+ *
+ * format:
+ * ISON :nicklist
+ */
+
+CMD_FUNC(cmd_ison)
+{
+	char buf[BUFSIZE];
+	char request[BUFSIZE];
+	char namebuf[USERLEN + HOSTLEN + 4];
+	Client *acptr;
+	char *s, *user;
+	char *p = NULL;
+
+	if (!MyUser(client))
+		return;
+
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "ISON");
+		return;
+	}
+
+	ircsnprintf(buf, sizeof(buf), ":%s %d %s :", me.name, RPL_ISON, client->name);
+
+	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_user(s, NULL)))
+		{
+			if (user)
+			{
+				ircsnprintf(namebuf, sizeof(namebuf), "%s@%s", acptr->user->username, GetHost(acptr));
+				if (!match_simple(user, namebuf)) continue;
+				*--user = '!';
+			}
+
+			strlcat(buf, s, sizeof(buf));
+			strlcat(buf, " ", sizeof(buf));
+		}
+	}
+
+	sendto_one(client, NULL, "%s", buf);
+}
diff --git a/ircd/src/modules/issued-by-tag.c b/ircd/src/modules/issued-by-tag.c
@@ -0,0 +1,155 @@
+/*
+ * unrealircd.org/issued-by message tag (server only)
+ * Shows who or what actually issued the command.
+ * (C) Copyright 2023-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"issued-by-tag",
+	"6.0",
+	"unrealircd.org/issued-by message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Forward declarations */
+int issued_by_mtag_is_ok(Client *client, const char *name, const char *value);
+int issued_by_mtag_should_send_to_client(Client *target);
+void mtag_inherit_issued_by(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
+void _mtag_add_issued_by(MessageTag **mtags, Client *client, MessageTag *recv_mtags);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_MTAG_GENERATE_ISSUED_BY_IRC, _mtag_add_issued_by);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/issued-by";
+	mtag.is_ok = issued_by_mtag_is_ok;
+	mtag.should_send_to_client = issued_by_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_inherit_issued_by);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending 'unrealircd.org/issued-by'
+ * is permitted to do so and uses a permitted syntax.
+ * We simply allow unrealircd.org/issued-by ONLY from servers and with any syntax.
+ */
+int issued_by_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_inherit_issued_by(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/issued-by");
+	if (m)
+	{
+		m = duplicate_mtag(m);
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int issued_by_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target) || IsOper(target))
+		return 1;
+
+	return 0;
+}
+
+/** Add "unrealircd.org/issued-by" tag, if applicable.
+ * @param mtags		Pointer to the message tags linked list head
+ * @param client	The client issuing the command, or NULL for none.
+ * @param recv_mtags	The mtags to inherit from, or NULL for none.
+ * @notes If specifying both 'client' and 'recv_mtags' then
+ * if inheritance through 'recv_mtags' takes precedence (if it exists).
+ *
+ * Typical usage is:
+ * For locally generated:
+ *   mtag_add_issued_by(&mtags, client, NULL);
+ * For inheriting from remote requests:
+ *   mtag_add_issued_by(&mtags, NULL, recv_mtags);
+ * For both, such as if the command is used from RPC:
+ *   mtag_add_issued_by(&mtags, client, recv_mtags);
+ */
+void _mtag_add_issued_by(MessageTag **mtags, Client *client, MessageTag *recv_mtags)
+{
+	MessageTag *m;
+	char buf[512];
+
+	m = find_mtag(recv_mtags, "unrealircd.org/issued-by");
+	if (m)
+	{
+		m = duplicate_mtag(m);
+		AddListItem(m, *mtags);
+		return;
+	}
+
+	if (client == NULL)
+		return;
+
+	if (IsRPC(client) && client->rpc)
+	{
+		// TODO: test with all of: local rpc through unix socket, local rpc web, RRPC
+		if (client->rpc->issuer)
+		{
+			snprintf(buf, sizeof(buf), "RPC:%s@%s:%s", client->rpc->rpc_user, client->uplink->name, client->rpc->issuer);
+		} else {
+			snprintf(buf, sizeof(buf), "RPC:%s@%s", client->rpc->rpc_user, client->uplink->name);
+		}
+	} else
+	if (IsULine(client))
+	{
+		if (IsUser(client))
+			snprintf(buf, sizeof(buf), "SERVICES:%s@%s", client->name, client->uplink->name);
+		else
+			snprintf(buf, sizeof(buf), "SERVICES:%s", client->name);
+	} else
+	if (IsOper(client))
+	{
+		const char *operlogin = moddata_client_get(client, "operlogin");
+		if (operlogin)
+			snprintf(buf, sizeof(buf), "OPER:%s@%s:%s", client->name, client->uplink->name, operlogin);
+		else
+			snprintf(buf, sizeof(buf), "OPER:%s@%s", client->name, client->uplink->name);
+	} else
+	{
+		return;
+	}
+
+	m = safe_alloc(sizeof(MessageTag));
+	safe_strdup(m->name, "unrealircd.org/issued-by");
+	safe_strdup(m->value, buf);
+	AddListItem(m, *mtags);
+}
diff --git a/ircd/src/modules/join.c b/ircd/src/modules/join.c
@@ -0,0 +1,615 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/join.c
+ *   (C) 2005 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"
+
+/* Forward declarations */
+CMD_FUNC(cmd_join);
+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;
+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 */
+#define MSG_JOIN 	"JOIN"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"join",
+	"5.0",
+	"command /join", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	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_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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* This function checks if a locally connected user may join the channel.
+ * It also provides an number of hooks where modules can plug in to.
+ * Note that the order of checking has been carefully thought of
+ * (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, const char *key, char **errmsg)
+{
+	Hook *h;
+
+	/* An /INVITE lets you bypass all restrictions */
+	if (is_invited(client, channel))
+	{
+		int j = 0;
+		for (h = Hooks[HOOKTYPE_INVITE_BYPASS]; h; h = h->next)
+		{
+			j = (*(h->func.intfunc))(client,channel);
+			if (j != 0)
+				break;
+		}
+		/* Bypass is OK, unless a HOOKTYPE_INVITE_BYPASS hook returns HOOK_DENY */
+		if (j != HOOK_DENY)
+			return 0;
+	}
+
+	for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next)
+	{
+		int i = (*(h->func.intfunc))(client,channel,key, errmsg);
+		if (i != 0)
+			return i;
+	}
+
+	/* See if we can evade this ban */
+	if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
+	{
+		*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->name))
+	{
+		*errmsg = STR_ERR_OPERSPVERIFY;
+		return (ERR_OPERSPVERIFY);
+	}
+#endif
+#endif
+
+	return 0;
+}
+
+/*
+** cmd_join
+**	parv[1] = channel
+**	parv[2] = channel password (key)
+**
+** Due to message tags, remote servers should only send 1 channel
+** per JOIN. Or even better, use SJOIN instead.
+** Otherwise we cannot use unique msgid's and such.
+** UnrealIRCd 4 and probably UnrealIRCd 3.2.something already do
+** this, so this comment is mostly for services coders, I guess.
+*/
+CMD_FUNC(cmd_join)
+{
+	int r;
+
+	if (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))
+		return;
+	do_join(client, parc, parv);
+	bouncedtimes = 0;
+}
+
+/** Send JOIN message for 'client' to all users in 'channel'.
+ * Taking into account that not everyone in channel should see the JOIN (mode +D)
+ * and taking into account the different types of JOIN (due to CAP extended-join).
+ */
+void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags)
+{
+	int chanops_only = invisible_user_in_channel(client, channel);
+	Member *lp;
+	Client *acptr;
+	char joinbuf[512];
+	char exjoinbuf[512];
+
+	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);
+
+	for (lp = channel->members; lp; lp = lp->next)
+	{
+		acptr = lp->client;
+
+		if (!MyConnect(acptr))
+			continue; /* only locally connected clients */
+
+		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);
+	}
+}
+
+/* Routine that actually makes a user join the channel
+ * this does no actual checking (banned, etc.) it just adds the user.
+ * Note: this is called for local JOIN and remote JOIN, but not for SJOIN.
+ */
+void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, const char *member_modes)
+{
+	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) */
+	const char *parv[3];
+
+	/* Same way as in SJOIN */
+	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, 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->name, modes_to_sjoin_prefix(member_modes), client->id);
+
+	if (MyUser(client))
+	{
+		/*
+		   ** Make a (temporal) creationtime, if someone joins
+		   ** during a net.reconnect : between remote join and
+		   ** the mode with TS. --Run
+		 */
+		if (channel->creationtime == 0)
+		{
+			channel->creationtime = TStime();
+			sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld",
+			    me.id, channel->name, (long long)channel->creationtime);
+		}
+
+		if (channel->topic)
+		{
+			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 && MODES_ON_JOIN)
+		{
+			MessageTag *mtags_mode = NULL;
+			Cmode *cm;
+			char modebuf[BUFSIZE], parabuf[BUFSIZE];
+			int should_destroy = 0;
+
+			channel->mode.mode = MODES_ON_JOIN;
+
+			/* Param fun */
+			for (cm=channelmodes; cm; cm = cm->next)
+			{
+				if (!cm->letter || !cm->paracount)
+					continue;
+				if (channel->mode.mode & cm->mode)
+				        cm_putparameter(channel, cm->letter, iConf.modes_on_join.extparams[cm->letter]);
+			}
+
+			*modebuf = *parabuf = 0;
+			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->name, modebuf, parabuf);
+			sendto_server(NULL, 0, 0, mtags_mode, ":%s MODE %s %s %s %lld",
+			    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);
+			RunHook(HOOKTYPE_LOCAL_CHANMODE, &me, channel, mtags_mode, modebuf, parabuf, 0, 0, &should_destroy);
+			free_message_tags(mtags_mode);
+		}
+
+		parv[0] = NULL;
+		parv[1] = channel->name;
+		parv[2] = NULL;
+		do_cmd(client, NULL, "NAMES", 2, 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 {
+		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);
+	free_message_tags(mtags_sjoin);
+}
+
+/** User request to join a channel.
+ * This routine is normally called from cmd_join but can also be called from
+ * do_join->can_join->link module->do_join if the channel is 'linked' (chmode +L).
+ * We therefore use a counter 'bouncedtimes' which is set to 0 in cmd_join,
+ * 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, const char *parv[])
+{
+	char request[BUFSIZE];
+	char request_key[BUFSIZE];
+	char jbuf[BUFSIZE], jbuf2[BUFSIZE];
+	const char *orig_parv1;
+	Membership *lp;
+	Channel *channel;
+	char *name, *key = NULL;
+	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)
+
+	if (parc < 2 || *parv[1] == '\0')
+	{
+		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 */
+
+	if (bouncedtimes > MAXBOUNCE)
+	{
+		/* bounced too many times. yeah.. should be in the link module, I know.. then again, who cares.. */
+		sendnotice(client, "*** Couldn't join %s ! - Link setting was too bouncy", parv[1]);
+		RET();
+	}
+
+	*jbuf = '\0';
+	/*
+	   ** Rebuild list of channels joined to be the actual result of the
+	   ** JOIN.  Note that "JOIN 0" is the destructive problem.
+	 */
+	strlcpy(request, parv[1], sizeof(request));
+	for (i = 0, name = strtoken(&p, request, ","); name; i++, name = strtoken(&p, NULL, ","))
+	{
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "JOIN");
+			break;
+		}
+		if (*name == '0' && !atoi(name))
+		{
+			/* 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...
+			 * We still support it in remote joins for compatibility.
+			 */
+			if (MyUser(client) && (i != 0))
+				continue;
+			strlcpy(jbuf, "0", sizeof(jbuf));
+			continue;
+		} else
+		if (MyConnect(client) && !valid_channelname(name))
+		{
+			send_invalid_channelname(client, name);
+			if (IsOper(client) && find_channel(name))
+			{
+				/* Give IRCOps a bit more information */
+				sendnotice(client, "Channel '%s' is unjoinable because it contains illegal characters. "
+				                   "However, it does exist because another server in your "
+				                   "network, which has a more loose restriction, created it. "
+				                   "See https://www.unrealircd.org/docs/Set_block#set::allowed-channelchars",
+				                   name);
+			}
+			continue;
+		}
+		else if (!IsChannelName(name))
+		{
+			if (MyUser(client))
+				sendnumeric(client, ERR_NOSUCHCHANNEL, name);
+			continue;
+		}
+		if (*jbuf)
+			strlcat(jbuf, ",", sizeof jbuf);
+		strlcat(jbuf, name, sizeof(jbuf));
+	}
+
+	/* We are going to overwrite 'jbuf' with the calls to strtoken()
+	 * a few lines further down. Copy it to 'jbuf2' and make that
+	 * the new parv[1].. or at least temporarily.
+	 */
+	strlcpy(jbuf2, jbuf, sizeof(jbuf2));
+	parv[1] = jbuf2;
+
+	p = NULL;
+	if (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, ",");
+	     name;
+	     key = key ? strtoken(&p2, NULL, ",") : NULL, name = strtoken(&p, NULL, ","))
+	{
+		MessageTag *mtags = NULL;
+
+		/*
+		   ** JOIN 0 sends out a part for all channels a user
+		   ** has joined.
+		 */
+		if (*name == '0' && !atoi(name))
+		{
+			/* Rewritten so to generate a PART for each channel to servers,
+			 * so the same msgid is used for each part on all servers. -- Syzop
+			 */
+			while ((lp = client->user->channel))
+			{
+				MessageTag *mtags = NULL;
+				channel = lp->channel;
+
+				new_message(client, NULL, &mtags);
+
+				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
+				               ":%s PART %s :%s",
+				               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))
+					RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels");
+
+				remove_user_from_channel(client, channel, 0);
+				free_message_tags(mtags);
+			}
+			continue;
+		}
+
+		if (MyConnect(client))
+		{
+			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)
+				{
+					sendnumeric(client, ERR_TOOMANYCHANNELS, name);
+					RET();
+				}
+/* RESTRICTCHAN */
+			if (conf_deny_channel)
+			{
+				if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL))
+				{
+					ConfigItem_deny_channel *d;
+					if ((d = find_channel_allowed(client, name)))
+					{
+						if (d->warn)
+						{
+							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);
+						if (d->redirect)
+						{
+							sendnotice(client, "*** Redirecting you to %s", d->redirect);
+							parv[0] = NULL;
+							parv[1] = d->redirect;
+							do_join(client, 2, parv);
+						}
+						if (d->class)
+							sendnotice(client, "*** Can not join %s: Your class is not allowed", name);
+						continue;
+					}
+				}
+			}
+			if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL) && (tklban = find_qline(client, name, &ishold)))
+			{
+				sendnumeric(client, ERR_FORBIDDENCHANNEL, name, tklban->ptr.nameban->reason);
+				continue;
+			}
+			/* ugly set::spamfilter::virus-help-channel-deny hack.. */
+			if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN &&
+			    !strcasecmp(name, SPAMFILTER_VIRUSCHAN) &&
+			    !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL) && !spamf_ugly_vchanoverride)
+			{
+				Channel *channel = find_channel(name);
+				
+				if (!channel || !is_invited(client, channel))
+				{
+					sendnotice(client, "*** Cannot join '%s' because it's the virus-help-channel "
+					                   "which is reserved for infected users only", name);
+					continue;
+				}
+			}
+		}
+
+		channel = make_channel(name);
+		if (channel && (lp = find_membership_link(client->user->channel, channel)))
+			continue;
+
+		if (!channel)
+			continue;
+
+		i = HOOK_CONTINUE;
+		if (!MyConnect(client))
+			member_modes = "";
+		else
+		{
+			Hook *h;
+			char *errmsg = NULL;
+			for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) 
+			{
+				i = (*(h->func.intfunc))(client,channel,key);
+				if (i == HOOK_DENY || i == HOOK_ALLOW)
+					break;
+			}
+			/* Denied, get out now! */
+			if (i == HOOK_DENY)
+			{
+				/* Rejected... if we just created a new chan we should destroy it too. -- Syzop */
+				if (!channel->users)
+					sub1_from_channel(channel);
+				continue;
+			}
+			/* If they are allowed, don't check can_join */
+			if (i != HOOK_ALLOW && 
+			   (i = can_join(client, channel, key, &errmsg)))
+			{
+				if (i != -1)
+					send_cannot_join_error(client, i, errmsg, name);
+				continue;
+			}
+		}
+
+		/* Generate a new message without inheritance.
+		 * We can do this because remote joins don't follow this code path,
+		 * or are highly discouraged to anyway.
+		 * Remote servers use SJOIN and never reach this function.
+		 * Locally we do follow this code path with JOIN and then generating
+		 * a new_message() here is exactly what we want:
+		 * Each "JOIN #a,#b,#c" gets processed individually in this loop
+		 * and is sent by join_channel() as a SJOIN for #a, then SJOIN for #b,
+		 * and so on, each with their own unique msgid and such.
+		 */
+		new_message(client, NULL, &mtags);
+		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, const char *member_flags)
+{
+	static char modebuf[512]; /* returned */
+	char flagbuf[8]; /* For holding "vhoaq" */
+	char parabuf[512];
+	int n, i;
+
+	if (BadPtr(member_flags))
+		return "";
+
+	parabuf[0] = '\0';
+	n = strlen(member_flags);
+	if (n)
+	{
+		for (i=0; i < n; i++)
+		{
+			strlcat(parabuf, client->name, sizeof(parabuf));
+			if (i < n - 1)
+				strlcat(parabuf, " ", sizeof(parabuf));
+		}
+		/* And we have our mode line! */
+		snprintf(modebuf, sizeof(modebuf), "+%s %s", member_flags, parabuf);
+		return modebuf;
+	}
+
+	return "";
+}
diff --git a/ircd/src/modules/jointhrottle.c b/ircd/src/modules/jointhrottle.c
@@ -0,0 +1,248 @@
+/*
+ * Jointhrottle (set::anti-flood::join-flood).
+ * (C) Copyright 2005-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This was PREVIOUSLY channel mode +j but has been moved to the
+ * set::anti-flood::join-flood block instead since people rarely need
+ * to tweak this per-channel and it's nice to have this on by default.
+ *
+ * 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
+  = {
+	"jointhrottle",
+	"5.0",
+	"Join flood protection (set::anti-flood::join-flood)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+ModuleInfo *ModInfo = NULL;
+
+ModDataInfo *jointhrottle_md; /* Module Data structure which we acquire */
+
+typedef struct JoinFlood JoinFlood;
+
+struct JoinFlood {
+	JoinFlood *prev, *next;
+	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, 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);
+JoinFlood *jointhrottle_addentry(Client *client, Channel *channel);
+
+MOD_TEST()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModInfo = modinfo;
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "jointhrottle";
+	mreq.free = jointhrottle_md_free;
+	mreq.serialize = NULL; /* not supported */
+	mreq.unserialize = NULL; /* not supported */
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	jointhrottle_md = ModDataAdd(modinfo->handle, mreq);
+	if (!jointhrottle_md)
+		abort();
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, jointhrottle_can_join);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, jointhrottle_local_join);
+	
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	EventAdd(ModInfo->handle, "jointhrottle_cleanup_structs", jointhrottle_cleanup_structs, NULL, 60000, 0);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_FAILED;
+}
+
+static int isjthrottled(Client *client, Channel *channel)
+{
+	JoinFlood *e;
+	FloodSettings *settings = get_floodsettings_for_user(client, FLD_JOIN);
+
+	if (!MyUser(client))
+		return 0;
+
+	/* Grab user<->chan entry.. */
+	for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
+		if (!strcasecmp(e->name, channel->name))
+			break;
+	
+	if (!e)
+		return 0; /* Not present, so cannot be throttled */
+
+	/* Ok... now the actual check:
+	 * if ([timer valid] && [one more join would exceed num])
+	 */
+	if (((TStime() - e->firstjoin) < settings->period[FLD_JOIN]) &&
+	    (e->numjoins >= settings->limit[FLD_JOIN]))
+		return 1; /* Throttled */
+
+	return 0;
+}
+
+static void jointhrottle_increase_usercounter(Client *client, Channel *channel)
+{
+	JoinFlood *e;
+
+	if (!MyUser(client))
+		return;
+		
+	/* Grab user<->chan entry.. */
+	for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
+		if (!strcasecmp(e->name, channel->name))
+			break;
+	
+	if (!e)
+	{
+		/* Allocate one */
+		e = jointhrottle_addentry(client, channel);
+		e->firstjoin = TStime();
+		e->numjoins = 1;
+	} else
+	if ((TStime() - e->firstjoin) < iConf.floodsettings->period[FLD_JOIN]) /* still valid? */
+	{
+		e->numjoins++;
+	} else {
+		/* reset :p */
+		e->firstjoin = TStime();
+		e->numjoins = 1;
+	}
+}
+
+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)
+{
+	jointhrottle_increase_usercounter(client, channel);
+	return 0;
+}
+
+/** Adds a JoinFlood entry to user & channel and returns entry.
+ * NOTE: Does not check for already-existing-entry
+ */
+JoinFlood *jointhrottle_addentry(Client *client, Channel *channel)
+{
+	JoinFlood *e;
+
+#ifdef DEBUGMODE
+	if (!IsUser(client))
+		abort();
+
+	for (e=moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
+		if (!strcasecmp(e->name, channel->name))
+			abort(); /* already exists -- should never happen */
+#endif
+
+	e = safe_alloc(sizeof(JoinFlood));
+	strlcpy(e->name, channel->name, sizeof(e->name));
+
+	/* Insert our new entry as (new) head */
+	if (moddata_local_client(client, jointhrottle_md).ptr)
+	{
+		JoinFlood *current_head = moddata_local_client(client, jointhrottle_md).ptr;
+		current_head->prev = e;
+		e->next = current_head;
+	}
+	moddata_local_client(client, jointhrottle_md).ptr = e;
+
+	return e;
+}
+
+/** Regularly cleans up user/chan structs */
+EVENT(jointhrottle_cleanup_structs)
+{
+	Client *client;
+	JoinFlood *jf, *jf_next;
+	
+	list_for_each_entry(client, &lclient_list, lclient_node)
+	{
+		if (!MyUser(client))
+			continue; /* only (local) persons.. */
+
+		for (jf = moddata_local_client(client, jointhrottle_md).ptr; jf; jf = jf_next)
+		{
+			jf_next = jf->next;
+			
+			if (jf->firstjoin + iConf.floodsettings->period[FLD_JOIN] > TStime())
+				continue; /* still valid entry */
+			if (moddata_local_client(client, jointhrottle_md).ptr == jf)
+			{
+				/* change head */
+				moddata_local_client(client, jointhrottle_md).ptr = jf->next; /* could be set to NULL now */
+				if (jf->next)
+					jf->next->prev = NULL;
+			} else {
+				/* change non-head entries */
+				jf->prev->next = jf->next; /* could be set to NULL now */
+				if (jf->next)
+					jf->next->prev = jf->prev;
+			}
+			safe_free(jf);
+		}
+	}
+}
+
+void jointhrottle_md_free(ModData *m)
+{
+	JoinFlood *j, *j_next;
+
+	if (!m->ptr)
+		return;
+
+	for (j = m->ptr; j; j = j_next)
+	{
+		j_next = j->next;
+		safe_free(j);
+	}	
+
+	m->ptr = NULL;
+}
diff --git a/ircd/src/modules/json-log-tag.c b/ircd/src/modules/json-log-tag.c
@@ -0,0 +1,97 @@
+/*
+ *   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_TEST()
+{
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -1000000001); /* load very early */
+	return MOD_SUCCESS;
+}
+
+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);
+
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, 1000000001); /* unload very late */
+	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/ircd/src/modules/jumpserver.c b/ircd/src/modules/jumpserver.c
@@ -0,0 +1,271 @@
+/*
+ * JumpServer: This module can redirect clients to another server.
+ * (C) Copyright 2004-2016 Bram Matthys (Syzop).
+ *
+ * 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
+  = {
+	"jumpserver",
+	"1.1",
+	"/jumpserver command",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+CMD_FUNC(cmd_jumpserver);
+int jumpserver_preconnect(Client *);
+void jumpserver_free_jss(ModData *m);
+
+/* Jumpserver status struct */
+typedef struct JSS JSS;
+struct JSS
+{
+	char *reason;
+	char *server;
+	int port;
+	char *tls_server;
+	int tls_port;
+};
+
+JSS *jss=NULL; /**< JumpServer Status. NULL=disabled. */
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	LoadPersistentPointer(modinfo, jss, jumpserver_free_jss);
+	CommandAdd(modinfo->handle, "JUMPSERVER", cmd_jumpserver, 3, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, jumpserver_preconnect);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	SavePersistentPointer(modinfo, jss);
+	return MOD_SUCCESS;
+}
+
+static void do_jumpserver_exit_client(Client *client)
+{
+	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);
+}
+
+static void redirect_all_clients(void)
+{
+	int count = 0;
+	Client *client, *next;
+
+	list_for_each_entry_safe(client, next, &lclient_list, lclient_node)
+	{
+		if (IsUser(client) && !IsOper(client))
+		{
+			do_jumpserver_exit_client(client);
+			count++;
+		}
+	}
+	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)
+{
+	if (jss)
+	{
+		do_jumpserver_exit_client(client);
+		return HOOK_DENY;
+	}
+	return HOOK_CONTINUE;
+}
+
+void free_jss(void)
+{
+	if (jss)
+	{
+		safe_free(jss->server);
+		safe_free(jss->reason);
+		safe_free(jss->tls_server);
+		safe_free(jss);
+		jss = NULL;
+	}
+}
+
+void jumpserver_free_jss(ModData *m)
+{
+	free_jss();
+}
+
+CMD_FUNC(cmd_jumpserver)
+{
+	char *serv, *tlsserv=NULL, *p;
+	const char *reason;
+	int all=0, port=6667, sslport=6697;
+	char request[BUFSIZE];
+	char logbuf[512];
+
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		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'",
+				jss->server, jss->port, jss->reason);
+		else
+			sendnotice(client, "JumpServer is \002DISABLED\002");
+		return;
+	}
+
+	if ((parc > 1) && (!strcasecmp(parv[1], "OFF") || !strcasecmp(parv[1], "STOP")))
+	{
+		if (!jss)
+		{
+			sendnotice(client, "JUMPSERVER: No redirect active (already OFF)");
+			return;
+		}
+		free_jss();
+		unreal_log(ULOG_INFO, "jumpserver", "JUMPSERVER_DISABLED", client,
+		           "[jumpserver] $client.details turned jumpserver OFF");
+		return;
+	}
+
+	if (parc < 4)
+	{
+		/* Waah, pretty verbose usage info ;) */
+		sendnotice(client, "Use: /JUMPSERVER <server>[: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");
+		sendnotice(client, "And then for example 10 minutes later...");
+		sendnotice(client, "         /JUMPSERVER irc2.test.net ALL This server will be upgraded, please use irc2.test.net for now");
+		sendnotice(client, "Use: '/JUMPSERVER OFF' to turn off any redirects");
+		return;
+	}
+
+	/* Parsing code follows... */
+
+	strlcpy(request, parv[1], sizeof(request));
+	serv = request;
+	
+	p = strchr(serv, '/');
+	if (p)
+	{
+		*p = '\0';
+		tlsserv = p+1;
+	}
+	
+	p = strchr(serv, ':');
+	if (p)
+	{
+		*p++ = '\0';
+		port = atoi(p);
+		if ((port < 1) || (port > 65535))
+		{
+			sendnotice(client, "Invalid serverport specified (%d)", port);
+			return;
+		}
+	}
+	if (tlsserv)
+	{
+		p = strchr(tlsserv, ':');
+		if (p)
+		{
+			*p++ = '\0';
+			sslport = atoi(p);
+			if ((sslport < 1) || (sslport > 65535))
+			{
+				sendnotice(client, "Invalid TLS serverport specified (%d)", sslport);
+				return;
+			}
+		}
+		if (!*tlsserv)
+			tlsserv = NULL;
+	}
+	if (!strcasecmp(parv[2], "new"))
+		all = 0;
+	else if (!strcasecmp(parv[2], "all"))
+		all = 1;
+	else {
+		sendnotice(client, "ERROR: Invalid action '%s', should be 'NEW' or 'ALL' (see /jumpserver help for usage)", parv[2]);
+		return;
+	}
+
+	reason = parv[3];
+
+	/* Free any old stuff (needed!) */
+	if (jss)
+		free_jss();
+
+	jss = safe_alloc(sizeof(JSS));
+
+	/* Set it */
+	safe_strdup(jss->server, serv);
+	jss->port = port;
+	if (tlsserv)
+	{
+		safe_strdup(jss->tls_server, tlsserv);
+		jss->tls_port = sslport;
+	}
+	safe_strdup(jss->reason, reason);
+
+	/* Broadcast/log */
+	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/ircd/src/modules/kick.c b/ircd/src/modules/kick.c
@@ -0,0 +1,369 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/kick.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"
+
+ModuleHeader MOD_HEADER
+  = {
+	"kick",
+	"5.0",
+	"command /kick",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+CMD_FUNC(cmd_kick);
+void _kick_user(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_KICK_USER, _kick_user);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "KICK", cmd_kick, 3, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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
+ * @param client	The evil user doing the kick, can be &me
+ * @param victim	The target user that will be kicked
+ * @param comment	The KICK comment (cannot be NULL)
+ * @notes The msgid in initial_mtags is actually used as a prefix.
+ *        The actual mtag will be "initial_mtags_msgid-suffix_msgid"
+ *        All this is done in order for message tags to be
+ *        consistent accross servers.
+ *        The suffix is necessary to handle multi-target-kicks.
+ *        If initial_mtags is NULL then we will autogenerate one.
+ */
+void _kick_user(MessageTag *initial_mtags, Channel *channel, Client *client, Client *victim, char *comment)
+{
+	MessageTag *mtags = NULL;
+	int initial_mtags_generated = 0;
+
+	if (!initial_mtags)
+	{
+		/* Yeah, we allow callers to be lazy.. */
+		initial_mtags_generated = 1;
+		new_message(client, NULL, &initial_mtags);
+	}
+
+	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))
+		RunHook(HOOKTYPE_LOCAL_KICK, client, victim, channel, mtags, comment);
+	else
+		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,
+			       "h", 0,
+			       SEND_LOCAL, mtags,
+			       ":%s KICK %s %s :%s",
+			       client->name, channel->name, victim->name, comment);
+
+		if (MyUser(victim))
+		{
+			sendto_prefix_one(victim, client, mtags, ":%s KICK %s %s :%s",
+				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->name, victim->name, comment);
+	}
+
+	sendto_server(client, 0, 0, mtags, ":%s KICK %s %s :%s",
+	    client->id, channel->name, victim->id, comment);
+
+	free_message_tags(mtags);
+	if (initial_mtags_generated)
+	{
+		free_message_tags(initial_mtags);
+		initial_mtags = NULL;
+	}
+
+	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 (single channel)
+**	parv[2] = client to kick (comma separated)
+**	parv[3] = kick comment
+*/
+
+CMD_FUNC(cmd_kick)
+{
+	Client *target;
+	Channel *channel;
+	int  chasing = 0;
+	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')
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "KICK");
+		return;
+	}
+
+	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;
+	}
+
+	/* 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;
+	}
+
+	strlcpy(request, parv[2], sizeof(request));
+	for (user = strtoken(&p2, request, ","); user; user = strtoken(&p2, NULL, ","))
+	{
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, user, maxtargets, "KICK");
+			break;
+		}
+
+		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)
+		{
+			if (MyUser(client))
+				sendnumeric(client, ERR_USERNOTINCHANNEL, user, request_chans);
+			continue;
+		}
+
+		if (IsULine(client) || IsServer(client) || IsMe(client))
+			goto attack;
+
+		/* 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 "target" access flags */
+		target_member_modes = get_channel_access(target, channel);
+
+		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);
+
+			if (n == EX_DENY)
+				ret = n;
+			else if (n == EX_ALWAYS_DENY)
+			{
+				ret = n;
+				break;
+			}
+		}
+
+		if (ret == EX_ALWAYS_DENY)
+		{
+			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) */
+		}
+
+		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 */
+
+				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 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")))
+			{
+				kick_operoverride_msg(client, channel, target, comment);
+				goto attack;
+			}	/* is_chan_op */
+
+		}
+
+		/* 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) */
+			{
+				/* IRCop kicking owner/prot */
+				kick_operoverride_msg(client, channel, target, comment);
+				goto attack;
+			}
+			else if (!IsULine(client) && (target != client) && MyUser(client))
+			{
+				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;
+			}
+		}
+
+		/* 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;
+		}
+
+		/* 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;
+		}
+
+		kick_user(recv_mtags, channel, client, target, comment);
+	}
+}
diff --git a/ircd/src/modules/kill.c b/ircd/src/modules/kill.c
@@ -0,0 +1,184 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/kill.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_kill);
+
+ModuleHeader MOD_HEADER
+  = {
+	"kill",
+	"5.0",
+	"command /kill",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "KILL", cmd_kill, 2, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/** The KILL command - this forcefully terminates a users' connection.
+ * parv[1] = kill victim(s) - comma separated list
+ * parv[2] = reason
+ */
+CMD_FUNC(cmd_kill)
+{
+	char targetlist[BUFSIZE];
+	char reason[BUFSIZE];
+	char buf2[BUFSIZE];
+	char *str;
+	char *nick, *save = NULL;
+	Client *target;
+	Hook *h;
+	int ntargets = 0;
+	int maxtargets = max_targets_for_command("KILL");
+
+	if ((parc < 3) || BadPtr(parv[2]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "KILL");
+		return;
+	}
+
+	if (!IsServer(client->direction) && !ValidatePermissionsForPath("kill:global",client,NULL,NULL,NULL) && !ValidatePermissionsForPath("kill:local",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (MyUser(client))
+		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, ","))
+	{
+		MessageTag *mtags = NULL;
+
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, nick, maxtargets, "KILL");
+			break;
+		}
+
+		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.
+		 * We don't do this for remote KILL requests as we have UID for that.
+		 */
+		if (!target && MyUser(client))
+		{
+			target = get_history(nick, KILLCHASETIMELIMIT);
+			if (target)
+				sendnotice(client, "*** KILL changed from %s to %s", nick, target->name);
+		}
+
+		if (!target)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, nick);
+			continue;
+		}
+
+		if ((!MyConnect(target) && MyUser(client) && !ValidatePermissionsForPath("kill:global",client,target,NULL,NULL))
+		    || (MyConnect(target) && MyUser(client)
+		    && !ValidatePermissionsForPath("kill:local",client,target,NULL,NULL)))
+		{
+			sendnumeric(client, ERR_NOPRIVILEGES);
+			continue;
+		}
+
+		/* Hooks can plug-in here to reject a kill */
+		if (MyUser(client))
+		{
+			int ret = EX_ALLOW;
+			for (h = Hooks[HOOKTYPE_PRE_KILL]; h; h = h->next)
+			{
+				/* note: parameters are: client, victim, reason. reason can be NULL !! */
+				ret = (*(h->func.intfunc))(client, target, reason);
+				if (ret != EX_ALLOW)
+					break;
+			}
+			if ((ret == EX_DENY) || (ret == EX_ALWAYS_DENY))
+				continue; /* reject kill for this particular user */
+		}
+
+		/* From here on, the kill is probably going to be successful. */
+
+		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);
+
+		/* Victim gets a little notification (s)he is about to die */
+		if (MyConnect(target))
+		{
+			sendto_prefix_one(target, client, NULL, ":%s KILL %s :%s",
+			    client->name, target->name, reason);
+		}
+
+		if (MyConnect(target) && MyConnect(client))
+		{
+			/* Local kill. This is handled as if it were a QUIT */
+		}
+		else
+		{
+			/* Kill from one server to another (we may be src, victim or something in-between) */
+
+			/* Broadcast it to other servers */
+			sendto_server(client, 0, 0, mtags, ":%s KILL %s :%s",
+			              client->id, target->id, reason);
+
+			/* Don't send a QUIT for this */
+			SetKilled(target);
+
+			ircsnprintf(buf2, sizeof(buf2), "Killed by %s (%s)", client->name, reason);
+		}
+
+		if (MyUser(client))
+			RunHook(HOOKTYPE_LOCAL_KILL, client, target, reason);
+
+		ircsnprintf(buf2, sizeof(buf2), "Killed by %s (%s)", client->name, reason);
+		exit_client(target, mtags, buf2);
+
+		free_message_tags(mtags);
+
+		if (IsDead(client))
+			return; /* stop processing if we killed ourselves */
+	}
+}
diff --git a/ircd/src/modules/knock.c b/ircd/src/modules/knock.c
@@ -0,0 +1,160 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/knock.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_knock);
+
+#define MSG_KNOCK 	"KNOCK"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"knock",
+	"5.0",
+	"command /knock", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_KNOCK, cmd_knock, 2, CMD_USER);
+	ISupportAdd(modinfo->handle, "KNOCK", NULL);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_knock
+**	parv[1] - channel
+**	parv[2] - reason
+**
+** Coded by Stskeeps
+** Additional bugfixes/ideas by codemastr
+** (C) codemastr & Stskeeps
+** 
+** 2019-11-27: Behavior change. We now send the KNOCK
+** across servers and only deliver the channel notice
+** to local channel members. The reason for this is that
+** otherwise we cannot count KNOCKs network-wide which
+** caused knock-floods per-channel to be per-server
+** rather than global, which undesirable.
+** Unfortunately, this means that if you have a mixed
+** U4 and U5 network you will see KNOCK notices twice
+** for every attempt.
+*/
+CMD_FUNC(cmd_knock)
+{
+	Channel *channel;
+	Hook *h;
+	int i = 0;
+	MessageTag *mtags = NULL;
+	const char *reason;
+
+	if (IsServer(client))
+		return;
+
+	if (parc < 2 || *parv[1] == '\0')
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "KNOCK");
+		return;
+	}
+
+	reason = parv[2] ? parv[2] : "no reason specified";
+
+	if (MyConnect(client) && !valid_channelname(parv[1]))
+	{
+		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+		return;
+	}
+
+	if (!(channel = find_channel(parv[1])))
+	{
+		sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "Channel does not exist!");
+		return;
+	}
+
+	/* IsMember bugfix by codemastr */
+	if (IsMember(client, channel) == 1)
+	{
+		sendnumeric(client, ERR_CANNOTKNOCK, channel->name, "You're already there!");
+		return;
+	}
+
+	if (!has_channel_mode(channel, 'i'))
+	{
+		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->name, "You're banned!");
+		return;
+	}
+
+	for (h = Hooks[HOOKTYPE_PRE_KNOCK]; h; h = h->next)
+	{
+		i = (*(h->func.intfunc))(client, channel, &reason);
+		if (i == HOOK_DENY || i == HOOK_ALLOW)
+			break;
+	}
+
+	if (i == HOOK_DENY)
+		return;
+
+	if (MyUser(client) &&
+	    !ValidatePermissionsForPath("immune:knock-flood",client,NULL,NULL,NULL) &&
+	    flood_limit_exceeded(client, FLD_KNOCK))
+	{
+		sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "You are KNOCK flooding");
+		return;
+	}
+
+	new_message(&me, NULL, &mtags);
+
+	sendto_channel(channel, &me, NULL, "o",
+	               0, SEND_LOCAL, mtags,
+	               ":%s NOTICE @%s :[Knock] by %s!%s@%s (%s)",
+	               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->name, reason);
+
+	if (MyUser(client))
+		sendnotice(client, "Knocked on %s", channel->name);
+
+        RunHook(HOOKTYPE_KNOCK, client, channel, mtags, parv[2]);
+
+	free_message_tags(mtags);
+}
diff --git a/ircd/src/modules/labeled-response.c b/ircd/src/modules/labeled-response.c
@@ -0,0 +1,399 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/labeled-response.c
+ *   (C) 2019 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"labeled-response",
+	"5.0",
+	"Labeled response CAP",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Data structures */
+typedef struct LabeledResponseContext LabeledResponseContext;
+struct LabeledResponseContext {
+	Client *client; /**< The client who issued the original command with a label */
+	char label[256]; /**< The label attached to this command */
+	char batch[BATCHLEN+1]; /**< The generated batch id */
+	int responses; /**< Number of lines sent back to client */
+	int sent_remote; /**< Command has been sent to remote server */
+	char firstbuf[MAXLINELENGTH]; /**< First buffered response */
+};
+
+/* Forward declarations */
+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);
+void _labeled_response_set_context(void *ctx);
+void _labeled_response_force_end(void);
+
+/* Our special version of SupportBatch() assumes that remote servers always handle it */
+#define SupportBatch(x)		(MyConnect(x) ? HasCapability((x), "batch") : 1)
+#define SupportLabel(x)		(HasCapabilityFast((x), CAP_LABELED_RESPONSE))
+
+/* Variables */
+static LabeledResponseContext currentcmd;
+static long CAP_LABELED_RESPONSE = 0L;
+
+static char packet[MAXLINELENGTH*2];
+
+int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddPVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SAVE_CONTEXT, _labeled_response_save_context);
+	EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SET_CONTEXT, _labeled_response_set_context);
+	EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_FORCE_END, _labeled_response_force_end);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+	ClientCapability *c;
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&currentcmd, 0, sizeof(currentcmd));
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "labeled-response";
+	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_LABELED_RESPONSE);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "label";
+	mtag.is_ok = labeled_response_mtag_is_ok;
+	mtag.clicap_handler = c;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_COMMAND, -1000000000, lr_pre_command);
+	HookAdd(modinfo->handle, HOOKTYPE_POST_COMMAND, 1000000000, lr_post_command);
+	HookAdd(modinfo->handle, HOOKTYPE_CLOSE_CONNECTION, 1000000000, lr_close_connection);
+	HookAdd(modinfo->handle, HOOKTYPE_PACKET, 1000000000, lr_packet);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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;
+
+	if (IsServer(from))
+		return 0;
+
+	for (; mtags; mtags = mtags->next)
+	{
+		if (!strcmp(mtags->name, "label") && mtags->value)
+		{
+			strlcpy(currentcmd.label, mtags->value, sizeof(currentcmd.label));
+			currentcmd.client = from;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+char *gen_start_batch(void)
+{
+	static char buf[512];
+
+	generate_batch_id(currentcmd.batch);
+
+	if (MyConnect(currentcmd.client))
+	{
+		/* Local connection */
+		snprintf(buf, sizeof(buf), "@label=%s :%s BATCH +%s labeled-response",
+			currentcmd.label,
+			me.name,
+			currentcmd.batch);
+	} else {
+		/* Remote connection: requires intra-server BATCH syntax */
+		snprintf(buf, sizeof(buf), "@label=%s :%s BATCH %s +%s labeled-response",
+			currentcmd.label,
+			me.name,
+			currentcmd.client->name,
+			currentcmd.batch);
+	}
+	return buf;
+}
+
+int lr_post_command(Client *from, MessageTag *mtags, const char *buf)
+{
+	/* ** IMPORTANT **
+	 * Take care NOT to return here, use 'goto done' instead
+	 * as some variables need to be cleared.
+	 */
+
+	/* We may have to send a response or end a BATCH here, if all of
+	 * the following is true:
+	 * 1. The client is still online (from is not NULL)
+	 * 2. A "label" was attached
+	 * 3. The client supports BATCH (or is remote)
+	 * 4. The command has not been forwarded to a remote server
+	 *    (in which case they would be handling it, and not us)
+	 * 5. Unless labeled_response_force is set, in which case
+	 *    we are assumed to have handled it anyway (necessary for
+	 *    commands like PRIVMSG, quite rare).
+	 */
+	if (from && currentcmd.client &&
+	    !(currentcmd.sent_remote && !currentcmd.responses && !labeled_response_force))
+	{
+		Client *savedptr;
+
+		if (currentcmd.responses == 0)
+		{
+			MessageTag *m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, "label");
+			safe_strdup(m->value, currentcmd.label);
+			memset(&currentcmd, 0, sizeof(currentcmd));
+			sendto_one(from, m, ":%s ACK", me.name);
+			free_message_tags(m);
+			goto done;
+		} else
+		if (currentcmd.responses == 1)
+		{
+			/* We have buffered this response earlier,
+			 * now we will send it
+			 */
+			int more_tags = currentcmd.firstbuf[0] == '@';
+			currentcmd.client = NULL; /* prevent lr_packet from interfering */
+			snprintf(packet, sizeof(packet)-3,
+				 "@label=%s%s%s",
+				 currentcmd.label,
+				 more_tags ? ";" : " ",
+				 more_tags ? currentcmd.firstbuf+1 : currentcmd.firstbuf);
+			/* Format the IRC message correctly here, so we can take the
+			 * quick path through sendbufto_one().
+			 */
+			strlcat(packet, "\r\n", sizeof(packet));
+			sendbufto_one(from, packet, strlen(packet));
+			goto done;
+		}
+
+		/* End the batch */
+		if (!labeled_response_inhibit_end)
+		{
+			savedptr = currentcmd.client;
+			currentcmd.client = NULL;
+			if (MyConnect(savedptr))
+				sendto_one(from, NULL, ":%s BATCH -%s", me.name, currentcmd.batch);
+			else
+				sendto_one(from, NULL, ":%s BATCH %s -%s", me.name, savedptr->name, currentcmd.batch);
+		}
+	}
+done:
+	memset(&currentcmd, 0, sizeof(currentcmd));
+	labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0;
+	return 0;
+}
+
+int lr_close_connection(Client *client)
+{
+	/* Flush all data before closing connection */
+	lr_post_command(client, NULL, NULL);
+	return 0;
+}
+
+/** Helper function for lr_packet() to skip the message tags prefix,
+ * and possibly @batch as well.
+ */
+char *skip_tags(char *msg)
+{
+	if (*msg != '@')
+		return msg;
+	if (!strncmp(msg, "@batch", 6))
+	{
+		char *p;
+		for (p = msg; *p; p++)
+			if ((*p == ';') || (*p == ' '))
+				return p;
+	}
+	return msg+1; /* just skip the '@' */
+}
+
+int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len)
+{
+	if (currentcmd.client && !labeled_response_inhibit)
+	{
+		/* Labeled response is active */
+		if (currentcmd.client == intended_to)
+		{
+			/* Add the label */
+			if (currentcmd.responses == 0)
+			{
+				int n = *len;
+				if (n > sizeof(currentcmd.firstbuf))
+					n = sizeof(currentcmd.firstbuf);
+				strlcpy(currentcmd.firstbuf, *msg, n);
+				/* Don't send anything -- yet */
+				*msg = NULL;
+				*len = 0;
+			} else
+			if (currentcmd.responses == 1)
+			{
+				/* Start the batch now, normally this would be a sendto_one()
+				 * but doing so is not possible since we are in the sending code :(
+				 * The code below is almost unbearable to see, but the alternative
+				 * is to use an intermediate buffer or pointer jugling, of which
+				 * the former is slower than this implementation and with the latter
+				 * it is easy to make a mistake and create an overflow issue.
+				 * So guess I'll stick with this...
+				 */
+				char *batchstr = gen_start_batch();
+				int more_tags_one = currentcmd.firstbuf[0] == '@';
+				int more_tags_two = **msg == '@';
+
+				if (!strncmp(*msg, "@batch", 6))
+				{
+					/* Special case: current message (*msg) already contains a batch */
+					snprintf(packet, sizeof(packet),
+						 "%s\r\n"
+						 "@batch=%s%s%s\r\n"
+						 "%s",
+						 batchstr,
+						 currentcmd.batch,
+						 more_tags_one ? ";" : " ",
+						 more_tags_one ? currentcmd.firstbuf+1 : currentcmd.firstbuf,
+						 *msg);
+				} else
+				{
+					/* Regular case: current message (*msg) contains no batch yet, add one.. */
+					snprintf(packet, sizeof(packet),
+						 "%s\r\n"
+						 "@batch=%s%s%s\r\n"
+						 "@batch=%s%s%s",
+						 batchstr,
+						 currentcmd.batch,
+						 more_tags_one ? ";" : " ",
+						 more_tags_one ? currentcmd.firstbuf+1 : currentcmd.firstbuf,
+						 currentcmd.batch,
+						 more_tags_two ? ";" : " ",
+						 more_tags_two ? *msg+1 : *msg);
+				}
+				*msg = packet;
+				*len = strlen(*msg);
+			} else {
+				/* >2 responses.... the first 2 have already been sent */
+				if (!strncmp(*msg, "@batch", 6))
+				{
+					/* No buffer change needed, already contains a (now inner) batch */
+				} else {
+					int more_tags = **msg == '@';
+					snprintf(packet, sizeof(packet), "@batch=%s%s%s",
+						currentcmd.batch,
+						more_tags ? ";" : " ",
+						more_tags ? *msg+1 : *msg);
+					*msg = packet;
+					*len = strlen(*msg);
+				}
+			}
+			currentcmd.responses++;
+		}
+		else if (IsServer(to) || !MyUser(to))
+		{
+			currentcmd.sent_remote = 1;
+		}
+	}
+
+	return 0;
+}
+
+/** 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, const char *name, const char *value)
+{
+	if (BadPtr(value))
+		return 0;
+
+	if (IsServer(client))
+		return 1;
+
+	/* Ignore the label if the client does not support both
+	 * (draft/)labeled-response and batch. Yeah, batch too,
+	 * it's too much hassle to support labeled-response without
+	 * batch and the end result is quite broken too.
+	 */
+	if (MyUser(client) && (!SupportLabel(client) || !SupportBatch(client)))
+		return 0;
+
+	/* Do some basic sanity checking for non-servers */
+	if (strlen(value) <= 64)
+		return 1;
+
+	return 0;
+}
+
+/** Save current context for later use in labeled-response.
+ * Currently used in /LIST. Is not planned for other places tbh.
+ */
+void *_labeled_response_save_context(void)
+{
+	LabeledResponseContext *ctx = safe_alloc(sizeof(LabeledResponseContext));
+	memcpy(ctx, &currentcmd, sizeof(LabeledResponseContext));
+	return (void *)ctx;
+}
+
+/** Set previously saved context 'ctx', or clear the context.
+ * @param ctx    The context, or NULL to clear the context.
+ * @note The client from the previously saved context should be
+ *       the same. Don't save one context when processing
+ *       client A and then restore it when processing client B (duh).
+ */
+void _labeled_response_set_context(void *ctx)
+{
+	if (ctx == NULL)
+	{
+		/* This means: clear the current context */
+		memset(&currentcmd, 0, sizeof(currentcmd));
+	} else {
+		/* Set the current context to the provided one */
+		memcpy(&currentcmd, ctx, sizeof(LabeledResponseContext));
+	}
+}
+
+/** Force an end of the labeled-response (only used in /LIST atm) */
+void _labeled_response_force_end(void)
+{
+	if (currentcmd.client)
+		lr_post_command(currentcmd.client, NULL, NULL);
+}
diff --git a/ircd/src/modules/lag.c b/ircd/src/modules/lag.c
@@ -0,0 +1,85 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/lag.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_lag);
+
+/* Place includes here */
+#define MSG_LAG         "LAG"   /* Lag detect */
+
+ModuleHeader MOD_HEADER
+  = {
+	"lag",	/* Name of module */
+	"5.0", /* Version */
+	"command /lag", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_LAG, cmd_lag, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* cmd_lag (lag measure) - Stskeeps
+ * parv[1] = server to query
+*/
+
+CMD_FUNC(cmd_lag)
+{
+	if (!ValidatePermissionsForPath("server:info:lag",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "LAG");
+		return;
+	}
+
+	if (*parv[1] == '\0')
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "LAG");
+		return;
+	}
+
+	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/ircd/src/modules/link-security.c b/ircd/src/modules/link-security.c
@@ -0,0 +1,281 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/link-security.c
+ *   (C) 2017 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"link-security",
+	"5.0",
+	"Link Security CAP",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Forward declarations */
+const char *link_security_md_serialize(ModData *m);
+void link_security_md_unserialize(const char *str, ModData *m);
+EVENT(checklinksec);
+const char *link_security_capability_parameter(Client *client);
+CMD_FUNC(cmd_linksecurity);
+
+/* Global variables */
+ModDataInfo *link_security_md;
+int local_link_security = -1;
+int global_link_security = -1;
+int effective_link_security = -1;
+
+/** Module initalization */
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1);
+	
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "link-security";
+	mreq.type = MODDATATYPE_CLIENT;
+	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)
+	{
+		config_error("Unable to ModDataAdd() -- too many 3rd party modules loaded perhaps?");
+		abort();
+	}
+	
+	CommandAdd(modinfo->handle, "LINKSECURITY", cmd_linksecurity, MAXPARA, CMD_USER);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	ClientCapabilityInfo cap;
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "unrealircd.org/link-security";
+	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
+	cap.parameter = link_security_capability_parameter;
+	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
+
+	EventAdd(modinfo->handle, "checklinksec", checklinksec, NULL, 2000, 0);
+	checklinksec(NULL);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Magic value to differentiate between "not set" and "zero".
+ * Only used for internal moddata storage, not exposed
+ * outside these two functions.
+ */
+#define LNKSECMAGIC 100
+
+const char *link_security_md_serialize(ModData *m)
+{
+	static char buf[32];
+	if (m->i == 0)
+		return NULL; /* not set */
+	snprintf(buf, sizeof(buf), "%d", m->i - LNKSECMAGIC);
+	return buf;
+}
+
+void link_security_md_unserialize(const char *str, ModData *m)
+{
+	m->i = atoi(str) + LNKSECMAGIC;
+}
+
+/** Return 1 if the server certificate is verified for
+ * server 'client', return 0 if not.
+ */
+int certificate_verification_active(Client *client)
+{
+	ConfigItem_link *conf;
+	
+	if (!client->server || !client->server->conf)
+		return 0; /* wtf? */
+	conf = client->server->conf;
+	
+	if (conf->verify_certificate)
+		return 1; /* yes, verify-certificate is 'yes' */
+	
+	if ((conf->auth->type == AUTHTYPE_TLS_CLIENTCERT) ||
+	    (conf->auth->type == AUTHTYPE_TLS_CLIENTCERTFP) ||
+	    (conf->auth->type == AUTHTYPE_SPKIFP))
+	{
+		/* yes, verified by link::password being a
+		 * certificate fingerprint or certificate file.
+		 */
+	    return 1;
+	}
+
+	return 0; /* no, certificate is not verified in any way */
+}
+
+/** Calculate our (local) link-security level.
+ * This means stepping through the list of directly linked
+ * servers and determining if they are linked via TLS and
+ * certificate verification is active.
+ * @returns value from 0 to 2.
+ */
+int our_link_security(void)
+{
+	Client *client;
+	int level = 2; /* safest */
+	
+	list_for_each_entry(client, &server_list, special_node)
+	{
+		if (IsLocalhost(client))
+			continue; /* server connected via localhost */
+		if (!IsSecure(client))
+			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 */
+	}
+	
+	return level;
+}
+
+char *valtostr(int i)
+{
+	static char buf[32];
+	snprintf(buf, sizeof(buf), "%d", i);
+	return buf;
+}
+
+/** Check link security. This is called every X seconds to see if there
+ * is a change, either local or network-wide.
+ */
+EVENT(checklinksec)
+{
+	int last_local_link_security = local_link_security;
+	int last_global_link_security = global_link_security;
+	Client *client;
+	int v;
+	int warning_sent = 0;
+	
+	local_link_security = our_link_security();
+	if (local_link_security != last_local_link_security)
+	{
+		/* Our own link-security changed (for better or worse),
+		 * Set and broadcast it immediately to the other servers.
+		 */
+		moddata_client_set(&me, "link-security", valtostr(local_link_security));
+	}
+
+	global_link_security = 2;
+	list_for_each_entry(client, &global_server_list, client_node)
+	{
+		const char *s = moddata_client_get(client, "link-security");
+		if (s)
+		{
+			v = atoi(s);
+			if (v == 0)
+			{
+				global_link_security = 0;
+				break;
+			}
+			if (v == 1)
+				global_link_security = 1;
+		}
+	}
+	
+	if (local_link_security < last_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)
+	{
+		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;
+	}
+	
+	effective_link_security = MIN(local_link_security, global_link_security);
+
+	if (warning_sent)
+	{
+		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));
+	}
+}
+
+const char *link_security_capability_parameter(Client *client)
+{
+	return valtostr(effective_link_security);
+}
+
+/** /LINKSECURITY command */
+CMD_FUNC(cmd_linksecurity)
+{
+	Client *acptr;
+	
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	
+	sendtxtnumeric(client, "== Link Security Report ==");
+	
+	sendtxtnumeric(client, "= By server =");
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		const char *s = moddata_client_get(acptr, "link-security");
+		if (s)
+			sendtxtnumeric(client, "%s: level %d", acptr->name, atoi(s));
+		else
+			sendtxtnumeric(client, "%s: level UNKNOWN", acptr->name);
+	}
+	
+	sendtxtnumeric(client, "-");
+	sendtxtnumeric(client, "= Network =");
+	sendtxtnumeric(client, "This results in an effective (network-wide) link-security of level %d", effective_link_security);
+	sendtxtnumeric(client, "-");
+	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 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");
+	sendtxtnumeric(client, "see https://www.unrealircd.org/docs/Link_security");
+}
diff --git a/ircd/src/modules/links.c b/ircd/src/modules/links.c
@@ -0,0 +1,77 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_links);
+
+#define MSG_LINKS 	"LINKS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"links",
+	"5.0",
+	"command /links", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_LINKS, cmd_links, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+CMD_FUNC(cmd_links)
+{
+	Client *acptr;
+	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
+
+	if (!MyUser(client))
+		return;
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		/* Some checks */
+		if (HIDE_ULINES && IsULine(acptr) && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
+			continue;
+		if (flat)
+			sendnumeric(client, RPL_LINKS, acptr->name, me.name,
+			    1, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
+		else
+			sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink ? acptr->uplink->name : me.name,
+			    acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
+	}
+
+	sendnumeric(client, RPL_ENDOFLINKS, "*");
+}
diff --git a/ircd/src/modules/list.c b/ircd/src/modules/list.c
@@ -0,0 +1,468 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/list.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_list);
+int send_list(Client *client);
+
+#define MSG_LIST 	"LIST"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"list",
+	"5.0",
+	"command /LIST",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+typedef struct ChannelListOptions ChannelListOptions;
+struct ChannelListOptions {
+	NameList *yeslist;
+	NameList *nolist;
+	unsigned int starthash;
+	short int showall;
+	unsigned short usermin;
+	int  usermax;
+	time_t currenttime;
+	time_t chantimemin;
+	time_t chantimemax;
+	time_t topictimemin;
+	time_t topictimemax;
+	void *lr_context;
+};
+
+/* Global variables */
+ModDataInfo *list_md = NULL;
+char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
+/* Macros */
+#define CHANNELLISTOPTIONS(x)       ((ChannelListOptions *)moddata_local_client(x, list_md).ptr)
+#define ALLOCATE_CHANNELLISTOPTIONS(client)	do { moddata_local_client(client, list_md).ptr = safe_alloc(sizeof(ChannelListOptions)); } while(0)
+#define free_list_options(client)		list_md_free(&moddata_local_client(client, list_md))
+
+#define DoList(x)               (MyUser((x)) && CHANNELLISTOPTIONS((x)))
+#define IsSendable(x)		(DBufLength(&x->local->sendQ) < 2048)
+
+/* Forward declarations */
+EVENT(send_queued_list_data);
+void list_md_free(ModData *md);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "list";
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	mreq.free = list_md_free;
+	list_md = ModDataAdd(modinfo->handle, mreq);
+	if (!list_md)
+	{
+		config_error("could not register list moddata");
+		return MOD_FAILED;
+	}
+
+	CommandAdd(modinfo->handle, MSG_LIST, cmd_list, MAXPARA, CMD_USER);
+	EventAdd(modinfo->handle, "send_queued_list_data", send_queued_list_data, NULL, 1500, 0);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Originally from bahamut, modified a bit for UnrealIRCd by codemastr
+ * also Opers can now see +s channels -- codemastr */
+
+/*
+ * parv[1] = channel
+ */
+CMD_FUNC(cmd_list)
+{
+	Channel *channel;
+	time_t currenttime = TStime();
+	char *name, *p = NULL;
+	ChannelListOptions *lopt = NULL;
+	int usermax, usermin, error = 0, doall = 0;
+	time_t chantimemin, chantimemax;
+	time_t topictimemin, topictimemax;
+	NameList *yeslist = NULL;
+	NameList *nolist = NULL;
+	int ntargets = 0;
+	int maxtargets = max_targets_for_command("LIST");
+	char request[BUFSIZE];
+
+	static char *usage[] = {
+		"   Usage: /LIST <options>",
+		"",
+		"If you don't include any options, the default is to send you the",
+		"entire unfiltered list of channels. Below are the options you can",
+		"use, and what channels LIST will return when you use them.",
+		">number  List channels with more than <number> people.",
+		"<number  List channels with less than <number> people.",
+		"C>number List channels created more than <number> minutes ago.",
+		"C<number List channels created less than <number> minutes ago.",
+		"T>number List channels whose topics are older than <number> minutes",
+		"         (Ie, they have not changed in the last <number> minutes.",
+		"T<number List channels whose topics are not older than <number> minutes.",
+		"*mask*   List channels that match *mask*",
+		"!*mask*  List channels that do not match *mask*",
+		NULL
+	};
+
+	/* Remote /LIST is not supported */
+	if (!MyUser(client))
+		return;
+
+	/* If a /LIST is in progress then a new one will cancel it */
+	if (CHANNELLISTOPTIONS(client))
+	{
+		sendnumeric(client, RPL_LISTEND);
+		free_list_options(client);
+		return;
+	}
+
+	if (parc < 2 || BadPtr(parv[1]))
+	{
+		sendnumeric(client, RPL_LISTSTART);
+		ALLOCATE_CHANNELLISTOPTIONS(client);
+		CHANNELLISTOPTIONS(client)->showall = 1;
+
+		if (send_list(client))
+		{
+			/* Save context since there is more to be sent */
+			CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context();
+			labeled_response_inhibit_end = 1;
+		}
+
+		return;
+	}
+
+	if ((parc == 2) && (parv[1][0] == '?') && (parv[1][1] == '\0'))
+	{
+		char **ptr = usage;
+		for (; *ptr; ptr++)
+			sendnumeric(client, RPL_LISTSYNTAX, *ptr);
+		return;
+	}
+
+	sendnumeric(client, RPL_LISTSTART);
+
+	chantimemax = topictimemax = currenttime + 86400;
+	chantimemin = topictimemin = 0;
+	usermin = 0;		/* Minimum of 0 */
+	usermax = -1;		/* No maximum */
+
+	strlcpy(request, parv[1], sizeof(request));
+	for (name = strtoken(&p, request, ","); name && !error; name = strtoken(&p, NULL, ","))
+	{
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "LIST");
+			break;
+		}
+		switch (*name)
+		{
+			case '<':
+				usermax = atoi(name + 1) - 1;
+				doall = 1;
+				break;
+			case '>':
+				usermin = atoi(name + 1) + 1;
+				doall = 1;
+				break;
+			case 'C':
+			case 'c':	/* Channel time -- creation time? */
+				++name;
+				switch (*name++)
+				{
+					case '<':
+						chantimemin = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					case '>':
+						chantimemax = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					default:
+						sendnumeric(client, ERR_LISTSYNTAX);
+						error = 1;
+				}
+				break;
+			case 'T':
+			case 't':
+				++name;
+				switch (*name++)
+				{
+					case '<':
+						topictimemin = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					case '>':
+						topictimemax = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					default:
+						sendnumeric(client, ERR_LISTSYNTAX);
+						error = 1;
+				}
+				break;
+			default:
+				/* A channel, possibly with wildcards.
+				 * Thought for the future: Consider turning wildcard
+				 * processing on the fly.
+				 * new syntax: !channelmask will tell ircd to ignore
+				 * any channels matching that mask, and then
+				 * channelmask will tell ircd to send us a list of
+				 * channels only masking channelmask. Note: Specifying
+				 * a channel without wildcards will return that
+				 * channel even if any of the !channelmask masks
+				 * matches it.
+				 */
+				if (*name == '!')
+				{
+					/* Negative matching by name */
+					doall = 1;
+					add_name_list(nolist, name + 1);
+				}
+				else if (strchr(name, '*') || strchr(name, '?'))
+				{
+					/* Channel with wildcards */
+					doall = 1;
+					add_name_list(yeslist, name);
+				}
+				else
+				{
+					/* A specific channel name without wildcards */
+					channel = find_channel(name);
+					if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL)))
+					{
+						modebuf[0] = '[';
+						channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
+
+						if (modebuf[2] == '\0')
+							modebuf[0] = '\0';
+						else
+							strlcat(modebuf, "]", sizeof modebuf);
+
+						sendnumeric(client, RPL_LIST, name, channel->users, modebuf,
+							    channel->topic ? channel->topic : "");
+					}
+				}
+		} /* switch */
+	} /* for */
+
+	if (doall)
+	{
+		ALLOCATE_CHANNELLISTOPTIONS(client);
+		CHANNELLISTOPTIONS(client)->usermin = usermin;
+		CHANNELLISTOPTIONS(client)->usermax = usermax;
+		CHANNELLISTOPTIONS(client)->topictimemax = topictimemax;
+		CHANNELLISTOPTIONS(client)->topictimemin = topictimemin;
+		CHANNELLISTOPTIONS(client)->chantimemax = chantimemax;
+		CHANNELLISTOPTIONS(client)->chantimemin = chantimemin;
+		CHANNELLISTOPTIONS(client)->nolist = nolist;
+		CHANNELLISTOPTIONS(client)->yeslist = yeslist;
+
+		if (send_list(client))
+		{
+			/* Save context since there is more to be sent */
+			CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context();
+			labeled_response_inhibit_end = 1;
+		}
+		return;
+	}
+
+	sendnumeric(client, RPL_LISTEND);
+}
+/*
+ * The function which sends the actual channel list back to the user.
+ * Operates by stepping through the hashtable, sending the entries back if
+ * they match the criteria.
+ * client = Local client to send the output back to.
+ * Taken from bahamut, modified for UnrealIRCd by codemastr.
+ */
+int send_list(Client *client)
+{
+	Channel *channel;
+	ChannelListOptions *lopt = CHANNELLISTOPTIONS(client);
+	unsigned int  hashnum;
+	int numsend = (get_sendq(client) / 768) + 1; /* (was previously hard-coded) */
+	/* ^
+	 * numsend = Number (roughly) of lines to send back. Once this number has
+	 * been exceeded, send_list will finish with the current hash bucket,
+	 * and record that number as the number to start next time send_list
+	 * is called for this user. So, this function will almost always send
+	 * back more lines than specified by numsend (though not by much,
+	 * assuming the hashing algorithm works well). Be conservative in your
+	 * choice of numsend. -Rak
+	 */	
+
+	/* Begin of /LIST? then send official channels first. */
+	if ((lopt->starthash == 0) && conf_offchans)
+	{
+		ConfigItem_offchans *x;
+		for (x = conf_offchans; x; x = x->next)
+		{
+			if (find_channel(x->name))
+				continue; /* exists, >0 users.. will be sent later */
+			sendnumeric(client, RPL_LIST, x->name, 0, "",
+			            x->topic ? x->topic : "");
+		}
+	}
+
+	for (hashnum = lopt->starthash; hashnum < CHAN_HASH_TABLE_SIZE; hashnum++)
+	{
+		if (numsend > 0)
+			for (channel = hash_get_chan_bucket(hashnum); channel; channel = channel->hnextch)
+			{
+				if (SecretChannel(channel)
+				    && !IsMember(client, channel)
+				    && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
+					continue;
+
+				/* set::hide-list { deny-channel } */
+				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->name))
+					continue;
+
+				/* Much more readable like this -- codemastr */
+				if ((!lopt->showall))
+				{
+					/* User count must be in range */
+					if ((channel->users < lopt->usermin) ||
+					    ((lopt->usermax >= 0) && (channel->users > lopt->usermax)))
+						continue;
+
+					/* Creation time must be in range */
+					if ((channel->creationtime && (channel->creationtime < lopt->chantimemin)) ||
+					    (channel->creationtime > lopt->chantimemax))
+						continue;
+
+					/* Topic time must be in range */
+					if ((channel->topic_time < lopt->topictimemin) ||
+					    (channel->topic_time > lopt->topictimemax))
+						continue;
+
+					/* Must not be on nolist (if it exists) */
+					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->name))
+						continue;
+				}
+				modebuf[0] = '[';
+				channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
+				if (modebuf[2] == '\0')
+					modebuf[0] = '\0';
+				else
+					strlcat(modebuf, "]", sizeof modebuf);
+				if (!ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
+					sendnumeric(client, RPL_LIST,
+					    ShowChannel(client,
+					    channel) ? channel->name :
+					    "*", channel->users,
+					    ShowChannel(client, channel) ?
+					    modebuf : "",
+					    ShowChannel(client,
+					    channel) ? (channel->topic ?
+					    channel->topic : "") : "");
+				else
+					sendnumeric(client, RPL_LIST, channel->name,
+					    channel->users,
+					    modebuf,
+					    (channel->topic ? channel->topic : ""));
+				numsend--;
+			}
+		else
+			break;
+	}
+
+	/* All done */
+	if (hashnum == CHAN_HASH_TABLE_SIZE)
+	{
+		sendnumeric(client, RPL_LISTEND);
+		free_list_options(client);
+		return 0;
+	}
+
+	/*
+	 * We've exceeded the limit on the number of channels to send back
+	 * at once.
+	 */
+	lopt->starthash = hashnum;
+	return 1;
+}
+
+EVENT(send_queued_list_data)
+{
+	Client *client, *saved;
+	list_for_each_entry_safe(client, saved, &lclient_list, lclient_node)
+	{
+		if (DoList(client) && IsSendable(client))
+		{
+			labeled_response_set_context(CHANNELLISTOPTIONS(client)->lr_context);
+			if (!send_list(client))
+			{
+				/* We are done! */
+				labeled_response_force_end();
+			}
+			labeled_response_set_context(NULL);
+		}
+	}
+}
+
+/** Called on client exit: free the channel list options of this user */
+void list_md_free(ModData *md)
+{
+	ChannelListOptions *lopt = (ChannelListOptions *)md->ptr;
+
+	if (!lopt)
+		return;
+
+	free_entire_name_list(lopt->yeslist);
+	free_entire_name_list(lopt->nolist);
+	safe_free(lopt->lr_context);
+
+	safe_free(md->ptr);
+}
diff --git a/ircd/src/modules/locops.c b/ircd/src/modules/locops.c
@@ -0,0 +1,74 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_locops);
+
+#define MSG_LOCOPS 	"LOCOPS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"locops",
+	"5.0",
+	"command /locops", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_LOCOPS, cmd_locops, 1, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_locops (write to opers who are +g currently online *this* server)
+**      parv[1] = message text
+*/
+CMD_FUNC(cmd_locops)
+{
+	const char *message = parc > 1 ? parv[1] : NULL;
+
+	if (BadPtr(message))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "LOCOPS");
+		return;
+	}
+	if (MyUser(client) && !ValidatePermissionsForPath("chat:locops",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	sendto_umode(UMODE_OPER, "from %s: %s", client->name, message);
+}
diff --git a/ircd/src/modules/lusers.c b/ircd/src/modules/lusers.c
@@ -0,0 +1,96 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/lusers.c
+ *   (C) 2005 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_lusers);
+
+#define MSG_LUSERS 	"LUSERS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"lusers",
+	"5.0",
+	"command /lusers", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_LUSERS, cmd_lusers, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * parv[1] = server to query
+ */
+CMD_FUNC(cmd_lusers)
+{
+char flatmap;
+
+	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;
+
+	/* Just to correct results ---Stskeeps */
+	if (irccounts.clients > irccounts.global_max)
+		irccounts.global_max = irccounts.clients;
+	if (irccounts.me_clients > irccounts.me_max)
+		irccounts.me_max = irccounts.me_clients;
+
+	sendnumeric(client, RPL_LUSERCLIENT,
+	    irccounts.clients - irccounts.invisible, irccounts.invisible,
+	    irccounts.servers);
+
+	if (irccounts.operators)
+		sendnumeric(client, RPL_LUSEROP, irccounts.operators);
+	if (irccounts.unknown)
+		sendnumeric(client, RPL_LUSERUNKNOWN, irccounts.unknown);
+	if (irccounts.channels)
+		sendnumeric(client, RPL_LUSERCHANNELS, irccounts.channels);
+	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 > max_connection_count)
+	{
+		max_connection_count = irccounts.me_clients;
+		if (max_connection_count % 10 == 0)	/* only send on even tens */
+		{
+			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/ircd/src/modules/map.c b/ircd/src/modules/map.c
@@ -0,0 +1,243 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_map);
+
+#define MSG_MAP 	"MAP"	
+
+static int lmax = 0;
+static int umax = 0;
+
+static int dcount(int n)
+{
+   int cnt = 0;
+
+   while (n != 0)
+   {
+	   n = n/10;
+	   cnt++;
+   }
+
+   return cnt;
+}
+
+ModuleHeader MOD_HEADER
+  = {
+	"map",
+	"5.0",
+	"command /map", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_MAP, cmd_map, MAXPARA, CMD_USER);
+	ISupportAdd(modinfo->handle, "MAP", NULL);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * New /MAP format -Potvin
+ * dump_map function.
+ */
+static void dump_map(Client *client, Client *server, char *mask, int prompt_length, int length)
+{
+	static char prompt[64];
+	char *p = &prompt[prompt_length];
+	int  cnt = 0;
+	Client *acptr;
+
+	*p = '\0';
+
+	if (prompt_length > 60)
+		sendnumeric(client, RPL_MAPMORE, prompt, length, server->name);
+	else
+	{
+		char tbuf[256];
+		char sid[10];
+		int len = length - strlen(server->name) + 1;
+
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+		if (IsOper(client))
+			snprintf(sid, sizeof(sid), " [%s]", server->id);
+		sendnumeric(client, RPL_MAP, prompt, server->name, tbuf, umax,
+			server->server->users, (double)(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(server->server->users * 100.0 / irccounts.clients),
+			IsOper(client) ? sid : "");
+		cnt = 0;
+	}
+
+	if (prompt_length > 0)
+	{
+		p[-1] = ' ';
+		if (p[-2] == '`')
+			p[-2] = ' ';
+	}
+	if (prompt_length > 60)
+		return;
+
+	strcpy(p, "|-");
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if (acptr->uplink != server ||
+ 		    (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)))
+			continue;
+		SetMap(acptr);
+		cnt++;
+	}
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
+			continue;
+		if (acptr->uplink != server)
+			continue;
+		if (!IsMap(acptr))
+			continue;
+		if (--cnt == 0)
+			*p = '`';
+		dump_map(client, acptr, mask, prompt_length + 2, length - 2);
+	}
+
+	if (prompt_length > 0)
+		p[-1] = '-';
+}
+
+void dump_flat_map(Client *client, Client *server, int length)
+{
+	char buf[4];
+	char tbuf[256];
+	Client *acptr;
+	int cnt = 0, len = 0, hide_ulines;
+
+	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
+
+	len = length - strlen(server->name) + 3;
+	if (len < 0)
+		len = 0;
+	if (len > 255)
+		len = 255;
+
+	tbuf[len--] = '\0';
+	while (len >= 0)
+		tbuf[len--] = '-';
+
+	sendnumeric(client, RPL_MAP, "", server->name, tbuf, umax, server->server->users,
+		(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+		(server->server->users * 100.0 / irccounts.clients), "");
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
+			continue;
+		cnt++;
+	}
+
+	strcpy(buf, "|-");
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
+			continue;
+		if (--cnt == 0)
+			*buf = '`';
+
+		len = length - strlen(acptr->name) + 1;
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+
+		sendnumeric(client, RPL_MAP, buf, acptr->name, tbuf, umax, acptr->server->users,
+			(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(acptr->server->users * 100.0 / irccounts.clients), "");
+	}
+}
+
+/*
+** New /MAP format. -Potvin
+** cmd_map (NEW)
+**
+**      parv[1] = server mask
+**/
+CMD_FUNC(cmd_map)
+{
+	Client *acptr;
+	int  longest = strlen(me.name);
+	float avg_users;
+
+	umax = 0;
+	lmax = 0;
+
+	if (parc < 2)
+		parv[1] = "*";
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		int perc = (acptr->server->users * 100 / irccounts.clients);
+		if ((strlen(acptr->name) + acptr->hopcount * 2) > longest)
+			longest = strlen(acptr->name) + acptr->hopcount * 2;
+		if (lmax < perc)
+			lmax = perc;
+		if (umax < dcount(acptr->server->users))
+			umax = dcount(acptr->server->users);
+	}
+
+	if (longest > 60)
+		longest = 60;
+	longest += 2;
+
+	if (FLAT_MAP && !ValidatePermissionsForPath("server:info:map:real-map",client,NULL,NULL,NULL))
+		dump_flat_map(client, &me, longest);
+	else
+		dump_map(client, &me, "*", 0, longest);
+
+	avg_users = irccounts.clients * 1.0 / irccounts.servers;
+	sendnumeric(client, RPL_MAPUSERS, irccounts.servers, (irccounts.servers > 1 ? "s" : ""), irccounts.clients,
+		(irccounts.clients > 1 ? "s" : ""), avg_users);
+	sendnumeric(client, RPL_MAPEND);
+}
diff --git a/ircd/src/modules/max-unknown-connections-per-ip.c b/ircd/src/modules/max-unknown-connections-per-ip.c
@@ -0,0 +1,96 @@
+/*
+ * Connection throttling (set::max-unknown-connections-per-ip)
+ * (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"max-unknown-connections-per-ip",
+	"6.0.0",
+	"set::max-unknown-connections-per-ip",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declaration */
+int max_unknown_connections_accept(Client *client);
+int max_unknown_connections_ip_change(Client *client, const char *oldip);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -2000, max_unknown_connections_accept);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -2000, max_unknown_connections_ip_change);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This checks set::max-unknown-connections-per-ip,
+ * which is an important safety feature.
+ */
+static int check_too_many_unknown_connections(Client *client)
+{
+	int cnt = 1;
+	Client *c;
+
+	if (!find_tkl_exception(TKL_CONNECT_FLOOD, client))
+	{
+		list_for_each_entry(c, &unknown_list, lclient_node)
+		{
+			if (client->local && client->local->listener &&
+			    (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD))
+			{
+				continue;
+			}
+			if (!strcmp(client->ip,GetIP(c)))
+			{
+				cnt++;
+				if (cnt > iConf.max_unknown_connections_per_ip)
+					return 1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+int max_unknown_connections_accept(Client *client)
+{
+	if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
+		return 0;
+
+	/* Check set::max-unknown-connections-per-ip */
+	if (check_too_many_unknown_connections(client))
+	{
+		send_raw_direct(client, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)", client->ip);
+		return HOOK_DENY;
+	}
+
+	return 0;
+}
+
+int max_unknown_connections_ip_change(Client *client, const char *oldip)
+{
+	/* Check set::max-unknown-connections-per-ip */
+	if (check_too_many_unknown_connections(client))
+	{
+		sendto_one(client, NULL, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)", client->ip);
+		return HOOK_DENY;
+	}
+
+	return 0;
+}
diff --git a/ircd/src/modules/md.c b/ircd/src/modules/md.c
@@ -0,0 +1,527 @@
+/*
+ * Module Data module (command MD)
+ * (C) Copyright 2014-.. Bram Matthys and The UnrealIRCd Team
+ *
+ * This file contains all commands that deal with sending and
+ * receiving module data over the network.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"md",
+	"5.0",
+	"command /MD (S2S only)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+CMD_FUNC(cmd_md);
+void _broadcast_md_client(ModDataInfo *mdi, Client *client, ModData *md);
+void _broadcast_md_channel(ModDataInfo *mdi, Channel *channel, ModData *md);
+void _broadcast_md_member(ModDataInfo *mdi, Channel *channel, Member *m, ModData *md);
+void _broadcast_md_membership(ModDataInfo *mdi, Client *client, Membership *m, ModData *md);
+void _broadcast_md_globalvar(ModDataInfo *mdi, ModData *md);
+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);
+void _broadcast_moddata_client(Client *client);
+
+extern MODVAR ModDataInfo *MDInfo;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CLIENT, _broadcast_md_client);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CHANNEL, _broadcast_md_channel);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_MEMBER, _broadcast_md_member);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_MEMBERSHIP, _broadcast_md_membership);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_GLOBALVAR, _broadcast_md_globalvar);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CLIENT_CMD, _broadcast_md_client_cmd);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CHANNEL_CMD, _broadcast_md_channel_cmd);
+	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);
+	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MODDATA_CLIENT, _broadcast_moddata_client);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "MD", cmd_md, MAXPARA, CMD_SERVER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+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
+ *
+ * If <value> is ommitted, the variable is unset & freed.
+ *
+ * The appropriate module is called to set the data (unserialize) and
+ * then the command is broadcasted to all other servers.
+ *
+ * Technical documentation (if writing services) is available at:
+ * https://www.unrealircd.org/docs/Server_protocol:MD_command
+ * Module API documentation (if writing an UnrealIRCd module):
+ * https://www.unrealircd.org/docs/Dev:Module_Storage
+ */
+CMD_FUNC(cmd_md)
+{
+	const char *type, *objname, *varname, *value;
+	ModDataInfo *md;
+
+	if (!IsServer(client) || (parc < 4) || BadPtr(parv[3]))
+		return;
+
+	type = parv[1];
+	objname = parv[2];
+	varname = parv[3];
+	value = parv[4]; /* may be NULL */
+
+	if (!strcmp(type, "client"))
+	{
+		Client *target = find_client(objname, NULL);
+		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
+		{
+			if (md->free)
+				md->free(&moddata_client(target, md));
+			memset(&moddata_client(target, md), 0, sizeof(ModData));
+		}
+		/* Pass on to other servers */
+		broadcast_md_client_cmd(client->direction, client, target, varname, value);
+	} else
+	if (!strcmp(type, "channel"))
+	{
+		Channel *channel = find_channel(objname);
+		md = findmoddata_byname(varname, MODDATATYPE_CHANNEL);
+		if (!md || !md->unserialize || !channel)
+			return;
+		if (value)
+			md->unserialize(value, &moddata_channel(channel, md));
+		else
+		{
+			if (md->free)
+				md->free(&moddata_channel(channel, md));
+			memset(&moddata_channel(channel, md), 0, sizeof(ModData));
+		}
+		/* Pass on to other servers */
+		broadcast_md_channel_cmd(client->direction, client, channel, varname, value);
+	} 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;
+		*p++ = '\0';
+
+		channel = find_channel(objname);
+		if (!channel)
+			return;
+
+		target = find_user(p, NULL);
+		if (!target)
+			return;
+
+		m = find_member_link(channel->members, target);
+		if (!m)
+			return;
+
+		md = findmoddata_byname(varname, MODDATATYPE_MEMBER);
+		if (!md || !md->unserialize)
+			return;
+
+		if (!md_access_check(client, md, target))
+			return;
+
+		if (value)
+			md->unserialize(value, &moddata_member(m, md));
+		else
+		{
+			if (md->free)
+				md->free(&moddata_member(m, md));
+			memset(&moddata_member(m, md), 0, sizeof(ModData));
+		}
+		/* Pass on to other servers */
+		broadcast_md_member_cmd(client->direction, client, channel, target, varname, value);
+	} 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;
+		*p++ = '\0';
+
+		target = find_user(objname, NULL);
+		if (!target)
+			return;
+
+		channel = find_channel(p);
+		if (!channel)
+			return;
+
+		m = find_membership_link(target->user->channel, channel);
+		if (!m)
+			return;
+
+		md = findmoddata_byname(varname, MODDATATYPE_MEMBERSHIP);
+		if (!md || !md->unserialize)
+			return;
+
+		if (!md_access_check(client, md, target))
+			return;
+
+		if (value)
+			md->unserialize(value, &moddata_membership(m, md));
+		else
+		{
+			if (md->free)
+				md->free(&moddata_membership(m, md));
+			memset(&moddata_membership(m, md), 0, sizeof(ModData));
+		}
+		/* Pass on to other servers */
+		broadcast_md_membership_cmd(client->direction, client, target, channel, varname, value);
+	} else
+	if (!strcmp(type, "globalvar"))
+	{
+		/* objname is ignored */
+		md = findmoddata_byname(varname, MODDATATYPE_GLOBAL_VARIABLE);
+		if (!md || !md->unserialize)
+			return;
+		if (value)
+			md->unserialize(value, &moddata_global_variable(md));
+		else
+		{
+			if (md->free)
+				md->free(&moddata_global_variable(md));
+			memset(&moddata_global_variable(md), 0, sizeof(ModData));
+		}
+		/* Pass on to other servers */
+		broadcast_md_globalvar_cmd(client->direction, client, varname, value);
+	}
+}
+
+void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, const char *varname, const char *value)
+{
+	if (value)
+	{
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s :%s",
+			sender->id, "client", client->id, varname, value);
+	}
+	else
+	{
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s",
+			sender->id, "client", client->id, varname);
+	}
+}
+
+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->name, varname, value);
+	else
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s",
+			sender->id, "channel", channel->name, varname);
+}
+
+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->name, client->id, varname, value);
+	}
+	else
+	{
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s",
+			sender->id, "member", channel->name, client->id, varname);
+	}
+}
+
+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->name, varname, value);
+	}
+	else
+	{
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s",
+			sender->id, "membership", client->id, channel->name, varname);
+	}
+}
+
+void _broadcast_md_globalvar_cmd(Client *except, Client *sender, const char *varname, const char *value)
+{
+	if (value)
+	{
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s :%s",
+			sender->id, "globalvar", varname, value);
+	}
+	else
+	{
+		sendto_server(except, 0, 0, NULL, ":%s MD %s %s",
+			sender->id, "globalvar", varname);
+	}
+}
+
+/** Send module data update to all servers.
+ * @param mdi    Module Data Info structure (which you received from ModDataAdd)
+ * @param client The affected client
+ * @param md     The ModData. May be NULL for unset.
+ */
+ 
+void _broadcast_md_client(ModDataInfo *mdi, Client *client, ModData *md)
+{
+	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)
+{
+	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)
+{
+	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)
+{
+	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)
+{
+	const char *value = md ? mdi->serialize(md) : NULL;
+
+	broadcast_md_globalvar_cmd(NULL, &me, mdi->name, value);
+}
+
+/** Send all moddata attached to client 'client' to remote server 'srv' (if the module wants this), called by .. */
+void _send_moddata_client(Client *srv, Client *client)
+{
+	ModDataInfo *mdi;
+
+	for (mdi = MDInfo; mdi; mdi = mdi->next)
+	{
+		if ((mdi->type == MODDATATYPE_CLIENT) && mdi->sync && mdi->serialize)
+		{
+			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);
+		}
+	}
+}
+
+/** 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)
+{
+	ModDataInfo *mdi;
+
+	for (mdi = MDInfo; mdi; mdi = mdi->next)
+	{
+		if ((mdi->type == MODDATATYPE_CHANNEL) && mdi->sync && mdi->serialize)
+		{
+			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->name, mdi->name, value);
+		}
+	}
+}
+
+/** Send all moddata attached to member & memberships for 'channel' to remote server 'srv' (if the module wants this), called by SJOIN */
+void _send_moddata_members(Client *srv)
+{
+	ModDataInfo *mdi;
+	Channel *channel;
+	Client *client;
+
+	for (channel = channels; channel; channel = channel->nextch)
+	{
+		Member *m;
+		for (m = channel->members; m; m = m->next)
+		{
+			client = m->client;
+			if (client->direction == srv)
+				continue; /* from srv's direction */
+			for (mdi = MDInfo; mdi; mdi = mdi->next)
+			{
+				if ((mdi->type == MODDATATYPE_MEMBER) && mdi->sync && mdi->serialize)
+				{
+					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->name, client->id, mdi->name, value);
+				}
+			}
+		}
+	}
+
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		Membership *m;
+		if (!IsUser(client) || !client->user)
+			continue;
+
+		if (client->direction == srv)
+			continue; /* from srv's direction */
+
+		for (m = client->user->channel; m; m = m->next)
+		{
+			for (mdi = MDInfo; mdi; mdi = mdi->next)
+			{
+				if ((mdi->type == MODDATATYPE_MEMBERSHIP) && mdi->sync && mdi->serialize)
+				{
+					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->name, mdi->name, value);
+				}
+			}
+		}
+	}
+}
+
+/** Broadcast moddata attached to client 'client' to all servers. */
+void _broadcast_moddata_client(Client *client)
+{
+	Client *acptr;
+
+	list_for_each_entry(acptr, &server_list, special_node)
+	{
+		send_moddata_client(acptr, client);
+	}
+}
diff --git a/ircd/src/modules/message-ids.c b/ircd/src/modules/message-ids.c
@@ -0,0 +1,156 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/message-ids.c
+ *   (C) 2019 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"message-ids",
+	"5.0",
+	"msgid CAP",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_ACCOUNT_TAG = 0L;
+
+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()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "msgid";
+	mtag.is_ok = msgid_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_or_inherit_msgid);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending
+ * 'msgid' is permitted to do so and uses a permitted
+ * syntax.
+ * We simply allow msgid ONLY from servers and with any syntax.
+ */
+int msgid_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client) && !BadPtr(value))
+		return 1;
+
+	return 0;
+}
+
+/** Generate a msgid.
+ * @returns a MessageTag struct that you can use directly.
+ * @note
+ * Apparently there has been some discussion on what method to use to
+ * generate msgid's. I am not going to list them here. Just saying that
+ * they have been considered and we chose to go for a string that contains
+ * 128+ bits of randomness, which has an extremely low chance of colissions.
+ * Or, to quote wikipedia on the birthday attack problem:
+ * "For comparison, 10^-18 to 10^-15 is the uncorrectable bit error rate
+ *  of a typical hard disk. In theory, hashes or UUIDs being 128 bits,
+ *  should stay within that range until about 820 billion outputs"
+ * For reference, 10^-15 is 0.000000000000001%
+ * The main reasons for this choice are: that it is extremely simple,
+ * the chance of making a mistake in an otherwise complex implementation
+ * is nullified and we don't risk "leaking" any details.
+ */
+MessageTag *mtag_generate_msgid(void)
+{
+	MessageTag *m = safe_alloc(sizeof(MessageTag));
+	safe_strdup(m->name, "msgid");
+	m->value = safe_alloc(MSGIDLEN+1);
+	gen_random_alnum(m->value, MSGIDLEN);
+	return m;
+}
+
+
+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)
+		m = duplicate_mtag(m);
+	else
+		m = mtag_generate_msgid();
+
+	if (signature)
+	{
+		/* Special case:
+		 * Some commands will receive a single msgid from
+		 * a remote server for multiple events.
+		 * Take for example SJOIN which may contain 5 joins,
+		 * 3 bans setting, 2 invites, and setting a few modes.
+		 * This way we can still generate unique msgid's
+		 * for such sub-events. It is a hash of the subevent
+		 * concatenated to the existing msgid.
+		 * The hash is the first half of a SHA256 hash, then
+		 * base64'd, and with the == suffix removed.
+		 */
+		char prefix[MSGIDLEN+1], *p;
+		strlcpy(prefix, m->value, sizeof(prefix));
+		p = strchr(prefix, '-');
+		if (p)
+		{
+			/* It is possible that we have more stacking.
+			 * IOTW: we are already stacked like xxx-yyy
+			 * and it would have become an xxx-yyy-zzz
+			 * sequence. Instead, we strip the yyy-
+			 * so the end result will be xxx-zzz.
+			 *
+			 * One example code path would be when someone joins
+			 * and the issecure module sets -Z.
+			 */
+			*p = '\0';
+		}
+		SHA256_CTX hash;
+		char binaryhash[SHA256_DIGEST_LENGTH];
+		char b64hash[SHA256_DIGEST_LENGTH*2+1];
+		char newbuf[256];
+		memset(&binaryhash, 0, sizeof(binaryhash));
+		memset(&b64hash, 0, sizeof(b64hash));
+		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);
+		safe_strdup(m->value, newbuf);
+	}
+	AddListItem(m, *mtag_list);
+}
diff --git a/ircd/src/modules/message-tags.c b/ircd/src/modules/message-tags.c
@@ -0,0 +1,311 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/message-tags.c
+ *   (C) 2019 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"message-tags",
+	"5.0",
+	"Message tags CAP", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+long CAP_MESSAGE_TAGS = 0L;
+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);
+
+	EfunctionAddConstString(modinfo->handle, EFUNC_MTAGS_TO_STRING, _mtags_to_string);
+	EfunctionAddVoid(modinfo->handle, EFUNC_PARSE_MESSAGE_TAGS, _parse_message_tags);
+
+	return 0;
+}
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "message-tags";
+	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_MESSAGE_TAGS);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Unescape a message tag (name or value).
+ * @param in  The input string
+ * @param out The output string for writing
+ * @note  No size checking, so ensure that the output buffer
+ *        is at least as long as the input buffer.
+ */
+void message_tag_unescape(char *in, char *out)
+{
+	for (; *in; in++)
+	{
+		if (*in == '\\')
+		{
+			in++;
+			if (*in == ':')
+				*out++ = ';';  /* \: to ; */
+			else if (*in == 's')
+				*out++ = ' ';  /* \s to SPACE */
+			else if (*in == 'r')
+				*out++ = '\r'; /* \r to CR */
+			else if (*in == 'n')
+				*out++ = '\n'; /* \n to LF */
+			else if (*in == '\0')
+				break; /* unfinished escaping (\) */
+			else
+				*out++ = *in; /* all rest is as-is */
+			continue;
+		}
+		*out++ = *in;
+	}
+	*out = '\0';
+}
+
+/** Escape a message tag (name or value).
+ * @param in  The input string
+ * @param out The output string for writing
+ * @note  No size checking, so ensure that the output buffer
+ *        is at least twice as long as the input buffer + 1.
+ */
+void message_tag_escape(char *in, char *out)
+{
+	for (; *in; in++)
+	{
+		if (*in == ';')
+		{
+			*out++ = '\\';
+			*out++ = ':';
+		} else
+		if (*in == ' ')
+		{
+			*out++ = '\\';
+			*out++ = 's';
+		} else
+		if (*in == '\\')
+		{
+			*out++ = '\\';
+			*out++ = '\\';
+		} else
+		if (*in == '\r')
+		{
+			*out++ = '\\';
+			*out++ = 'r';
+		} else
+		if (*in == '\n')
+		{
+			*out++ = '\\';
+			*out++ = 'n';
+		} else
+		{
+			*out++ = *in;
+		}
+	}
+	*out = '\0';
+}
+
+/** Incoming filter for message tags */
+int message_tag_ok(Client *client, char *name, char *value)
+{
+	MessageTagHandler *m;
+
+	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;
+
+	return 0;
+}
+
+void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list)
+{
+	char *remainder;
+	char *element, *p, *x;
+	static char name[8192], value[8192];
+	MessageTag *m;
+
+	remainder = strchr(*str, ' ');
+	if (remainder)
+		*remainder = '\0';
+
+	if (!IsServer(client) && (strlen(*str) > 4094))
+	{
+		sendnumeric(client, ERR_INPUTTOOLONG);
+		remainder = NULL; /* stop parsing */
+	}
+
+	if (!remainder)
+	{
+		/* A message with only message tags (or starting with @ anyway).
+		 * This is useless. So we make it point to the NUL byte,
+		 * aka: empty message.
+		 * This is also used by a line-length-check above to force the
+		 * same error condition ("don't parse this").
+		 */
+		for (; **str; *str += 1);
+		return;
+	}
+
+	/* Now actually parse the tags: */
+	for (element = strtoken(&p, *str+1, ";"); element; element = strtoken(&p, NULL, ";"))
+	{
+		*name = *value = '\0';
+
+		/* Element has style: 'name=value', or it could be just 'name' */
+		x = strchr(element, '=');
+		if (x)
+		{
+			*x++ = '\0';
+			message_tag_unescape(x, value);
+		}
+		message_tag_unescape(element, name);
+
+		/* Let the message tag handler check if this mtag is
+		 * acceptable. If so, we add it to the list.
+		 */
+		if (message_tag_ok(client, name, value))
+		{
+			m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, name);
+			/* Both NULL and empty become NULL: */
+			if (!*value)
+				m->value = NULL;
+			else /* a real value... */
+				safe_strdup(m->value, value);
+			AddListItem(m, *mtag_list);
+		}
+	}
+
+	*str = remainder + 1;
+}
+
+/** Outgoing filter for tags */
+int client_accepts_tag(const char *token, Client *client)
+{
+	MessageTagHandler *m;
+
+	/* Send all tags to remote links, without checking here.
+	 * Note that mtags_to_string() already prevents sending messages
+	 * with message tags to links without PROTOCTL MTAGS, so we can
+	 * simply always return 1 here, regardless of checking (again).
+	 */
+	if (IsServer(client) || !MyConnect(client))
+		return 1;
+
+	m = MessageTagHandlerFind(token);
+	if (!m)
+		return 0;
+
+	/* Maybe there is an outgoing filter in effect (usually not) */
+	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
+	 * send any message tag, regardless of other CAP's.
+	 */
+	if (HasCapability(client, "message-tags"))
+		return 1;
+
+	/* We continue here if the client did not indicate 'message-tags' support... */
+
+	/* If 'message-tags' is not indicated, then these cannot be sent as they don't
+	 * have a CAP to enable anyway (eg: msgid):
+	 */
+	if (m->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED)
+		return 0;
+
+	/* Otherwise, check if the capability is set:
+	 * eg 'account-tag' for 'account', 'time' for 'server-time' and so on..
+	 */
+	if (m->clicap_handler && (client->local->caps & m->clicap_handler->cap))
+		return 1;
+
+	return 0;
+}
+
+/** Return the message tag string (without @) of the message tag linked list.
+ * Taking into account the restrictions that 'client' may have.
+ * @returns A string (static buffer) or NULL if no tags at all (!)
+ */
+const char *_mtags_to_string(MessageTag *m, Client *client)
+{
+	static char buf[4096], name[8192], value[8192];
+	static char tbuf[4094];
+
+	if (!m)
+		return NULL;
+
+	/* Remote servers need to indicate support via PROTOCTL MTAGS */
+	if (client->direction && IsServer(client->direction) && !SupportMTAGS(client->direction))
+		return NULL;
+
+	*buf = '\0';
+	for (; m; m = m->next)
+	{
+		if (!client_accepts_tag(m->name, client))
+			continue;
+		if (m->value)
+		{
+			message_tag_escape(m->name, name);
+			message_tag_escape(m->value, value);
+			snprintf(tbuf, sizeof(tbuf), "%s=%s;", name, value);
+		} else {
+			message_tag_escape(m->name, name);
+			snprintf(tbuf, sizeof(tbuf), "%s;", name);
+		}
+		strlcat(buf, tbuf, sizeof(buf));
+	}
+
+	if (!*buf)
+		return NULL;
+
+	/* Strip off the final semicolon */
+	buf[strlen(buf)-1] = '\0';
+
+	return buf;
+}
diff --git a/ircd/src/modules/message.c b/ircd/src/modules/message.c
@@ -0,0 +1,710 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/message.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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"
+
+/* Forward declarations */
+const char *_StripColors(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, 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 */
+
+ModuleHeader MOD_HEADER
+  = {
+	"message",	/* Name of module */
+	"6.0.2", /* Version */
+	"private message and notice", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCOLORS, _StripColors);
+	EfunctionAdd(modinfo->handle, EFUNC_CAN_SEND_TO_CHANNEL, _can_send_to_channel);
+	return MOD_SUCCESS;
+}
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "PRIVMSG", cmd_private, 2, CMD_USER|CMD_SERVER|CMD_RESETIDLE|CMD_VIRUS);
+	CommandAdd(modinfo->handle, "NOTICE", cmd_notice, 2, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, "TAGMSG", cmd_tagmsg, 1, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	CAP_MESSAGE_TAGS = ClientCapabilityBit("message-tags");
+
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+#define CANPRIVMSG_CONTINUE		100
+#define CANPRIVMSG_SEND			101
+/** Check if PRIVMSG's are permitted from a person to another person.
+ * client:	source client
+ * target:	target client
+ * sendtype:	One of SEND_TYPE_*
+ * text:	Pointer to a pointer to a text [in, out]
+ * cmd:		Pointer to a pointer which contains the command to use [in, out]
+ */
+int can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype)
+{
+	int ret;
+	Hook *h;
+	int n;
+	static char errbuf[256];
+
+	*errmsg = NULL;
+
+	if (IsVirus(client))
+	{
+		ircsnprintf(errbuf, sizeof(errbuf), "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
+		*errmsg = errbuf;
+		return 0;
+	}
+
+	if (MyUser(client) && target_limit_exceeded(client, target, target->name))
+	{
+		/* target_limit_exceeded() is an exception, in the sense that
+		 * it will send a different numeric. So we don't set errmsg.
+		 */
+		return 0;
+	}
+
+	if (is_silenced(client, target))
+	{
+		RunHook(HOOKTYPE_SILENCED, client, target, sendtype);
+		/* Silently discarded, no error message */
+		return 0;
+	}
+
+	// Possible FIXME: make match_spamfilter also use errmsg, or via a wrapper? or use same numeric?
+	if (MyUser(client))
+	{
+		int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG);
+		const char *cmd = sendtype_to_cmd(sendtype);
+
+		if (match_spamfilter(client, *msgtext, spamtype, cmd, target->name, 0, NULL))
+			return 0;
+	}
+
+	n = HOOK_CONTINUE;
+	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_USER]; h; h = h->next)
+	{
+		n = (*(h->func.intfunc))(client, target, msgtext, errmsg, sendtype);
+		if (n == HOOK_DENY)
+		{
+			if (!*errmsg)
+			{
+				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;
+		}
+		if (!*msgtext || !**msgtext)
+		{
+			if (sendtype != SEND_TYPE_TAGMSG)
+				return 0;
+			else
+				*msgtext = "";
+		}
+	}
+
+	return 1;
+}
+
+/** Check if user is allowed to send to a prefix (eg: @#channel).
+ * @param client	The client (sender)
+ * @param channel	The target channel
+ * @param mode		The member mode to send to (eg: 'o')
+ */
+int can_send_to_member_mode(Client *client, Channel *channel, char mode)
+{
+	Membership *lp;
+
+	if (op_can_override("channel:override:message:prefix",client,channel,NULL))
+		return 1;
+
+	lp = find_membership_link(client->user->channel, channel);
+
+	/* Check if user is allowed to send. RULES:
+	 * Need at least voice (+) in order to send to +,% or @
+	 * Need at least ops (@) in order to send to & or ~
+	 */
+	if (!lp || !check_channel_access_membership(lp, "vhoaq"))
+	{
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return 0;
+	}
+
+#if 0
+	if (!(prefix & PREFIX_OP) && ((prefix & PREFIX_OWNER) || (prefix & PREFIX_ADMIN)) &&
+	    !check_channel_access_membership(lp, "oaq"))
+	{
+		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
+		return 0;
+	}
+#endif
+
+	return 1;
+}
+
+int has_client_mtags(MessageTag *mtags)
+{
+	MessageTag *m;
+
+	for (m = mtags; m; m = m->next)
+		if (*m->name == '+')
+			return 1;
+	return 0;
+}
+
+/* General message handler to users and channels. Used by PRIVMSG, NOTICE, etc.
+ */
+void cmd_message(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], SendType sendtype)
+{
+	Client *target;
+	Channel *channel;
+	char targets[BUFSIZE];
+	char *targetstr, *p, *p2, *pc;
+	const char *text, *errmsg;
+	int ret;
+	int ntargets = 0;
+	const char *cmd = sendtype_to_cmd(sendtype);
+	int maxtargets = max_targets_for_command(cmd);
+	Hook *h;
+	MessageTag *mtags;
+	int sendflags;
+
+	/* Force a labeled-response, even if we don't send anything
+	 * and the request was sent to other servers (which won't
+	 * reply either :D).
+	 */
+	labeled_response_force = 1;
+
+	if (parc < 2 || *parv[1] == '\0')
+	{
+		sendnumeric(client, ERR_NORECIPIENT, cmd);
+		return;
+	}
+
+	if ((sendtype != SEND_TYPE_TAGMSG) && (parc < 3 || *parv[2] == '\0'))
+	{
+		sendnumeric(client, ERR_NOTEXTTOSEND);
+		return;
+	}
+
+	if (MyConnect(client))
+		parv[1] = (char *)canonize(parv[1]);
+
+	strlcpy(targets, parv[1], sizeof(targets));
+	for (p = NULL, targetstr = strtoken(&p, targets, ","); targetstr; targetstr = strtoken(&p, NULL, ","))
+	{
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, targetstr, maxtargets, cmd);
+			break;
+		}
+
+		/* The nicks "ircd" and "irc" are special (and reserved) */
+		if (!strcasecmp(targetstr, "ircd") && MyUser(client))
+			return;
+
+		if (!strcasecmp(targetstr, "irc") && MyUser(client))
+		{
+			/* When ban version { } is enabled the IRCd sends a CTCP VERSION request
+			 * from the "IRC" nick. So we need to handle CTCP VERSION replies to "IRC".
+			 */
+			if (!strncmp(parv[2], "\1VERSION ", 9))
+				ban_version(client, parv[2] + 9);
+			else if (!strncmp(parv[2], "\1SCRIPT ", 8))
+				ban_version(client, parv[2] + 8);
+			return;
+		}
+
+		p2 = strchr(targetstr, '#');
+
+		/* Message to channel */
+		if (p2 && (channel = find_channel(p2)))
+		{
+			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)
+			{
+				/* Replace target so the privmsg always goes to the "official" channel name */
+				strlcpy(pfixchan, channel->name, sizeof(pfixchan));
+				targetstr = pfixchan;
+			}
+
+			if (IsVirus(client) && strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN))
+			{
+				sendnotice(client, "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
+				continue;
+			}
+
+			text = parv[2];
+			errmsg = NULL;
+			if (MyUser(client) && !IsULine(client))
+			{
+				if (!can_send_to_channel(client, channel, &text, &errmsg, sendtype))
+				{
+					/* Send the error message, but only if:
+					 * 1) The user has not been killed
+					 * 2) It is not a NOTICE
+					 */
+					if (IsDead(client))
+						return;
+					if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE) && !BadPtr(errmsg))
+						sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->name, errmsg, p2);
+					continue; /* skip delivery to this target */
+				}
+			}
+			mtags = NULL;
+			sendflags = SEND_ALL;
+
+			if (!strchr(CHANCMDPFX,parv[2][0]))
+				sendflags |= SKIP_DEAF;
+
+			if ((*parv[2] == '\001') && strncmp(&parv[2][1], "ACTION ", 7))
+				sendflags |= SKIP_CTCP;
+
+			if (MyUser(client))
+			{
+				int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG);
+
+				if (match_spamfilter(client, text, spamtype, cmd, channel->name, 0, NULL))
+					return;
+			}
+
+			new_message(client, recv_mtags, &mtags);
+
+			RunHook(HOOKTYPE_PRE_CHANMSG, client, channel, mtags, text, sendtype);
+
+			if (!text)
+			{
+				free_message_tags(mtags);
+				continue;
+			}
+
+			if (sendtype != SEND_TYPE_TAGMSG)
+			{
+				/* PRIVMSG or NOTICE */
+				sendto_channel(channel, client, client->direction,
+					       member_modes, 0, sendflags, mtags,
+					       ":%s %s %s :%s",
+					       client->name, cmd, targetstr, text);
+			} else {
+				/* TAGMSG:
+				 * Only send if the message includes any user message tags
+				 * and if the 'message-tags' module is loaded.
+				 * Do not allow empty and useless TAGMSG.
+				 */
+				if (!CAP_MESSAGE_TAGS || !has_client_mtags(mtags))
+				{
+					free_message_tags(mtags);
+					continue;
+				}
+				sendto_channel(channel, client, client->direction,
+					       member_modes, CAP_MESSAGE_TAGS, sendflags, mtags,
+					       ":%s TAGMSG %s",
+					       client->name, targetstr);
+			}
+
+			RunHook(HOOKTYPE_CHANMSG, client, channel, sendflags, member_modes, targetstr, mtags, text, sendtype);
+
+			free_message_tags(mtags);
+
+			continue;
+		}
+		else if (p2)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, p2);
+			continue;
+		}
+
+
+		/* Message to $servermask */
+		if (*targetstr == '$')
+		{
+			MessageTag *mtags = NULL;
+
+			if (!ValidatePermissionsForPath("chat:notice:global", client, NULL, NULL, NULL))
+			{
+				/* Apparently no other IRCd does this, but I think it's confusing not to
+				 * send an error message, especially with our new privilege system.
+				 * Error message could be more descriptive perhaps.
+				 */
+				sendnumeric(client, ERR_NOPRIVILEGES);
+				continue;
+			}
+			new_message(client, recv_mtags, &mtags);
+			sendto_match_butone(IsServer(client->direction) ? client->direction : NULL,
+			    client, targetstr + 1,
+			    (*targetstr == '#') ? MATCH_HOST :
+			    MATCH_SERVER,
+			    mtags,
+			    ":%s %s %s :%s", client->name, cmd, targetstr, parv[2]);
+			free_message_tags(mtags);
+			continue;
+		}
+
+		/* nickname addressed? */
+		target = hash_find_nickatserver(targetstr, NULL);
+		if (target)
+		{
+			const char *errmsg = NULL;
+			text = parv[2];
+			if (!can_send_to_user(client, target, &text, &errmsg, sendtype))
+			{
+				/* Message is discarded */
+				if (IsDead(client))
+					return;
+				if ((sendtype != SEND_TYPE_NOTICE) && !BadPtr(errmsg))
+					sendnumeric(client, ERR_CANTSENDTOUSER, target->name, errmsg);
+			} else
+			{
+				/* We may send the message */
+				MessageTag *mtags = NULL;
+
+				/* Inform sender that recipient is away, if this is so */
+				if ((sendtype == SEND_TYPE_PRIVMSG) && MyConnect(client) && target->user && target->user->away)
+					sendnumeric(client, RPL_AWAY, target->name, target->user->away);
+
+				new_message(client, recv_mtags, &mtags);
+				if ((sendtype == SEND_TYPE_TAGMSG) && !has_client_mtags(mtags))
+				{
+					free_message_tags(mtags);
+					continue;
+				}
+				labeled_response_inhibit = 1;
+				if (MyUser(target))
+				{
+					/* Deliver to end-user */
+					if (sendtype == SEND_TYPE_TAGMSG)
+					{
+						if (HasCapability(target, "message-tags"))
+						{
+							sendto_prefix_one(target, client, mtags, ":%s %s %s",
+									  client->name, cmd, target->name);
+						}
+					} else {
+						sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
+								  client->name, cmd, target->name, text);
+					}
+				} else {
+					/* Send to another server */
+					if (sendtype == SEND_TYPE_TAGMSG)
+					{
+						sendto_prefix_one(target, client, mtags, ":%s %s %s",
+								  client->id, cmd, target->id);
+					} else {
+						sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
+								  client->id, cmd, target->id, text);
+					}
+				}
+				labeled_response_inhibit = 0;
+				RunHook(HOOKTYPE_USERMSG, client, target, mtags, text, sendtype);
+				free_message_tags(mtags);
+				continue;
+			}
+			continue; /* Message has been delivered or rejected, continue with next target */
+		}
+
+		/* If nick@server -and- the @server portion was set::services-server then send a special message */
+		if (!target && SERVICES_NAME)
+		{
+			char *server = strchr(targetstr, '@');
+			if (server && strncasecmp(server + 1, SERVICES_NAME, strlen(SERVICES_NAME)) == 0)
+			{
+				sendnumeric(client, ERR_SERVICESDOWN, targetstr);
+				continue;
+			}
+		}
+
+		/* nothing, nada, not anything found */
+		sendnumeric(client, ERR_NOSUCHNICK, targetstr);
+		continue;
+	}
+}
+
+/*
+** cmd_private
+**	parv[1] = receiver list
+**	parv[2] = message text
+*/
+CMD_FUNC(cmd_private)
+{
+	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_PRIVMSG);
+}
+
+/*
+** cmd_notice
+**	parv[1] = receiver list
+**	parv[2] = notice text
+*/
+CMD_FUNC(cmd_notice)
+{
+	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_NOTICE);
+}
+
+/*
+** cmd_tagmsg
+**	parv[1] = receiver list
+*/
+CMD_FUNC(cmd_tagmsg)
+{
+	/* compatibility hack */
+	parv[2] = "";
+	parv[3] = NULL;
+	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_TAGMSG);
+}
+
+/* Taken from xchat by Peter Zelezny
+ * changed very slightly by codemastr
+ * RGB color stripping support added -- codemastr
+ *
+ * NOTE: if you change/update/enhance StripColors() then consider changing
+ *       the StripControlCodes() function as well (in misc.c) !!
+ */
+const char *_StripColors(const char *text)
+{
+	int i = 0, len = strlen(text), save_len=0;
+	char nc = 0, col = 0, rgb = 0;
+	const char *save_text=NULL;
+	static char new_str[4096];
+
+	while (len > 0) 
+	{
+		if ((col && isdigit(*text) && nc < 2) ||
+		    ((col == 1) && (*text == ',') && isdigit(text[1]) && (nc > 0) && (nc < 3)))
+		{
+			nc++;
+			if (*text == ',')
+			{
+				nc = 0;
+				col++;
+			}
+		}
+		/* Syntax for RGB is ^DHHHHHH where H is a hex digit.
+		 * If < 6 hex digits are specified, the code is displayed
+		 * as text
+		 */
+		else if ((rgb && isxdigit(*text) && nc < 6) || (rgb && *text == ',' && nc < 7))
+		{
+			nc++;
+			if (*text == ',')
+				nc = 0;
+		}
+		else 
+		{
+			if (col)
+				col = 0;
+			if (rgb)
+			{
+				if (nc != 6)
+				{
+					text = save_text+1;
+					len = save_len-1;
+					rgb = 0;
+					continue;
+				}
+				rgb = 0;
+			}
+			if (*text == '\003') 
+			{
+				col = 1;
+				nc = 0;
+			}
+			else if (*text == '\004')
+			{
+				save_text = text;
+				save_len = len;
+				rgb = 1;
+				nc = 0;
+			}
+			else if (*text != '\026') /* (strip reverse too) */
+			{
+				new_str[i] = *text;
+				i++;
+			}
+		}
+		text++;
+		len--;
+	}
+	new_str[i] = 0;
+	if (new_str[0] == '\0')
+		return NULL;
+	return new_str;
+}
+
+/** Check ban version { } blocks, returns 1  if banned and  0 if not. */
+int ban_version(Client *client, const char *text)
+{
+	int len;
+	ConfigItem_ban *ban;
+	char ctcp_reply[BUFSIZE];
+
+	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 ((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 */
+
+		if (find_tkl_exception(TKL_BAN_VERSION, client))
+			return 0; /* we are exempt */
+
+		place_host_ban(client, ban->action, ban->reason, BAN_VERSION_TKL_TIME);
+		return 1;
+	}
+
+	return 0;
+}
+
+/** Can user send a message to this channel?
+ * @param client    The client
+ * @param channel   The channel
+ * @param msgtext   The message to send (MAY be changed, even if user is allowed to send)
+ * @param errmsg    The error message (will be filled in)
+ * @param sendtype  One of SEND_TYPE_*
+ * @returns Returns 1 if the user is allowed to send, otherwise 0.
+ * (note that this behavior was reversed in UnrealIRCd versions <5.x.
+ */
+int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype)
+{
+	Membership *lp;
+	int  member, i = 0;
+	Hook *h;
+
+	if (!MyUser(client))
+		return 1;
+
+	*errmsg = NULL;
+
+	member = IsMember(client, channel);
+
+	lp = find_membership_link(client->user->channel, channel);
+
+	/* Modules can plug in as well */
+	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_CHANNEL]; h; h = h->next)
+	{
+		i = (*(h->func.intfunc))(client, channel, lp, msgtext, errmsg, sendtype);
+		if (i != HOOK_CONTINUE)
+		{
+			if (!*errmsg)
+			{
+				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;
+		}
+		if (!*msgtext || !**msgtext)
+		{
+			if (sendtype != SEND_TYPE_TAGMSG)
+				return 0;
+			else
+				*msgtext = "";
+		}
+	}
+
+	if (i != HOOK_CONTINUE)
+	{
+		if (!*errmsg)
+			*errmsg = "You are banned";
+		/* Don't send message if the user was previously a member
+		 * and isn't anymore, so if the user is KICK'ed, eg by floodprot.
+		 */
+		if (member && !IsDead(client) && !find_membership_link(client->user->channel, channel))
+			*errmsg = NULL;
+		return 0;
+	}
+
+	/* Now we are going to check bans */
+
+	/* ..but first: exempt ircops */
+	if (op_can_override("channel:override:message:ban",client,channel,NULL))
+		return 1;
+
+	/* 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)
+			*errmsg = "You are banned";
+		return 0;
+	}
+
+	return 1;
+}
diff --git a/ircd/src/modules/mkpasswd.c b/ircd/src/modules/mkpasswd.c
@@ -0,0 +1,106 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/mkpasswd.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   mkpasswd command
+ *
+ *   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_mkpasswd);
+
+#define MSG_MKPASSWD 	"MKPASSWD"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"mkpasswd",
+	"5.0",
+	"command /mkpasswd", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_MKPASSWD, cmd_mkpasswd, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_mkpasswd
+**      parv[1] = password to encrypt
+*/
+CMD_FUNC(cmd_mkpasswd)
+{
+	short type;
+	const char *result = NULL;
+
+	if (!MKPASSWD_FOR_EVERYONE && !IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	if (!IsOper(client))
+	{
+		/* 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.
+		 */
+		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]))
+	{
+		sendnotice(client, "*** Syntax: /mkpasswd <authmethod> :parameter");
+		return;
+	}
+	/* Don't want to take any risk ;p. -- Syzop */
+	if (strlen(parv[2]) > 64)
+	{
+		sendnotice(client, "*** Your parameter (text-to-hash) is too long.");
+		return;
+	}
+	if ((type = Auth_FindType(NULL, parv[1])) == -1)
+	{
+		sendnotice(client, "*** %s is not an enabled authentication method", parv[1]);
+		return;
+	}
+
+	if (!(result = Auth_Hash(type, parv[2])))
+	{
+		sendnotice(client, "*** Authentication method %s failed", parv[1]);
+		return;
+	}
+
+	sendnotice(client, "*** Authentication phrase (method=%s, para=%s) is: %s",
+		parv[1], parv[2], result);
+}
diff --git a/ircd/src/modules/mode.c b/ircd/src/modules/mode.c
@@ -0,0 +1,1569 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/mode.c
+ *   (C) 2005-.. 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
+  = {
+	"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, 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, MessageTag *mtags, const char *modes, const char *parameters);
+CMD_FUNC(_cmd_umode);
+
+/* local: */
+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]);
+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 char *mode_cutoff(const char *s);
+void mode_operoverride_msg(Client *client, Channel *channel, char *modebuf, char *parabuf);
+
+static int samode_in_progress = 0;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_DO_MODE, _do_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, "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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * cmd_mode -- written by binary (garryb@binary.islesfan.net)
+ * Completely rewrote it.  The old mode command was 820 lines of ICKY
+ * coding, which is a complete waste, because I wrote it in 570 lines of
+ * *decent* coding.  This is also easier to read, change, and fine-tune.  Plus,
+ * everything isn't scattered; everything's grouped where it should be.
+ *
+ * parv[1] - channel
+ */
+CMD_FUNC(cmd_mode)
+{
+	long unsigned sendts = 0;
+	Ban *ban;
+	Channel *channel;
+
+	/* Now, try to find the channel in question */
+	if (parc > 1)
+	{
+		if (*parv[1] == '#')
+		{
+			channel = find_channel(parv[1]);
+			if (!channel)
+			{
+				CALL_CMD_FUNC(cmd_umode);
+				return;
+			}
+		} else
+		{
+			CALL_CMD_FUNC(cmd_umode);
+			return;
+		}
+	} else
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "MODE");
+		return;
+	}
+
+	if (MyConnect(client) && !valid_channelname(parv[1]))
+	{
+		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+		return;
+	}
+
+	if (parc < 3)
+	{
+		char modebuf[BUFSIZE], parabuf[BUFSIZE];
+		*modebuf = *parabuf = '\0';
+
+		modebuf[1] = '\0';
+		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;
+	}
+
+	/* 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) && !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) && !check_channel_access(client, channel, "oaq") &&
+	    check_channel_access(client, channel, "h") && ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
+	{
+		opermode = 2;
+		goto aftercheck;
+	}
+#endif
+
+	/* User does not have permission to use the MODE command */
+	if (MyUser(client) && !IsULine(client) && !check_channel_access(client, channel, "hoaq") &&
+	    !ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
+	{
+		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) && (sendts > channel->creationtime))
+	{
+		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')
+		sendts = -1;
+	if (IsServer(client) && sendts != -1)
+		parc--;		/* server supplied a time stamp, remove it now */
+
+aftercheck:
+
+	/* This is to prevent excess +<whatever> modes. -- Syzop */
+	if (MyUser(client) && parv[2])
+	{
+		parv[2] = mode_cutoff(parv[2]);
+	}
+
+	/* Filter out the unprivileged FIRST. *
+	 * Now, we can actually do the mode.  */
+
+	(void)do_mode(channel, client, recv_mtags, parc - 2, parv + 2, sendts, 0);
+	/* After this don't touch 'channel' anymore, as permanent module may have destroyed the channel */
+	opermode = 0; /* Important since sometimes forgotten. -- Syzop */
+}
+
+/** 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
+ * @returns The cleaned up string
+ */
+static char *mode_cutoff(const char *i)
+{
+	static char newmodebuf[BUFSIZE];
+	char *o;
+	unsigned short modesleft = MAXMODEPARAMS * 2; /* be generous... */
+
+	strlcpy(newmodebuf, i, sizeof(newmodebuf));
+
+	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, 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;
+	int i;
+	char tschange = 0;
+	MultiLineMode *m;
+
+	/* Please keep the next 3 lines next to each other */
+	samode_in_progress = samode;
+	m = set_mode(channel, client, parc, parv, &pcount, pvar);
+	samode_in_progress = 0;
+
+	if (IsServer(client))
+	{
+		if (sendts > 0)
+		{
+			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 > channel->creationtime && channel->creationtime)
+			{
+				/* Their timestamp is wrong */
+				sendts = channel->creationtime;
+				sendto_one(client, NULL, ":%s MODE %s + %lld", me.name,
+				    channel->name, (long long)channel->creationtime);
+			}
+		}
+		if (sendts == -1)
+			sendts = channel->creationtime;
+	}
+
+	if (!m)
+	{
+		/* No modes changed (empty mode change) */
+		if (tschange && !m)
+		{
+			/* 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;
+	}
+
+	/* Now loop through the multiline modes... */
+	for (i = 0; i < m->numlines; i++)
+	{
+		char *modebuf = m->modeline[i];
+		char *parabuf = m->paramline[i];
+		MessageTag *mtags = NULL;
+		int should_destroy = 0;
+
+		if (IsUser(orig_client) && samode && MyUser(orig_client))
+		{
+			if (!sajoinmode)
+			{
+				char buf[512];
+				snprintf(buf, sizeof(buf), "%s%s%s", modebuf, *parabuf ? " " : "", parabuf);
+				unreal_log(ULOG_INFO, "samode", "SAMODE_COMMAND", orig_client,
+					   "Client $client used SAMODE $channel ($mode)",
+					   log_data_channel("channel", channel),
+					   log_data_string("mode", buf));
+			}
+
+			client = &me;
+			sendts = 0;
+		}
+
+		if (m->numlines == 1)
+		{
+			/* Single mode lines are easy: retain original msgid etc */
+			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);
+		}
+
+		/* IMPORTANT: if you return, don't forget to free mtags!! */
+
+		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);
+
+		/* opermode for twimodesystem --sts */
+#ifndef NO_OPEROVERRIDE
+		if ((opermode == 1) && IsUser(client))
+		{
+			mode_operoverride_msg(client, channel, modebuf, parabuf);
+
+			sendts = 0;
+		}
+#endif
+
+		sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
+			       ":%s MODE %s %s %s",
+			       client->name, channel->name, modebuf, parabuf);
+
+		if (IsServer(client) || IsMe(client))
+		{
+			sendto_server(client, 0, 0, mtags,
+				      ":%s MODE %s %s %s %lld",
+				      client->id, channel->name,
+				      modebuf, parabuf,
+				      (sendts != -1) ? (long long)sendts : 0LL);
+		} else
+		{
+			sendto_server(client, 0, 0, mtags,
+				      ":%s MODE %s %s %s",
+				      client->id, channel->name,
+				      modebuf, parabuf);
+		}
+
+		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);
+
+		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.
+ */
+MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, int pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
+{
+	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;
+
+	/* 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;
+
+	/* 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 (!cm->letter || cm->paracount)
+			continue;
+		/* have it now and didn't have it before? */
+		if ((channel->mode.mode & cm->mode) &&
+		    !(oldem & cm->mode))
+		{
+			if (what != MODE_ADD)
+			{
+				strlcat_letter(m->modeline[curr], '+', BUFSIZE);
+				what = MODE_ADD;
+			}
+			strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
+		}
+	}
+
+	/* Which paramless modes got unset? Eg -r */
+	for (cm=channelmodes; cm; cm = cm->next)
+	{
+		if (!cm->letter || cm->unset_with_param)
+			continue;
+		/* don't have it now and did have it before */
+		if (!(channel->mode.mode & cm->mode) && (oldem & cm->mode))
+		{
+			if (what != MODE_DEL)
+			{
+				strlcat_letter(m->modeline[curr], '-', BUFSIZE);
+				what = MODE_DEL;
+			}
+			strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
+		}
+	}
+
+	/* 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 ((strlen(m->modeline[curr]) + strlen(m->paramline[curr]) + strlen(&pvar[cnt][2])) > 507)
+		{
+			if (curr == MAXMULTILINEMODES)
+			{
+				/* Should be impossible.. */
+				unreal_log(ULOG_ERROR, "mode", "MODE_MULTILINE_EXCEEDED", client,
+				           "A mode string caused an avalanche effect of more than $max_multiline_modes modes "
+				           "in channel $channel. Caused by client $client. Expect a desync.",
+				           log_data_integer("max_multiline_modes", MAXMULTILINEMODES),
+				           log_data_channel("channel", channel));
+				break;
+			}
+			curr++;
+			m->modeline[curr] = safe_alloc(BUFSIZE);
+			m->paramline[curr] = safe_alloc(BUFSIZE);
+			m->numlines = curr+1;
+			what = 0;
+		}
+		if ((*(pvar[cnt]) == '+') && what != MODE_ADD)
+		{
+			strlcat_letter(m->modeline[curr], '+', BUFSIZE);
+			what = MODE_ADD;
+		}
+		if ((*(pvar[cnt]) == '-') && what != MODE_DEL)
+		{
+			strlcat_letter(m->modeline[curr], '-', BUFSIZE);
+			what = MODE_DEL;
+		}
+		strlcat_letter(m->modeline[curr], *(pvar[cnt] + 1), BUFSIZE);
+		strlcat(m->paramline[curr], &pvar[cnt][2], BUFSIZE);
+		strlcat_letter(m->paramline[curr], ' ', BUFSIZE);
+	}
+
+	for (i = 0; i <= curr; i++)
+	{
+		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';
+	}
+
+	/* Now check for completely empty mode: */
+	if ((curr == 0) && empty_mode(m->modeline[0]))
+	{
+		/* And convert it to a NULL result */
+		safe_free_multilinemode(m);
+		return NULL;
+	}
+
+	return m;
+}
+
+const char *mode_ban_handler(Client *client, Channel *channel, const char *param, int what, int extbtype, Ban **banlist)
+{
+	const char *tmpstr;
+	BanContext *b;
+
+	tmpstr = clean_ban_mask(param, what, client, 0);
+	if (BadPtr(tmpstr))
+	{
+		/* Invalid ban. See if we can send an error about that (only for extbans) */
+		if (MyUser(client) && is_extended_ban(param))
+		{
+			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)
+		{
+			if ((extbtype == EXBTYPE_INVEX) && !(extban->options & EXTBOPT_INVEX))
+				return NULL; /* this extended ban type does not support INVEX */
+			if (extban->is_ok)
+			{
+				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 (ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
+					{
+						/* TODO: send operoverride notice */
+					} else {
+						b->banstr = nextbanstr;
+						b->is_ok_check = EXBCHK_ACCESS_ERR;
+						extban->is_ok(b);
+						safe_free(b);
+						return NULL;
+					}
+				}
+				b->banstr = nextbanstr;
+				b->is_ok_check = EXBCHK_PARAM;
+				if (!extban->is_ok(b))
+				{
+					safe_free(b);
+					return NULL;
+				}
+				safe_free(b);
+			}
+		}
+	}
+
+	if ( (what == MODE_ADD && add_listmode(banlist, client, channel, tmpstr)) ||
+	     (what == MODE_DEL && del_listmode(banlist, channel, tmpstr)))
+	{
+		return NULL;	/* already exists */
+	}
+
+	return tmpstr;
+}
+
+/** 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
+
+	ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
+	            "%c%c%s",
+	            (what == MODE_ADD) ? '+' : '-',
+	            modeletter,
+	            str);
+	(*pcount)++;
+}
+
+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;
+
+	/* Check if there is a parameter present */
+	if (!param || *pcount >= MAXMODEPARAMS)
+		return 0;
+
+	switch (modetype)
+	{
+		case MODE_BAN:
+			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:
+			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:
+			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 1;
+}
+
+/** Check access and if granted, set the extended chanmode to the requested value in memory.
+  * @returns amount of params eaten (0 or 1)
+  */
+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->letter;
+	int x;
+	const char *morphed;
+
+	if ((what == MODE_DEL) && handler->unset_with_param)
+		paracnt = 1; /* there's always an exception! */
+
+	/* Expected a param and it isn't there? */
+	if (paracnt && (!param || (*pcount >= MAXMODEPARAMS)))
+		return 0;
+
+	/* Prevent remote users from setting local channel modes */
+	if (handler->local && !MyUser(client))
+		return paracnt;
+
+	if (MyUser(client))
+	{
+		x = handler->is_ok(client, channel, mode, param, EXCHK_ACCESS, what);
+		if ((x == EX_ALWAYS_DENY) ||
+		    ((x == EX_DENY) && !op_can_override("channel:override:mode:del",client,channel,handler) && !samode_in_progress))
+		{
+			handler->is_ok(client, channel, mode, param, EXCHK_ACCESS_ERR, what);
+			return paracnt; /* Denied & error msg sent */
+		}
+		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 */
+		if (!IsULine(client) && IsUser(client) && op_can_override("channel:override:mode:del",client,channel,handler) &&
+		    (handler->is_ok(client, channel, mode, param, EXCHK_ACCESS, what) != EX_ALLOW))
+		{
+			opermode = 1; /* override in progress... */
+		}
+	}
+
+	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->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.
+			 * we do eat the parameter. -- Syzop
+			 */
+			return paracnt;
+		}
+	}
+
+	/* w00t... a parameter mode */
+	if (handler->paracount)
+	{
+		if (what == MODE_DEL)
+		{
+			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.
+				 */
+				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.
+				 */
+			}
+		} else {
+			/* add: is the parameter ok? */
+			if (handler->is_ok(client, channel, mode, param, EXCHK_PARAM, what) == FALSE)
+				return paracnt; /* rejected by is_ok */
+
+			morphed = handler->conv_param(param, client, channel);
+			if (!morphed)
+				return paracnt; /* rejected by conv_param */
+
+			/* is it already set at the same value? if so, ignore it. */
+			if (channel->mode.mode & handler->mode)
+			{
+				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... */
+			}
+			do_mode_char_write(pvar, pcount, what, handler->letter, handler->conv_param(param, client, channel));
+			param = morphed; /* set param to converted parameter. */
+		}
+	}
+
+	if (what == MODE_ADD)
+	{	/* + */
+		channel->mode.mode |= handler->mode;
+		if (handler->paracount)
+			cm_putparameter(channel, handler->letter, param);
+		RunHook(HOOKTYPE_MODECHAR_ADD, channel, (int)mode);
+	} else
+	{	/* - */
+		channel->mode.mode &= ~(handler->mode);
+		RunHook(HOOKTYPE_MODECHAR_DEL, channel, (int)mode);
+		if (handler->paracount)
+			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_client("target", target),
+			   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 */
+
+	/* HOOKTYPE_MODE_DEOP code */
+	if (what == MODE_DEL)
+	{
+		int ret = EX_ALLOW;
+		const char *badmode = NULL;
+		Hook *h;
+		const char *my_access;
+		Membership *my_membership;
+
+		/* Set "my_access" to access flags of the requestor */
+		if (IsUser(client) && (my_membership = find_membership_link(client->user->channel, channel)))
+			my_access = my_membership->member_modes; /* client */
+		else
+			my_access = ""; /* server */
+
+		for (h = Hooks[HOOKTYPE_MODE_DEOP]; h; h = h->next)
+		{
+			int n = (*(h->func.intfunc))(client, target, channel, what, modechar, my_access, member->member_modes, &badmode);
+			if (n == EX_DENY)
+			{
+				ret = n;
+			} else
+			if (n == EX_ALWAYS_DENY)
+			{
+				ret = n;
+				break;
+			}
+		}
+
+		if (ret == EX_ALWAYS_DENY)
+		{
+			if (MyUser(client) && badmode)
+				sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
+
+			if (MyUser(client))
+				return; /* stop processing this mode */
+		}
+
+		/* 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,handler))
+			{
+				if (badmode)
+					sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
+				return; /* stop processing this mode */
+			} else {
+				opermode = 1;
+			}
+		}
+	}
+
+	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
+ */
+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->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->server)
+			return 0;
+		client = client->direction;
+	}
+
+	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->server->features.chanmodes[1] && strchr(client->server->features.chanmodes[1], mode))
+		return 1; /* 1 parameter for set, 1 parameter for unset */
+
+	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->server->features.chanmodes[3] && strchr(client->server->features.chanmodes[3], mode))
+		return 0; /* no parameter for set, no parameter for unset */
+
+	if (mode == '&')
+		return 0; /* & indicates bounce, it is not an actual mode character */
+
+	if (mode == 'F')
+		return (what == MODE_ADD) ? 1 : 0; /* Future compatibility */
+
+	/* If we end up here it means we have no idea if it is a parameter-eating or paramless
+	 * channel mode. That's actually pretty bad. This shouldn't happen since CHANMODES=
+	 * is sent since 2003 and the (often also required) EAUTH PROTOCTL is in there since 2010.
+	 */
+	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;
+}
+
+/** Quick way to find out parameter count for a channel mode.
+ * Only for LOCAL mode requests. For remote modes use
+ * paracount_for_chanmode_from_server() instead.
+ */
+int paracount_for_chanmode(u_int what, char 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.server->features.chanmodes[1] && strchr(me.server->features.chanmodes[1], mode))
+		return 1; /* 1 parameter for set, 1 parameter for unset */
+
+	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.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;
+}
+
+MultiLineMode *_set_mode(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
+                        char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
+{
+	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;
+#ifdef DEVELOP
+	char *tmpo = NULL;
+#endif
+	CoreChannelModeTable *tab = &corechannelmodetable[0];
+	CoreChannelModeTable foundat;
+	int found = 0;
+	int sent_mlock_warning = 0;
+	int checkrestr = 0, warnrestr = 1;
+	Cmode_t oldem;
+	paracount = 1;
+	*pcount = 0;
+
+	oldem = channel->mode.mode;
+	if (RESTRICT_CHANNELMODES && !ValidatePermissionsForPath("immune:restrict-channelmodes",client,NULL,channel,NULL)) /* "cache" this */
+		checkrestr = 1;
+
+	for (curchr = parv[0]; *curchr; curchr++)
+	{
+		switch (*curchr)
+		{
+			case '+':
+				what = MODE_ADD;
+				break;
+			case '-':
+				what = MODE_DEL;
+				break;
+			default:
+				if (MyUser(client) && channel->mode_lock && strchr(channel->mode_lock, *curchr) != NULL)
+				{
+					if (!IsOper(client) || find_server(SERVICES_NAME, NULL) ||
+					    !ValidatePermissionsForPath("channel:override:mlock",client,NULL,channel,NULL))
+					{
+						if (!sent_mlock_warning)
+						{
+							sendnumeric(client, ERR_MLOCKRESTRICTED, channel->name, *curchr, channel->mode_lock);
+							sent_mlock_warning++;
+						}
+						continue;
+					}
+				}
+				found = 0;
+				tab = &corechannelmodetable[0];
+				while ((tab->mode != 0x0) && found == 0)
+				{
+					if (tab->flag == *curchr)
+					{
+						found = 1;
+						foundat = *tab;
+					}
+					tab++;
+				}
+				if (found == 1)
+				{
+					modetype = foundat.mode;
+				} else {
+					/* Maybe in extmodes */
+					for (cm=channelmodes; cm; cm = cm->next)
+					{
+						if (cm->letter == *curchr)
+						{
+							found = 2;
+							break;
+						}
+					}
+				}
+				if (found == 0) /* Mode char unknown */
+				{
+					if (!MyUser(client))
+						paracount += paracount_for_chanmode_from_server(client, what, *curchr);
+					else
+						sendnumeric(client, ERR_UNKNOWNMODE, *curchr);
+					break;
+				}
+
+				if (checkrestr && strchr(RESTRICT_CHANNELMODES, *curchr))
+				{
+					if (warnrestr)
+					{
+						sendnotice(client, "Setting/removing of channelmode(s) '%s' has been disabled.",
+							RESTRICT_CHANNELMODES);
+						warnrestr = 0;
+					}
+					paracount += paracount_for_chanmode(what, *curchr);
+					break;
+				}
+
+				if ((paracount < parc) && parv[paracount])
+				{
+					strlcpy(argumentbuf, parv[paracount], sizeof(argumentbuf));
+					argument = argumentbuf;
+				} else {
+					argument = NULL;
+				}
+
+				if (found == 1)
+					paracount += do_mode_char_list_mode(channel, modetype, *curchr, argument, what, client, pcount, pvar);
+				else if (found == 2)
+					paracount += do_extmode_char(channel, cm, argument, what, client, pcount, pvar);
+				break;
+		} /* switch(*curchr) */
+	} /* for loop through mode letters */
+
+	mlm = make_mode_str(client, channel, oldem, *pcount, pvar);
+	return mlm;
+}
+
+/*
+ * cmd_umode() added 15/10/91 By Darren Reed.
+ * parv[1] - username to change mode for
+ * parv[2] - modes to change
+ */
+CMD_FUNC(_cmd_umode)
+{
+	Umode *um;
+	const char *m;
+	Client *acptr;
+	int what;
+	long oldumodes = 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)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "MODE");
+		return;
+	}
+
+	if (!(acptr = find_user(parv[1], NULL)))
+	{
+		if (MyConnect(client))
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		}
+		return;
+	}
+	if (acptr != client)
+	{
+		sendnumeric(client, ERR_USERSDONTMATCH);
+		return;
+	}
+
+	if (parc < 3)
+	{
+		sendnumeric(client, RPL_UMODEIS, get_usermode_string(client));
+		if (client->user->snomask)
+			sendnumeric(client, RPL_SNOMASK, client->user->snomask);
+		return;
+	}
+
+	userhost_save_current(client); /* save host, in case we do any +x/-x or similar */
+
+	oldumodes = client->umodes;
+
+	if (RESTRICT_USERMODES && MyUser(client) && !ValidatePermissionsForPath("immune:restrict-usermodes",client,NULL,NULL,NULL))
+		chk_restrict = 1;
+
+	if (client->user->snomask)
+		strlcpy(oldsnomask, client->user->snomask, sizeof(oldsnomask));
+
+	/*
+	 * parse mode change string(s)
+	 */
+	for (m = parv[2]; *m; m++)
+	{
+		if (chk_restrict && strchr(RESTRICT_USERMODES, *m))
+		{
+			if (!umode_restrict_err)
+			{
+				sendnotice(client, "Setting/removing of usermode(s) '%s' has been disabled.",
+					RESTRICT_USERMODES);
+				umode_restrict_err = 1;
+			}
+			continue;
+		}
+		switch (*m)
+		{
+			case '+':
+				what = MODE_ADD;
+				break;
+			case '-':
+				what = MODE_DEL;
+				break;
+				/* we may not get these,
+				 * but they shouldnt be in default
+				 */
+			case ' ':
+			case '\t':
+				break;
+			case 's':
+				if (what == MODE_DEL)
+				{
+					if (parc >= 4 && client->user->snomask)
+					{
+						set_snomask(client, parv[3]);
+						if (client->user->snomask == NULL)
+							goto def;
+						break;
+					} else {
+						set_snomask(client, NULL);
+						goto def;
+					}
+				}
+				if ((what == MODE_ADD) && IsOper(client))
+				{
+					if (parc < 4)
+						set_snomask(client, OPER_SNOMASKS);
+					else
+						set_snomask(client, parv[3]);
+					goto def;
+				}
+				break;
+			case 'o':
+			case 'O':
+				if (IsQuarantined(client->direction))
+				{
+					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;
+				}
+				/* A local user trying to set himself +o/+O is denied here.
+				 * A while later (outside this loop) it is handled as well (and +C, +N, etc too)
+				 * but we need to take care here too because it might cause problems
+				 * that's just asking for bugs! -- Syzop.
+				 */
+				if (MyUser(client) && (what == MODE_ADD)) /* Someone setting himself +o? Deny it. */
+					break;
+				goto def;
+			case 't':
+			case 'x':
+				/* set::anti-flood::vhost-flood */
+				if (MyUser(client))
+				{
+					if ((what == MODE_DEL) && !ValidatePermissionsForPath("immune:vhost-flood",client,NULL,NULL,NULL) &&
+							flood_limit_exceeded(client, FLD_VHOST))
+					{
+						/* Throttle... */
+						if (!modex_err)
+						{
+							sendnotice(client, "*** Setting -%c too fast. Please try again later.", *m);
+							modex_err = 1;
+						}
+						break;
+					}
+				}
+
+				switch (UHOST_ALLOWED)
+				{
+				case UHALLOW_ALWAYS:
+					goto def;
+				case UHALLOW_NEVER:
+					if (MyUser(client))
+					{
+						if (!modex_err)
+						{
+							sendnotice(client, "*** Setting %c%c is disabled",
+								what == MODE_ADD ? '+' : '-', *m);
+							modex_err = 1;
+						}
+						break;
+					}
+					goto def;
+				case UHALLOW_NOCHANS:
+					if (MyUser(client) && client->user->joined)
+					{
+						if (!modex_err)
+						{
+							sendnotice(client, "*** Setting %c%c can not be done while you are on channels",
+								what == MODE_ADD ? '+' : '-', *m);
+							modex_err = 1;
+						}
+						break;
+					}
+					goto def;
+				case UHALLOW_REJOIN:
+					/* Handled later */
+					goto def;
+				}
+				break;
+			default:
+			def:
+				for (um = usermodes; um; um = um->next)
+				{
+					if (um->letter == *m)
+					{
+						if (um->allowed && !um->allowed(client,what))
+							break;
+						if (what == MODE_ADD)
+							client->umodes |= um->mode;
+						else
+							client->umodes &= ~um->mode;
+						break;
+					}
+				}
+				if (!um && MyConnect(client) && !rpterror)
+				{
+					sendnumeric(client, ERR_UMODEUNKNOWNFLAG);
+					rpterror = 1;
+				}
+				break;
+		} /* switch */
+	} /* for */
+
+	/* Don't let non-ircops set ircop-only modes or snomasks */
+	if (!ValidatePermissionsForPath("self:opermodes",client,NULL,NULL,NULL))
+	{
+		if ((oldumodes & UMODE_OPER) && IsOper(client))
+		{
+			/* User is an oper but does not have the self:opermodes capability.
+			 * This only happens for heavily restricted IRCOps.
+			 * Fixes bug https://bugs.unrealircd.org/view.php?id=5130
+			 */
+			int i;
+
+			/* MODES */
+			for (um = usermodes; um; um = um->next)
+			{
+				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.
+					 */
+					if ((client->umodes & um->mode) && !(oldumodes & um->mode))
+						client->umodes &= ~um->mode; /* remove */
+				}
+			}
+
+			/* SNOMASKS: user can delete existing but not add new ones */
+			if (client->user->snomask)
+			{
+				char rerun;
+				do {
+					char *p;
+
+					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: */
+			remove_oper_privileges(client, 0);
+		}
+	}
+
+	if (MyUser(client) && (client->umodes & UMODE_SECURE) && !IsSecure(client))
+		client->umodes &= ~UMODE_SECURE;
+
+	/* -x translates to -xt (if applicable) */
+	if ((oldumodes & UMODE_HIDE) && !IsHidden(client))
+		client->umodes &= ~UMODE_SETHOST;
+
+	/* Vhost unset = unset some other data as well */
+	if ((oldumodes & UMODE_SETHOST) && !IsSetHost(client))
+	{
+		swhois_delete(client, "vhost", "*", &me, NULL);
+	}
+
+	/* +x or -t+x */
+	if ((IsHidden(client) && !(oldumodes & UMODE_HIDE)) ||
+	    ((oldumodes & UMODE_SETHOST) && !IsSetHost(client) && IsHidden(client)))
+	{
+		if (!dontspread)
+			sendto_server(client, PROTO_VHP, 0, NULL, ":%s SETHOST :%s",
+				client->name, client->user->virthost);
+
+		/* Set the vhost */
+		safe_strdup(client->user->virthost, client->user->cloakedhost);
+
+		/* Notify */
+		userhost_changed(client);
+	}
+
+	/* -x */
+	if (!IsHidden(client) && (oldumodes & UMODE_HIDE))
+	{
+		/* (Re)create the cloaked virthost, because it will be used
+		 * for ban-checking... free+recreate here because it could have
+		 * been a vhost for example. -- Syzop
+		 */
+		safe_strdup(client->user->virthost, client->user->cloakedhost);
+
+		/* Notify */
+		userhost_changed(client);
+	}
+	/*
+	 * If I understand what this code is doing correctly...
+	 * If the user WAS an operator and has now set themselves -o/-O
+	 * then remove their access, d'oh!
+	 * In order to allow opers to do stuff like go +o, +h, -o and
+	 * remain +h, I moved this code below those checks. It should be
+	 * O.K. The above code just does normal access flag checks. This
+	 * only changes the operflag access level.  -Cabal95
+	 */
+	if ((oldumodes & UMODE_OPER) && !IsOper(client) && MyConnect(client))
+	{
+		list_del(&client->special_node);
+		if (MyUser(client))
+			RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL);
+		remove_oper_privileges(client, 0);
+	}
+
+	if (!(oldumodes & UMODE_OPER) && IsOper(client))
+		irccounts.operators++;
+
+	/* deal with opercounts and stuff */
+	if ((oldumodes & UMODE_OPER) && !IsOper(client))
+	{
+		irccounts.operators--;
+		VERIFY_OPERCOUNT(client, "umode1");
+	} else /* YES this 'else' must be here, otherwise we can decrease twice. fixes opercount bug. */
+	if (!(oldumodes & UMODE_HIDEOPER) && IsHideOper(client))
+	{
+		irccounts.operators--;
+		VERIFY_OPERCOUNT(client, "umode2");
+	}
+	/* end of dealing with opercounts */
+
+	if ((oldumodes & UMODE_HIDEOPER) && !IsHideOper(client))
+	{
+		irccounts.operators++;
+	}
+	if (!(oldumodes & UMODE_INVISIBLE) && IsInvisible(client))
+		irccounts.invisible++;
+	if ((oldumodes & UMODE_INVISIBLE) && !IsInvisible(client))
+		irccounts.invisible--;
+
+	if (MyConnect(client) && !IsOper(client))
+		remove_oper_privileges(client, 0);
+
+	/*
+	 * compare new flags with old flags and send string which
+	 * will cause servers to update correctly.
+	 */
+	if (oldumodes != client->umodes)
+		RunHook(HOOKTYPE_UMODE_CHANGE, client, oldumodes, client->umodes);
+	if (dontspread == 0)
+		send_umode_out(client, 1, oldumodes);
+
+	if (MyConnect(client) && client->user->snomask && strcmp(oldsnomask, client->user->snomask))
+		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
+}
+
+CMD_FUNC(cmd_mlock)
+{
+	Channel *channel = NULL;
+	time_t t;
+
+	if ((parc < 3) || BadPtr(parv[2]))
+		return;
+
+	t = (time_t) atol(parv[1]);
+
+	/* Now, try to find the channel in question */
+	channel = find_channel(parv[2]);
+	if (!channel)
+		return;
+
+	/* Senders' Channel t is higher, drop it. */
+	if (t > channel->creationtime)
+		return;
+
+	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, MessageTag *mtags, const char *modes, const 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, mtags, 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/ircd/src/modules/module.def b/ircd/src/modules/module.def
@@ -0,0 +1,5 @@
+EXPORTS
+	Mod_Header
+	Mod_Init
+	Mod_Load
+	Mod_Unload
diff --git a/ircd/src/modules/monitor.c b/ircd/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/ircd/src/modules/motd.c b/ircd/src/modules/motd.c
@@ -0,0 +1,123 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/motd.c
+ *   (C) 2005 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_motd);
+
+#define MSG_MOTD 	"MOTD"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"motd",
+	"5.0",
+	"command /motd", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_MOTD, cmd_motd, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * Heavily modified from the ircu cmd_motd by codemastr
+ * Also svsmotd support added
+ */
+CMD_FUNC(cmd_motd)
+{
+	ConfigItem_tld *tld;
+	MOTDFile *themotd;
+	MOTDLine *motdline;
+	int  svsnofile = 0;
+
+	if (IsServer(client))
+		return;
+
+	if (hunt_server(client, recv_mtags, "MOTD", 1, parc, parv) != HUNTED_ISME)
+	{
+		if (MyUser(client))
+			add_fake_lag(client, 15000);
+		return;
+	}
+
+	tld = find_tld(client);
+
+	if (tld && tld->motd.lines)
+		themotd = &tld->motd;
+	else
+		themotd = &motd;
+
+	if (themotd == NULL || themotd->lines == NULL)
+	{
+		sendnumeric(client, ERR_NOMOTD);
+		svsnofile = 1;
+		goto svsmotd;
+	}
+
+	sendnumeric(client, RPL_MOTDSTART, me.name);
+
+	/* tm_year should be zero only if the struct is zero-ed */
+	if (themotd && themotd->lines && themotd->last_modified.tm_year)
+	{
+		sendnumericfmt(client, RPL_MOTD, ":- %.04d-%.02d-%.02d %.02d:%02d",
+			themotd->last_modified.tm_year + 1900,
+			themotd->last_modified.tm_mon + 1,
+			themotd->last_modified.tm_mday,
+			themotd->last_modified.tm_hour,
+			themotd->last_modified.tm_min);
+	}
+
+	motdline = NULL;
+	if (themotd)
+		motdline = themotd->lines;
+	while (motdline)
+	{
+		sendnumeric(client, RPL_MOTD,
+			motdline->line);
+		motdline = motdline->next;
+	}
+	svsmotd:
+
+	motdline = svsmotd.lines;
+	while (motdline)
+	{
+		sendnumeric(client, RPL_MOTD,
+			motdline->line);
+		motdline = motdline->next;
+	}
+	if (svsnofile == 0)
+		sendnumeric(client, RPL_ENDOFMOTD);
+}
diff --git a/ircd/src/modules/names.c b/ircd/src/modules/names.c
@@ -0,0 +1,190 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/names.c
+ *   (C) 2006 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_names);
+
+long CAP_MULTI_PREFIX = 0L;
+long CAP_USERHOST_IN_NAMES = 0L;
+
+#define MSG_NAMES 	"NAMES"
+
+ModuleHeader MOD_HEADER
+  = {
+	"names",
+	"5.0",
+	"command /names", 
+	"UnrealIRCd Team",
+	"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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/************************************************************************
+ * cmd_names() - Added by Jto 27 Apr 1989
+ * 12 Feb 2000 - geesh, time for a rewrite -lucas
+ ************************************************************************/
+
+/*
+** cmd_names
+**	parv[1] = channel
+*/
+#define TRUNCATED_NAMES 64
+CMD_FUNC(cmd_names)
+{
+	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;
+	Client *acptr;
+	int member;
+	Member *cm;
+	int idx, flag = 1, spos;
+	const char *para = parv[1], *s;
+	char nuhBuffer[NICKLEN+USERLEN+HOSTLEN+3];
+	char buf[BUFSIZE];
+
+	if (parc < 2 || !MyConnect(client))
+	{
+		sendnumeric(client, RPL_ENDOFNAMES, "*");
+		return;
+	}
+
+	for (s = para; *s; s++)
+	{
+		if (*s == ',')
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, s+1, 1, "NAMES");
+			return;
+		}
+	}
+
+	channel = find_channel(para);
+
+	if (!channel || (!ShowChannel(client, channel) && !ValidatePermissionsForPath("channel:see:names:secret",client,NULL,channel,NULL)))
+	{
+		sendnumeric(client, RPL_ENDOFNAMES, para);
+		return;
+	}
+
+	/* 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))
+		buf[0] = '@';
+	else
+		buf[0] = '*';
+
+	idx = 1;
+	buf[idx++] = ' ';
+	for (s = channel->name; *s; s++)
+		buf[idx++] = *s;
+	buf[idx++] = ' ';
+	buf[idx++] = ':';
+
+	/* If we go through the following loop and never add anything,
+	   we need this to be empty, otherwise spurious things from the
+	   LAST /names call get stuck in there.. - lucas */
+	buf[idx] = '\0';
+
+	spos = idx;		/* starting point in buffer for names! */
+
+	for (cm = channel->members; cm; cm = cm->next)
+	{
+		acptr = cm->client;
+		if (IsInvisible(acptr) && !member && !ValidatePermissionsForPath("channel:see:names:invisible",client,acptr,channel,NULL))
+			continue;
+
+		if (!user_can_see_member(client, acptr, channel))
+			continue; /* invisible (eg: due to delayjoin) */
+
+		if (!multiprefix)
+		{
+			/* 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) */
+			strcpy(&buf[idx], modes_to_prefix(cm->member_modes));
+			idx += strlen(&buf[idx]);
+		}
+
+		if (!uhnames) {
+			s = acptr->name;
+		} else {
+			strlcpy(nuhBuffer,
+			        make_nick_user_host(acptr->name, acptr->user->username, GetHost(acptr)),
+				bufLen + 1);
+			s = nuhBuffer;
+		}
+		/* 's' is intialized above to point to either acptr->name (normal),
+		 * or to nuhBuffer (for UHNAMES).
+		 */
+		for (; *s; s++)
+			buf[idx++] = *s;
+		if (cm->next)
+			buf[idx++] = ' ';
+		buf[idx] = '\0';
+		flag = 1;
+		if (mlen + idx + bufLen + MEMBERMODESLEN >= BUFSIZE - 1)
+		{
+			sendnumeric(client, RPL_NAMREPLY, buf);
+			idx = spos;
+			flag = 0;
+		}
+	}
+
+	if (flag)
+		sendnumeric(client, RPL_NAMREPLY, buf);
+
+	sendnumeric(client, RPL_ENDOFNAMES, para);
+}
diff --git a/ircd/src/modules/netinfo.c b/ircd/src/modules/netinfo.c
@@ -0,0 +1,141 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_netinfo);
+
+#define MSG_NETINFO 	"NETINFO"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"netinfo",
+	"5.0",
+	"command /netinfo", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_NETINFO, cmd_netinfo, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** NETINFO: Share configuration settings with directly linked server.
+ * Originally written by Stskeeps
+ *
+ * Technical documentation:
+ * https://www.unrealircd.org/docs/Server_protocol:NETINFO_command
+ *
+ * parv[1] = max global count
+ * parv[2] = time of end sync
+ * parv[3] = unreal protocol using (numeric)
+ * parv[4] = cloak key check (> u2302)
+ * parv[5] = free(**)
+ * parv[6] = free(**)
+ * parv[7] = free(**)
+ * parv[8] = network name
+ */
+CMD_FUNC(cmd_netinfo)
+{
+	long 		lmax;
+	long 		endsync, protocol;
+	char		buf[512];
+
+	if (parc < 9)
+		return;
+
+	/* Only allow from directly connected servers */
+	if (!MyConnect(client))
+		return;
+
+	if (IsNetInfo(client))
+	{
+		unreal_log(ULOG_WARNING, "link", "NETINFO_ALREADY_RECEIVED", client,
+		           "Got NETINFO from server $client, but we already received it earlier!");
+		return;
+	}
+
+	/* is a long therefore not ATOI */
+	lmax = atol(parv[1]);
+	endsync = atol(parv[2]);
+	protocol = atol(parv[3]);
+
+	/* max global count != max_global_count --sts */
+	if (lmax > irccounts.global_max)
+	{
+		irccounts.global_max = lmax;
+		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));
+	}
+
+	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(NETWORK_NAME, parv[8]) == 0))
+	{
+		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))
+	{
+		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_KEY_CHECKSUM, sizeof(buf));
+	if (*parv[4] != '*' && strcmp(buf, parv[4]))
+	{
+		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/ircd/src/modules/nick.c b/ircd/src/modules/nick.c
@@ -0,0 +1,1321 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/nick.c
+ *   (C) 1999-2005 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
+  = {
+	"nick",
+	"5.0",
+	"command /nick",
+	"UnrealIRCd Team",
+	"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 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);
+void nick_collision(Client *cptr, const char *newnick, const char *newid, Client *new, Client *existing, int type);
+int AllowClient(Client *client);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAdd(modinfo->handle, EFUNC_REGISTER_USER, _register_user);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "NICK", cmd_nick, MAXPARA, CMD_USER|CMD_SERVER|CMD_UNREGISTERED);
+	CommandAdd(modinfo->handle, "UID", cmd_uid, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Hmm.. don't we already have such a function? */
+void set_user_modes_dont_spread(Client *client, const char *umode)
+{
+	const char *args[4];
+
+	args[0] = client->name;
+	args[1] = client->name;
+	args[2] = umode;
+	args[3] = NULL;
+
+	dontspread = 1;
+	do_cmd(client, NULL, "MODE", 3, args);
+	dontspread = 0;
+}
+
+/** 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;
+	MessageTag *mtags = NULL;
+
+	/* '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) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
+	{
+		ircstats.is_kill++;
+		unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client,
+		           "Server link $server tried to change '$client' to bad nick '$nick' -- rejected.",
+		           log_data_string("nick", parv[1]),
+		           log_data_client("server", client->uplink));
+		mtags = NULL;
+		new_message(client, NULL, &mtags);
+		sendto_one(client, mtags, ":%s KILL %s :Illegal nick name", me.id, client->id);
+		SetKilled(client);
+		exit_client(client, mtags, "Illegal nick name");
+		free_message_tags(mtags);
+		mtags = NULL;
+		return;
+	}
+
+	/* Check Q-lines / ban nick */
+	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)) && !ishold)
+	{
+		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)))
+	{
+		/* If existing nick is still in handshake, kill it */
+		if (IsUnknown(acptr) && MyConnect(acptr))
+		{
+			SetKilled(acptr);
+			exit_client(acptr, NULL, "Overridden");
+		} else
+		if (acptr == client)
+		{
+			/* 100% identical? Must be a bug, but ok */
+			if (!strcmp(acptr->name, nick))
+				return;
+			/* Allows change of case in their nick */
+			removemoder = 0; /* don't set the user -r */
+		} else
+		{
+			/*
+			   ** A NICK change has collided (e.g. message type ":old NICK new").
+			 */
+			differ = (mycmp(acptr->user->username, client->user->username) ||
+			          mycmp(acptr->user->realhost, client->user->realhost));
+
+			if (!(parc > 2) || lastnick == acptr->lastnick)
+			{
+				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], 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], client->id, client, acptr, NICKCOL_EXISTING_WON);
+				return; /* their user lost, ignore the NICK */
+			} else
+			{
+				return;		/* just in case */
+			}
+		}
+	}
+
+	mtags = NULL;
+
+	if (!IsULine(client))
+	{
+		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);
+	RunHook(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
+	client->lastnick = lastnick ? lastnick : TStime();
+	add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE);
+	sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
+	    client->id, nick, (long long)client->lastnick);
+	sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
+	if (removemoder)
+		client->umodes &= ~UMODE_REGNICK;
+
+	/* Finally set new nick name. */
+	del_from_client_hash_table(client->name, client);
+	strlcpy(client->name, nick, sizeof(client->name));
+	add_to_client_hash_table(nick, client);
+
+	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];
+	char oldnick[NICKLEN + 1];
+	char descbuf[BUFSIZE];
+	Membership *mp;
+	int newuser = 0;
+	unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
+	Hook *h;
+	int ret;
+
+	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)
+	{
+		snprintf(descbuf, sizeof descbuf, "A minimum length of %d chars is required", iConf.min_nick_length);
+		sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], descbuf);
+		return;
+	}
+
+	/* Enforce maximum nick length */
+	strlcpy(nick, parv[1], iConf.nick_length + 1);
+
+	/* Check if this is a valid nick name */
+	if (!do_nick_name(nick))
+	{
+		sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], "Illegal characters");
+		return;
+	}
+
+	/* Check for collisions / in use */
+	if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
+	{
+		sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, "Reserved for internal IRCd purposes");
+		return;
+	}
+
+	if (MyUser(client))
+	{
+		/* Local client changing nick: check spamfilter */
+		spamfilter_build_user_string(spamfilter_user, nick, client);
+		if (match_spamfilter(client, spamfilter_user, SPAMF_USER, "NICK", NULL, 0, NULL))
+			return;
+	}
+
+	/* Check Q-lines / ban nick */
+	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)))
+	{
+		if (ishold)
+		{
+			sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, tklban->ptr.nameban->reason);
+			return;
+		}
+		if (!ValidatePermissionsForPath("immune:server-ban:ban-nick",client,NULL,NULL,nick))
+		{
+			add_fake_lag(client, 4000); /* lag them up */
+			sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, 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 */
+	}
+
+	if (!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
+		add_fake_lag(client, 3000);
+
+	if ((acptr = find_client(nick, NULL)))
+	{
+		/* Shouldn't be possible since dot is disallowed: */
+		if (IsServer(acptr))
+		{
+			sendnumeric(client, ERR_NICKNAMEINUSE, nick);
+			return;
+		}
+		if (acptr == client)
+		{
+			/* New nick is exactly the same as the old nick? */
+			if (!strcmp(acptr->name, nick))
+				return;
+			/* Changing cAsE */
+			removemoder = 0;
+		} else
+		/* Collision with a nick of a session that is still in handshake */
+		if (IsUnknown(acptr) && MyConnect(acptr))
+		{
+			/* Kill the other connection that is still in progress */
+			SetKilled(acptr);
+			exit_client(acptr, NULL, "Overridden");
+		} else
+		{
+			sendnumeric(client, ERR_NICKNAMEINUSE, nick);
+			return;	/* NICK message ignored */
+		}
+	}
+
+	/* 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)
+		{
+			/*
+			 * Client setting NICK the first time.
+			 *
+			 * Generate a random string for them to pong with.
+			 */
+			client->local->nospoof = getrandom32();
+
+			sendto_one(client, NULL, "PING :%X", client->local->nospoof);
+		}
+
+		/* Copy password to the passwd field if it's given after NICK */
+		if ((parc > 2) && (strlen(parv[2]) <= PASSWDLEN))
+			safe_strdup(client->local->passwd, parv[2]);
+
+		/* This had to be copied here to avoid problems.. */
+		strlcpy(client->name, nick, sizeof(client->name));
+
+		/* Let's see if we can get online now... */
+		if (is_handshake_finished(client))
+		{
+			/* Send a CTCP VERSION */
+			if (!iConf.ping_cookie && USE_BAN_VERSION && MyConnect(client))
+				sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1", me.name, nick);
+
+			client->lastnick = TStime();
+			if (!register_user(client))
+			{
+				if (IsDead(client))
+					return;
+				/* ..otherwise.. fallthrough so we run the same code
+				 * as in case of !is_handshake_finished()
+				 */
+			} else {
+				/* New user! */
+				strlcpy(nick, client->name, sizeof(nick)); /* don't ask, but I need this. do not remove! -- Syzop */
+			}
+		}
+	} else
+	if (MyUser(client))
+	{
+		MessageTag *mtags = NULL;
+		int ret;
+
+		/* Existing client nick-changing */
+
+		/*
+		   ** If the client belongs to me, then check to see
+		   ** if client is currently on any channels where it
+		   ** is currently banned.  If so, do not allow the nick
+		   ** change to occur.
+		   ** Also set 'lastnick' to current time, if changed.
+		 */
+		for (mp = client->user->channel; mp; mp = mp->next)
+		{
+			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->name);
+				return;
+			}
+			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->name);
+				return;
+			}
+
+			for (h = Hooks[HOOKTYPE_CHAN_PERMIT_NICK_CHANGE]; h; h = h->next)
+			{
+				ret = (*(h->func.intfunc))(client,mp->channel);
+				if (ret != HOOK_CONTINUE)
+					break;
+			}
+
+			if (ret == HOOK_DENY)
+			{
+				sendnumeric(client, ERR_NONICKCHANGE, mp->channel->name);
+				return;
+			}
+		}
+
+		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);
+		RunHook(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
+		add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE);
+		client->lastnick = TStime(); /* needs to be done AFTER add_history() */
+		sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
+		    client->id, nick, (long long)client->lastnick);
+		sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
+		sendto_one(client, mtags, ":%s NICK :%s", client->name, nick);
+		free_message_tags(mtags);
+		if (removemoder)
+			client->umodes &= ~UMODE_REGNICK;
+	} else
+	{
+		/* Someone changing nicks in the pre-registered phase */
+	}
+
+	del_from_client_hash_table(client->name, client);
+
+	strlcpy(client->name, nick, sizeof(client->name));
+	add_to_client_hash_table(nick, client);
+
+	/* update fdlist --nenolod */
+	snprintf(descbuf, sizeof(descbuf), "Client: %s", nick);
+	fd_desc(client->local->fd, descbuf);
+
+	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);
+}
+
+/*
+** cmd_uid
+**	parv[1] = nickname
+**      parv[2] = hopcount
+**      parv[3] = timestamp
+**      parv[4] = username
+**      parv[5] = hostname
+**      parv[6] = UID
+**	parv[7] = account name (SVID)
+**      parv[8] = umodes
+**	parv[9] = virthost, * if none
+**	parv[10] = cloaked host, * if none
+**	parv[11] = ip
+**	parv[12] = info
+**
+** Technical documentation is available at:
+** https://www.unrealircd.org/docs/Server_protocol:UID_command
+*/
+CMD_FUNC(cmd_uid)
+{
+	TKL *tklban;
+	int ishold;
+	Client *acptr, *serv = NULL;
+	Client *acptrs;
+	char nick[NICKLEN + 1];
+	char buf[BUFSIZE];
+	long lastnick = 0;
+	int differ = 1;
+	const char *hostname, *username, *sstamp, *umodes, *virthost, *ip_raw, *realname;
+	const char *ip = NULL;
+
+	if (parc < 13)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "UID");
+		return;
+	}
+
+	/* It's not just the directly attached client which must be a
+	 * server. The source itself needs to be a server.
+	 */
+	if (!IsServer(client))
+	{
+		sendnumeric(client, ERR_NOTFORUSERS, "UID");
+		return;
+	}
+
+	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) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
+	{
+		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++;
+		/* 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;
+	}
+
+	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 :Bad UID", me.id, parv[6]);
+		return;
+	}
+
+	if (!valid_host(hostname, 0))
+	{
+		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 (strcmp(virthost, "*") && !valid_host(virthost, 0))
+	{
+		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)))
+		{
+			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;
+		}
+	}
+
+	/* Kill quarantined opers early... */
+	if (IsQuarantined(client->direction) && strchr(parv[8], 'o'))
+	{
+		ircstats.is_kill++;
+		/* 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)
+	{
+		/* If there's a collision with a user that is still in handshake, on our side,
+		 * then we can just kill our client and continue.
+		 */
+		if (MyConnect(acptr) && IsUnknown(acptr))
+		{
+			SetKilled(acptr);
+			exit_client(acptr, NULL, "Overridden");
+			goto nickkill2done;
+		}
+
+		lastnick = atol(parv[3]);
+		differ = (mycmp(acptr->user->username, parv[4]) || mycmp(acptr->user->realhost, parv[5]));
+
+		if (acptr->lastnick == lastnick)
+		{
+			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EQUAL);
+			return;	/* We killed both users, now stop the process. */
+		}
+
+		if ((differ && (acptr->lastnick > lastnick)) ||
+		    (!differ && (acptr->lastnick < lastnick)) || acptr->direction == client->direction)	/* we missed a QUIT somewhere ? */
+		{
+			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_NEW_WON);
+			/* We got rid of the "wrong" user. Introduce the correct one. */
+			/* ^^ hmm.. why was this absent in nenolod's code, resulting in a 'return 0'? seems wrong. */
+			goto nickkill2done;
+		}
+
+		if ((differ && (acptr->lastnick < lastnick)) || (!differ && (acptr->lastnick > lastnick)))
+		{
+			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EXISTING_WON);
+			return;	/* Ignore the NICK */
+		}
+		return; /* just in case */
+	}
+
+nickkill2done:
+	/* Proceed with introducing the new client, change source (replace client) */
+
+	serv = client;
+	client = make_client(serv->direction, serv);
+	strlcpy(client->id, parv[6], IDLEN);
+	add_client_to_list(client);
+	add_to_id_hash_table(client->id, client);
+	client->lastnick = atol(parv[3]);
+	strlcpy(client->name, nick, NICKLEN+1);
+	add_to_client_hash_table(nick, client);
+
+	make_user(client);
+
+	/* Note that cloaked host aka parv[10] is unused */
+
+	client->user->server = find_or_add(client->uplink->name);
+	strlcpy(client->user->realhost, hostname, sizeof(client->user->realhost));
+	if (ip)
+		safe_strdup(client->ip, ip);
+
+	if (*sstamp != '*')
+		strlcpy(client->user->account, sstamp, sizeof(client->user->account));
+
+	strlcpy(client->info, realname, sizeof(client->info));
+	strlcpy(client->user->username, username, USERLEN + 1);
+	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);
+
+	/* 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
+		 * since that can only happen for local users.
+		 */
+	}
+
+	RunHook(HOOKTYPE_REMOTE_CONNECT, client);
+
+	if (!IsULine(serv) && IsSynched(serv))
+	{
+		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 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))
+	{
+		CALL_CMD_FUNC(cmd_nick_local);
+	} 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
+	{
+		CALL_CMD_FUNC(cmd_nick_remote);
+	}
+}
+
+/** Welcome the user on IRC.
+ * Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything...
+ */
+void welcome_user(Client *client, TKL *viruschan_tkl)
+{
+	int i;
+	ConfigItem_tld *tld;
+	char buf[BUFSIZE];
+
+	/* 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();
+
+	RunHook(HOOKTYPE_WELCOME, client, 0);
+	sendnumeric(client, RPL_WELCOME, NETWORK_NAME, client->name, client->user->username, client->user->realhost);
+
+	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))
+	{
+		sendnumeric(client, RPL_HOSTHIDDEN, client->user->virthost);
+		RunHook(HOOKTYPE_WELCOME, client, 396);
+	}
+
+	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));
+		}
+	}
+
+	{
+		const char *parv[2];
+		parv[0] = NULL;
+		parv[1] = NULL;
+		do_cmd(client, NULL, "LUSERS", 1, parv);
+		if (IsDead(client))
+			return;
+	}
+
+	RunHook(HOOKTYPE_WELCOME, client, 266);
+
+	short_motd(client);
+
+	RunHook(HOOKTYPE_WELCOME, client, 376);
+
+#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
+
+	if (client->umodes & UMODE_INVISIBLE)
+		irccounts.invisible++;
+
+	build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
+
+	sendto_serv_butone_nickcmd(client->direction, NULL, client, (*buf == '\0' ? "+" : buf));
+
+	broadcast_moddata_client(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);
+
+	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));
+
+	RunHook(HOOKTYPE_LOCAL_CONNECT, client);
+
+	/* 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();
+
+	RunHook(HOOKTYPE_WELCOME, client, 999);
+
+	/* 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;
+	}
+
+	/* Force the user to join the given chans -- codemastr */
+	tld = find_tld(client);
+
+	if (tld && !BadPtr(tld->channel))
+	{
+		char *chans = strdup(tld->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;
+	}
+}
+
+/** Make a valid client->user->username, or try to anyway.
+ * @param client	The client to check
+ * @param noident	Whether we should ignore the first ~ or not
+ * @returns 1 if the username is acceptable, 0 if not.
+ * @note This function will modify client->user->username to make it valid.
+ *       Only if there are zero valid characters it will return 0.
+ * @note There is also valid_username() in src/misc.c
+ */
+int make_valid_username(Client *client, int noident)
+{
+	static char stripuser[USERLEN + 1];
+	char *i;
+	char *o = stripuser;
+	char filtered = 0; /* any changes? */
+
+	*stripuser = '\0';
+
+	for (i = client->user->username + noident; *i; i++)
+	{
+		if (isallowed(*i))
+			*o++ = *i;
+		else
+			filtered = 1;
+	}
+	*o = '\0';
+
+	if (filtered == 0)
+		return 1; /* No change needed, all good */
+
+	if (*stripuser == '\0')
+		return 0; /* Zero valid characters, reject it */
+
+	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 */
+}
+
+/** 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 (!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)
+		{
+			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);
+	}
+
+	/* 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));
+
+	/* Check allow { } blocks... */
+	if (!AllowClient(client))
+	{
+		ircstats.is_ref++;
+		/* For safety, we have an extra kill here */
+		if (!IsDead(client))
+			exit_client(client, NULL, "Rejected");
+		return 0;
+	}
+
+	if (IsUseIdent(client))
+	{
+		if (IsIdentSuccess(client))
+		{
+			/* ident succeeded: overwite client->user->username with the ident reply */
+			strlcpy(client->user->username, client->ident, sizeof(client->user->username));
+		} else
+		if (IDENT_CHECK)
+		{
+			/* 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;
+		}
+	}
+
+	/* Now validate the username. This may alter client->user->username
+	 * or reject it completely.
+	 */
+	if (!make_valid_username(client, noident))
+	{
+		exit_client(client, NULL, "Hostile username. Please use only 0-9 a-z A-Z _ - and . in your username.");
+		return 0;
+	}
+
+	/* 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);
+
+	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;
+		}
+	}
+
+	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)
+			{
+				/* 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 (ret == HOOK_ALLOW)
+			break;
+	}
+
+	SetUser(client);
+
+	make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
+
+	/* client->user->virthost should never be empty */
+	if (!IsSetHost(client) || !client->user->virthost)
+		safe_strdup(client->user->virthost, client->user->cloakedhost);
+
+	snprintf(descbuf, sizeof descbuf, "Client: %s", client->name);
+	fd_desc(client->local->fd, descbuf);
+
+	/* Move user from unknown list to client list */
+	list_move(&client->lclient_node, &lclient_list);
+
+	/* Update counts */
+	irccounts.unknown--;
+	irccounts.clients++;
+	irccounts.me_clients++;
+	if (client->uplink && client->uplink->server)
+		client->uplink->server->users++;
+
+	if (IsSecure(client))
+	{
+		client->umodes |= UMODE_SECURE;
+		RunHook(HOOKTYPE_SECURE_CONNECT, client);
+	}
+
+	safe_free(client->local->passwd);
+
+	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)));
+
+	/* Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything... */
+	welcome_user(client, savetkl);
+
+	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, const char *newnick, const char *newid, Client *new, Client *existing, int type)
+{
+	char comment[512];
+	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;
+	if (type == NICKCOL_EXISTING_WON)
+		snprintf(comment, sizeof(comment), "Nick collision: %s <- %s", new_server, existing_server);
+	else if (type == NICKCOL_NEW_WON)
+		snprintf(comment, sizeof(comment), "Nick collision: %s <- %s", existing_server, new_server);
+	else
+		snprintf(comment, sizeof(comment), "Nick collision: %s <-> %s", existing_server, new_server);
+
+	/* We only care about the direction from this point, not about the originally sending server */
+	cptr = cptr->direction;
+
+	if ((type == NICKCOL_EQUAL) || (type == NICKCOL_EXISTING_WON))
+	{
+		/* Kill 'new':
+		 * - 'new' is known by the cptr-side as 'newnick' already
+		 * - if not nick-changing then the other servers don't know this user
+		 * - if nick-changing, then the the other servers know the user as new->name
+		 */
+
+		/* cptr case first... this side knows the user by newnick/newid */
+		/* SID server can kill 'new' by ID */
+		sendto_one(cptr, NULL, ":%s KILL %s :%s", me.id, newid, comment);
+
+		/* non-cptr case... only necessary if nick-changing. */
+		if (new)
+		{
+			MessageTag *mtags = NULL;
+
+			new_message(new, NULL, &mtags);
+
+			/* non-cptr side knows this user by their old nick name */
+			sendto_server(cptr, 0, 0, mtags, ":%s KILL %s :%s", me.id, new->id, comment);
+
+			/* Exit the client */
+			ircstats.is_kill++;
+			SetKilled(new);
+			exit_client(new, mtags, comment);
+
+			free_message_tags(mtags);
+		}
+	}
+
+	if ((type == NICKCOL_EQUAL) || (type == NICKCOL_NEW_WON))
+	{
+		MessageTag *mtags = NULL;
+
+		new_message(existing, NULL, &mtags);
+
+		/* Now let's kill 'existing' */
+		sendto_server(NULL, 0, 0, mtags, ":%s KILL %s :%s", me.id, existing->id, comment);
+
+		/* Exit the client */
+		ircstats.is_kill++;
+		SetKilled(existing);
+		exit_client(existing, mtags, comment);
+
+		free_message_tags(mtags);
+	}
+}
+
+/** Returns 1 if allow::maxperip is exceeded by 'client' */
+int exceeds_maxperip(Client *client, ConfigItem_allow *aconf)
+{
+	Client *acptr;
+	int local_cnt = 1;
+	int global_cnt = 1;
+
+	if (find_tkl_exception(TKL_MAXPERIP, client))
+		return 0; /* exempt */
+
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (IsUser(acptr) && !strcmp(GetIP(acptr), GetIP(client)))
+		{
+			if (MyUser(acptr))
+			{
+				local_cnt++;
+				if (local_cnt > aconf->maxperip)
+					return 1;
+			}
+			global_cnt++;
+			if (global_cnt > aconf->global_maxperip)
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/** Allow or reject the client based on allow { } blocks and all other restrictions.
+ * @param client     Client to check (local)
+ * @param username   Username, for some reason...
+ * @returns 1 if OK, 0 if client is rejected (likely killed too)
+ */
+int AllowClient(Client *client)
+{
+	static char sockhost[HOSTLEN + 1];
+	int i;
+	ConfigItem_allow *aconf;
+	char *hname;
+	static char uhost[HOSTLEN + USERLEN + 3];
+	static char fullname[HOSTLEN + 1];
+
+	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
+	{
+		exit_client(client, NULL, iConf.plaintext_policy_user_message->line);
+		return 0;
+	}
+
+	if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
+	{
+		const char *msg = outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client);
+		exit_client(client, NULL, msg);
+		return 0;
+	}
+
+	for (aconf = conf_allow; aconf; aconf = aconf->next)
+	{
+		if (aconf->flags.tls && !IsSecure(client))
+			continue;
+
+		if (!user_allowed_by_security_group(client, aconf->match))
+			continue;
+
+		/* Check authentication */
+		if (aconf->auth && !Auth_Check(client, aconf->auth, client->local->passwd))
+		{
+			/* Incorrect password/authentication - but was is it required? */
+			if (aconf->flags.reject_on_auth_failure)
+			{
+				exit_client(client, NULL, iConf.reject_message_unauthorized);
+				return 0;
+			} else {
+				continue; /* Continue (this is the default behavior) */
+			}
+		}
+
+		if (!aconf->flags.noident)
+			SetUseIdent(client);
+
+		if (aconf->flags.useip)
+			set_sockhost(client, GetIP(client));
+
+		if (exceeds_maxperip(client, aconf))
+		{
+			/* Already got too many with that ip# */
+			exit_client(client, NULL, iConf.reject_message_too_many_connections);
+			return 0;
+		}
+
+		if (!((aconf->class->clients + 1) > aconf->class->maxclients))
+		{
+			client->local->class = aconf->class;
+			client->local->class->clients++;
+		}
+		else
+		{
+			/* Class is full */
+			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;
+		}
+		return 1;
+	}
+	/* User did not match any allow { } blocks: */
+	exit_client(client, NULL, iConf.reject_message_unauthorized);
+	return 0;
+}
diff --git a/ircd/src/modules/nocodes.c b/ircd/src/modules/nocodes.c
@@ -0,0 +1,102 @@
+/*
+ * "No Codes", a very simple (but often requested) module written by Syzop.
+ * This module will strip messages with bold/underline/reverse if chanmode
+ * +S is set, and block them if +c is set.
+ *
+ *   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
+= {
+	"nocodes",	/* Name of module */
+	"1.3", /* Version */
+	"Strip/block color/bold/underline/italic/reverse - by Syzop", /* Short description of module */
+	"UnrealIRCd Team", /* Author */
+	"unrealircd-6",
+};
+
+int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, nocodes_can_send_to_channel);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+static int has_controlcodes(const char *p)
+{
+	for (; *p; p++)
+		if ((*p == '\002') || (*p == '\037') || (*p == '\026') || (*p == '\035')) /* bold, underline, reverse, italic */
+			return 1;
+	return 0;
+}
+
+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;
+	int i;
+
+	if (has_channel_mode(channel, 'S'))
+	{
+		if (!has_controlcodes(*msg))
+			return HOOK_CONTINUE;
+
+		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_COLOR);
+			if (i == HOOK_ALLOW)
+				return HOOK_CONTINUE; /* bypass +S restriction */
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		strlcpy(retbuf, StripControlCodes(*msg), sizeof(retbuf));
+		*msg = retbuf;
+		return HOOK_CONTINUE;
+	} else
+	if (has_channel_mode(channel, 'c'))
+	{
+		if (!has_controlcodes(*msg))
+			return HOOK_CONTINUE;
+
+		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_COLOR);
+			if (i == HOOK_ALLOW)
+				return HOOK_CONTINUE; /* bypass +c restriction */
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		*errmsg = "Control codes (color/bold/underline/italic/reverse) are not permitted in this channel";
+		return HOOK_DENY;
+	}
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/oper.c b/ircd/src/modules/oper.c
@@ -0,0 +1,381 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/oper.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_OPER        "OPER"  /* OPER */
+
+ModuleHeader MOD_HEADER
+  = {
+	"oper",	/* Name of module */
+	"5.0", /* Version */
+	"command /oper", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+CMD_FUNC(cmd_oper);
+int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
+int oper_connect(Client *client);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAdd(modinfo->handle, EFUNC_MAKE_OPER, _make_oper);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, MSG_OPER, cmd_oper, MAXPARA, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, oper_connect);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void set_oper_host(Client *client, const char *host)
+{
+	char uhost[HOSTLEN + USERLEN + 1];
+	char *p;
+
+	if (!valid_vhost(host))
+		return;
+
+	strlcpy(uhost, host, sizeof(uhost));
+
+	if ((p = strchr(uhost, '@')))
+	{
+		*p++ = '\0';
+		strlcpy(client->user->username, uhost, sizeof(client->user->username));
+		sendto_server(NULL, 0, 0, NULL, ":%s SETIDENT %s",
+		              client->id, client->user->username);
+		host = p;
+	}
+	safe_strdup(client->user->virthost, host);
+	if (MyConnect(client))
+		sendto_server(NULL, 0, 0, NULL, ":%s SETHOST :%s", client->id, client->user->virthost);
+	client->umodes |= UMODE_SETHOST|UMODE_HIDE;
+}
+
+int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost)
+{
+	long old_umodes = client->umodes & ALL_UMODES;
+
+	userhost_save_current(client);
+
+	/* Put in the right class (if any) */
+	if (clientclass)
+	{
+		if (client->local->class)
+			client->local->class->clients--;
+		client->local->class = clientclass;
+		client->local->class->clients++;
+	}
+
+	/* set oper user modes */
+	client->umodes |= UMODE_OPER;
+	if (modes)
+		client->umodes |= modes; /* oper::modes */
+	else
+		client->umodes |= OPER_MODES; /* set::modes-on-oper */
+
+	/* oper::vhost */
+	if (vhost)
+	{
+		set_oper_host(client, vhost);
+	} else
+	if (IsHidden(client) && !client->user->virthost)
+	{
+		/* +x has just been set by modes-on-oper and no vhost. cloak the oper! */
+		safe_strdup(client->user->virthost, client->user->cloakedhost);
+	}
+
+	userhost_changed(client);
+
+	unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client,
+		   "$client.details is now an IRC Operator [oper-block: $oper_block] [operclass: $operclass]",
+		   log_data_string("oper_block", operblock_name),
+		   log_data_string("operclass", operclass));
+
+	/* set oper snomasks */
+	if (snomask)
+		set_snomask(client, snomask); /* oper::snomask */
+	else
+		set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */
+
+	send_umode_out(client, 1, old_umodes);
+	if (client->user->snomask)
+		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
+
+	list_add(&client->special_node, &oper_list);
+
+	RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock_name, operclass);
+
+	sendnumeric(client, RPL_YOUREOPER);
+
+	/* Update statistics */
+	if (IsInvisible(client) && !(old_umodes & UMODE_INVISIBLE))
+		irccounts.invisible++;
+	if (IsOper(client) && !IsHideOper(client))
+		irccounts.operators++;
+
+	if (SHOWOPERMOTD == 1)
+	{
+		const char *args[1] = { NULL };
+		do_cmd(client, NULL, "OPERMOTD", 1, args);
+	}
+
+	if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0"))
+	{
+		char *chans = strdup(OPER_AUTO_JOIN_CHANS);
+		const char *args[3] = {
+			client->name,
+			chans,
+			NULL
+		};
+		do_cmd(client, NULL, "JOIN", 3, args);
+		safe_free(chans);
+		/* Theoretically the oper may be killed on join. Would be fun, though */
+		if (IsDead(client))
+			return 0;
+	}
+
+	return 1;
+}
+
+/*
+** cmd_oper
+**	parv[1] = oper name
+**	parv[2] = oper password
+*/
+CMD_FUNC(cmd_oper)
+{
+	ConfigItem_oper *operblock;
+	const char *operblock_name, *password;
+
+	if (!MyUser(client))
+		return;
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "OPER");
+		return;
+	}
+
+	if (SVSNOOP)
+	{
+		sendnotice(client,
+		    "*** This server is in NOOP mode, you cannot /oper");
+		return;
+	}
+
+	if (IsOper(client))
+	{
+		sendnotice(client, "You are already an IRC Operator. If you want to re-oper then de-oper first via /MODE yournick -o");
+		return;
+	}
+
+	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);
+		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;
+	}
+
+	/* set::outdated-tls-policy::oper 'deny' */
+	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));
+		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(operblock_name)))
+	{
+		sendnumeric(client, ERR_NOOPERHOST);
+		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 (!user_allowed_by_security_group(client, operblock->match))
+	{
+		sendnumeric(client, ERR_NOOPERHOST);
+		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;
+	}
+
+	if (operblock->auth && !Auth_Check(client, operblock->auth, password))
+	{
+		sendnumeric(client, ERR_PASSWDMISMATCH);
+		if (FAILOPER_WARN)
+			sendnotice(client,
+			    "*** Your attempt has been logged.");
+		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;
+	}
+
+	/* Authentication of the oper succeeded (like, password, ssl cert),
+	 * but we still have some other restrictions to check below as well,
+	 * like 'require-modes' and 'maxlogins'...
+	 */
+
+	/* Check oper::require_modes */
+	if (operblock->require_modes & ~client->umodes)
+	{
+		sendnumericfmt(client, ERR_NOOPERHOST, ":You are missing user modes required to OPER");
+		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");
+		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;
+	}
+
+	if (operblock->maxlogins && (count_oper_sessions(operblock->name) >= operblock->maxlogins))
+	{
+		sendnumeric(client, ERR_NOOPERHOST);
+		sendnotice(client, "Your maximum number of concurrent oper logins has been reached (%d)",
+			operblock->maxlogins);
+		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;
+	}
+
+	/* /OPER really succeeded now. Start processing it. */
+
+	/* Store which oper block was used to become IRCOp (for maxlogins and whois) */
+	safe_strdup(client->user->operlogin, operblock->name);
+
+	/* oper::swhois */
+	if (operblock->swhois)
+	{
+		SWhois *s;
+		for (s = operblock->swhois; s; s = s->next)
+			swhois_add(client, "oper", -100, s->line, &me, NULL);
+	}
+
+	make_oper(client, operblock->name, operblock->operclass, operblock->class, operblock->modes, operblock->snomask, operblock->vhost);
+
+	/* set::plaintext-policy::oper 'warn' */
+	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_WARN))
+	{
+		sendnotice_multiline(client, iConf.plaintext_policy_oper_message);
+		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));
+		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"));
+	}
+}
+
+int oper_connect(Client *client)
+{
+	ConfigItem_oper *e;
+
+	if (IsOper(client))
+		return 0;
+
+	for (e = conf_oper; e; e = e->next)
+	{
+		if (e->auto_login && user_allowed_by_security_group(client, e->match))
+		{
+			/* Ideally we would check all the criteria that cmd_oper does.
+			 * I'm taking a shortcut for now that is not ideal...
+			 */
+			const char *parx[3];
+			parx[0] = NULL;
+			parx[1] = e->name;
+			parx[2] = NULL;
+			do_cmd(client, NULL, "OPER", 3, parx);
+			return 0;
+		}
+	}
+
+	return 0;
+}
diff --git a/ircd/src/modules/operinfo.c b/ircd/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 or later
+ */
+
+#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, const char *oper_block, const char *operclass);
+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, const char *oper_block, const char *operclass)
+{
+	if (up)
+	{
+		moddata_client_set(client, "operlogin", oper_block);
+		moddata_client_set(client, "operclass", 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/ircd/src/modules/opermotd.c b/ircd/src/modules/opermotd.c
@@ -0,0 +1,90 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/opermotd.c
+ *   (C) 2005 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_opermotd);
+
+#define MSG_OPERMOTD 	"OPERMOTD"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"opermotd",
+	"5.0",
+	"command /opermotd", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_OPERMOTD, cmd_opermotd, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * Modified from comstud by codemastr
+ */
+CMD_FUNC(cmd_opermotd)
+{
+	MOTDLine *motdline;
+	ConfigItem_tld *tld;
+
+	if (!ValidatePermissionsForPath("server:opermotd",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	tld = find_tld(client);
+	if (tld && tld->opermotd.lines)
+		motdline = tld->opermotd.lines;
+	else
+		motdline = opermotd.lines;
+
+	if (!motdline)
+	{
+		sendnumeric(client, ERR_NOOPERMOTD);
+		return;
+	}
+	sendnumeric(client, RPL_MOTDSTART, me.name);
+	sendnumeric(client, RPL_MOTD, "IRC Operator Message of the Day");
+
+	while (motdline)
+	{
+		sendnumeric(client, RPL_MOTD,
+			   motdline->line);
+		motdline = motdline->next;
+	}
+	sendnumericfmt(client, RPL_ENDOFMOTD, ":End of /OPERMOTD command.");
+}
diff --git a/ircd/src/modules/part.c b/ircd/src/modules/part.c
@@ -0,0 +1,216 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/part.c
+ *   (C) 2005 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_part);
+
+#define MSG_PART 	"PART"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"part",
+	"5.0",
+	"command /part", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_PART, cmd_part, 2, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_part
+**	parv[1] = channel
+**	parv[2] = comment (added by Lefler)
+*/
+CMD_FUNC(cmd_part)
+{
+	char request[BUFSIZE];
+	Channel *channel;
+	Membership *lp;
+	char *p = NULL, *name;
+	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");
+	
+	if (parc < 2 || parv[1][0] == '\0')
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "PART");
+		return;
+	}
+
+	if (MyUser(client))
+	{
+		if (IsShunned(client))
+			commentx = NULL;
+		if (STATIC_PART)
+		{
+			if (!strcasecmp(STATIC_PART, "yes") || !strcmp(STATIC_PART, "1"))
+				commentx = NULL;
+			else if (!strcasecmp(STATIC_PART, "no") || !strcmp(STATIC_PART, "0"))
+				; /* keep original reason */
+			else
+				commentx = STATIC_PART;
+		}
+		if (commentx)
+		{
+			if (match_spamfilter(client, commentx, SPAMF_PART, "PART", parv[1], 0, NULL))
+				commentx = NULL;
+			if (IsDead(client))
+				return;
+		}
+	}
+
+	strlcpy(request, parv[1], sizeof(request));
+	for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
+	{
+		MessageTag *mtags = NULL;
+
+		if (MyUser(client) && (++ntargets > maxtargets))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "PART");
+			break;
+		}
+
+		channel = find_channel(name);
+		if (!channel)
+		{
+			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
+			continue;
+		}
+
+		/* 'commentx' is the general part msg, but it can be changed
+		 * per-channel (eg some chans block badwords, strip colors, etc)
+		 * so we copy it to 'comment' and use that in this for loop :)
+		 */
+		comment = commentx;
+
+		if (!(lp = find_membership_link(client->user->channel, channel)))
+		{
+			/* Normal to get get when our client did a kick
+			   ** for a remote client (who sends back a PART),
+			   ** so check for remote client or not --Run
+			 */
+			if (MyUser(client))
+				sendnumeric(client, ERR_NOTONCHANNEL, name);
+			continue;
+		}
+
+		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;
+		}
+
+		if (MyConnect(client))
+		{
+			Hook *tmphook;
+			for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_PART]; tmphook; tmphook = tmphook->next) {
+				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->name);
+
+		/* Send to other servers... */
+		sendto_server(client, 0, 0, mtags, ":%s PART %s :%s",
+			client->id, channel->name, comment ? comment : "");
+
+		if (invisible_user_in_channel(client, channel))
+		{
+			/* Show PART only to chanops and self */
+			if (!comment)
+			{
+				sendto_channel(channel, client, client,
+					       "ho", 0,
+					       SEND_LOCAL, mtags,
+					       ":%s PART %s",
+					       client->name, channel->name);
+				if (MyUser(client))
+				{
+					sendto_one(client, mtags, ":%s!%s@%s PART %s",
+						client->name, client->user->username, GetHost(client), channel->name);
+				}
+			}
+			else
+			{
+				sendto_channel(channel, client, client,
+					       "ho", 0,
+					       SEND_LOCAL, mtags,
+					       ":%s PART %s %s",
+					       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->name, comment);
+				}
+			}
+		}
+		else
+		{
+			/* Show PART to all users in channel */
+			if (!comment)
+			{
+				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
+				               ":%s PART %s",
+				               client->name, channel->name);
+			} else {
+				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
+				               ":%s PART %s :%s",
+				               client->name, channel->name, comment);
+			}
+		}
+
+		if (MyUser(client))
+			RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, comment);
+		else
+			RunHook(HOOKTYPE_REMOTE_PART, client, channel, mtags, comment);
+
+		free_message_tags(mtags);
+
+		remove_user_from_channel(client, channel, 0);
+	}
+}
diff --git a/ircd/src/modules/pass.c b/ircd/src/modules/pass.c
@@ -0,0 +1,84 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/pass.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_pass);
+
+#define MSG_PASS 	"PASS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"pass",
+	"5.0",
+	"command /pass", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_PASS, cmd_pass, 1, CMD_UNREGISTERED|CMD_USER|CMD_SERVER);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/***************************************************************************
+ * cmd_pass() - Added Sat, 4 March 1989
+ ***************************************************************************/
+/*
+** cmd_pass
+**	parv[1] = password
+*/
+CMD_FUNC(cmd_pass)
+{
+	const char *password = parc > 1 ? parv[1] : NULL;
+
+	if (!MyConnect(client) || (!IsUnknown(client) && !IsHandshake(client)))
+	{
+		sendnumeric(client, ERR_ALREADYREGISTRED);
+		return;
+	}
+
+	if (BadPtr(password))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "PASS");
+		return;
+	}
+
+	/* Store the password */
+	safe_strldup(client->local->passwd, password, PASSWDLEN+1);
+
+	/* note: the original non-truncated password is supplied as 2nd parameter. */
+	RunHookReturn(HOOKTYPE_LOCAL_PASS, !=0, client, password);
+}
diff --git a/ircd/src/modules/pingpong.c b/ircd/src/modules/pingpong.c
@@ -0,0 +1,208 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/pingpong.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_ping);
+CMD_FUNC(cmd_pong);
+CMD_FUNC(cmd_nospoof);
+
+/* Place includes here */
+#define MSG_PING        "PING"  /* PING */
+#define MSG_PONG        "PONG"  /* PONG */
+
+ModuleHeader MOD_HEADER
+  = {
+	"pingpong",	/* Name of module */
+	"5.0", /* Version */
+	"ping, pong and nospoof", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_PING, cmd_ping, MAXPARA, CMD_USER|CMD_SERVER|CMD_SHUN);
+	CommandAdd(modinfo->handle, MSG_PONG, cmd_pong, MAXPARA, CMD_UNREGISTERED|CMD_USER|CMD_SERVER|CMD_SHUN|CMD_VIRUS);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_ping
+**	parv[1] = origin
+**	parv[2] = destination
+*/
+CMD_FUNC(cmd_ping)
+{
+	Client *target;
+	const char *origin, *destination;
+
+	if (parc < 2 || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NOORIGIN);
+		return;
+	}
+
+	origin = parv[1];
+	destination = parv[2];	/* Will get NULL or pointer (parc >= 2!!) */
+
+	if (!MyUser(client))
+		origin = client->name;
+
+	if (!BadPtr(destination) && mycmp(destination, me.name) != 0 && mycmp(destination, me.id) != 0)
+	{
+		if (MyUser(client))
+			origin = client->name; /* Make sure origin is not spoofed */
+		if ((target = find_server_quick(destination)) && (target != &me))
+			sendto_one(target, NULL, ":%s PING %s :%s", client->name, origin, destination);
+		else
+		{
+			sendnumeric(client, ERR_NOSUCHSERVER, destination);
+			return;
+		}
+	}
+	else
+	{
+		MessageTag *mtags = NULL;
+		new_message(&me, recv_mtags, &mtags);
+		sendto_one(client, mtags, ":%s PONG %s :%s", me.name,
+		    (destination) ? destination : me.name, origin);
+		free_message_tags(mtags);
+	}
+}
+
+/*
+** cmd_nospoof - allows clients to respond to no spoofing patch
+**	parv[1] = code
+*/
+CMD_FUNC(cmd_nospoof)
+{
+	unsigned long result;
+
+	if (IsNotSpoof(client))
+		return;
+	if (IsRegistered(client))
+		return;
+	if (!*client->name)
+		return;
+	if (BadPtr(parv[1]))
+	{
+		sendnotice(client, "ERROR: Invalid PING response. Your client must respond back with PONG :<cookie>");
+		return;
+	}
+
+	result = strtoul(parv[1], NULL, 16);
+
+	if (result != client->local->nospoof)
+	{
+		/* Apparently we also accept PONG <irrelevant> <cookie>... */
+		if (BadPtr(parv[2]))
+		{
+			sendnotice(client, "ERROR: Invalid PING response. Your client must respond back with PONG :<cookie>");
+			return;
+		}
+		result = strtoul(parv[2], NULL, 16);
+		if (result != client->local->nospoof)
+		{
+			sendnotice(client, "ERROR: Invalid PING response. Your client must respond back with PONG :<cookie>");
+			return;
+		}
+	}
+
+	client->local->nospoof = 0;
+
+	if (USE_BAN_VERSION && MyConnect(client))
+		sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
+			   me.name, client->name);
+
+	if (is_handshake_finished(client))
+		register_user(client);
+}
+
+/*
+** cmd_pong
+**	parv[1] = origin
+**	parv[2] = destination
+*/
+CMD_FUNC(cmd_pong)
+{
+	Client *target;
+	const char *origin, *destination;
+
+	if (!IsRegistered(client))
+	{
+		CALL_CMD_FUNC(cmd_nospoof);
+		return;
+	}
+
+	if (parc < 2 || *parv[1] == '\0')
+	{
+		sendnumeric(client, ERR_NOORIGIN);
+		return;
+	}
+
+	origin = parv[1];
+	destination = parv[2];
+	ClearPingSent(client);
+	ClearPingWarning(client);
+
+	/* Remote pongs for clients? uhh... */
+	if (MyUser(client) || !IsRegistered(client))
+		return;
+
+	/* PONG from a server - either for us, or needs relaying.. */
+	if (!BadPtr(destination) && mycmp(destination, me.name) != 0)
+	{
+		if ((target = find_client(destination, NULL)) ||
+		    (target = find_server_quick(destination)))
+		{
+			if (IsUser(client) && !IsServer(target))
+			{
+				sendnumeric(client, ERR_NOSUCHSERVER, destination);
+				return;
+			} else
+			{
+				MessageTag *mtags = NULL;
+				new_message(client, recv_mtags, &mtags);
+				sendto_one(target, mtags, ":%s PONG %s %s", client->name, origin, destination);
+				free_message_tags(mtags);
+			}
+		}
+		else
+		{
+			sendnumeric(client, ERR_NOSUCHSERVER, destination);
+			return;
+		}
+	}
+}
diff --git a/ircd/src/modules/plaintext-policy.c b/ircd/src/modules/plaintext-policy.c
@@ -0,0 +1,75 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/plaintext-policy.c
+ *   (C) 2017 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"plaintext-policy",
+	"5.0",
+	"Plaintext Policy CAP",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	return MOD_SUCCESS;
+}
+
+void init_plaintext_policy(ModuleInfo *modinfo);
+
+MOD_LOAD()
+{
+	/* init_plaintext_policy is delayed to MOD_LOAD due to configuration dependency */
+	init_plaintext_policy(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+const char *plaintext_policy_capability_parameter(Client *client)
+{
+	static char buf[128];
+	
+	snprintf(buf, sizeof(buf), "user=%s,oper=%s,server=%s",
+             policy_valtostr(iConf.plaintext_policy_user),
+             policy_valtostr(iConf.plaintext_policy_oper),
+             policy_valtostr(iConf.plaintext_policy_server));
+	return buf;
+}
+
+void init_plaintext_policy(ModuleInfo *modinfo)
+{
+	ClientCapabilityInfo cap;
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "unrealircd.org/plaintext-policy";
+	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
+	cap.parameter = plaintext_policy_capability_parameter;
+	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
+}
diff --git a/ircd/src/modules/protoctl.c b/ircd/src/modules/protoctl.c
@@ -0,0 +1,409 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/protoctl.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_protoctl);
+
+#define MSG_PROTOCTL 	"PROTOCTL"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"protoctl",
+	"5.0",
+	"command /protoctl", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_PROTOCTL, cmd_protoctl, MAXPARA, CMD_UNREGISTERED|CMD_SERVER|CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+#define MAX_SERVER_TIME_OFFSET 60
+
+/* The PROTOCTL command is used for negotiating capabilities with
+ * directly connected servers.
+ * See https://www.unrealircd.org/docs/Server_protocol:PROTOCTL_command
+ * for all technical documentation, especially if you are a server
+ * or services coder.
+ */
+CMD_FUNC(cmd_protoctl)
+{
+	int  i;
+	int first_protoctl = IsProtoctlReceived(client) ? 0 : 1; /**< First PROTOCTL we receive? Special ;) */
+	char proto[512];
+	char *name, *value, *p;
+
+	if (!MyConnect(client))
+		return; /* Remote PROTOCTL's are not supported */
+
+	SetProtoctlReceived(client);
+
+	for (i = 1; i < parc; i++)
+	{
+		strlcpy(proto, parv[i], sizeof proto);
+		p = strchr(proto, '=');
+		if (p)
+		{
+			name = proto;
+			*p++ = '\0';
+			value = p;
+		} else {
+			name = proto;
+			value = NULL;
+		}
+
+		if (!strcmp(name, "NAMESX"))
+		{
+			SetCapability(client, "multi-prefix");
+		}
+		else if (!strcmp(name, "UHNAMES") && UHNAMES_ENABLED)
+		{
+			SetCapability(client, "userhost-in-names");
+		}
+		else if (IsUser(client))
+		{
+			return;
+		}
+		else if (!strcmp(name, "VL"))
+		{
+			SetVL(client);
+		}
+		else if (!strcmp(name, "VHP"))
+		{
+			SetVHP(client);
+		}
+		else if (!strcmp(name, "CLK"))
+		{
+			SetCLK(client);
+		}
+		else if (!strcmp(name, "SJSBY") && iConf.ban_setter_sync)
+		{
+			SetSJSBY(client);
+		}
+		else if (!strcmp(name, "MTAGS"))
+		{
+			SetMTAGS(client);
+		}
+		else if (!strcmp(name, "NEXTBANS"))
+		{
+			SetNEXTBANS(client);
+		}
+		else if (!strcmp(name, "NICKCHARS") && value)
+		{
+			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
+				continue;
+			/* Ok, server is either authenticated, or is an outgoing connect... */
+			/* Some combinations are fatal because they would lead to mass-kills:
+			 * - use of 'utf8' on our server but not on theirs
+			 */
+			if (strstr(charsys_get_current_languages(), "utf8") && !strstr(value, "utf8"))
+			{
+				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()))
+			{
+				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->server)
+				safe_strdup(client->server->features.nickchars, value);
+
+			/* If this is a runtime change (so post-handshake): */
+			if (IsServer(client))
+				broadcast_sinfo(client, NULL, client);
+		}
+		else if (!strcmp(name, "CHANNELCHARS") && value)
+		{
+			int their_value;
+
+			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
+				continue;
+
+			their_value = allowed_channelchars_strtoval(value);
+			if (their_value != iConf.allowed_channelchars)
+			{
+				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;
+			}
+		}
+		else if (!strcmp(name, "SID") && value)
+		{
+			Client *aclient;
+			char *sid = value;
+
+			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
+			{
+				exit_client(client, NULL, "Got PROTOCTL SID before EAUTH, that's the wrong order!");
+				return;
+			}
+
+			if (*client->id && (strlen(client->id)==3))
+			{
+				exit_client(client, NULL, "Got PROTOCTL SID twice");
+				return;
+			}
+
+			if (!valid_sid(value))
+			{
+				exit_client(client, NULL, "Invalid SID. The first character must be a digit and the other two characters must be A-Z0-9. Eg: 0AA.");
+				return;
+			}
+
+			if (IsServer(client))
+			{
+				exit_client(client, NULL, "Got PROTOCTL SID after SERVER, that's the wrong order!");
+				return;
+			}
+
+			if ((aclient = hash_find_id(sid, NULL)) != NULL)
+			{
+				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SID_COLLISION", client,
+					   "Server link $client rejected. Server with SID $sid already exist via uplink $existing_client.server.uplink.",
+					   log_data_string("sid", sid),
+					   log_data_client("existing_client", aclient));
+				exit_client(client, NULL, "SID collision");
+				return;
+			}
+
+			if (*client->id)
+				del_from_id_hash_table(client->id, client); /* delete old UID entry (created on connect) */
+			strlcpy(client->id, sid, IDLEN);
+			add_to_id_hash_table(client->id, client); /* add SID */
+		}
+		else if (!strcmp(name, "EAUTH") && value)
+		{
+			/* Early authorization: EAUTH=servername,protocol,flags,software
+			 * (Only servername is mandatory, rest is optional)
+			 */
+			int ret;
+			char *p;
+			char *servername = NULL, *protocol = NULL, *flags = NULL, *software = NULL;
+			char buf[512];
+			ConfigItem_link *aconf = NULL;
+
+			if (IsEAuth(client))
+			{
+				exit_client(client, NULL, "PROTOCTL EAUTH received twice");
+				return;
+			}
+
+			strlcpy(buf, value, sizeof(buf));
+			p = strchr(buf, ' ');
+			if (p)
+			{
+				*p = '\0';
+				p = NULL;
+			}
+			
+			servername = strtoken_noskip(&p, buf, ",");
+			if (!servername || !valid_server_name(servername))
+			{
+				exit_client(client, NULL, "Bogus server name");
+				return;
+			}
+			
+			
+			protocol = strtoken_noskip(&p, NULL, ",");
+			if (protocol)
+			{
+				flags = strtoken_noskip(&p, NULL, ",");
+				if (flags)
+					software = strtoken_noskip(&p, NULL, ",");
+			}
+			
+			/* 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 (!(aconf = verify_link(client)))
+				return;
+
+			/* note: software, protocol and flags may be NULL */
+			if (!check_deny_version(client, software, protocol ? atoi(protocol) : 0, flags))
+				return;
+
+			SetEAuth(client);
+			make_server(client); /* allocate and set client->server */
+			if (protocol)
+				client->server->features.protocol = atoi(protocol);
+			if (software)
+				safe_strdup(client->server->features.software, software);
+			if (is_services_but_not_ulined(client))
+			{
+				exit_client_fmt(client, NULL, "Services detected but no ulines { } for server name %s", client->name);
+				return;
+			}
+			if (!IsHandshake(client) && aconf) /* Send PASS early... */
+				sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
+		}
+		else if (!strcmp(name, "SERVERS") && value)
+		{
+			Client *aclient, *srv;
+			char *sid = NULL;
+			
+			if (!IsEAuth(client))
+				continue;
+				
+			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: SERVERS=001,002,0AB,004,005
+			 */
+
+			add_pending_net(client, value);
+
+			aclient = find_non_pending_net_duplicates(client);
+			if (aclient)
+			{
+				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;
+			}
+			
+			aclient = find_pending_net_duplicates(client, &srv, &sid);
+			if (aclient)
+			{
+				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;
+			}
+
+			/* Send our PROTOCTL SERVERS= back if this was NOT a response */
+			if (*value != '*')
+				send_protoctl_servers(client, 1);
+		}
+		else if (!strcmp(name, "TS") && value && (IsServer(client) || IsEAuth(client)))
+		{
+			long t = atol(value);
+
+			if (t < 10000)
+				continue; /* ignore */
+
+			if ((TStime() - t) > MAX_SERVER_TIME_OFFSET)
+			{
+				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)
+			{
+				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;
+			}
+		}
+		else if (!strcmp(name, "MLOCK"))
+		{
+			client->local->proto |= PROTO_MLOCK;
+		}
+		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->server)
+		{
+			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->server)
+		{
+			client->server->boottime = atol(value);
+		}
+		else if (!strcmp(name, "EXTSWHOIS"))
+		{
+			client->local->proto |= PROTO_EXTSWHOIS;
+		}
+		/* You can add protocol extensions here.
+		 * Use 'name' and 'value' (the latter may be NULL).
+		 *
+		 * DO NOT error or warn on unknown proto; we just don't
+		 * support it.
+		 */
+	}
+
+	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
+		 * of older servers).
+		 */
+		send_server_message(client);
+	}
+}
diff --git a/ircd/src/modules/quit.c b/ircd/src/modules/quit.c
@@ -0,0 +1,177 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/quit.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_quit);
+
+#define MSG_QUIT        "QUIT"  /* QUIT */
+
+ModuleHeader MOD_HEADER
+  = {
+	"quit",	/* Name of module */
+	"5.0", /* Version */
+	"command /quit", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_QUIT, cmd_quit, 1, CMD_UNREGISTERED|CMD_USER|CMD_VIRUS);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_quit
+**	parv[1] = comment
+*/
+CMD_FUNC(cmd_quit)
+{
+	const char *comment = (parc > 1 && parv[1]) ? parv[1] : client->name;
+	char commentbuf[MAXQUITLEN + 1];
+	char commentbuf2[MAXQUITLEN + 1];
+
+	if (parc > 1 && parv[1])
+	{
+		strlncpy(commentbuf, parv[1], sizeof(commentbuf), iConf.quit_length);
+		comment = commentbuf;
+	} else {
+		comment = client->name;
+	}
+
+	if (MyUser(client))
+	{
+		int n;
+		Hook *tmphook;
+
+		if (STATIC_QUIT)
+		{
+			exit_client(client, recv_mtags, STATIC_QUIT);
+			return;
+		}
+
+		if (IsVirus(client))
+		{
+			exit_client(client, recv_mtags, "Client exited");
+			return;
+		}
+
+		if (match_spamfilter(client, comment, SPAMF_QUIT, "QUIT", NULL, 0, NULL))
+		{
+			comment = client->name;
+			if (IsDead(client))
+				return;
+		}
+		
+		if (!ValidatePermissionsForPath("immune:anti-spam-quit-message-time",client,NULL,NULL,NULL) && ANTI_SPAM_QUIT_MSG_TIME)
+		{
+			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;
+			const char *newcomment;
+			Channel *channel;
+
+			for (lp = client->user->channel; lp; lp = lp_next)
+			{
+				channel = lp->channel;
+				newcomment = comment;
+				lp_next = lp->next;
+
+				for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_QUIT_CHAN]; tmphook; tmphook = tmphook->next)
+				{
+					newcomment = (*(tmphook->func.stringfunc))(client, channel, comment);
+					if (!newcomment)
+						break;
+				}
+
+				if (newcomment && is_banned(client, channel, BANCHK_LEAVE_MSG, &newcomment, NULL))
+					newcomment = NULL;
+
+				/* Comment changed? Then PART the user before we do the QUIT. */
+				if (comment != newcomment)
+				{
+					const char *parx[4];
+					char tmp[512];
+					int ret;
+
+
+					parx[0] = 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): */
+					if (IsDead(client))
+						return;
+				}
+			}
+		}
+
+		for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_QUIT]; tmphook; tmphook = tmphook->next)
+		{
+			comment = (*(tmphook->func.stringfunc))(client, comment);
+			if (!comment)
+			{			
+				comment = client->name;
+				break;
+			}
+		}
+
+		if (PREFIX_QUIT)
+			snprintf(commentbuf2, sizeof(commentbuf2), "%s: %s", PREFIX_QUIT, comment);
+		else
+			strlcpy(commentbuf2, comment, sizeof(commentbuf2));
+
+		exit_client(client, recv_mtags, commentbuf2);
+	}
+	else
+	{
+		/* Remote quits and non-person quits always use their original comment.
+		 * Also pass recv_mtags so to keep the msgid and such.
+		 */
+		exit_client(client, recv_mtags, comment);
+	}
+}
diff --git a/ircd/src/modules/real-quit-reason.c b/ircd/src/modules/real-quit-reason.c
@@ -0,0 +1,81 @@
+/*
+ * unrealircd.org/real-quit-reason message tag (server only)
+ * This is really server-only, it does not traverse to any clients.
+ * (C) Copyright 2023-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"real-quit-reason",
+	"6.0",
+	"unrealircd.org/real-quit-reason message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Forward declarations */
+int real_quit_reason_mtag_is_ok(Client *client, const char *name, const char *value);
+int real_quit_reason_mtag_should_send_to_client(Client *target);
+void mtag_inherit_real_quit_reason(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/real-quit-reason";
+	mtag.is_ok = real_quit_reason_mtag_is_ok;
+	mtag.should_send_to_client = real_quit_reason_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_inherit_real_quit_reason);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending 'unrealircd.org/real-quit-reason'
+ * is permitted to do so and uses a permitted syntax.
+ * We simply allow unrealircd.org/real-quit-reason ONLY from servers and with any syntax.
+ */
+int real_quit_reason_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_inherit_real_quit_reason(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/real-quit-reason");
+	if (m)
+	{
+		m = duplicate_mtag(m);
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int real_quit_reason_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target))
+		return 1;
+
+	return 0;
+}
diff --git a/ircd/src/modules/reply-tag.c b/ircd/src/modules/reply-tag.c
@@ -0,0 +1,116 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/reply-tag.c
+ *   (C) 2021 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* This implements https://ircv3.net/specs/client-tags/reply */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"reply-tag",
+	"5.0",
+	"+reply client tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+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()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+#if 0
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "+reply";
+	mtag.is_ok = replytag_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+#endif
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "+draft/reply";
+	mtag.is_ok = replytag_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_replytag);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending the mtag is permitted to do so.
+ */
+int replytag_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	const char *p;
+
+	/* Require a non-empty parameter */
+	if (BadPtr(value))
+		return 0;
+
+	/* All our PRIVMSG/NOTICE msgid's are of this size: */
+	if (strlen(value) != MSGIDLEN)
+		return 0;
+
+	for (p = value; *p; p++)
+		if (!isalnum(*p))
+			return 0; /* non-alphanumeric */
+
+	return 1; /* OK */
+}
+
+void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m;
+
+	if (IsUser(client))
+	{
+#if 0
+		m = find_mtag(recv_mtags, "+reply");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+#endif
+		m = find_mtag(recv_mtags, "+draft/reply");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+	}
+}
diff --git a/ircd/src/modules/reputation.c b/ircd/src/modules/reputation.c
@@ -0,0 +1,1379 @@
+/*
+ * reputation - Provides a scoring system for "known users".
+ * (C) Copyright 2015-2019 Bram Matthys (Syzop) and the UnrealIRCd team.
+ * License: GPLv2 or later
+ *
+ * How this works is simple:
+ * Every 5 minutes the IP address of all the connected users receive
+ * a point. Registered users receive 2 points every 5 minutes.
+ * The total reputation score is then later used, by other modules, for
+ * example to make decisions such as to reject or allow a user if the
+ * server is under attack.
+ * The reputation scores are saved in a database. By default this file
+ * is data/reputation.db (often ~/unrealircd/data/reputation.db).
+ *
+ * See also https://www.unrealircd.org/docs/Connthrottle
+ */
+
+#include "unrealircd.h"
+
+#define REPUTATION_VERSION "1.2"
+
+/* Change to #define to benchmark. Note that this will add random
+ * reputation entries so should never be used on production servers!!!
+ */
+#undef BENCHMARK
+#undef TEST
+
+/* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
+ * 10k random IP's with various expire times:
+ * - load db:  23 ms
+ * - expiry:    1 ms
+ * - save db:   7 ms
+ * 100k random IP's with various expire times:
+ * - load db: 103 ms
+ * - expiry:   10 ms
+ * - save db:  32 ms
+ * So, even for 100,000 unique IP's, the initial load of the database
+ * would delay the UnrealIRCd boot process only for 0.1 second.
+ * The writing of the db, which happens every 5 minutes, for such
+ * amount of IP's takes 32ms (0.03 second).
+ * Of course, exact figures will depend on the storage and cache.
+ * That being said, the file for 100k random IP's is slightly under
+ * 3MB, so not big, which likely means the timing will be similar
+ * for a broad number of (storage) systems.
+ */
+ 
+#ifndef TEST
+ #define BUMP_SCORE_EVERY	300
+ #define DELETE_OLD_EVERY	605
+ #define SAVE_DB_EVERY		902
+#else
+ #define BUMP_SCORE_EVERY 	3
+ #define DELETE_OLD_EVERY	3
+ #define SAVE_DB_EVERY		3
+#endif
+
+#ifndef CALLBACKTYPE_REPUTATION_STARTTIME
+ #define CALLBACKTYPE_REPUTATION_STARTTIME 5
+#endif
+
+ModuleHeader MOD_HEADER
+  = {
+	"reputation",
+	REPUTATION_VERSION,
+	"Known IP's scoring system",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Defines */
+
+#define MAXEXPIRES 10
+
+#define REPUTATION_SCORE_CAP 10000
+
+#define UPDATE_SCORE_MARGIN 1
+
+#define REPUTATION_HASH_TABLE_SIZE 2048
+
+#define Reputation(client)	moddata_client(client, reputation_md).l
+
+#define WARN_WRITE_ERROR(fname) \
+	do { \
+		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) \
+	do { \
+		if (!(x)) { \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+
+/* Definitions (structs, etc.) */
+
+struct cfgstruct {
+	int expire_score[MAXEXPIRES];
+	long expire_time[MAXEXPIRES];
+	char *database;
+	char *db_secret;
+};
+
+typedef struct ReputationEntry ReputationEntry;
+
+struct ReputationEntry {
+	ReputationEntry *prev, *next;
+	unsigned short score; /**< score for the user */
+	long last_seen; /**< user last seen (unix timestamp) */
+	int marker; /**< internal marker, not written to db */
+	char ip[1]; /*< ip address */
+};
+
+/* Global variables */
+
+static struct cfgstruct cfg; /**< Current configuration */
+static struct cfgstruct test; /**< Testing configuration (not active yet) */
+long reputation_starttime = 0;
+long reputation_writtentime = 0;
+
+static ReputationEntry *ReputationHashTable[REPUTATION_HASH_TABLE_SIZE];
+static char siphashkey_reputation[SIPHASH_KEY_LENGTH];
+
+static ModuleInfo ModInf;
+
+ModDataInfo *reputation_md; /* Module Data structure which we acquire */
+
+/* Forward declarations */
+void reputation_md_free(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, NameValuePrioList **list);
+int reputation_set_on_connect(Client *client);
+int reputation_pre_lconnect(Client *client);
+int reputation_ip_change(Client *client, const char *oldip);
+int reputation_connect_extinfo(Client *client, NameValuePrioList **list);
+int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+int reputation_config_posttest(int *errs);
+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);
+EVENT(reputation_save_db_evt);
+int reputation_load_db(void);
+int reputation_save_db(void);
+int reputation_starttime_callback(void);
+
+MOD_TEST()
+{
+	memcpy(&ModInf, modinfo, modinfo->size);
+	memset(&cfg, 0, sizeof(cfg));
+	memset(&test, 0, sizeof(cfg));
+	reputation_config_setdefaults(&test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reputation_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, reputation_config_posttest);
+	CallbackAdd(modinfo->handle, CALLBACKTYPE_REPUTATION_STARTTIME, reputation_starttime_callback);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
+
+	memset(&ReputationHashTable, 0, sizeof(ReputationHashTable));
+	siphash_generate_key(siphashkey_reputation);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "reputation";
+	mreq.free = reputation_md_free;
+	mreq.serialize = reputation_md_serialize;
+	mreq.unserialize = reputation_md_unserialize;
+	mreq.sync = 0; /* local! */
+	mreq.type = MODDATATYPE_CLIENT;
+	reputation_md = ModDataAdd(modinfo->handle, mreq);
+	if (!reputation_md)
+		abort();
+
+	reputation_config_setdefaults(&cfg);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reputation_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, reputation_whois);
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, reputation_set_on_connect);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, reputation_ip_change);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 2000000000, reputation_pre_lconnect); /* (prio: last) */
+	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, -1000000000, reputation_set_on_connect); /* (prio: near-first) */
+	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 0, reputation_connect_extinfo); /* (prio: near-first) */
+	CommandAdd(ModInf.handle, "REPUTATION", reputation_cmd, MAXPARA, CMD_USER|CMD_SERVER);
+	CommandAdd(ModInf.handle, "REPUTATIONUNPERM", reputationunperm, MAXPARA, CMD_USER|CMD_SERVER);
+	return MOD_SUCCESS;
+}
+
+#ifdef BENCHMARK
+void reputation_benchmark(int entries)
+{
+	char ip[64];
+	int i;
+	ReputationEntry *e;
+
+	srand(1234); // fixed seed
+
+	for (i = 0; i < entries; i++)
+	{
+		ReputationEntry *e = safe_alloc(sizeof(ReputationEntry) + 64);
+		snprintf(e->ip, 63, "%d.%d.%d.%d", rand()%255, rand()%255, rand()%255, rand()%255);
+		e->score = rand()%255 + 1;
+		e->last_seen = TStime();
+		if (find_reputation_entry(e->ip))
+		{
+			safe_free(e);
+			continue;
+		}
+		add_reputation_entry(e);
+	}
+}
+#endif
+MOD_LOAD()
+{
+	reputation_load_db();
+	if (reputation_starttime == 0)
+		reputation_starttime = TStime();
+	EventAdd(ModInf.handle, "delete_old_records", delete_old_records, NULL, DELETE_OLD_EVERY*1000, 0);
+	EventAdd(ModInf.handle, "add_scores", add_scores, NULL, BUMP_SCORE_EVERY*1000, 0);
+	EventAdd(ModInf.handle, "reputation_save_db", reputation_save_db_evt, NULL, SAVE_DB_EVERY*1000, 0);
+#ifdef BENCHMARK
+	reputation_benchmark(10000);
+#endif
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (loop.terminating)
+		reputation_save_db();
+	reputation_free_config(&test);
+	reputation_free_config(&cfg);
+	return MOD_SUCCESS;
+}
+
+void reputation_config_setdefaults(struct cfgstruct *cfg)
+{
+	/* data/reputation.db */
+	safe_strdup(cfg->database, "reputation.db");
+	convert_to_absolute_path(&cfg->database, PERMDATADIR);
+
+	/* EXPIRES the following entries if the IP does appear for some time: */
+	/* <=2 points after 1 hour */
+	cfg->expire_score[0] = 2;
+#ifndef TEST
+	cfg->expire_time[0]   = 3600;
+#else
+	cfg->expire_time[0]   = 36;
+#endif
+	/* <=6 points after 7 days */
+	cfg->expire_score[1] = 6;
+	cfg->expire_time[1]   = 86400*7;
+	/* ANY result that has not been seen for 30 days */
+	cfg->expire_score[2] = -1;
+	cfg->expire_time[2]   = 86400*30;
+}
+
+void reputation_free_config(struct cfgstruct *cfg)
+{
+	safe_free(cfg->database);
+	safe_free(cfg->db_secret);
+}
+
+int reputation_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::reputation.. */
+	if (!ce || strcmp(ce->name, "reputation"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: blank set::reputation::%s without value",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		} else
+		if (!strcmp(cep->name, "database"))
+		{
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
+		} else
+		if (!strcmp(cep->name, "db-secret"))
+		{
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
+			{
+				config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
+				errors++;
+				continue;
+			}
+			safe_strdup(test.db_secret, cep->value);
+		} else
+		{
+			config_error("%s:%i: unknown directive set::reputation::%s",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	/* We are only interrested in set::reputation.. */
+	if (!ce || strcmp(ce->name, "reputation"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database"))
+		{
+			safe_strdup(cfg.database, cep->value);
+		} else
+		if (!strcmp(cep->name, "db-secret"))
+		{
+			safe_strdup(cfg.db_secret, cep->value);
+		}
+	}
+	return 1;
+}
+
+int reputation_config_posttest(int *errs)
+{
+	int errors = 0;
+	char *errstr;
+
+	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+	{
+		config_error("[reputation] %s", errstr);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/** Parse database header and set variables appropriately */
+int parse_db_header_old(char *buf)
+{
+	char *header=NULL, *version=NULL, *starttime=NULL, *writtentime=NULL;
+	char *p=NULL;
+
+	if (strncmp(buf, "REPDB", 5))
+		return 0;
+
+	header = strtoken(&p, buf, " ");
+	if (!header)
+		return 0;
+
+	version = strtoken(&p, NULL, " ");
+	if (!version || (atoi(version) != 1))
+		return 0;
+
+	starttime = strtoken(&p, NULL, " ");
+	if (!starttime)
+		return 0;
+
+	writtentime = strtoken(&p, NULL, " ");
+	if (!writtentime)
+		return 0;
+
+	reputation_starttime = atol(starttime);
+	reputation_writtentime = atol(writtentime);
+
+	return 1;
+}
+
+void reputation_load_db_old(void)
+{
+	FILE *fd;
+	char buf[512], *p;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	fd = fopen(cfg.database, "r");
+	if (!fd)
+	{
+		config_warn("WARNING: Could not open/read database '%s': %s", cfg.database, strerror(ERRNO));
+		return;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	if (fgets(buf, 512, fd) == NULL)
+	{
+		config_error("WARNING: Database file corrupt ('%s')", cfg.database);
+		fclose(fd);
+		return;
+	}
+
+	/* Header contains: REPDB <version> <starttime> <writtentime>
+	 * Where:
+	 * REPDB:        Literally the string "REPDB".
+	 * <version>     This is version 1 at the time of this writing.
+	 * <starttime>   The time that recording of reputation started,
+	 *               in other words: when this module was first loaded, ever.
+	 * <writtentime> Time that the database was last written.
+	 */
+	if (!parse_db_header_old(buf))
+	{
+		config_error("WARNING: Cannot load database %s. Error reading header. "
+		             "Database corrupt? Or are you downgrading from a newer "
+		             "UnrealIRCd version perhaps? This is not supported.",
+		             cfg.database);
+		fclose(fd);
+		return;
+	}
+
+	while(fgets(buf, 512, fd) != NULL)
+	{
+		char *ip = NULL, *score = NULL, *last_seen = NULL;
+		ReputationEntry *e;
+
+		stripcrlf(buf);
+		/* Format: <ip> <score> <last seen> */
+		ip = strtoken(&p, buf, " ");
+		if (!ip)
+			continue;
+		score = strtoken(&p, NULL, " ");
+		if (!score)
+			continue;
+		last_seen = strtoken(&p, NULL, " ");
+		if (!last_seen)
+			continue;
+
+		e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
+		strcpy(e->ip, ip); /* safe, see alloc above */
+		e->score = atoi(score);
+		e->last_seen = atol(last_seen);
+
+		add_reputation_entry(e);
+	}
+	fclose(fd);
+
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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
+}
+
+#define R_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			config_warn("[reputation] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+			unrealdb_close(db); \
+			safe_free(ip); \
+			return 0; \
+		} \
+	} while(0)
+
+int reputation_load_db_new(UnrealDB *db)
+{
+	uint64_t l_db_version = 0;
+	uint64_t l_starttime = 0;
+	uint64_t l_writtentime = 0;
+	uint64_t count = 0;
+	uint64_t i;
+	char *ip = NULL;
+	uint16_t score;
+	uint64_t last_seen;
+	ReputationEntry *e;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	R_SAFE(unrealdb_read_int64(db, &l_db_version)); /* reputation db version */
+	if (l_db_version > 2)
+	{
+		config_error("[reputation] Reputation DB is of a newer version (%ld) than supported by us (%ld). "
+		             "Did you perhaps downgrade your UnrealIRCd?",
+		             (long)l_db_version, (long)2);
+		unrealdb_close(db);
+		return 0;
+	}
+	R_SAFE(unrealdb_read_int64(db, &l_starttime)); /* starttime of data gathering */
+	R_SAFE(unrealdb_read_int64(db, &l_writtentime)); /* current time */
+	R_SAFE(unrealdb_read_int64(db, &count)); /* number of entries */
+
+	reputation_starttime = l_starttime;
+	reputation_writtentime = l_writtentime;
+
+	for (i=0; i < count; i++)
+	{
+		R_SAFE(unrealdb_read_str(db, &ip));
+		R_SAFE(unrealdb_read_int16(db, &score));
+		R_SAFE(unrealdb_read_int64(db, &last_seen));
+
+		e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
+		strcpy(e->ip, ip); /* safe, see alloc above */
+		e->score = score;
+		e->last_seen = last_seen;
+		add_reputation_entry(e);
+		safe_free(ip);
+	}
+	unrealdb_close(db);
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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;
+}
+
+/** Load the reputation DB.
+ * Strategy is:
+ * 1) Check for the old header "REPDB 1", if so then call reputation_load_db_old().
+ * 2) Otherwise, open with unrealdb routine
+ * 3) If that fails due to a password provided but the file is unrealdb without password
+ *    then fallback to open without a password (so users can easily upgrade to encrypted)
+ */
+int reputation_load_db(void)
+{
+	FILE *fd;
+	UnrealDB *db;
+	char buf[512];
+
+	fd = fopen(cfg.database, "r");
+	if (!fd)
+	{
+		/* Database does not exist. Could be first boot */
+		config_warn("[reputation] No database present at '%s', will start a new one", cfg.database);
+		return 1;
+	}
+
+	*buf = '\0';
+	if (fgets(buf, sizeof(buf), fd) == NULL)
+	{
+		fclose(fd);
+		config_warn("[reputation] Database at '%s' is 0 bytes", cfg.database);
+		return 1;
+	}
+	fclose(fd);
+	if (!strncmp(buf, "REPDB 1 ", 8))
+	{
+		reputation_load_db_old();
+		return 1; /* not so good to always pretend succes */
+	}
+
+	/* Otherwise, it is an unrealdb, crypted or not */
+	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+		{
+			/* Database does not exist. Could be first boot */
+			config_warn("[reputation] No database present at '%s', will start a new one", cfg.database);
+			return 1;
+		} else
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+		{
+			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+		}
+		if (!db)
+		{
+			config_error("[reputation] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
+			return 0;
+		}
+	}
+	return reputation_load_db_new(db);
+}
+
+int reputation_save_db_old(void)
+{
+	FILE *fd;
+	char tmpfname[512];
+	int i;
+	ReputationEntry *e;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	/* We write to a temporary file. Only to rename it later if everything was ok */
+	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+
+	fd = fopen(tmpfname, "w");
+	if (!fd)
+	{
+		config_error("ERROR: Could not open/write database '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
+		return 0;
+	}
+
+	if (fprintf(fd, "REPDB 1 %lld %lld\n", (long long)reputation_starttime, (long long)TStime()) < 0)
+		goto write_fail;
+
+	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+	{
+		for (e = ReputationHashTable[i]; e; e = e->next)
+		{
+			if (fprintf(fd, "%s %d %lld\n", e->ip, (int)e->score, (long long)e->last_seen) < 0)
+			{
+write_fail:
+				config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
+				fclose(fd);
+				return 0;
+			}
+		}
+	}
+
+	if (fclose(fd) < 0)
+	{
+		config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
+		return 0;
+	}
+
+	/* Everything went fine. We rename our temporary file to the existing
+	 * DB file (will overwrite), which is more or less an atomic operation.
+	 */
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(cfg.database);
+#endif
+	if (rename(tmpfname, cfg.database) < 0)
+	{
+		config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!",
+			tmpfname, cfg.database, strerror(ERRNO));
+		return 0;
+	}
+
+	reputation_writtentime = TStime();
+
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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;
+}
+
+int reputation_save_db(void)
+{
+	UnrealDB *db;
+	char tmpfname[512];
+	int i;
+	uint64_t count;
+	ReputationEntry *e;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+#ifdef TEST
+	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) */
+	if (cfg.db_secret == NULL)
+		return reputation_save_db_old();
+
+	/* We write to a temporary file. Only to rename it later if everything was ok */
+	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+
+	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+	if (!db)
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	/* Write header */
+	W_SAFE(unrealdb_write_int64(db, 2)); /* reputation db version */
+	W_SAFE(unrealdb_write_int64(db, reputation_starttime)); /* starttime of data gathering */
+	W_SAFE(unrealdb_write_int64(db, TStime())); /* current time */
+
+	/* Count entries */
+	count = 0;
+	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+		for (e = ReputationHashTable[i]; e; e = e->next)
+			count++;
+	W_SAFE(unrealdb_write_int64(db, count)); /* Number of DB entries */
+
+	/* Now write the actual individual entries: */
+	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+	{
+		for (e = ReputationHashTable[i]; e; e = e->next)
+		{
+			W_SAFE(unrealdb_write_str(db, e->ip));
+			W_SAFE(unrealdb_write_int16(db, e->score));
+			W_SAFE(unrealdb_write_int64(db, e->last_seen));
+		}
+	}
+
+	if (!unrealdb_close(db))
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	/* Everything went fine. We rename our temporary file to the existing
+	 * DB file (will overwrite), which is more or less an atomic operation.
+	 */
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(cfg.database);
+#endif
+	if (rename(tmpfname, cfg.database) < 0)
+	{
+		config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!",
+			tmpfname, cfg.database, strerror(ERRNO));
+		return 0;
+	}
+
+	reputation_writtentime = TStime();
+
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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(const char *ip)
+{
+	return siphash(ip, siphashkey_reputation) % REPUTATION_HASH_TABLE_SIZE;
+}
+
+void add_reputation_entry(ReputationEntry *e)
+{
+	int hashv = hash_reputation_entry(e->ip);
+
+	AddListItem(e, ReputationHashTable[hashv]);
+}
+
+ReputationEntry *find_reputation_entry(const char *ip)
+{
+	ReputationEntry *e;
+	int hashv = hash_reputation_entry(ip);
+
+	for (e = ReputationHashTable[hashv]; e; e = e->next)
+		if (!strcmp(e->ip, ip))
+			return e;
+
+	return NULL;
+}
+
+int reputation_lookup_score_and_set(Client *client)
+{
+	char *ip = client->ip;
+	ReputationEntry *e;
+
+	Reputation(client) = 0; /* (re-)set to zero (yes, important!) */
+	if (ip)
+	{
+		e = find_reputation_entry(ip);
+		if (e)
+		{
+			Reputation(client) = e->score; /* SET MODDATA */
+		}
+	}
+	return Reputation(client);
+}
+
+/** Called when the user connects.
+ * Locally: very early, just after the TCP/IP connection has
+ * been established, before any data.
+ * Remote user: early in the HOOKTYPE_REMOTE_CONNECT hook.
+ */
+int reputation_set_on_connect(Client *client)
+{
+	reputation_lookup_score_and_set(client);
+	return 0;
+}
+
+int reputation_ip_change(Client *client, const char *oldip)
+{
+	reputation_lookup_score_and_set(client);
+	return 0;
+}
+
+int reputation_pre_lconnect(Client *client)
+{
+	/* User will likely be accepted. Inform other servers about the score
+	 * we have for this user. For more information about this type of
+	 * server to server traffic, see the reputation_server_cmd function.
+	 *
+	 * Note that we use reputation_lookup_score_and_set() here
+	 * and not Reputation(client) because we want to RE-LOOKUP
+	 * the score for the IP in the database. We do this because
+	 * between reputation_set_on_connect() and reputation_pre_lconnect()
+	 * the IP of the user may have been changed due to IP-spoofing
+	 * (WEBIRC).
+	 */
+	int score = reputation_lookup_score_and_set(client);
+
+	sendto_server(NULL, 0, 0, NULL, ":%s REPUTATION %s %d", me.id, GetIP(client), score);
+
+	return 0;
+}
+
+EVENT(add_scores)
+{
+	static int marker = 0;
+	char *ip;
+	Client *client;
+	ReputationEntry *e;
+
+	/* This marker is used so we only bump score for an IP entry
+	 * once and not twice (or more) if there are multiple users
+	 * with the same IP address.
+	 */
+	marker += 2;
+
+	/* These macros make the code below easier to read. Also,
+	 * this explains why we just did marker+=2 and not marker++.
+	 */
+	#define MARKER_UNREGISTERED_USER (marker)
+	#define MARKER_REGISTERED_USER (marker+1)
+
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (!IsUser(client))
+			continue; /* skip servers, unknowns, etc.. */
+
+		ip = client->ip;
+		if (!ip)
+			continue;
+
+		e = find_reputation_entry(ip);
+		if (!e)
+		{
+			/* Create */
+			e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
+			strcpy(e->ip, ip); /* safe, allocated above */
+			add_reputation_entry(e);
+		}
+
+		/* If this is not a duplicate entry, then bump the score.. */
+		if ((e->marker != MARKER_UNREGISTERED_USER) && (e->marker != MARKER_REGISTERED_USER))
+		{
+			e->marker = MARKER_UNREGISTERED_USER;
+			if (e->score < REPUTATION_SCORE_CAP)
+			{
+				/* Regular users receive a point. */
+				e->score++;
+				/* Registered users receive an additional point */
+				if (IsLoggedIn(client) && (e->score < REPUTATION_SCORE_CAP))
+				{
+					e->score++;
+					e->marker = MARKER_REGISTERED_USER;
+				}
+			}
+		} else
+		if ((e->marker == MARKER_UNREGISTERED_USER) && IsLoggedIn(client) && (e->score < REPUTATION_SCORE_CAP))
+		{
+			/* This is to catch a special case:
+			 * If there are 2 or more users with the same IP
+			 * address and the first user was not registered
+			 * then the IP entry only received a score bump of +1.
+			 * If the 2nd user (with same IP) is a registered
+			 * user then the IP should actually receive a
+			 * score bump of +2 (in total).
+			 */
+			e->score++;
+			e->marker = MARKER_REGISTERED_USER;
+		}
+
+		e->last_seen = TStime();
+		Reputation(client) = e->score; /* update moddata */
+	}
+}
+
+/** Is this entry expired? */
+static inline int is_reputation_expired(ReputationEntry *e)
+{
+	int i;
+	for (i = 0; i < MAXEXPIRES; i++)
+	{
+		if (cfg.expire_time[i] == 0)
+			break; /* end of all entries */
+		if ((e->score <= cfg.expire_score[i]) && (TStime() - e->last_seen > cfg.expire_time[i]))
+			return 1;
+	}
+	return 0;
+}
+
+/** If the reputation changed (due to server syncing) then update the
+ * individual users reputation score as well.
+ */
+void reputation_changed_update_users(ReputationEntry *e)
+{
+	Client *client;
+
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (client->ip && !strcmp(e->ip, client->ip))
+		{
+			/* With some (possibly unneeded) care to only go forward */
+			if (Reputation(client) < e->score)
+				Reputation(client) = e->score;
+		}
+	}
+}
+
+EVENT(delete_old_records)
+{
+	int i;
+	ReputationEntry *e, *e_next;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+	{
+		for (e = ReputationHashTable[i]; e; e = e_next)
+		{
+			e_next = e->next;
+
+			if (is_reputation_expired(e))
+			{
+#ifdef DEBUGMODE
+				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);
+			}
+		}
+	}
+
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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
+}
+
+EVENT(reputation_save_db_evt)
+{
+	reputation_save_db();
+}
+
+CMD_FUNC(reputationunperm)
+{
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	ModuleSetOptions(ModInf.handle, MOD_OPT_PERM, 0);
+
+	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)
+{
+	add_fmt_nvplist(list, 0, "reputation", "%d", GetReputation(client));
+	return 0;
+}
+
+int count_reputation_records(void)
+{
+	int i;
+	ReputationEntry *e;
+	int total = 0;
+
+	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+		for (e = ReputationHashTable[i]; e; e = e->next)
+			total++;
+
+	return total;
+}
+
+void reputation_channel_query(Client *client, Channel *channel)
+{
+	Member *m;
+	char buf[512];
+	char tbuf[256];
+	char **nicks;
+	int *scores;
+	int cnt = 0, i, j;
+	ReputationEntry *e;
+
+	sendtxtnumeric(client, "Users and reputation scores for %s:", channel->name);
+
+	/* Step 1: build a list of nicks and their reputation */
+	nicks = safe_alloc((channel->users+1) * sizeof(char *));
+	scores = safe_alloc((channel->users+1) * sizeof(int));
+	for (m = channel->members; m; m = m->next)
+	{
+		nicks[cnt] = m->client->name;
+		if (m->client->ip)
+		{
+			e = find_reputation_entry(m->client->ip);
+			if (e)
+				scores[cnt] = e->score;
+		}
+		if (++cnt > channel->users)
+		{
+			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
+			break; /* safety net */
+		}
+	}
+
+	/* Step 2: lazy selection sort */
+	for (i = 0; i < cnt && nicks[i]; i++)
+	{
+		for (j = i+1; j < cnt && nicks[j]; j++)
+		{
+			if (scores[i] < scores[j])
+			{
+				char *nick_tmp;
+				int score_tmp;
+				nick_tmp = nicks[i];
+				score_tmp = scores[i];
+				nicks[i] = nicks[j];
+				scores[i] = scores[j];
+				nicks[j] = nick_tmp;
+				scores[j] = score_tmp;
+			}
+		}
+	}
+
+	/* Step 3: send the (ordered) list to the user */
+	*buf = '\0';
+	for (i = 0; i < cnt && nicks[i]; i++)
+	{
+		snprintf(tbuf, sizeof(tbuf), "%s\00314(%d)\003 ", nicks[i], scores[i]);
+		if ((strlen(tbuf)+strlen(buf) > 400) || !nicks[i+1])
+		{
+			sendtxtnumeric(client, "%s%s", buf, tbuf);
+			*buf = '\0';
+		} else {
+			strlcat(buf, tbuf, sizeof(buf));
+		}
+	}
+	sendtxtnumeric(client, "End of list.");
+	safe_free(nicks);
+	safe_free(scores);
+}
+
+void reputation_list_query(Client *client, int maxscore)
+{
+	Client *target;
+	ReputationEntry *e;
+
+	sendtxtnumeric(client, "Users and reputation scores <%d:", maxscore);
+
+	list_for_each_entry(target, &client_list, client_node)
+	{
+		int score = 0;
+
+		if (!IsUser(target) || IsULine(target) || !target->ip)
+			continue;
+
+		e = find_reputation_entry(target->ip);
+		if (e)
+			score = e->score;
+		if (score >= maxscore)
+			continue;
+		sendtxtnumeric(client, "%s!%s@%s [%s] \017(score: %d)",
+			target->name,
+			target->user->username,
+			target->user->realhost,
+			target->ip,
+			score);
+	}
+	sendtxtnumeric(client, "End of list.");
+}
+
+CMD_FUNC(reputation_user_cmd)
+{
+	ReputationEntry *e;
+	const char *ip;
+
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnotice(client, "Reputation module statistics:");
+		sendnotice(client, "Recording for: %lld seconds (since unixtime %lld)",
+			(long long)(TStime() - reputation_starttime),
+			(long long)reputation_starttime);
+		if (reputation_writtentime)
+		{
+			sendnotice(client, "Last successful db write: %lld seconds ago (unixtime %lld)",
+				(long long)(TStime() - reputation_writtentime),
+				(long long)reputation_writtentime);
+		} else {
+			sendnotice(client, "Last successful db write: never");
+		}
+		sendnotice(client, "Current number of records (IP's): %d", count_reputation_records());
+		sendnotice(client, "-");
+		sendnotice(client, "Available commands:");
+		sendnotice(client, "/REPUTATION [nick]     Show reputation info about nick name");
+		sendnotice(client, "/REPUTATION [ip]       Show reputation info about IP address");
+		sendnotice(client, "/REPUTATION [channel]  List users in channel along with their reputation score");
+		sendnotice(client, "/REPUTATION <NN        List users with reputation score below value NN");
+		return;
+	}
+
+	if (strchr(parv[1], '.') || strchr(parv[1], ':'))
+	{
+		ip = parv[1];
+	} else
+	if (parv[1][0] == '#')
+	{
+		Channel *channel = find_channel(parv[1]);
+		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) && !IsMember(client,channel))
+		{
+			sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
+			return;
+		}
+		reputation_channel_query(client, channel);
+		return;
+	} else
+	if (parv[1][0] == '<')
+	{
+		int max = atoi(parv[1] + 1);
+		if (max < 1)
+		{
+			sendnotice(client, "REPUTATION: Invalid search value specified. Use for example '/REPUTATION <5' to search on less-than-five");
+			return;
+		}
+		reputation_list_query(client, max);
+		return;
+	} else {
+		Client *target = find_user(parv[1], NULL);
+		if (!target)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+			return;
+		}
+		ip = target->ip;
+		if (!ip)
+		{
+			sendnotice(client, "No IP address information available for user '%s'.", parv[1]); /* e.g. services */
+			return;
+		}
+	}
+
+	e = find_reputation_entry(ip);
+	if (!e)
+	{
+		sendnotice(client, "No reputation record found for IP %s", ip);
+		return;
+	}
+
+	sendnotice(client, "****************************************************");
+	sendnotice(client, "Reputation record for IP %s:", ip);
+	sendnotice(client, "    Score: %hd", e->score);
+	sendnotice(client, "Last seen: %lld seconds ago (unixtime: %lld)",
+		(long long)(TStime() - e->last_seen),
+		(long long)e->last_seen);
+	sendnotice(client, "****************************************************");
+}
+
+/** The REPUTATION server command handler.
+ * Syntax: :server REPUTATION <ip> <score>
+ * Where the <score> may be prefixed by an asterisk (*).
+ *
+ * The best way to explain this command is to illustrate by example:
+ * :servera REPUTATION 1.2.3.4 0
+ * Then serverb, which might have a score of 2 for this IP, will:
+ * - Send back to the servera direction:  :serverb REPUTATION 1.2.3.4 *2
+ *   So the original server (and direction) receive a score update.
+ * - Propagate to non-servera direction: :servera REPUTATION 1.2.3.4 2
+ *   So use the new higher score (2 rather than 0).
+ * Then the next server may do the same. It MUST propagate to non-serverb
+ * direction and MAY (again) update the score even higher.
+ *
+ * If the score is not prefixed by * then the server may do as above and
+ * send back to the uplink an "update" of the score. If, however, the
+ * score is prefixed by * then the server will NEVER send back to the
+ * uplink, it may only propagate. This is to prevent loops.
+ *
+ * Note that some margin is used when deciding if the server should send
+ * back score updates. This is defined by UPDATE_SCORE_MARGIN.
+ * If this is for example set to 1 then a point difference of 1 will not
+ * yield a score update since such a minor score update is not worth the
+ * server to server traffic. Also, due to timing differences a score
+ * difference of 1 is quite likely to hapen in normal circumstances.
+ */
+CMD_FUNC(reputation_server_cmd)
+{
+	ReputationEntry *e;
+	const char *ip;
+	int score;
+	int allow_reply;
+
+	/* :server REPUTATION <ip> <score> */
+	if ((parc < 3) || BadPtr(parv[2]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "REPUTATION");
+		return;
+	}
+
+	ip = parv[1];
+
+	if (parv[2][0] == '*')
+	{
+		allow_reply = 0;
+		score = atoi(parv[2]+1);
+	} else {
+		allow_reply = 1;
+		score = atoi(parv[2]);
+	}
+
+	if (score > REPUTATION_SCORE_CAP)
+		score = REPUTATION_SCORE_CAP;
+
+	e = find_reputation_entry(ip);
+	if (allow_reply && e && (e->score > score) && (e->score - score > UPDATE_SCORE_MARGIN))
+	{
+		/* We have a higher score, inform the client direction about it.
+		 * This will prefix the score with a * so servers will never reply to it.
+		 */
+		sendto_one(client, NULL, ":%s REPUTATION %s *%d", me.id, parv[1], e->score);
+#ifdef DEBUGMODE
+		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 */
+	}
+
+	/* Update our score if sender has a higher score */
+	if (e && (score > e->score))
+	{
+#ifdef DEBUGMODE
+		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;
+		reputation_changed_update_users(e);
+	}
+
+	/* If we don't have any entry for this IP, add it now. */
+	if (!e && (score > 0))
+	{
+#ifdef DEBUGMODE
+		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 */
+		e->score = score;
+		e->last_seen = TStime();
+		add_reputation_entry(e);
+		reputation_changed_update_users(e);
+	}
+
+	/* Propagate to the non-client direction (score may be updated) */
+	sendto_server(client, 0, 0, NULL,
+	              ":%s REPUTATION %s %s%d",
+	              client->id,
+	              parv[1],
+	              allow_reply ? "" : "*",
+	              score);
+}
+
+CMD_FUNC(reputation_cmd)
+{
+	if (MyUser(client))
+		CALL_CMD_FUNC(reputation_user_cmd);
+	else if (IsServer(client) || IsMe(client))
+		CALL_CMD_FUNC(reputation_server_cmd);
+}
+
+int reputation_whois(Client *client, Client *target, NameValuePrioList **list)
+{
+	int reputation;
+
+	if (whois_get_policy(client, target, "reputation") != WHOIS_CONFIG_DETAILS_FULL)
+		return 0;
+
+	reputation = Reputation(target);
+	if (reputation > 0)
+	{
+		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;
+}
+
+void reputation_md_free(ModData *m)
+{
+	/* we have nothing to free actually, but we must set to zero */
+	m->l = 0;
+}
+
+const char *reputation_md_serialize(ModData *m)
+{
+	static char buf[32];
+	if (m->i == 0)
+		return NULL; /* not set (reputation always starts at 1) */
+	snprintf(buf, sizeof(buf), "%d", m->i);
+	return buf;
+}
+
+void reputation_md_unserialize(const char *str, ModData *m)
+{
+	m->i = atoi(str);
+}
+
+int reputation_starttime_callback(void)
+{
+	/* NB: fix this by 2038 */
+	return (int)reputation_starttime;
+}
diff --git a/ircd/src/modules/require-module.c b/ircd/src/modules/require-module.c
@@ -0,0 +1,576 @@
+/*
+ * Check for modules that are required across the network, as well as modules
+ * that *aren't* even allowed (deny/require module { } blocks)
+ * (C) Copyright 2019 Gottem 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"
+
+#define MSG_SMOD "SMOD"
+#define SMOD_FLAG_REQUIRED 'R'
+#define SMOD_FLAG_GLOBAL 'G'
+#define SMOD_FLAG_LOCAL 'L'
+
+ModuleHeader MOD_HEADER = {
+	"require-module",
+	"5.0.1",
+	"Require/deny modules across the network",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+typedef struct _denymod DenyMod;
+struct _denymod {
+	DenyMod *prev, *next;
+	char *name;
+	char *reason;
+};
+
+typedef struct _requiremod ReqMod;
+struct _requiremod {
+	ReqMod *prev, *next;
+	char *name;
+	char *minversion;
+};
+
+// Forward declarations
+Module *find_modptr_byname(char *name, unsigned strict);
+DenyMod *find_denymod_byname(char *name);
+ReqMod *find_reqmod_byname(char *name);
+
+int reqmods_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int reqmods_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+
+int reqmods_configtest_deny(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int reqmods_configrun_deny(ConfigFile *cf, ConfigEntry *ce, int type);
+
+int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int reqmods_configrun_require(ConfigFile *cf, ConfigEntry *ce, int type);
+
+CMD_FUNC(cmd_smod);
+int reqmods_hook_serverconnect(Client *client);
+
+// Globals
+extern MODVAR Module *Modules;
+DenyMod *DenyModList = NULL;
+ReqMod *ReqModList = NULL;
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reqmods_configtest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	MARK_AS_GLOBAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reqmods_configrun);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_CONNECT, 0, reqmods_hook_serverconnect);
+	CommandAdd(modinfo->handle, MSG_SMOD, cmd_smod, MAXPARA, CMD_SERVER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	DenyMod *dmod, *dnext;
+	ReqMod *rmod, *rnext;
+	for (dmod = DenyModList; dmod; dmod = dnext)
+	{
+		dnext = dmod->next;
+		safe_free(dmod->name);
+		safe_free(dmod->reason);
+		DelListItem(dmod, DenyModList);
+		safe_free(dmod);
+	}
+	for (rmod = ReqModList; rmod; rmod = rnext)
+	{
+		rnext = rmod->next;
+		safe_free(rmod->name);
+		safe_free(rmod->minversion);
+		DelListItem(rmod, ReqModList);
+		safe_free(rmod);
+	}
+	DenyModList = NULL;
+	ReqModList = NULL;
+	return MOD_SUCCESS;
+}
+
+Module *find_modptr_byname(char *name, unsigned strict)
+{
+	Module *mod;
+	for (mod = Modules; mod; mod = mod->next)
+	{
+		// Let's not be too strict with the name
+		if (!strcasecmp(mod->header->name, name))
+		{
+			if (strict && !(mod->flags & MODFLAG_LOADED))
+				mod = NULL;
+			return mod;
+		}
+	}
+	return NULL;
+}
+
+DenyMod *find_denymod_byname(char *name)
+{
+	DenyMod *dmod;
+	for (dmod = DenyModList; dmod; dmod = dmod->next)
+	{
+		if (!strcasecmp(dmod->name, name))
+			return dmod;
+	}
+	return NULL;
+}
+
+ReqMod *find_reqmod_byname(char *name)
+{
+	ReqMod *rmod;
+	for (rmod = ReqModList; rmod; rmod = rmod->next)
+	{
+		if (!strcasecmp(rmod->name, name))
+			return rmod;
+	}
+	return NULL;
+}
+
+int reqmods_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	if (type == CONFIG_DENY)
+		return reqmods_configtest_deny(cf, ce, type, errs);
+
+	if (type == CONFIG_REQUIRE)
+		return reqmods_configtest_require(cf, ce, type, errs);
+
+	return 0;
+}
+
+int reqmods_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	if (type == CONFIG_DENY)
+		return reqmods_configrun_deny(cf, ce, type);
+
+	if (type == CONFIG_REQUIRE)
+		return reqmods_configrun_require(cf, ce, type);
+
+	return 0;
+}
+
+int reqmods_configtest_deny(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+	int has_name, has_reason;
+
+	// We are only interested in deny module { }
+	if (strcmp(ce->value, "module"))
+		return 0;
+
+	has_name = has_reason = 0;
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strlen(cep->name))
+		{
+			config_error("%s:%i: blank directive for deny module { } block", cep->file->filename, cep->line_number);
+			errors++;
+			continue;
+		}
+
+		if (!cep->value || !strlen(cep->value))
+		{
+			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->name, "name"))
+		{
+			if (has_name)
+			{
+				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->value, 0))
+			{
+				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->name, "reason")) // Optional
+		{
+			// Still check for duplicate directives though
+			if (has_reason)
+			{
+				config_error("%s:%i: duplicate %s for deny module { } block", cep->file->filename, cep->line_number, cep->name);
+				errors++;
+				continue;
+			}
+			has_reason = 1;
+			continue;
+		}
+
+		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->file->filename, ce->line_number);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int reqmods_configrun_deny(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	DenyMod *dmod;
+
+	if (strcmp(ce->value, "module"))
+		return 0;
+
+	dmod = safe_alloc(sizeof(DenyMod));
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "name"))
+		{
+			safe_strdup(dmod->name, cep->value);
+			continue;
+		}
+
+		if (!strcmp(cep->name, "reason"))
+		{
+			safe_strdup(dmod->reason, cep->value);
+			continue;
+		}
+	}
+
+	// Just use a default reason if none was specified (since it's optional)
+	if (!dmod->reason || !strlen(dmod->reason))
+		 safe_strdup(dmod->reason, "no reason");
+	AddListItem(dmod, DenyModList);
+	return 1;
+}
+
+int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+	int has_name, has_minversion;
+
+	// We are only interested in require module { }
+	if (strcmp(ce->value, "module"))
+		return 0;
+
+	has_name = has_minversion = 0;
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strlen(cep->name))
+		{
+			config_error("%s:%i: blank directive for require module { } block", cep->file->filename, cep->line_number);
+			errors++;
+			continue;
+		}
+
+		if (!cep->value || !strlen(cep->value))
+		{
+			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->name, "name"))
+		{
+			if (has_name)
+			{
+				config_error("%s:%i: duplicate %s for require module { } block", cep->file->filename, cep->line_number, cep->name);
+				continue;
+			}
+
+			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->value);
+				errors++;
+			}
+
+			// Let's be nice and let configrun handle adding this module to the list
+			has_name = 1;
+			continue;
+		}
+
+		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->file->filename, cep->line_number, cep->name);
+				errors++;
+				continue;
+			}
+			has_minversion = 1;
+			continue;
+		}
+
+		// 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->file->filename, cep->line_number, cep->name);
+		errors++;
+	}
+
+	if (!has_name)
+	{
+		config_error("%s:%i: missing required 'name' directive for require module { } block", ce->file->filename, ce->line_number);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int reqmods_configrun_require(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	Module *mod;
+	ReqMod *rmod;
+	char *name, *minversion;
+
+	if (strcmp(ce->value, "module"))
+		return 0;
+
+	name = minversion = NULL;
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "name"))
+		{
+			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->value);
+				continue;
+			}
+
+			name = cep->value;
+			continue;
+		}
+
+		if (!strcmp(cep->name, "min-version"))
+		{
+			minversion = cep->value;
+			continue;
+		}
+	}
+
+	// While technically an error, let's not kill the entire server over it
+	if (!name)
+		return 1;
+
+	rmod = safe_alloc(sizeof(ReqMod));
+	safe_strdup(rmod->name, name);
+	if (minversion)
+		safe_strdup(rmod->minversion, minversion);
+	AddListItem(rmod, ReqModList);
+	return 1;
+}
+
+CMD_FUNC(cmd_smod)
+{
+	char modflag, name[64], *version;
+	char buf[BUFSIZE];
+	char *tmp, *p, *modbuf;
+	Module *mod;
+	DenyMod *dmod;
+	int i;
+	int abort;
+
+	// A non-server client shouldn't really be possible here, but still :D
+	if (!MyConnect(client) || !IsServer(client) || BadPtr(parv[1]))
+		return;
+
+	// Module strings are passed as 1 space-delimited parameter
+	strlcpy(buf, parv[1], sizeof(buf));
+	abort = 0;
+	for (modbuf = strtoken(&tmp, buf, " "); modbuf; modbuf = strtoken(&tmp, NULL, " "))
+	{
+		/* The order of checks is:
+		 * 1: deny module { } -- SQUIT always
+		 * 2 (if module not loaded): require module { } -- SQUIT always
+		 * 3 (if module not loaded): warn, but only if MOD_OPT_GLOBAL
+		 * 4 (optional, if module loaded only): require module::min-version
+		 */
+		p = strchr(modbuf, ':');
+		if (!p)
+			continue; /* malformed request */
+		modflag = *modbuf; // Get the module flag (FIXME: parses only first letter atm)
+		modbuf = p+1;
+		strlcpy(name, modbuf, sizeof(name)); // Let's work on a copy of the param
+
+		version = strchr(name, ':');
+		if (!version)
+			continue; /* malformed request */
+		*version++ = '\0';
+
+		// Even if a denied module is only required locally, let's still prevent a server that uses it from linking in
+		if ((dmod = find_denymod_byname(name)))
+		{
+			// Send this particular notice to local opers only
+			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;
+		}
+
+		// Doing a strict check for the module being fully loaded so we can emit an alert in that case too :>
+		mod = find_modptr_byname(name, 1);
+		if (!mod)
+		{
+			/* Since only the server missing the module will report it, we need to broadcast the warning network-wide ;]
+			 * Obviously we won't take any real action if the module seems to be locally required only, except if it's marked as required
+			 */
+			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
+				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')
+			{
+				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;
+		}
+
+		// Further checks are only necessary for explicitly required mods
+		if (modflag != 'R')
+			continue;
+
+		// Module is loaded on both servers and the other end is require { }'ing a specific module version
+		// An explicit version was specified in require module { } but our module version is less than that
+		if (*version != '*' && strnatcasecmp(mod->header->version, version) < 0)
+		{
+			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)
+	{
+		exit_client_fmt(client, NULL, "Link aborted due to missing or banned modules (see previous errors)");
+		return;
+	}
+}
+
+int reqmods_hook_serverconnect(Client *client)
+{
+	/* This function simply dumps a list of modules and their version to the other server,
+	 * which will then run through the received list and check the names/versions
+	 */
+	char modflag;
+	char modbuf[64];
+	char *modversion;
+	/* Try to use a large buffer, but take into account the hostname, command, spaces, etc */
+	char sendbuf[BUFSIZE - HOSTLEN - 16];
+	Module *mod;
+	ReqMod *rmod;
+	size_t len, modlen;
+
+	/* Let's not have leaves directly connected to the hub send their module list to other *leaves* as well =]
+	 * Since the hub will introduce all servers currently linked to it, this hook is actually called for every separate node
+	 */
+	if (!MyConnect(client))
+		return HOOK_CONTINUE;
+
+	sendbuf[0] = '\0';
+	len = 0;
+
+	/* At this stage we don't care if a module isn't global (or not fully loaded), we'll dump all modules so we can properly deny
+	 * certain ones across the network
+	 * Also, the G flag is only used for modules that tag themselves as global, since we're keeping separate lists for require (R flag) and deny
+	 */
+	for (mod = Modules; mod; mod = mod->next)
+	{
+		modflag = SMOD_FLAG_LOCAL;
+		modversion = mod->header->version;
+
+		// require { }'d modules should be loaded on this server anyways, meaning we don't have to use a separate loop for those =]
+		if ((rmod = find_reqmod_byname(mod->header->name)))
+		{
+			// require module::min-version overrides the version found in the module's header
+			modflag = SMOD_FLAG_REQUIRED;
+			modversion = (rmod->minversion ? rmod->minversion : "*");
+		}
+
+		else if ((mod->options & MOD_OPT_GLOBAL))
+			modflag = SMOD_FLAG_GLOBAL;
+
+		ircsnprintf(modbuf, sizeof(modbuf), "%c:%s:%s", modflag, mod->header->name, modversion);
+		modlen = strlen(modbuf);
+		if (len + modlen + 2 > sizeof(sendbuf)) // Account for space and nullbyte, otherwise the last module entry might be cut off
+		{
+			// "Flush" current list =]
+			sendto_one(client, NULL, ":%s %s :%s", me.id, MSG_SMOD, sendbuf);
+			sendbuf[0] = '\0';
+			len = 0;
+		}
+
+		/* Maybe account for the space between modules, can't do this earlier because otherwise the ircsnprintf() would skip past the nullbyte
+		 * of the previous module (which in turn terminates the string prematurely)
+		 */
+		ircsnprintf(sendbuf + len, sizeof(sendbuf) - len, "%s%s", (len > 0 ? " " : ""), modbuf);
+		if (len)
+			len++;
+		len += modlen;
+	}
+
+	// May have something left
+	if (sendbuf[0])
+		sendto_one(client, NULL, ":%s %s :%s", me.id, MSG_SMOD, sendbuf);
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/restrict-commands.c b/ircd/src/modules/restrict-commands.c
@@ -0,0 +1,409 @@
+/*
+ * Restrict specific commands unless certain conditions have been met
+ * (C) Copyright 2019 Gottem 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 = {
+	"restrict-commands",
+	"1.0.2",
+	"Restrict specific commands unless certain conditions have been met",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+typedef struct RestrictedCommand RestrictedCommand;
+struct RestrictedCommand {
+	RestrictedCommand *prev, *next;
+	char *cmd;
+	char *conftag;
+	SecurityGroup *except;
+};
+
+typedef struct {
+	char *conftag;
+	char *cmd;
+} CmdMap;
+
+// Forward declarations
+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, 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
+static ModuleInfo ModInf;
+RestrictedCommand *RestrictedCommandList = NULL;
+CmdMap conf_cmdmaps[] = {
+	// These are special cases in which we can't override the command, so they are handled through hooks instead
+	{ "channel-message", "PRIVMSG" },
+	{ "channel-notice", "NOTICE" },
+	{ "private-message", "PRIVMSG" },
+	{ "private-notice", "NOTICE" },
+	{ NULL, NULL, }, // REQUIRED for the loop to properly work
+};
+
+MOD_TEST()
+{
+	memcpy(&ModInf, modinfo, modinfo->size);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rcmd_configtest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, rcmd_configrun);
+
+	// Due to the nature of PRIVMSG/NOTICE we're gonna need to hook into PRE_* stuff instead of using command overrides
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, -1000000, rcmd_can_send_to_channel);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, -1000000, rcmd_can_send_to_user);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	RestrictedCommand *rcmd, *next;
+	for (rcmd = RestrictedCommandList; rcmd; rcmd = next)
+	{
+		next = rcmd->next;
+		safe_free(rcmd->conftag);
+		safe_free(rcmd->cmd);
+		free_security_group(rcmd->except);
+		DelListItem(rcmd, RestrictedCommandList);
+		safe_free(rcmd);
+	}
+	RestrictedCommandList = NULL;
+	return MOD_SUCCESS;
+}
+
+const char *find_cmd_byconftag(const char *conftag) {
+	CmdMap *cmap;
+	for (cmap = conf_cmdmaps; cmap->conftag; cmap++)
+	{
+		if (!strcmp(cmap->conftag, conftag))
+			return cmap->cmd;
+	}
+	return NULL;
+}
+
+RestrictedCommand *find_restrictions_bycmd(const char *cmd) {
+	RestrictedCommand *rcmd;
+	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
+	{
+		if (!strcasecmp(rcmd->cmd, cmd))
+			return rcmd;
+	}
+	return NULL;
+}
+
+RestrictedCommand *find_restrictions_byconftag(const char *conftag) {
+	RestrictedCommand *rcmd;
+	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
+	{
+		if (rcmd->conftag && !strcmp(rcmd->conftag, conftag))
+			return rcmd;
+	}
+	return NULL;
+}
+
+int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	int warn_disable = 0;
+	ConfigEntry *cep, *cep2;
+
+	// We are only interested in set::restrict-commands
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "restrict-commands"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
+		{
+			if (!strcmp(cep2->name, "disable"))
+			{
+				config_warn("%s:%i: set::restrict-commands::%s: the 'disable' option has been removed.",
+				            cep2->file->filename, cep2->line_number, cep->name);
+				if (!warn_disable)
+				{
+					config_warn("Simply remove 'disable yes;' from the configuration file and "
+				                   "it will have the same effect without it (will disable the command).");
+					warn_disable = 1;
+				}
+				continue;
+			}
+
+			if (!strcmp(cep2->name, "except"))
+			{
+				test_match_block(cf, cep2, &errors);
+				continue;
+			}
+
+			if (!cep2->value)
+			{
+				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->name, "connect-delay"))
+			{
+				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->file->filename, cep2->line_number, cep->name);
+					errors++;
+				}
+				continue;
+			}
+
+			if (!strcmp(cep2->name, "exempt-identified"))
+				continue;
+
+			if (!strcmp(cep2->name, "exempt-webirc"))
+				continue;
+
+			if (!strcmp(cep2->name, "exempt-tls"))
+				continue;
+
+			if (!strcmp(cep2->name, "exempt-reputation-score"))
+			{
+				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->file->filename, cep2->line_number, cep->name);
+					errors++;
+				}
+				continue;
+			}
+
+			config_error("%s:%i: unknown directive set::restrict-commands::%s::%s", cep2->file->filename, cep2->line_number, cep->name, cep2->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep, *cep2;
+	const char *cmd, *conftag;
+	RestrictedCommand *rcmd;
+
+	// We are only interested in set::restrict-commands
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "restrict-commands"))
+		return 0;
+
+	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->name)))
+			conftag = cep->name;
+		else
+			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
+		if (!conftag)
+		{
+			// Let's hope nobody tries to unload the module for PRIVMSG/NOTICE :^)
+			if (!CommandExists(cmd))
+			{
+				config_warn("[restrict-commands] Command '%s' does not exist. Did you mistype? Or is the module providing it not loaded?", cmd);
+				continue;
+			}
+			if (find_restrictions_bycmd(cmd))
+			{
+				config_warn("[restrict-commands] Multiple set::restrict-commands items for command '%s'. "
+				            "Only one config block will be effective.",
+				            cmd);
+				continue;
+			}
+			if (!CommandOverrideAdd(ModInf.handle, cmd, 0, rcmd_override))
+			{
+				config_warn("[restrict-commands] Failed to add override for '%s' (NO RESTRICTIONS APPLY)", cmd);
+				continue;
+			}
+		}
+
+		rcmd = safe_alloc(sizeof(RestrictedCommand));
+		safe_strdup(rcmd->cmd, cmd);
+		safe_strdup(rcmd->conftag, conftag);
+		rcmd->except = safe_alloc(sizeof(SecurityGroup));
+
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
+		{
+			if (!strcmp(cep2->name, "except"))
+			{
+				conf_match_block(cf, cep2, &rcmd->except);
+				continue;
+			}
+
+			if (!cep2->value)
+				continue;
+
+			if (!strcmp(cep2->name, "connect-delay"))
+			{
+				rcmd->except->connect_time = config_checkval(cep2->value, CFG_TIME);
+				continue;
+			}
+
+			if (!strcmp(cep2->name, "exempt-identified"))
+			{
+				rcmd->except->identified = config_checkval(cep2->value, CFG_YESNO);
+				continue;
+			}
+			
+			if (!strcmp(cep2->name, "exempt-webirc"))
+			{
+				rcmd->except->webirc = config_checkval(cep2->value, CFG_YESNO);
+				continue;
+			}
+
+			if (!strcmp(cep2->name, "exempt-tls"))
+			{
+				rcmd->except->tls = config_checkval(cep2->value, CFG_YESNO);
+				continue;
+			}
+
+			if (!strcmp(cep2->name, "exempt-reputation-score"))
+			{
+				rcmd->except->reputation_score = atoi(cep2->value);
+				continue;
+			}
+		}
+		AddListItem(rcmd, RestrictedCommandList);
+	}
+
+	return 1;
+}
+
+int rcmd_canbypass(Client *client, RestrictedCommand *rcmd)
+{
+	if (!client || !rcmd)
+		return 1;
+	if (user_allowed_by_security_group(client, rcmd->except))
+		return 1;
+	return 0;
+}
+
+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;
+
+	return HOOK_CONTINUE;
+}
+
+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))
+		return HOOK_CONTINUE; /* bypass/exempt */
+
+	if (rcmd_block_message(client, *text, sendtype, errmsg, "user", (sendtype == SEND_TYPE_NOTICE ? "private-notice" : "private-message")))
+		return HOOK_DENY;
+
+	return HOOK_CONTINUE;
+}
+
+int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag)
+{
+	RestrictedCommand *rcmd;
+	static char errbuf[256];
+
+	// Let's allow non-local users, opers and U:Lines early =]
+	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
+		return 0;
+
+	rcmd = find_restrictions_byconftag(conftag);
+	if (rcmd && !rcmd_canbypass(client, rcmd))
+	{
+		int notice = (sendtype == SEND_TYPE_NOTICE ? 1 : 0); // temporary hack FIXME !!!
+		if (rcmd->except->connect_time)
+		{
+			ircsnprintf(errbuf, sizeof(errbuf),
+				    "You cannot send %ss to %ss until you've been connected for %ld seconds or more",
+				    (notice ? "notice" : "message"), display, rcmd->except->connect_time);
+		} else {
+			ircsnprintf(errbuf, sizeof(errbuf),
+				    "Sending of %ss to %ss been disabled by the network administrators",
+				    (notice ? "notice" : "message"), display);
+		}
+		*errmsg = errbuf;
+		return 1;
+	}
+
+	// No restrictions apply, process command as normal =]
+	return 0;
+}
+
+CMD_OVERRIDE_FUNC(rcmd_override)
+{
+	RestrictedCommand *rcmd;
+
+	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
+	{
+		CALL_NEXT_COMMAND_OVERRIDE();
+		return;
+	}
+
+	rcmd = find_restrictions_bycmd(ovr->command->cmd);
+	if (rcmd && !rcmd_canbypass(client, rcmd))
+	{
+		if (rcmd->except->connect_time)
+		{
+			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
+			               "%s :You must be connected for at least %ld seconds before you can use this command",
+			               ovr->command->cmd, rcmd->except->connect_time);
+		} else {
+			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
+			               "%s :This command is disabled by the network administrator",
+			               ovr->command->cmd);
+		}
+		return;
+	}
+
+	// No restrictions apply, process command as normal =]
+	CALL_NEXT_COMMAND_OVERRIDE();
+}
diff --git a/ircd/src/modules/rmtkl.c b/ircd/src/modules/rmtkl.c
@@ -0,0 +1,295 @@
+/*
+ * Easily remove *-Lines in bulk
+ * (C) Copyright 2019 Gottem 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 = {
+	"rmtkl",
+	"1.4",
+	"Adds /rmtkl command to easily remove *-Lines in bulk",
+	"Gottem and the UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+#define IsParam(x) (parc > (x) && !BadPtr(parv[(x)]))
+#define IsNotParam(x) (parc <= (x) || BadPtr(parv[(x)]))
+
+typedef struct {
+	int type;
+	char flag;
+	char *txt;
+	char *operpriv;
+} TKLType;
+
+static void dump_str(Client *client, const char **buf);
+static TKLType *find_TKLType_by_flag(char flag);
+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[] = {
+	{ TKL_KILL, 'k', "K-Line", "server-ban:kline:remove" },
+	{ TKL_ZAP, 'z',	"Z-Line", "server-ban:zline:local:remove" },
+	{ TKL_KILL | TKL_GLOBAL, 'G', "G-Line", "server-ban:gline:remove" },
+	{ TKL_ZAP | TKL_GLOBAL, 'Z', "Global Z-Line", "server-ban:zline:global:remove" },
+	{ TKL_SHUN | TKL_GLOBAL, 's', "Shun", "server-ban:shun:remove" },
+//	{ TKL_SPAMF | TKL_GLOBAL, 'F', "Global Spamfilter", "server-ban:spamfilter:remove" }, TODO: re-add spamfilter support
+	{ 0, 0, "Unknown *-Line", 0 },
+};
+
+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.",
+	"Syntax:",
+	"    \002/rmtkl\002 \037user@host\037 \037type\037 [\037comment\037] [\037-skipperm\037] [\037-silent\037]",
+	"The \037user@host\037 field is a wildcard mask to match the target of a ban.",
+	"The \037type\037 field may contain any number of the following characters:",
+	"    k, z, G, Z, s, F and *",
+	"    These correspond to (local) K-Line, (local) Z-Line, G-Line, Global Z-Line, (global) Shun and (global) Spamfilter",
+	"    (asterisk includes every type besides F)",
+	"The \037comment\037 field is also a wildcard mask to match the reason text of a ban. If specified, it must always",
+	"come \037before\037 the options starting with \002-\002.",
+	"Examples:",
+	"    - \002/rmtkl * *\002",
+	"        [remove \037all\037 supported TKLs except spamfilters]",
+	"    - \002/rmtkl *@*.mx GZ\002 * -skipperm",
+	"        [remove all Mexican G/Z-Lines while skipping over permanent ones]",
+/*	"    - \002/rmtkl * * *Zombie*\002",
+	"        [remove all non-spamfilter bans having \037Zombie\037 in the reason field]", TODO: re-add spamfilter support  */
+	"*** \002End of help\002 ***",
+	NULL
+};
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (CommandExists("RMTKL"))
+	{
+		config_error("Command RMTKL already exists");
+		return MOD_FAILED;
+	}
+	CommandAdd(modinfo->handle, "RMTKL", rmtkl, 5, CMD_USER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+static void dump_str(Client *client, const char **buf)
+{
+	if (!MyUser(client))
+		return;
+
+	// Using sendto_one() instead of sendnumericfmt() because the latter strips indentation and stuff ;]
+	for (; *buf != NULL; buf++)
+		sendto_one(client, NULL, ":%s %03d %s :%s", me.name, RPL_TEXT, client->name, *buf);
+
+	// Let user take 8 seconds to read it
+	add_fake_lag(client, 8000);
+}
+
+static TKLType *find_TKLType_by_flag(char flag)
+{
+	TKLType *t;
+	for (t = tkl_types; t->type; t++)
+		if (t->flag == flag)
+			break;
+	return t;
+}
+
+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, const char *uhmask, const char *commentmask, int skipperm, int silent)
+{
+	if (tkl->type != tkltype->type)
+		return 0;
+
+	// Let's not touch Q-Lines
+	if (tkl->type & TKL_NAME)
+		return 0;
+
+	/* Don't touch TKL's that were added through config */
+	if (tkl->flags & TKL_FLAG_CONFIG)
+		return 0;
+
+	if (TKLIsSpamfilter(tkl))
+	{
+#if 0
+//FIXME: re-add spamfilter support
+		// Is a spamfilter added through IRC, we can remove this if the "user" mask matches the reason
+		if (!match_simple(uhmask, tkl->reason))
+			return 0;
+#endif
+	} else
+	if (TKLIsServerBan(tkl))
+	{
+		if (!match_simple(uhmask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
+			return 0;
+
+		if (commentmask && !match_simple(commentmask, tkl->ptr.serverban->reason))
+			return 0;
+	} else
+		return 0;
+
+	if (skipperm && tkl->expire_at == 0)
+		return 0;
+
+	if (!silent)
+		sendnotice_tkl_del(client->name, tkl);
+
+	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
+
+	if (tkl->type & TKL_SHUN)
+		tkl_check_local_remove_shun(tkl);
+	tkl_del_line(tkl);
+	return 1;
+}
+
+CMD_FUNC(rmtkl)
+{
+	TKL *tkl, *next;
+	TKLType *tkltype;
+	const char *types, *uhmask, *commentmask, *p;
+	char tklchar;
+	int tklindex, tklindex2, skipperm, silent;
+	unsigned int count;
+	char broadcast[BUFSIZE];
+
+	if (!IsULine(client) && !IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (IsNotParam(1))
+	{
+		dump_str(client, rmtkl_help);
+		return;
+	}
+
+	if (IsNotParam(2))
+	{
+		sendnotice(client, "Not enough parameters. Type /RMTKL for help.");
+		return;
+	}
+
+	uhmask = parv[1];
+	types = parv[2];
+	commentmask = NULL;
+	skipperm = 0;
+	silent = 0;
+	count = 0;
+	snprintf(broadcast, sizeof(broadcast), ":%s RMTKL %s %s", client->name, types, uhmask);
+
+	// Check for optionals
+	if (IsParam(3))
+	{
+		// Comment mask, if specified, always goes third
+		if (*parv[3] != '-')
+			commentmask = parv[3];
+		else
+			rmtkl_check_options(parv[3], &skipperm, &silent);
+		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[3]);
+	}
+	if (IsParam(4))
+	{
+		rmtkl_check_options(parv[4], &skipperm, &silent);
+		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[4]);
+	}
+	if (IsParam(5))
+	{
+		rmtkl_check_options(parv[5], &skipperm, &silent);
+		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[5]);
+	}
+
+	// Wildcard resolves to everything but 'F', since spamfilters are a bit special
+	if (strchr(types, '*'))
+		types = "kzGZs";
+
+	// Make sure the oper actually has the privileges to remove the *-Lines he wants
+	if (!IsULine(client))
+	{
+		for (p = types; *p; p++)
+		{
+			tkltype = find_TKLType_by_flag(*p);
+			if (!tkltype->type)
+				continue;
+
+			if (!ValidatePermissionsForPath(tkltype->operpriv, client, NULL, NULL, NULL))
+			{
+				sendnumeric(client, ERR_NOPRIVILEGES);
+				return;
+			}
+		}
+	}
+
+	// Broadcast the command to other servers *before* we proceed with removal
+	sendto_server(NULL, 0, 0, NULL, "%s", broadcast);
+
+	// Loop over all supported types
+	for (tkltype = tkl_types; tkltype->type; tkltype++) {
+		if (!strchr(types, tkltype->flag))
+			continue;
+
+		// Loop over all TKL entries, first try the ones in the hash table
+		tklchar = tkl_typetochar(tkltype->type);
+		tklindex = tkl_ip_hash_type(tklchar);
+		if (tklindex >= 0)
+		{
+			for (tklindex2 = 0; tklindex2 < TKLIPHASHLEN2; tklindex2++)
+			{
+				for (tkl = tklines_ip_hash[tklindex][tklindex2]; tkl; tkl = next)
+				{
+					next = tkl->next;
+					count += rmtkl_tryremove(client, tkltype, tkl, uhmask, commentmask, skipperm, silent);
+				}
+			}
+		}
+
+		// Then the regular *-Lines (not an else because certain TKLs might have a hash as well as a plain linked list)
+		tklindex = tkl_hash(tklchar);
+		for (tkl = tklines[tklindex]; tkl; tkl = next)
+		{
+			next = tkl->next;
+			count += rmtkl_tryremove(client, tkltype, tkl, uhmask, commentmask, skipperm, silent);
+		}
+	}
+
+	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/ircd/src/modules/rpc/Makefile.in b/ircd/src/modules/rpc/Makefile.in
@@ -0,0 +1,55 @@
+#************************************************************************
+#*   IRC - Internet Relay Chat, src/modules/rpc/Makefile
+#*   Copyright (C) Carsten V. Munk 2001 & The UnrealIRCd Team
+#*
+#*   This program is free software; you can redistribute it and/or modify
+#*   it under the terms of the GNU General Public License as published by
+#*   the Free Software Foundation; either version 1, or (at your option)
+#*   any later version.
+#*
+#*   This program is distributed in the hope that it will be useful,
+#*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#*   GNU General Public License for more details.
+#*
+#*   You should have received a copy of the GNU General Public License
+#*   along with this program; if not, write to the Free Software
+#*   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#*/
+
+CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
+
+INCLUDES = ../../include/channel.h \
+	../../include/common.h ../../include/config.h ../../include/dbuf.h \
+	../../include/dynconf.h ../../include/fdlist.h ../../include/h.h \
+	../../include/ircsprintf.h \
+	../../include/license.h \
+	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
+	../../include/numeric.h ../../include/dns.h \
+	../../include/resource.h ../../include/setup.h \
+	../../include/struct.h ../../include/sys.h \
+	../../include/types.h \
+	../../include/version.h ../../include/whowas.h
+
+R_MODULES= \
+	rpc.so stats.so user.so server.so channel.so server_ban.so \
+	server_ban_exception.so name_ban.so spamfilter.so \
+	log.so whowas.so
+
+MODULES=$(R_MODULES)
+MODULEFLAGS=@MODULEFLAGS@
+RM=@RM@
+
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
+all: build
+
+build: $(MODULES)
+
+clean:
+	$(RM) -f *.o *.so *~ core
+
+%.so: %.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o $@ $<
diff --git a/ircd/src/modules/rpc/channel.c b/ircd/src/modules/rpc/channel.c
@@ -0,0 +1,230 @@
+/* channel.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/channel",
+	"1.0.5",
+	"channel.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+void rpc_channel_list(Client *client, json_t *request, json_t *params);
+void rpc_channel_get(Client *client, json_t *request, json_t *params);
+void rpc_channel_set_mode(Client *client, json_t *request, json_t *params);
+void rpc_channel_set_topic(Client *client, json_t *request, json_t *params);
+void rpc_channel_kick(Client *client, json_t *request, json_t *params);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_channel_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_channel_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.set_mode";
+	r.call = rpc_channel_set_mode;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.set_topic";
+	r.call = rpc_channel_set_topic;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.kick";
+	r.call = rpc_channel_kick;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void rpc_channel_list(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *list, *item;
+	Channel *channel;
+	int details;
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 1);
+	if (details >= 5)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of >=5 is not allowed in this call");
+		return;
+	}
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (channel = channels; channel; channel=channel->nextch)
+	{
+		item = json_object();
+		json_expand_channel(item, NULL, channel, details);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_get(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname;
+	Channel *channel;
+	int details;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 3);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_channel(result, "channel", channel, details);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_set_mode(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname, *modes, *parameters;
+	MessageTag *mtags = NULL;
+	Channel *channel;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	REQUIRE_PARAM_STRING("modes", modes);
+	REQUIRE_PARAM_STRING("parameters", parameters);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+	set_channel_mode(channel, mtags, modes, parameters);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_set_topic(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname, *topic, *set_by=NULL, *str;
+	Channel *channel;
+	time_t set_at = 0;
+	MessageTag *mtags = NULL;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	REQUIRE_PARAM_STRING("topic", topic);
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	OPTIONAL_PARAM_STRING("set_at", str);
+	if (str)
+		set_at = server_time_to_unix_time(str);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+	set_channel_topic(&me, channel, mtags, topic, set_by, set_at);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_kick(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname, *nick, *reason;
+	MessageTag *mtags = NULL;
+	Channel *channel;
+	Client *acptr;
+	time_t set_at = 0;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+	kick_user(mtags, channel, &me, acptr, reason);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success
+	 * TODO: actually we can do a find_member() check and such to see if the user is kicked!
+	 * that is, assuming single channel ;)
+	 */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/log.c b/ircd/src/modules/rpc/log.c
@@ -0,0 +1,139 @@
+/* log.* RPC calls
+ * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/log",
+	"1.0.0",
+	"log.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params);
+void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params);
+int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "log.subscribe";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_log_hook_subscribe;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/log] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "log.unsubscribe";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_log_hook_unsubscribe;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/log] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_LOG, 0, rpc_log_hook);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params)
+{
+	json_t *result;
+	json_t *sources;
+	size_t index;
+	json_t *value;
+	const char *str;
+	LogSource *s;
+
+	sources = json_object_get(params, "sources");
+	if (!sources || !json_is_array(sources))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "sources");
+		return;
+	}
+
+	/* Erase the old subscriptions first */
+	free_log_sources(client->rpc->log_sources);
+	client->rpc->log_sources = NULL;
+
+	/* Add subscriptions... */
+	json_array_foreach(sources, index, value)
+	{
+		str = json_get_value(value);
+		if (!str)
+			continue;
+
+		s = add_log_source(str);
+		AddListItem(s, client->rpc->log_sources);
+	}
+
+	result = json_boolean(1);
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/** log.unsubscribe: unsubscribe from all log messages */
+void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params)
+{
+	json_t *result;
+
+	free_log_sources(client->rpc->log_sources);
+	client->rpc->log_sources = NULL;
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf)
+{
+	Client *client;
+	json_t *request = NULL;
+
+	if (!strcmp(subsystem, "rawtraffic"))
+		return 0;
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (IsRPC(client) && client->rpc->log_sources &&
+		    log_sources_match(client->rpc->log_sources, loglevel, subsystem, event_id, 0))
+		{
+			if (request == NULL)
+			{
+				/* Lazy initalization */
+				request = json_object();
+				json_object_set_new(request, "method", json_string_unreal("log.event"));
+			}
+			rpc_response(client, request, json);
+		}
+	}
+
+	if (request)
+		json_decref(request);
+
+	return 0;
+}
diff --git a/ircd/src/modules/rpc/name_ban.c b/ircd/src/modules/rpc/name_ban.c
@@ -0,0 +1,241 @@
+/* name_ban.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/name_ban",
+	"1.0.1",
+	"name_ban.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_name_ban_list);
+RPC_CALL_FUNC(rpc_name_ban_get);
+RPC_CALL_FUNC(rpc_name_ban_del);
+RPC_CALL_FUNC(rpc_name_ban_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "name_ban.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_name_ban_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "name_ban.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_name_ban_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "name_ban.del";
+	r.call = rpc_name_ban_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "name_ban.add";
+	r.call = rpc_name_ban_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_name_ban_list)
+{
+	json_t *result, *list, *item;
+	int index;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsNameBan(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+TKL *my_find_tkl_nameban(const char *name)
+{
+	TKL *tkl;
+
+	for (tkl = tklines[tkl_hash('Q')]; tkl; tkl = tkl->next)
+	{
+		if (!TKLIsNameBan(tkl))
+			continue;
+		if (!strcasecmp(name, tkl->ptr.nameban->name))
+			return tkl;
+	}
+	return NULL;
+}
+
+RPC_CALL_FUNC(rpc_name_ban_get)
+{
+	json_t *result, *list, *item;
+	const char *name;
+	TKL *tkl;
+
+	REQUIRE_PARAM_STRING("name", name);
+
+	if (!(tkl = my_find_tkl_nameban(name)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_name_ban_del)
+{
+	json_t *result, *list, *item;
+	const char *name;
+	const char *set_by;
+	TKL *tkl;
+	const char *tkllayer[7];
+
+	REQUIRE_PARAM_STRING("name", name);
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if (!(tkl = my_find_tkl_nameban(name)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	tkllayer[0] = NULL;
+	tkllayer[1] = "-";
+	tkllayer[2] = "Q";
+	tkllayer[3] = "*";
+	tkllayer[4] = name;
+	tkllayer[5] = set_by;
+	tkllayer[6] = NULL;
+	cmd_tkl(&me, NULL, 6, tkllayer);
+
+	if (!my_find_tkl_nameban(name))
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based ban.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+	}
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_name_ban_add)
+{
+	json_t *result, *list, *item;
+	const char *name;
+	const char *str;
+	const char *reason;
+	const char *set_by;
+	time_t tkl_expire_at;
+	time_t tkl_set_at = TStime();
+	TKL *tkl;
+
+	REQUIRE_PARAM_STRING("name", name);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	/* Duration / expiry time */
+	if ((str = json_object_get_string(params, "duration_string")))
+	{
+		tkl_expire_at = config_checkval(str, CFG_TIME);
+		if (tkl_expire_at > 0)
+			tkl_expire_at = TStime() + tkl_expire_at;
+	} else
+	if ((str = json_object_get_string(params, "expire_at")))
+	{
+		tkl_expire_at = server_time_to_unix_time(str);
+	} else
+	{
+		/* Never expire */
+		tkl_expire_at = 0;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
+		return;
+	}
+
+	if (my_find_tkl_nameban(name))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban already exists");
+		return;
+	}
+
+	tkl = tkl_add_nameban(TKL_NAME|TKL_GLOBAL, name, 0, reason,
+	                      set_by, tkl_expire_at, tkl_set_at,
+	                      0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/rpc.c b/ircd/src/modules/rpc/rpc.c
@@ -0,0 +1,1922 @@
+/*
+ * RPC module - for remote management of UnrealIRCd
+ * (C)Copyright 2022 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+   
+#include "unrealircd.h"
+#include "dns.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"rpc/rpc",
+	"1.0.4",
+	"RPC module for remote management",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/** Maximum length of an rpc-user THIS { }.
+ * As we use the "RPC:" prefix it is nicklen minus that.
+ */
+#define RPCUSERLEN (NICKLEN-4)
+
+/** Timers can be minimum every <this> msec */
+#define RPC_MINIMUM_TIMER_MSEC 250
+
+/* Structs */
+typedef struct RPCUser RPCUser;
+struct RPCUser {
+	RPCUser *prev, *next;
+	SecurityGroup *match;
+	char *name;
+	AuthConfig *auth;
+};
+
+typedef struct RRPC RRPC;
+struct RRPC {
+	RRPC *prev, *next;
+	int request;
+	char source[IDLEN+1];
+	char destination[IDLEN+1];
+	char *requestid;
+	dbuf data;
+};
+
+typedef struct OutstandingRRPC OutstandingRRPC;
+struct OutstandingRRPC {
+	OutstandingRRPC *prev, *next;
+	time_t sent;
+	char source[IDLEN+1];
+	char destination[IDLEN+1];
+	char *requestid;
+};
+
+typedef struct RPCTimer RPCTimer;
+struct RPCTimer {
+	RPCTimer *prev, *next;
+	long every_msec;
+	Client *client;
+	char *timer_id;
+	json_t *request;
+	struct timeval last_run;
+};
+
+/* Forward declarations */
+int rpc_config_test_listen(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int rpc_config_run_ex_listen(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
+int rpc_config_test_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int rpc_config_run_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type);
+int rpc_client_accept(Client *client);
+int rpc_pre_local_handshake_timeout(Client *client, const char **comment);
+void rpc_client_handshake_unix_socket(Client *client);
+void rpc_client_handshake_web(Client *client);
+int rpc_handle_webrequest(Client *client, WebRequest *web);
+int rpc_handle_webrequest_websocket(Client *client, WebRequest *web);
+int rpc_websocket_handshake_send_response(Client *client);
+int rpc_handle_webrequest_data(Client *client, WebRequest *web, const char *buf, int len);
+int rpc_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
+int rpc_packet_in_websocket(Client *client, char *readbuf, int length);
+int rpc_packet_in_unix_socket(Client *client, const char *readbuf, int *length);
+void rpc_call_text(Client *client, const char *buf, int len);
+void rpc_call(Client *client, json_t *request);
+void _rpc_response(Client *client, json_t *request, json_t *result);
+void _rpc_error(Client *client, json_t *request, JsonRpcError error_code, const char *error_message);
+void _rpc_error_fmt(Client *client, json_t *request, JsonRpcError error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5)));
+void _rpc_send_request_to_remote(Client *source, Client *target, json_t *request);
+void _rpc_send_response_to_remote(Client *source, Client *target, json_t *response);
+int _rrpc_supported_simple(Client *target, char **problem_server);
+int _rrpc_supported(Client *target, const char *module, const char *minimum_version, char **problem_server);
+int rpc_handle_auth(Client *client, WebRequest *web);
+int rpc_parse_auth_basic_auth(Client *client, WebRequest *web, char **username, char **password);
+int rpc_parse_auth_uri(Client *client, WebRequest *web, char **username, char **password);
+RPC_CALL_FUNC(rpc_rpc_info);
+RPC_CALL_FUNC(rpc_rpc_set_issuer);
+RPC_CALL_FUNC(rpc_rpc_add_timer);
+RPC_CALL_FUNC(rpc_rpc_del_timer);
+CMD_FUNC(cmd_rrpc);
+EVENT(rpc_remote_timeout);
+EVENT(rpc_do_timers);
+json_t *rrpc_data(RRPC *r);
+void free_rrpc_list(ModData *m);
+void free_outstanding_rrpc_list(ModData *m);
+void free_rpc_timer(RPCTimer *r);
+void free_rpc_timer_list(ModData *m);
+void rpc_call_remote(RRPC *r);
+void rpc_response_remote(RRPC *r);
+int rpc_handle_free_client(Client *client);
+int rpc_handle_server_quit(Client *client, MessageTag *mtags);
+int rpc_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child);
+const char *rrpc_md_serialize(ModData *m);
+void rrpc_md_unserialize(const char *str, ModData *m);
+void rrpc_md_free(ModData *m);
+
+/* Macros */
+#define RPC_PORT(client)  ((client->local && client->local->listener) ? client->local->listener->rpc_options : 0)
+#define WSU(client)     ((WebSocketUser *)moddata_client(client, websocket_md).ptr)
+
+/* Global variables */
+ModDataInfo *websocket_md = NULL; /* (imported) */
+RPCUser *rpcusers = NULL;
+RRPC *rrpc_list = NULL;
+OutstandingRRPC *outstanding_rrpc_list = NULL;
+RPCTimer *rpc_timer_list = NULL;
+ModDataInfo *rrpc_md;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test_listen);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test_rpc_user);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_RESPONSE, _rpc_response);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR, _rpc_error);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR_FMT, TO_VOIDFUNC(_rpc_error_fmt));
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_SEND_REQUEST_TO_REMOTE, _rpc_send_request_to_remote);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_SEND_RESPONSE_TO_REMOTE, _rpc_send_response_to_remote);
+	EfunctionAdd(modinfo->handle, EFUNC_RRPC_SUPPORTED, _rrpc_supported);
+	EfunctionAdd(modinfo->handle, EFUNC_RRPC_SUPPORTED_SIMPLE, _rrpc_supported_simple);
+
+	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT); /* can be NULL */
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, rpc_config_run_ex_listen);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, rpc_config_run_rpc_user);
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, -5000, rpc_client_accept);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT, 0, rpc_pre_local_handshake_timeout);
+	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, rpc_packet_in_unix_socket);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, rpc_handle_server_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_FREE_CLIENT, 0, rpc_handle_free_client);
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, 0, rpc_json_expand_client_server);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.info";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_info;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.info] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.set_issuer";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_set_issuer;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.set_issuer] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.add_timer";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_add_timer;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.add_timer] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.del_timer";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_del_timer;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.del_timer] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "rrpc";
+	mreq.type = MODDATATYPE_CLIENT;
+	mreq.serialize = rrpc_md_serialize;
+	mreq.unserialize = rrpc_md_unserialize;
+	mreq.free = rrpc_md_free;
+	mreq.sync = 1;
+	mreq.self_write = 1;
+	rrpc_md = ModDataAdd(modinfo->handle, mreq);
+	if (!rrpc_md)
+	{
+		config_error("[rpc/rpc] Unable to ModDataAdd() -- too many 3rd party modules loaded perhaps?");
+		abort();
+	}
+
+	LoadPersistentPointer(modinfo, rrpc_list, free_rrpc_list);
+	LoadPersistentPointer(modinfo, outstanding_rrpc_list, free_outstanding_rrpc_list);
+	LoadPersistentPointer(modinfo, rpc_timer_list, free_rpc_timer_list);
+
+	CommandAdd(modinfo->handle, "RRPC", cmd_rrpc, MAXPARA, CMD_SERVER);
+
+	EventAdd(modinfo->handle, "rpc_remote_timeout", rpc_remote_timeout, NULL, 1000, 0);
+	EventAdd(modinfo->handle, "rpc_do_timers", rpc_do_timers, NULL, RPC_MINIMUM_TIMER_MSEC, 0);
+
+	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
+
+	return MOD_SUCCESS;
+}
+
+#define MYRRPCMODULES		me.moddata[rrpc_md->slot].ptr
+#define RRPCMODULES(client)	((NameValuePrioList *)moddata_client(client, rrpc_md).ptr)
+
+void rpc_do_moddata(void)
+{
+	Module *m;
+
+	free_nvplist(MYRRPCMODULES);
+	MYRRPCMODULES = NULL;
+
+	for (m = Modules; m; m = m->next)
+		if (!strncmp(m->header->name, "rpc/", 4))
+			add_nvplist((NameValuePrioList **)&MYRRPCMODULES, 0, m->header->name + 4, m->header->version);
+}
+
+MOD_LOAD()
+{
+	rpc_do_moddata();
+	return MOD_SUCCESS;
+}
+
+void free_config(void)
+{
+	RPCUser *e, *e_next;
+	for (e = rpcusers; e; e = e_next)
+	{
+		e_next = e->next;
+		safe_free(e->name);
+		free_security_group(e->match);
+		Auth_FreeAuthConfig(e->auth);
+		safe_free(e);
+	}
+	rpcusers = NULL;
+}
+
+MOD_UNLOAD()
+{
+	free_config();
+	SavePersistentPointer(modinfo, rrpc_list);
+	SavePersistentPointer(modinfo, outstanding_rrpc_list);
+	SavePersistentPointer(modinfo, rpc_timer_list);
+	return MOD_SUCCESS;
+}
+
+int rpc_config_test_listen(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	int ext = 0;
+	ConfigEntry *cep;
+
+	if (type != CONFIG_LISTEN_OPTIONS)
+		return 0;
+
+	/* We are only interested in listen::options::rpc.. */
+	if (!ce || !ce->name || strcmp(ce->name, "rpc"))
+		return 0;
+
+	/* No options atm */
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int rpc_config_run_ex_listen(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
+{
+	ConfigEntry *cep, *cepp;
+	ConfigItem_listen *l;
+
+	if (type != CONFIG_LISTEN_OPTIONS)
+		return 0;
+
+	/* We are only interrested in listen::options::rpc.. */
+	if (!ce || !ce->name || strcmp(ce->name, "rpc"))
+		return 0;
+
+	l = (ConfigItem_listen *)ptr;
+	l->options |= LISTENER_NO_CHECK_CONNECT_FLOOD;
+	if (l->socket_type == SOCKET_TYPE_UNIX)
+	{
+		l->start_handshake = rpc_client_handshake_unix_socket;
+	} else {
+		l->options |= LISTENER_TLS;
+		l->start_handshake = rpc_client_handshake_web;
+		l->webserver = safe_alloc(sizeof(WebServer));
+		l->webserver->handle_request = rpc_handle_webrequest;
+		l->webserver->handle_body = rpc_handle_webrequest_data;
+	}
+	l->rpc_options = 1;
+
+	return 1;
+}
+
+/** Valid name for rpc-user THISNAME { } ? */
+static int valid_rpc_user_name(const char *str)
+{
+	const char *p;
+
+	if (strlen(str) > RPCUSERLEN)
+		return 0;
+
+	for (p = str; *p; p++)
+		if (!isalnum(*p) && !strchr("_-", *p))
+			return 0;
+
+	return 1;
+}
+
+int rpc_config_test_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	char has_match = 1, has_password = 1;
+	ConfigEntry *cep;
+
+	/* We are only interested in rpc-user { } */
+	if ((type != CONFIG_MAIN) || !ce || !ce->name || strcmp(ce->name, "rpc-user"))
+		return 0;
+
+	if (!ce->value)
+	{
+		config_error("%s:%d: rpc-user block needs to have a name, eg: rpc-user apiuser { }",
+		             ce->file->filename, ce->line_number);
+		*errs = 1;
+		return -1; /* quick return */
+	}
+
+	if (!valid_rpc_user_name(ce->value))
+	{
+		config_error("%s:%d: rpc-user block has invalid name '%s'. "
+		             "Can be max %d long and may only contain a-z, A-Z, 0-9, - and _.",
+		             ce->file->filename, ce->line_number,
+		             ce->value, RPCUSERLEN);
+		errors++;
+	}
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "match"))
+		{
+			has_match = 1;
+			test_match_block(cf, cep, &errors);
+		} else
+		if (!strcmp(cep->name, "password"))
+		{
+			has_password = 1;
+			if (Auth_CheckError(cep) < 0)
+				errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int rpc_config_run_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	RPCUser *e;
+
+	/* We are only interested in rpc-user { } */
+	if ((type != CONFIG_MAIN) || !ce || !ce->name || strcmp(ce->name, "rpc-user"))
+		return 0;
+
+	e = safe_alloc(sizeof(RPCUser));
+	safe_strdup(e->name, ce->value);
+	AddListItem(e, rpcusers);
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "match"))
+		{
+			conf_match_block(cf, cep, &e->match);
+		} else
+		if (!strcmp(cep->name, "password"))
+		{
+			e->auth = AuthBlockToAuthConfig(cep);
+		}
+	}
+	return 1;
+}
+
+/** Incoming HTTP request: delegate it to websocket handler or HTTP POST */
+int rpc_handle_webrequest(Client *client, WebRequest *web)
+{
+	if (!rpc_handle_auth(client, web))
+		return 0; /* rejected */
+
+	if (get_nvplist(web->headers, "Sec-WebSocket-Key"))
+		return rpc_handle_webrequest_websocket(client, web);
+
+	if (!strcmp(web->uri, "/api"))
+	{
+		if (web->method != HTTP_METHOD_POST)
+		{
+			webserver_send_response(client, 200, "To use the UnrealIRCd RPC API you need to make a POST request. See https://www.unrealircd.org/docs/RPC\n");
+			return 0;
+		}
+		webserver_send_response(client, 200, NULL); /* continue.. */
+		return 1; /* accept */
+	}
+
+	webserver_send_response(client, 404, "Page not found.\n");
+	return 0;
+}
+
+/** Handle HTTP request - websockets handshake.
+ */
+int rpc_handle_webrequest_websocket(Client *client, WebRequest *web)
+{
+	NameValuePrioList *r;
+	const char *value;
+
+	if (!websocket_md)
+	{
+		webserver_send_response(client, 405, "Websockets are disabled on this server (module 'websocket_common' not loaded).\n");
+		return 0;
+	}
+
+	/* Allocate a new WebSocketUser struct for this session */
+	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
+	/* ...and set the default protocol (text or binary) */
+	WSU(client)->type = WEBSOCKET_TYPE_TEXT;
+
+	value = get_nvplist(web->headers, "Sec-WebSocket-Key");
+	if (strchr(value, ':'))
+	{
+		/* This would cause unserialization issues. Should be base64 anyway */
+		webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
+		return 0; // FIXME: 0 here, -1 in the other, what is it ???
+	}
+	safe_strdup(WSU(client)->handshake_key, value);
+
+	rpc_websocket_handshake_send_response(client);
+	return 1; /* ACCEPT */
+}
+
+/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
+int rpc_websocket_handshake_send_response(Client *client)
+{
+	char buf[512], hashbuf[64];
+	char sha1out[20]; /* 160 bits */
+
+	WSU(client)->handshake_completed = 1;
+
+	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
+	sha1hash_binary(sha1out, buf, strlen(buf));
+	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
+
+	snprintf(buf, sizeof(buf),
+	         "HTTP/1.1 101 Switching Protocols\r\n"
+	         "Upgrade: websocket\r\n"
+	         "Connection: Upgrade\r\n"
+	         "Sec-WebSocket-Accept: %s\r\n\r\n",
+	         hashbuf);
+
+	/* Caution: we bypass sendQ flood checking by doing it this way.
+	 * Risk is minimal, though, as we only permit limited text only
+	 * once per session.
+	 */
+	dbuf_put(&client->local->sendQ, buf, strlen(buf));
+	send_queued(client);
+
+	return 0;
+}
+
+int rpc_handle_webrequest_data(Client *client, WebRequest *web, const char *buf, int len)
+{
+	if (WSU(client))
+	{
+		/* Websocket user */
+		return rpc_handle_body_websocket(client, web, buf, len);
+	}
+
+	/* We only handle POST to /api -- reject all the rest */
+	if (strcmp(web->uri, "/api") || (web->method != HTTP_METHOD_POST))
+	{
+		webserver_send_response(client, 404, "Page not found\n");
+		return 0;
+	}
+
+	// NB: content_length
+	// NB: chunked transfers?
+	if (!webserver_handle_body(client, web, buf, len))
+	{
+		webserver_send_response(client, 400, "Error handling POST body data\n");
+		return 0;
+	}
+
+
+	if (web->request_body_complete)
+	{
+		if (!web->request_buffer)
+		{
+			webserver_send_response(client, 500, "Error while processing POST body data\n");
+			return 0;
+		}
+		//config_status("GOT: '%s'", buf);
+		rpc_call_text(client, web->request_buffer, web->request_buffer_size);
+		send_queued(client);
+		webserver_close_client(client);
+	}
+
+	return 0;
+}
+
+int rpc_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
+{
+	return websocket_handle_websocket(client, web, readbuf2, length2, rpc_packet_in_websocket);
+}
+
+int rpc_packet_in_websocket(Client *client, char *readbuf, int length)
+{
+	rpc_call_text(client, readbuf, length);
+	return 0; /* and if dead?? */
+}
+
+int rpc_packet_in_unix_socket(Client *client, const char *readbuf, int *length)
+{
+	char buf[READBUFSIZE];
+
+	if (!RPC_PORT(client) || !(client->local->listener->socket_type == SOCKET_TYPE_UNIX) || (*length <= 0))
+		return 1; /* Not for us */
+
+	dbuf_put(&client->local->recvQ, readbuf, *length);
+
+	while (DBufLength(&client->local->recvQ))
+	{
+		int len = dbuf_getmsg(&client->local->recvQ, buf);
+		if (len <= 0)
+			break;
+		rpc_call_text(client, buf, len);
+		if (IsDead(client))
+			break;
+	}
+
+	return 0;
+}
+
+void rpc_close(Client *client)
+{
+	send_queued(client);
+
+	/* May not be a web request actually, but this works: */
+	webserver_close_client(client);
+}
+
+/** Handle the RPC request: input is a buffer with a certain length.
+ * This calls rpc_call()
+ */
+void rpc_call_text(Client *client, const char *readbuf, int len)
+{
+	json_t *request = NULL;
+	json_error_t jerr;
+#if JANSSON_VERSION_HEX >= 0x020100
+	const char *buf = readbuf;
+	request = json_loadb(buf, len, JSON_REJECT_DUPLICATES, &jerr);
+#else
+	char buf[2048];
+
+	*buf = '\0';
+	strlncpy(buf, readbuf, sizeof(buf), len);
+
+	request = json_loads(buf, JSON_REJECT_DUPLICATES, &jerr);
+#endif
+	if (!request)
+	{
+		unreal_log(ULOG_INFO, "rpc", "RPC_INVALID_JSON", client,
+		           "Received unparsable JSON request from $client",
+		           log_data_string("json_incoming", buf));
+		rpc_error(client, NULL, JSON_RPC_ERROR_PARSE_ERROR, "Unparsable JSON data");
+		/* This is a fatal error */
+		rpc_close(client);
+		return;
+	}
+	rpc_call(client, request);
+	json_decref(request);
+}
+
+void rpc_sendto(Client *client, const char *buf, int len)
+{
+	if (IsDead(client))
+		return;
+	if (MyConnect(client) && IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
+	{
+		/* Websocket */
+		int utf8bufsize = len*2 + 16;
+		char *utf8buf = safe_alloc(utf8bufsize);
+		char *newbuf = unrl_utf8_make_valid(buf, utf8buf, utf8bufsize, 1);
+		int newlen = strlen(newbuf);
+		int ws_sendbufsize = newlen + 64 + ((newlen / 1024) * 64); // some random magic
+		char *ws_sendbuf = safe_alloc(ws_sendbufsize);
+		websocket_create_packet_ex(WSOP_TEXT, &newbuf, &newlen, ws_sendbuf, ws_sendbufsize);
+		dbuf_put(&client->local->sendQ, newbuf, newlen);
+		safe_free(ws_sendbuf);
+		safe_free(utf8buf);
+	} else {
+		/* Unix domain socket or HTTP */
+		dbuf_put(&client->local->sendQ, buf, len);
+		dbuf_put(&client->local->sendQ, "\n", 1);
+	}
+	mark_data_to_send(client);
+}
+
+void _rpc_error(Client *client, json_t *request, JsonRpcError error_code, const char *error_message)
+{
+	/* Careful, we are in the "error" routine, so everything can be NULL */
+	const char *method = NULL;
+	json_t *id = NULL;
+	char *json_serialized;
+	json_t *error;
+
+	/* Start a new object for the error response */
+	json_t *j = json_object();
+
+	if (request)
+	{
+		method = json_object_get_string(request, "method");
+		id = json_object_get(request, "id");
+	}
+
+	json_object_set_new(j, "jsonrpc", json_string_unreal("2.0"));
+	if (method)
+		json_object_set_new(j, "method", json_string_unreal(method));
+	if (id)
+		json_object_set(j, "id", id);
+
+	error = json_object();
+	json_object_set_new(j, "error", error);
+	json_object_set_new(error, "code", json_integer(error_code));
+	json_object_set_new(error, "message", json_string_unreal(error_message));
+
+	unreal_log(ULOG_INFO, "rpc", "RPC_CALL_ERROR", client,
+	           "[rpc] Client $client: RPC call $method",
+	           log_data_string("method", method ? method : "<invalid>"));
+
+
+	json_serialized = json_dumps(j, 0);
+	if (!json_serialized)
+	{
+		unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_ERROR_SERIALIZE_FAILED", NULL,
+		           "[BUG] rpc_error() failed to serialize response "
+		           "for request from $client ($method)",
+		           log_data_string("method", method));
+		json_decref(j);
+		return;
+	}
+
+	if (MyConnect(client))
+		rpc_sendto(client, json_serialized, strlen(json_serialized));
+	else
+		rpc_send_response_to_remote(&me, client, j);
+
+#ifdef DEBUGMODE
+	unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
+		   "[rpc] Client $client: RPC result error: $response",
+		   log_data_string("response", json_serialized));
+#endif
+	json_decref(j);
+	safe_free(json_serialized);
+}
+
+void _rpc_error_fmt(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...)
+{
+	char buf[512];
+
+	va_list vl;
+	va_start(vl, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, vl);
+	va_end(vl);
+	rpc_error(client, request, error_code, buf);
+}
+
+void _rpc_response(Client *client, json_t *request, json_t *result)
+{
+	const char *method = json_object_get_string(request, "method");
+	json_t *id = json_object_get(request, "id");
+	char *json_serialized;
+	json_t *j = json_object();
+
+	json_object_set_new(j, "jsonrpc", json_string_unreal("2.0"));
+	json_object_set_new(j, "method", json_string_unreal(method));
+	if (id)
+		json_object_set(j, "id", id); /* 'id' is optional */
+	json_object_set(j, "result", result);
+
+	json_serialized = json_dumps(j, 0);
+	if (!json_serialized)
+	{
+		unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_RESPONSE_SERIALIZE_FAILED", NULL,
+		           "[BUG] rpc_response() failed to serialize response "
+		           "for request from $client ($method)",
+		           log_data_string("method", method));
+		json_decref(j);
+		return;
+	}
+
+	if (MyConnect(client))
+		rpc_sendto(client, json_serialized, strlen(json_serialized));
+	else
+		rpc_send_response_to_remote(&me, client, j);
+
+#ifdef DEBUGMODE
+	unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
+		   "[rpc] Client $client: RPC response result: $response",
+		   log_data_string("response", json_serialized));
+#endif
+	json_decref(j);
+	safe_free(json_serialized);
+}
+
+int sanitize_params_actual(Client *client, json_t *request, const char *str)
+{
+	if (!str)
+		return 1;
+
+	if (strlen(str) > 510)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Strings cannot be longer than 510 characters in the request");
+		return 0;
+	}
+
+	if (strchr(str, '\n') || strchr(str, '\r'))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Strings may not contain \n or \r in the request");
+		return 0;
+	}
+
+	return 1;
+}
+
+int sanitize_params(Client *client, json_t *request, json_t *j)
+{
+	/* Check the current object itself */
+	const char *str = json_string_value(j);
+	if (str && !sanitize_params_actual(client, request, str))
+		return 0;
+
+	/* Now walk through the object, if needed */
+
+	if (json_is_array(j))
+	{
+		size_t index;
+		json_t *value;
+		json_array_foreach(j, index, value)
+		{
+			if (!sanitize_params(client, request, value))
+				return 0;
+		}
+	} else
+	if (json_is_object(j))
+	{
+		const char *key;
+		json_t *value;
+		json_object_foreach(j, key, value)
+		{
+			if (!sanitize_params_actual(client, request, key))
+				return 0;
+			if (!sanitize_params(client, request, value))
+				return 0;
+		}
+	}
+
+	return 1;
+}
+
+/** Log the RPC request */
+void rpc_call_log(Client *client, RPCHandler *handler, json_t *request, const char *method, json_t *params)
+{
+	const char *key;
+	json_t *value_object;
+	char params_string[512], tbuf[256];
+
+	*params_string = '\0';
+	json_object_foreach(params, key, value_object)
+	{
+		const char *value = json_get_value(value_object);
+		if (value)
+		{
+			snprintf(tbuf, sizeof(tbuf), "%s='%s', ", key, value);
+			strlcat(params_string, tbuf, sizeof(params_string));
+		}
+	}
+	if (*params_string)
+		params_string[strlen(params_string)-2] = '\0'; /* cut off last comma */
+
+	// TODO: pass log_data_json() or something, pass the entire 'request' ? For JSON logging
+
+	if (client->rpc && client->rpc->issuer)
+	{
+		if (*params_string)
+		{
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client ($issuer): $params_string",
+				   log_data_string("issuer", client->rpc->issuer),
+				   log_data_string("method", method),
+				   log_data_string("params_string", params_string));
+		} else {
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client ($issuer)",
+				   log_data_string("issuer", client->rpc->issuer),
+				   log_data_string("method", method));
+		}
+	} else {
+		if (*params_string)
+		{
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client: $params_string",
+				   log_data_string("method", method),
+				   log_data_string("params_string", params_string));
+		} else {
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client",
+				   log_data_string("method", method));
+		}
+	}
+}
+
+/** Parse an RPC request, except that it does not validate 'params'.
+ * @param client	The client issuing the request
+ * @param mainrequest	The underlying request that we should send errors to (usually same as 'request')
+ * @param request	The request that needs parsing
+ * @param method	This will be filled in if successfully parsed
+ * @param handler	This will be filled in if the handler is found
+ * @retval 0		An error occured while parsing or the method was not found.
+ * @retval 1		All good. You still need to validate 'params', though.
+ */
+int parse_rpc_call(Client *client, json_t *mainrequest, json_t *request, const char **method, RPCHandler **handler)
+{
+	const char *jsonrpc;
+	json_t *id;
+	const char *str;
+
+	*method = NULL;
+	*handler = NULL;
+
+	jsonrpc = json_object_get_string(request, "jsonrpc");
+	if (!jsonrpc || strcasecmp(jsonrpc, "2.0"))
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Only JSON-RPC version 2.0 is supported");
+		return 0;
+	}
+
+	id = json_object_get(request, "id");
+	if (!id)
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'id'");
+		return 0;
+	}
+
+	if ((str = json_string_value(id)))
+	{
+		if (strlen(str) > 32)
+		{
+			rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' cannot be longer than 32 characters in UnrealIRCd JSON-RPC");
+			return 0;
+		}
+		if (strchr(str, '\n') || strchr(str, '\r'))
+		{
+			rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' may not contain \n or \r in UnrealIRCd JSON-RPC");
+			return 0;
+		}
+	} else if (!json_is_integer(id))
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' must be a string or an integer in UnrealIRCd JSON-RPC");
+		return 0;
+	}
+
+	*method = json_object_get_string(request, "method");
+	if (!*method)
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'method' to call");
+		return 0;
+	}
+
+	*handler = RPCHandlerFind(*method);
+	if (!*handler)
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_METHOD_NOT_FOUND, "Unsupported method");
+		return 0;
+	}
+
+	return 1;
+}
+
+/** Handle the RPC request: request is in JSON */
+void rpc_call(Client *client, json_t *request)
+{
+	const char *method;
+	json_t *params;
+	RPCHandler *handler;
+
+	if (!parse_rpc_call(client, request, request, &method, &handler))
+		return; /* Error already returned to caller */
+
+	params = json_object_get(request, "params");
+	if (params)
+	{
+		if (!(handler->flags & RPC_HANDLER_FLAGS_UNFILTERED) &&
+		    !sanitize_params(client, request, params))
+		{
+			return;
+		}
+	} else
+	{
+		/* Params is optional, so create an empty params object instead
+		 * to make life easier of the RPC handlers (no need to check NULL).
+		 */
+		params = json_object();
+		json_object_set_new(request, "params", params);
+	}
+
+	rpc_call_log(client, handler, request, method, params);
+
+#ifdef DEBUGMODE
+	{
+		char *call = json_dumps(request, 0);
+		if (call)
+		{
+			unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
+				   "[rpc] Client $client: RPC call: $call",
+				   log_data_string("call", call));
+			safe_free(call);
+		}
+	}
+#endif
+	handler->call(client, request, params);
+}
+
+/** Called very early on accept() of the socket, before TLS is ready */
+int rpc_client_accept(Client *client)
+{
+	if (RPC_PORT(client))
+	{
+		SetRPC(client);
+		client->rpc = safe_alloc(sizeof(RPCClient));
+	}
+	return 0;
+}
+
+/** Called upon handshake of unix socket (direct JSON usage, no auth) */
+void rpc_client_handshake_unix_socket(Client *client)
+{
+	if (client->local->listener->socket_type != SOCKET_TYPE_UNIX)
+		abort(); /* impossible */
+
+	strlcpy(client->name, "RPC:local", sizeof(client->name));
+	SetRPC(client);
+	client->rpc = safe_alloc(sizeof(RPCClient));
+	safe_strdup(client->rpc->rpc_user, "<local>");
+
+	/* Allow incoming data to be read from now on.. */
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+/** Called upon handshake, after TLS is ready (before any HTTP header parsing) */
+void rpc_client_handshake_web(Client *client)
+{
+	RPCUser *r;
+	char found = 0;
+
+	/* Explicitly mark as RPC, since the TLS layer may
+	 * have set us to SetUnknown() after the TLS handshake.
+	 */
+	SetRPC(client);
+	if (!client->rpc)
+		client->rpc = safe_alloc(sizeof(RPCClient));
+
+	/* Is the client allowed by any rpc-user { } block?
+	 * If not, reject the client immediately, before
+	 * processing any HTTP data.
+	 */
+	for (r = rpcusers; r; r = r->next)
+	{
+		if (user_allowed_by_security_group(client, r->match))
+		{
+			found = 1;
+			break;
+		}
+	}
+	if (!found)
+	{
+		webserver_send_response(client, 401, "Access denied");
+		return;
+	}
+
+	/* Allow incoming data to be read from now on.. */
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+#define RPC_WEBSOCKET_PING_TIME 120
+
+int rpc_pre_local_handshake_timeout(Client *client, const char **comment)
+{
+	/* Don't hang up websocket connections */
+	if (IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
+	{
+		long t = TStime() - client->local->last_msg_received;
+		if ((t > RPC_WEBSOCKET_PING_TIME*2) && IsPingSent(client))
+		{
+			*comment = "No websocket PONG received in time.";
+			return HOOK_CONTINUE;
+		} else
+		if ((t > RPC_WEBSOCKET_PING_TIME) && !IsPingSent(client) && !IsDead(client))
+		{
+			char pingbuf[4];
+			const char *pkt = pingbuf;
+			int pktlen = sizeof(pingbuf);
+			pingbuf[0] = 0x11;
+			pingbuf[1] = 0x22;
+			pingbuf[2] = 0x33;
+			pingbuf[3] = 0x44;
+			websocket_create_packet_simple(WSOP_PING, &pkt, &pktlen);
+			dbuf_put(&client->local->sendQ, pkt, pktlen);
+			send_queued(client);
+			SetPingSent(client);
+		}
+		return HOOK_ALLOW; /* prevent closing the connection due to timeout */
+	}
+
+	return HOOK_CONTINUE;
+}
+
+RPCUser *find_rpc_user(const char *username)
+{
+	RPCUser *r;
+	for (r = rpcusers; r; r = r->next)
+		if (!strcmp(r->name, username))
+			return r;
+	return NULL;
+}
+
+/** This function deals with authentication after the HTTP request was received.
+ * It is called for both ordinary HTTP(S) requests and Websockets.
+ * Note that there has also been some pre-filtering done in rpc_client_handshake()
+ * to see if the IP address was allowed to connect at all (::match),
+ * but here we actually check the 'correct' rpc-user { } block.
+ * @param client	The client to authenticate
+ * @param web		The webrequest (containing the headers)
+ * @return 1 on success, 0 on failure
+ */
+int rpc_handle_auth(Client *client, WebRequest *web)
+{
+	char *username = NULL, *password = NULL;
+	RPCUser *r;
+
+	if (!rpc_parse_auth_basic_auth(client, web, &username, &password) &&
+	    !rpc_parse_auth_uri(client, web, &username, &password))
+	{
+		webserver_send_response(client, 401, "Authentication required");
+		return 0;
+	}
+
+	if (username && password && ((r = find_rpc_user(username))))
+	{
+		if (user_allowed_by_security_group(client, r->match) &&
+		    Auth_Check(client, r->auth, password))
+		{
+			/* Authenticated! */
+			snprintf(client->name, sizeof(client->name), "RPC:%s", r->name);
+			safe_strdup(client->rpc->rpc_user, r->name);
+			return 1;
+		}
+	}
+
+	/* Authentication failed */
+	webserver_send_response(client, 401, "Authentication required");
+	return 0;
+}
+
+int rpc_parse_auth_basic_auth(Client *client, WebRequest *web, char **username, char **password)
+{
+	const char *auth_header = get_nvplist(web->headers, "Authorization");
+	static char buf[512];
+	char *p;
+	int n;
+
+	if (!auth_header)
+		return 0;
+
+	/* We only support basic auth */
+	if (strncasecmp(auth_header, "Basic ", 6))
+		return 0;
+
+	p = strchr(auth_header, ' ');
+	skip_whitespace(&p);
+	n = b64_decode(p, buf, sizeof(buf)-1);
+	if (n <= 1)
+		return 0;
+	buf[n] = '\0';
+
+	p = strchr(buf, ':');
+	if (!p)
+		return 0;
+	*p++ = '\0';
+
+	*username = buf;
+	*password = p;
+	return 1;
+}
+
+// TODO: the ?a=b&c=d stuff should be urldecoded by 'webserver'
+int rpc_parse_auth_uri(Client *client, WebRequest *web, char **username, char **password)
+{
+	static char buf[2048];
+	char *str, *p;
+
+	if (!web->uri)
+		return 0;
+
+	strlcpy(buf, web->uri, sizeof(buf));
+	str = strstr(buf, "username=");
+	if (!str)
+		return 0;
+	str += 9;
+	*username = str;
+	p = strchr(str, '&');
+	if (p)
+	{
+		*p++ = '\0';
+		p = strstr(p, "password=");
+		if (p)
+		{
+			p += 9;
+			*password = p;
+			p = strchr(str, '&');
+			if (p)
+				*p = '\0';
+		}
+	}
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_rpc_info)
+{
+	json_t *result, *methods, *item;
+	RPCHandler *r;
+
+	result = json_object();
+	methods = json_object();
+	json_object_set_new(result, "methods", methods);
+
+	for (r = rpchandlers; r; r = r->next)
+	{
+		item = json_object();
+		json_object_set_new(item, "name", json_string_unreal(r->method));
+		if (r->owner)
+		{
+			json_object_set_new(item, "module", json_string_unreal(r->owner->header->name));
+			json_object_set_new(item, "version", json_string_unreal(r->owner->header->version));
+		}
+		json_object_set_new(methods, r->method, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_rpc_set_issuer)
+{
+	json_t *result;
+	const char *name;
+	char buf[512];
+
+	REQUIRE_PARAM_STRING("name", name);
+
+	/* Do some validation on the name */
+	strlcpy(buf, name, sizeof(buf));
+	if (!do_remote_nick_name(buf) || strcmp(buf, name))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME,
+		          "The 'name' contains illegal characters or is too long. "
+		          "The same rules as for nick names apply.");
+		return;
+	}
+
+	safe_strdup(client->rpc->issuer, name);
+
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void free_rrpc(RRPC *r)
+{
+	safe_free(r->requestid);
+	DBufClear(&r->data);
+	DelListItem(r, rrpc_list);
+	safe_free(r);
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_rrpc_list(ModData *m)
+{
+	RRPC *r, *r_next;
+
+	for (r = rrpc_list; r; r = r_next)
+	{
+		r_next = r->next;
+		free_rrpc(r);
+	}
+}
+
+void free_outstanding_rrpc(OutstandingRRPC *r)
+{
+	safe_free(r->requestid);
+	DelListItem(r, outstanding_rrpc_list);
+	safe_free(r);
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_outstanding_rrpc_list(ModData *m)
+{
+	OutstandingRRPC *r, *r_next;
+
+	for (r = outstanding_rrpc_list; r; r = r_next)
+	{
+		r_next = r->next;
+		free_outstanding_rrpc(r);
+	}
+}
+
+/** Remove timer from rpc_timer_list and free it */
+void free_rpc_timer(RPCTimer *r)
+{
+	safe_free(r->timer_id);
+	json_decref(r->request);
+	DelListItem(r, rpc_timer_list);
+	safe_free(r);
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_rpc_timer_list(ModData *m)
+{
+	RPCTimer *r, *r_next;
+
+	for (r = rpc_timer_list; r; r = r_next)
+	{
+		r_next = r->next;
+		free_rpc_timer(r);
+	}
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_rpc_timers_for_user(Client *client)
+{
+	RPCTimer *r, *r_next;
+
+	for (r = rpc_timer_list; r; r = r_next)
+	{
+		r_next = r->next;
+		if (r->client == client)
+			free_rpc_timer(r);
+	}
+}
+
+RPCTimer *find_rpc_timer(Client *client, const char *timer_id)
+{
+	RPCTimer *r;
+
+	for (r = rpc_timer_list; r; r = r->next)
+	{
+		if ((r->client == client) && !strcmp(timer_id, r->timer_id))
+			return r;
+	}
+	return NULL;
+}
+
+/** When a server quits, cancel all the RPC requests to/from those clients */
+int rpc_handle_server_quit(Client *client, MessageTag *mtags)
+{
+	RRPC *r, *r_next;
+	OutstandingRRPC *or, *or_next;
+
+	for (r = rrpc_list; r; r = r_next)
+	{
+		r_next = r->next;
+		if (!strncmp(client->id, r->source, SIDLEN) ||
+		    !strncmp(client->id, r->destination, SIDLEN))
+		{
+			free_rrpc(r);
+		}
+	}
+
+	for (or = outstanding_rrpc_list; or; or = or_next)
+	{
+		or_next = or->next;
+		if (!strcmp(client->id, or->destination))
+		{
+			Client *client = find_client(or->source, NULL);
+			if (client)
+			{
+				json_t *j = json_object();
+				json_object_set_new(j, "id", json_string_unreal(or->requestid));
+				rpc_error(client, NULL, JSON_RPC_ERROR_SERVER_GONE, "Remote server disconnected while processing the request");
+				json_decref(j);
+			}
+			free_outstanding_rrpc(or);
+		}
+	}
+
+	return 0;
+}
+
+EVENT(rpc_remote_timeout)
+{
+	OutstandingRRPC *or, *or_next;
+	time_t deadline = TStime() - 15;
+
+	for (or = outstanding_rrpc_list; or; or = or_next)
+	{
+		or_next = or->next;
+		if (or->sent < deadline)
+		{
+			Client *client = find_client(or->source, NULL);
+			if (client)
+			{
+				json_t *request = json_object();
+				json_object_set_new(request, "id", json_string_unreal(or->requestid));
+				rpc_error(client, request, JSON_RPC_ERROR_TIMEOUT, "Request timed out");
+				json_decref(request);
+			}
+			free_outstanding_rrpc(or);
+		}
+	}
+}
+
+RRPC *find_rrpc(const char *source, const char *destination, const char *requestid)
+{
+	RRPC *r;
+	for (r = rrpc_list; r; r = r->next)
+	{
+		if (!strcmp(r->source, source) &&
+		    !strcmp(r->destination, destination) &&
+		    !strcmp(r->requestid, requestid))
+		{
+			return r;
+		}
+	}
+	return NULL;
+}
+
+OutstandingRRPC *find_outstandingrrpc(const char *source, const char *requestid)
+{
+	OutstandingRRPC *r;
+	for (r = outstanding_rrpc_list; r; r = r->next)
+	{
+		if (!strcmp(r->source, source) &&
+		    !strcmp(r->requestid, requestid))
+		{
+			return r;
+		}
+	}
+	return NULL;
+}
+
+/* Remote RPC call over the network (RRPC)
+ * :<server> RRPC <REQ|RES> <source> <destination> <requestid> [S|C|F] :<request data>
+ * S = Start
+ * C = Continuation
+ * F = Finish
+ */
+CMD_FUNC(cmd_rrpc)
+{
+	int request;
+	const char *source, *destination, *requestid, *type, *data;
+	RRPC *r;
+	Client *dest;
+	char sid[SIDLEN+1];
+	char binarydata[BUFSIZE+1];
+	int binarydatalen;
+
+	if ((parc < 7) || BadPtr(parv[6]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "KNOCK");
+		return;
+	}
+
+	if (!strcmp(parv[1], "REQ"))
+	{
+		request = 1;
+	} else if (!strcmp(parv[1], "RES"))
+	{
+		request = 0;
+	} else {
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Invalid parameter");
+		return;
+	}
+
+	source = parv[2];
+	destination = parv[3];
+	requestid = parv[4];
+	type = parv[5];
+	data = parv[6];
+
+	/* Search by SID (first 3 characters of destination)
+	 * so we can always deliver, even forn unknown UID destinations
+	 * in case this is a response.
+	 */
+	strlcpy(sid, destination, sizeof(sid));
+	dest = find_server_quick(sid);
+	if (!dest)
+	{
+		sendnumeric(client, ERR_NOSUCHSERVER, sid);
+		return;
+	}
+
+	if (dest != &me)
+	{
+		/* Just pass it along... */
+		sendto_one(dest, recv_mtags, ":%s RRPC %s %s %s %s %s :%s",
+		           client->id, parv[1], parv[2], parv[3], parv[4], parv[5], parv[6]);
+		return;
+	}
+
+	/* It's for us! So handle it ;) */
+
+	if (strchr(type, 'S'))
+	{
+		r = find_rrpc(source, destination, requestid);
+		if (r)
+		{
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Duplicate request found");
+			/* We actually terminate the existing RRPC as well,
+			 * because there's a big risk of the the two different ones
+			 * merging in subsequent RRPC... C ... commands. Bad!
+			 * (and yeah this does not handle the case where you have
+			 *  like 3 or more duplicate request id requests... so be it..)
+			 */
+			free_rrpc(r);
+			return;
+		}
+		/* A new request */
+		r = safe_alloc(sizeof(RRPC));
+		strlcpy(r->source, source, sizeof(r->source));
+		strlcpy(r->destination, destination, sizeof(r->destination));
+		safe_strdup(r->requestid, requestid);
+		r->request = request;
+		dbuf_queue_init(&r->data);
+		AddListItem(r, rrpc_list);
+	} else
+	if (strchr(type, 'C') || strchr(type, 'F'))
+	{
+		r = find_rrpc(source, destination, requestid);
+		if (!r)
+		{
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Request not found");
+			return;
+		}
+	} else
+	{
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Only actions S/C/F are supported");
+		return;
+	}
+
+	/* Append the data */
+	dbuf_put(&r->data, data, strlen(data));
+
+	/* Now check if the request happens to be terminated */
+	if (strchr(type, 'F'))
+	{
+		if (r->request)
+			rpc_call_remote(r);
+		else
+			rpc_response_remote(r);
+		free_rrpc(r);
+		return;
+	}
+}
+
+/** Convert the RRPC data to actual workable JSON output */
+json_t *rrpc_data(RRPC *r)
+{
+	int datalen;
+	char *data;
+	json_t *j;
+	json_error_t jerr;
+
+	datalen = dbuf_get(&r->data, &data);
+	j = json_loads(data, JSON_REJECT_DUPLICATES, &jerr);
+	safe_free(data);
+
+	return j;
+}
+
+/** Received a remote RPC request (from a client on another server) */
+void rpc_call_remote(RRPC *r)
+{
+	json_t *request = NULL;
+	Client *server;
+	Client *client;
+	char sid[SIDLEN+1];
+
+	request = rrpc_data(r);
+	if (!request)
+	{
+		// TODO: handle invalid JSON
+		return;
+	}
+
+	/* Create a (fake) client structure */
+	strlcpy(sid, r->source, sizeof(sid));
+	server = find_server_quick(sid);
+	if (!server)
+	{
+		return;
+	}
+	client = make_client(server->direction, server);
+	strlcpy(client->id, r->source, sizeof(client->id));
+	client->rpc = safe_alloc(sizeof(RPCClient));
+	strlcpy(client->name, "RPC:remote", sizeof(client->name));
+	safe_strdup(client->rpc->rpc_user, "<remote>");
+	// Note: NOT added to hash table or id table etc.
+	list_add(&client->client_node, &rpc_remote_list);
+	rpc_call(client, request);
+	json_decref(request);
+
+	/* And free the temporary client, unless it is async... */
+	if (!IsAsyncRPC(client))
+		free_client(client);
+}
+
+/** Received a remote RPC response (from another server) to our local RPC client */
+void rpc_response_remote(RRPC *r)
+{
+	OutstandingRRPC *or;
+	Client *client = find_client(r->destination, NULL);
+	json_t *json, *j;
+
+	if (!client)
+		return;
+
+	or = find_outstandingrrpc(client->id, r->requestid);
+	if (!or)
+		return; /* Not a known outstanding request, maybe the client left already */
+
+	json = rrpc_data(r);
+	if (!json)
+		return;
+
+	if ((j = json_object_get(json, "result")))
+	{
+		rpc_response(client, json, j);
+	} else if ((j = json_object_get(json, "error")))
+	{
+		json_t *x;
+		int errorcode = 0;
+		const char *error_message = json_object_get_string(j, "message");
+		if ((x = json_object_get(j, "errorcode")))
+			errorcode = json_integer_value(x);
+		if (errorcode == 0)
+			errorcode = JSON_RPC_ERROR_INTERNAL_ERROR;
+		rpc_error(client, json, errorcode, error_message ? error_message : "");
+	}
+
+	json_decref(json);
+
+	free_outstanding_rrpc(or);
+}
+
+const char *rpc_id(json_t *request)
+{
+	static char rid[128];
+	const char *requestid;
+	json_t *j;
+
+	j = json_object_get(request, "id");
+	if (!j)
+		return NULL;
+
+	requestid = json_string_value(j);
+	if (!requestid)
+	{
+		json_int_t v = json_integer_value(j);
+		if (v == 0)
+			return NULL;
+		snprintf(rid, sizeof(rid), "%lld", (long long)v);
+		requestid = rid;
+	}
+
+	return requestid;
+}
+
+/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
+void rpc_send_generic_to_remote(Client *source, Client *target, const char *requesttype, json_t *json)
+{
+	char *json_serialized;
+	json_t *j;
+	const char *type;
+	const char *requestid;
+	char *str;
+	int bytes; /* bytes in this frame */
+	int bytes_remaining; /* bytes remaining overall */
+	int start_frame = 1; /* set to 1 if this is the start frame */
+	char data[451];
+
+	requestid = rpc_id(json);
+	if (!requestid)
+		return;
+
+	json_serialized = json_dumps(json, 0);
+	if (!json_serialized)
+		return;
+
+	/* :<server> RRPC REQ <source> <destination> <requestid> [S|C|F] :<request data>
+	 * S = Start
+	 * C = Continuation
+	 * F = Finish
+	 */
+
+	bytes_remaining = strlen(json_serialized);
+	for (str = json_serialized, bytes = MIN(bytes_remaining, 450);
+	     str && *str && bytes_remaining;
+	     str += bytes, bytes = MIN(bytes_remaining, 450))
+	{
+		bytes_remaining -= bytes;
+		if (start_frame == 1)
+		{
+			start_frame = 0;
+			if (bytes_remaining > 0)
+				type = "S"; /* start (with later continuation frames) */
+			else
+				type = "SF"; /* start and finish */
+		} else
+		if (bytes_remaining > 0)
+		{
+			type = "C"; /* continuation frame (with later a finish frame) */
+		} else {
+			type = "F"; /* finish frame (the last frame) */
+		}
+
+		strlncpy(data, str, sizeof(data), bytes);
+
+		sendto_one(target, NULL, ":%s RRPC %s %s %s %s %s :%s",
+		           me.id,
+		           requesttype,
+		           source->id,
+		           target->id,
+		           requestid,
+		           type,
+		           data);
+	}
+
+	safe_free(json_serialized);
+}
+
+int _rrpc_supported_simple(Client *target, char **problem_server)
+{
+	if (!moddata_client_get(target, "rrpc"))
+	{
+		if (problem_server)
+			*problem_server = target->name;
+		return 0;
+	}
+	if ((target != target->direction) && !rrpc_supported_simple(target->direction, problem_server))
+		return 0;
+	return 1;
+}
+
+int _rrpc_supported(Client *target, const char *module, const char *minimum_version, char **problem_server)
+{
+	if (!moddata_client_get(target, "rrpc"))
+	{
+		if (problem_server)
+			*problem_server = target->name;
+		return 0;
+	}
+	if ((target != target->direction) && !rrpc_supported_simple(target->direction, problem_server))
+		return 0;
+	return 1;
+}
+
+/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
+void _rpc_send_request_to_remote(Client *source, Client *target, json_t *request)
+{
+	OutstandingRRPC *r;
+	const char *requestid = rpc_id(request);
+	char *problem_server = NULL;
+
+	if (!requestid)
+	{
+		/* should never happen, since already covered upstream, but just to be sure... */
+		rpc_error(source, NULL, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' must be a string or an integer in UnrealIRCd JSON-RPC");
+		return;
+	}
+
+	if (find_outstandingrrpc(source->id, requestid))
+	{
+		rpc_error(source, NULL, JSON_RPC_ERROR_INVALID_REQUEST, "A request with that id is already in progress. Use unique id's!");
+		return;
+	}
+
+	/* If we already detect that next server cannot satisfy the request then stop it right now */
+	if (!rrpc_supported_simple(target, &problem_server))
+	{
+		rpc_error_fmt(source, request, JSON_RPC_ERROR_REMOTE_SERVER_NO_RPC, "Server %s does not support remote JSON-RPC", problem_server);
+		return;
+	}
+
+	/* Add the request to the "Outstanding RRPC list" */
+	r = safe_alloc(sizeof(OutstandingRRPC));
+	r->sent = TStime();
+	strlcpy(r->source, source->id, sizeof(r->source));
+	strlcpy(r->destination, target->id, sizeof(r->destination));
+	safe_strdup(r->requestid, requestid);
+	AddListItem(r, outstanding_rrpc_list);
+
+	/* And send it! */
+	rpc_send_generic_to_remote(source, target, "REQ", request);
+}
+
+/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
+void _rpc_send_response_to_remote(Client *source, Client *target, json_t *response)
+{
+	rpc_send_generic_to_remote(source, target, "RES", response);
+}
+
+const char *rrpc_md_serialize(ModData *m)
+{
+	static char buf[512];
+	char tmp[128];
+	NameValuePrioList *nv;
+
+	if (m->ptr == NULL)
+		return NULL;
+
+	*buf = '\0';
+	for (nv = m->ptr; nv; nv = nv->next)
+	{
+		snprintf(tmp, sizeof(tmp), "%s:%s,", nv->name, nv->value);
+		strlcat(buf, tmp, sizeof(buf));
+	}
+	if (*buf)
+		buf[strlen(buf)-1] = '\0'; // strip last comma
+
+	return buf;
+}
+
+void rrpc_md_unserialize(const char *str, ModData *m)
+{
+	char buf[1024], *p, *name, *value;
+
+	/* First free everything if needed */
+	if (m->ptr)
+	{
+		free_nvplist(m->ptr);
+		m->ptr = NULL;
+	}
+
+	if (BadPtr(str))
+		return; /* Done already */
+
+	strlcpy(buf, str, sizeof(buf));
+	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
+	{
+		value = strchr(name, ':');
+		if (!value)
+			continue;
+		*value++ = '\0';
+		add_nvplist((NameValuePrioList **)&m->ptr, 0, name, value);
+	}
+}
+
+void rrpc_md_free(ModData *m)
+{
+	if (m->ptr)
+	{
+		free_nvplist(m->ptr);
+		m->ptr = NULL;
+	}
+}
+
+int rpc_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child)
+{
+	NameValuePrioList *nv = RRPCMODULES(client);
+	json_t *rpc_modules;
+
+	if (!nv || (detail < 2))
+		return 0;
+
+	/* All this belongs under 'features' */
+	child = json_object_get(child, "features");
+	if (!child)
+		return 0;
+
+	rpc_modules = json_array();
+	json_object_set_new(child, "rpc_modules", rpc_modules);
+	for (; nv; nv = nv->next)
+	{
+		json_t *e = json_object();
+		json_object_set_new(e, "name", json_string_unreal(nv->name));
+		json_object_set_new(e, "version", json_string_unreal(nv->value));
+		json_array_append_new(rpc_modules, e);
+	}
+	return 0;
+}
+
+RPC_CALL_FUNC(rpc_rpc_add_timer)
+{
+	json_t *result;
+	json_t *subrequest;
+	long every_msec;
+	const char *timer_id;
+	const char *method;
+	RPCHandler *handler;
+	RPCTimer *timer;
+
+	REQUIRE_PARAM_INTEGER("every_msec", every_msec);
+	REQUIRE_PARAM_STRING("timer_id", timer_id);
+
+	subrequest = json_object_get(params, "request");
+	if (!subrequest)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "request");
+		return;
+	}
+
+	if (every_msec < RPC_MINIMUM_TIMER_MSEC)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS,
+		              "Value for every_msec may not be less than %d",
+		              (int)RPC_MINIMUM_TIMER_MSEC);
+		return;
+	}
+
+	/* Do some validation on the name */
+	if (!parse_rpc_call(client, request, subrequest, &method, &handler))
+		return; /* Error already returned to caller */
+
+	/* We don't validate 'params' here, but do so at runtime */
+
+	/* Check if a timer with that same name already exists FOR THIS CLIENT */
+	if (find_rpc_timer(client, timer_id))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Timer already exists with timer_id '%s'", timer_id);
+		return;
+	}
+
+	timer = safe_alloc(sizeof(RPCTimer));
+	timer->every_msec = every_msec;
+	timer->client = client;
+	safe_strdup(timer->timer_id, timer_id);
+	json_incref(subrequest);
+	timer->request = subrequest;
+	AddListItem(timer, rpc_timer_list);
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+EVENT(rpc_do_timers)
+{
+	RPCTimer *e, *e_next;
+
+	for (e = rpc_timer_list; e; e = e_next)
+	{
+		e_next = e->next;
+		if (minimum_msec_since_last_run(&e->last_run, e->every_msec))
+		{
+			rpc_call(e->client, e->request);
+		}
+		// TODO: maybe do counts as well?
+	}
+}
+
+/** Client being freed? If RPC then cancel timers, if any */
+int rpc_handle_free_client(Client *client)
+{
+	if (IsRPC(client))
+		free_rpc_timers_for_user(client);
+	return 0;
+}
+
+RPC_CALL_FUNC(rpc_rpc_del_timer)
+{
+	const char *timer_id;
+	RPCTimer *r;
+	json_t *result;
+
+	REQUIRE_PARAM_STRING("timer_id", timer_id);
+
+	r = find_rpc_timer(client, timer_id);
+	if (!r)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_NOT_FOUND, "Timer not found with timer_id '%s'", timer_id);
+		return;
+	}
+	free_rpc_timer(r);
+
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/server.c b/ircd/src/modules/rpc/server.c
@@ -0,0 +1,416 @@
+/* server.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/server",
+	"1.0.0",
+	"server.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_server_list);
+RPC_CALL_FUNC(rpc_server_get);
+RPC_CALL_FUNC(rpc_server_rehash);
+RPC_CALL_FUNC(rpc_server_connect);
+RPC_CALL_FUNC(rpc_server_disconnect);
+RPC_CALL_FUNC(rpc_server_module_list);
+
+int rpc_server_rehash_log(int failure, json_t *rehash_log);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "server.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.rehash";
+	r.call = rpc_server_rehash;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.connect";
+	r.call = rpc_server_connect;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.disconnect";
+	r.call = rpc_server_disconnect;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.module_list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_module_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_REHASH_LOG, 0, rpc_server_rehash_log);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_server_list)
+{
+	json_t *result, *list, *item;
+	Client *acptr;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if (!IsServer(acptr) && !IsMe(acptr))
+			continue;
+
+		item = json_object();
+		json_expand_client(item, NULL, acptr, 99);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_get)
+{
+	json_t *result, *list, *item;
+	const char *server;
+	Client *acptr;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+
+	result = json_object();
+	json_expand_client(result, "server", acptr, 99);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_rehash)
+{
+	json_t *result, *list, *item;
+	const char *server;
+	Client *acptr;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+
+	if (acptr != &me)
+	{
+		/* Forward to remote */
+		if (rrpc_supported_simple(acptr, NULL))
+		{
+			/* Server supports RRPC and will handle the response */
+			rpc_send_request_to_remote(client, acptr, request);
+		} else {
+			/* Server does not support RRPC, so we can only do best effort: */
+			sendto_one(acptr, NULL, ":%s REHASH %s", me.id, acptr->name);
+			result = json_boolean(1);
+			rpc_response(client, request, result);
+			json_decref(result);
+		}
+		return;
+	}
+
+	if (client->rpc->rehash_request)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "A rehash initiated by you is already in progress");
+		return;
+	}
+
+	/* It's for me... */
+
+	SetMonitorRehash(client);
+	SetAsyncRPC(client);
+	client->rpc->rehash_request = json_copy(request); // or json_deep_copy ??
+
+	if (!loop.rehashing)
+	{
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
+		request_rehash(client);
+	} /* else.. we simply joined the rehash request so we are notified as well ;) */
+
+	/* Do NOT send the JSON Response here, it is done by rpc_server_rehash_log()
+	 * after the REHASH completed (which may take several seconds).
+	 */
+}
+
+int rpc_server_rehash_log(int failure, json_t *rehash_log)
+{
+	Client *client, *next;
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (IsRPC(client) && IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
+		{
+			rpc_response(client, client->rpc->rehash_request, rehash_log);
+			json_decref(client->rpc->rehash_request);
+			client->rpc->rehash_request = NULL;
+		}
+	}
+	list_for_each_entry_safe(client, next, &rpc_remote_list, client_node)
+	{
+		if (IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
+		{
+			rpc_response(client, client->rpc->rehash_request, rehash_log);
+			json_decref(client->rpc->rehash_request);
+			client->rpc->rehash_request = NULL;
+			free_client(client);
+		}
+	}
+	return 0;
+}
+
+RPC_CALL_FUNC(rpc_server_connect)
+{
+	json_t *result, *list, *item;
+	const char *server, *link_name;
+	Client *acptr;
+	ConfigItem_link *link;
+	const char *err;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+	REQUIRE_PARAM_STRING("link", link_name);
+
+	if (acptr != &me)
+	{
+		/* Not supported atm */
+		result = json_boolean(0);
+		rpc_response(client, request, result);
+		json_decref(result);
+		return;
+	}
+
+	if (find_server_quick(link_name))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Server is already linked");
+		return;
+	}
+
+	link = find_link(link_name);
+	if (!link)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name does not exist in any link block");
+		return;
+	}
+	if (!link->outgoing.hostname && !link->outgoing.file)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name exists but is not configured as an OUTGOING server.");
+		return;
+	}
+
+	if ((err = check_deny_link(link, 0)))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_DENIED, "Server linking is denied via a deny link { } block: %s", err);
+		return;
+	}
+
+	unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
+		   "CONNECT: Link to $link_block requested by $client",
+		   log_data_link_block(link));
+
+	connect_server(link, client, NULL);
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_disconnect)
+{
+	json_t *result, *list, *item;
+	const char *server, *link_name, *reason;
+	Client *acptr, *target;
+	MessageTag *mtags = NULL;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+	REQUIRE_PARAM_STRING("link", link_name);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (acptr != &me)
+	{
+		/* Not supported atm */
+		result = json_boolean(0);
+		rpc_response(client, request, result);
+		json_decref(result);
+		return;
+	}
+
+	target = find_server_quick(link_name);
+	if (!target)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server link not found");
+		return;
+	}
+
+	if (target == &me)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "We cannot disconnect ourselves");
+		return;
+	}
+
+	unreal_log(ULOG_INFO, "link", "SQUIT", client,
+	           "SQUIT: Forced server disconnect of $target by $client ($reason)",
+	           log_data_client("target", target),
+	           log_data_string("reason", reason));
+
+	/* The actual SQUIT: */
+	new_message(client, NULL, &mtags);
+	exit_client_ex(target, NULL, mtags, reason);
+	free_message_tags(mtags);
+
+	/* Return true */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void json_expand_module(json_t *j, const char *key, Module *m, int detail)
+{
+	char buf[BUFSIZE+1];
+	json_t *child;
+
+	if (key)
+	{
+		child = json_object();
+		json_object_set_new(j, key, child);
+	} else {
+		child = j;
+	}
+
+	json_object_set_new(child, "name", json_string_unreal(m->header->name));
+	json_object_set_new(child, "version", json_string_unreal(m->header->version));
+	json_object_set_new(child, "author", json_string_unreal(m->header->author));
+	json_object_set_new(child, "description", json_string_unreal(m->header->description));
+	json_object_set_new(child, "third_party", json_boolean(m->options & MOD_OPT_OFFICIAL ? 0 : 1));
+	json_object_set_new(child, "permanent", json_boolean(m->options & MOD_OPT_PERM ? 1 : 0));
+	json_object_set_new(child, "permanent_but_reloadable", json_boolean(m->options & MOD_OPT_PERM_RELOADABLE ? 1 : 0));
+}
+
+RPC_CALL_FUNC(rpc_server_module_list)
+{
+	json_t *result, *list, *item;
+	const char *server;
+	Client *acptr;
+	Module *m;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+
+	if (acptr != &me)
+	{
+		/* Forward to remote */
+		rpc_send_request_to_remote(client, acptr, request);
+		return;
+	}
+
+	/* Return true */
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (m = Modules; m; m = m->next)
+	{
+		item = json_object();
+		json_expand_module(item, NULL, m, 1);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/server_ban.c b/ircd/src/modules/rpc/server_ban.c
@@ -0,0 +1,342 @@
+/* server_ban.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/server_ban",
+	"1.0.3",
+	"server_ban.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_server_ban_list);
+RPC_CALL_FUNC(rpc_server_ban_get);
+RPC_CALL_FUNC(rpc_server_ban_del);
+RPC_CALL_FUNC(rpc_server_ban_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "server_ban.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban.del";
+	r.call = rpc_server_ban_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban.add";
+	r.call = rpc_server_ban_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_list)
+{
+	json_t *result, *list, *item;
+	int index, index2;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				if (TKLIsServerBan(tkl))
+				{
+					item = json_object();
+					json_expand_tkl(item, NULL, tkl, 1);
+					json_array_append_new(list, item);
+				}
+			}
+		}
+	}
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsServerBan(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/** Shared code for selecting a server ban, for .add/.del/.get */
+int server_ban_select_criteria(Client *client, json_t *request, json_t *params,
+                               const char **name,
+                               const char **type_name,
+                               char *tkl_type_char,
+                               int *tkl_type_int,
+                               char **usermask,
+                               char **hostmask,
+                               int *soft)
+{
+	const char *error;
+
+	*name = json_object_get_string(params, "name");
+	if (!*name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
+		return 0;
+	}
+
+	*type_name = json_object_get_string(params, "type");
+	if (!*type_name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'type'");
+		return 0;
+	}
+
+	*tkl_type_char = tkl_configtypetochar(*type_name);
+	if (!*tkl_type_char)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s'", *type_name);
+		return 0;
+	}
+	*tkl_type_int = tkl_chartotype(*tkl_type_char);
+	if (!TKLIsServerBanType(*tkl_type_int))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s' (type exists but is not valid for in server_ban.*)", *type_name);
+		return 0;
+	}
+
+	if (!server_ban_parse_mask(client, 0, *tkl_type_char, *name, usermask, hostmask, soft, &error))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
+		return 0;
+	}
+
+	/* Hm, shouldn't this be done by server_ban_parse_mask() ? */
+	if (*soft && (*usermask[0] == '%'))
+		*usermask = *usermask + 1;
+
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_get)
+{
+	json_t *result, *list, *item;
+	const char *name, *type_name;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	char tkl_type_char;
+	int tkl_type_int;
+
+	if (!server_ban_select_criteria(client, request, params,
+	                                &name, &type_name,
+	                                &tkl_type_char, &tkl_type_int,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_del)
+{
+	json_t *result, *list, *item;
+	const char *name, *type_name;
+	const char *set_by;
+	char *usermask, *hostmask;
+	char usermask_mess[256];
+	int soft;
+	TKL *tkl;
+	char tkl_type_char;
+	int tkl_type_int;
+	const char *tkllayer[10];
+	char tkl_type_str[2];
+
+	if (!server_ban_select_criteria(client, request, params,
+	                                &name, &type_name,
+	                                &tkl_type_char, &tkl_type_int,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	tkl_type_str[0] = tkl_type_char;
+	tkl_type_str[1] = '\0';
+
+	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	tkllayer[1] = "-";
+	tkllayer[2] = tkl_type_str;
+	if (soft)
+	{
+		/* I don't like this fiddling.
+		 * It will be gone when we move from cmd_tkl() to a real function though.
+		 */
+		snprintf(usermask_mess, sizeof(usermask_mess), "%%%s", usermask);
+		tkllayer[3] = usermask_mess;
+	} else {
+		tkllayer[3] = usermask;
+	}
+	tkllayer[4] = hostmask;
+	tkllayer[5] = set_by;
+	tkllayer[6] = NULL;
+	cmd_tkl(&me, NULL, 6, tkllayer);
+
+	if (!find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based ban.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+	}
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_add)
+{
+	json_t *result, *list, *item;
+	const char *name, *type_name;
+	const char *set_by;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	char tkl_type_char;
+	int tkl_type_int;
+	char tkl_type_str[2];
+	const char *reason;
+	const char *str;
+	time_t tkl_expire_at;
+	time_t tkl_set_at = TStime();
+
+	if (!server_ban_select_criteria(client, request, params,
+	                                &name, &type_name,
+	                                &tkl_type_char, &tkl_type_int,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	tkl_type_str[0] = tkl_type_char;
+	tkl_type_str[1] = '\0';
+
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	/* Duration / expiry time */
+	if ((str = json_object_get_string(params, "duration_string")))
+	{
+		tkl_expire_at = config_checkval(str, CFG_TIME);
+		if (tkl_expire_at > 0)
+			tkl_expire_at = TStime() + tkl_expire_at;
+	} else
+	if ((str = json_object_get_string(params, "expire_at")))
+	{
+		tkl_expire_at = server_time_to_unix_time(str);
+	} else
+	{
+		/* Never expire */
+		tkl_expire_at = 0;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
+		return;
+	}
+
+	if (find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban with that mask already exists");
+		return;
+	}
+
+	tkl = tkl_add_serverban(tkl_type_int, usermask, hostmask, reason,
+	                        set_by, tkl_expire_at, tkl_set_at,
+	                        soft, 0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/server_ban_exception.c b/ircd/src/modules/rpc/server_ban_exception.c
@@ -0,0 +1,314 @@
+/* server_ban_exception.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/server_ban_exception",
+	"1.0.1",
+	"server_ban_exception.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_server_ban_exception_list);
+RPC_CALL_FUNC(rpc_server_ban_exception_get);
+RPC_CALL_FUNC(rpc_server_ban_exception_del);
+RPC_CALL_FUNC(rpc_server_ban_exception_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "server_ban_exception.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_exception_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban_exception.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_exception_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban_exception.del";
+	r.call = rpc_server_ban_exception_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban_exception.add";
+	r.call = rpc_server_ban_exception_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_list)
+{
+	json_t *result, *list, *item;
+	int index, index2;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				if (TKLIsBanException(tkl))
+				{
+					item = json_object();
+					json_expand_tkl(item, NULL, tkl, 1);
+					json_array_append_new(list, item);
+				}
+			}
+		}
+	}
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsBanException(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/** Shared code for selecting a server ban, for .add/.del/.get */
+int server_ban_exception_select_criteria(Client *client, json_t *request, json_t *params, int add,
+                               const char **name,
+                               const char **exception_types,
+                               char **usermask,
+                               char **hostmask,
+                               int *soft)
+{
+	const char *error;
+
+	*name = json_object_get_string(params, "name");
+	if (!*name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
+		return 0;
+	}
+
+	if (add)
+	{
+		*exception_types = json_object_get_string(params, "exception_types");
+		if (!*exception_types)
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'exception_types'");
+			return 0;
+		}
+	} else {
+		*exception_types = NULL;
+	}
+
+	if (!server_ban_exception_parse_mask(client, add, *exception_types, *name, usermask, hostmask, soft, &error))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
+		return 0;
+	}
+
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_get)
+{
+	json_t *result, *list, *item;
+	const char *name, *exception_types;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+
+	if (!server_ban_exception_select_criteria(client, request, params, 0,
+	                                &name, &exception_types,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	if (!(tkl = find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft)) &&
+	    !(tkl = find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban exception not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_del)
+{
+	json_t *result, *list, *item;
+	const char *name, *exception_types;
+	const char *set_by;
+	const char *error;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	const char *tkllayer[11];
+
+	if (!server_ban_exception_select_criteria(client, request, params, 0,
+	                                &name, &exception_types,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	if (!(tkl = find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft)) &&
+	    !(tkl = find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban exception not found");
+		return;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	tkllayer[1] = "-";
+	tkllayer[2] = "E";
+	tkllayer[3] = usermask;
+	tkllayer[4] = hostmask;
+	tkllayer[5] = set_by;
+	tkllayer[6] = "0";
+	tkllayer[7] = "-";
+	tkllayer[8] = "-";
+	tkllayer[9] = "-";
+	tkllayer[10] = NULL;
+	cmd_tkl(&me, NULL, 6, tkllayer);
+
+	if (!find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft) &&
+	    !find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft))
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based ban.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+	}
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_add)
+{
+	json_t *result, *list, *item;
+	const char *name, *exception_types;
+	const char *set_by;
+	const char *error;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	const char *reason;
+	const char *str;
+	time_t tkl_expire_at;
+	time_t tkl_set_at = TStime();
+
+	if (!server_ban_exception_select_criteria(client, request, params, 1,
+	                                &name, &exception_types,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	// FIXME: where is set_by? is this missing in others as well? :p -> OPTIONAL!
+
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	/* Duration / expiry time */
+	if ((str = json_object_get_string(params, "duration_string")))
+	{
+		tkl_expire_at = config_checkval(str, CFG_TIME);
+		if (tkl_expire_at > 0)
+			tkl_expire_at = TStime() + tkl_expire_at;
+	} else
+	if ((str = json_object_get_string(params, "expire_at")))
+	{
+		tkl_expire_at = server_time_to_unix_time(str);
+	} else
+	{
+		/* Never expire */
+		tkl_expire_at = 0;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
+		return;
+	}
+
+	if (find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft) ||
+	    find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban exception with that mask already exists");
+		return;
+	}
+
+	tkl = tkl_add_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask,
+	                           NULL, reason,
+	                           set_by, tkl_expire_at, tkl_set_at,
+	                           soft, exception_types, 0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/spamfilter.c b/ircd/src/modules/rpc/spamfilter.c
@@ -0,0 +1,326 @@
+/* spamfilter.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/spamfilter",
+	"1.0.3",
+	"spamfilter.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_spamfilter_list);
+RPC_CALL_FUNC(rpc_spamfilter_get);
+RPC_CALL_FUNC(rpc_spamfilter_del);
+RPC_CALL_FUNC(rpc_spamfilter_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "spamfilter.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_spamfilter_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "spamfilter.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_spamfilter_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "spamfilter.del";
+	r.call = rpc_spamfilter_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "spamfilter.add";
+	r.call = rpc_spamfilter_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_list)
+{
+	json_t *result, *list, *item;
+	int index;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsSpamfilter(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/* Shared code for selecting a spamfilter, for .add/.del/get */
+int spamfilter_select_criteria(Client *client, json_t *request, json_t *params, const char **name, int *match_type,
+                               int *targets, char *targetbuf, size_t targetbuflen, BanAction *action, char *actionbuf)
+{
+	const char *str;
+
+	*name = json_object_get_string(params, "name");
+	if (!*name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
+		return 0;
+	}
+
+	str = json_object_get_string(params, "match_type");
+	if (!str)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'match_type'");
+		return 0;
+	}
+	*match_type = unreal_match_method_strtoval(str);
+	if (!*match_type)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'match_type'");
+		return 0;
+	}
+
+	str = json_object_get_string(params, "spamfilter_targets");
+	if (!str)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'spamfilter_targets'");
+		return 0;
+	}
+	*targets = spamfilter_gettargets(str, NULL);
+	if (!*targets)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value(s) for parameter 'spamfilter_targets'");
+		return 0;
+	}
+	strlcpy(targetbuf, spamfilter_target_inttostring(*targets), targetbuflen);
+
+	str = json_object_get_string(params, "ban_action");
+	if (!str)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'ban_action'");
+		return 0;
+	}
+	*action = banact_stringtoval(str);
+	if (!*action)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_action'");
+		return 0;
+	}
+	actionbuf[0] = banact_valtochar(*action);
+	actionbuf[1] = '\0';
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_get)
+{
+	json_t *result;
+	int type = TKL_SPAMF|TKL_GLOBAL;
+	const char *name;
+	TKL *tkl;
+	BanAction action;
+	int match_type = 0;
+	int targets = 0;
+	char targetbuf[64];
+	char actionbuf[2];
+
+	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
+		return; /* Error already communicated to client */
+
+	tkl = find_tkl_spamfilter(type, name, action, targets);
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_add)
+{
+	json_t *result;
+	int type = TKL_SPAMF|TKL_GLOBAL;
+	const char *str;
+	const char *name, *reason;
+	const char *set_by;
+	time_t ban_duration = 0;
+	TKL *tkl;
+	Match *m;
+	BanAction action;
+	int match_type = 0;
+	int targets = 0;
+	char targetbuf[64];
+	char actionbuf[2];
+	char reasonbuf[512];
+	char *err = NULL;
+
+	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
+		return; /* Error already communicated to client */
+
+	/* Reason */
+	reason = json_object_get_string(params, "reason");
+	if (!reason)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'reason'");
+		return;
+	}
+
+	/* Ban duration */
+	if ((str = json_object_get_string(params, "ban_duration")))
+	{
+		ban_duration = config_checkval(str, CFG_TIME);
+		if (ban_duration < 0)
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_duration'");
+			return;
+		}
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if (find_tkl_spamfilter(type, name, action, targets))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A spamfilter with that regex+action+target already exists");
+		return;
+	}
+
+	/* Convert reason to use internal storage and wire format */
+	reason = unreal_encodespace(reason);
+	strlcpy(reasonbuf, reason, sizeof(reasonbuf));
+	reason = reasonbuf;
+
+	/* now check the regex / match field (only when adding) */
+	m = unreal_create_match(match_type, name, &err);
+	if (!m)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid regex or match string specified");
+		return;
+	}
+
+	tkl = tkl_add_spamfilter(type, targets, action, m, set_by, 0, TStime(),
+	                         ban_duration, reason, 0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_del)
+{
+	json_t *result;
+	int type = TKL_SPAMF|TKL_GLOBAL;
+	const char *name;
+	const char *set_by;
+	TKL *tkl;
+	BanAction action;
+	int match_type = 0;
+	int targets = 0;
+	char targetbuf[64];
+	char actionbuf[2];
+	const char *tkllayer[13];
+
+	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
+		return; /* Error already communicated to client */
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	tkl = find_tkl_spamfilter(type, name, action, targets);
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	/* Wait.. this is a bit dull? */
+	tkllayer[1] = "-";
+	tkllayer[2] = "F";
+	tkllayer[3] = targetbuf;
+	tkllayer[4] = actionbuf;
+	tkllayer[5] = set_by;
+	tkllayer[6] = "-";
+	tkllayer[7] = "0";
+	tkllayer[8] = "0";
+	tkllayer[9] = "-";
+	tkllayer[10] = unreal_match_method_valtostr(match_type);
+	tkllayer[11] = name;
+	tkllayer[12] = NULL;
+
+	cmd_tkl(&me, NULL, 12, tkllayer);
+
+	tkl = find_tkl_spamfilter(type, name, action, targets);
+	if (!tkl)
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Spamfilter still exists so failure to remove.
+		 * Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based spamfilter.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+		return;
+	}
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/stats.c b/ircd/src/modules/rpc/stats.c
@@ -0,0 +1,214 @@
+/* stats.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/stats",
+	"1.0.2",
+	"stats.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+void rpc_stats_get(Client *client, json_t *request, json_t *params);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "stats.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_stats_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/stats] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void json_expand_countries(json_t *main, const char *name, NameValuePrioList *geo)
+{
+	json_t *list = json_array();
+	json_t *item;
+
+	json_object_set_new(main, name, list);
+
+	for (; geo; geo = geo->next)
+	{
+		item = json_object();
+		json_object_set_new(item, "country", json_string_unreal(geo->name));
+		json_object_set_new(item, "count", json_integer(0 - geo->priority));
+		json_array_append_new(list, item);
+	}
+}
+
+void rpc_stats_user(json_t *main, int detail)
+{
+	Client *client;
+	int total = 0, ulined = 0, oper = 0;
+	json_t *child;
+	GeoIPResult *geo;
+	NameValuePrioList *countries = NULL;
+
+	child = json_object();
+	json_object_set_new(main, "user", child);
+
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (IsUser(client))
+		{
+			total++;
+			if (IsULine(client))
+				ulined++;
+			else if (IsOper(client))
+				oper++;
+			if (detail >= 1)
+			{
+				geo = geoip_client(client);
+				if (geo && geo->country_code)
+				{
+					NameValuePrioList *e = find_nvplist(countries, geo->country_code);
+					if (e)
+					{
+						DelListItem(e, countries);
+						e->priority--;
+						AddListItemPrio(e, countries, e->priority);
+					} else {
+						add_nvplist(&countries, -1, geo->country_code, NULL);
+					}
+				}
+			}
+		}
+	}
+
+	json_object_set_new(child, "total", json_integer(total));
+	json_object_set_new(child, "ulined", json_integer(ulined));
+	json_object_set_new(child, "oper", json_integer(oper));
+	json_object_set_new(child, "record", json_integer(irccounts.global_max));
+	if (detail >= 1)
+		json_expand_countries(child, "countries", countries);
+}
+
+void rpc_stats_channel(json_t *main)
+{
+	json_t *child = json_object();
+	json_object_set_new(main, "channel", child);
+	json_object_set_new(child, "total", json_integer(irccounts.channels));
+}
+
+void rpc_stats_server(json_t *main)
+{
+	Client *client;
+	int total = 0, ulined = 0, oper = 0;
+	json_t *child = json_object();
+	json_object_set_new(main, "server", child);
+
+	total++; /* ourselves */
+	list_for_each_entry(client, &global_server_list, client_node)
+	{
+		if (IsServer(client))
+		{
+			total++;
+			if (IsULine(client))
+				ulined++;
+		}
+	}
+
+	json_object_set_new(child, "total", json_integer(total));
+	json_object_set_new(child, "ulined", json_integer(ulined));
+}
+
+void rpc_stats_server_ban(json_t *main)
+{
+	Client *client;
+	int index, index2;
+	TKL *tkl;
+	int total = 0;
+	int server_ban = 0;
+	int server_ban_exception = 0;
+	int spamfilter = 0;
+	int name_ban = 0;
+	json_t *child = json_object();
+	json_object_set_new(main, "server_ban", child);
+
+	/* First, hashed entries.. */
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				total++;
+				if (TKLIsServerBan(tkl))
+					server_ban++;
+				else if (TKLIsBanException(tkl))
+					server_ban_exception++;
+				else if (TKLIsNameBan(tkl))
+					name_ban++;
+				else if (TKLIsSpamfilter(tkl))
+					spamfilter++;
+			}
+		}
+	}
+
+	/* Now normal entries.. */
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			total++;
+			if (TKLIsServerBan(tkl))
+				server_ban++;
+			else if (TKLIsBanException(tkl))
+				server_ban_exception++;
+			else if (TKLIsNameBan(tkl))
+				name_ban++;
+			else if (TKLIsSpamfilter(tkl))
+				spamfilter++;
+		}
+	}
+
+	json_object_set_new(child, "total", json_integer(total));
+	json_object_set_new(child, "server_ban", json_integer(server_ban));
+	json_object_set_new(child, "spamfilter", json_integer(spamfilter));
+	json_object_set_new(child, "name_ban", json_integer(name_ban));
+	json_object_set_new(child, "server_ban_exception", json_integer(server_ban_exception));
+}
+
+void rpc_stats_get(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *statsname;
+	Channel *stats;
+	int details;
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 1);
+
+	result = json_object();
+	rpc_stats_server(result);
+	rpc_stats_user(result, details);
+	rpc_stats_channel(result);
+	rpc_stats_server_ban(result);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/user.c b/ircd/src/modules/rpc/user.c
@@ -0,0 +1,697 @@
+/* user.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/user",
+	"1.0.7",
+	"user.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_user_list);
+RPC_CALL_FUNC(rpc_user_get);
+RPC_CALL_FUNC(rpc_user_set_nick);
+RPC_CALL_FUNC(rpc_user_set_username);
+RPC_CALL_FUNC(rpc_user_set_realname);
+RPC_CALL_FUNC(rpc_user_set_vhost);
+RPC_CALL_FUNC(rpc_user_set_mode);
+RPC_CALL_FUNC(rpc_user_set_snomask);
+RPC_CALL_FUNC(rpc_user_set_oper);
+RPC_CALL_FUNC(rpc_user_kill);
+RPC_CALL_FUNC(rpc_user_quit);
+RPC_CALL_FUNC(rpc_user_join);
+RPC_CALL_FUNC(rpc_user_part);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "user.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_user_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_user_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_nick";
+	r.call = rpc_user_set_nick;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_username";
+	r.call = rpc_user_set_username;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_realname";
+	r.call = rpc_user_set_realname;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_vhost";
+	r.call = rpc_user_set_vhost;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_mode";
+	r.call = rpc_user_set_mode;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_snomask";
+	r.call = rpc_user_set_snomask;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_oper";
+	r.call = rpc_user_set_oper;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.kill";
+	r.call = rpc_user_kill;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.quit";
+	r.call = rpc_user_quit;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.join";
+	r.call = rpc_user_join;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.part";
+	r.call = rpc_user_part;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_user_list)
+{
+	json_t *result, *list, *item;
+	Client *acptr;
+	int details;
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2);
+	if (details == 3)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
+		return;
+	}
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (!IsUser(acptr))
+			continue;
+
+		item = json_object();
+		json_expand_client(item, NULL, acptr, details);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_get)
+{
+	json_t *result, *list, *item;
+	const char *nick;
+	Client *acptr;
+	int details;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 4);
+	if (details == 3)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
+		return;
+	}
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_client(result, "client", acptr, details);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_nick)
+{
+	json_t *result, *list, *item;
+	MessageTag *mtags = NULL;
+	const char *args[5];
+	const char *nick, *newnick_requested, *str;
+	int force = 0;
+	char newnick[NICKLEN+1];
+	char tsbuf[32];
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("newnick", newnick_requested);
+	strlcpy(newnick, newnick_requested, iConf.nick_length + 1);
+	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (!do_nick_name(newnick) || strcmp(newnick, newnick_requested) ||
+	    !strcasecmp(newnick, "IRC") || !strcasecmp(newnick, "IRCd"))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New nickname contains forbidden character(s) or is too long");
+		return;
+	}
+
+	if (!strcmp(nick, newnick))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old nickname and new nickname are identical");
+		return;
+	}
+
+	if (!force)
+	{
+		/* Check other restrictions */
+		Client *check = find_user(newnick, NULL);
+		int ishold = 0;
+
+		/* Check if in use by someone else (do allow case-changing) */
+		if (check && (acptr != check))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "New nickname is already taken by another user");
+			return;
+		}
+
+		// Can't really check for spamfilter here, since it assumes user is local
+
+		// But we can check q-lines...
+		if (find_qline(acptr, newnick, &ishold))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New nickname is forbidden by q-line");
+			return;
+		}
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = newnick;
+	snprintf(tsbuf, sizeof(tsbuf), "%lld", (long long)TStime());
+	args[3] = tsbuf;
+	args[4] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "SVSNICK", 4, args);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_username)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *username, *str;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("username", username);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (!valid_username(username))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New username contains forbidden character(s) or is too long");
+		return;
+	}
+
+	if (!strcmp(acptr->user->username, username))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new user name are identical");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = username;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "CHGIDENT", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if (!strcmp(acptr->user->username, username))
+		result = json_boolean(1);
+	else
+		result = json_boolean(0);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_realname)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *realname, *str;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("realname", realname);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (strlen(realname) > REALLEN)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New real name is too long");
+		return;
+	}
+
+	if (!strcmp(acptr->info, realname))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new real name are identical");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = realname;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "CHGNAME", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if (!strcmp(acptr->info, realname))
+		result = json_boolean(1);
+	else
+		result = json_boolean(0);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_vhost)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *vhost, *str;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("vhost", vhost);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if ((strlen(vhost) > HOSTLEN) || !valid_host(vhost, 0))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New vhost contains forbidden character(s) or is too long");
+		return;
+	}
+
+	if (!strcmp(GetHost(acptr), vhost))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new vhost are identical");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = vhost;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "CHGHOST", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if (!strcmp(GetHost(acptr), vhost))
+		result = json_boolean(1);
+	else
+		result = json_boolean(0);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_mode)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *modes, *str;
+	int hidden;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("modes", modes);
+	OPTIONAL_PARAM_BOOLEAN("hidden", hidden, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = modes;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, hidden ? "SVSMODE" : "SVS2MODE", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result (always true) */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_snomask)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *snomask, *str;
+	int hidden;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("snomask", snomask);
+	OPTIONAL_PARAM_BOOLEAN("hidden", hidden, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = snomask;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, hidden ? "SVSSNO" : "SVS2SNO", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result (always true) */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_oper)
+{
+	json_t *result, *list, *item;
+	const char *args[9];
+	const char *nick, *oper_account, *oper_class;
+	const char *class=NULL, *modes=NULL, *snomask=NULL, *vhost=NULL;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+	char default_modes[64];
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("oper_account", oper_account);
+	REQUIRE_PARAM_STRING("oper_class", oper_class);
+	OPTIONAL_PARAM_STRING("class", class);
+	OPTIONAL_PARAM_STRING("modes", modes);
+	OPTIONAL_PARAM_STRING("snomask", snomask);
+	OPTIONAL_PARAM_STRING("vhost", vhost);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (modes == NULL)
+	{
+		strlcpy(default_modes, get_usermode_string_raw(OPER_MODES), sizeof(default_modes));
+		modes = default_modes;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = oper_account;
+	args[3] = oper_class;
+	args[4] = class ? class : "opers";
+	args[5] = modes;
+	args[6] = snomask ? snomask : iConf.oper_snomask;
+	args[7] = vhost ? vhost : "-";
+	args[8] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "SVSO", 8, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result (always true) */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_kill)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *reason;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = reason;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "KILL", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if ((acptr = find_user(nick, NULL)) && !IsDead(acptr))
+		result = json_boolean(0);
+	else
+		result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_quit)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *reason;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = reason;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "SVSKILL", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if ((acptr = find_user(nick, NULL)) && !IsDead(acptr))
+		result = json_boolean(0);
+	else
+		result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_join)
+{
+	json_t *result, *list, *item;
+	const char *args[5];
+	const char *nick, *channel, *key=NULL;
+	Client *acptr;
+	MessageTag *mtags = NULL;
+	int force = 0;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("channel", channel);
+	OPTIONAL_PARAM_STRING("key", key);
+	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = channel;
+
+	if (force == 0)
+	{
+		args[3] = key;
+		args[4] = NULL;
+		do_cmd(&me, mtags, "SVSJOIN", key ? 4 : 3, args);
+	} else {
+		args[3] = NULL;
+		do_cmd(&me, mtags, "SAJOIN", 3, args);
+	}
+
+	safe_free_message_tags(mtags);
+
+	/* Return result -- yeah this is always true, not so good.. :D
+	 * It is that way because we (this server) may not actually
+	 * do the SVSJOIN at all, we may be just relaying it to some
+	 * other server.
+	 */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_part)
+{
+	json_t *result, *list, *item;
+	const char *args[5];
+	const char *nick, *channel, *reason=NULL;
+	Client *acptr;
+	MessageTag *mtags = NULL;
+	int force = 0;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("channel", channel);
+	OPTIONAL_PARAM_STRING("reason", reason);
+	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = channel;
+	args[3] = reason;
+	args[4] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, NULL, force ? "SAPART" : "SVSPART", reason ? 4 : 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result. Always 'true' at the moment.
+	 * Technically we could check if the user is in all of these channels.
+	 * But then again, do we really want to return failure if one specified
+	 * channel does not exist out of X channels to be parted? Not worth it.
+	 */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rpc/whowas.c b/ircd/src/modules/rpc/whowas.c
@@ -0,0 +1,148 @@
+/* whowas.* RPC calls
+ * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/whowas",
+	"1.0.0",
+	"whowas.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Externals */
+extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_whowas_get);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "whowas.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_whowas_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/whowas] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+const char *whowas_event_to_string(WhoWasEvent event)
+{
+	if (event == WHOWAS_EVENT_QUIT)
+		return "quit";
+	if (event == WHOWAS_EVENT_NICK_CHANGE)
+		return "nick-change";
+	if (event == WHOWAS_EVENT_SERVER_TERMINATING)
+		return "server-terminating";
+	return "unknown";
+}
+
+void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail)
+{
+	json_t *child;
+	json_t *user = NULL;
+	char buf[BUFSIZE+1];
+
+	if (key)
+	{
+		child = json_object();
+		json_object_set_new(j, key, child);
+	} else {
+		child = j;
+	}
+
+	json_object_set_new(child, "name", json_string_unreal(e->name));
+	json_object_set_new(child, "event", json_string_unreal(whowas_event_to_string(e->event)));
+	json_object_set_new(child, "logon_time", json_timestamp(e->logon));
+	json_object_set_new(child, "logoff_time", json_timestamp(e->logoff));
+
+	if (detail == 0)
+		return;
+
+	json_object_set_new(child, "hostname", json_string_unreal(e->hostname));
+	json_object_set_new(child, "ip", json_string_unreal(e->ip));
+
+	snprintf(buf, sizeof(buf), "%s!%s@%s", e->name, e->username, e->hostname);
+	json_object_set_new(child, "details", json_string_unreal(buf));
+
+	if (detail < 2)
+		return;
+
+	if (e->connected_since)
+		json_object_set_new(child, "connected_since", json_timestamp(e->connected_since));
+
+	/* client.user */
+	user = json_object();
+	json_object_set_new(child, "user", user);
+
+	json_object_set_new(user, "username", json_string_unreal(e->username));
+	if (!BadPtr(e->realname))
+		json_object_set_new(user, "realname", json_string_unreal(e->realname));
+	if (!BadPtr(e->virthost))
+		json_object_set_new(user, "vhost", json_string_unreal(e->virthost));
+	json_object_set_new(user, "servername", json_string_unreal(e->servername));
+	if (!BadPtr(e->account))
+		json_object_set_new(user, "account", json_string_unreal(e->account));
+}
+
+RPC_CALL_FUNC(rpc_whowas_get)
+{
+	json_t *result, *list, *item;
+	int details;
+	int i;
+	const char *nick;
+	const char *ip;
+
+	OPTIONAL_PARAM_STRING("nick", nick);
+	OPTIONAL_PARAM_STRING("ip", ip);
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2);
+	if (details == 3)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
+		return;
+	}
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		WhoWas *e = &WHOWAS[i];
+		if (!e->name)
+			continue;
+		if (nick && !match_simple(nick, e->name))
+			continue;
+		if (ip && !match_simple(ip, e->ip))
+			continue;
+		item = json_object();
+		json_expand_whowas(item, NULL, e, details);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/ircd/src/modules/rules.c b/ircd/src/modules/rules.c
@@ -0,0 +1,89 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_rules);
+
+#define MSG_RULES 	"RULES"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"rules",
+	"5.0",
+	"command /rules", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_RULES, cmd_rules, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * Heavily modified from the ircu cmd_motd by codemastr
+ * Also svsmotd support added
+ */
+CMD_FUNC(cmd_rules)
+{
+	ConfigItem_tld *tld;
+	MOTDLine *temp;
+
+	if (hunt_server(client, recv_mtags, "RULES", 1, parc, parv) != HUNTED_ISME)
+		return;
+
+	tld = find_tld(client);
+	if (tld && tld->rules.lines)
+		temp = tld->rules.lines;
+	else
+		temp = rules.lines;
+
+	if (temp == NULL)
+	{
+		sendnumeric(client, ERR_NORULES);
+		return;
+
+	}
+
+	sendnumeric(client, RPL_RULESSTART, me.name);
+
+	while (temp)
+	{
+		sendnumeric(client, RPL_RULES,
+		    temp->line);
+		temp = temp->next;
+	}
+	sendnumeric(client, RPL_ENDOFRULES);
+}
diff --git a/ircd/src/modules/sajoin.c b/ircd/src/modules/sajoin.c
@@ -0,0 +1,294 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sajoin.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_sajoin);
+
+#define MSG_SAJOIN 	"SAJOIN"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"sajoin",
+	"5.0",
+	"command /sajoin", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SAJOIN, cmd_sajoin, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+static void log_sajoin(Client *client, MessageTag *mtags, Client *target, const char *channels)
+{
+	const char *issuer = command_issued_by_rpc(mtags);
+	if (issuer)
+	{
+		unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $issuer used SAJOIN to make $target join $channels",
+			   log_data_string("issuer", issuer),
+			   log_data_client("target", target),
+			   log_data_string("channels", channels));
+	} else {
+		unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $client used SAJOIN to make $target join $channels",
+			   log_data_client("target", target),
+			   log_data_string("channels", channels));
+	}
+}
+
+/* cmd_sajoin() - Lamego - Wed Jul 21 20:04:48 1999
+   Copied off PTlink IRCd (C) PTlink coders team.
+   Coded for Sadmin by Stskeeps
+   also Modified by NiQuiL (niquil@programmer.net)
+	parv[1] - nick to make join
+	parv[2] - channel(s) to join
+*/
+CMD_FUNC(cmd_sajoin)
+{
+	Client *target;
+	char request[BUFSIZE];
+	char jbuf[BUFSIZE];
+	int did_anything = 0;
+	int ntargets = 0;
+	int maxtargets = max_targets_for_command("SAJOIN");
+
+	if (parc < 3)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SAJOIN");
+		return;
+	}
+
+	if (!(target = find_user(parv[1], NULL)))
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		return;
+	}
+
+	/* Is this user disallowed from operating on this victim at all? */
+	if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sajoin",client,target,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		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 only log... */
+	if (!MyUser(target))
+	{
+		log_sajoin(client, recv_mtags, 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 parted = 0;
+	
+		*jbuf = 0;
+
+		/* Now works like cmd_join */
+		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)
+			{
+				sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "SAJOIN");
+				break;
+			}
+
+			mode = prefix_to_mode(*name);
+			if (mode)
+			{
+				prefix = *name;
+				name++; /* skip the prefix */
+			}
+
+			if (strlen(name) > CHANNELLEN)
+			{
+				sendnotice(client, "Channel name too long: %s", name);
+				continue;
+			}
+
+			if (*name == '0' && !atoi(name) && !mode)
+			{
+				strlcpy(jbuf, "0", sizeof(jbuf));
+				parted = 1;
+				continue;
+			}
+
+			if (!valid_channelname(name))
+			{
+				send_invalid_channelname(client, name);
+				continue;
+			}
+
+			channel = make_channel(name);
+
+			/* If this _specific_ channel is not permitted, skip it */
+			if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sajoin",client,target,channel,NULL))
+			{
+				sendnumeric(client, ERR_NOPRIVILEGES);
+				continue;
+			}
+
+			if (!parted && channel && (lp = find_membership_link(target->user->channel, channel)))
+			{
+				sendnumeric(client, ERR_USERONCHANNEL, target->name, name);
+				continue;
+			}
+			if (*jbuf)
+				strlcat(jbuf, ",", sizeof(jbuf));
+			if (prefix)
+				strlcat_letter(jbuf, prefix, sizeof(jbuf));
+			strlcat(jbuf, name, sizeof(jbuf));
+		}
+		if (!*jbuf)
+			return;
+
+		strlcpy(request, jbuf, sizeof(request));
+		*jbuf = 0;
+		for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
+		{
+			MessageTag *mtags = NULL;
+			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) && !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
+				 */
+				did_anything = 1;
+				while ((lp = target->user->channel))
+				{
+					MessageTag *mtags = NULL;
+					channel = lp->channel;
+
+					new_message(target, NULL, &mtags);
+					mtag_add_issued_by(&mtags, client, recv_mtags);
+					sendto_channel(channel, target, NULL, 0, 0, SEND_LOCAL, mtags,
+					               ":%s PART %s :%s",
+					               target->name, channel->name, "Left all channels");
+					sendto_server(NULL, 0, 0, mtags, ":%s PART %s :Left all channels", target->name, channel->name);
+					if (MyConnect(target))
+						RunHook(HOOKTYPE_LOCAL_PART, target, channel, mtags, "Left all channels");
+					free_message_tags(mtags);
+					remove_user_from_channel(target, channel, 0);
+				}
+				strlcpy(jbuf, "0", sizeof(jbuf));
+				continue;
+			}
+			member_modes = (ChannelExists(name)) ? "" : LEVEL_ON_JOIN;
+			channel = make_channel(name);
+			if (channel && (lp = find_membership_link(target->user->channel, channel)))
+				continue;
+
+			i = HOOK_CONTINUE;
+			for (h = Hooks[HOOKTYPE_CAN_SAJOIN]; h; h = h->next)
+			{
+				i = (*(h->func.intfunc))(target,channel,client);
+				if (i != HOOK_CONTINUE)
+					break;
+			}
+
+			if (i == HOOK_DENY)
+				continue; /* process next channel */
+
+			/* Generate a new message without inheritance.
+			 * We can do this because we are the server that
+			 * will send a JOIN for each channel due to this loop.
+			 * Each with their own unique msgid.
+			 */
+			new_message(target, NULL, &mtags);
+			mtag_add_issued_by(&mtags, client, recv_mtags);
+			join_channel(channel, target, mtags, member_modes);
+			if (prefix)
+			{
+				char *modes;
+				const char *mode_args[3];
+
+				opermode = 0;
+				sajoinmode = 1;
+
+				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(modes);
+			}
+			free_message_tags(mtags);
+			did_anything = 1;
+			if (*jbuf)
+				strlcat(jbuf, ",", sizeof jbuf);
+			strlcat(jbuf, name, sizeof jbuf);
+		}
+
+		//if (did_anything)
+		//{
+		//	sendnotice(target, "*** You were forced to join %s", jbuf);
+		//	log_sajoin(client, recv_mtags, target, jbuf);
+		//}
+	}
+}
diff --git a/ircd/src/modules/samode.c b/ircd/src/modules/samode.c
@@ -0,0 +1,89 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/samode.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_samode);
+
+#define MSG_SAMODE 	"SAMODE"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"samode",
+	"5.0",
+	"command /samode", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SAMODE, cmd_samode, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * cmd_samode
+ * parv[1] = channel
+ * parv[2] = modes
+ * -t
+ */
+CMD_FUNC(cmd_samode)
+{
+	Channel *channel;
+	MessageTag *mtags = NULL;
+
+	if (parc <= 2)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SAMODE");
+		return;
+	}
+
+	channel = find_channel(parv[1]);
+	if (!channel)
+	{
+		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+		return;
+	}
+
+	if (!ValidatePermissionsForPath("sacmd:samode", client, NULL, channel, NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	opermode = 0;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_mode(channel, client, mtags, parc - 2, parv + 2, 0, 1);
+	safe_free_message_tags(mtags);
+}
diff --git a/ircd/src/modules/sapart.c b/ircd/src/modules/sapart.c
@@ -0,0 +1,210 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sapart.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_sapart);
+
+#define MSG_SAPART 	"SAPART"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"sapart",
+	"5.0",
+	"command /sapart", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SAPART, cmd_sapart, 3, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+static void log_sapart(Client *client, MessageTag *mtags, Client *target, const char *channels, const char *comment)
+{
+	const char *issuer = command_issued_by_rpc(mtags);
+
+	if (issuer)
+	{
+		if (comment)
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $issuer used SAPART to make $target part $channels ($reason)",
+				   log_data_string("issuer", issuer),
+				   log_data_client("target", target),
+				   log_data_string("channels", channels),
+				   log_data_string("reason", comment));
+		}
+		else
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $issuer used SAPART to make $target part $channels",
+				   log_data_string("issuer", issuer),
+				   log_data_client("target", target),
+				   log_data_string("channels", channels));
+		}
+	} else {
+		if (comment)
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels ($reason)",
+				   log_data_client("target", target),
+				   log_data_string("channels", channels),
+				   log_data_string("reason", comment));
+		}
+		else
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels",
+				   log_data_client("target", target),
+				   log_data_string("channels", channels));
+		}
+	}
+}
+
+
+/* cmd_sapart() - Lamego - Wed Jul 21 20:04:48 1999
+   Copied off PTlink IRCd (C) PTlink coders team.
+   Coded for Sadmin by Stskeeps
+   also Modified by NiQuiL (niquil@programmer.net)
+	parv[1] - nick to make part
+	parv[2] - channel(s) to part
+	parv[3] - comment
+*/
+
+CMD_FUNC(cmd_sapart)
+{
+	Client *target;
+	Channel *channel;
+	MessageTag *mtags = NULL;
+	Membership *lp;
+	char *name, *p = NULL;
+	int i;
+	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");
+
+	if ((parc < 3) || BadPtr(parv[2]))
+        {
+                sendnumeric(client, ERR_NEEDMOREPARAMS, "SAPART");
+                return;
+        }
+
+        if (!(target = find_user(parv[1], NULL)))
+        {
+                sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+                return;
+        }
+
+	/* See if we can operate on this vicim/this command */
+	if (!ValidatePermissionsForPath("sacmd:sapart",client,target,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	/* 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))
+	{
+		log_sapart(client, recv_mtags, target, parv[2], comment);
+		return;
+	}
+
+	/* 'target' is our client... */
+
+	*jbuf = 0;
+	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 = find_channel(name)))
+		{
+			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
+			continue;
+		}
+
+		/* Validate oper can do this on chan/victim */
+		if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sapart",client,target,channel,NULL))
+		{
+			sendnumeric(client, ERR_NOPRIVILEGES);
+			continue;
+		}
+
+		if (!(lp = find_membership_link(target->user->channel, channel)))
+		{
+			sendnumeric(client, ERR_USERNOTINCHANNEL, target->name, name);
+			continue;
+		}
+		if (*jbuf)
+			strlcat(jbuf, ",", sizeof jbuf);
+		strlncat(jbuf, name, sizeof jbuf, sizeof(jbuf) - i - 1);
+		i += strlen(name) + 1;
+	}
+
+	if (!*jbuf)
+		return;
+
+	strlcpy(request, jbuf, sizeof(request));
+
+	log_sapart(client, recv_mtags, target, request, comment);
+
+	//if (comment)
+	//{
+	//	snprintf(commentx, sizeof(commentx), "SAPart: %s", comment);
+	//	sendnotice(target, "*** You were forced to part %s (%s)", request, commentx);
+	//} else {
+	//	sendnotice(target, "*** You were forced to part %s", request);
+	//}
+
+	parv[0] = target->name; // nick
+	parv[1] = request; // chan
+	parv[2] = comment ? commentx : NULL; // comment
+
+	/* Now, do the actual parting: */
+	mtag_add_issued_by(&mtags, client, recv_mtags);
+	do_cmd(target, mtags, "PART", comment ? 3 : 2, parv);
+	safe_free_message_tags(mtags);
+
+	/* NOTE: target may be killed now due to the part reason @ spamfilter */
+}
diff --git a/ircd/src/modules/sasl.c b/ircd/src/modules/sasl.c
@@ -0,0 +1,412 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sasl.c
+ *   (C) 2012 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
+  = {
+	"sasl",
+	"5.2.1",
+	"SASL", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+void saslmechlist_free(ModData *m);
+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);
+
+/* Macros */
+#define MSG_AUTHENTICATE "AUTHENTICATE"
+#define MSG_SASL "SASL"
+#define AGENT_SID(agent_p)	(agent_p->user != NULL ? agent_p->user->server : agent_p->name)
+
+/* Variables */
+long CAP_SASL = 0L;
+
+/*
+ * The following people were involved in making the previous iteration of SASL over
+ * IRC which allowed psuedo-identifiers:
+ *
+ * danieldg, Daniel de Graff <danieldg@inspircd.org>
+ * jilles, Jilles Tjoelker <jilles@stack.nl>
+ * Jobe, Matthew Beeching <jobe@mdbnet.co.uk>
+ * gxti, Michael Tharp <gxti@partiallystapled.com>
+ * nenolod, William Pitcock <nenolod@dereferenced.org>
+ *
+ * Thanks also to all of the client authors which have implemented SASL in their
+ * clients.  With the backwards-compatibility layer allowing "lightweight" SASL
+ * implementations, we now truly have a universal authentication mechanism for
+ * IRC.
+ */
+
+int sasl_account_login(Client *client, MessageTag *mtags)
+{
+	if (!MyConnect(client))
+		return 0;
+
+	/* Notify user */
+	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->account, client->user->account);
+	}
+	else
+	{
+		sendnumeric(client, RPL_LOGGEDOUT,
+			BadPtr(client->name) ? "*" : client->name,
+			BadPtr(client->user->username) ? "*" : client->user->username,
+			BadPtr(client->user->realhost) ? "*" : client->user->realhost);
+	}
+	return 0;
+}
+
+
+/*
+ * SASL message
+ *
+ * parv[1]: distribution mask
+ * parv[2]: target
+ * parv[3]: mode/state
+ * parv[4]: data
+ * parv[5]: out-of-bound data
+ */
+CMD_FUNC(cmd_sasl)
+{
+	if (!SASL_SERVER || MyUser(client) || (parc < 4) || !parv[4])
+		return;
+
+	if (!strcasecmp(parv[1], me.name) || !strncmp(parv[1], me.id, 3))
+	{
+		Client *target;
+
+		target = find_client(parv[2], NULL);
+		if (!target || !MyConnect(target))
+			return;
+
+		if (target->user == NULL)
+			make_user(target);
+
+		/* reject if another SASL agent is answering */
+		if (*target->local->sasl_agent && strcasecmp(client->name, target->local->sasl_agent))
+			return;
+		else
+			strlcpy(target->local->sasl_agent, client->name, sizeof(target->local->sasl_agent));
+
+		if (*parv[3] == 'C')
+		{
+			RunHookReturn(HOOKTYPE_SASL_CONTINUATION, !=0, target, parv[4]);
+			sendto_one(target, NULL, "AUTHENTICATE %s", parv[4]);
+		}
+		else if (*parv[3] == 'D')
+		{
+			*target->local->sasl_agent = '\0';
+			if (*parv[4] == 'F')
+			{
+				target->local->sasl_sent_time = 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++;
+				RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 1);
+				sendnumeric(target, RPL_SASLSUCCESS);
+			}
+		}
+		else if (*parv[3] == 'M')
+			sendnumeric(target, RPL_SASLMECHS, parv[4]);
+
+		return;
+	}
+
+	/* not for us; propagate. */
+	sendto_server(client, 0, 0, NULL, ":%s SASL %s %s %c %s %s",
+	    client->name, parv[1], parv[2], *parv[3], parv[4], parc > 5 ? parv[5] : "");
+}
+
+/*
+ * AUTHENTICATE message
+ *
+ * parv[1]: data
+ */
+CMD_FUNC(cmd_authenticate)
+{
+	Client *agent_p = NULL;
+
+	/* Failing to use CAP REQ for sasl is a protocol violation. */
+	if (!SASL_SERVER || !MyConnect(client) || BadPtr(parv[1]) || !HasCapability(client, "sasl"))
+		return;
+
+	if ((parv[1][0] == ':') || strchr(parv[1], ' '))
+	{
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "AUTHENTICATE", "Invalid parameter");
+		return;
+	}
+
+	if (strlen(parv[1]) > 400)
+	{
+		sendnumeric(client, ERR_SASLTOOLONG);
+		return;
+	}
+
+	if (client->user == NULL)
+		make_user(client);
+
+	if (*client->local->sasl_agent)
+		agent_p = find_client(client->local->sasl_agent, NULL);
+
+	if (agent_p == NULL)
+	{
+		char *addr = BadPtr(client->ip) ? "0" : client->ip;
+		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);
+
+		if (certfp)
+			sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s %s",
+			    me.name, SASL_SERVER, client->id, parv[1], certfp);
+		else
+			sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s",
+			    me.name, SASL_SERVER, client->id, parv[1]);
+	}
+	else
+		sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s C %s",
+		    me.name, AGENT_SID(agent_p), client->id, parv[1]);
+
+	client->local->sasl_out++;
+	client->local->sasl_sent_time = TStime();
+}
+
+static int abort_sasl(Client *client)
+{
+	client->local->sasl_sent_time = 0;
+
+	if (client->local->sasl_out == 0 || client->local->sasl_complete)
+		return 0;
+
+	client->local->sasl_out = client->local->sasl_complete = 0;
+	sendnumeric(client, ERR_SASLABORTED);
+
+	if (*client->local->sasl_agent)
+	{
+		Client *agent_p = find_client(client->local->sasl_agent, NULL);
+
+		if (agent_p != NULL)
+		{
+			sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s D A",
+			    me.name, AGENT_SID(agent_p), client->id);
+			return 0;
+		}
+	}
+
+	sendto_server(NULL, 0, 0, NULL, ":%s SASL * %s D A", me.name, client->id);
+	return 0;
+}
+
+/** Is this capability visible?
+ * Note that 'client' may be NULL when queried from CAP DEL / CAP NEW
+ */
+int sasl_capability_visible(Client *client)
+{
+	if (!SASL_SERVER || !find_server(SASL_SERVER, NULL))
+		return 0;
+
+	/* Don't advertise 'sasl' capability if we are going to reject the
+	 * user anyway due to set::plaintext-policy. This way the client
+	 * won't attempt SASL authentication and thus it prevents the client
+	 * from sending the password unencrypted (in case of method PLAIN).
+	 */
+	if (client && !IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
+		return 0;
+
+	/* Similarly, don't advertise when we are going to reject the user
+	 * due to set::outdated-tls-policy.
+	 */
+	if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
+		return 0;
+
+	return 1;
+}
+
+int sasl_connect(Client *client)
+{
+	return abort_sasl(client);
+}
+
+int sasl_quit(Client *client, MessageTag *mtags, const char *comment)
+{
+	return abort_sasl(client);
+}
+
+int sasl_server_quit(Client *client, MessageTag *mtags)
+{
+	if (!SASL_SERVER)
+		return 0;
+
+	/* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */
+	if (!strcasecmp(client->name, SASL_SERVER))
+		send_cap_notify(0, "sasl");
+
+	return 0;
+}
+
+void auto_discover_sasl_server(int justlinked)
+{
+	if (!SASL_SERVER && SERVICES_NAME)
+	{
+		Client *client = find_server(SERVICES_NAME, NULL);
+		if (client && moddata_client_get(client, "saslmechlist"))
+		{
+			/* SASL server found */
+			if (justlinked)
+			{
+				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)
+				sasl_server_synced(client);
+		}
+	}
+}
+
+int sasl_server_synced(Client *client)
+{
+	if (!SASL_SERVER)
+	{
+		auto_discover_sasl_server(1);
+		return 0;
+	}
+
+	/* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */
+	if (!strcasecmp(client->name, SASL_SERVER))
+		send_cap_notify(1, "sasl");
+
+	return 0;
+}
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	CommandAdd(modinfo->handle, MSG_SASL, cmd_sasl, MAXPARA, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, MSG_AUTHENTICATE, cmd_authenticate, MAXPARA, CMD_UNREGISTERED|CMD_USER);
+
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, sasl_connect);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, sasl_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, sasl_server_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_SYNCED, 0, sasl_server_synced);
+	HookAdd(modinfo->handle, HOOKTYPE_ACCOUNT_LOGIN, 0, sasl_account_login);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "sasl";
+	cap.visible = sasl_capability_visible;
+	cap.parameter = sasl_capability_parameter;
+	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SASL);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "saslmechlist";
+	mreq.free = saslmechlist_free;
+	mreq.serialize = saslmechlist_serialize;
+	mreq.unserialize = saslmechlist_unserialize;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.self_write = 1;
+	mreq.type = MODDATATYPE_CLIENT;
+	ModDataAdd(modinfo->handle, mreq);
+
+	EventAdd(modinfo->handle, "sasl_timeout", sasl_timeout, NULL, 2000, 0);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	auto_discover_sasl_server(0);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void saslmechlist_free(ModData *m)
+{
+	safe_free(m->str);
+}
+
+const char *saslmechlist_serialize(ModData *m)
+{
+	if (!m->str)
+		return NULL;
+	return m->str;
+}
+
+void saslmechlist_unserialize(const char *str, ModData *m)
+{
+	safe_strdup(m->str, str);
+}
+
+const char *sasl_capability_parameter(Client *client)
+{
+	Client *server;
+
+	if (SASL_SERVER)
+	{
+		server = find_server(SASL_SERVER, NULL);
+		if (server)
+			return moddata_client_get(server, "saslmechlist"); /* NOTE: could still return NULL */
+	}
+
+	return NULL;
+}
+
+EVENT(sasl_timeout)
+{
+	Client *client;
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (client->local->sasl_sent_time &&
+		    (TStime() - client->local->sasl_sent_time > iConf.sasl_timeout))
+		{
+			sendnotice(client, "SASL request timed out (server or client misbehaving) -- aborting SASL and continuing connection...");
+			abort_sasl(client);
+		}
+	}
+}
diff --git a/ircd/src/modules/sdesc.c b/ircd/src/modules/sdesc.c
@@ -0,0 +1,92 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sdesc.c
+ *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
+ *
+ *   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_sdesc);
+
+#define MSG_SDESC 	"SDESC"	/* sdesc */
+
+ModuleHeader MOD_HEADER
+  = {
+	"sdesc",	/* Name of module */
+	"5.0", /* Version */
+	"command /sdesc", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SDESC, cmd_sdesc, 1, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* cmd_sdesc - 15/05/1999 - Stskeeps
+ *  :prefix SDESC
+ *  parv[1] - description
+ *  D: Sets server info if you are Server Admin (ONLINE)
+*/
+
+CMD_FUNC(cmd_sdesc)
+{
+	if (!ValidatePermissionsForPath("server:description",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SDESC");
+		return;
+	}
+
+	if (strlen(parv[1]) > REALLEN)
+	{
+		if (MyConnect(client))
+		{
+			sendnotice(client, "*** /SDESC Error: \"Server info\" may maximum be %i characters of length",
+				REALLEN);
+			return;
+		}
+	}
+
+	strlncpy(client->uplink->info, parv[1], sizeof(client->uplink->info), REALLEN);
+
+	sendto_server(client, 0, 0, NULL, ":%s SDESC :%s", client->name, parv[1]);
+
+	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/ircd/src/modules/sendsno.c b/ircd/src/modules/sendsno.c
@@ -0,0 +1,101 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/sendsno.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_sendsno);
+
+#define MSG_SENDSNO   "SENDSNO"
+
+ModuleHeader MOD_HEADER
+  = {
+	"sendsno",	/* Name of module */
+	"5.0", /* Version */
+	"command /sendsno", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SENDSNO, cmd_sendsno, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_sendsno - Written by Syzop, bit based on SENDUMODE from Stskeeps
+**      parv[1] = target snomask
+**      parv[2] = message text
+** Servers can use this to:
+**   :server.unreal.net SENDSNO e :Hiiiii
+*/
+CMD_FUNC(cmd_sendsno)
+{
+	MessageTag *mtags = NULL;
+	const char *sno, *msg, *p;
+	Client *acptr;
+
+	if ((parc < 3) || BadPtr(parv[2]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SENDSNO");
+		return;
+	}
+	sno = parv[1];
+	msg = parv[2];
+
+	new_message(client, recv_mtags, &mtags);
+
+	/* Forward to others... */
+	sendto_server(client, 0, 0, mtags, ":%s SENDSNO %s :%s", client->id, parv[1], parv[2]);
+
+	list_for_each_entry(acptr, &oper_list, special_node)
+	{
+		if (acptr->user->snomask)
+		{
+			char found = 0;
+			for (p = sno; *p; p++)
+			{
+				if (strchr(acptr->user->snomask, *p))
+				{
+					found = 1;
+					break;
+				}
+			}
+			if (found)
+				sendto_one(acptr, mtags, ":%s NOTICE %s :%s", client->name, acptr->name, msg);
+		}
+	}
+
+	free_message_tags(mtags);
+}
diff --git a/ircd/src/modules/sendumode.c b/ircd/src/modules/sendumode.c
@@ -0,0 +1,95 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/sendumode.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_sendumode);
+
+/* Place includes here */
+#define MSG_SENDUMODE   "SENDUMODE"
+#define MSG_SMO         "SMO"
+
+ModuleHeader MOD_HEADER
+  = {
+	"sendumode",	/* Name of module */
+	"5.0", /* Version */
+	"command /sendumode", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SENDUMODE, cmd_sendumode, MAXPARA, CMD_SERVER);
+	CommandAdd(modinfo->handle, MSG_SMO, cmd_sendumode, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** SENDUMODE - Send to usermode command (S2S traffic only).
+ * parv[1] = target user modes
+ * parv[2] = message text
+ * For example:
+ * :server SENDUMODE o :Serious problem: blablabla
+ */
+CMD_FUNC(cmd_sendumode)
+{
+	MessageTag *mtags = NULL;
+	Client *acptr;
+	const char *message;
+	const char *p;
+	int i;
+	long umode_s = 0;
+
+	message = (parc > 3) ? parv[3] : parv[2];
+
+	if (parc < 3)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SENDUMODE");
+		return;
+	}
+
+	new_message(client, recv_mtags, &mtags);
+
+	sendto_server(client, 0, 0, mtags, ":%s SENDUMODE %s :%s", client->id, parv[1], message);
+
+	umode_s = set_usermode(parv[1]);
+
+	list_for_each_entry(acptr, &oper_list, special_node)
+	{
+		if (acptr->umodes & umode_s)
+			sendto_one(acptr, mtags, ":%s NOTICE %s :%s", client->name, acptr->name, message);
+	}
+
+	free_message_tags(mtags);
+}
diff --git a/ircd/src/modules/server-time.c b/ircd/src/modules/server-time.c
@@ -0,0 +1,99 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/server-time.c
+ *   (C) 2019 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"server-time",
+	"5.0",
+	"server-time CAP",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_SERVER_TIME = 0L;
+
+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()
+{
+	ClientCapabilityInfo cap;
+	ClientCapability *c;
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "server-time";
+	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SERVER_TIME);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "time";
+	mtag.is_ok = server_time_mtag_is_ok;
+	mtag.clicap_handler = c;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_or_inherit_time);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending
+ * 'server-time' is permitted to do so and uses a permitted
+ * syntax.
+ * We simply allow server-time ONLY from servers.
+ */
+int server_time_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client) && !BadPtr(value))
+		return 1;
+
+	return 0;
+}
+
+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)
+	{
+		m = duplicate_mtag(m);
+	} else
+	{
+		m = safe_alloc(sizeof(MessageTag));
+		safe_strdup(m->name, "time");
+		safe_strdup(m->value, timestamp_iso8601_now());
+	}
+	AddListItem(m, *mtag_list);
+}
diff --git a/ircd/src/modules/server.c b/ircd/src/modules/server.c
@@ -0,0 +1,2259 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/server.c
+ *   (C) 2004-present 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"
+
+/* 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;
+};
+
+typedef struct ConfigItem_deny_link ConfigItem_deny_link;
+struct ConfigItem_deny_link {
+	ConfigItem_deny_link *prev, *next;
+	ConfigFlag_except flag;
+	ConfigItem_mask  *mask;
+	CRuleNode *rule; /**< parsed crule */
+	char *prettyrule; /**< human printable version */
+	char *reason; /**< Reason for the deny link */
+};
+
+/* Forward declarations */
+void server_config_setdefaults(cfgstruct *cfg);
+void server_config_free();
+int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(server_autoconnect);
+EVENT(server_handshake_timeout);
+void send_channel_modes_sjoin3(Client *to, Channel *channel);
+CMD_FUNC(cmd_server);
+CMD_FUNC(cmd_sid);
+ConfigItem_link *_verify_link(Client *client);
+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 *);
+int _is_services_but_not_ulined(Client *client);
+const char *_check_deny_link(ConfigItem_link *link, int auto_connect);
+int server_stats_denylink_all(Client *client, const char *para);
+int server_stats_denylink_auto(Client *client, const char *para);
+
+/* Global variables */
+static cfgstruct cfg;
+static char *last_autoconnect_server = NULL;
+static ConfigItem_deny_link *conf_deny_link = NULL;
+
+ModuleHeader MOD_HEADER
+  = {
+	"server",
+	"5.0",
+	"command /server", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_PROTOCTL_SERVERS, _send_protoctl_servers);
+	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_SERVER_MESSAGE, _send_server_message);
+	EfunctionAddPVoid(modinfo->handle, EFUNC_VERIFY_LINK, TO_PVOIDFUNC(_verify_link));
+	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);
+	EfunctionAdd(modinfo->handle, EFUNC_IS_SERVICES_BUT_NOT_ULINED, _is_services_but_not_ulined);
+	EfunctionAddConstString(modinfo->handle, EFUNC_CHECK_DENY_LINK, _check_deny_link);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, server_config_test);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	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);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, server_stats_denylink_all);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, server_stats_denylink_auto);
+	CommandAdd(modinfo->handle, "SERVER", cmd_server, MAXPARA, CMD_UNREGISTERED|CMD_SERVER);
+	CommandAdd(modinfo->handle, "SID", cmd_sid, MAXPARA, CMD_SERVER);
+
+	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()
+{
+	server_config_free();
+	SavePersistentPointer(modinfo, last_autoconnect_server);
+	return MOD_SUCCESS;
+}
+
+/** 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;
+}
+
+void server_config_free(void)
+{
+	ConfigItem_deny_link *d, *d_next;
+
+	for (d = conf_deny_link; d; d = d_next)
+	{
+		d_next = d->next;
+		unreal_delete_masks(d->mask);
+		crule_free(&d->rule);
+		safe_free(d->prettyrule);
+		safe_free(d->reason);
+		DelListItem(d, conf_deny_link);
+		safe_free(d);
+	}
+	conf_deny_link = NULL;
+}
+
+int server_config_test_set_server_linking(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		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_set_server_linking(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	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_config_test_deny_link(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+        int errors = 0;
+        ConfigEntry *cep;
+	char has_mask = 0, has_rule = 0, has_type = 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->items)
+		{
+			if (config_is_blankorempty(cep, "deny link"))
+			{
+				errors++;
+				continue;
+			}
+			else if (!strcmp(cep->name, "mask"))
+			{
+				has_mask = 1;
+			} else if (!strcmp(cep->name, "rule"))
+			{
+				int val = 0;
+				if (has_rule)
+				{
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny link::rule");
+					continue;
+				}
+				has_rule = 1;
+				if ((val = crule_test(cep->value)))
+				{
+					config_error("%s:%i: deny link::rule contains an invalid expression: %s",
+						cep->file->filename,
+						cep->line_number,
+						crule_errstring(val));
+					errors++;
+				}
+			}
+			else if (!strcmp(cep->name, "type"))
+			{
+				if (has_type)
+				{
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny link::type");
+					continue;
+				}
+				has_type = 1;
+				if (!strcmp(cep->value, "auto"))
+				;
+				else if (!strcmp(cep->value, "all"))
+				;
+				else {
+					config_status("%s:%i: unknown deny link type",
+					cep->file->filename, cep->line_number);
+					errors++;
+				}
+			} else if (!strcmp(cep->name, "reason"))
+			{
+			}
+			else
+			{
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "deny link", cep->name);
+				errors++;
+			}
+		}
+		else
+		{
+			// Sections
+			if (!strcmp(cep->name, "mask"))
+			{
+				if (cep->value || cep->items)
+					has_mask = 1;
+			}
+			else
+			{
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "deny link", cep->name);
+				errors++;
+				continue;
+			}
+		}
+	}
+	if (!has_mask)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny link::mask");
+		errors++;
+	}
+	if (!has_rule)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny link::rule");
+		errors++;
+	}
+	if (!has_type)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny link::type");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int server_config_run_deny_link(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	ConfigItem_deny_link *deny;
+
+	deny = safe_alloc(sizeof(ConfigItem_deny_link));
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "mask"))
+		{
+			unreal_add_masks(&deny->mask, cep);
+		}
+		else if (!strcmp(cep->name, "rule"))
+		{
+			deny->rule = crule_parse(cep->value);
+			safe_strdup(deny->prettyrule, cep->value);
+		}
+		else if (!strcmp(cep->name, "reason"))
+		{
+			safe_strdup(deny->reason, cep->value);
+		}
+		else if (!strcmp(cep->name, "type")) {
+			if (!strcmp(cep->value, "all"))
+				deny->flag.type = CRULE_ALL;
+			else if (!strcmp(cep->value, "auto"))
+				deny->flag.type = CRULE_AUTO;
+		}
+	}
+
+	/* Set a default reason, if needed */
+	if (!deny->reason)
+		safe_strdup(deny->reason, "Denied");
+
+	AddListItem(deny, conf_deny_link);
+	return 1;
+}
+
+int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	if ((type == CONFIG_SET) && !strcmp(ce->name, "server-linking"))
+		return server_config_test_set_server_linking(cf, ce, type, errs);
+
+	if ((type == CONFIG_DENY) && !strcmp(ce->value, "link"))
+		return server_config_test_deny_link(cf, ce, type, errs);
+
+	return 0; /* not for us */
+}
+
+int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	if ((type == CONFIG_SET) && ce && !strcmp(ce->name, "server-linking"))
+		return server_config_run_set_server_linking(cf, ce, type);
+
+	if ((type == CONFIG_DENY) && !strcmp(ce->value, "link"))
+		return server_config_run_deny_link(cf, ce, type);
+
+	return 0; /* not for us */
+}
+
+int server_needs_linking(ConfigItem_link *aconf)
+{
+	Client *client;
+	ConfigItem_class *class;
+
+	/* We're only interested in autoconnect blocks that also have
+	 * a valid link::outgoing configuration. We also ignore
+	 * temporary link blocks (not that they should exist...).
+	 */
+	if (!(aconf->outgoing.options & CONNECT_AUTO) ||
+	    (!aconf->outgoing.hostname && !aconf->outgoing.file) ||
+	    (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 */
+	if (check_deny_link(aconf, 1))
+		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)
+ * @param software	Software version in use (can be NULL)
+ * @param protoctol	UnrealIRCd protocol version in use (can be 0)
+ * @param flags		Server flags (hardly ever used, can be NULL)
+ * @returns 1 if link is denied (client is already killed), 0 if not.
+ */
+int _check_deny_version(Client *cptr, char *software, int protocol, char *flags)
+{
+	ConfigItem_deny_version *vlines;
+	
+	for (vlines = conf_deny_version; vlines; vlines = vlines->next)
+	{
+		if (match_simple(vlines->mask, cptr->name))
+			break;
+	}
+	
+	if (vlines)
+	{
+		char *proto = vlines->version;
+		char *vflags = vlines->flags;
+		int result = 0, i;
+		switch (*proto)
+		{
+			case '<':
+				proto++;
+				if (protocol < atoi(proto))
+					result = 1;
+				break;
+			case '>':
+				proto++;
+				if (protocol > atoi(proto))
+					result = 1;
+				break;
+			case '=':
+				proto++;
+				if (protocol == atoi(proto))
+					result = 1;
+				break;
+			case '!':
+				proto++;
+				if (protocol != atoi(proto))
+					result = 1;
+				break;
+			default:
+				if (protocol == atoi(proto))
+					result = 1;
+				break;
+		}
+		if (protocol == 0 || *proto == '*')
+			result = 0;
+
+		if (result)
+		{
+			exit_client(cptr, NULL, "Denied by deny version { } block");
+			return 0;
+		}
+
+		if (flags)
+		{
+			for (i = 0; vflags[i]; i++)
+			{
+				if (vflags[i] == '!')
+				{
+					i++;
+					if (strchr(flags, vflags[i])) {
+						result = 1;
+						break;
+					}
+				}
+				else if (!strchr(flags, vflags[i]))
+				{
+						result = 1;
+						break;
+				}
+			}
+
+			if (*vflags == '*' || !strcmp(flags, "0"))
+				result = 0;
+		}
+
+		if (result)
+		{
+			exit_client(cptr, NULL, "Denied by deny version { } block");
+			return 0;
+		}
+	}
+	
+	return 1;
+}
+
+/** Send our PROTOCTL SERVERS=x,x,x,x stuff.
+ * When response is set, it will be PROTOCTL SERVERS=*x,x,x (mind the asterisk).
+ */
+void _send_protoctl_servers(Client *client, int response)
+{
+	char buf[512];
+	Client *acptr;
+	int sendit = 1;
+
+	sendto_one(client, NULL, "PROTOCTL EAUTH=%s,%d,%s%s,UnrealIRCd-%s",
+		me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", buildid);
+		
+	ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=%s", response ? "*" : "");
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s,", acptr->id);
+		sendit = 1;
+		if (strlen(buf) > sizeof(buf)-12)
+		{
+			if (buf[strlen(buf)-1] == ',')
+				buf[strlen(buf)-1] = '\0';
+			sendto_one(client, NULL, "%s", buf);
+			/* We use the asterisk here too for continuation lines */
+			ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=*");
+			sendit = 0;
+		}
+	}
+	
+	/* Remove final comma (if any) */
+	if (buf[strlen(buf)-1] == ',')
+		buf[strlen(buf)-1] = '\0';
+
+	if (sendit)
+		sendto_one(client, NULL, "%s", buf);
+}
+
+void _send_server_message(Client *client)
+{
+	if (client->server && client->server->flags.server_sent)
+	{
+#ifdef DEBUGMODE
+		abort();
+#endif
+		return;
+	}
+
+	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->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 client The client which issued the command
+ * @returns On successfull authentication, the link block is returned. On failure NULL is returned (client has been killed!).
+ */
+ConfigItem_link *_verify_link(Client *client)
+{
+	ConfigItem_link *link, *orig_link;
+	Client *acptr = NULL, *ocptr = NULL;
+	ConfigItem_ban *bconf;
+
+	/* We set the sockhost here so you can have incoming masks based on hostnames.
+	 * Perhaps a bit late to do it here, but does anyone care?
+	 */
+	if (client->local->hostp && client->local->hostp->h_name)
+		set_sockhost(client, client->local->hostp->h_name);
+
+	if (!client->local->passwd)
+	{
+		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 NULL;
+	}
+
+	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->server->conf
+		 */
+
+		/* Actually we still need to double check the servername to avoid confusion. */
+		if (strcasecmp(client->name, client->server->conf->servername))
+		{
+			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 NULL;
+		}
+		link = client->server->conf;
+		goto skip_host_check;
+	} else {
+		/* Hunt the linkblock down ;) */
+		link = find_link(client->name);
+	}
+	
+	if (!link)
+	{
+		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 NULL;
+	}
+	
+	if (!link->incoming.match)
+	{
+		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::match set.",
+		           log_data_link_block(link));
+		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
+		return NULL;
+	}
+
+	orig_link = link;
+	if (!user_allowed_by_security_group(client, link->incoming.match))
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_INCOMING_MASK_MISMATCH", client,
+		           "Link with server $client.details denied: Server is in link block but link::incoming::mask didn't match",
+		           log_data_link_block(orig_link));
+		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
+		return NULL;
+	}
+
+skip_host_check:
+	/* 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
+		 * they mix different authentication systems (plaintext password
+		 * vs an "TLS Auth type" like spkifp/tlsclientcert/tlsclientcertfp).
+		 * The 'if' statement below is a bit complex but it consists of 2 things:
+		 * 1. Check if our side expects a plaintext password but we did not receive one
+		 * 2. Check if our side expects a non-plaintext password but we did receive one
+		 */
+		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, "*")))
+		{
+			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)
+		{
+			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)
+		{
+			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)
+		{
+			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
+		{
+			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));
+		}
+		exit_client(client, NULL, "Link denied (Authentication failed)");
+		return NULL;
+	}
+
+	/* Verify the TLS certificate (if requested) */
+	if (link->verify_certificate)
+	{
+		char *errstr = NULL;
+
+		if (!IsTLS(client))
+		{
+			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 NULL;
+		}
+		if (!verify_certificate(client->local->ssl, link->servername, &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 NULL;
+		}
+	}
+
+	if ((bconf = find_ban(NULL, client->name, CONF_BAN_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 NULL;
+	}
+
+	if (link->class->clients + 1 > link->class->maxclients)
+	{
+		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 NULL;
+	}
+	if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_DENY) && !IsSecure(client))
+	{
+		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 NULL;
+	}
+	if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_DENY) && outdated_tls_client(client))
+	{
+		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 NULL;
+	}
+	/* 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 NULL;
+		} 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");
+		}
+	}
+
+	return link;
+}
+
+/** Server command. Only for locally connected servers!!.
+ * parv[1] = server name
+ * parv[2] = hop count
+ * parv[3] = server description, may include protocol version and other stuff too (VL)
+ */
+CMD_FUNC(cmd_server)
+{
+	const char *servername = NULL;	/* Pointer for servername */
+	char *ch = NULL;	/* */
+	char descbuf[BUFSIZE];
+	int  hop = 0;
+	char info[REALLEN + 61];
+	ConfigItem_link *aconf = NULL;
+	char *flags = NULL, *protocol = NULL, *inf = NULL, *num = NULL;
+	int incoming;
+	const char *err;
+
+	if (IsUser(client))
+	{
+		sendnumeric(client, ERR_ALREADYREGISTRED);
+		sendnotice(client, "*** Sorry, but your IRC program doesn't appear to support changing servers.");
+		return;
+	}
+
+	if (parc < 4 || (!*parv[3]))
+	{
+		exit_client(client, NULL,  "Not enough SERVER parameters");
+		return;
+	}
+
+	servername = parv[1];
+
+	/* Remote 'SERVER' command is not possible on a 100% SID network */
+	if (!MyConnect(client))
+	{
+		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;
+	}
+
+	if (client->local->listener && (client->local->listener->options & LISTENER_CLIENTSONLY))
+	{
+		exit_client(client, NULL, "This port is for clients only");
+		return;
+	}
+
+	if (!valid_server_name(servername))
+	{
+		exit_client(client, NULL, "Bogus server name");
+		return;
+	}
+
+	if (!client->local->passwd)
+	{
+		exit_client(client, NULL, "Missing password");
+		return;
+	}
+
+	/* 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 (!(aconf = verify_link(client)))
+		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.
+	 */
+
+
+	if (strlen(client->id) != 3)
+	{
+		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;
+	}
+
+	hop = atol(parv[2]);
+	if (hop != 1)
+	{
+		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;
+	}
+	client->hopcount = hop;
+
+	strlcpy(info, parv[parc - 1], sizeof(info));
+
+	/* Parse "VL" data in description */
+	if (SupportVL(client))
+	{
+		char tmp[REALLEN + 61];
+		inf = protocol = flags = num = NULL;
+		strlcpy(tmp, info, sizeof(tmp)); /* work on a copy */
+
+		/* We are careful here to allow invalid syntax or missing
+		 * stuff, which mean that particular variable will stay NULL.
+		 */
+
+		protocol = strtok(tmp, "-");
+		if (protocol)
+			flags = strtok(NULL, "-");
+		if (flags)
+			num = strtok(NULL, " ");
+		if (num)
+			inf = strtok(NULL, "");
+		if (inf)
+		{
+			strlcpy(client->info, inf[0] ? inf : "server", sizeof(client->info)); /* set real description */
+
+			if (!_check_deny_version(client, NULL, atoi(protocol), flags))
+				return; /* Rejected */
+		} else {
+			strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
+		}
+	} else {
+		strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
+	}
+
+	if ((err = check_deny_link(aconf, 0)))
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DENY_LINK_BLOCK", client,
+			   "Server link $servername rejected by deny link { } block: $reason",
+			   log_data_string("servername", servername),
+			   log_data_string("reason", err));
+		exit_client_fmt(client, NULL, "Disallowed by connection rule: %s", err);
+		return;
+	}
+
+	if (aconf->options & CONNECT_QUARANTINE)
+		SetQuarantined(client);
+
+	ircsnprintf(descbuf, sizeof descbuf, "Server: %s", servername);
+	fd_desc(client->local->fd, descbuf);
+
+	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).
+ * parv[1] = server name
+ * parv[2] = hop count (always >1)
+ * parv[3] = SID
+ * parv[4] = server description
+ */
+CMD_FUNC(cmd_sid)
+{
+	Client *acptr, *ocptr;
+	ConfigItem_link	*aconf;
+	ConfigItem_ban *bconf;
+	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))
+	{
+		sendnumeric(client, ERR_NOTFORUSERS, "SID");
+		return;
+	}
+
+	if (parc < 4 || BadPtr(parv[3]))
+	{
+		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;
+	}
+
+	/* Check if server already exists... */
+	if ((acptr = find_server(servername, NULL)))
+	{
+		/* Found. Bad. Quit. */
+
+		if (IsMe(acptr))
+		{
+			/* 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;
+		}
+
+		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 = (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)))
+	{
+		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("servername", servername),
+		           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))
+	{
+		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 = atoi(parv[2]);
+	if (hop < 2)
+	{
+		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 (!client->direction->server->conf)
+	{
+		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;
+	}
+
+	aconf = client->direction->server->conf;
+
+	if (!aconf->hub)
+	{
+		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))
+	{
+		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;
+	}
+
+	if (aconf->leaf)
+	{
+		if (!match_simple(aconf->leaf, servername))
+		{
+			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))
+	{
+		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(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);
+	SetServer(acptr);
+	/* 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);
+	irccounts.servers++;
+	find_or_add(acptr->name);
+	add_client_to_list(acptr);
+	add_to_client_hash_table(acptr->name, acptr);
+	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->uplink->id, acptr->name, hop + 1, acptr->id, acptr->info);
+
+	RunHook(HOOKTYPE_POST_SERVER_CONNECT, acptr);
+}
+
+void _introduce_user(Client *to, Client *acptr)
+{
+	char buf[512];
+
+	build_umode_string(acptr, 0, SEND_UMODES, buf);
+
+	sendto_one_nickcmd(to, NULL, acptr, buf);
+	
+	send_moddata_client(to, acptr);
+
+	if (acptr->user->away)
+		sendto_one(to, NULL, ":%s AWAY :%s", acptr->id, acptr->user->away);
+
+	if (acptr->user->swhois)
+	{
+		SWhois *s;
+		for (s = acptr->user->swhois; s; s = s->next)
+		{
+			if (CHECKSERVERPROTO(to, PROTO_EXTSWHOIS))
+			{
+				sendto_one(to, NULL, ":%s SWHOIS %s + %s %d :%s",
+					me.id, acptr->name, s->setby, s->priority, s->line);
+			} else
+			{
+				sendto_one(to, NULL, ":%s SWHOIS %s :%s",
+					me.id, acptr->name, s->line);
+			}
+		}
+	}
+}
+
+#define SafeStr(x)    ((x && *(x)) ? (x) : "*")
+
+/** Broadcast SINFO.
+ * @param cptr   The server to send the information about.
+ * @param to     The server to send the information TO (NULL for broadcast).
+ * @param except The direction NOT to send to.
+ * This function takes into account that the server may not
+ * provide all of the detailed info. If any information is
+ * absent we will send 0 for numbers and * for NULL strings.
+ */
+void _broadcast_sinfo(Client *acptr, Client *to, Client *except)
+{
+	char chanmodes[128], buf[512];
+
+	if (acptr->server->features.chanmodes[0])
+	{
+		snprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
+			 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->server->boottime,
+		      acptr->server->features.protocol,
+		      SafeStr(acptr->server->features.usermodes),
+		      chanmodes,
+		      SafeStr(acptr->server->features.nickchars),
+		      SafeStr(acptr->server->features.software));
+
+	if (to)
+	{
+		/* Targetted to one server */
+		sendto_one(to, NULL, ":%s SINFO %s", acptr->id, buf);
+	} else {
+		/* Broadcast (except one side...) */
+		sendto_server(except, 0, 0, NULL, ":%s SINFO %s", acptr->id, buf);
+	}
+}
+
+/** 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)
+{
+	Client *acptr;
+
+	if (incoming)
+	{
+		/* If this is an incomming connection, then we have just received
+		 * their stuff and now send our PASS, PROTOCTL and SERVER messages back.
+		 */
+		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 : "*");
+
+		send_proto(client, aconf);
+		send_server_message(client);
+	}
+
+	/* Broadcast new server to the rest of the network */
+	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(client, NULL, client);
+
+	/* Send moddata of &me (if any, likely minimal) */
+	send_moddata_client(client, &me);
+
+	list_for_each_entry_reverse(acptr, &global_server_list, client_node)
+	{
+		/* acptr->direction == acptr for acptr == client */
+		if (acptr->direction == client)
+			continue;
+
+		if (IsServer(acptr))
+		{
+			sendto_one(client, NULL, ":%s SID %s %d %s :%s",
+			    acptr->uplink->id,
+			    acptr->name, acptr->hopcount + 1,
+			    acptr->id, acptr->info);
+
+			/* Also signal to the just-linked server which
+			 * servers are fully linked.
+			 * Now you might ask yourself "Why don't we just
+			 * assume every server you get during link phase
+			 * is fully linked?", well.. there's a race condition
+			 * if 2 servers link (almost) at the same time,
+			 * then you would think the other one is fully linked
+			 * while in fact he was not.. -- Syzop.
+			 */
+			if (acptr->server->flags.synced)
+				sendto_one(client, NULL, ":%s EOS", acptr->id);
+			/* Send SINFO of our servers to their side */
+			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 == client */
+		if (acptr->direction == client)
+			continue;
+		if (IsUser(acptr))
+			introduce_user(client, acptr);
+	}
+	/*
+	   ** Last, pass all channels plus statuses
+	 */
+	{
+		Channel *channel;
+		for (channel = channels; channel; channel = channel->nextch)
+		{
+			send_channel_modes_sjoin3(client, channel);
+			if (channel->topic_time)
+				sendto_one(client, NULL, "TOPIC %s %s %lld :%s",
+				    channel->name, channel->topic_nick,
+				    (long long)channel->topic_time, channel->topic);
+			send_moddata_channel(client, channel);
+		}
+	}
+	
+	/* Send ModData for all member(ship) structs */
+	send_moddata_members(client);
+	
+	/* pass on TKLs */
+	tkl_sync(client);
+
+	RunHook(HOOKTYPE_SERVER_SYNC, client);
+
+	sendto_one(client, NULL, "NETINFO %i %lld %i %s 0 0 0 :%s",
+	    irccounts.global_max, (long long)TStime(), UnrealProtocol,
+	    CLOAK_KEY_CHECKSUM,
+	    NETWORK_NAME);
+
+	/* Send EOS (End Of Sync) to the just linked server... */
+	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
+ * looked weird and just plain inefficient. We now fill up our send-buffer
+ * really as much as we can, without causing any overflows of course.
+ */
+void send_channel_modes_sjoin3(Client *to, Channel *channel)
+{
+	MessageTag *mtags = NULL;
+	Member *members;
+	Member *lp;
+	Ban *ban;
+	short nomode, nopara;
+	char tbuf[512]; /* work buffer, for temporary data */
+	char buf[1024]; /* send buffer */
+	char *bufptr; /* points somewhere in 'buf' */
+	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->name != '#')
+		return;
+
+	nomode = 0;
+	nopara = 0;
+	members = channel->members;
+
+	/* First we'll send channel, channel modes and members and status */
+
+	*modebuf = *parabuf = '\0';
+	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;
+	if (!(*parabuf))
+		nopara = 1;
+
+	/* Generate a new message (including msgid).
+	 * Due to the way SJOIN works, we will use the same msgid for
+	 * multiple SJOIN messages to servers. Rest assured that clients
+	 * will never see these duplicate msgid's though. They
+	 * will see a 'special' version instead with a suffix.
+	 */
+	new_message(&me, NULL, &mtags);
+
+	if (nomode && nopara)
+	{
+		ircsnprintf(buf, sizeof(buf),
+		    ":%s SJOIN %lld %s :", me.id,
+		    (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->name, modebuf);
+	}
+	if (!nopara && !nomode)
+	{
+		ircsnprintf(buf, sizeof(buf),
+		    ":%s SJOIN %lld %s %s %s :", me.id,
+		    (long long)channel->creationtime, channel->name, modebuf, parabuf);
+	}
+
+	prebuflen = strlen(buf);
+	bufptr = buf + prebuflen;
+
+	/* RULES:
+	 * - Use 'tbuf' as a working buffer, use 'p' to advance in 'tbuf'.
+	 *   Thus, be sure to do a 'p = tbuf' at the top of the loop.
+	 * - When one entry has been build, check if strlen(buf) + strlen(tbuf) > BUFSIZE - 8,
+	 *   if so, do not concat but send the current result (buf) first to the server
+	 *   and reset 'buf' to only the prebuf part (all until the ':').
+	 *   Then, in both cases, concat 'tbuf' to 'buf' and continue
+	 * - Be sure to ALWAYS zero terminate (*p = '\0') when the entry has been build.
+	 * - Be sure to add a space after each entry ;)
+	 *
+	 * For a more illustrated view, take a look at the first for loop, the others
+	 * are pretty much the same.
+	 *
+	 * Follow these rules, and things would be smooth and efficient (network-wise),
+	 * if you ignore them, expect crashes and/or heap corruption, aka: HELL.
+	 * You have been warned.
+	 *
+	 * Side note: of course things would be more efficient if the prebuf thing would
+	 * not be sent every time, but that's another story
+	 *      -- Syzop
+	 */
+
+	for (lp = members; lp; lp = lp->next)
+	{
+		p = mystpcpy(tbuf, modes_to_sjoin_prefix(lp->member_modes)); /* eg @+ */
+		p = mystpcpy(p, lp->client->id); /* nick (well, id) */
+		*p++ = ' ';
+		*p = '\0';
+
+		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
+		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
+		{
+			/* 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';
+		}
+		/* concat our stuff.. */
+		bufptr = mystpcpy(bufptr, tbuf);
+	}
+
+	for (ban = channel->banlist; ban; ban = ban->next)
+	{
+		p = tbuf;
+		if (SupportSJSBY(to))
+			p += add_sjsby(p, ban->who, ban->when);
+		*p++ = '&';
+		p = mystpcpy(p, ban->banstr);
+		*p++ = ' ';
+		*p = '\0';
+		
+		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
+		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
+		{
+			/* 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';
+		}
+		/* concat our stuff.. */
+		bufptr = mystpcpy(bufptr, tbuf);
+	}
+
+	for (ban = channel->exlist; ban; ban = ban->next)
+	{
+		p = tbuf;
+		if (SupportSJSBY(to))
+			p += add_sjsby(p, ban->who, ban->when);
+		*p++ = '"';
+		p = mystpcpy(p, ban->banstr);
+		*p++ = ' ';
+		*p = '\0';
+		
+		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
+		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
+		{
+			/* 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';
+		}
+		/* concat our stuff.. */
+		bufptr = mystpcpy(bufptr, tbuf);
+	}
+
+	for (ban = channel->invexlist; ban; ban = ban->next)
+	{
+		p = tbuf;
+		if (SupportSJSBY(to))
+			p += add_sjsby(p, ban->who, ban->when);
+		*p++ = '\'';
+		p = mystpcpy(p, ban->banstr);
+		*p++ = ' ';
+		*p = '\0';
+		
+		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
+		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
+		{
+			/* 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';
+		}
+		/* concat our stuff.. */
+		bufptr = mystpcpy(bufptr, tbuf);
+	}
+
+	if (buf[prebuflen] || !sent)
+		sendto_one(to, mtags, "%s", buf);
+
+	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 && !aconf->outgoing.file)
+	{
+		/* 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 or link::outgoing::file 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 && !aconf->outgoing.file)
+	{
+		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 ? aconf->outgoing.hostname : aconf->outgoing.file, 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 ? aconf->outgoing.hostname : "127.0.0.1");
+	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,
+		   aconf->outgoing.file
+		   ? "Trying to activate link with server $client ($link_block.file)..."
+		   : "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 && !aconf->outgoing.file)
+	{
+		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NOIP", client,
+		           "Connect to $client failed: no IP address or file to connect to",
+		           log_data_link_block(aconf));
+		return 0; /* handled upstream or shouldn't happen */
+	}
+
+	if (aconf->outgoing.file)
+		SetUnixSocket(client);
+	else if (strchr(aconf->connect_ip, ':'))
+		SetIPV6(client);
+	
+	safe_strdup(client->ip, aconf->connect_ip ? aconf->connect_ip : "127.0.0.1");
+	
+	snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(client, TRUE));
+	client->local->fd = fd_socket(IsUnixSocket(client) ? AF_UNIX : (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 ? aconf->outgoing.hostname : "127.0.0.1");
+
+	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,
+			    aconf->outgoing.file ? aconf->outgoing.file : client->ip,
+			    aconf->outgoing.port, client->local->socket_type))
+	{
+			unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
+				   aconf->outgoing.file
+				   ? "Connect to $client ($link_block.file) failed: $socket_error"
+				   : "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;
+}
+
+int _is_services_but_not_ulined(Client *client)
+{
+	if (!client->server || !client->server->features.software || !*client->name)
+		return 0; /* cannot detect software version or name not available yet */
+
+	if (our_strcasestr(client->server->features.software, "anope") ||
+	    our_strcasestr(client->server->features.software, "atheme"))
+	{
+		if (!find_uline(client->name))
+		{
+			unreal_log(ULOG_ERROR, "link", "LINK_NO_ULINES", client,
+			           "Server $client is a services server ($software). "
+			           "However, server $me does not have $client in the ulines { } block, "
+			           "which is required for services servers. "
+			           "See https://www.unrealircd.org/docs/Ulines_block",
+			           log_data_client("me", &me),
+			           log_data_string("software", client->server->features.software));
+			return 1; /* Is services AND no ulines { } entry */
+		}
+	}
+	return 0;
+}
+
+/** Check if this link should be denied due to deny link { } configuration
+ * @param link		The link block
+ * @param auto_connect	Set this to 1 if this is called from auto connect code
+ *			(it will then check both CRULE_AUTO + CRULE_ALL)
+ *			set it to 0 otherwise (will not check CRULE_AUTO blocks).
+ * @returns The deny block if the server should be denied, or NULL if no deny block.
+ */
+const char *_check_deny_link(ConfigItem_link *link, int auto_connect)
+{
+	ConfigItem_deny_link *d;
+
+	for (d = conf_deny_link; d; d = d->next)
+	{
+		if ((auto_connect == 0) && (d->flag.type == CRULE_AUTO))
+			continue;
+		if (unreal_mask_match_string(link->servername, d->mask) &&
+		    crule_eval(d->rule))
+		{
+			return d->reason;
+		}
+	}
+	return NULL;
+}
+
+int server_stats_denylink_all(Client *client, const char *para)
+{
+	ConfigItem_deny_link *links;
+	ConfigItem_mask *m;
+
+	if (!para || !(!strcmp(para, "D") || !strcasecmp(para, "denylinkall")))
+		return 0;
+
+	for (links = conf_deny_link; links; links = links->next)
+	{
+		if (links->flag.type == CRULE_ALL)
+		{
+			for (m = links->mask; m; m = m->next)
+				sendnumeric(client, RPL_STATSDLINE, 'D', m->mask, links->prettyrule);
+		}
+	}
+
+	return 1;
+}
+
+int server_stats_denylink_auto(Client *client, const char *para)
+{
+	ConfigItem_deny_link *links;
+	ConfigItem_mask *m;
+
+	if (!para || !(!strcmp(para, "d") || !strcasecmp(para, "denylinkauto")))
+		return 0;
+
+	for (links = conf_deny_link; links; links = links->next)
+	{
+		if (links->flag.type == CRULE_AUTO)
+		{
+			for (m = links->mask; m; m = m->next)
+				sendnumeric(client, RPL_STATSDLINE, 'd', m->mask, links->prettyrule);
+		}
+	}
+
+	return 1;
+}
diff --git a/ircd/src/modules/sethost.c b/ircd/src/modules/sethost.c
@@ -0,0 +1,151 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sethost.c
+ *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
+ *
+ *   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_sethost);
+
+/* Place includes here */
+#define MSG_SETHOST 	"SETHOST"	/* sethost */
+
+ModuleHeader MOD_HEADER
+  = {
+	"sethost",	/* Name of module */
+	"5.0", /* Version */
+	"command /sethost", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SETHOST, cmd_sethost, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+   cmd_sethost() added by Stskeeps (30/04/1999)
+               (modified at 15/05/1999) by Stskeeps | Potvin
+   :prefix SETHOST newhost
+   parv[1] - newhost
+*/
+CMD_FUNC(cmd_sethost)
+{
+	const char *vhost;
+
+	if (MyUser(client) && !ValidatePermissionsForPath("self:set:host",client,NULL,NULL,NULL))
+	{
+  		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc < 2)
+		vhost = NULL;
+	else
+		vhost = parv[1];
+
+	if (BadPtr(vhost))
+	{	
+		if (MyConnect(client))
+			sendnotice(client, "*** Syntax: /SetHost <new host>");
+		return;
+	}
+
+	if (strlen(parv[1]) > (HOSTLEN))
+	{
+		if (MyConnect(client))
+			sendnotice(client, "*** /SetHost Error: Hostnames are limited to %i characters.", HOSTLEN);
+		return;
+	}
+
+	if (!valid_host(vhost, 0))
+	{
+		sendnotice(client, "*** /SetHost Error: A hostname may only contain a-z, A-Z, 0-9, '-' & '.'.");
+		return;
+	}
+	if (vhost[0] == ':')
+	{
+		sendnotice(client, "*** A hostname cannot start with ':'");
+		return;
+	}
+
+	if (MyUser(client) && !strcmp(GetHost(client), vhost))
+	{
+		sendnotice(client, "/SetHost Error: requested host is same as current host.");
+		return;
+	}
+
+	userhost_save_current(client);
+
+	switch (UHOST_ALLOWED)
+	{
+		case UHALLOW_NEVER:
+			if (MyUser(client))
+			{
+				sendnotice(client, "*** /SetHost is disabled");
+				return;
+			}
+			break;
+		case UHALLOW_ALWAYS:
+			break;
+		case UHALLOW_NOCHANS:
+			if (MyUser(client) && client->user->joined)
+			{
+				sendnotice(client, "*** /SetHost can not be used while you are on a channel");
+				return;
+			}
+			break;
+		case UHALLOW_REJOIN:
+			/* join sent later when the host has been changed */
+			break;
+	}
+
+	/* hide it */
+	client->umodes |= UMODE_HIDE;
+	client->umodes |= UMODE_SETHOST;
+	/* get it in */
+	safe_strdup(client->user->virthost, vhost);
+	/* spread it out */
+	sendto_server(client, 0, 0, NULL, ":%s SETHOST %s", client->id, parv[1]);
+
+	userhost_changed(client);
+
+	if (MyConnect(client))
+	{
+		sendto_one(client, NULL, ":%s MODE %s :+xt", client->name, client->name);
+		sendnotice(client, 
+		    "Your nick!user@host-mask is now (%s!%s@%s) - To disable it type /mode %s -x",
+		     client->name, client->user->username, vhost,
+		    client->name);
+	}
+}
diff --git a/ircd/src/modules/setident.c b/ircd/src/modules/setident.c
@@ -0,0 +1,125 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/setident.c
+ *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
+ *
+ *   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_SETIDENT 	"SETIDENT"	/* set ident */
+
+CMD_FUNC(cmd_setident);
+
+ModuleHeader MOD_HEADER
+  = {
+	"setident",	/* Name of module */
+	"5.0", /* Version */
+	"/setident", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SETIDENT, cmd_setident, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* cmd_setident - 12/05/1999 - Stskeeps
+ * :prefix SETIDENT newident
+ * parv[1] - newident
+ * D: This will set your username to be <x> (like (/setident Root))
+ * (if you are IRCop) **efg*
+ * Cloning of cmd_sethost at some points - so same authors ;P
+*/
+CMD_FUNC(cmd_setident)
+{
+	const char *vident, *s;
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		if (MyConnect(client))
+			sendnotice(client, "*** Syntax: /SETIDENT <new ident>");
+		return;
+	}
+
+	vident = parv[1];
+
+	switch (UHOST_ALLOWED)
+	{
+		case UHALLOW_ALWAYS:
+			break;
+		case UHALLOW_NEVER:
+			if (MyUser(client))
+			{
+				sendnotice(client, "*** /SETIDENT is disabled");
+				return;
+			}
+			break;
+		case UHALLOW_NOCHANS:
+			if (MyUser(client) && client->user->joined)
+			{
+				sendnotice(client, "*** /SETIDENT cannot be used while you are on a channel");
+				return;
+			}
+			break;
+		case UHALLOW_REJOIN:
+			/* dealt with later */
+			break;
+	}
+
+	if (strlen(vident) > USERLEN)
+	{
+		if (MyConnect(client))
+			sendnotice(client, "*** /SETIDENT Error: Usernames are limited to %i characters.", USERLEN);
+		return;
+	}
+
+	/* Check if the new ident contains illegal characters */
+	if (!valid_username(vident))
+	{
+		sendnotice(client, "*** /SETIDENT Error: A username may contain a-z, A-Z, 0-9, '-', '~' & '.'.");
+		return;
+	}
+
+	userhost_save_current(client);
+
+	strlcpy(client->user->username, vident, sizeof(client->user->username));
+
+	sendto_server(client, 0, 0, NULL, ":%s SETIDENT %s", client->id, parv[1]);
+
+	userhost_changed(client);
+
+	if (MyConnect(client))
+	{
+		sendnotice(client, "Your nick!user@host-mask is now (%s!%s@%s)",
+		                 client->name, client->user->username, GetHost(client));
+	}
+}
diff --git a/ircd/src/modules/setname.c b/ircd/src/modules/setname.c
@@ -0,0 +1,162 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/setname.c
+ *   (c) 1999-2001 Dominick Meglio (codemastr) <codemastr@unrealircd.com>
+ *
+ *   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_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
+  = {
+	"setname",	/* Name of module */
+	"5.0", /* Version */
+	"command /setname", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	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;
+}
+
+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))
+   this is now compatible with IRCv3 SETNAME --k4be
+*/ 
+CMD_FUNC(cmd_setname)
+{
+	int xx;
+	char oldinfo[REALLEN + 1];
+	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64];
+	ConfigItem_ban *bconf;
+	MessageTag *mtags = NULL;
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SETNAME");
+		return;
+	}
+
+	if (strlen(parv[1]) > REALLEN)
+	{
+		if (!MyConnect(client))
+			return;
+		if (HasCapabilityFast(client, CAP_SETNAME))
+		{
+			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 the new name before we check, but don't send to servers unless it is ok */
+		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 */
+			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;
+		}
+
+		/* Check for realname bans here too */
+		if (!ValidatePermissionsForPath("immune:server-ban:ban-realname",client,NULL,NULL,NULL) &&
+		    ((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME))))
+		{
+			banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
+			return;
+		}
+	} else {
+		/* remote user */
+		strlcpy(client->info, parv[1], sizeof(client->info));
+	}
+
+	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))
+	{
+		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_CHANGE, client, oldinfo);
+}
diff --git a/ircd/src/modules/silence.c b/ircd/src/modules/silence.c
@@ -0,0 +1,241 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/silence.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_silence);
+
+ModuleHeader MOD_HEADER
+  = {
+	"silence",
+	"5.0",
+	"command /silence", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Structs */
+typedef struct Silence Silence;
+/** A /SILENCE entry */
+struct Silence
+{
+	Silence *prev, *next;
+	char mask[1]; /**< user!nick@host mask of silence entry */
+};
+
+/* Global variables */
+ModDataInfo *silence_md = NULL;
+
+/* Macros */
+#define SILENCELIST(x)       ((Silence *)moddata_local_client(x, silence_md).ptr)
+
+/* Forward declarations */
+int _is_silenced(Client *, Client *);
+int _del_silence(Client *client, const char *mask);
+int _add_silence(Client *client, const char *mask, int senderr);
+void silence_md_free(ModData *md);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAdd(modinfo->handle, EFUNC_ADD_SILENCE, _add_silence);
+	EfunctionAdd(modinfo->handle, EFUNC_DEL_SILENCE, _del_silence);
+	EfunctionAdd(modinfo->handle, EFUNC_IS_SILENCED, _is_silenced);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "silence";
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	mreq.free = silence_md_free;
+	silence_md = ModDataAdd(modinfo->handle, mreq);
+	if (!silence_md)
+	{
+		config_error("could not register silence moddata");
+		return MOD_FAILED;
+	}
+	CommandAdd(modinfo->handle, "SILENCE", cmd_silence, MAXPARA, CMD_USER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** The /SILENCE command - server-side ignore list.
+ * Syntax:
+ * SILENCE +user  To add a user from the silence list
+ * SILENCE -user  To remove a user from the silence list
+ * SILENCE        To send the current silence list
+ *
+ */
+
+CMD_FUNC(cmd_silence)
+{
+	Silence *s;
+	const char *p;
+	char action;
+
+	if (MyUser(client))
+	{
+		if (parc < 2 || BadPtr(parv[1]))
+		{
+			for (s = SILENCELIST(client); s; s = s->next)
+				sendnumeric(client, RPL_SILELIST, s->mask);
+			sendnumeric(client, RPL_ENDOFSILELIST);
+			return;
+		}
+		p = parv[1];
+		action = *p;
+		if (action == '-' || action == '+')
+		{
+			p++;
+		} else
+		if (!strchr(p, '@') && !strchr(p, '.') && !strchr(p, '!') && !strchr(p, '*') && !find_user(p, NULL))
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+			return;
+		} else
+		{
+			action = '+';
+		}
+		p = pretty_mask(p);
+		if ((action == '-' && del_silence(client, p)) ||
+		    (action != '-' && add_silence(client, p, 1)))
+		{
+			sendto_prefix_one(client, client, NULL, ":%s SILENCE %c%s",
+			    client->name, action, p);
+		}
+		return;
+	}
+
+	/* Probably server to server traffic.
+	 * We don't care about this anymore on UnrealIRCd 5 and later.
+	 */
+}
+
+/** Delete item from the silence list.
+ * @param client The client.
+ * @param mask The mask to delete from the list.
+ * @returns 1 if entry was found and deleted, 0 if not found.
+ */
+int _del_silence(Client *client, const char *mask)
+{
+	Silence *s;
+
+	for (s = SILENCELIST(client); s; s = s->next)
+	{
+		if (mycmp(mask, s->mask) == 0)
+		{
+			DelListItemUnchecked(s, moddata_local_client(client, silence_md).ptr);
+			safe_free(s);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/** Add item to the silence list.
+ * @param client The client.
+ * @param mask The mask to add to the list.
+ * @returns 1 if silence entry added,
+ *          0 if not added, eg: full or already covered by an existing silence entry.
+ */
+int _add_silence(Client *client, const char *mask, int senderr)
+{
+	Silence *s;
+	int cnt = 0;
+
+	if (!MyUser(client))
+		return 0;
+
+	for (s = SILENCELIST(client); s; s = s->next)
+	{
+		if ((strlen(s->mask) > MAXSILELENGTH) || (++cnt >= SILENCE_LIMIT))
+		{
+			if (senderr)
+				sendnumeric(client, ERR_SILELISTFULL, mask);
+			return 0;
+		}
+		else
+		{
+			if (match_simple(s->mask, mask))
+				return 0;
+		}
+	}
+
+	/* Add the new entry */
+	s = safe_alloc(sizeof(Silence)+strlen(mask));
+	strcpy(s->mask, mask); /* safe, allocated above */
+	AddListItemUnchecked(s, moddata_local_client(client, silence_md).ptr);
+	return 1;
+}
+
+/** Check whether sender is silenced by receiver.
+ * @param sender    The client that intends to send a message.
+ * @param receiver  The client that would receive the message.
+ * @returns 1 if sender is silenced by receiver (do NOT send the message),
+ *          0 if not silenced (go ahead and send).
+ */
+int _is_silenced(Client *sender, Client *receiver)
+{
+	Silence *s;
+	char mask[HOSTLEN + NICKLEN + USERLEN + 5];
+
+	if (!MyUser(receiver) || !receiver->user || !sender->user || !SILENCELIST(receiver))
+		return 0;
+
+	ircsnprintf(mask, sizeof(mask), "%s!%s@%s", sender->name, sender->user->username, GetHost(sender));
+
+	for (s = SILENCELIST(receiver); s; s = s->next)
+	{
+		if (match_simple(s->mask, mask))
+			return 1;
+	}
+
+	return 0;
+}
+
+/** Called on client exit: free the silence list of this user */
+void silence_md_free(ModData *md)
+{
+	Silence *b, *b_next;
+
+	for (b = md->ptr; b; b = b_next)
+	{
+		b_next = b->next;
+		safe_free(b);
+	}
+	md->ptr = NULL;
+}
diff --git a/ircd/src/modules/sinfo.c b/ircd/src/modules/sinfo.c
@@ -0,0 +1,171 @@
+/*
+ * cmd_sinfo - Server information
+ * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team.
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"sinfo",
+	"5.0",
+	"Server information",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+CMD_FUNC(cmd_sinfo);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, "SINFO", cmd_sinfo, MAXPARA, CMD_USER|CMD_SERVER);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** SINFO server-to-server command.
+ * Technical documentation is available at:
+ * https://www.unrealircd.org/docs/Server_protocol:SINFO_command
+ * ^ contains important remarks regarding when to send it and when not.
+ */
+CMD_FUNC(sinfo_server)
+{
+	char buf[512];
+
+	if (MyConnect(client))
+	{
+		/* It is a protocol violation to send an SINFO for yourself,
+		 * eg if you are server 001, then you cannot send :001 SINFO ....
+		 * Exiting the client may seem harsh, but this way we force users
+		 * to use the correct protocol. If we would not do this then some
+		 * services coders may think they should use only SINFO while in
+		 * fact for directly connected servers they should use things like
+		 * PROTOCTL CHANMODES=... USERMODES=... NICKCHARS=.... etc, and
+		 * failure to do so will lead to potential desyncs or other major
+		 * issues.
+		 */
+		exit_client(client, NULL, "Protocol error: you cannot send SINFO about yourself");
+		return;
+	}
+
+	/* :SID SINFO up_since protocol umodes chanmodes nickchars :software name
+	 *               1        2        3      4        5        6 (last one)
+	 * If we extend it then 'software name' will still be the last one, so
+	 * it may become 7, 8 or 9. New elements are inserted right before it.
+	 */
+
+	if ((parc < 6) || BadPtr(parv[6]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SINFO");
+		return;
+	}
+
+	client->server->boottime = atol(parv[1]);
+	client->server->features.protocol = atoi(parv[2]);
+
+	if (!strcmp(parv[3], "*"))
+		safe_free(client->server->features.usermodes);
+	else
+		safe_strdup(client->server->features.usermodes, parv[3]);
+
+	if (!strcmp(parv[4], "*"))
+	{
+		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->server->features.nickchars);
+	else
+		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->server->features.software);
+	else
+		safe_strdup(client->server->features.software, parv[parc-1]);
+
+	if (is_services_but_not_ulined(client))
+	{
+		char buf[512];
+		snprintf(buf, sizeof(buf), "Services detected but no ulines { } for server name %s", client->name);
+		exit_client_ex(client, &me, NULL, buf);
+		return;
+	}
+
+	/* Broadcast to 'the other side' of the net */
+	concat_params(buf, sizeof(buf), parc, parv);
+	sendto_server(client, 0, 0, NULL, ":%s SINFO %s", client->id, buf);
+}
+
+#define SafeDisplayStr(x)  ((x && *(x)) ? (x) : "-")
+CMD_FUNC(sinfo_user)
+{
+	Client *acptr;
+
+	if (!IsOper(client))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		sendtxtnumeric(client, "*** Server %s:", acptr->name);
+		sendtxtnumeric(client, "Protocol: %d",
+		               acptr->server->features.protocol);
+		sendtxtnumeric(client, "Software: %s",
+		               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->server->boottime));
+			sendtxtnumeric(client, "Uptime: %s",
+			               pretty_time_val(TStime() - acptr->server->boottime));
+		}
+		sendtxtnumeric(client, "User modes: %s",
+		               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->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->server->features.nickchars));
+	}
+}
+
+CMD_FUNC(cmd_sinfo)
+{
+	if (IsServer(client))
+		CALL_CMD_FUNC(sinfo_server);
+	else if (MyUser(client))
+		CALL_CMD_FUNC(sinfo_user);
+}
diff --git a/ircd/src/modules/sjoin.c b/ircd/src/modules/sjoin.c
@@ -0,0 +1,821 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sjoin.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_sjoin);
+
+#define MSG_SJOIN 	"SJOIN"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"sjoin",
+	"5.1",
+	"command /sjoin", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SJOIN, cmd_sjoin, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+typedef struct xParv aParv;
+struct xParv {
+	int  parc;
+	const char *parv[256];
+};
+
+aParv pparv;
+
+aParv *mp2parv(char *xmbuf, char *parmbuf)
+{
+	int  c;
+	char *p, *s;
+
+	pparv.parv[0] = xmbuf;
+	c = 1;
+	
+	for (s = strtoken(&p, parmbuf, " "); s; s = strtoken(&p, NULL, " "))
+	{
+		pparv.parv[c] = s;
+		c++; /* in my dreams */
+	}
+	pparv.parv[c] = NULL;
+	pparv.parc = c;
+	return (&pparv);
+}
+
+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->name, modebuf, parabuf);
+	sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
+	               ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
+	if (MyConnect(client))
+		RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel);
+	else
+		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
+ *
+ *  parv[1] = channel timestamp
+ *  parv[2] = channel name
+ *
+ *  if parc == 3:
+ *  parv[3] = nick names + modes - all in one parameter
+ *
+ *  if parc == 4:
+ *  parv[3] = channel modes
+ *  parv[4] = nick names + modes - all in one parameter
+ *
+ *  if parc > 4:
+ *  parv[3] = channel modes
+ *  parv[4 to parc - 2] = mode parameters
+ *  parv[parc - 1] = nick names + modes
+ */
+
+/* Note: with regards to message tags we use new_message_special()
+ *       here extensively. This because one SJOIN command can (often)
+ *       generate multiple events that are sent to clients,
+ *       for example 1 SJOIN can cause multiple joins, +beI, etc.
+ *       -- Syzop
+ */
+
+/* Some ugly macros, but useful */
+#define Addit(mode,param) if ((strlen(parabuf) + strlen(param) + 11 < MODEBUFLEN) && (b <= MAXMODEPARAMS)) { \
+	if (*parabuf) \
+		strcat(parabuf, " ");\
+	strcat(parabuf, param);\
+	modebuf[b++] = mode;\
+	modebuf[b] = 0;\
+}\
+else {\
+	send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf); \
+	strcpy(parabuf,param);\
+	/* modebuf[0] should stay what it was ('+' or '-') */ \
+	modebuf[1] = mode;\
+	modebuf[2] = '\0';\
+	b = 2;\
+}
+#define Addsingle(x) do { modebuf[b] = x; b++; modebuf[b] = '\0'; } while(0)
+#define CheckStatus(x,y) do { if (modeflags & (y)) { Addit((x), acptr->name); } } while(0)
+
+CMD_FUNC(cmd_sjoin)
+{
+	unsigned short nopara;
+	unsigned short nomode; /**< An SJOIN without MODE? */
+	unsigned short removeours; /**< Remove our modes */
+	unsigned short removetheirs; /**< Remove their modes (or actually: do not ADD their modes, the MODE -... line will be sent later by the other side) */
+	unsigned short merge;	/**< same timestamp: merge their & our modes */
+	char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
+	char cbuf[1024];
+	char scratch_buf[1024]; /**< scratch buffer */
+	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 :") */
+	char *s = NULL;
+	Channel *channel; /**< Channel */
+	aParv *ap;
+	int pcount, i;
+	Hook *h;
+	Cmode *cm;
+	time_t ts, oldts;
+	unsigned short b=0;
+	char *tp, *p, *saved = NULL;
+	
+	if (!IsServer(client) || parc < 4)
+		return;
+
+	if (!IsChannelName(parv[2]))
+		return;
+
+	merge = nopara = nomode = removeours = removetheirs = 0;
+
+	if (parc < 6)
+		nopara = 1;
+
+	if (parc < 5)
+		nomode = 1;
+
+	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;
+		channel->creationtime = ts;
+	}
+	else if (channel->creationtime < ts)
+	{
+		removetheirs = 1;
+	}
+	else if (channel->creationtime == ts)
+	{
+		merge = 1;
+	}
+
+	parabuf[0] = '\0';
+	modebuf[0] = '+';
+	modebuf[1] = '\0';
+
+	/* Grab current modes -> modebuf & parabuf */
+	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;
+
+		modebuf[0] = '-';
+
+		/* remove our modes if any */
+		if (!empty_mode(modebuf))
+		{
+			MessageTag *mtags = NULL;
+			MultiLineMode *mlm;
+			ap = mp2parv(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 */
+		modebuf[0] = '-';
+		modebuf[1] = '\0';
+		parabuf[0] = '\0';
+		b = 1;
+		while(channel->banlist)
+		{
+			Ban *ban = channel->banlist;
+			Addit('b', ban->banstr);
+			channel->banlist = ban->next;
+			safe_free(ban->banstr);
+			safe_free(ban->who);
+			free_ban(ban);
+		}
+		while(channel->exlist)
+		{
+			Ban *ban = channel->exlist;
+			Addit('e', ban->banstr);
+			channel->exlist = ban->next;
+			safe_free(ban->banstr);
+			safe_free(ban->who);
+			free_ban(ban);
+		}
+		while(channel->invexlist)
+		{
+			Ban *ban = channel->invexlist;
+			Addit('I', ban->banstr);
+			channel->invexlist = ban->next;
+			safe_free(ban->banstr);
+			safe_free(ban->who);
+			free_ban(ban);
+		}
+		for (lp = channel->members; lp; lp = lp->next)
+		{
+			Membership *lp2 = find_membership_link(lp->client->user->channel, channel);
+
+			/* Remove all our modes, one by one */
+			for (p = lp->member_modes; *p; p++)
+			{
+				Addit(*p, lp->client->name);
+			}
+			/* And clear all the flags in memory */
+			*lp->member_modes = *lp2->member_modes = '\0';
+		}
+		if (b > 1)
+		{
+			modebuf[b] = '\0';
+			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
+		}
+
+		/* since we're dropping our modes, we want to clear the mlock as well. --nenolod */
+		set_channel_mlock(client, channel, NULL, FALSE);
+	}
+	/* Mode setting done :), now for our beloved clients */
+	parabuf[0] = 0;
+	modebuf[0] = '+';
+	modebuf[1] = '\0';
+	b = 1;
+	strlcpy(cbuf, parv[parc-1], sizeof cbuf);
+
+	sj3_parabuf[0] = '\0';
+	for (i = 2; i <= (parc - 2); i++)
+	{
+		strlcat(sj3_parabuf, parv[i], sizeof sj3_parabuf);
+		if (((i + 1) <= (parc - 2)))
+			strlcat(sj3_parabuf, " ", sizeof sj3_parabuf);
+	}
+
+	/* Now process adding of users & adding of list modes (bans/exempt/invex) */
+
+	snprintf(uid_buf, sizeof uid_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, sj3_parabuf);
+
+	for (s = strtoken(&saved, cbuf, " "); s; s = strtoken(&saved, NULL, " "))
+	{
+		char *setby = client->name; /**< Set by (nick, nick!user@host, or server name) */
+		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 */
+
+		*item_modes = 0;
+		i = 0;
+		tp = s;
+
+		/* UnrealIRCd 4.2.2 and later support "SJSBY" which allows communicating
+		 * setat/setby information for bans, ban exempts and invite exceptions.
+		 */
+		if (SupportSJSBY(client->direction) && (*tp == '<'))
+		{
+			/* Special prefix to communicate timestamp and setter:
+			 * "<" + timestamp + "," + nick[!user@host] + ">" + normal SJOIN stuff
+			 * For example: "<12345,nick>&some!nice@ban"
+			 */
+			char *end = strchr(tp, '>'), *p;
+			if (!end)
+			{
+				/* this obviously should never happen */
+				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';
+
+			p = strchr(tp, ',');
+			if (!p)
+			{
+				/* missing setby parameter */
+				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';
+
+			setat = atol(tp+1);
+			setby = p;
+			sjsby_info = 1;
+
+			tp = end; /* the remainder is used for the actual ban/exempt/invex */
+		}
+
+		/* Process the SJOIN prefixes... */
+		for (p = tp; *p; p++)
+		{
+			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))
+			{
+				p++;
+				break;
+			}
+		}
+
+		/* Now set 'prefix' to the prefixes we encountered.
+		 * This is basically the range tp..p
+		 */
+		strlncpy(prefix, tp, sizeof(prefix), p - tp);
+
+		/* Now copy the "nick" (which can actually be a ban/invex/exempt) */
+		strlcpy(item, p, sizeof(item));
+		if (*item == '\0')
+			continue;
+
+		/* 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;
+
+			/* 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_user(item, NULL)))
+				continue;
+
+			if (acptr->direction != client->direction)
+			{
+				if (IsMember(acptr, channel))
+				{
+					/* Nick collision, don't kick or it desyncs -Griever*/
+					continue;
+				}
+			
+				sendto_one(client, NULL,
+				    ":%s KICK %s %s :Fake direction",
+				    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)
+				*item_modes = '\0';
+
+			if (!IsMember(acptr, channel))
+			{
+				/* User joining the channel, send JOIN to local users.
+				 */
+				MessageTag *mtags = NULL;
+
+				add_user_to_channel(channel, acptr, item_modes);
+				unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", acptr,
+					   "User $client joined $channel",
+					   log_data_channel("channel", channel),
+					   log_data_string("modes", item_modes));
+				RunHook(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags);
+				new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->name);
+				send_join_to_local_users(acptr, channel, mtags);
+				free_message_tags(mtags);
+			}
+
+			/* 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, channel->name);
+				/* Double-check the new buffer is sufficient to concat the data */
+				if (strlen(uid_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
+				{
+					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;
+				}
+			}
+			sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, acptr->id);
+
+			if (strlen(uid_sjsby_buf) + strlen(prefix) + IDLEN > 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, 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)
+				{
+					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;
+				}
+			}
+			sprintf(uid_sjsby_buf+strlen(uid_sjsby_buf), "%s%s ", prefix, acptr->id);
+		}
+		else
+		{
+			/* It's a list mode................ */
+			const char *str;
+			
+			if (removetheirs)
+				continue;
+
+			/* 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 (*item_modes == 'b')
+			{
+				if (add_listmode_ex(&channel->banlist, client, channel, item, setby, setat) != -1)
+				{
+					Addit('b', item);
+				}
+			}
+			if (*item_modes == 'e')
+			{
+				if (add_listmode_ex(&channel->exlist, client, channel, item, setby, setat) != -1)
+				{
+					Addit('e', item);
+				}
+			}
+			if (*item_modes == 'I')
+			{
+				if (add_listmode_ex(&channel->invexlist, client, channel, item, setby, setat) != -1)
+				{
+					Addit('I', item);
+				}
+			}
+
+			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, channel->name);
+				/* Double-check the new buffer is sufficient to concat the data */
+				if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 5)
+				{
+					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, item);
+
+			*scratch_buf = '\0';
+			if (sjsby_info)
+				add_sjsby(scratch_buf, setby, setat);
+			strcat(scratch_buf, prefix);
+			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, channel->name);
+				/* Double-check the new buffer is sufficient to concat the data */
+				if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 5)
+				{
+					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;
+				}
+			}
+			strcpy(uid_sjsby_buf+strlen(uid_sjsby_buf), scratch_buf); /* size already checked above */
+		}
+		continue;
+	}
+
+	/* Send out any possible remainder.. */
+	sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
+	sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
+
+	if (!empty_mode(modebuf))
+	{
+		modebuf[b] = '\0';
+		send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
+	}
+	
+	if (!merge && !removetheirs && !nomode)
+	{
+		MessageTag *mtags = NULL;
+		MultiLineMode *mlm;
+
+		strlcpy(modebuf, parv[3], sizeof modebuf);
+		parabuf[0] = '\0';
+		if (!nopara)
+		{
+			for (b = 4; b <= (parc - 2); b++)
+			{
+				strlcat(parabuf, parv[b], sizeof parabuf);
+				strlcat(parabuf, " ", sizeof parabuf);
+			}
+		}
+		ap = mp2parv(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.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);
+		parabuf[0] = '\0';
+		if (!nopara)
+		{
+			for (b = 4; b <= (parc - 2); b++)
+			{
+				strlcat(parabuf, parv[b], sizeof parabuf);
+				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);
+		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.
+		 */
+		strlcpy(modebuf, "-", sizeof modebuf);
+		parabuf[0] = '\0';
+		b = 1;
+
+		/* 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'))
+		{
+			/* 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 had something that is now gone
+		 * note that: oldmode.* = us, channel->mode.* = merged.
+		 */
+		for (cm=channelmodes; cm; cm = cm->next)
+		{
+			if (cm->letter &&
+			    !cm->local &&
+			    (oldmode.mode & cm->mode) &&
+			    !(channel->mode.mode & cm->mode))
+			{
+				if (cm->paracount)
+				{
+					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(cm->letter);
+				}
+			}
+		}
+
+		if (b > 1)
+		{
+			Addsingle('+');
+		}
+		else
+		{
+			strlcpy(modebuf, "+", sizeof modebuf);
+			b = 1;
+		}
+
+		/* 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 (cm=channelmodes; cm; cm = cm->next)
+		{
+			if ((cm->letter) &&
+			    !(oldmode.mode & cm->mode) &&
+			    (channel->mode.mode & cm->mode))
+			{
+				if (cm->paracount)
+				{
+					const char *parax = cm_getparameter(channel, cm->letter);
+					if (parax)
+					{
+						Addit(cm->letter, parax);
+					}
+				} else {
+					Addsingle(cm->letter);
+				}
+			}
+		}
+
+		/* now, if we had diffent para modes - this loop really could be done better, but */
+
+		/* Now, check for any param differences in extended channel modes..
+		 * note that: oldmode.* = us before, channel->mode.* = merged.
+		 * if we win: copy oldmode to channel mode, if they win: send the mode
+		 */
+		for (cm=channelmodes; cm; cm = cm->next)
+		{
+			if (cm->letter && cm->paracount &&
+			    (oldmode.mode & cm->mode) &&
+			    (channel->mode.mode & cm->mode))
+			{
+				int r;
+				const char *parax;
+				char flag = cm->letter;
+				void *ourm = GETPARASTRUCTEX(oldmode.mode_params, flag);
+				void *theirm = GETPARASTRUCT(channel, flag);
+				
+				r = cm->sjoin_check(channel, ourm, theirm);
+				switch (r)
+				{
+					case EXSJ_WEWON:
+						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);
+						Addit(cm->letter, parax);
+						break;
+
+					case EXSJ_SAME:
+						break;
+
+					case EXSJ_MERGE:
+						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:
+						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;
+				}
+			}
+		}
+
+		Addsingle('\0');
+
+		if (!empty_mode(modebuf))
+			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
+
+		/* free the oldmode.* crap :( */
+		extcmode_free_paramlist(oldmode.mode_params);
+	}
+
+	for (h = Hooks[HOOKTYPE_CHANNEL_SYNCED]; h; h = h->next)
+	{
+		int i = (*(h->func.intfunc))(channel,merge,removetheirs,nomode);
+		if (i == 1)
+			return; /* channel no longer exists */
+	}
+
+	/* we should be synced by now, */
+	if ((oldts != -1) && (oldts != channel->creationtime))
+	{
+		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
+	 * the channel actually has no users in it at this point,
+	 * then destroy the channel.
+	 */
+	if (!channel->users)
+	{
+		sub1_from_channel(channel);
+		return;
+	}
+}
diff --git a/ircd/src/modules/slog.c b/ircd/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, j, 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/ircd/src/modules/sqline.c b/ircd/src/modules/sqline.c
@@ -0,0 +1,87 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/sqline.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_sqline);
+
+/* Place includes here */
+#define MSG_SQLINE      "SQLINE"        /* SQLINE */
+
+
+
+ModuleHeader MOD_HEADER
+  = {
+	"sqline",	/* Name of module */
+	"5.0", /* Version */
+	"command /sqline", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SQLINE, cmd_sqline, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* cmd_sqline
+ *	parv[1] = nickmask
+ *	parv[2] = reason
+ */
+CMD_FUNC(cmd_sqline)
+{
+	char mo[32];
+	const char *comment = (parc == 3) ? parv[2] : NULL;
+	const char *tkllayer[9] = {
+		me.name,        /*0  server.name */
+		"+",            /*1  +|- */
+		"Q",            /*2  G   */
+		"*" ,           /*3  user */
+		parv[1],        /*4  host */
+		client->name,     /*5  setby */
+		"0",            /*6  expire_at */
+		NULL,           /*7  set_at */
+		"no reason"     /*8  reason */
+	};
+
+	if (parc < 2)
+		return;
+
+	ircsnprintf(mo, sizeof(mo), "%lld", (long long)TStime());
+	tkllayer[7] = mo;
+	tkllayer[8] = comment ? comment : "no reason";
+	cmd_tkl(&me, NULL, 9, tkllayer);
+}
diff --git a/ircd/src/modules/squit.c b/ircd/src/modules/squit.c
@@ -0,0 +1,153 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/squit.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_squit);
+
+#define MSG_SQUIT 	"SQUIT"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"squit",
+	"5.0",
+	"command /squit", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SQUIT, cmd_squit, 2, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_squit
+**	parv[1] = server name
+**	parv[parc-1] = comment
+*/
+CMD_FUNC(cmd_squit)
+{
+	const char *server;
+	Client *target;
+	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?
+
+	if (!ValidatePermissionsForPath("route:local",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SQUIT");
+		return;
+	}
+
+	server = parv[1];
+
+	target = find_server_quick(server);
+	if (target && IsMe(target))
+	{
+		target = client->direction;
+		server = client->direction->local->sockhost;
+	}
+
+	/*
+	   ** SQUIT semantics is tricky, be careful...
+	   **
+	   ** The old (irc2.2PL1 and earlier) code just cleans away the
+	   ** server client from the links (because it is never true
+	   ** "client->direction == target".
+	   **
+	   ** This logic here works the same way until "SQUIT host" hits
+	   ** the server having the target "host" as local link. Then it
+	   ** will do a real cleanup spewing SQUIT's and QUIT's to all
+	   ** directions, also to the link from which the orinal SQUIT
+	   ** came, generating one unnecessary "SQUIT host" back to that
+	   ** link.
+	   **
+	   ** One may think that this could be implemented like
+	   ** "hunt_server" (e.g. just pass on "SQUIT" without doing
+	   ** nothing until the server having the link as local is
+	   ** reached). Unfortunately this wouldn't work in the real life,
+	   ** because either target may be unreachable or may not comply
+	   ** with the request. In either case it would leave target in
+	   ** links--no command to clear it away. So, it's better just
+	   ** clean out while going forward, just to be sure.
+	   **
+	   ** ...of course, even better cleanout would be to QUIT/SQUIT
+	   ** dependant users/servers already on the way out, but
+	   ** currently there is not enough information about remote
+	   ** clients to do this...   --msa
+	 */
+	if (!target)
+	{
+		sendnumeric(client, ERR_NOSUCHSERVER, server);
+		return;
+	}
+	if (MyUser(client) && ((!ValidatePermissionsForPath("route:global",client,NULL,NULL,NULL) && !MyConnect(target)) ||
+	    (!ValidatePermissionsForPath("route:local",client,NULL,NULL,NULL) && MyConnect(target))))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	/*
+	   **  Notify all opers, if my local link is remotely squitted
+	 */
+	if (MyConnect(target) && !MyUser(client))
+	{
+		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))
+	{
+		if (target->user)
+		{
+			sendnotice(client, "ERROR: You're connected to %s, we cannot SQUIT ourselves",
+			           me.name);
+			return;
+		}
+		unreal_log(ULOG_INFO, "link", "SQUIT", client,
+		           "SQUIT: Forced server disconnect of $target by $client ($reason)",
+		           log_data_client("target", target),
+		           log_data_string("reason", comment));
+	}
+
+	exit_client_ex(target, client->direction, recv_mtags, comment);
+}
diff --git a/ircd/src/modules/sreply.c b/ircd/src/modules/sreply.c
@@ -0,0 +1,87 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/sreply.c
+ *   (C) 2022 Valware and the UnrealIRCd Team
+ * 
+ *   Allows services to send Standard Replies in response to non-privmsg commands:
+ *   https://ircv3.net/specs/extensions/standard-replies
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+CMD_FUNC(cmd_sreply);
+
+ModuleHeader MOD_HEADER
+  = {
+	"sreply",	/* Name of module */
+	"1.0", /* Version */
+	"Server command SREPLY", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "SREPLY", cmd_sreply, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/**
+ * cmd_sreply
+ * @param parv[1]		Nick|UID
+ * @param parv[2]		"F", "W" or "N" for FAIL, WARN and NOTE.
+ * @param parv[3]		The rest of the message
+*/
+CMD_FUNC(cmd_sreply)
+{
+	Client *target;
+
+	if (parc < 4)
+		return;
+
+	target = find_user(parv[1], NULL);
+	if (!target && !(target = find_server_by_uid(parv[1])))
+		return;
+
+	if (!MyUser(target))
+	{
+		/* Target is a remote user/server */
+		sendto_one(target, recv_mtags, ":%s SREPLY %s %s :%s", client->name, parv[1], parv[2], parv[3]);
+		return;
+	}
+
+	/* For a locally connected user... */
+	if (!strcmp(parv[2],"F"))
+		sendto_one(target, recv_mtags, ":%s FAIL %s", client->name, parv[3]);
+	else if (!strcmp(parv[2],"W"))
+		sendto_one(target, recv_mtags, ":%s WARN %s", client->name, parv[3]);
+	else if (!strcmp(parv[2],"N"))
+		sendto_one(target, recv_mtags, ":%s NOTE %s", client->name, parv[3]);
+}
diff --git a/ircd/src/modules/staff.c b/ircd/src/modules/staff.c
@@ -0,0 +1,178 @@
+/*
+ *   cmd_staff: Displays a file(/URL) when the /STAFF command is used.
+ *   (C) Copyright 2004-2016 Syzop <syzop@vulnscan.org>
+ *   (C) Copyright 2003-2004 AngryWolf <angrywolf@flashmail.com>
+ *
+ *   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
+  = {
+	"staff",
+	"3.8",
+	"/STAFF command",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#define MSG_STAFF	"STAFF"
+
+#define DEF_STAFF_FILE   CONFDIR "/network.staff"
+#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 -"
+#define RPL_ENDOFSTAFF   ":%s 702 %s :End of /STAFF command."
+#define RPL_NOSTAFF      ":%s 703 %s :Network Staff File is missing"
+
+/* Forward declarations */
+static void unload_motd_file(MOTDFile *list);
+CMD_FUNC(cmd_staff);
+static int cb_test(ConfigFile *, ConfigEntry *, int, int *);
+static int cb_conf(ConfigFile *, ConfigEntry *, int);
+static int cb_stats(Client *client, const char *flag);
+static void FreeConf();
+
+static MOTDFile staff;
+static char *staff_file = NULL;
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cb_test);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	memset(&staff, 0, sizeof(staff));
+
+	CommandAdd(modinfo->handle, MSG_STAFF, cmd_staff, MAXPARA, CMD_USER);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cb_conf);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, cb_stats);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	FreeConf();
+	unload_motd_file(&staff);
+
+	return MOD_SUCCESS;
+}
+
+static void FreeConf()
+{
+	safe_free(staff_file);
+}
+
+static void unload_motd_file(MOTDFile *list)
+{
+	MOTDLine *old, *new;
+
+	if (!list)
+		return;
+
+	new = list->lines;
+
+	if (!new)
+		return;
+
+	while (new)
+	{
+		old = new->next;
+		safe_free(new->line);
+		safe_free(new);
+		new = old;
+	}
+}
+
+static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+
+	if (type == CONFIG_SET)
+	{
+		if (!strcmp(ce->name, "staff-file"))
+		{
+			*errs = errors;
+			return errors ? -1 : 1;
+		}
+	}
+
+	return 0;
+}
+
+static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	if (type == CONFIG_SET)
+	{
+		if (!strcmp(ce->name, "staff-file"))
+		{
+			convert_to_absolute_path(&ce->value, CONFDIR);
+			read_motd(ce->value, &staff);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int cb_stats(Client *client, const char *flag)
+{
+	if (*flag == 'S')
+	{
+		sendtxtnumeric(client, "staff-file: %s", STAFF_FILE);
+		return 1;
+	}
+
+	return 0;
+}
+
+/** The routine that actual does the /STAFF command */
+CMD_FUNC(cmd_staff)
+{
+	MOTDFile *temp;
+	MOTDLine *aLine;
+
+	if (!IsUser(client))
+		return;
+
+	if (hunt_server(client, recv_mtags, "STAFF", 1, parc, parv) != HUNTED_ISME)
+		return;
+
+	if (!staff.lines)
+	{
+		sendto_one(client, NULL, RPL_NOSTAFF, me.name, client->name);
+		return;
+	}
+
+	sendto_one(client, NULL, RPL_STAFFSTART, me.name, client->name, NETWORK_NAME);
+
+	temp = &staff;
+
+	for (aLine = temp->lines; aLine; aLine = aLine->next)
+		sendto_one(client, NULL, RPL_STAFF, me.name, client->name, aLine->line);
+
+	sendto_one(client, NULL, RPL_ENDOFSTAFF, me.name, client->name);
+}
diff --git a/ircd/src/modules/standard-replies.c b/ircd/src/modules/standard-replies.c
@@ -0,0 +1,58 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/standard-replies.c
+ *   (C) 2023 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"standard-replies",
+	"6.0",
+	"standard-replies CAP", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_STANDARD_REPLIES = 0L;
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "standard-replies";
+	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_STANDARD_REPLIES);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
diff --git a/ircd/src/modules/starttls.c b/ircd/src/modules/starttls.c
@@ -0,0 +1,121 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/starttls.c
+ *   (C) 2009 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+CMD_FUNC(cmd_starttls);
+
+#define MSG_STARTTLS 	"STARTTLS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"starttls",
+	"5.0",
+	"command /starttls", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+long CLICAP_STARTTLS;
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	CommandAdd(modinfo->handle, MSG_STARTTLS, cmd_starttls, MAXPARA, CMD_UNREGISTERED);
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "tls";
+	ClientCapabilityAdd(modinfo->handle, &cap, &CLICAP_STARTTLS);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+CMD_FUNC(cmd_starttls)
+{
+	SSL_CTX *ctx;
+	int tls_options;
+
+	if (!MyConnect(client) || !IsUnknown(client))
+		return;
+
+	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;
+
+	/* This should never happen? */
+	if (!ctx)
+	{
+		/* Pretend STARTTLS is an unknown command, this is the safest approach */
+		sendnumeric(client, ERR_NOTREGISTERED);
+		return;
+	}
+
+	/* Is STARTTLS disabled? (same response as above) */
+	if (tls_options & TLSFLAG_NOSTARTTLS)
+	{
+		sendnumeric(client, ERR_NOTREGISTERED);
+		return;
+	}
+
+	if (IsSecure(client))
+	{
+		sendnumeric(client, ERR_STARTTLS, "STARTTLS failed. Already using TLS.");
+		return;
+	}
+
+	dbuf_delete(&client->local->recvQ, DBufLength(&client->local->recvQ)); /* Clear up any remaining plaintext commands */
+	sendnumeric(client, RPL_STARTTLS);
+	send_queued(client);
+
+	SetStartTLSHandshake(client);
+	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 (!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);
+		goto fail;
+	}
+
+	/* HANDSHAKE IN PROGRESS */
+	return;
+fail:
+	/* Failure */
+	sendnumeric(client, ERR_STARTTLS, "STARTTLS failed");
+	client->local->ssl = NULL;
+	ClearTLS(client);
+	SetUnknown(client);
+}
diff --git a/ircd/src/modules/stats.c b/ircd/src/modules/stats.c
@@ -0,0 +1,1057 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/stats.c
+ *   (C) 2004-present 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_stats);
+
+#define MSG_STATS 	"STATS"
+
+ModuleHeader MOD_HEADER
+  = {
+	"stats",
+	"5.0",
+	"command /stats",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_STATS, cmd_stats, 3, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+extern MODVAR int  max_connection_count;
+
+int stats_banversion(Client *, const char *);
+int stats_links(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_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
+
+struct statstab {
+	char flag;
+	char *longflag;
+	int (*func)(Client *client, const char *para);
+	int options;
+};
+
+/* Must be listed lexicographically */
+/* Long flags must be lowercase */
+struct statstab StatsTable[] = {
+	{ 'B', "banversion",	stats_banversion,	0		},
+	{ 'C', "link", 		stats_links,		0 		},
+	{ 'G', "gline",		stats_gline,		FLAGS_AS_PARA	},
+	{ 'H', "link",	 	stats_links,		0 		},
+	{ 'I', "allow",		stats_allow,		0 		},
+	{ 'K', "kline",		stats_kline,		0 		},
+	{ 'L', "linkinfoall",	stats_linkinfoall,	SERVER_AS_PARA	},
+	{ 'M', "command",	stats_command,		0 		},
+	{ 'O', "oper",		stats_oper,		0 		},
+	{ 'P', "port",		stats_port,		0 		},
+	{ 'Q', "sqline",	stats_sqline,		FLAGS_AS_PARA 	},
+	{ 'S', "set",		stats_set,		0		},
+	{ 'T', "traffic",	stats_traffic,		0 		},
+	{ 'U', "uline",		stats_uline,		0 		},
+	{ 'V', "vhost", 	stats_vhost,		0 		},
+	{ 'W', "fdtable",       stats_fdtable,          0               },
+	{ 'X', "notlink",	stats_notlink,		0 		},
+	{ 'Y', "class",		stats_class,		0 		},
+	{ 'c', "link", 		stats_links,		0 		},
+	{ 'e', "except",	stats_except,		0 		},
+	{ 'f', "spamfilter",	stats_spamfilter,	FLAGS_AS_PARA	},
+	{ 'g', "gline",		stats_gline,		FLAGS_AS_PARA	},
+	{ 'h', "link", 		stats_links,		0 		},
+	{ 'j', "officialchans", stats_officialchannels, 0 		},
+	{ 'k', "kline",		stats_kline,		0 		},
+	{ 'l', "linkinfo",	stats_linkinfo,		SERVER_AS_PARA 	},
+	{ 'm', "command",	stats_command,		0 		},
+	{ 'n', "banrealname",	stats_banrealname,	0 		},
+	{ 'o', "oper",		stats_oper,		0 		},
+	{ 'q', "bannick",	stats_bannick,		FLAGS_AS_PARA	},
+	{ 'r', "chanrestrict",	stats_chanrestrict,	0 		},
+	{ 's', "shun",		stats_shun,		FLAGS_AS_PARA	},
+	{ 't', "tld",		stats_tld,		0 		},
+	{ 'u', "uptime",	stats_uptime,		0 		},
+	{ 'v', "denyver",	stats_denyver,		0 		},
+	{ 'x', "notlink",	stats_notlink,		0 		},
+	{ 'y', "class",		stats_class,		0 		},
+	{ 0, 	NULL, 		NULL, 			0		}
+};
+
+int stats_compare(const char *s1, const char *s2)
+{
+	/* The long stats flags are always lowercase */
+	while (*s1 == tolower(*s2))
+	{
+		if (*s1 == 0)
+			return 0;
+		s1++;
+		s2++;
+	}
+	return 1;
+}
+
+static inline struct statstab *stats_binary_search(char c) {
+	int start = 0;
+	int stop = sizeof(StatsTable)/sizeof(StatsTable[0])-1;
+	int mid;
+	while (start <= stop) {
+		mid = (start+stop)/2;
+		if (c < StatsTable[mid].flag)
+			stop = mid-1;
+		else if (StatsTable[mid].flag == c)
+			return &StatsTable[mid];
+		else
+			start = mid+1;
+	}
+	return NULL;
+}
+
+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))
+			return &StatsTable[i];
+	return NULL;
+}
+
+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);
+	return buf;
+}
+
+static inline void stats_help(Client *client)
+{
+	sendnumeric(client, RPL_STATSHELP, "/Stats flags:");
+	sendnumeric(client, RPL_STATSHELP, "B - banversion - Send the ban version list");
+	sendnumeric(client, RPL_STATSHELP, "b - badword - Send the badwords list");
+	sendnumeric(client, RPL_STATSHELP, "C - link - Send the link block list");
+	sendnumeric(client, RPL_STATSHELP, "d - denylinkauto - Send the deny link (auto) block list");
+	sendnumeric(client, RPL_STATSHELP, "D - denylinkall - Send the deny link (all) block list");
+	sendnumeric(client, RPL_STATSHELP, "e - except - Send the ban exception list (ELINEs and in config))");
+	sendnumeric(client, RPL_STATSHELP, "f - spamfilter - Send the spamfilter list");
+	sendnumeric(client, RPL_STATSHELP, "F - denydcc - Send the deny dcc and allow dcc block lists");
+	sendnumeric(client, RPL_STATSHELP, "G - gline - Send the gline and gzline list");
+	sendnumeric(client, RPL_STATSHELP, "  Extended flags: [+/-mrs] [mask] [reason] [setby]");
+	sendnumeric(client, RPL_STATSHELP, "   m Return glines matching/not matching the specified mask");
+	sendnumeric(client, RPL_STATSHELP, "   r Return glines with a reason matching/not matching the specified reason");
+	sendnumeric(client, RPL_STATSHELP, "   s Return glines set by/not set by clients matching the specified name");
+	sendnumeric(client, RPL_STATSHELP, "I - allow - Send the allow block list");
+	sendnumeric(client, RPL_STATSHELP, "j - officialchans - Send the offical channels list");
+	sendnumeric(client, RPL_STATSHELP, "K - kline - Send the ban user/ban ip/except ban block list");
+	sendnumeric(client, RPL_STATSHELP, "l - linkinfo - Send link information");
+	sendnumeric(client, RPL_STATSHELP, "L - linkinfoall - Send all link information");
+	sendnumeric(client, RPL_STATSHELP, "M - command - Send list of how many times each command was used");
+	sendnumeric(client, RPL_STATSHELP, "n - banrealname - Send the ban realname block list");
+	sendnumeric(client, RPL_STATSHELP, "O - oper - Send the oper block list");
+	sendnumeric(client, RPL_STATSHELP, "P - port - Send information about ports");
+	sendnumeric(client, RPL_STATSHELP, "q - bannick - Send the ban nick block list");
+	sendnumeric(client, RPL_STATSHELP, "Q - sqline - Send the global qline list");
+	sendnumeric(client, RPL_STATSHELP, "r - chanrestrict - Send the channel deny/allow block list");
+	sendnumeric(client, RPL_STATSHELP, "S - set - Send the set block list");
+	sendnumeric(client, RPL_STATSHELP, "s - shun - Send the shun list");
+	sendnumeric(client, RPL_STATSHELP, "  Extended flags: [+/-mrs] [mask] [reason] [setby]");
+	sendnumeric(client, RPL_STATSHELP, "   m Return shuns matching/not matching the specified mask");
+	sendnumeric(client, RPL_STATSHELP, "   r Return shuns with a reason matching/not matching the specified reason");
+	sendnumeric(client, RPL_STATSHELP, "   s Return shuns set by/not set by clients matching the specified name");
+	sendnumeric(client, RPL_STATSHELP, "t - tld - Send the tld block list");
+	sendnumeric(client, RPL_STATSHELP, "T - traffic - Send traffic information");
+	sendnumeric(client, RPL_STATSHELP, "u - uptime - Send the server uptime and connection count");
+	sendnumeric(client, RPL_STATSHELP, "U - uline - Send the ulines block list");
+	sendnumeric(client, RPL_STATSHELP, "v - denyver - Send the deny version block list");
+	sendnumeric(client, RPL_STATSHELP, "V - vhost - Send the vhost block list");
+	sendnumeric(client, RPL_STATSHELP, "W - fdtable - Send the FD table listing");
+	sendnumeric(client, RPL_STATSHELP, "X - notlink - Send the list of servers that are not current linked");
+	sendnumeric(client, RPL_STATSHELP, "Y - class - Send the class block list");
+}
+
+static inline int allow_user_stats_short(char c)
+{
+	char l;
+	if (!ALLOW_USER_STATS)
+		return 0;
+	if (strchr(ALLOW_USER_STATS, c))
+		return 1;
+	l = tolower(c);
+	/* Hack for the flags that are case insensitive */
+	if (l == 'o' || l == 'y' || l == 'k' || l == 'g' || l == 'x' || l == 'c' ||
+		l =='f' || l == 'i' || l == 'h' || l == 'm')
+	{
+		if (islower(c) && strchr(ALLOW_USER_STATS, toupper(c)))
+			return 1;
+		else if (isupper(c) && strchr(ALLOW_USER_STATS, tolower(c)))
+			return 1;
+	}
+	/* Hack for c/C/H/h */
+	if (l == 'c')
+	{
+		if (strpbrk(ALLOW_USER_STATS, "hH"))
+			return 1;
+	} else if (l == 'h')
+		if (strpbrk(ALLOW_USER_STATS, "cC"))
+			return 1;
+	return 0;
+}
+
+static inline int allow_user_stats_long(const char *s)
+{
+	OperStat *os;
+	for (os = iConf.allow_user_stats_ext; os; os = os->next)
+	{
+		if (!strcasecmp(os->flag, s))
+			return 1;
+	}
+	return 0;
+}
+
+/* This is pretty slow, but it isn't used often so it isn't a big deal */
+static inline char *allow_user_stats_long_to_short()
+{
+	static char buffer[BUFSIZE+1];
+	int i = 0;
+	OperStat *os;
+	for (os = iConf.allow_user_stats_ext; os; os = os->next)
+	{
+		struct statstab *stat = stats_search(os->flag);
+		if (!stat)
+			continue;
+		if (!strchr(ALLOW_USER_STATS, stat->flag))
+			buffer[i++] = stat->flag;
+	}
+	buffer[i] = 0;
+	return buffer;
+}
+
+CMD_FUNC(cmd_stats)
+{
+	struct statstab *stat;
+	char flags[2];
+
+	if (parc == 3 && parv[2][0] != '+' && parv[2][0] != '-')
+	{
+		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, "STATS", 2, parc, parv) != HUNTED_ISME)
+			return;
+	}
+	if (parc < 2 || !*parv[1])
+	{
+		stats_help(client);
+		sendnumeric(client, RPL_ENDOFSTATS, '*');
+		return;
+	}
+
+	/* Decide if we are looking for 1 char or a string */
+	if (parv[1][0] && !parv[1][1])
+	{
+		if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL) && !allow_user_stats_short(parv[1][0]))
+		{
+			sendnumeric(client, ERR_NOPRIVILEGES);
+			return;
+		}
+		/* Old style, we can use a binary search here */
+		stat = stats_binary_search(parv[1][0]);
+	}
+	else
+	{
+		if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL) && !allow_user_stats_long(parv[1]))
+		{
+			sendnumeric(client, ERR_NOPRIVILEGES);
+			return;
+		}
+		/* New style, search the hard way */
+		stat = stats_search(parv[1]);
+	}
+
+	if (!stat)
+	{
+		/* Not found. Perhaps a module provides it? */
+		Hook *h;
+		int found = 0, n;
+		for (h = Hooks[HOOKTYPE_STATS]; h; h = h->next)
+		{
+			n = (*(h->func.intfunc))(client, parv[1]);
+			if (n == 1)
+				found = 1;
+		}
+		if (!found)
+			stats_help(client);
+		sendnumeric(client, RPL_ENDOFSTATS, '*');
+		return;
+	}
+
+	flags[0] = stat->flag;
+	flags[1] = '\0';
+
+	if (stat->options & FLAGS_AS_PARA)
+	{
+		if (parc > 2 && (parv[2][0] == '+' || parv[2][0] == '-'))
+		{
+			if (parc > 3)
+				stat->func(client, stats_combine_parv(parv[2],parv[3]));
+			else
+				stat->func(client, parv[2]);
+		}
+		else if (parc > 3)
+			stat->func(client, parv[3]);
+		else
+			stat->func(client, NULL);
+	}
+	else if (stat->options & SERVER_AS_PARA)
+	{
+		if (parc > 2)
+			stat->func(client, parv[2]);
+		else
+			stat->func(client, NULL);
+	}
+	else
+		stat->func(client, NULL);
+
+	/* Modules can append data:
+	 * ('STATS S' already has special code for this that
+	 *  maintains certain ordering, so not included here)
+	 */
+	if (stat->flag != 'S')
+	{
+		RunHook(HOOKTYPE_STATS, client, flags);
+	}
+
+	sendnumeric(client, RPL_ENDOFSTATS, stat->flag);
+}
+
+int stats_banversion(Client *client, const char *para)
+{
+	ConfigItem_ban *bans;
+	for (bans = conf_ban; bans; bans = bans->next)
+	{
+		if (bans->flag.type != CONF_BAN_VERSION)
+			continue;
+		sendnumeric(client, RPL_STATSBANVER,
+			bans->mask, bans->reason ? bans->reason : "No Reason");
+	}
+	return 0;
+}
+
+int stats_links(Client *client, const char *para)
+{
+	ConfigItem_link *link_p;
+#ifdef DEBUGMODE
+	Client *acptr;
+#endif
+	for (link_p = conf_link; link_p; link_p = link_p->next)
+	{
+		sendnumericfmt(client, RPL_STATSCLINE, "C - * %s %i %s %s%s%s",
+			link_p->servername,
+			link_p->outgoing.port,
+			link_p->class->name,
+			(link_p->outgoing.options & CONNECT_AUTO) ? "a" : "",
+			(link_p->outgoing.options & CONNECT_TLS) ? "S" : "",
+			(link_p->flag.temporary == 1) ? "T" : "");
+#ifdef DEBUGMODE
+		sendnotice(client, "%s (%p) has refcount %d",
+			link_p->servername, link_p, link_p->refcount);
+#endif
+		if (link_p->hub)
+			sendnumericfmt(client, RPL_STATSHLINE, "H %s * %s",
+				link_p->hub, link_p->servername);
+		else if (link_p->leaf)
+			sendnumericfmt(client, RPL_STATSLLINE, "L %s * %s %d",
+				link_p->leaf, link_p->servername, link_p->leaf_depth);
+	}
+#ifdef DEBUGMODE
+	list_for_each_entry(acptr, &client_list, client_node)
+		if (MyConnect(acptr) && acptr->server && !IsMe(acptr))
+		{
+			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->server->conf,
+					acptr->server->conf->refcount,
+					acptr->server->conf->flag.temporary ? "YES" : "NO");
+		}
+#endif
+	return 0;
+}
+
+int stats_gline(Client *client, const char *para)
+{
+	int cnt = 0;
+	tkl_stats(client, TKL_GLOBAL|TKL_KILL, para, &cnt);
+	tkl_stats(client, TKL_GLOBAL|TKL_ZAP, para, &cnt);
+	return 0;
+}
+
+int stats_spamfilter(Client *client, const char *para)
+{
+	int cnt = 0;
+	tkl_stats(client, TKL_SPAMF, para, &cnt);
+	tkl_stats(client, TKL_GLOBAL|TKL_SPAMF, para, &cnt);
+	return 0;
+}
+
+int stats_except(Client *client, const char *para)
+{
+	int cnt = 0;
+	tkl_stats(client, TKL_EXCEPTION, para, &cnt);
+	tkl_stats(client, TKL_EXCEPTION|TKL_GLOBAL, para, &cnt);
+	return 0;
+}
+
+int stats_allow(Client *client, const char *para)
+{
+	ConfigItem_allow *allows;
+	NameValuePrioList *m;
+
+	for (allows = conf_allow; allows; allows = allows->next)
+	{
+		for (m = allows->match->printable_list; m; m = m->next)
+		{
+			sendnumeric(client, RPL_STATSILINE,
+				    namevalue_nospaces(m), "-",
+				    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, const char *para)
+{
+	int i;
+	RealCommand *mptr;
+	for (i = 0; i < 256; i++)
+		for (mptr = CommandHash[i]; mptr; mptr = mptr->next)
+			if (mptr->count)
+			sendnumeric(client, RPL_STATSCOMMANDS, mptr->cmd,
+				mptr->count, mptr->bytes);
+
+	return 0;
+}
+
+int stats_oper(Client *client, const char *para)
+{
+	ConfigItem_oper *o;
+	NameValuePrioList *m;
+
+	for (o = conf_oper; o; o = o->next)
+	{
+		for (m = o->match->printable_list; m; m = m->next)
+		{
+			sendnumeric(client, RPL_STATSOLINE,
+			            'O', namevalue_nospaces(m), o->name,
+			            o->operclass ? o->operclass: "",
+			            o->class->name ? o->class->name : "");
+		}
+	}
+	return 0;
+}
+
+static char *stats_port_helper(ConfigItem_listen *listener)
+{
+	static char buf[256];
+
+	ircsnprintf(buf, sizeof(buf), "%s%s%s",
+	    (listener->options & LISTENER_CLIENTSONLY)? "clientsonly ": "",
+	    (listener->options & LISTENER_SERVERSONLY)? "serversonly ": "",
+	    (listener->options & LISTENER_DEFER_ACCEPT)? "defer-accept ": "");
+
+	/* And one of these.. */
+	if (listener->options & LISTENER_CONTROL)
+		strlcat(buf, "control ", sizeof(buf));
+	else if (listener->socket_type == SOCKET_TYPE_UNIX)
+		;
+	else if (listener->options & LISTENER_TLS)
+		strlcat(buf, "tls ", sizeof(buf));
+	else
+		strlcat(buf, "plaintext ", sizeof(buf));
+	return buf;
+}
+
+int stats_port(Client *client, const char *para)
+{
+	ConfigItem_listen *listener;
+
+	for (listener = conf_listen; listener != NULL; listener = listener->next)
+	{
+		if (!(listener->options & LISTENER_BOUND))
+			continue;
+		if ((listener->options & LISTENER_SERVERSONLY) && !ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
+			continue;
+		if (listener->socket_type == SOCKET_TYPE_UNIX)
+		{
+			sendnotice(client, "*** Listener on %s (UNIX): has %i client(s), options: %s %s",
+				   listener->file,
+				   listener->clients,
+				   stats_port_helper(listener),
+				   listener->flag.temporary ? "[TEMPORARY]" : "");
+		} else {
+			sendnotice(client, "*** Listener on %s:%i (%s): has %i client(s), options: %s %s",
+				   listener->ip,
+				   listener->port,
+				   listener->socket_type == SOCKET_TYPE_IPV6 ? "IPv6" : "IPv4",
+				   listener->clients,
+				   stats_port_helper(listener),
+				   listener->flag.temporary ? "[TEMPORARY]" : "");
+		}
+	}
+	return 0;
+}
+
+int stats_bannick(Client *client, const char *para)
+{
+	int cnt = 0;
+	tkl_stats(client, TKL_NAME, para, &cnt);
+	tkl_stats(client, TKL_GLOBAL|TKL_NAME, para, &cnt);
+	return 0;
+}
+
+int stats_traffic(Client *client, const char *para)
+{
+	Client *acptr;
+	IRCStatistics *sp;
+	IRCStatistics tmp;
+	time_t now = TStime();
+
+	sp = &tmp;
+	memcpy(sp, &ircstats, sizeof(IRCStatistics));
+
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+	{
+		if (IsServer(acptr))
+		{
+			sp->is_sti += now - acptr->local->creationtime;
+			sp->is_sv++;
+		}
+		else if (IsUser(acptr))
+		{
+			sp->is_cti += now - acptr->local->creationtime;
+			sp->is_cl++;
+		}
+		else if (IsUnknown(acptr))
+			sp->is_ni++;
+	}
+
+	sendnumericfmt(client, RPL_STATSDEBUG, "accepts %u refused %u", sp->is_ac, sp->is_ref);
+	sendnumericfmt(client, RPL_STATSDEBUG, "unknown commands %u prefixes %u", sp->is_unco, sp->is_unpf);
+	sendnumericfmt(client, RPL_STATSDEBUG, "nick collisions %u unknown closes %u", sp->is_kill, sp->is_ni);
+	sendnumericfmt(client, RPL_STATSDEBUG, "wrong direction %u empty %u", sp->is_wrdi, sp->is_empt);
+	sendnumericfmt(client, RPL_STATSDEBUG, "numerics seen %u mode fakes %u", sp->is_num, sp->is_fake);
+	sendnumericfmt(client, RPL_STATSDEBUG, "auth successes %u fails %u", sp->is_asuc, sp->is_abad);
+	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, "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, const char *para)
+{
+	int i;
+
+	for (i = 0; i < MAXCONNECTIONS; i++)
+	{
+		FDEntry *fde = &fd_table[i];
+
+		if (!fde->is_open)
+			continue;
+
+		sendnumericfmt(client, RPL_STATSDEBUG,
+			"fd %3d, desc '%s', read-hdl %p, write-hdl %p, cbdata %p",
+			fde->fd, fde->desc, fde->read_callback, fde->write_callback, fde->data);
+	}
+
+	return 0;
+}
+
+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, const char *para)
+{
+	ConfigItem_vhost *vhosts;
+	NameValuePrioList *m;
+
+	for (vhosts = conf_vhost; vhosts; vhosts = vhosts->next)
+	{
+		for (m = vhosts->match->printable_list; m; m = m->next)
+		{
+			sendtxtnumeric(client, "vhost %s%s%s %s %s",
+			               vhosts->virtuser ? vhosts->virtuser : "",
+			               vhosts->virtuser ? "@" : "",
+			               vhosts->virthost,
+			               vhosts->login,
+			               namevalue_nospaces(m));
+		}
+	}
+	return 0;
+}
+
+int stats_kline(Client *client, const char *para)
+{
+	int cnt = 0;
+	tkl_stats(client, TKL_KILL, NULL, &cnt);
+	tkl_stats(client, TKL_ZAP, NULL, &cnt);
+	return 0;
+}
+
+int stats_banrealname(Client *client, const char *para)
+{
+	ConfigItem_ban *bans;
+	for (bans = conf_ban; bans; bans = bans->next)
+	{
+		if (bans->flag.type == CONF_BAN_REALNAME)
+		{
+			sendnumeric(client, RPL_STATSNLINE, bans->mask, bans->reason
+				? bans->reason : "<no reason>");
+		}
+	}
+	return 0;
+}
+
+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, const char *para)
+{
+	ConfigItem_deny_channel *dchans;
+	ConfigItem_allow_channel *achans;
+	for (dchans = conf_deny_channel; dchans; dchans = dchans->next)
+	{
+		sendtxtnumeric(client, "deny %s %c %s", dchans->channel, dchans->warn ? 'w' : '-', dchans->reason);
+	}
+  	for (achans = conf_allow_channel; achans; achans = achans->next)
+  	{
+		sendtxtnumeric(client, "allow %s", achans->channel);
+	}
+	return 0;
+}
+
+int stats_shun(Client *client, const char *para)
+{
+	int cnt = 0;
+	tkl_stats(client, TKL_GLOBAL|TKL_SHUN, para, &cnt);
+	return 0;
+}
+
+/* should this be moved to a seperate stats flag? */
+int stats_officialchannels(Client *client, const char *para)
+{
+	ConfigItem_offchans *x;
+
+	for (x = conf_offchans; x; x = x->next)
+	{
+		sendtxtnumeric(client, "%s %s", x->name, x->topic ? x->topic : "");
+	}
+	return 0;
+}
+
+#define SafePrint(x)   ((x) ? (x) : "")
+
+/** Helper for stats_set() */
+static void stats_set_anti_flood(Client *client, FloodSettings *f)
+{
+	int i;
+
+	for (i=0; floodoption_names[i]; i++)
+	{
+		if (i == FLD_CONVERSATIONS)
+		{
+			sendtxtnumeric(client, "anti-flood::%s::%s: %d users, new user every %s",
+				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",
+				f->name, floodoption_names[i],
+				(int)f->limit[i], pretty_time_val(f->period[i]));
+		}
+	}
+}
+
+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))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return 0;
+	}
+
+	sendtxtnumeric(client, "*** Configuration Report ***");
+	sendtxtnumeric(client, "network-name: %s", NETWORK_NAME);
+	sendtxtnumeric(client, "default-server: %s", DEFAULT_SERVER);
+	if (SERVICES_NAME)
+	{
+		sendtxtnumeric(client, "services-server: %s", SERVICES_NAME);
+	}
+	if (STATS_SERVER)
+	{
+		sendtxtnumeric(client, "stats-server: %s", STATS_SERVER);
+	}
+	if (SASL_SERVER)
+	{
+		sendtxtnumeric(client, "sasl-server: %s", SASL_SERVER);
+	}
+	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);
+	sendtxtnumeric(client, "modes-on-connect: %s", get_usermode_string_raw(CONN_MODES));
+	sendtxtnumeric(client, "modes-on-oper: %s", get_usermode_string_raw(OPER_MODES));
+	*modebuf = *parabuf = 0;
+	chmode_str(&iConf.modes_on_join, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf));
+	sendtxtnumeric(client, "modes-on-join: %s %s", modebuf, parabuf);
+	if (iConf.min_nick_length)
+		sendtxtnumeric(client, "min-nick-length: %i", iConf.min_nick_length);
+	sendtxtnumeric(client, "nick-length: %i", iConf.nick_length);
+	sendtxtnumeric(client, "snomask-on-oper: %s", OPER_SNOMASK);
+	if (ALLOW_USER_STATS)
+	{
+		char *longflags = allow_user_stats_long_to_short();
+		sendtxtnumeric(client, "allow-user-stats: %s%s", ALLOW_USER_STATS, longflags ? longflags : "");
+	}
+	if (RESTRICT_USERMODES)
+		sendtxtnumeric(client, "restrict-usermodes: %s", RESTRICT_USERMODES);
+	if (RESTRICT_CHANNELMODES)
+		sendtxtnumeric(client, "restrict-channelmodes: %s", RESTRICT_CHANNELMODES);
+	if (RESTRICT_EXTENDEDBANS)
+		sendtxtnumeric(client, "restrict-extendedbans: %s", RESTRICT_EXTENDEDBANS);
+	switch (UHOST_ALLOWED)
+	{
+		case UHALLOW_NEVER:
+			uhallow = "never";
+			break;
+		case UHALLOW_NOCHANS:
+			uhallow = "not-on-channels";
+			break;
+		case UHALLOW_REJOIN:
+			uhallow = "force-rejoin";
+			break;
+		case UHALLOW_ALWAYS:
+		default:
+			uhallow = "always";
+			break;
+	}
+	if (uhallow)
+		sendtxtnumeric(client, "allow-userhost-change: %s", uhallow);
+	sendtxtnumeric(client, "hide-ban-reason: %d", HIDE_BAN_REASON);
+	sendtxtnumeric(client, "anti-spam-quit-message-time: %s", pretty_time_val(ANTI_SPAM_QUIT_MSG_TIME));
+	sendtxtnumeric(client, "channel-command-prefix: %s", CHANCMDPFX ? CHANCMDPFX : "`");
+	sendtxtnumeric(client, "tls::certificate: %s", SafePrint(iConf.tls_options->certificate_file));
+	sendtxtnumeric(client, "tls::key: %s", SafePrint(iConf.tls_options->key_file));
+	sendtxtnumeric(client, "tls::trusted-ca-file: %s", SafePrint(iConf.tls_options->trusted_ca_file));
+	sendtxtnumeric(client, "tls::options: %s", iConf.tls_options->options & TLSFLAG_FAILIFNOCERT ? "FAILIFNOCERT" : "");
+	sendtxtnumeric(client, "options::show-opermotd: %d", SHOWOPERMOTD);
+	sendtxtnumeric(client, "options::hide-ulines: %d", HIDE_ULINES);
+	sendtxtnumeric(client, "options::identd-check: %d", IDENT_CHECK);
+	sendtxtnumeric(client, "options::fail-oper-warn: %d", FAILOPER_WARN);
+	sendtxtnumeric(client, "options::show-connect-info: %d", SHOWCONNECTINFO);
+	sendtxtnumeric(client, "options::no-connect-tls-info: %d", NOCONNECTTLSLINFO);
+	sendtxtnumeric(client, "options::dont-resolve: %d", DONT_RESOLVE);
+	sendtxtnumeric(client, "options::mkpasswd-for-everyone: %d", MKPASSWD_FOR_EVERYONE);
+	sendtxtnumeric(client, "options::allow-insane-bans: %d", ALLOW_INSANE_BANS);
+	sendtxtnumeric(client, "options::allow-part-if-shunned: %d", ALLOW_PART_IF_SHUNNED);
+	sendtxtnumeric(client, "maxchannelsperuser: %i", MAXCHANNELSPERUSER);
+	sendtxtnumeric(client, "ping-warning: %i seconds", PINGWARNING);
+	sendtxtnumeric(client, "auto-join: %s", AUTO_JOIN_CHANS ? AUTO_JOIN_CHANS : "0");
+	sendtxtnumeric(client, "oper-auto-join: %s", OPER_AUTO_JOIN_CHANS ? OPER_AUTO_JOIN_CHANS : "0");
+	sendtxtnumeric(client, "static-quit: %s", STATIC_QUIT ? STATIC_QUIT : "<none>");
+	sendtxtnumeric(client, "static-part: %s", STATIC_PART ? STATIC_PART : "<none>");
+	sendtxtnumeric(client, "who-limit: %d", WHOLIMIT);
+	sendtxtnumeric(client, "silence-limit: %d", SILENCE_LIMIT);
+	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);
+	sendtxtnumeric(client, "anti-flood::connect-flood: %d per %s", THROTTLING_COUNT, pretty_time_val(THROTTLING_PERIOD));
+	sendtxtnumeric(client, "anti-flood::handshake-data-flood::amount: %ld bytes", iConf.handshake_data_flood_amount);
+	sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-action: %s", banact_valtostring(iConf.handshake_data_flood_ban_action));
+	sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-time: %s", pretty_time_val(iConf.handshake_data_flood_ban_time));
+
+	/* set::anti-flood */
+	for (s = securitygroups; s; s = s->next)
+		if ((f = find_floodsettings_block(s->name)))
+			stats_set_anti_flood(client, f);
+	f = find_floodsettings_block("unknown-users");
+	stats_set_anti_flood(client, f);
+
+	//if (AWAY_PERIOD)
+	//	sendtxtnumeric(client, "anti-flood::away-flood: %d per %s", AWAY_COUNT, pretty_time_val(AWAY_PERIOD));
+	//sendtxtnumeric(client, "anti-flood::nick-flood: %d per %s", NICK_COUNT, pretty_time_val(NICK_PERIOD));
+	sendtxtnumeric(client, "handshake-timeout: %s", pretty_time_val(iConf.handshake_timeout));
+	sendtxtnumeric(client, "sasl-timeout: %s", pretty_time_val(iConf.sasl_timeout));
+	sendtxtnumeric(client, "ident::connect-timeout: %s", pretty_time_val(IDENT_CONNECT_TIMEOUT));
+	sendtxtnumeric(client, "ident::read-timeout: %s", pretty_time_val(IDENT_READ_TIMEOUT));
+	sendtxtnumeric(client, "spamfilter::ban-time: %s", pretty_time_val(SPAMFILTER_BAN_TIME));
+	sendtxtnumeric(client, "spamfilter::ban-reason: %s", SPAMFILTER_BAN_REASON);
+	sendtxtnumeric(client, "spamfilter::virus-help-channel: %s", SPAMFILTER_VIRUSCHAN);
+	if (SPAMFILTER_EXCEPT)
+		sendtxtnumeric(client, "spamfilter::except: %s", SPAMFILTER_EXCEPT);
+	sendtxtnumeric(client, "check-target-nick-bans: %s", CHECK_TARGET_NICK_BANS ? "yes" : "no");
+	sendtxtnumeric(client, "plaintext-policy::user: %s", policy_valtostr(iConf.plaintext_policy_user));
+	sendtxtnumeric(client, "plaintext-policy::oper: %s", policy_valtostr(iConf.plaintext_policy_oper));
+	sendtxtnumeric(client, "plaintext-policy::server: %s", policy_valtostr(iConf.plaintext_policy_server));
+	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));
+	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);
+#endif
+	return 1;
+}
+
+int stats_tld(Client *client, const char *para)
+{
+	ConfigItem_tld *tld;
+	NameValuePrioList *m;
+
+	for (tld = conf_tld; tld; tld = tld->next)
+	{
+		for (m = tld->match->printable_list; m; m = m->next)
+		{
+			sendnumeric(client, RPL_STATSTLINE, namevalue_nospaces(m),
+			            tld->motd_file,
+			            tld->rules_file ? tld->rules_file : "none");
+		}
+	}
+
+	return 0;
+}
+
+int stats_uptime(Client *client, const char *para)
+{
+	long long uptime;
+
+	uptime = TStime() - me.local->fake_lag;
+	sendnumeric(client, RPL_STATSUPTIME,
+	    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, const char *para)
+{
+	ConfigItem_deny_version *versions;
+	for (versions = conf_deny_version; versions; versions = versions->next)
+	{
+		sendnumeric(client, RPL_STATSVLINE,
+			versions->version, versions->flags, versions->mask);
+	}
+	return 0;
+}
+
+int stats_notlink(Client *client, const char *para)
+{
+	ConfigItem_link *link_p;
+
+	for (link_p = conf_link; link_p; link_p = link_p->next)
+	{
+		if (!find_server_quick(link_p->servername))
+		{
+			sendnumeric(client, RPL_STATSXLINE, link_p->servername,
+				link_p->outgoing.port);
+		}
+	}
+	return 0;
+}
+
+int stats_class(Client *client, const char *para)
+{
+	ConfigItem_class *classes;
+
+	for (classes = conf_class; classes; classes = classes->next)
+	{
+		sendnumeric(client, RPL_STATSYLINE, classes->name, classes->pingfreq, classes->connfreq,
+			classes->maxclients, classes->sendq, classes->recvq ? classes->recvq : DEFAULT_RECVQ);
+#ifdef DEBUGMODE
+		sendnotice(client, "class '%s' has clients=%d, xrefcount=%d",
+			classes->name, classes->clients, classes->xrefcount);
+#endif
+	}
+	return 0;
+}
+
+int stats_linkinfo(Client *client, const char *para)
+{
+	return stats_linkinfoint(client, para, 0);
+}
+
+int stats_linkinfoall(Client *client, const char *para)
+{
+	return stats_linkinfoint(client, para, 1);
+}
+
+int stats_linkinfoint(Client *client, const char *para, int all)
+{
+	int remote = 0;
+	int wilds = 0;
+	int doall = 0;
+	Client *acptr;
+
+	/*
+	 * send info about connections which match, or all if the
+	 * mask matches me.name.  Only restrictions are on those who
+	 * are invisible not being visible to 'foreigners' who use
+	 * a wild card based search to list it.
+	 */
+	if (para)
+	{
+		if (!mycmp(para, me.name))
+			doall = 2;
+		else if (match_simple(para, me.name))
+			doall = 1;
+		if (strchr(para, '*') || strchr(para, '?'))
+			wilds = 1;
+	}
+	else
+		para = me.name;
+
+	sendnumericfmt(client, RPL_STATSLINKINFO, "Name SendQ SendM SendBytes RcveM RcveBytes Open_since :Idle");
+
+	if (!MyUser(client))
+	{
+		remote = 1;
+		wilds = 0;
+	}
+
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+	{
+		if (IsInvisible(acptr) && (doall || wilds) &&
+			!IsOper(acptr) && (acptr != client))
+			continue;
+		if (remote && doall && !IsServer(acptr) && !IsMe(acptr))
+			continue;
+		if (remote && !doall && IsServer(acptr))
+			continue;
+		if (!doall && wilds && !match_simple(para, acptr->name))
+			continue;
+		if (!(para && (IsServer(acptr) || IsListening(acptr))) &&
+		    !(doall || wilds) &&
+		    mycmp(para, acptr->name))
+		{
+			continue;
+		}
+
+		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->server->flags.synced ? "SYNCED" : "NOT SYNCED!!");
+	}
+#endif
+	return 0;
+}
diff --git a/ircd/src/modules/sts.c b/ircd/src/modules/sts.c
@@ -0,0 +1,111 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/sts.c
+ *   (C) 2017 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"sts",
+	"5.0",
+	"Strict Transport Security CAP", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	return MOD_SUCCESS;
+}
+
+void init_sts(ModuleInfo *modinfo);
+
+MOD_LOAD()
+{
+	/* init_sts is delayed to MOD_LOAD due to configuration dependency */
+	init_sts(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Check if this capability should be visible.
+ * Note that 'client' may be NULL.
+ */
+int sts_capability_visible(Client *client)
+{
+	TLSOptions *ssl;
+
+	/* This is possible if queried from the CAP NEW/DEL code */
+	if (client == NULL)
+		return (iConf.tls_options && iConf.tls_options->sts_port) ? 1 : 0;
+
+	if (!IsSecure(client))
+	{
+		if (iConf.tls_options && iConf.tls_options->sts_port)
+			return 1; /* YES, non-TLS user and set::tls::sts-policy configured */
+		return 0; /* NO, there is no sts-policy */
+	}
+
+	ssl = FindTLSOptionsForUser(client);
+
+	if (ssl && ssl->sts_port)
+		return 1;
+
+	return 0;
+}
+
+const char *sts_capability_parameter(Client *client)
+{
+	TLSOptions *ssl;
+	static char buf[256];
+
+	if (IsSecure(client))
+		ssl = FindTLSOptionsForUser(client);
+	else
+		ssl = iConf.tls_options;
+
+	if (!ssl)
+		return ""; /* This would be odd. */
+
+	snprintf(buf, sizeof(buf), "port=%d,duration=%ld", ssl->sts_port, ssl->sts_duration);
+	if (ssl->sts_preload)
+		strlcat(buf, ",preload", sizeof(buf));
+
+	return buf;
+}
+
+void init_sts(ModuleInfo *modinfo)
+{
+	ClientCapabilityInfo cap;
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "sts";
+	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
+	cap.visible = sts_capability_visible;
+	cap.parameter = sts_capability_parameter;
+	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
+}
diff --git a/ircd/src/modules/svsjoin.c b/ircd/src/modules/svsjoin.c
@@ -0,0 +1,97 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/svsjoin.c
+ *   (C) 2000-2001 Carsten V. Munk 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"
+
+CMD_FUNC(cmd_svsjoin);
+
+/* Place includes here */
+#define MSG_SVSJOIN       "SVSJOIN"
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsjoin",	/* Name of module */
+	"5.0", /* Version */
+	"command /svsjoin", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSJOIN, cmd_svsjoin, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/* cmd_svsjoin() - Lamego - Wed Jul 21 20:04:48 1999
+   Copied off PTlink IRCd (C) PTlink coders team.
+	parv[1] - nick to make join
+	parv[2] - channel(s) to join
+	parv[3] - (optional) channel key(s)
+*/
+CMD_FUNC(cmd_svsjoin)
+{
+	Client *target;
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if ((parc < 3) || !(target = find_user(parv[1], NULL)))
+		return;
+
+	if (MyUser(target))
+	{
+		parv[0] = target->name;
+		parv[1] = parv[2];
+		if (parc == 3)
+		{
+			parv[2] = NULL;
+			do_cmd(target, NULL, "JOIN", 2, parv);
+			/* NOTE: 'target' may be killed if we ever implement spamfilter join channel target */
+		} else {
+			parv[2] = parv[3];
+			parv[3] = NULL;
+			do_cmd(target, NULL, "JOIN", 3, parv);
+			/* NOTE: 'target' may be killed if we ever implement spamfilter join channel target */
+		}
+	}
+	else
+	{
+		if (parc == 3)
+			sendto_one(target, NULL, ":%s SVSJOIN %s %s", client->name,
+			    parv[1], parv[2]);
+		else
+			sendto_one(target, NULL, ":%s SVSJOIN %s %s %s", client->name,
+				parv[1], parv[2], parv[3]);
+	}
+}
diff --git a/ircd/src/modules/svskill.c b/ircd/src/modules/svskill.c
@@ -0,0 +1,88 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/svskill.c
+ *   (C) 2004 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"
+
+CMD_FUNC(cmd_svskill);
+
+#define MSG_SVSKILL	"SVSKILL"
+
+ModuleHeader MOD_HEADER
+  = {
+	"svskill",	/* Name of module */
+	"5.0", /* Version */
+	"command /svskill", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSKILL, cmd_svskill, MAXPARA, CMD_SERVER|CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_svskill
+**	parv[1] = client
+**	parv[2] = kill message
+*/
+CMD_FUNC(cmd_svskill)
+{
+	MessageTag *mtags = NULL;
+	Client *target;
+	const char *comment = "SVS Killed";
+	int n;
+
+	if (parc < 2)
+		return;
+	if (parc > 3)
+		return;
+	if (parc == 3)
+		comment = parv[2];
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if (!(target = find_user(parv[1], NULL)))
+		return;
+
+	/* for new_message() we use target here, makes sense for the exit_client, right? */
+	new_message(target, recv_mtags, &mtags);
+	sendto_server(client, 0, 0, mtags, ":%s SVSKILL %s :%s", client->id, target->id, comment);
+	SetKilled(target);
+	exit_client(target, mtags, comment);
+	free_message_tags(mtags);
+}
diff --git a/ircd/src/modules/svslogin.c b/ircd/src/modules/svslogin.c
@@ -0,0 +1,101 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svslogin.c
+ *   (C) 2022 The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+#define MSG_SVSLOGIN "SVSLOGIN"
+
+CMD_FUNC(cmd_svslogin);
+
+ModuleHeader MOD_HEADER
+  = {
+	"svslogin",
+	"6.0",
+	"command /SVSLOGIN", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSLOGIN, cmd_svslogin, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * SVSLOGIN message
+ *
+ * parv[1]: propagation mask
+ * parv[2]: target
+ * parv[3]: account name (SVID)
+ */
+CMD_FUNC(cmd_svslogin)
+{
+	Client *target;
+
+	if (MyUser(client) || (parc < 3) || !parv[3])
+		return;
+
+	/* We actually ignore parv[1] since this is a broadcast message.
+	 * It is a broadcast message because we want ALL servers to know
+	 * that the user is now logged in under account xyz.
+	 */
+
+	target = find_client(parv[2], NULL);
+	if (target)
+	{
+		if (IsServer(target))
+			return;
+
+		if (target->user == NULL)
+			make_user(target);
+
+		strlcpy(target->user->account, parv[3], sizeof(target->user->account));
+		user_account_login(recv_mtags, target);
+		if (MyConnect(target) && IsDead(target))
+			return; /* was killed due to *LINE on ~a probably */
+	} else {
+		/* It is perfectly normal for target to be NULL as this
+		 * happens during registration phase (pre-connect).
+		 * It just means we cannot set any properties for this user,
+		 * which is fine in that case, since it will be synced via
+		 * the UID message instead.
+		 * We still have to broadcast the message, which is why
+		 * we do not return here.
+		 */
+	}
+
+	/* Propagate to the rest of the network */
+	sendto_server(client, 0, 0, NULL, ":%s SVSLOGIN %s %s %s",
+	              client->name, parv[1], parv[2], parv[3]);
+}
+\ No newline at end of file
diff --git a/ircd/src/modules/svslusers.c b/ircd/src/modules/svslusers.c
@@ -0,0 +1,79 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svslusers.c
+ *   (C) 2002 codemastr [Dominick Meglio] (codemastr@unrealircd.com)
+ *
+ *   SVSLUSERS command, allows remote setting of local and global max user count
+ *
+ *   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_svslusers);
+
+#define MSG_SVSLUSERS 	"SVSLUSERS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"svslusers",
+	"5.0",
+	"command /svslusers", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSLUSERS, cmd_svslusers, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_svslusers
+**      parv[1] = server to update
+**      parv[2] = max global users
+**      parv[3] = max local users
+**      If -1 is specified for either number, it is ignored and the current count
+**      is kept.
+*/
+CMD_FUNC(cmd_svslusers)
+{
+        if (!IsSvsCmdOk(client) || parc < 4)
+		return;  
+        if (hunt_server(client, NULL, "SVSLUSERS", 1, parc, parv) == HUNTED_ISME)
+        {
+		int temp;
+		temp = atoi(parv[2]);
+		if (temp >= 0)
+			irccounts.global_max = temp;
+		temp = atoi(parv[3]);
+		if (temp >= 0) 
+			irccounts.me_max = temp;
+        }
+}
diff --git a/ircd/src/modules/svsmode.c b/ircd/src/modules/svsmode.c
@@ -0,0 +1,640 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svsmode.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   SVSMODE and SVS2MODE commands
+ *
+ *   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.
+ */
+
+/* FIXME: this one needs a lot more mtag work !! with _special... */
+
+#include "unrealircd.h"
+
+void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param);
+CMD_FUNC(cmd_svsmode);
+CMD_FUNC(cmd_svs2mode);
+
+#define MSG_SVSMODE 	"SVSMODE"	
+#define MSG_SVS2MODE    "SVS2MODE"
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsmode",
+	"5.0",
+	"command /svsmode and svs2mode", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+char modebuf[BUFSIZE], parabuf[BUFSIZE];
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSMODE, cmd_svsmode, MAXPARA, CMD_SERVER|CMD_USER);
+	CommandAdd(modinfo->handle, MSG_SVS2MODE, cmd_svs2mode, MAXPARA, CMD_SERVER|CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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];
+
+	/* BUILD HOSTS */
+
+	*uhost = *vhost = *ihost = *chost = '\0';
+
+	strlcpy(uhost, make_nick_user_host(acptr->name, 
+		acptr->user->username, acptr->user->realhost),
+		sizeof uhost);
+
+	if (GetIP(acptr)) /* only if we actually have an IP */
+		strlcpy(ihost, make_nick_user_host(acptr->name,
+			acptr->user->username, GetIP(acptr)),
+			sizeof ihost);
+
+	/* The next could have been an IsSetHost(), but I'm playing it safe with regards to backward compat. */
+	if (IsHidden(acptr) &&
+	    !(*acptr->user->cloakedhost && !strcasecmp(acptr->user->virthost, acptr->user->cloakedhost))) 
+	{
+		strlcpy(vhost, make_nick_user_host(acptr->name,
+			acptr->user->username, acptr->user->virthost),
+			sizeof vhost);
+	}
+
+	if (*acptr->user->cloakedhost) /* only if we know the cloaked host */
+		strlcpy(chost, make_nick_user_host(acptr->name, 
+			acptr->user->username, acptr->user->cloakedhost),
+			sizeof chost);
+
+	/* SELECT BANLIST */
+
+	switch (chmode)
+	{
+		case 'b':
+			banlist = &channel->banlist;
+			break;
+		case 'e':
+			banlist = &channel->exlist;
+			break;
+		case 'I':
+			banlist = &channel->invexlist;
+			break;
+		default:
+			abort();
+	}
+
+	/* 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;
+		if (match_simple(ban->banstr, uhost) ||
+		    (*vhost && match_simple(ban->banstr, vhost)) ||
+		    (*ihost && match_simple(ban->banstr, ihost)) ||
+		    (*chost && match_simple(ban->banstr, chost)))
+		{
+			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, &nextbanstr)))
+		{
+			if (extban->is_banned_events & b->ban_check_types)
+			{
+				b->banstr = nextbanstr;
+				if (extban->is_banned(b))
+				{
+					add_send_mode_param(channel, acptr, '-', chmode, ban->banstr);
+					del_listmode(banlist, channel, ban->banstr);
+				}
+			}
+		}
+	}
+	safe_free(b);
+}
+
+void clear_bans(Client *client, Channel *channel, char chmode)
+{
+	Extban *extban;
+	Ban *ban, *bnext;
+	Ban **banlist;
+
+	switch (chmode)
+	{
+		case 'b':
+			banlist = &channel->banlist;
+			break;
+		case 'e':
+			banlist = &channel->exlist;
+			break;
+		case 'I':
+			banlist = &channel->invexlist;
+			break;
+		default:
+			abort();
+	}
+	
+	for (ban = *banlist; ban; ban = bnext)
+	{
+		bnext = ban->next;
+		if (chmode != 'I' && (*ban->banstr == '~') && (extban = findmod_by_bantype(ban->banstr, NULL)))
+		{
+			if (!(extban->is_banned_events & BANCHK_JOIN))
+				continue;
+		}
+		add_send_mode_param(channel, client, '-',  chmode, ban->banstr);
+		del_listmode(banlist, channel, ban->banstr);
+	}
+}
+
+/** Special Channel MODE command for services, used by SVSMODE and SVS2MODE.
+ * @note
+ * This SVSMODE/SVS2MODE for channels is not simply the regular MODE "but for
+ * services". No, it does different things.
+ *
+ *  Syntax: SVSMODE <channel> <mode and params>
+ *
+ * There are three variants (do NOT mix them!):
+ * 1) SVSMODE #chan -[b|e|I]
+ *    This will remove all bans/exempts/invex in the channel.
+ * 2) SVSMODE #chan -[b|e|I] nickname
+ *    This will remove all bans/exempts/invex that affect nickname.
+ *    Eg: -b nick may remove bans places on IP, host, cloaked host, vhost,
+ *    basically anything that matches this nickname. Note that the
+ *    user with the specified nickname needs to be online for this to work.
+ * 3) SVSMODE #chan -[v|h|o|a|q]
+ *    This will remove the specified mode(s) from all users in the channel.
+ *    Eg: -o will result in a MODE -o for every channel operator.
+ *
+ * OLD syntax had a 'ts' parameter. No services are known to use this.
+ */
+void channel_svsmode(Client *client, int parc, const char *parv[]) 
+{
+	Channel *channel;
+	Client *target;
+	const char *m;
+	int what = MODE_ADD;
+	int i = 4; // wtf is this
+	Member *member;
+	int channel_flags;
+
+	*parabuf = *modebuf = '\0';
+
+	if ((parc < 3) || BadPtr(parv[2]))
+		return;
+
+	if (!(channel = find_channel(parv[1])))
+		return;
+
+	for (m = parv[2]; *m; m++)
+	{
+		if (*m == '+') 
+		{
+			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)))
+				{
+					i++;
+					break;
+				}
+				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);
+				}
+			}
+		}
+	}
+
+	/* only send message if modes have changed */
+	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->name,  modebuf, parabuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s%s", client->id, channel->name, modebuf, parabuf, IsServer(client)?" 0":"");
+
+		/* Activate this hook just like cmd_mode.c */
+		RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel);
+
+		free_message_tags(mtags);
+
+		*parabuf = 0;
+	}
+}
+
+/** Special User MODE command for Services.
+ * @note
+ * 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] - 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, const char *parv[], int show_change)
+{
+	Umode *um;
+	const char *m;
+	Client *target;
+	int  what;
+	long oldumodes = 0;
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	what = MODE_ADD;
+
+	if (parc < 3)
+		return;
+
+	if (parv[1][0] == '#') 
+	{
+		channel_svsmode(client, parc, parv);
+		return;
+	}
+
+	if (!(target = find_user(parv[1], NULL)))
+		return;
+
+	userhost_save_current(target);
+
+	oldumodes = target->umodes;
+
+	/* parse mode change string(s) */
+	for (m = parv[2]; *m; m++)
+		switch (*m)
+		{
+			case '+':
+				what = MODE_ADD;
+				break;
+			case '-':
+				what = MODE_DEL;
+				break;
+
+			/* we may not get these, but they shouldnt be in default */
+			case ' ':
+			case '\n':
+			case '\r':
+			case '\t':
+				break;
+			case 'i':
+				if ((what == MODE_ADD) && !(target->umodes & UMODE_INVISIBLE))
+					irccounts.invisible++;
+				if ((what == MODE_DEL) && (target->umodes & UMODE_INVISIBLE))
+					irccounts.invisible--;
+				goto setmodex;
+			case 'o':
+				if ((what == MODE_ADD) && !(target->umodes & UMODE_OPER))
+				{
+					if (!IsOper(target) && MyUser(target))
+						list_add(&target->special_node, &oper_list);
+
+					irccounts.operators++;
+				}
+				if ((what == MODE_DEL) && (target->umodes & UMODE_OPER))
+				{
+					if (target->umodes & UMODE_HIDEOPER)
+					{
+						/* clear 'H' too, and opercount stays the same.. */
+						target->umodes &= ~UMODE_HIDEOPER;
+					} else {
+						irccounts.operators--;
+					}
+
+					if (MyUser(target) && !list_empty(&target->special_node))
+						list_del(&target->special_node);
+					
+					/* 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, NULL);
+					remove_oper_privileges(target, 0);
+				}
+				goto setmodex;
+			case 'H':
+				if (what == MODE_ADD && !(target->umodes & UMODE_HIDEOPER))
+				{
+					if (!IsOper(target) && !strchr(parv[2], 'o')) /* (ofcoz this strchr() is flawed) */
+					{
+						/* isn't an oper, and would not become one either.. abort! */
+						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--;
+				}
+				if (what == MODE_DEL && (target->umodes & UMODE_HIDEOPER))
+					irccounts.operators++;
+				goto setmodex;
+			case 'd':
+				if (parv[3])
+				{
+					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 */
+				}
+				else
+				{
+					/* setting deaf */
+					goto setmodex;
+				}
+				break;
+			case 'x':
+				if (what == MODE_DEL)
+				{
+					/* -x */
+					if (target->user->virthost)
+					{
+						/* Removing mode +x and virthost set... recalculate host then (but don't activate it!) */
+						safe_strdup(target->user->virthost, target->user->cloakedhost);
+					}
+				} else
+				{
+					/* +x */
+					if (!target->user->virthost)
+					{
+						/* Hmm... +x but no virthost set, that's bad... use cloakedhost.
+						 * Not sure if this could ever happen, but just in case... -- Syzop
+						 */
+						safe_strdup(target->user->virthost, target->user->cloakedhost);
+					}
+					/* Announce the new host to VHP servers if we're setting the virthost to the cloakedhost.
+					 * In other cases, we can assume that the host has been broadcasted already (after all,
+					 * how else could it have been changed...?).
+					 * NOTES: we're doing a strcasecmp here instead of simply checking if it's a "+x but
+					 * not -t"-case. The reason for this is that the 't' might follow ("+xt" instead of "+tx"),
+					 * in which case we would have needlessly announced it. Ok I didn't test it but that's
+					 * the idea behind it :P. -- Syzop
+					 */
+					if (MyUser(target) && !strcasecmp(target->user->virthost, target->user->cloakedhost))
+						sendto_server(NULL, PROTO_VHP, 0, NULL, ":%s SETHOST :%s", target->id,
+							target->user->virthost);
+				}
+				goto setmodex;
+			case 't':
+				/* We support -t nowadays, which means we remove the vhost and set the cloaked host
+				 * (note that +t is a NOOP, that code is in +x)
+				 */
+				if (what == MODE_DEL)
+				{
+					/* First, check if there's a change at all. Perhaps it's a -t on someone
+					 * with no virthost or virthost being cloakedhost?
+					 * Also, check to make sure user->cloakedhost exists at all.
+					 * This so we won't crash in weird cases like non-conformant servers.
+					 */
+					if (target->user->virthost && *target->user->cloakedhost && strcasecmp(target->user->cloakedhost, GetHost(target)))
+					{
+						/* Make the change effective: */
+						safe_strdup(target->user->virthost, target->user->cloakedhost);
+						/* And broadcast the change to VHP servers */
+						if (MyUser(target))
+							sendto_server(NULL, PROTO_VHP, 0, NULL, ":%s SETHOST :%s", target->id,
+								target->user->virthost);
+					}
+					goto setmodex;
+				}
+				break;
+			case 'z':
+				/* Setting and unsetting user mode 'z' remotely is not supported */
+				break;
+			default:
+				setmodex:
+				for (um = usermodes; um; um = um->next)
+				{
+					if (um->letter == *m)
+					{
+						if (what == MODE_ADD)
+							target->umodes |= um->mode;
+						else
+							target->umodes &= ~um->mode;
+						break;
+					}
+				}
+				break;
+		} /*switch*/
+
+	if (parc > 3)
+		sendto_server(client, 0, 0, recv_mtags, ":%s %s %s %s %s",
+		    client->id, show_change ? "SVS2MODE" : "SVSMODE",
+		    parv[1], parv[2], parv[3]);
+	else
+		sendto_server(client, 0, 0, recv_mtags, ":%s %s %s %s",
+		    client->id, show_change ? "SVS2MODE" : "SVSMODE",
+		    parv[1], parv[2]);
+
+	/* Here we trigger the same hooks that cmd_mode does and, likewise,
+	   only if the old flags (oldumodes) are different than the newly-
+	   set ones */
+	if (oldumodes != target->umodes)
+		RunHook(HOOKTYPE_UMODE_CHANGE, target, oldumodes, target->umodes);
+
+	if (show_change)
+	{
+		char buf[BUFSIZE];
+		build_umode_string(target, oldumodes, ALL_UMODES, buf);
+		if (MyUser(target) && *buf)
+		{
+			MessageTag *mtags = NULL;
+			new_message(client, recv_mtags, &mtags);
+			sendto_one(target, mtags, ":%s MODE %s :%s", client->name, target->name, buf);
+			safe_free_message_tags(mtags);
+		}
+	}
+
+	userhost_changed(target); /* we can safely call this, even if nothing changed */
+
+	VERIFY_OPERCOUNT(target, "svsmodeX");
+}
+
+/*
+ * cmd_svsmode() added by taz
+ * parv[1] - username to change mode for
+ * parv[2] - modes to change
+ * parv[3] - account name (if mode contains 'd')
+ */
+CMD_FUNC(cmd_svsmode)
+{
+	do_svsmode(client, recv_mtags, parc, parv, 0);
+}
+
+/*
+ * cmd_svs2mode() added by Potvin
+ * parv[1] - username to change mode for
+ * parv[2] - modes to change
+ * parv[3] - account name (if mode contains 'd')
+ */
+CMD_FUNC(cmd_svs2mode)
+{
+	do_svsmode(client, recv_mtags, parc, parv, 1);
+}
+
+void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param)
+{
+	static char *modes = NULL, lastwhat;
+	static short count = 0;
+	short send = 0;
+	
+	if (!modes) modes = modebuf;
+	
+	if (!modebuf[0])
+	{
+		modes = modebuf;
+		*modes++ = what;
+		*modes = 0;
+		lastwhat = what;
+		*parabuf = 0;
+		count = 0;
+	}
+	if (lastwhat != what)
+	{
+		*modes++ = what;
+		*modes = 0;
+		lastwhat = what;
+	}
+	if (strlen(parabuf) + strlen(param) + 11 < MODEBUFLEN)
+	{
+		if (*parabuf) 
+			strcat(parabuf, " ");
+		strcat(parabuf, param);
+		*modes++ = mode;
+		*modes = 0;
+		count++;
+	}
+	else if (*parabuf) 
+		send = 1;
+
+	if (count == MAXMODEPARAMS)
+		send = 1;
+
+	if (send)
+	{
+		MessageTag *mtags = NULL;
+		/* NOTE: cannot use 'recv_mtag' here because MODE could be rewrapped. Not ideal :( */
+		new_message(from, NULL, &mtags);
+		sendto_channel(channel, from, from, 0, 0, SEND_LOCAL, mtags,
+		               ":%s MODE %s %s %s",
+		               from->name, channel->name, modebuf, parabuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s%s", from->id, channel->name, modebuf, parabuf, IsServer(from)?" 0":"");
+		free_message_tags(mtags);
+		send = 0;
+		*parabuf = 0;
+		modes = modebuf;
+		*modes++ = what;
+		lastwhat = what;
+		if (count != MAXMODEPARAMS)
+		{
+			strcpy(parabuf, param);
+			*modes++ = mode;
+			count = 1;
+		}
+		else 
+			count = 0;
+		*modes = 0;
+	}
+}
diff --git a/ircd/src/modules/svsmotd.c b/ircd/src/modules/svsmotd.c
@@ -0,0 +1,112 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svsmotd.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   SVSMOTD command
+ *
+ *   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_svsmotd);
+
+#define MSG_SVSMOTD 	"SVSMOTD"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsmotd",
+	"5.0",
+	"command /svsmotd", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSMOTD, cmd_svsmotd, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_svsmotd
+**
+*/
+CMD_FUNC(cmd_svsmotd)
+{
+	FILE *conf = NULL;
+
+	if (!IsSvsCmdOk(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[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]);
+
+	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;
+	}
+
+	/* We editted it, so rehash it -- codemastr */
+	read_motd(conf_files->svsmotd_file, &svsmotd);
+}
diff --git a/ircd/src/modules/svsnick.c b/ircd/src/modules/svsnick.c
@@ -0,0 +1,122 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svsnick.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   svsnick command
+ *
+ *   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_svsnick);
+
+#define MSG_SVSNICK 	"SVSNICK"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsnick",
+	"5.0",
+	"command /svsnick", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSNICK, cmd_svsnick, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+/*
+** cmd_svsnick
+**      parv[1] = old nickname
+**      parv[2] = new nickname
+**      parv[3] = timestamp
+*/
+CMD_FUNC(cmd_svsnick)
+{
+	Client *acptr;
+	Client *ocptr; /* Other client */
+	MessageTag *mtags = NULL;
+	char nickname[NICKLEN+1];
+	char oldnickname[NICKLEN+1];
+	time_t ts;
+
+	if (!IsSvsCmdOk(client) || parc < 4 || (strlen(parv[2]) > NICKLEN))
+		return; /* This looks like an error anyway -Studded */
+
+	if (hunt_server(client, NULL, "SVSNICK", 1, parc, parv) != HUNTED_ISME)
+		return; /* Forwarded, done */
+
+	strlcpy(nickname, parv[2], sizeof(nickname));
+	if (do_nick_name(nickname) == 0)
+		return;
+
+	if (!(acptr = find_user(parv[1], NULL)))
+		return; /* User not found, bail out */
+
+	if ((ocptr = find_client(nickname, NULL)) && ocptr != acptr) /* Collision */
+	{
+		exit_client(acptr, NULL,
+		                   "Nickname collision due to forced "
+		                   "nickname change, your nick was overruled");
+		return;
+	}
+
+	/* if the new nickname is identical to the old one, ignore it */
+	if (!strcmp(acptr->name, nickname))
+		return;
+
+	strlcpy(oldnickname, acptr->name, sizeof(oldnickname));
+
+	if (acptr != ocptr)
+		acptr->umodes &= ~UMODE_REGNICK;
+	ts = atol(parv[3]);
+
+	/* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */
+	new_message(acptr, NULL, &mtags);
+	mtag_add_issued_by(&mtags, client, recv_mtags);
+	RunHook(HOOKTYPE_LOCAL_NICKCHANGE, acptr, mtags, nickname);
+	sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, nickname);
+	sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, nickname);
+	sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%lld", acptr->id, nickname, (long long)ts);
+
+	add_history(acptr, 1, WHOWAS_EVENT_NICK_CHANGE);
+	acptr->lastnick = ts; /* needs to be done AFTER add_history() */
+	del_from_client_hash_table(acptr->name, acptr);
+
+	unreal_log(ULOG_INFO, "nick", "FORCED_NICK_CHANGE", acptr,
+	           "$client.details has been forced to change their nickname to $new_nick_name",
+	           log_data_string("new_nick_name", nickname));
+
+	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/ircd/src/modules/svsnline.c b/ircd/src/modules/svsnline.c
@@ -0,0 +1,157 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svsnline.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   SVSNLINE Command
+ *
+ *   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_svsnline);
+
+#define MSG_SVSNLINE 	"SVSNLINE"	/* svsnline */
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsnline",	/* Name of module */
+	"5.0", /* Version */
+	"command /svsnline", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSNLINE, cmd_svsnline, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void wipe_svsnlines(void)
+{
+	ConfigItem_ban *bconf, *next;
+	
+	for (bconf = conf_ban; bconf; bconf = next)
+	{
+		next = bconf->next;
+		if ((bconf->flag.type == CONF_BAN_REALNAME) &&
+			(bconf->flag.type2 == CONF_BAN_TYPE_AKILL))
+		{
+			DelListItem(bconf, conf_ban);
+			safe_free(bconf->mask);
+			safe_free(bconf->reason);
+			safe_free(bconf);
+		}
+	}
+}
+
+/*
+ * cmd_svsnline
+ * SVSNLINE + reason_where_is_space :realname mask with spaces
+ * SVSNLINE - :realname mask
+ * SVSNLINE *     Wipes
+ * -Stskeeps
+*/
+CMD_FUNC(cmd_svsnline)
+{
+	ConfigItem_ban *bconf;
+	char		*s;
+
+	if (parc < 2)
+		return;
+
+	switch (*parv[1])
+	{
+		  /* Allow non-U-Lines to send ONLY SVSNLINE +, but don't send it out
+		   * unless it is from a U-Line -- codemastr */
+	  case '+':
+	  {
+		  if (parc < 4)
+			  return;
+		 
+		  if (!find_banEx(NULL, parv[3], CONF_BAN_REALNAME, CONF_BAN_TYPE_AKILL))
+		  {
+			bconf = safe_alloc(sizeof(ConfigItem_ban));
+			bconf->flag.type = CONF_BAN_REALNAME;
+			safe_strdup(bconf->mask, parv[3]);
+			safe_strdup(bconf->reason, parv[2]);
+			for (s = bconf->reason; *s; s++)
+				if (*s == '_')
+					*s = ' ';
+			bconf->flag.type2 = CONF_BAN_TYPE_AKILL;
+			AddListItem(bconf, conf_ban);
+		  } 
+		 
+		  if (IsSvsCmdOk(client))
+			sendto_server(client, 0, 0, NULL, ":%s SVSNLINE + %s :%s",
+			    client->id, parv[2], parv[3]);
+		  break;
+	  }
+	  case '-':
+	  {
+		  if (!IsSvsCmdOk(client))
+			  return;
+		  if (parc < 3)
+			  return;
+		  
+		  for (bconf = conf_ban; bconf; bconf = bconf->next)
+		  {
+			if (bconf->flag.type != CONF_BAN_REALNAME)
+				continue;
+			if (bconf->flag.type2 != CONF_BAN_TYPE_AKILL)
+				continue;
+			if (!strcasecmp(bconf->mask, parv[2]))
+				break;
+		  }
+		  if (bconf)
+		  {
+		  	DelListItem(bconf, conf_ban);
+	  		safe_free(bconf->mask);
+	  		safe_free(bconf->reason);
+		  	safe_free(bconf);
+		  	
+		  }
+		  sendto_server(client, 0, 0, NULL, ":%s SVSNLINE - %s", client->id, parv[2]);
+		  break;
+	  }
+	  case '*':
+	  {
+		  if (!IsSvsCmdOk(client))
+			  return;
+	          wipe_svsnlines();
+		  sendto_server(client, 0, 0, NULL, ":%s SVSNLINE *", client->id);
+		  break;
+	  }
+
+	}
+}
diff --git a/ircd/src/modules/svsnolag.c b/ircd/src/modules/svsnolag.c
@@ -0,0 +1,105 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svsnolag.c
+ *   (C) 2006 Alex Berezhnyak and djGrrr
+ *
+ *   Fake lag exception - SVSNOLAG and SVS2NOLAG commands
+ *
+ *   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_svsnolag);
+CMD_FUNC(cmd_svs2nolag);
+
+#define MSG_SVSNOLAG 	"SVSNOLAG"	
+#define MSG_SVS2NOLAG 	"SVS2NOLAG"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsnolag",
+	"5.0",
+	"commands /svsnolag and /svs2nolag", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSNOLAG, cmd_svsnolag, MAXPARA, CMD_SERVER);
+	CommandAdd(modinfo->handle, MSG_SVS2NOLAG, cmd_svs2nolag, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void do_svsnolag(Client *client, int parc, const char *parv[], int show_change)
+{
+	Client *target;
+	char *cmd = show_change ? MSG_SVS2NOLAG : MSG_SVSNOLAG;
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if (parc < 3)
+		return;
+
+	if (!(target = find_user(parv[2], NULL)))
+		return;
+
+	if (!MyUser(target))
+	{
+		sendto_one(target, NULL, ":%s %s %s %s", client->name, cmd, parv[1], parv[2]);
+		return;
+	}
+
+	if (*parv[1] == '+')
+	{
+		if (!IsNoFakeLag(target))
+		{
+			SetNoFakeLag(target);
+			if (show_change)
+				sendnotice(target, "You are now exempted from fake lag");
+		}
+	}
+	if (*parv[1] == '-')
+	{
+		if (IsNoFakeLag(target))
+		{
+			ClearNoFakeLag(target);
+			if (show_change)
+				sendnotice(target, "You are no longer exempted from fake lag");
+		}
+	}
+}
+
+CMD_FUNC(cmd_svsnolag)
+{
+	do_svsnolag(client, parc, parv, 0);
+}
+
+CMD_FUNC(cmd_svs2nolag)
+{
+	do_svsnolag(client, parc, parv, 1);
+}
diff --git a/ircd/src/modules/svsnoop.c b/ircd/src/modules/svsnoop.c
@@ -0,0 +1,97 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svsnoop.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   svsnoop command
+ *
+ *   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_svsnoop);
+
+#define MSG_SVSNOOP 	"SVSNOOP"	
+
+
+ModuleHeader MOD_HEADER
+  = {
+	"svsnoop",
+	"5.0",
+	"command /svsnoop", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSNOOP, cmd_svsnoop, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+CMD_FUNC(cmd_svsnoop)
+{
+	Client *acptr;
+
+	if (!(IsSvsCmdOk(client) && parc > 2))
+		return;
+
+	if (hunt_server(client, NULL, "SVSNOOP", 1, parc, parv) == HUNTED_ISME)
+	{
+		if (parv[2][0] == '+')
+		{
+			SVSNOOP = 1;
+			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))
+				{
+					if (IsOper(acptr))
+					{
+						irccounts.operators--;
+						VERIFY_OPERCOUNT(acptr, "svsnoop");
+					}
+
+					if (!list_empty(&acptr->special_node))
+						list_del(&acptr->special_node);
+
+					RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL);
+					remove_oper_privileges(acptr, 1);
+				}
+			}
+		}
+		else
+		{
+			SVSNOOP = 0;
+			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/ircd/src/modules/svso.c b/ircd/src/modules/svso.c
@@ -0,0 +1,141 @@
+/* src/modules/svso.c - Grant IRCOp rights (for Services)
+ * (C) Copyright 2022 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"svso",
+	"6.0.0",
+	"Grant oper privileges via SVSO services command",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+CMD_FUNC(cmd_svso);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	CommandAdd(modinfo->handle, "SVSO", cmd_svso, MAXPARA, CMD_USER|CMD_SERVER);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Syntax: SVSO <uid|nick> <oper account> <operclass> <class> <modes> <snomask> <vhost>
+ * All these parameters need to be set, you cannot leave any of them out,
+ * HOWEVER some can be set to "-" to skip setting them, this is true for:
+ * <class>, <modes>, <snomask>, <vhost>
+ *
+ * In UnrealIRCd the <operclass> will be prefixed by "services:" if not already
+ * present. It is up to you to include or omit it.
+ */
+CMD_FUNC(cmd_svso)
+{
+	Client *acptr;
+	char oper_account[64];
+	const char *operclass;
+	const char *clientclass;
+	ConfigItem_class *clientclass_c;
+	const char *modes;
+	long modes_i = 0;
+	const char *snomask;
+	const char *vhost;
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if ((parc < 8) || BadPtr(parv[7]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSO");
+		return;
+	}
+
+	operclass = parv[3];
+	clientclass = parv[4];
+	modes = parv[5];
+	snomask = parv[6];
+	vhost = parv[7];
+
+	acptr = find_user(parv[1], NULL);
+	if (!acptr)
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
+		return;
+	}
+
+	if (!MyUser(acptr))
+	{
+		/* Forward it to the correct server, and we are done... */
+		sendto_one(acptr, recv_mtags, ":%s SVSO %s %s %s %s %s %s %s",
+		           client->name, acptr->id, parv[2], parv[3], parv[4], parv[5], parv[6], parv[7]);
+		return;
+	}
+
+	/* CAVEAT ALERT !
+	 * Don't mix up 'client' and 'acptr' below...
+	 * 'client' is the server or services pseudouser that requests the change
+	 * 'acptr' is the person that will be made OPER
+	 */
+
+	/* If we get here, we validate the request and then make the user oper. */
+	if (!find_operclass(operclass))
+	{
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Operclass does not exist");
+		return;
+	}
+
+	/* Set any items to NULL if they are skipped (on request) */
+	if (!strcmp(clientclass, "-"))
+		clientclass = NULL;
+	if (!strcmp(modes, "-"))
+		modes = NULL;
+	if (!strcmp(snomask, "-"))
+		snomask = NULL;
+	if (!strcmp(vhost, "-"))
+		vhost = NULL;
+
+	/* First, maybe the user is oper already? Then de-oper them.. */
+	if (IsOper(acptr))
+	{
+		int was_hidden_oper = IsHideOper(acptr) ? 1 : 0;
+
+		list_del(&acptr->special_node);
+		RunHook(HOOKTYPE_LOCAL_OPER, acptr, 0, NULL, NULL);
+		remove_oper_privileges(acptr, 1);
+
+		if (!was_hidden_oper)
+			irccounts.operators--;
+		VERIFY_OPERCOUNT(acptr, "svso");
+
+	}
+
+	if (vhost && !valid_vhost(vhost))
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper: vhost contains illegal characters or is too long");
+
+	/* Prefix the oper block name with "remote:" if it hasn't already */
+	if (!strncmp(parv[2], "remote:", 7))
+		strlcpy(oper_account, parv[2], sizeof(oper_account));
+	else
+		snprintf(oper_account, sizeof(oper_account), "remote:%s", parv[2]);
+
+	/* These needs to be looked up... */
+	clientclass_c = find_class(clientclass); /* NULL is fine! */
+	if (modes)
+		modes_i = set_usermode(modes);
+
+	if (!make_oper(acptr, oper_account, operclass, clientclass_c, modes_i, snomask, vhost))
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper");
+}
diff --git a/ircd/src/modules/svspart.c b/ircd/src/modules/svspart.c
@@ -0,0 +1,90 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/svspart.c
+ *   (C) 2000-2001 Carsten V. Munk 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"
+
+CMD_FUNC(cmd_svspart);
+
+#define MSG_SVSPART       "SVSPART"
+
+ModuleHeader MOD_HEADER
+  = {
+	"svspart",	/* Name of module */
+	"5.0", /* Version */
+	"command /svspart", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSPART, cmd_svspart, 3, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/* cmd_svspart() - Lamego - Wed Jul 21 20:04:48 1999
+   Copied off PTlink IRCd (C) PTlink coders team.
+  Modified for PART by Stskeeps
+	parv[1] - nick to make part
+	parv[2] - channel(s) to part
+	parv[3] - comment
+*/
+CMD_FUNC(cmd_svspart)
+{
+	Client *target;
+	const char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if (parc < 3 || !(target = find_user(parv[1], NULL))) 
+		return;
+
+	if (MyUser(target))
+	{
+		parv[0] = target->name;
+		parv[1] = parv[2];
+		parv[2] = comment;
+		parv[3] = NULL;
+		do_cmd(target, NULL, "PART", comment ? 3 : 2, parv);
+		/* NOTE: target may be killed now by spamfilter due to the part reason */
+	}
+	else
+	{
+		if (comment)
+			sendto_one(target, NULL, ":%s SVSPART %s %s :%s", client->name,
+			    parv[1], parv[2], parv[3]);
+		else
+			sendto_one(target, NULL, ":%s SVSPART %s %s", client->name,
+			    parv[1], parv[2]);
+	}
+}
diff --git a/ircd/src/modules/svssilence.c b/ircd/src/modules/svssilence.c
@@ -0,0 +1,98 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/svssilence.c
+ *   (C) 2003 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"
+
+CMD_FUNC(cmd_svssilence);
+
+ModuleHeader MOD_HEADER
+  = {
+	"svssilence",	/* Name of module */
+	"5.0", /* Version */
+	"command /svssilence", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "SVSSILENCE", cmd_svssilence, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/* cmd_svssilence()
+ * written by Syzop (copied a lot from cmd_silence),
+ * suggested by <??>.
+ * parv[1] - target nick
+ * parv[2] - space delimited silence list (+Blah +Blih -Bluh etc)
+ * SERVER DISTRIBUTION:
+ * Since UnrealIRCd 5 it is directed to the target client (previously: broadcasted).
+ */
+CMD_FUNC(cmd_svssilence)
+{
+	Client *target;
+	int mine;
+	char *p, *cp, c;
+	char request[BUFSIZE];
+	
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
+		return;
+	
+	if (!MyUser(target))
+	{
+		sendto_one(target, NULL, ":%s SVSSILENCE %s :%s", client->name, parv[1], parv[2]);
+		return;
+	}
+
+	/* It's for our client */
+	strlcpy(request, parv[2], sizeof(request));
+	for (p = strtok(request, " "); p; p = strtok(NULL, " "))
+	{
+		c = *p;
+		if ((c == '-') || (c == '+'))
+			p++;
+		else if (!(strchr(p, '@') || strchr(p, '.') || strchr(p, '!') || strchr(p, '*')))
+		{
+			/* "no such nick" */
+			continue;
+		}
+		else
+			c = '+';
+		cp = pretty_mask(p);
+		if ((c == '-' && !del_silence(target, cp)) ||
+		    (c != '-' && !add_silence(target, cp, 0)))
+		{
+			sendto_prefix_one(target, target, NULL, ":%s SILENCE %c%s", client->name, c, cp);
+		}
+	}
+}
diff --git a/ircd/src/modules/svssno.c b/ircd/src/modules/svssno.c
@@ -0,0 +1,111 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svssno.c
+ *   (C) 2004 Dominick Meglio (codemastr) 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"
+
+CMD_FUNC(cmd_svssno);
+CMD_FUNC(cmd_svs2sno);
+
+#define MSG_SVSSNO 	"SVSSNO"	
+#define MSG_SVS2SNO 	"SVS2SNO"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"svssno",
+	"5.0",
+	"command /svssno", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSSNO, cmd_svssno, MAXPARA, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, MSG_SVS2SNO, cmd_svs2sno, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * do_svssno() 
+ * parv[1] - username to change snomask for
+ * parv[2] - snomasks to change
+ * show_change determines whether to show the change to the user
+ */
+void do_svssno(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], int show_change)
+{
+	const char *p;
+	Client *target;
+	int what = MODE_ADD, i;
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if (parc < 2)
+		return;
+
+	if (parv[1][0] == '#') 
+		return;
+
+	if (!(target = find_user(parv[1], NULL)))
+		return;
+
+	if (hunt_server(client, recv_mtags, show_change ? "SVS2SNO" : "SVSSNO", 1, parc, parv) != HUNTED_ISME)
+		return;
+
+	if (MyUser(target))
+	{
+		if (parc == 2)
+			set_snomask(target, NULL);
+		else
+			set_snomask(target, parv[2]);
+	}
+
+	if (show_change && target->user->snomask)
+	{
+		MessageTag *mtags = NULL;
+		new_message(client, recv_mtags, &mtags);
+		// TODO: sendnumeric has no mtags support :D
+		sendnumeric(target, RPL_SNOMASK, target->user->snomask);
+		safe_free_message_tags(mtags);
+	}
+}
+
+CMD_FUNC(cmd_svssno)
+{
+	do_svssno(client, recv_mtags, parc, parv, 0);
+}
+
+CMD_FUNC(cmd_svs2sno)
+{
+	do_svssno(client, recv_mtags, parc, parv, 1);
+}
diff --git a/ircd/src/modules/svswatch.c b/ircd/src/modules/svswatch.c
@@ -0,0 +1,80 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/svswatch.c
+ *   (C) 2003 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"
+
+CMD_FUNC(cmd_svswatch);
+
+/* Place includes here */
+#define MSG_SVSWATCH       "SVSWATCH"
+
+ModuleHeader MOD_HEADER
+  = {
+	"svswatch",	/* Name of module */
+	"5.0", /* Version */
+	"command /svswatch", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSWATCH, cmd_svswatch, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+	
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/* cmd_svswatch() - written by Syzop, suggested by Griever.
+ * parv[1] - target nick
+ * parv[2] - parameters
+ */
+CMD_FUNC(cmd_svswatch)
+{
+	Client *target;
+
+	if (!IsSvsCmdOk(client))
+		return;
+
+	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
+		return;
+
+	if (MyUser(target))
+	{
+		parv[0] = target->name;
+		parv[1] = parv[2];
+		parv[2] = NULL;
+		do_cmd(target, NULL, "WATCH", 2, parv);
+	}
+	else
+		sendto_one(target, NULL, ":%s SVSWATCH %s :%s", client->name, parv[1], parv[2]);
+}
diff --git a/ircd/src/modules/swhois.c b/ircd/src/modules/swhois.c
@@ -0,0 +1,110 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/swhois.c
+ *   (C) 2001 The UnrealIRCd Team
+ *
+ *   SWHOIS command
+ *
+ *   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_swhois);
+
+#define MSG_SWHOIS 	"SWHOIS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"swhois",
+	"5.0",
+	"command /swhois", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SWHOIS, cmd_swhois, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+/** SWHOIS: add or delete additional whois titles to user.
+ * Old syntax:
+ * parv[1] = nickname
+ * parv[2] = new swhois
+ * New syntax (since July 2015, by Syzop):
+ * parv[1] = nickname
+ * parv[2] = + or -
+ * parv[3] = added-by tag
+ * parv[4] = priority
+ * parv[5] = swhois
+ */
+CMD_FUNC(cmd_swhois)
+{
+	Client *target;
+	char tag[HOSTLEN+1];
+	char swhois[SWHOISLEN+1];
+	int add;
+	int priority = 0;
+
+	*tag = *swhois = '\0';
+
+	if (parc < 3)
+		return;
+
+	target = find_user(parv[1], NULL);
+	if (!target)
+		return;
+
+	if ((parc > 5) && !BadPtr(parv[5]))
+	{
+		/* New syntax */
+		add = (*parv[2] == '+') ? 1 : 0;
+		strlcpy(tag, parv[3], sizeof(tag));
+		priority = atoi(parv[4]);
+		strlcpy(swhois, parv[5], sizeof(swhois));
+	} else {
+		/* Old syntax */
+		strlcpy(tag, client->name, sizeof(tag));
+		if (BadPtr(parv[2]))
+		{
+			/* Delete. Hmmmm. Let's just delete anything with that tag. */
+			strcpy(swhois, "*");
+			add = 0;
+		} else {
+			/* Add */
+			add = 1;
+			strlcpy(swhois, parv[2], sizeof(swhois));
+		}
+	}
+
+	if (add)
+		swhois_add(target, tag, priority, swhois, client, client);
+	else
+		swhois_delete(target, tag, swhois, client, client);
+}
diff --git a/ircd/src/modules/targetfloodprot.c b/ircd/src/modules/targetfloodprot.c
@@ -0,0 +1,323 @@
+/* Target flood protection
+ * (C)Copyright 2020 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+   
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"targetfloodprot",
+	"5.0",
+	"Target flood protection (set::anti-flood::target-flood)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#define TFP_PRIVMSG	0
+#define TFP_NOTICE	1
+#define TFP_TAGMSG	2
+#define TFP_MAX		3
+
+typedef struct TargetFlood TargetFlood;
+struct TargetFlood {
+	unsigned short cnt[TFP_MAX];
+	time_t t[TFP_MAX];
+};
+
+typedef struct TargetFloodConfig TargetFloodConfig;
+struct TargetFloodConfig {
+	int cnt[TFP_MAX];
+	int t[TFP_MAX];
+};
+
+/* Forward declarations */
+int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+void targetfloodprot_mdata_free(ModData *m);
+int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, 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;
+ModDataInfo *targetfloodprot_channel_md = NULL;
+TargetFloodConfig *channelcfg = NULL;
+TargetFloodConfig *privatecfg = NULL;
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, targetfloodprot_config_test);
+	return MOD_SUCCESS;
+}
+
+/** Allocate config and set default configuration */
+void targetfloodprot_defaults(void)
+{
+	channelcfg = safe_alloc(sizeof(TargetFloodConfig));
+	privatecfg = safe_alloc(sizeof(TargetFloodConfig));
+
+	/* set::anti-flood::target-flood::channel-privmsg */
+	channelcfg->cnt[TFP_PRIVMSG] = 45;
+	channelcfg->t[TFP_PRIVMSG] = 5;
+	/* set::anti-flood::target-flood::channel-notice */
+	channelcfg->cnt[TFP_NOTICE] = 15;
+	channelcfg->t[TFP_NOTICE] = 5;
+	/* set::anti-flood::target-flood::channel-tagmsg */
+	channelcfg->cnt[TFP_TAGMSG] = 15;
+	channelcfg->t[TFP_TAGMSG] = 5;
+
+	/* set::anti-flood::target-flood::private-privmsg */
+	privatecfg->cnt[TFP_PRIVMSG] = 30;
+	privatecfg->t[TFP_PRIVMSG] = 5;
+	/* set::anti-flood::target-flood::private-notice */
+	privatecfg->cnt[TFP_NOTICE] = 10;
+	privatecfg->t[TFP_NOTICE] = 5;
+	/* set::anti-flood::target-flood::private-tagmsg */
+	privatecfg->cnt[TFP_TAGMSG] = 10;
+	privatecfg->t[TFP_TAGMSG] = 5;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, targetfloodprot_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, targetfloodprot_can_send_to_channel);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, targetfloodprot_can_send_to_user);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "targetfloodprot";
+	mreq.serialize = NULL;
+	mreq.unserialize = NULL;
+	mreq.free = targetfloodprot_mdata_free;
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	targetfloodprot_client_md = ModDataAdd(modinfo->handle, mreq);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "targetfloodprot";
+	mreq.serialize = NULL;
+	mreq.unserialize = NULL;
+	mreq.free = targetfloodprot_mdata_free;
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_CHANNEL;
+	targetfloodprot_channel_md = ModDataAdd(modinfo->handle, mreq);
+
+	targetfloodprot_defaults();
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	safe_free(channelcfg);
+	safe_free(privatecfg);
+	return MOD_SUCCESS;
+}
+
+int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET_ANTI_FLOOD)
+		return 0;
+
+	/* We are only interrested in set::anti-flood::target-flood.. */
+	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		CheckNull(cep);
+
+		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->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->file->filename, cep->line_number,
+				             cep->name);
+				errors++;
+			}
+		} else
+		{
+			config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep, *cepp;
+
+	if (type != CONFIG_SET_ANTI_FLOOD)
+		return 0;
+
+	/* We are only interrested in set::anti-flood::target-flood.. */
+	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		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;
+}
+
+/** UnrealIRCd internals: free object. */
+void targetfloodprot_mdata_free(ModData *m)
+{
+	/* we don't have any members to free, so this is easy */
+	safe_free(m->ptr);
+}
+
+int sendtypetowhat(SendType sendtype)
+{
+	if (sendtype == SEND_TYPE_PRIVMSG)
+		return 0;
+	if (sendtype == SEND_TYPE_NOTICE)
+		return 1;
+	if (sendtype == SEND_TYPE_TAGMSG)
+		return 2;
+#ifdef DEBUGMODE
+	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, const char **msg, const char **errmsg, SendType sendtype)
+{
+	TargetFlood *flood;
+	static char errbuf[256];
+	int what;
+
+	/* This is redundant, right? */
+	if (!MyUser(client))
+		return HOOK_CONTINUE;
+
+	/* U-Lines, servers and IRCOps override */
+	if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
+		return HOOK_CONTINUE;
+
+	what = sendtypetowhat(sendtype);
+
+	if (moddata_channel(channel, targetfloodprot_channel_md).ptr == NULL)
+	{
+		/* Alloc a new entry if it doesn't exist yet */
+		moddata_channel(channel, targetfloodprot_channel_md).ptr = safe_alloc(sizeof(TargetFlood));
+	}
+
+	flood = (TargetFlood *)moddata_channel(channel, targetfloodprot_channel_md).ptr;
+
+	if ((TStime() - flood->t[what]) >= channelcfg->t[what])
+	{
+		/* Reset due to moving into a new time slot */
+		flood->t[what] = TStime();
+		flood->cnt[what] = 1;
+		return HOOK_CONTINUE; /* forget about it.. */
+	}
+
+	if (flood->cnt[what] >= channelcfg->cnt[what])
+	{
+		/* Flood detected */
+		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
+			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $channel",
+			   log_data_string("flood_type", "target-flood-channel"),
+			   log_data_channel("channel", channel));
+		snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
+		*errmsg = errbuf;
+		return HOOK_DENY;
+	}
+
+	flood->cnt[what]++;
+	return HOOK_CONTINUE;
+}
+
+int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
+{
+	TargetFlood *flood;
+	static char errbuf[256];
+	int what;
+
+	/* Check if it is our TARGET ('target'), so yeah
+	 * be aware that 'client' may be remote client in all the code that follows!
+	 */
+	if (!MyUser(target))
+		return HOOK_CONTINUE;
+
+	/* U-Lines, servers and IRCOps override */
+	if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
+		return HOOK_CONTINUE;
+
+	what = sendtypetowhat(sendtype);
+
+	if (moddata_local_client(target, targetfloodprot_client_md).ptr == NULL)
+	{
+		/* Alloc a new entry if it doesn't exist yet */
+		moddata_local_client(target, targetfloodprot_client_md).ptr = safe_alloc(sizeof(TargetFlood));
+	}
+
+	flood = (TargetFlood *)moddata_local_client(target, targetfloodprot_client_md).ptr;
+
+	if ((TStime() - flood->t[what]) >= privatecfg->t[what])
+	{
+		/* Reset due to moving into a new time slot */
+		flood->t[what] = TStime();
+		flood->cnt[what] = 1;
+		return HOOK_CONTINUE; /* forget about it.. */
+	}
+
+	if (flood->cnt[what] >= privatecfg->cnt[what])
+	{
+		/* Flood detected */
+		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
+			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $target",
+			   log_data_string("flood_type", "target-flood-user"),
+			   log_data_client("target", target));
+		snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
+		*errmsg = errbuf;
+		return HOOK_DENY;
+	}
+
+	flood->cnt[what]++;
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/third/Makefile.in b/ircd/src/modules/third/Makefile.in
@@ -0,0 +1,50 @@
+#************************************************************************
+#*   IRC - Internet Relay Chat, src/modules/chanmodes/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/dns.h \
+	../../include/resource.h ../../include/setup.h \
+	../../include/struct.h ../../include/sys.h \
+	../../include/types.h \
+	../../include/version.h ../../include/whowas.h
+
+MODULEFLAGS=@MODULEFLAGS@
+RM=@RM@
+
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
+all: build
+
+build:
+	../../buildmod $(MAKE)
+
+custommodule: $(MODULEFILE).c
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o $(MODULEFILE).so $(MODULEFILE).c $(EXLIBS)
+
+clean:
+	$(RM) -f *.o *.so *~ core
diff --git a/ircd/src/modules/time.c b/ircd/src/modules/time.c
@@ -0,0 +1,66 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/time.c
+ *   (C) 2004 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"
+
+CMD_FUNC(cmd_time);
+
+/* Place includes here */
+#define MSG_TIME	"TIME"
+
+ModuleHeader MOD_HEADER
+  = {
+	"time",	/* Name of module */
+	"5.0", /* Version */
+	"command /time", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_TIME, cmd_time, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_time
+**	parv[1] = servername
+*/
+CMD_FUNC(cmd_time)
+{
+	if (hunt_server(client, recv_mtags, "TIME", 1, parc, parv) == HUNTED_ISME)
+		sendnumeric(client, RPL_TIME, me.name, long_date(0));
+}
diff --git a/ircd/src/modules/tkl.c b/ircd/src/modules/tkl.c
@@ -0,0 +1,5437 @@
+/*
+ * Unreal Internet Relay Chat Daemon, src/modules/tkl.c
+ * TKL Commands: server bans, spamfilters, etc.
+ * (C) 1999-2019 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
+= {
+	"tkl",
+	"5.0",
+	"Server ban commands such as /GLINE, /SPAMFILTER, etc.",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+int tkl_config_test_spamfilter(ConfigFile *, ConfigEntry *, int, int *);
+int tkl_config_run_spamfilter(ConfigFile *, ConfigEntry *, int);
+int tkl_config_test_ban(ConfigFile *, ConfigEntry *, int, int *);
+int tkl_config_run_ban(ConfigFile *, ConfigEntry *, int);
+int tkl_config_test_except(ConfigFile *, ConfigEntry *, int, int *);
+int tkl_config_run_except(ConfigFile *, ConfigEntry *, int);
+int tkl_config_test_set(ConfigFile *, ConfigEntry *, int, int *);
+int tkl_config_run_set(ConfigFile *, ConfigEntry *, int);
+int tkl_ip_change(Client *client, const char *oldip);
+int tkl_accept(Client *client);
+void check_set_spamfilter_utf8_setting_changed(void);
+CMD_FUNC(cmd_gline);
+CMD_FUNC(cmd_shun);
+CMD_FUNC(cmd_tempshun);
+CMD_FUNC(cmd_gzline);
+CMD_FUNC(cmd_kline);
+CMD_FUNC(cmd_zline);
+CMD_FUNC(cmd_spamfilter);
+CMD_FUNC(cmd_eline);
+void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type);
+int _tkl_hash(unsigned int c);
+char _tkl_typetochar(int type);
+int _tkl_chartotype(char c);
+char _tkl_configtypetochar(const char *name);
+int tkl_banexception_chartotype(char c);
+char *_tkl_type_string(TKL *tk);
+char *_tkl_type_config_string(TKL *tk);
+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);
+TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match,
+                           char *reason, char *set_by,
+                           time_t expire_at, time_t set_at, int soft, char *bantypes, int flags);
+TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
+                          time_t expire_at, time_t set_at, int flags);
+TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by,
+                             time_t expire_at, time_t set_at,
+                             time_t spamf_tkl_duration, char *spamf_tkl_reason,
+                             int flags);
+void _sendnotice_tkl_del(char *removed_by, TKL *tkl);
+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);
+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, 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, 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(const char *rmask, Client *client, int options);
+int _unreal_match_iplist(Client *client, NameList *l);
+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);
+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, BanAction action, unsigned short target);
+int _find_tkl_exception(int ban_type, Client *client);
+int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+static void add_default_exempts(void);
+int parse_extended_server_ban(const char *mask_in, Client *client, char **error, int skip_checking, char *buf1, size_t buf1len, char *buf2, size_t buf2len);
+void _tkl_added(Client *client, TKL *tkl);
+
+/* Externals (only for us :D) */
+extern int MODVAR spamf_ugly_vchanoverride;
+
+typedef struct TKLTypeTable TKLTypeTable;
+struct TKLTypeTable
+{
+	char *config_name;        /**< The name as used in the configuration file */
+	char letter;              /**< The letter ised in the TKL S2S command */
+	int type;                 /**< TKL_xxx, optionally OR'ed with TKL_GLOBAL */
+	char *log_name;           /**< Used for logging and server notices */
+	unsigned tkltype:1;       /**< Is a type available in cmd_tkl() and friends */
+	unsigned exceptiontype:1; /**< Is a type available for exceptions */
+	unsigned needip:1;        /**< When using this exempt option, only IP addresses are permitted (processed before DNS/ident lookups etc) */
+};
+
+/** This table which defines all TKL types and TKL exception types.
+ * If you wonder about the messy order: gline/kline/gzline/zline
+ * are at the top for performance reasons. They make up 99% of the TKLs.
+ *
+ * IMPORTANT IF YOU ARE ADDING A NEW TYPE TO THIS TABLE:
+ * - also update eline_syntax()
+ * - update help.conf (HELPOP ELINE)
+ * - more?
+ */
+TKLTypeTable tkl_types[] = {
+	/* <config name> <letter> <TKL_xxx type>               <logging name> <tkl option?> <exempt option?> <need ip address?> */
+	{ "gline",                'G', TKL_KILL       | TKL_GLOBAL, "G-Line",               1, 1, 0 },
+	{ "kline",                'k', TKL_KILL,                    "K-Line",               1, 1, 0 },
+	{ "gzline",               'Z', TKL_ZAP        | TKL_GLOBAL, "Global Z-Line",        1, 1, 1 },
+	{ "zline",                'z', TKL_ZAP,                     "Z-Line",               1, 1, 1 },
+	{ "spamfilter",           'F', TKL_SPAMF      | TKL_GLOBAL, "Spamfilter",           1, 1, 0 },
+	{ "qline",                'Q', TKL_NAME       | TKL_GLOBAL, "Q-Line",               1, 1, 0 },
+	{ "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-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 },
+	{ "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD,    "Handshake data flood", 0, 1, 1 },
+	{ "antirandom",           'r', TKL_ANTIRANDOM,              "Antirandom",           0, 1, 0 },
+	{ "antimixedutf8",        '8', TKL_ANTIMIXEDUTF8,           "Antimixedutf8",        0, 1, 0 },
+	{ "ban-version",          'v', TKL_BAN_VERSION,             "Ban Version",          0, 1, 0 },
+	{ NULL,                   '\0', 0,                          NULL,                   0, 0, 0 },
+};
+#define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version"
+
+/* Global variables for this module */
+
+int max_stats_matches = 1000;
+int mtag_spamfilters_present = 0; /**< Are any spamfilters with type SPAMF_MTAG present? */
+long previous_spamfilter_utf8 = 0;
+static int firstboot = 0;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_spamfilter);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_ban);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_except);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_set);
+	EfunctionAdd(modinfo->handle, EFUNC_TKL_HASH, _tkl_hash);
+#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));
+	EfunctionAdd(modinfo->handle, EFUNC_TKL_CONFIGTYPETOCHAR, TO_INTFUNC(_tkl_configtypetochar));
+#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));
+	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SPAMFILTER, TO_PVOIDFUNC(_tkl_add_spamfilter));
+	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_DEL_LINE, _tkl_del_line);
+	EfunctionAddVoid(modinfo->handle, EFUNC_FREE_TKL, _free_tkl);
+	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, _tkl_check_local_remove_shun);
+	EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
+	EfunctionAdd(modinfo->handle, EFUNC_FIND_SHUN, _find_shun);
+	EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
+	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
+	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKLINE_MATCH_ZAP, TO_PVOIDFUNC(_find_tkline_match_zap));
+	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SERVERBAN, TO_PVOIDFUNC(_find_tkl_serverban));
+	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_BANEXCEPTION, TO_PVOIDFUNC(_find_tkl_banexception));
+	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_NAMEBAN, TO_PVOIDFUNC(_find_tkl_nameban));
+	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SPAMFILTER, TO_PVOIDFUNC(_find_tkl_spamfilter));
+	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_STATS, _tkl_stats);
+	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_SYNCH, _tkl_sync);
+	EfunctionAddVoid(modinfo->handle, EFUNC_CMD_TKL, _cmd_tkl);
+	EfunctionAdd(modinfo->handle, EFUNC_PLACE_HOST_BAN, _place_host_ban);
+	EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER, _match_spamfilter);
+	EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER_MTAGS, _match_spamfilter_mtags);
+	EfunctionAdd(modinfo->handle, EFUNC_JOIN_VIRUSCHAN, _join_viruschan);
+	EfunctionAddVoid(modinfo->handle, EFUNC_SPAMFILTER_BUILD_USER_STRING, _spamfilter_build_user_string);
+	EfunctionAdd(modinfo->handle, EFUNC_MATCH_USER, _match_user);
+	EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH, _tkl_ip_hash);
+	EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH_TYPE, _tkl_ip_hash_type);
+	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);
+	EfunctionAdd(modinfo->handle, EFUNC_UNREAL_MATCH_IPLIST, _unreal_match_iplist);
+	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_PARSE_MASK, TO_INTFUNC(_server_ban_parse_mask));
+	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_EXCEPTION_PARSE_MASK, TO_INTFUNC(_server_ban_exception_parse_mask));
+	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_ADDED, _tkl_added);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (loop.booted == 0)
+		firstboot = 1;
+	LoadPersistentLong(modinfo, previous_spamfilter_utf8);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_spamfilter);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_set);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 2000000000, tkl_ip_change);
+	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -1000, tkl_accept);
+	CommandAdd(modinfo->handle, "GLINE", cmd_gline, 3, CMD_OPER);
+	CommandAdd(modinfo->handle, "SHUN", cmd_shun, 3, CMD_OPER);
+	CommandAdd(modinfo->handle, "TEMPSHUN", cmd_tempshun, 2, CMD_OPER);
+	CommandAdd(modinfo->handle, "ZLINE", cmd_zline, 3, CMD_OPER);
+	CommandAdd(modinfo->handle, "KLINE", cmd_kline, 3, CMD_OPER);
+	CommandAdd(modinfo->handle, "GZLINE", cmd_gzline, 3, CMD_OPER);
+	CommandAdd(modinfo->handle, "SPAMFILTER", cmd_spamfilter, 7, CMD_OPER);
+	CommandAdd(modinfo->handle, "ELINE", cmd_eline, 4, CMD_OPER);
+	CommandAdd(modinfo->handle, "TKL", _cmd_tkl, MAXPARA, CMD_OPER|CMD_SERVER);
+	add_default_exempts();
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	check_mtag_spamfilters_present();
+	check_set_spamfilter_utf8_setting_changed();
+	EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	SavePersistentLong(modinfo, previous_spamfilter_utf8);
+	return MOD_SUCCESS;
+}
+
+/** Test a spamfilter { } block in the configuration file */
+int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep, *cepp;
+	int errors = 0;
+	char *match = NULL, *reason = NULL;
+	char has_target = 0, has_match = 0, has_action = 0, has_reason = 0, has_bantime = 0, has_match_type = 0;
+	int match_type = 0;
+
+	/* We are only interested in spamfilter { } blocks */
+	if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "target"))
+		{
+			if (has_target)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::target");
+				continue;
+			}
+			has_target = 1;
+			if (cep->value)
+			{
+				if (!spamfilter_getconftargets(cep->value))
+				{
+					config_error("%s:%i: unknown spamfiler target type '%s'",
+						cep->file->filename, cep->line_number, cep->value);
+					errors++;
+				}
+			}
+			else if (cep->items)
+			{
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+				{
+					if (!spamfilter_getconftargets(cepp->name))
+					{
+						config_error("%s:%i: unknown spamfiler target type '%s'",
+							cepp->file->filename,
+							cepp->line_number, cepp->name);
+						errors++;
+					}
+				}
+			}
+			else
+			{
+				config_error_empty(cep->file->filename,
+					cep->line_number, "spamfilter", cep->name);
+				errors++;
+			}
+			continue;
+		}
+		if (!cep->value)
+		{
+			config_error_empty(cep->file->filename, cep->line_number,
+				"spamfilter", cep->name);
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "reason"))
+		{
+			if (has_reason)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::reason");
+				continue;
+			}
+			has_reason = 1;
+			reason = cep->value;
+		}
+		else if (!strcmp(cep->name, "match"))
+		{
+			if (has_match)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::match");
+				continue;
+			}
+			has_match = 1;
+			match = cep->value;
+		}
+		else if (!strcmp(cep->name, "action"))
+		{
+			if (has_action)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::action");
+				continue;
+			}
+			has_action = 1;
+			if (!banact_stringtoval(cep->value))
+			{
+				config_error("%s:%i: spamfilter::action has unknown action type '%s'",
+					cep->file->filename, cep->line_number, cep->value);
+				errors++;
+			}
+		}
+		else if (!strcmp(cep->name, "ban-time"))
+		{
+			if (has_bantime)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::ban-time");
+				continue;
+			}
+			has_bantime = 1;
+		}
+		else if (!strcmp(cep->name, "match-type"))
+		{
+			if (has_match_type)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "spamfilter::match-type");
+				continue;
+			}
+			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->file->filename, ce->line_number);
+				errors++;
+				*errs = errors;
+				return -1; /* return now, otherwise there will be issues */
+			}
+			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->file->filename, cep->line_number,
+				             cep->value);
+				errors++;
+				continue;
+			}
+			has_match_type = 1;
+		}
+		else
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"spamfilter", cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	if (match && match_type)
+	{
+		Match *m;
+		char *err;
+
+		m = unreal_create_match(match_type, match, &err);
+		if (!m)
+		{
+			config_error("%s:%i: spamfilter::match contains an invalid regex: %s",
+				ce->file->filename,
+				ce->line_number,
+				err);
+			errors++;
+		} else
+		{
+			unreal_delete_match(m);
+		}
+	}
+
+	if (!has_match)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"spamfilter::match");
+		errors++;
+	}
+	if (!has_target)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"spamfilter::target");
+		errors++;
+	}
+	if (!has_action)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"spamfilter::action");
+		errors++;
+	}
+	if (match && reason && (strlen(match) + strlen(reason) > 505))
+	{
+		config_error("%s:%i: spamfilter block problem: match + reason field are together over 505 bytes, "
+		             "please choose a shorter regex or reason",
+		             ce->file->filename, ce->line_number);
+		errors++;
+	}
+	if (!has_match_type)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"spamfilter::match-type");
+		errors++;
+	}
+
+	if (match && !strcmp(match, "^LOL! //echo -a \\$\\(\\$decode\\(.+,m\\),[0-9]\\)$"))
+	{
+		config_warn("*** IMPORTANT ***");
+		config_warn("You have old examples in your spamfilter.conf. "
+		             "We suggest you to edit this file and replace the examples.");
+		config_warn("Please read https://www.unrealircd.org/docs/FAQ#old-spamfilter-conf !!!");
+		config_warn("*****************");
+	}
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/** Process a spamfilter { } block in the configuration file */
+int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	ConfigEntry *cepp;
+	char *word = NULL;
+	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->name, "spamfilter"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "match"))
+		{
+			word = cep->value;
+		}
+		else if (!strcmp(cep->name, "target"))
+		{
+			if (cep->value)
+				target = spamfilter_getconftargets(cep->value);
+			else
+			{
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+					target |= spamfilter_getconftargets(cepp->name);
+			}
+		}
+		else if (!strcmp(cep->name, "action"))
+		{
+			action = banact_stringtoval(cep->value);
+		}
+		else if (!strcmp(cep->name, "reason"))
+		{
+			banreason = cep->value;
+		}
+		else if (!strcmp(cep->name, "ban-time"))
+		{
+			bantime = config_checkval(cep->value, CFG_TIME);
+		}
+		else if (!strcmp(cep->name, "match-type"))
+		{
+			match_type = unreal_match_method_strtoval(cep->value);
+		}
+	}
+
+	m = unreal_create_match(match_type, word, NULL);
+	tkl_add_spamfilter(TKL_SPAMF,
+	                    target,
+	                    action,
+	                    m,
+	                    "-config-",
+	                    0,
+	                    TStime(),
+	                    bantime,
+	                    banreason,
+	                    TKL_FLAG_CONFIG);
+	return 1;
+}
+
+/** Test a ban { } block in the configuration file */
+int tkl_config_test_ban(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	char has_mask = 0, has_reason = 0;
+
+	/* We are only interested in ban { } blocks */
+	if (type != CONFIG_BAN)
+		return 0;
+
+	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
+	    strcmp(ce->value, "ip"))
+	{
+		return 0;
+	}
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (config_is_blankorempty(cep, "ban"))
+		{
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "mask"))
+		{
+			if (has_mask)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::mask");
+				continue;
+			}
+			has_mask = 1;
+		}
+		else if (!strcmp(cep->name, "reason"))
+		{
+			if (has_reason)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "ban::reason");
+				continue;
+			}
+			has_reason = 1;
+		}
+		else
+		{
+			config_error("%s:%i: unknown directive ban %s::%s",
+				cep->file->filename, cep->line_number,
+				ce->value,
+				cep->name);
+			errors++;
+		}
+	}
+
+	if (!has_mask)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"ban::mask");
+		errors++;
+	}
+
+	if (!has_reason)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"ban::reason");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/** Process a ban { } block in the configuration file */
+int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
+{
+	ConfigEntry *cep;
+	char *usermask = NULL;
+	char *hostmask = NULL;
+	char *reason = NULL;
+	int tkltype;
+
+	/* We are only interested in ban { } blocks */
+	if (configtype != CONFIG_BAN)
+		return 0;
+
+	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
+	    strcmp(ce->value, "ip"))
+	{
+		return 0;
+	}
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "mask"))
+		{
+			if (is_extended_server_ban(cep->value))
+			{
+				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: Could not add extended server ban '%s': %s",
+						cep->file->filename, cep->line_number, cep->value, err);
+					goto tcrb_end;
+				}
+				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)
+				{
+					*p++ = '\0';
+					safe_strdup(usermask, buf);
+					safe_strdup(hostmask, p);
+				} else {
+					safe_strdup(hostmask, cep->value);
+				}
+			}
+		} else
+		if (!strcmp(cep->name, "reason"))
+		{
+			safe_strdup(reason, cep->value);
+		}
+	}
+
+	if (!usermask)
+		safe_strdup(usermask, "*");
+
+	if (!reason)
+		safe_strdup(reason, "-");
+
+	if (!strcmp(ce->value, "nick"))
+		tkltype = TKL_NAME;
+	else if (!strcmp(ce->value, "user"))
+		tkltype = TKL_KILL;
+	else if (!strcmp(ce->value, "ip"))
+		tkltype = TKL_ZAP;
+	else
+		abort(); /* impossible */
+
+	if (TKLIsNameBanType(tkltype))
+		tkl_add_nameban(tkltype, hostmask, 0, reason, "-config-", 0, TStime(), TKL_FLAG_CONFIG);
+	else if (TKLIsServerBanType(tkltype))
+		tkl_add_serverban(tkltype, usermask, hostmask, reason, "-config-", 0, TStime(), 0, TKL_FLAG_CONFIG);
+
+tcrb_end:
+	safe_free(usermask);
+	safe_free(hostmask);
+	safe_free(reason);
+	return 1;
+}
+
+int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
+{
+	ConfigEntry *cep, *cepp;
+	int errors = 0;
+	char has_mask = 0, has_match = 0;
+
+	/* We are only interested in except { } blocks */
+	if (configtype != CONFIG_EXCEPT)
+		return 0;
+
+	/* These are the types that we handle */
+	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->value, "tkl"))
+	{
+		config_error("%s:%d: except tkl { } has been renamed to except ban { }",
+			ce->file->filename, ce->line_number);
+		config_status("Please rename your block in the configuration file.");
+		*errs = 1;
+		return -1;
+	}
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "mask"))
+		{
+			if (cep->value || cep->items)
+			{
+				has_mask = 1;
+				test_match_block(cf, cep, &errors);
+			}
+		} else
+		if (!strcmp(cep->name, "match"))
+		{
+			if (cep->value || cep->items)
+			{
+				has_match = 1;
+				test_match_block(cf, cep, &errors);
+			}
+		} else
+		if (!strcmp(cep->name, "type"))
+		{
+			if (cep->items)
+			{
+				/* type { x; y; z; }; */
+				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->file->filename, cepp->line_number, cepp->name,
+							ALL_VALID_EXCEPTION_TYPES);
+						errors++;
+					}
+			} else
+			if (cep->value)
+			{
+				/* type x; */
+				if (!tkl_banexception_configname_to_chars(cep->value))
+				{
+					config_error("%s:%d: except ban::type '%s' unknown. Must be one of: %s",
+						cep->file->filename, cep->line_number, cep->value,
+						ALL_VALID_EXCEPTION_TYPES);
+					errors++;
+				}
+			}
+		} else {
+			config_error_unknown(cep->file->filename,
+				cep->line_number, "except", cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	if (!has_mask && !has_match)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"except ban::match");
+		errors++;
+	}
+	if (has_mask && has_match)
+	{
+		config_error("%s:%d: You cannot have both ::mask and ::match. "
+		             "You should only use except::match.",
+		             ce->file->filename, ce->line_number);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
+{
+	ConfigEntry *cep, *cepp;
+	SecurityGroup *match = NULL;
+	char bantypes[64];
+
+	/* We are only interested in except { } blocks */
+	if (configtype != CONFIG_EXCEPT)
+		return 0;
+
+	/* These are the types that we handle */
+	if (strcmp(ce->value, "ban") && strcmp(ce->value, "throttle") &&
+	    strcmp(ce->value, "blacklist") &&
+	    strcmp(ce->value, "spamfilter"))
+	{
+		return 0;
+	}
+
+	*bantypes = '\0';
+
+	/* First configure all the types */
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "type"))
+		{
+			if (cep->items)
+			{
+				/* type { x; y; z; }; */
+				for (cepp = cep->items; cepp; cepp = cepp->next)
+				{
+					char *str = tkl_banexception_configname_to_chars(cepp->name);
+					strlcat(bantypes, str, sizeof(bantypes));
+				}
+			} else
+			if (cep->value)
+			{
+				/* type x; */
+				char *str = tkl_banexception_configname_to_chars(cep->value);
+				strlcat(bantypes, str, sizeof(bantypes));
+			}
+		} else
+		if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
+		{
+			conf_match_block(cf, cep, &match);
+		}
+	}
+
+	if (!*bantypes)
+	{
+		/* Default setting if no 'type' is specified: */
+		if (!strcmp(ce->value, "ban"))
+			strlcpy(bantypes, "kGzZs", sizeof(bantypes));
+		else if (!strcmp(ce->value, "throttle"))
+			strlcpy(bantypes, "c", sizeof(bantypes));
+		else if (!strcmp(ce->value, "blacklist"))
+			strlcpy(bantypes, "b", sizeof(bantypes));
+		else if (!strcmp(ce->value, "spamfilter"))
+			strlcpy(bantypes, "f", sizeof(bantypes));
+		else
+			abort(); /* someone can't code */
+	}
+
+	tkl_add_banexception(TKL_EXCEPTION, "-", "-", match, "Added in configuration file",
+	                     "-config-", 0, TStime(), 0, bantypes, TKL_FLAG_CONFIG);
+
+	return 1;
+}
+
+int tkl_config_test_set(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
+{
+	int errors = 0;
+
+	/* We are only interested in set { } blocks */
+	if (configtype != CONFIG_SET)
+		return 0;
+
+	if (!strcmp(ce->name, "max-stats-matches"))
+	{
+		if (!ce->value)
+		{
+			config_error("%s:%i: set::max-stats-matches: no value specified",
+				ce->file->filename, ce->line_number);
+			errors++;
+		}
+		// allow any other value, including 0 and negative.
+		*errs = errors;
+		return errors ? -1 : 1;
+	}
+	return 0;
+}
+
+int tkl_config_run_set(ConfigFile *cf, ConfigEntry *ce, int configtype)
+{
+	/* We are only interested in set { } blocks */
+	if (configtype != CONFIG_SET)
+		return 0;
+
+	if (!strcmp(ce->name, "max-stats-matches"))
+	{
+		max_stats_matches = atoi(ce->value);
+		return 1;
+	}
+
+	return 0;
+}
+
+/** Recompile all spamfilters due to set::spamfilter::utf8 setting change */
+void recompile_spamfilters(void)
+{
+	TKL *tkl;
+	Match *m;
+	char *err;
+	int converted = 0;
+	int index;
+
+	index = tkl_hash('F');
+	for (tkl = tklines[index]; tkl; tkl = tkl->next)
+	{
+		if (!TKLIsSpamfilter(tkl) || (tkl->ptr.spamfilter->match->type != MATCH_PCRE_REGEX))
+			continue;
+		m = unreal_create_match(MATCH_PCRE_REGEX, tkl->ptr.spamfilter->match->str, &err);
+		if (!m)
+		{
+			unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_COMPILE_ERROR", NULL,
+			           "Spamfilter no longer compiles upon utf8 change, error: $error. "
+			           "Spamfilter '$tkl' ($tkl.reason). "
+			           "Spamfilter not transformed to/from utf8.",
+			           log_data_tkl("tkl", tkl),
+			           log_data_string("error", err ? err : "Unknown"));
+			continue;
+		}
+
+		unreal_delete_match(tkl->ptr.spamfilter->match); /* unset old one */
+		tkl->ptr.spamfilter->match = m; /* set new one */
+		converted++;
+	}
+	unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_UTF8_CONVERTED", NULL,
+	           "Spamfilter: Recompiled $count spamfilters due to set::spamfilter::utf8 change.",
+	           log_data_integer("count", converted));
+}
+
+void check_set_spamfilter_utf8_setting_changed(void)
+{
+	if (firstboot)
+	{
+		/* First boot, not a rehash */
+		previous_spamfilter_utf8 = iConf.spamfilter_utf8;
+		return;
+	}
+
+	if (previous_spamfilter_utf8 != iConf.spamfilter_utf8)
+		recompile_spamfilters();
+
+	previous_spamfilter_utf8 = iConf.spamfilter_utf8;
+}
+
+/** Return unique spamfilter id for TKL */
+char *spamfilter_id(TKL *tk)
+{
+	static char buf[128];
+
+	snprintf(buf, sizeof(buf), "%p", (void *)tk);
+	return buf;
+}
+
+int tkl_ip_change(Client *client, const char *oldip)
+{
+	TKL *tkl;
+	if ((tkl = find_tkline_match_zap(client)))
+		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
+	return 0;
+}
+
+int tkl_accept(Client *client)
+{
+	TKL *tkl;
+	if ((tkl = find_tkline_match_zap(client)))
+	{
+		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
+		return HOOK_DENY;
+	}
+	return 0;
+}
+
+/** GLINE - Global kline.
+** Syntax: /gline [+|-]u@h mask time :reason
+**
+** parv[1] = [+|-]u@h mask
+** parv[2] = for how long
+** parv[3] = reason
+*/
+CMD_FUNC(cmd_gline)
+{
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:gline",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc == 1)
+	{
+		const char *parv[3];
+		parv[0] = NULL;
+		parv[1] = "gline";
+		parv[2] = NULL;
+		do_cmd(client, recv_mtags, "STATS", 2, parv);
+	} else
+	{
+		cmd_tkl_line(client, parc, parv, "G");
+	}
+}
+
+/** GZLINE - Global zline.
+ */
+CMD_FUNC(cmd_gzline)
+{
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:zline:global",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc == 1)
+	{
+		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;
+		do_cmd(client, recv_mtags, "STATS", 2, parv);
+	} else {
+		cmd_tkl_line(client, parc, parv, "Z");
+	}
+}
+
+/** SHUN - Shun a user so it can no longer execute any meaningful commands.
+ */
+CMD_FUNC(cmd_shun)
+{
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:shun",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc == 1)
+	{
+		const char *parv[3];
+		parv[0] = NULL;
+		parv[1] = "shun";
+		parv[2] = NULL;
+		do_cmd(client, recv_mtags, "STATS", 2, parv);
+	} else {
+		cmd_tkl_line(client, parc, parv, "s");
+	}
+}
+
+/** TEMPSHUN - Temporarily shun a user so it can no longer execute
+ *  any meaningful commands - until the user disconnects (session only).
+ */
+CMD_FUNC(cmd_tempshun)
+{
+	Client *target;
+	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)))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "TEMPSHUN");
+		return;
+	}
+	if (parv[1][0] == '+')
+		name = parv[1]+1;
+	else if (parv[1][0] == '-')
+	{
+		name = parv[1]+1;
+		remove = 1;
+	} else
+		name = parv[1];
+
+	target = find_user(name, NULL);
+	if (!target)
+	{
+		sendnumeric(client, ERR_NOSUCHNICK, name);
+		return;
+	}
+	if (!MyUser(target))
+	{
+		sendto_one(target, NULL, ":%s TEMPSHUN %c%s :%s",
+		           client->id, remove ? '-' : '+', target->id, comment);
+	} else {
+		char buf[1024];
+		if (!remove)
+		{
+			if (IsShunned(target))
+			{
+				sendnotice(client, "User '%s' already shunned", target->name);
+			} else if (ValidatePermissionsForPath("immune:server-ban:shun",target,NULL,NULL,NULL))
+			{
+				sendnotice(client, "You cannot tempshun '%s' because (s)he is an oper with 'immune:server-ban:shun' privilege", target->name);
+			} else
+			{
+				SetShunned(target);
+				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))
+			{
+				sendnotice(client, "User '%s' is not shunned", target->name);
+			} else {
+				ClearShunned(target);
+				unreal_log(ULOG_INFO, "tkl", "TKL_DEL_TEMPSHUN", client,
+					   "Temporary shun removed from user $target.details [by: $client]",
+					   log_data_client("target", target));
+			}
+		}
+	}
+}
+
+/** KLINE - Kill line (ban user from local server)
+ */
+CMD_FUNC(cmd_kline)
+{
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:kline:local:add",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc == 1)
+	{
+		const char *parv[3];
+		parv[0] = NULL;
+		parv[1] = "kline";
+		parv[2] = NULL;
+		do_cmd(client, recv_mtags, "STATS", 2, parv);
+		return;
+	}
+
+	if (!ValidatePermissionsForPath("server-ban:kline:remove",client,NULL,NULL,NULL) && *parv[1] == '-')
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	cmd_tkl_line(client, parc, parv, "k");
+}
+
+/** Generate stats for '/GLINE -stats' and such */
+void tkl_general_stats(Client *client)
+{
+	int index, index2;
+	TKL *tkl;
+	int total = 0;
+	int subtotal;
+
+	/* First, hashed entries.. */
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			subtotal = 0;
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+				subtotal++;
+			if (subtotal > 0)
+				sendnotice(client, "Slot %d:%d has %d item(s)", index, index2, subtotal);
+			total += subtotal;
+		}
+	}
+	sendnotice(client, "Hashed TKL items: %d item(s)", total);
+
+	/* Now normal entries.. */
+	subtotal = 0;
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+			subtotal++;
+	}
+	sendnotice(client, "Standard TKL items: %d item(s)", subtotal);
+	total += subtotal;
+	sendnotice(client, "Grand total TKL items: %d item(s)", total);
+}
+
+/** 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 TLS handshake, etc.)
+ */
+CMD_FUNC(cmd_zline)
+{
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:zline:local:add",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc == 1)
+	{
+		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;
+		do_cmd(client, recv_mtags, "STATS", 2, parv);
+		return;
+	}
+
+	if ((parc > 1) && !BadPtr(parv[1]) && !strcasecmp(parv[1], "-stats"))
+	{
+		/* Print some statistics */
+		tkl_general_stats(client);
+		return;
+	}
+
+	cmd_tkl_line(client, parc, parv, "z");
+}
+
+/** Check if a ban is placed with a too broad mask (like '*') */
+int ban_too_broad(char *usermask, char *hostmask)
+{
+	char *p;
+	int cnt = 0;
+
+	/* Scary config setting. Hmmm. */
+	if (ALLOW_INSANE_BANS)
+		return 0;
+
+	/* Allow things like clone@*, dsfsf@*, etc.. */
+	if (!strchr(usermask, '*') && !strchr(usermask, '?'))
+		return 0;
+
+	/* If it's a CIDR, then check /mask first.. */
+	p = strchr(hostmask, '/');
+	if (p)
+	{
+		int cidrlen = atoi(p+1);
+		if (strchr(hostmask, ':'))
+		{
+			if (cidrlen < 48)
+				return 1; /* too broad IPv6 CIDR mask */
+		} else {
+			if (cidrlen < 16)
+				return 1; /* too broad IPv4 CIDR mask */
+		}
+	}
+
+	/* Must at least contain 4 non-wildcard/non-dot characters.
+	 * This will deal with non-CIDR and hosts, but any correct
+	 * CIDR mask will also pass this test (which is fine).
+	 */
+	for (p = hostmask; *p; p++)
+		if (*p != '*' && *p != '.' && *p != '?' && *p != ':')
+			cnt++;
+
+	if (cnt >= 4)
+		return 0;
+
+	return 1;
+}
+
+/** Ugly function, only meant to be called by cmd_tkl_line() */
+static int xline_exists(char *type, char *usermask, char *hostmask)
+{
+	char *umask = usermask;
+	int softban = 0;
+	int tpe = tkl_chartotype(type[0]);
+
+	if (*umask == '%')
+	{
+		umask++;
+		softban = 1;
+	}
+
+	return find_tkl_serverban(tpe, umask, hostmask, softban) ? 1 : 0;
+}
+
+/** 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;
+}
+
+/** Parse a server ban request such as 'blah@blah.com' or '~account:EvilUser'
+ * @param client	Client requesting the operation (can be NULL)
+ * @param add		Set to 1 for add ban, 0 for remove ban
+ * @param type		TKL type (character), see 2nd column of tkl_types[], eg 'G' for gline.
+ * @param str		The input string
+ * @param usermask_out	Will be set to the TKL usermask
+ * @param hostmask_out	Will be set to the TKL hostmask
+ * @param soft		Will be set to 1 if it's a softban, otherwise 0
+ * @param error		On failure, this will contain the error string
+ * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
+ * @retval 0	Failed: error is set appropriately
+ */
+int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
+{
+	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE];
+	char *hostmask = NULL, *usermask = NULL;
+	char *mask, *p;
+
+	/* Set defaults */
+	*usermask_out = *hostmask_out = NULL;
+	*soft = 0;
+
+	strlcpy(maskbuf, str, sizeof(maskbuf));
+	mask = maskbuf;
+
+	if ((*mask != '~') && strchr(mask, '!'))
+	{
+		*error = "Cannot have '!' in masks.";
+		return 0;
+	}
+
+	if (*mask == ':')
+	{
+		*error = "Mask cannot start with a ':'.";
+		return 0;
+	}
+
+	if (strchr(mask, ' '))
+	{
+		*error = "Mask may not contain spaces";
+		return 0;
+	}
+
+	/* Check if it's a softban */
+	if (*mask == '%')
+	{
+		*soft = 1;
+		if (!strchr("kGs", type))
+		{
+			*error = "The %% prefix (soft ban) is only available for KLINE, GLINE and SHUN. "
+			         "For technical reasons this will not work for (G)ZLINE.";
+			return 0;
+		}
+	}
+
+	/* Check if it's an extended server ban */
+	if (is_extended_server_ban(mask))
+	{
+		char *err;
+
+		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
+		{
+			/* If adding, reject it */
+			if (add)
+			{
+				*error = err;
+				return 0;
+			} else
+			{
+				/* 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 */
+			}
+		}
+		if (add && ((type == 'z') || (type == 'Z')))
+		{
+			*error = "(G)Zlines must be placed at *@\037IPMASK\037. "
+			         "Extended server bans don't work here because (g)zlines are processed "
+			         "BEFORE dns and ident lookups are done and before reading any client data. "
+			         "If you want to use extended server bans then use a KLINE/GLINE instead.";
+			return 0;
+		}
+		usermask = mask1buf; /* eg ~S: */
+		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
+	} else
+	{
+		/* Check if it's a hostmask and legal .. */
+		p = strchr(mask, '@');
+		if (p) {
+			if ((p == mask) || !p[1])
+			{
+				*error = "No user@host specified";
+				return 0;
+			}
+			usermask = strtok(mask, "@");
+			hostmask = strtok(NULL, "");
+			if (BadPtr(hostmask))
+			{
+				if (BadPtr(usermask))
+				{
+					*error = "Invalid mask";
+					return 0;
+				}
+				hostmask = usermask;
+				usermask = "*";
+			}
+			if (*hostmask == ':')
+			{
+				*error = "For technical reasons you cannot start the host with a ':', sorry";
+				return 0;
+			}
+			if (add && ((type == 'z') || (type == 'Z')))
+			{
+				/* It's a (G)ZLINE, make sure the user isn't specyfing a HOST.
+				 * Just a warning in 3.2.3, but an error in 3.2.4.
+				 */
+				if (strcmp(usermask, "*"))
+				{
+					*error = "(G)Zlines must be placed at \037*\037@ipmask, not \037user\037@ipmask. This is "
+					         "because (g)zlines are processed BEFORE dns and ident lookups are done. "
+					         "If you want to use usermasks, use a KLINE/GLINE instead.";
+					return 0;
+				}
+				for (p=hostmask; *p; p++)
+				{
+					if (isalpha(*p) && !isxdigit(*p))
+					{
+						*error = "ERROR: (g)zlines must be placed at *@\037IPMASK\037, not *@\037HOSTMASK\037 "
+						         "(so for example *@192.168.* is ok, but *@*.aol.com is not). "
+						         "This is because (g)zlines are processed BEFORE dns and ident lookups are done. "
+						         "If you want to use hostmasks instead of ipmasks, use a KLINE/GLINE instead.";
+						return 0;
+					}
+				}
+			}
+		}
+		else
+		{
+			/* It's seemingly a nick .. let's see if we can find the user */
+			Client *acptr;
+			if ((acptr = find_user(mask, NULL)))
+			{
+				BanAction action = BAN_ACT_KLINE; // just a dummy default
+				if ((type == 'z') || (type == 'Z'))
+					action = BAN_ACT_ZLINE; // to indicate zline (no hostname, no dns, etc)
+				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
+			}
+			else
+			{
+				*error = "Nickname not found";
+				return 0;
+			}
+		}
+	}
+
+	/* Success! */
+	*usermask_out = usermask;
+	*hostmask_out = hostmask;
+	return 1;
+}
+
+/** Intermediate layer between user functions such as KLINE/GLINE
+ * and the TKL layer (cmd_tkl).
+ * This allows us doing some syntax checking and other helpful
+ * things that are the same for many types of *LINES.
+ */
+void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
+{
+	time_t secs;
+	int add = 1, soft;
+	time_t i;
+	Client *acptr = NULL;
+	const char *mask;
+	const char *error;
+	char mo[64], mo2[64];
+	char *p, *usermask, *hostmask;
+	const char *tkllayer[10] = {
+		me.name,		/*0  server.name */
+		NULL,			/*1  +|- */
+		NULL,			/*2  G   */
+		NULL,			/*3  user */
+		NULL,			/*4  host */
+		NULL,			/*5  set_by */
+		"0",			/*6  expire_at */
+		NULL,			/*7  set_at */
+		"no reason",	/*8  reason */
+		NULL
+	};
+	struct tm *t;
+
+	if ((parc == 1) || BadPtr(parv[1]))
+		return; /* shouldn't happen */
+
+	mask = parv[1];
+
+	if (*mask == '-')
+	{
+		add = 0;
+		mask++;
+	}
+	else if (*mask == '+')
+	{
+		add = 1;
+		mask++;
+	}
+
+	if (!server_ban_parse_mask(client, add, *type, mask, &usermask, &hostmask, &soft, &error))
+	{
+		sendnotice(client, "[ERROR] %s", error);
+		return;
+	}
+
+	if (add && ban_too_broad(usermask, hostmask))
+	{
+		sendnotice(client, "*** [error] Too broad mask");
+		return;
+	}
+
+	secs = 0;
+
+	if (add && (parc > 3))
+	{
+		secs = config_checkval(parv[2], CFG_TIME);
+		if (secs < 0)
+		{
+			sendnotice(client, "*** [error] The time you specified is out of range!");
+			return;
+		}
+	}
+	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 (add)
+	{
+		if (secs == 0)
+		{
+			if (DEFAULT_BANTIME && (parc <= 3))
+				ircsnprintf(mo, sizeof(mo), "%lld", (long long)(DEFAULT_BANTIME + TStime()));
+			else
+				ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
+		}
+		else
+			ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
+		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
+		tkllayer[6] = mo;
+		tkllayer[7] = mo2;
+		if (parc > 3) {
+			tkllayer[8] = parv[3];
+		} else if (parc > 2) {
+			tkllayer[8] = parv[2];
+		}
+		/* Blerghhh... */
+		i = atol(mo);
+		t = gmtime(&i);
+		if (!t)
+		{
+			sendnotice(client, "*** [error] The time you specified is out of range");
+			return;
+		}
+
+		/* Some stupid checking */
+		if (xline_exists(type, usermask, hostmask))
+		{
+			sendnotice(client, "ERROR: Ban for %s@%s already exists.", usermask, hostmask);
+			return;
+		}
+
+		/* call the tkl layer .. */
+		cmd_tkl(&me, NULL, 9, tkllayer);
+	}
+	else
+	{
+		/* call the tkl layer .. */
+		cmd_tkl(&me, NULL, 6, tkllayer);
+
+	}
+}
+
+void eline_syntax(Client *client)
+{
+	sendnotice(client, " Syntax: /ELINE <user@host> <bantypes> <expiry-time> <reason>");
+	sendnotice(client, "     Or: /ELINE <extserverban> <bantypes> <expiry-time> <reason>");
+	sendnotice(client, "Valid bantypes are:");
+	sendnotice(client, "k: K-Line     G: G-Line");
+	sendnotice(client, "z: Z-Line     Z: Global Z-Line");
+	sendnotice(client, "Q: Q-Line");
+	sendnotice(client, "s: Shun");
+	sendnotice(client, "F: Spamfilter");
+	sendnotice(client, "b: Blacklist checking");
+	sendnotice(client, "c: Connect flood (bypass set::anti-flood::connect-flood))");
+	sendnotice(client, "d: Handshake data flood (no ZLINE on too much data before registration)");
+	sendnotice(client, "m: Bypass allow::maxperip restriction");
+	sendnotice(client, "r: Bypass antirandom module");
+	sendnotice(client, "8: Bypass antimixedutf8 module");
+	sendnotice(client, "v: Bypass ban version { } blocks");
+	sendnotice(client, "Examples:");
+	sendnotice(client, "/ELINE *@unrealircd.org kGF 0 This user is exempt");
+	sendnotice(client, "/ELINE ~S:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef kGF 0 Trusted user with this certificate fingerprint");
+	sendnotice(client, "-");
+	sendnotice(client, "To get a list of all current ELINEs, type: /STATS except");
+}
+
+/** Check if any of the specified types require the
+ * exception to be placed on *@ip rather than
+ * user@host or *@host. For eg zlines.
+ */
+TKLTypeTable *eline_type_requires_ip(const char *bantypes)
+{
+	int i;
+
+	for (i=0; tkl_types[i].config_name; i++)
+		if (tkl_types[i].needip && strchr(bantypes, tkl_types[i].letter))
+			return &tkl_types[i];
+	return NULL;
+}
+
+/** Checks a string to see if it contains invalid ban exception types */
+int contains_invalid_server_ban_exception_type(const char *str, char *c)
+{
+	const char *p;
+	for (p = str; *p; p++)
+	{
+		if (!tkl_banexception_chartotype(*p))
+		{
+			*c = *p;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/** Parse a server ban exception (ELINE) request such as 'blah@blah.com' or '~account:EvilUser'
+ * @param client	Client requesting the operation (can be NULL)
+ * @param add		Set to 1 for add ban, 0 for remove ban
+ * @param bantypes	Ban types to exempt from
+ * @param str		The input string
+ * @param usermask_out	Will be set to the TKL usermask
+ * @param hostmask_out	Will be set to the TKL hostmask
+ * @param soft		Will be set to 1 if it's a softban, otherwise 0
+ * @param error		On failure, this will contain the error string
+ * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
+ * @retval 0	Failed: error is set appropriately
+ */
+int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
+{
+	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE], errbuf[BUFSIZE];
+	char *hostmask = NULL, *usermask = NULL;
+	char *mask, *p;
+	TKLTypeTable *t;
+
+	/* Set defaults */
+	*usermask_out = *hostmask_out = NULL;
+	*soft = 0;
+
+	strlcpy(maskbuf, str, sizeof(maskbuf));
+	mask = maskbuf;
+
+	if ((*mask != '~') && strchr(mask, '!'))
+	{
+		*error = "Cannot have '!' in masks.";
+		return 0;
+	}
+
+	if (*mask == ':')
+	{
+		*error = "Mask cannot start with a ':'.";
+		return 0;
+	}
+
+	if (strchr(mask, ' '))
+	{
+		*error = "Mask may not contain spaces";
+		return 0;
+	}
+
+	if (*mask == '%')
+	{
+		*soft = 1;
+		/* do we need more sanity checks here? */
+	}
+
+	/* Check if it's an extended server ban */
+	if (is_extended_server_ban(mask))
+	{
+		char *err;
+
+		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
+		{
+			/* If adding, reject it */
+			if (add)
+			{
+				*error = err;
+				return 0;
+			} else
+			{
+				/* 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 */
+			}
+		}
+		if (add && (t = eline_type_requires_ip(bantypes)))
+		{
+			snprintf(errbuf, sizeof(errbuf),
+			         "ERROR: Ban exception with type '%c' does not work on extended server bans. "
+			         "This is because checking for %s takes places BEFORE "
+			         "extended bans can be checked.", t->letter, t->log_name);
+			*error = errbuf;
+			return 0;
+		}
+		usermask = mask1buf; /* eg ~S: */
+		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
+	} else
+	{
+		/* Check if it's a hostmask and legal .. */
+		p = strchr(mask, '@');
+		if (p) {
+			if ((p == mask) || !p[1])
+			{
+				*error = "No user@host specified";
+				return 0;
+			}
+			usermask = strtok(mask, "@");
+			hostmask = strtok(NULL, "");
+			if (BadPtr(hostmask))
+			{
+				if (BadPtr(usermask))
+				{
+					*error = "Invalid mask";
+					return 0;
+				}
+				hostmask = usermask;
+				usermask = "*";
+			}
+			if (*hostmask == ':')
+			{
+				*error = "For technical reasons you cannot start the host with a ':', sorry";
+				return 0;
+			}
+			if (add && ((t = eline_type_requires_ip(bantypes))))
+			{
+				/* Trying to exempt a user from a (G)ZLINE,
+				 * make sure the user isn't specifying a host then.
+				 */
+				if (strcmp(usermask, "*"))
+				{
+					snprintf(errbuf, sizeof(errbuf),
+					         "Ban exception with type '%c' need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
+					         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
+					         t->letter, t->log_name);
+					*error = errbuf;
+					return 0;
+				}
+				for (p=hostmask; *p; p++)
+				{
+					if (isalpha(*p) && !isxdigit(*p))
+					{
+						snprintf(errbuf, sizeof(errbuf),
+						         "Ban exception with type '%c' needs to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
+						         "(so for example *@192.168.* is OK, but *@*.aol.com is not). "
+						         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
+						         t->letter, t->log_name);
+						*error = errbuf;
+						return 0;
+					}
+				}
+			}
+		}
+		else
+		{
+			/* It's seemingly a nick .. let's see if we can find the user */
+			Client *acptr;
+			if ((acptr = find_user(mask, NULL)))
+			{
+				BanAction action = BAN_ACT_KLINE; // just a dummy default
+				if (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, (const char **)&usermask, (const char **)&hostmask);
+			}
+			else
+			{
+				*error = "Nickname not found";
+				return 0;
+			}
+		}
+	}
+
+	/* Success! */
+	*usermask_out = usermask;
+	*hostmask_out = hostmask;
+	return 1;
+}
+
+CMD_FUNC(cmd_eline)
+{
+	time_t secs = 0;
+	int add = 1;
+	int soft = 0;
+	const char *error = NULL;
+	Client *acptr = NULL;
+	char *mask = NULL;
+	char mo[64], mo2[64];
+	char maskbuf[BUFSIZE];
+	char mask1buf[BUFSIZE];
+	char mask2buf[BUFSIZE];
+	const char *p, *bantypes=NULL, *reason=NULL;
+	char *usermask, *hostmask;
+	const char *tkllayer[11] = {
+		me.name,		/*0  server.name */
+		NULL,			/*1  +|- */
+		NULL,			/*2  E   */
+		NULL,			/*3  user */
+		NULL,			/*4  host */
+		NULL,			/*5  set_by */
+		"0",			/*6  expire_at */
+		"-",			/*7  set_at */
+		"-",			/*8  ban types */
+		"-",			/*9  reason */
+		NULL
+	};
+	TKLTypeTable *t;
+
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:eline",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	/* For del we need at least:
+	 * ELINE -user@host
+	 * The 'add' case is checked later.
+	 */
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		eline_syntax(client);
+		return;
+	}
+
+	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
+	mask = maskbuf;
+	if (*mask == '-')
+	{
+		add = 0;
+		mask++;
+	}
+	else if (*mask == '+')
+	{
+		add = 1;
+		mask++;
+	}
+
+	/* For add we need more:
+	 * ELINE user@host bantypes expiry :reason
+	 */
+	if (add)
+	{
+		if ((parc < 5) || BadPtr(parv[4]))
+		{
+			eline_syntax(client);
+			return;
+		}
+		bantypes = parv[2];
+		reason = parv[4];
+	}
+
+	if (!server_ban_exception_parse_mask(client, add, bantypes, mask, &usermask, &hostmask, &soft, &error))
+	{
+		sendnotice(client, "[ERROR] %s", error);
+		return;
+	}
+
+	if (add)
+	{
+		secs = config_checkval(parv[3], CFG_TIME);
+		if ((secs <= 0) && (*parv[3] != '0'))
+		{
+			sendnotice(client, "*** [error] The expiry time you specified is out of range!");
+			eline_syntax(client);
+			return;
+		}
+	}
+
+	tkllayer[1] = add ? "+" : "-";
+	tkllayer[2] = "E";
+	tkllayer[3] = usermask;
+	tkllayer[4] = hostmask;
+	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
+
+	if (add)
+	{
+		char c;
+		/* Add ELINE */
+		if (secs == 0)
+			ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
+		else
+			ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
+		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
+		tkllayer[6] = mo;
+		tkllayer[7] = mo2;
+		tkllayer[8] = bantypes;
+		if (contains_invalid_server_ban_exception_type(bantypes, &c))
+		{
+			sendnotice(client, "ERROR: bantype '%c' is unrecognized (in '%s'). "
+			                   "Note that the bantypes are case sensitive. "
+			                   "Type /ELINE to see a list of all possible bantypes.",
+			                   c, bantypes);
+			return;
+		}
+		tkllayer[9] = reason;
+		/* call the tkl layer .. */
+		cmd_tkl(&me, NULL, 10, tkllayer);
+	}
+	else
+	{
+		/* Remove ELINE */
+		/* call the tkl layer .. */
+		cmd_tkl(&me, NULL, 10, tkllayer);
+
+	}
+}
+
+
+/** Helper function for cmd_spamfilter, explaining usage. */
+void spamfilter_usage(Client *client)
+{
+	sendnotice(client, "Use: /spamfilter [add|del|remove|+|-] [-simple|-regex] [type] [action] [tkltime] [tklreason] [regex]");
+	sendnotice(client, "See '/helpop ?spamfilter' for more information.");
+	sendnotice(client, "For an easy way to remove an existing spamfilter, use '/spamfilter del' without additional parameters");
+}
+
+/** Helper function for cmd_spamfilter, explaining usage has changed. */
+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)",
+	                 parv[2]);
+
+	if (*parv[2] != '-')
+		sendnotice(client, "Using the old 3.2.x /SPAMFILTER syntax? Note the new -regex/-simple field!!");
+
+	spamfilter_usage(client);
+}
+
+/** Delete a spamfilter by ID (the ID can be obtained via '/SPAMFILTER del' */
+void spamfilter_del_by_id(Client *client, const char *id)
+{
+	int index;
+	TKL *tk;
+	int found = 0;
+	char mo[32], mo2[32];
+	const char *tkllayer[13] = {
+		me.name,	/*  0 server.name */
+		NULL,		/*  1 +|- */
+		"F",		/*  2 F   */
+		NULL,		/*  3 usermask (targets) */
+		NULL,		/*  4 hostmask (action) */
+		NULL,		/*  5 set_by */
+		"0",		/*  6 expire_at */
+		"0",		/*  7 set_at */
+		"",			/*  8 tkl time */
+		"",			/*  9 tkl reason */
+		"",			/* 10 match method */
+		"",			/* 11 regex */
+		NULL
+	};
+
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tk = tklines[index]; tk; tk = tk->next)
+		{
+			if (((tk->type & (TKL_GLOBAL|TKL_SPAMF)) == (TKL_GLOBAL|TKL_SPAMF)) && !strcmp(spamfilter_id(tk), id))
+			{
+				found = 1;
+				break;
+			}
+		}
+		if (found)
+			break; /* break outer loop */
+	}
+
+	if (!tk)
+	{
+		sendnotice(client, "Sorry, no spamfilter found with that ID. Did you run '/spamfilter del' to get the appropriate id?");
+		return;
+	}
+
+	/* Spamfilter found. Now fill the tkllayer */
+	tkllayer[1] = "-";
+	tkllayer[3] = spamfilter_target_inttostring(tk->ptr.spamfilter->target); /* target(s) */
+	mo[0] = banact_valtochar(tk->ptr.spamfilter->action);
+	mo[1] = '\0';
+	tkllayer[4] = mo; /* action */
+	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
+	tkllayer[8] = "-";
+	tkllayer[9] = "-";
+	tkllayer[10] = unreal_match_method_valtostr(tk->ptr.spamfilter->match->type); /* matching type */
+	tkllayer[11] = tk->ptr.spamfilter->match->str; /* regex */
+	ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
+	tkllayer[7] = mo2; /* deletion time */
+
+	cmd_tkl(&me, NULL, 12, tkllayer);
+}
+
+/** Spamfilter to fight spam, advertising, worms and other bad things on IRC.
+ * See https://www.unrealircd.org/docs/Spamfilter for general documentation.
+ *
+ * /SPAMFILTER [add|del|remove|+|-] [match-type] [type] [action] [tkltime] [reason] [regex]
+ *                   1                    2         3       4        5        6        7
+ */
+CMD_FUNC(cmd_spamfilter)
+{
+	int add = 1;
+	char mo[32], mo2[32];
+	const char *tkllayer[13] = {
+		me.name,	/*  0 server.name */
+		NULL,		/*  1 +|- */
+		"F",		/*  2 F   */
+		NULL,		/*  3 usermask (targets) */
+		NULL,		/*  4 hostmask (action) */
+		NULL,		/*  5 set_by */
+		"0",		/*  6 expire_at */
+		"0",		/*  7 set_at */
+		"",			/*  8 tkl time */
+		"",			/*  9 tkl reason */
+		"",			/* 10 match method */
+		"",			/* 11 regex */
+		NULL
+	};
+	int targets = 0, action = 0;
+	char targetbuf[64], actionbuf[2];
+	char reason[512];
+	int n;
+	Match *m;
+	int match_type = 0;
+	char *err = NULL;
+
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:spamfilter",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (parc == 1)
+	{
+		const char *parv[3];
+		parv[0] = NULL;
+		parv[1] = "spamfilter";
+		parv[2] = NULL;
+		do_cmd(client, recv_mtags, "STATS", 2, parv);
+		return;
+	}
+
+	if ((parc <= 3) && !strcmp(parv[1], "del"))
+	{
+		if (!parv[2])
+		{
+			/* Show STATS with appropriate SPAMFILTER del command */
+			const char *parv[5];
+			parv[0] = NULL;
+			parv[1] = "spamfilter";
+			parv[2] = me.name;
+			parv[3] = "del";
+			parv[4] = NULL;
+			do_cmd(client, recv_mtags, "STATS", 4, parv);
+			return;
+		}
+		spamfilter_del_by_id(client, parv[2]);
+		return;
+	}
+
+	if ((parc == 7) && (*parv[2] != '-'))
+	{
+		spamfilter_new_usage(client,parv);
+		return;
+	}
+
+	if ((parc < 8) || BadPtr(parv[7]))
+	{
+		spamfilter_usage(client);
+		return;
+	}
+
+	/* parv[1]: [add|del|+|-]
+	 * parv[2]: match-type
+	 * parv[3]: type
+	 * parv[4]: action
+	 * parv[5]: tkl time
+	 * parv[6]: tkl reason (or block reason..)
+	 * parv[7]: regex
+	 */
+	if (!strcasecmp(parv[1], "add") || !strcmp(parv[1], "+"))
+		add = 1;
+	else if (!strcasecmp(parv[1], "del") || !strcmp(parv[1], "-") || !strcasecmp(parv[1], "remove"))
+		add = 0;
+	else
+	{
+		sendnotice(client, "1st parameter invalid");
+		spamfilter_usage(client);
+		return;
+	}
+
+	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");
+		return;
+	}
+
+	match_type = unreal_match_method_strtoval(parv[2]+1);
+	if (!match_type)
+	{
+		spamfilter_new_usage(client, parv);
+		return;
+	}
+
+	targets = spamfilter_gettargets(parv[3], client);
+	if (!targets)
+	{
+		spamfilter_usage(client);
+		return;
+	}
+
+	strlcpy(targetbuf, spamfilter_target_inttostring(targets), sizeof(targetbuf));
+
+	action = banact_stringtoval(parv[4]);
+	if (!action)
+	{
+		sendnotice(client, "Invalid 'action' field (%s)", parv[4]);
+		spamfilter_usage(client);
+		return;
+	}
+	actionbuf[0] = banact_valtochar(action);
+	actionbuf[1] = '\0';
+
+	if (add)
+	{
+		/* now check the regex / match field... */
+		m = unreal_create_match(match_type, parv[7], &err);
+		if (!m)
+		{
+			sendnotice(client, "Error in regex '%s': %s", parv[7], err);
+			return;
+		}
+		unreal_delete_match(m);
+	}
+
+	tkllayer[1] = add ? "+" : "-";
+	tkllayer[3] = targetbuf;
+	tkllayer[4] = actionbuf;
+	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
+
+	if (parv[5][0] == '-')
+	{
+		ircsnprintf(mo, sizeof(mo), "%lld", (long long)SPAMFILTER_BAN_TIME);
+		tkllayer[8] = mo;
+	}
+	else
+		tkllayer[8] = parv[5];
+
+	if (parv[6][0] == '-')
+		strlcpy(reason, unreal_encodespace(SPAMFILTER_BAN_REASON), sizeof(reason));
+	else
+		strlcpy(reason, parv[6], sizeof(reason));
+
+	tkllayer[9] = reason;
+	tkllayer[10] = parv[2]+1; /* +1 to skip the '-' */
+	tkllayer[11] = parv[7];
+
+	/* SPAMFILTER LENGTH CHECK.
+	 * We try to limit it here so '/stats f' output shows ok, output of that is:
+	 * :servername 229 destname F <target> <action> <num> <num> <num> <reason> <set_by> :<regex>
+	 * : ^NICKLEN       ^ NICKLEN                                       ^check   ^check   ^check
+	 * And for the other fields (and spacing/etc) we count on max 40 characters.
+	 * We also do >500 instead of >510, since that looks cleaner ;).. so actually we count
+	 * on 50 characters for the rest... -- Syzop
+	 */
+	n = strlen(reason) + strlen(parv[7]) + strlen(tkllayer[6]) + (NICKLEN * 2) + 40;
+	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 (add)
+	{
+		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
+		tkllayer[7] = mo2;
+	}
+
+	cmd_tkl(&me, NULL, 12, tkllayer);
+}
+
+/** tkl hash method.
+ * @param c   The tkl type character, see tkl_typetochar().
+ * @note      The input value 'c' is assumed to be in range a-z or A-Z!
+ *            Also, don't blindly change the hashmethod here, some things
+ *            depend on 'z' and 'Z' ending up in the same bucket.
+ */
+int _tkl_hash(unsigned int c)
+{
+#ifdef DEBUGMODE
+	if ((c >= 'a') && (c <= 'z'))
+		return c-'a';
+	else if ((c >= 'A') && (c <= 'Z'))
+		return c-'A';
+	else {
+		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
+	return (isupper(c) ? c-'A' : c-'a');
+#endif
+}
+
+/** tkl type to tkl character.
+ * NOTE: type is assumed to be valid.
+ */
+char _tkl_typetochar(int type)
+{
+	int i;
+	for (i=0; tkl_types[i].config_name; i++)
+		if ((tkl_types[i].type == type) && tkl_types[i].tkltype)
+			return tkl_types[i].letter;
+	unreal_log(ULOG_ERROR, "bug", "TKL_TYPETOCHAR_INVALID", NULL,
+	           "tkl_typetochar(): unknown type $tkl_type!!!",
+	           log_data_integer("tkl_type", type));
+	return 0;
+}
+
+/** tkl character to tkl type
+ * Returns 0 if invalid type.
+ */
+int _tkl_chartotype(char c)
+{
+	int i;
+	for (i=0; tkl_types[i].config_name; i++)
+		if ((tkl_types[i].letter == c) && tkl_types[i].tkltype)
+			return tkl_types[i].type;
+	return 0;
+}
+
+char _tkl_configtypetochar(const char *name)
+{
+	int i;
+	for (i=0; tkl_types[i].config_name; i++)
+		if (!strcmp(tkl_types[i].config_name, name))
+			return tkl_types[i].letter;
+	return 0;
+}
+
+int tkl_banexception_chartotype(char c)
+{
+	int i;
+	for (i=0; tkl_types[i].config_name; i++)
+		if ((tkl_types[i].letter == c) && tkl_types[i].exceptiontype)
+			return tkl_types[i].type;
+	return 0;
+}
+
+char *tkl_banexception_configname_to_chars(char *name)
+{
+	static char buf[128];
+	int i;
+
+	if (!strcasecmp(name, "all"))
+	{
+		/* 'all' means everything except qline: */
+		char *p = buf;
+		for (i=0; tkl_types[i].config_name; i++)
+		{
+			if (tkl_types[i].exceptiontype && !(tkl_types[i].type & TKL_NAME))
+				*p++ = tkl_types[i].letter;
+		}
+		*p = '\0';
+		return buf;
+	}
+
+	for (i=0; tkl_types[i].config_name; i++)
+	{
+		if (!strcasecmp(name, tkl_types[i].config_name) && tkl_types[i].exceptiontype)
+		{
+			buf[0] = tkl_types[i].letter;
+			buf[1] = '\0';
+			return buf;
+		}
+	}
+	return NULL;
+}
+
+/** Show TKL type as a string (used when adding/removing) */
+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));
+
+	for (i=0; tkl_types[i].config_name; i++)
+	{
+		if ((tkl_types[i].type == tkl->type) && tkl_types[i].tkltype)
+		{
+			strlcat(txt, tkl_types[i].log_name, sizeof(txt));
+			return txt;
+		}
+	}
+
+	strlcpy(txt, "Unknown *-Line", sizeof(txt));
+	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;
+	int extype;
+
+	if (!TKLIsBanException(except))
+		abort();
+
+	for (p = except->ptr.banexception->bantypes; *p; p++)
+	{
+		extype = tkl_banexception_chartotype(*p);
+		if ((extype & TKL_SPAMF) || (extype & TKL_SHUN) || (extype & TKL_NAME))
+		{
+			/* For spamfilter, shun and qline we don't care
+			 * whether they are global or not. That would only
+			 * be confusing to the admin.
+			 */
+			extype &= ~TKL_GLOBAL;
+			if (bantype & extype)
+				return 1;
+		} else {
+			/* Rest requires an exact match */
+			if (bantype == extype)
+				return 1;
+		}
+	}
+
+	return 0;
+}
+
+/** Used for finding out which element of the tkl_ip hash table is used (primary element) */
+int _tkl_ip_hash(char *ip)
+{
+	char ipbuf[64], *p;
+
+	for (p = ip; *p; p++)
+	{
+		if ((*p == '?') || (*p == '*') || (*p == '/'))
+			return -1; /* not an entry suitable for the ip hash table */
+	}
+	if (inet_pton(AF_INET, ip, &ipbuf) == 1)
+	{
+		/* IPv4 */
+		unsigned int v = (ipbuf[0] << 24) +
+		                 (ipbuf[1] << 16) +
+		                 (ipbuf[2] << 8)  +
+		                 ipbuf[3];
+		return v % TKLIPHASHLEN2;
+	} else
+	if (inet_pton(AF_INET6, ip, &ipbuf) == 1)
+	{
+		/* IPv6 (only upper 64 bits) */
+		unsigned int v1 = (ipbuf[0] << 24) +
+		                 (ipbuf[1] << 16) +
+		                 (ipbuf[2] << 8)  +
+		                 ipbuf[3];
+		unsigned int v2 = (ipbuf[4] << 24) +
+		                 (ipbuf[5] << 16) +
+		                 (ipbuf[6] << 8)  +
+		                 ipbuf[7];
+		return (v1 ^ v2) % TKLIPHASHLEN2;
+	} else
+	{
+		return -1;
+	}
+}
+
+// TODO: consider efunc
+int tkl_ip_hash_tkl(TKL *tkl)
+{
+	if (TKLIsServerBan(tkl))
+		return tkl_ip_hash(tkl->ptr.serverban->hostmask);
+	if (TKLIsBanException(tkl))
+		return tkl_ip_hash(tkl->ptr.banexception->hostmask);
+	return -1;
+}
+
+/** Used for finding out which tkl_ip hash table needs to be used (secondary element).
+ * NOTE: Returns -1 for types that are never on the TKL ip hash table, such as spamfilter.
+ *       This can be used by the caller as a quick way to find out if the type is supported.
+ */
+int _tkl_ip_hash_type(int type)
+{
+	if ((type == 'Z') || (type == 'z'))
+		return 0;
+	else if (type == 'G')
+		return 1;
+	else if (type == 'k')
+		return 2;
+	else if ((type == 'e') || (type == 'E'))
+		return 3;
+	else
+		return -1;
+}
+
+/* Find the appropriate list 'head' that we need to iterate.
+ * This is simply a helper that is used at 3 places and I hate duplicate code.
+ * NOTE: this function may return NULL.
+ */
+TKL *tkl_find_head(char type, char *hostmask, TKL *def)
+{
+	int index, index2;
+
+	/* First, check ip hash table TKL's... */
+	index = tkl_ip_hash_type(type);
+	if (index >= 0)
+	{
+		index2 = tkl_ip_hash(hostmask);
+		if (index2 >= 0)
+		{
+			/* iterate tklines_ip_hash[index][index2] */
+			return tklines_ip_hash[index][index2];
+		}
+	}
+	/* Fallback to the default */
+	return def;
+}
+
+/** Add a spamfilter entry to the list.
+ * @param type                TKL_SPAMF or TKL_SPAMF|TKL_GLOBAL.
+ * @param target              The spamfilter target (SPAMF_*)
+ * @param action              The spamfilter action (BAN_ACT_*)
+ * @param match               The match (this struct may contain a regex for example)
+ * @param set_by              Who (or what) set the ban
+ * @param expire_at           When will the ban expire (0 for permanent)
+ * @param set_at              When was the ban set
+ * @param spamf_tkl_duration  When will the ban placed by spamfilter expire
+ * @param spamf_tkl_reason    What is the reason for bans placed by spamfilter
+ * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
+ * @returns                   The TKL entry, or NULL in case of a problem,
+ *                            such as a regex failing to compile, memory problem, ..
+ */
+TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by,
+                             time_t expire_at, time_t set_at,
+                             time_t tkl_duration, char *tkl_reason,
+                             int flags)
+{
+	TKL *tkl;
+	int index;
+
+	if (!(type & TKL_SPAMF))
+		abort();
+
+	tkl = safe_alloc(sizeof(TKL));
+	/* First the common fields */
+	tkl->type = type;
+	tkl->flags = flags;
+	tkl->set_at = set_at;
+	safe_strdup(tkl->set_by, set_by);
+	tkl->expire_at = expire_at;
+	/* Then the spamfilter fields */
+	tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
+	tkl->ptr.spamfilter->target = target;
+	tkl->ptr.spamfilter->action = action;
+	tkl->ptr.spamfilter->match = match;
+	safe_strdup(tkl->ptr.spamfilter->tkl_reason, tkl_reason);
+	tkl->ptr.spamfilter->tkl_duration = tkl_duration;
+
+	if (tkl->ptr.spamfilter->target & SPAMF_USER)
+		loop.do_bancheck_spamf_user = 1;
+	if (tkl->ptr.spamfilter->target & SPAMF_AWAY)
+		loop.do_bancheck_spamf_away = 1;
+
+	/* Spamfilters go via the normal TKL list... */
+	index = tkl_hash(tkl_typetochar(type));
+	AddListItem(tkl, tklines[index]);
+
+	if (target & SPAMF_MTAG)
+		mtag_spamfilters_present = 1;
+
+	return tkl;
+}
+
+/** Add a server ban TKL entry.
+ * @param type                The TKL type, one of TKL_*,
+ *                            optionally OR'ed with TKL_GLOBAL.
+ * @param usermask            The user mask
+ * @param hostmask            The host mask
+ * @param reason              The reason for the ban
+ * @param set_by              Who (or what) set the ban
+ * @param expire_at           When will the ban expire (0 for permanent)
+ * @param set_at              When was the ban set
+ * @param soft                Whether it's a soft-ban
+ * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
+ * @returns                   The TKL entry, or NULL in case of a problem,
+ *                            such as a regex failing to compile, memory problem, ..
+ * @note
+ * Be sure not to call this function for spamfilters,
+ * qlines or exempts, which have their own function!
+ */
+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)
+{
+	TKL *tkl;
+	int index, index2;
+
+	if (!TKLIsServerBanType(type))
+		abort();
+
+	tkl = safe_alloc(sizeof(TKL));
+	/* First the common fields */
+	tkl->type = type;
+	tkl->flags = flags;
+	tkl->set_at = set_at;
+	safe_strdup(tkl->set_by, set_by);
+	tkl->expire_at = expire_at;
+	/* Now the server ban fields */
+	tkl->ptr.serverban = safe_alloc(sizeof(ServerBan));
+	safe_strdup(tkl->ptr.serverban->usermask, usermask);
+	safe_strdup(tkl->ptr.serverban->hostmask, hostmask);
+	if (soft)
+		tkl->ptr.serverban->subtype = TKL_SUBTYPE_SOFT;
+	safe_strdup(tkl->ptr.serverban->reason, reason);
+
+	/* For ip hash table TKL's... */
+	index = tkl_ip_hash_type(tkl_typetochar(type));
+	if (index >= 0)
+	{
+		index2 = tkl_ip_hash_tkl(tkl);
+		if (index2 >= 0)
+		{
+			AddListItem(tkl, tklines_ip_hash[index][index2]);
+			return tkl;
+		}
+	}
+
+	/* If we get here it's just for our normal list.. */
+	index = tkl_hash(tkl_typetochar(type));
+	AddListItem(tkl, tklines[index]);
+
+	return tkl;
+}
+
+/** Add a ban exception TKL entry.
+ * @param type                TKL_EXCEPTION or TKLEXCEPT|TKL_GLOBAL.
+ * @param usermask            The user mask
+ * @param hostmask            The host mask
+ * @param match               A securitygroup used for matching (can be NULL,
+ *                            if not NULL then this field is used as-is and not copied
+ *                            so caller should not free!)
+ * @param reason              The reason for the ban
+ * @param set_by              Who (or what) set the ban
+ * @param expire_at           When will the ban expire (0 for permanent)
+ * @param set_at              When was the ban set
+ * @param soft                Whether it's a soft-ban
+ * @param bantypes            The ban types to exempt from
+ * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
+ * @returns                   The TKL entry, or NULL in case of a problem,
+ *                            such as a regex failing to compile, memory problem, ..
+ * @note
+ * Be sure not to call this function for spamfilters,
+ * qlines or exempts, which have their own function!
+ */
+TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match,
+                           char *reason, char *set_by,
+                           time_t expire_at, time_t set_at, int soft, char *bantypes, int flags)
+{
+	TKL *tkl;
+	int index, index2;
+
+	if (!TKLIsBanExceptionType(type))
+		abort();
+	tkl = safe_alloc(sizeof(TKL));
+	/* First the common fields */
+	tkl->type = type;
+	tkl->flags = flags;
+	tkl->set_at = set_at;
+	safe_strdup(tkl->set_by, set_by);
+	tkl->expire_at = expire_at;
+	/* Now the ban except fields */
+	tkl->ptr.banexception = safe_alloc(sizeof(BanException));
+	safe_strdup(tkl->ptr.banexception->usermask, usermask);
+	safe_strdup(tkl->ptr.banexception->hostmask, hostmask);
+	tkl->ptr.banexception->match = match;
+	if (soft)
+		tkl->ptr.banexception->subtype = TKL_SUBTYPE_SOFT;
+	safe_strdup(tkl->ptr.banexception->bantypes, bantypes);
+	safe_strdup(tkl->ptr.banexception->reason, reason);
+
+	/* For ip hash table TKL's... */
+	index = tkl_ip_hash_type(tkl_typetochar(type));
+	if (index >= 0)
+	{
+		index2 = tkl_ip_hash_tkl(tkl);
+		if (index2 >= 0)
+		{
+			AddListItem(tkl, tklines_ip_hash[index][index2]);
+			return tkl;
+		}
+	}
+
+	/* If we get here it's just for our normal list.. */
+	index = tkl_hash(tkl_typetochar(type));
+	AddListItem(tkl, tklines[index]);
+
+	return tkl;
+}
+
+/** Add a name ban TKL entry (Q-Line), used for banning nicks and channels.
+ * @param type                The TKL type, one of TKL_*,
+ *                            optionally OR'ed with TKL_GLOBAL.
+ * @param name                The nick or channel to be banned (wildcards accepted)
+ * @param hold                Flag to indicate services hold
+ * @param reason              The reason for the ban
+ * @param set_by              Who (or what) set the ban
+ * @param expire_at           When will the ban expire (0 for permanent)
+ * @param set_at              When was the ban set
+ * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
+ * @returns                   The TKL entry, or NULL in case of a problem,
+ *                            such as a regex failing to compile, memory problem, ..
+ * @note
+ * Be sure not to call this function for spamfilters,
+ * qlines or exempts, which have their own function!
+ */
+TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
+                          time_t expire_at, time_t set_at, int flags)
+{
+	TKL *tkl;
+	int index;
+
+	if (!TKLIsNameBanType(type))
+		abort();
+
+	tkl = safe_alloc(sizeof(TKL));
+	/* First the common fields */
+	tkl->type = type;
+	tkl->flags = flags;
+	tkl->set_at = set_at;
+	safe_strdup(tkl->set_by, set_by);
+	tkl->expire_at = expire_at;
+	/* Now the name ban fields */
+	tkl->ptr.nameban = safe_alloc(sizeof(ServerBan));
+	safe_strdup(tkl->ptr.nameban->name, name);
+	tkl->ptr.nameban->hold = hold;
+	safe_strdup(tkl->ptr.nameban->reason, reason);
+
+	/* Name bans go via the normal TKL list.. */
+	index = tkl_hash(tkl_typetochar(type));
+	AddListItem(tkl, tklines[index]);
+
+	return tkl;
+}
+
+
+/** Free a TKL entry but do not remove from the list.
+ * (this assumes that it was not added yet or is already removed)
+ * Most people will use tkl_del_line() instead.
+ */
+void _free_tkl(TKL *tkl)
+{
+	/* Free the entry */
+	/* First, the common fields */
+	safe_free(tkl->set_by);
+	/* Now the type specific fields */
+	if (TKLIsServerBan(tkl) && tkl->ptr.serverban)
+	{
+		safe_free(tkl->ptr.serverban->usermask);
+		safe_free(tkl->ptr.serverban->hostmask);
+		safe_free(tkl->ptr.serverban->reason);
+		safe_free(tkl->ptr.serverban);
+	} else
+	if (TKLIsNameBan(tkl) && tkl->ptr.nameban)
+	{
+		safe_free(tkl->ptr.nameban->name);
+		safe_free(tkl->ptr.nameban->reason);
+		safe_free(tkl->ptr.nameban);
+	} else
+	if (TKLIsSpamfilter(tkl) && tkl->ptr.spamfilter)
+	{
+		/* Spamfilter */
+		safe_free(tkl->ptr.spamfilter->tkl_reason);
+		if (tkl->ptr.spamfilter->match)
+			unreal_delete_match(tkl->ptr.spamfilter->match);
+		safe_free(tkl->ptr.spamfilter);
+	} else
+	if (TKLIsBanException(tkl) && tkl->ptr.banexception)
+	{
+		safe_free(tkl->ptr.banexception->usermask);
+		safe_free(tkl->ptr.banexception->hostmask);
+		if (tkl->ptr.banexception->match)
+			free_security_group(tkl->ptr.banexception->match);
+		safe_free(tkl->ptr.banexception->bantypes);
+		safe_free(tkl->ptr.banexception->reason);
+		safe_free(tkl->ptr.banexception);
+	}
+	safe_free(tkl);
+}
+
+/** Delete a TKL entry from the list and free it.
+ * @param tkl The TKL entry.
+ */
+void _tkl_del_line(TKL *tkl)
+{
+	int index, index2;
+	int found = 0;
+
+	/* Try to find it in the ip TKL hash table first
+	 * (this only applies to server bans)
+	 */
+	index = tkl_ip_hash_type(tkl_typetochar(tkl->type));
+	if (index >= 0)
+	{
+		index2 = tkl_ip_hash_tkl(tkl);
+		if (index2 >= 0)
+		{
+#if 1
+			/* Temporary validation until an rmtkl(?) bug is fixed */
+			TKL *d;
+			int really_found = 0;
+			for (d = tklines_ip_hash[index][index2]; d; d = d->next)
+				if (d == tkl)
+				{
+					really_found = 1;
+					break;
+				}
+			if (!really_found)
+			{
+				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
+			DelListItem(tkl, tklines_ip_hash[index][index2]);
+			found = 1;
+		}
+	}
+
+	if (!found)
+	{
+		/* If we get here it's just for our normal list.. */
+		index = tkl_hash(tkl_typetochar(tkl->type));
+		DelListItem(tkl, tklines[index]);
+	}
+
+	/* Finally, free the entry */
+	free_tkl(tkl);
+	check_mtag_spamfilters_present();
+}
+
+/** Add some default ban exceptions - for localhost */
+static void add_default_exempts(void)
+{
+	/* The exempted ban types are only ones that will affect other connections as well,
+	 * such as gline, and not policy decissions such as maxperip exempt or bypass qlines.
+	 * Currently the list is: gline, kline, gzline, zline, shun, blacklist,
+	 *                        connect-flood, handshake-data-flood.
+	 */
+	tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.1", NULL, "localhost is always exempt",
+	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
+	tkl_add_banexception(TKL_EXCEPTION, "*", "::1", NULL, "localhost is always exempt",
+	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
+}
+
+/*
+ * tkl_check_local_remove_shun:
+ * removes shun from currently connected users affected by tmp.
+ */
+// TODO / FIXME: audit this function, it looks crazy
+void _tkl_check_local_remove_shun(TKL *tmp)
+{
+	long i;
+	char *chost, *cname, *cip;
+	int is_ip;
+	Client *client;
+
+	TKL *tk;
+	int keep_shun;
+
+	for (i = 0; i <= 5; i++)
+	{
+		list_for_each_entry(client, &lclient_list, lclient_node)
+			if (MyUser(client) && IsShunned(client))
+			{
+				chost = client->local->sockhost;
+				cname = client->user->username;
+
+				cip = GetIP(client);
+
+				if ((*tmp->ptr.serverban->hostmask >= '0') && (*tmp->ptr.serverban->hostmask <= '9'))
+					is_ip = 1;
+				else
+					is_ip = 0;
+
+				if (is_ip == 0 ?
+				    (match_simple(tmp->ptr.serverban->hostmask, chost) && match_simple(tmp->ptr.serverban->usermask, cname)) :
+				    (match_simple(tmp->ptr.serverban->hostmask, chost) || match_simple(tmp->ptr.serverban->hostmask, cip))
+				    && match_simple(tmp->ptr.serverban->usermask, cname))
+				{
+					/*
+					  before blindly marking this user as un-shunned, we need to check
+					  if the user is under any other existing shuns. (#0003906)
+					  Unfortunately, this requires crazy amounts of indentation ;-).
+
+					  This enumeration code is based off of _tkl_stats()
+					 */
+					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->ptr.serverban->hostmask >= '0') && (*tk->ptr.serverban->hostmask <= '9')
+							    /* the hostmask is an IP */
+							    && (match_simple(tk->ptr.serverban->hostmask, chost) || match_simple(tk->ptr.serverban->hostmask, cip)))
+								keep_shun = 1;
+							else
+								/* the hostmask is not an IP */
+								if (match_simple(tk->ptr.serverban->hostmask, chost) && match_simple(tk->ptr.serverban->usermask, cname))
+									keep_shun = 1;
+						}
+
+					if (!keep_shun)
+					{
+						ClearShunned(client);
+					}
+				}
+			}
+	}
+}
+
+
+/** This returns something like user@host, or %user@host, or ~a:Trusted
+ * 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)
+{
+	if (TKLIsServerBan(tkl))
+	{
+		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)) ? "%" : "",
+				tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
+		} else {
+			ircsnprintf(buf, buflen, "%s%s@%s",
+				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
+				tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
+		}
+	} else
+	if (TKLIsBanException(tkl))
+	{
+		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)) ? "%" : "",
+				tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask);
+		} else {
+			ircsnprintf(buf, buflen, "%s%s@%s",
+				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
+				tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask);
+		}
+	} else
+		abort();
+
+	return buf;
+}
+
+/** Deal with expiration of a specific TKL entry.
+ * This is a helper function for tkl_check_expire().
+ */
+void tkl_expire_entry(TKL *tkl)
+{
+	if (TKLIsServerBan(tkl))
+	{
+		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)
+		{
+			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))
+	{
+		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);
+
+	RunHook(HOOKTYPE_TKL_DEL, NULL, tkl);
+	tkl_del_line(tkl);
+}
+
+/** Regularly check TKL entries for expiration */
+EVENT(tkl_check_expire)
+{
+	TKL *tkl, *next;
+	time_t nowtime;
+	int index, index2;
+
+	nowtime = TStime();
+
+	/* First, hashed entries.. */
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = next)
+			{
+				next = tkl->next;
+				if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
+				{
+					tkl_expire_entry(tkl);
+				}
+			}
+		}
+	}
+
+	/* Now normal entries.. */
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = next)
+		{
+			next = tkl->next;
+			if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
+			{
+				tkl_expire_entry(tkl);
+			}
+		}
+	}
+}
+
+/* This is just a helper function for find_tkl_exception() */
+static int find_tkl_exception_matcher(Client *client, int ban_type, TKL *except_tkl)
+{
+	char uhost[NICKLEN+HOSTLEN+1];
+
+	if (!TKLIsBanException(except_tkl))
+		return 0;
+
+	if (!tkl_banexception_matches_type(except_tkl, ban_type))
+		return 0;
+
+	/* For config file except ban { } we use security groups instead of simple user/host */
+	if (except_tkl->ptr.banexception->match)
+		return user_allowed_by_security_group(client, except_tkl->ptr.banexception->match);
+
+	tkl_uhost(except_tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
+
+	if (match_user(uhost, client, MATCH_CHECK_REAL))
+	{
+		if (!(except_tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT))
+			return 1; /* hard ban exempt */
+		if ((except_tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) && IsLoggedIn(client))
+			return 1; /* soft ban exempt - only matches if user is logged in */
+	}
+
+	return 0; /* not found */
+}
+
+/** Search for TKL Exceptions for this user.
+ * @param ban_type   The ban type to check, normally ban_tkl->type.
+ * @param client     The user
+ * @returns 1 if ban exempt, 0 if not.
+ * @note
+ * If you have a TKL ban that matched, say, 'ban_tkl'.
+ * Then you call this function like this:
+ * if (find_tkl_exception(ban_tkl->type, client))
+ *     return 0; // User is exempt
+ * [.. continue and ban the user..]
+ */
+int _find_tkl_exception(int ban_type, Client *client)
+{
+	TKL *tkl;
+	int index, index2;
+	Hook *hook;
+
+	if (IsServer(client) || IsMe(client))
+		return 1;
+
+	/* First, the TKL ip hash table entries.. */
+	index = tkl_ip_hash_type('e');
+	index2 = tkl_ip_hash(GetIP(client));
+	if (index2 >= 0)
+	{
+		for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+		{
+			if (find_tkl_exception_matcher(client, ban_type, tkl))
+				return 1; /* exempt */
+		}
+	}
+
+	/* If not banned (yet), then check regular entries.. */
+	for (tkl = tklines[tkl_hash('e')]; tkl; tkl = tkl->next)
+	{
+			if (find_tkl_exception_matcher(client, ban_type, tkl))
+				return 1; /* exempt */
+	}
+
+	for (hook = Hooks[HOOKTYPE_TKL_EXCEPT]; hook; hook = hook->next)
+	{
+		if (hook->func.intfunc(client, ban_type) > 0)
+			return 1; /* exempt by hook */
+	}
+	return 0; /* Not exempt */
+}
+
+/** Helper function for find_tkline_match() */
+int find_tkline_match_matcher(Client *client, int skip_soft, TKL *tkl)
+{
+	char uhost[NICKLEN+HOSTLEN+1];
+
+	if (!TKLIsServerBan(tkl) || (tkl->type & TKL_SHUN))
+		return 0;
+
+	if (skip_soft && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT))
+		return 0;
+
+	tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
+
+	if (match_user(uhost, client, MATCH_CHECK_REAL))
+	{
+		/* If hard-ban, or soft-ban&unauthenticated.. */
+		if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
+		    ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(client)))
+		{
+			/* Found match. Now check for exception... */
+			if (find_tkl_exception(tkl->type, client))
+				return 0; /* exempted */
+			return 1; /* banned */
+		}
+	}
+
+	return 0; /* no match */
+}
+
+/** Check if user matches a *LINE. If so, kill the user.
+ * @retval 1 if client is banned, 0 if not
+ * @note Do not continue processing if the client is killed (0 return value).
+ * @note Return value changed with regards to UnrealIRCd 4!
+ */
+int _find_tkline_match(Client *client, int skip_soft)
+{
+	TKL *tkl;
+	int banned = 0;
+	int index, index2;
+
+	if (IsServer(client) || IsMe(client))
+		return 0;
+
+	/* First, the TKL ip hash table entries.. */
+	index2 = tkl_ip_hash(GetIP(client));
+	if (index2 >= 0)
+	{
+		for (index = 0; index < TKLIPHASHLEN1; index++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				banned = find_tkline_match_matcher(client, skip_soft, tkl);
+				if (banned)
+					break;
+			}
+			if (banned)
+				break;
+		}
+	}
+
+	/* If not banned (yet), then check regular entries.. */
+	if (!banned)
+	{
+		for (index = 0; index < TKLISTLEN; index++)
+		{
+			for (tkl = tklines[index]; tkl; tkl = tkl->next)
+			{
+				banned = find_tkline_match_matcher(client, skip_soft, tkl);
+				if (banned)
+					break;
+			}
+			if (banned)
+				break;
+		}
+	}
+
+	if (!banned)
+		return 0;
+
+	/* User is banned... */
+
+	RunHookReturnInt(HOOKTYPE_FIND_TKLINE_MATCH, !=99, client, tkl);
+
+	if (tkl->type & TKL_KILL)
+	{
+		ircstats.is_ref++;
+		if (tkl->type & TKL_GLOBAL)
+			banned_client(client, "G-Lined", tkl->ptr.serverban->reason, 1, 0);
+		else
+			banned_client(client, "K-Lined", tkl->ptr.serverban->reason, 0, 0);
+		return 1; /* killed */
+	} else
+	if (tkl->type & TKL_ZAP)
+	{
+		ircstats.is_ref++;
+		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
+		return 1; /* killed */
+	}
+
+	return 0;
+}
+
+/** Check if user is shunned.
+ * @param client   Client to check.
+ * @returns 1 if shunned, 0 if not.
+ */
+int _find_shun(Client *client)
+{
+	TKL *tkl;
+
+	if (IsServer(client) || IsMe(client))
+		return 0;
+
+	if (IsShunned(client))
+		return 1;
+
+	if (ValidatePermissionsForPath("immune:server-ban:shun",client,NULL,NULL,NULL))
+		return 0;
+
+	for (tkl = tklines[tkl_hash('s')]; tkl; tkl = tkl->next)
+	{
+		char uhost[NICKLEN+HOSTLEN+1];
+
+		if (!(tkl->type & TKL_SHUN))
+			continue;
+
+		tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
+
+		if (match_user(uhost, client, MATCH_CHECK_REAL))
+		{
+			/* If hard-ban, or soft-ban&unauthenticated.. */
+			if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
+			    ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(client)))
+			{
+				/* Found match. Now check for exception... */
+				if (find_tkl_exception(TKL_SHUN, client))
+					return 0;
+				SetShunned(client);
+				return 1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/** Helper function for spamfilter_build_user_string().
+ * This ensures IPv6 hosts are in brackets.
+ */
+char *SpamfilterMagicHost(char *i)
+{
+	static char buf[256];
+
+	if (!strchr(i, ':'))
+		return i;
+
+	/* otherwise, it's IPv6.. prepend it with [ and append a ] */
+	ircsnprintf(buf, sizeof(buf), "[%s]", i);
+	return buf;
+}
+
+/** Build the nick:user@host:realname string
+ * @param buf     The buffer used for storage, the size of
+ *                which should be at least NICKLEN+USERLEN+HOSTLEN+1.
+ * @param nick    The nickname (because client can be nick-changing).
+ * @param client  The affected client.
+ */
+void _spamfilter_build_user_string(char *buf, char *nick, Client *client)
+{
+	snprintf(buf, NICKLEN+USERLEN+HOSTLEN+1, "%s!%s@%s:%s",
+		nick, client->user->username, SpamfilterMagicHost(client->user->realhost), client->info);
+}
+
+
+/** Checks if the user matches a spamfilter of type 'u' (user,
+ * nick!user@host:realname ban).
+ * Written by: Syzop
+ * Assumes: only call for clients, possible assume on local clients [?]
+ * Return values: see match_spamfilter()
+ */
+int _find_spamfilter_user(Client *client, int flags)
+{
+	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
+
+	if (ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL))
+		return 0;
+
+	spamfilter_build_user_string(spamfilter_user, client->name, client);
+	return match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, flags, NULL);
+}
+
+/** Check a spamfilter against all local users and print a message.
+ * This is only used for the 'warn' action (BAN_ACT_WARN).
+ */
+int spamfilter_check_users(TKL *tkl)
+{
+	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
+	char buf[1024];
+	int matches = 0;
+	Client *client;
+
+	list_for_each_entry_reverse(client, &lclient_list, lclient_node)
+	{
+		if (MyUser(client))
+		{
+			spamfilter_build_user_string(spamfilter_user, client->name, client);
+			if (!unreal_match(tkl->ptr.spamfilter->match, spamfilter_user))
+				continue; /* No match */
+
+			/* matched! */
+			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));
+
+			RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, spamfilter_user, spamfilter_user, SPAMF_USER, NULL, tkl);
+			matches++;
+		}
+	}
+
+	return matches;
+}
+
+/** Check if the nick or channel name is banned (Q-Line).
+ * @param client   The possibly affected user.
+ * @param name     The nick or channel to check.
+ * @param is_hold  This will be SET (so OUT) if it's a services hold.
+ *
+ * @note Special handling:
+ * #*ble* will match with #bbleh
+ * *ble* will NOT match with #bbleh, will with bbleh
+ */
+TKL *_find_qline(Client *client, char *name, int *ishold)
+{
+	TKL *tkl;
+	int	points = 0;
+	*ishold = 0;
+
+	if (IsServer(client) || IsMe(client))
+		return NULL;
+
+	for (tkl = tklines[tkl_hash('q')]; tkl; tkl = tkl->next)
+	{
+		points = 0;
+
+		if (!TKLIsNameBan(tkl))
+			continue;
+
+		if (((*tkl->ptr.nameban->name == '#' && *name == '#') || (*tkl->ptr.nameban->name != '#' && *name != '#'))
+		    && match_simple(tkl->ptr.nameban->name, name))
+		{
+			points = 1;
+			break;
+		}
+	}
+
+	if (points != 1)
+		return NULL;
+
+	/* It's a services hold (except bans don't override this) */
+	if (tkl->ptr.nameban->hold)
+	{
+		*ishold = 1;
+		return tkl;
+	}
+
+	if (find_tkl_exception(TKL_NAME, client))
+		return NULL; /* exempt */
+
+	return tkl;
+}
+
+/** Helper function for find_tkline_match_zap() */
+TKL *find_tkline_match_zap_matcher(Client *client, TKL *tkl)
+{
+	if (!(tkl->type & TKL_ZAP))
+		return NULL;
+
+	if (match_user(tkl->ptr.serverban->hostmask, client, MATCH_CHECK_IP))
+	{
+		if (find_tkl_exception(TKL_ZAP, client))
+			return NULL; /* exempt */
+		return tkl; /* banned */
+	}
+
+	return NULL; /* no match */
+}
+
+/** Find matching (G)ZLINE, if any.
+ * Note: function prototype changed as per UnrealIRCd 4.2.0.
+ * @retval The (G)Z-Line that matched, or NULL if no such ban was found.
+ */
+TKL *_find_tkline_match_zap(Client *client)
+{
+	TKL *tkl, *ret;
+	int index, index2;
+
+	if (IsServer(client) || IsMe(client))
+		return NULL;
+
+	/* First, the TKL ip hash table entries.. */
+	index = tkl_ip_hash_type('z');
+	index2 = tkl_ip_hash(GetIP(client));
+	if (index2 >= 0)
+	{
+		for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+		{
+			ret = find_tkline_match_zap_matcher(client, tkl);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/* If not banned (yet), then check regular entries.. */
+	for (tkl = tklines[tkl_hash('z')]; tkl; tkl = tkl->next)
+	{
+		ret = find_tkline_match_zap_matcher(client, tkl);
+		if (ret)
+			return ret;
+	}
+
+	return NULL;
+}
+
+#define BY_MASK 0x1
+#define BY_REASON 0x2
+#define NOT_BY_MASK 0x4
+#define NOT_BY_REASON 0x8
+#define BY_SETBY 0x10
+#define NOT_BY_SETBY 0x20
+
+typedef struct {
+	int flags;
+	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(const char *para, TKLFlag *flag)
+{
+	static char paratmp[512]; /* <- copy of para, because it gets fragged by strtok() */
+	char *flags, *tmp;
+	char what = '+';
+
+	memset(flag, 0, sizeof(TKLFlag));
+	strlcpy(paratmp, para, sizeof(paratmp));
+	flags = strtok(paratmp, " ");
+	if (!flags)
+		return;
+
+	for (; *flags; flags++)
+	{
+		switch (*flags)
+		{
+			case '+':
+				what = '+';
+				break;
+			case '-':
+				what = '-';
+				break;
+			case 'm':
+				if (flag->mask || !(tmp = strtok(NULL, " ")))
+					continue;
+				if (what == '+')
+					flag->flags |= BY_MASK;
+				else
+					flag->flags |= NOT_BY_MASK;
+				flag->mask = tmp;
+				break;
+			case 'r':
+				if (flag->reason || !(tmp = strtok(NULL, " ")))
+					continue;
+				if (what == '+')
+					flag->flags |= BY_REASON;
+				else
+					flag->flags |= NOT_BY_REASON;
+				flag->reason = tmp;
+				break;
+			case 's':
+				if (flag->set_by || !(tmp = strtok(NULL, " ")))
+					continue;
+				if (what == '+')
+					flag->flags |= BY_SETBY;
+				else
+					flag->flags |= NOT_BY_SETBY;
+				flag->set_by = tmp;
+				break;
+		}
+	}
+}
+
+/** Does this TKL entry match the search terms?
+ * This is a helper function for tkl_stats().
+ */
+int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklflags, TKL *tkl)
+{
+	/***** First, handle the selection ******/
+
+	if (!BadPtr(para))
+	{
+		if (tklflags->flags & BY_SETBY)
+			if (!match_simple(tklflags->set_by, tkl->set_by))
+				return 0;
+		if (tklflags->flags & NOT_BY_SETBY)
+			if (match_simple(tklflags->set_by, tkl->set_by))
+				return 0;
+		if (TKLIsServerBan(tkl))
+		{
+			if (tklflags->flags & BY_MASK)
+			{
+				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
+					return 0;
+			}
+			if (tklflags->flags & NOT_BY_MASK)
+			{
+				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
+					return 0;
+			}
+			if (tklflags->flags & BY_REASON)
+				if (!match_simple(tklflags->reason, tkl->ptr.serverban->reason))
+					return 0;
+			if (tklflags->flags & NOT_BY_REASON)
+				if (match_simple(tklflags->reason, tkl->ptr.serverban->reason))
+					return 0;
+		} else
+		if (TKLIsNameBan(tkl))
+		{
+			if (tklflags->flags & BY_MASK)
+			{
+				if (!match_simple(tklflags->mask, tkl->ptr.nameban->name))
+					return 0;
+			}
+			if (tklflags->flags & NOT_BY_MASK)
+			{
+				if (match_simple(tklflags->mask, tkl->ptr.nameban->name))
+					return 0;
+			}
+			if (tklflags->flags & BY_REASON)
+				if (!match_simple(tklflags->reason, tkl->ptr.nameban->reason))
+					return 0;
+			if (tklflags->flags & NOT_BY_REASON)
+				if (match_simple(tklflags->reason, tkl->ptr.nameban->reason))
+					return 0;
+		} else
+		if (TKLIsBanException(tkl))
+		{
+			if (tklflags->flags & BY_MASK)
+			{
+				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
+					return 0;
+			}
+			if (tklflags->flags & NOT_BY_MASK)
+			{
+				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
+					return 0;
+			}
+			if (tklflags->flags & BY_REASON)
+				if (!match_simple(tklflags->reason, tkl->ptr.banexception->reason))
+					return 0;
+			if (tklflags->flags & NOT_BY_REASON)
+				if (match_simple(tklflags->reason, tkl->ptr.banexception->reason))
+					return 0;
+		}
+	}
+
+	/***** If we are still here then we have a match and will will send the STATS entry */
+	if (TKLIsServerBan(tkl))
+	{
+		char uhostbuf[BUFSIZE];
+		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
+		if (tkl->type == (TKL_KILL | TKL_GLOBAL))
+		{
+			sendnumeric(client, RPL_STATSGLINE, 'G', uhost,
+				   (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) ? (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) ? (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) ? (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) ? (long long)(tkl->expire_at - TStime()) : 0,
+				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
+		}
+	} else
+	if (TKLIsSpamfilter(tkl))
+	{
+		sendnumeric(client, RPL_STATSSPAMF,
+			(tkl->type & TKL_GLOBAL) ? 'F' : 'f',
+			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) ? (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"))
+		{
+			char *hash = spamfilter_id(tkl);
+			if (tkl->type & TKL_GLOBAL)
+			{
+				sendtxtnumeric(client, "To delete this spamfilter, use /SPAMFILTER del %s", hash);
+				sendtxtnumeric(client, "-");
+			} else {
+				sendtxtnumeric(client, "This spamfilter is stored in the configuration file and cannot be removed with /SPAMFILTER del");
+				sendtxtnumeric(client, "-");
+			}
+		}
+	} else
+	if (TKLIsNameBan(tkl))
+	{
+		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))
+	{
+		if (tkl->ptr.banexception->match)
+		{
+			/* Config-added: uses security groups */
+			NameValuePrioList *m;
+			for (m = tkl->ptr.banexception->match->printable_list; m; m = m->next)
+			{
+				sendnumeric(client, RPL_STATSEXCEPTTKL, namevalue_nospaces(m),
+					   tkl->ptr.banexception->bantypes,
+					   (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 {
+			/* IRC-added: uses simple user/host mask */
+			char uhostbuf[BUFSIZE];
+			char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
+			sendnumeric(client, RPL_STATSEXCEPTTKL, uhost,
+				   tkl->ptr.banexception->bantypes,
+				   (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 */
+		return 0;
+	}
+	return 1;
+}
+
+/* TKL Stats. This is used by /STATS gline and all the others */
+void _tkl_stats(Client *client, int type, const char *para, int *cnt)
+{
+	TKL *tk;
+	TKLFlag tklflags;
+	int index, index2;
+
+	if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
+		return;
+
+	if (!BadPtr(para))
+		parse_stats_params(para, &tklflags);
+
+	/* First the IP hashed entries (if applicable).. */
+	index = tkl_ip_hash_type(tkl_typetochar(type));
+	if (index >= 0)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tk = tklines_ip_hash[index][index2]; tk; tk = tk->next)
+			{
+				if (type && tk->type != type)
+					continue;
+				if (tkl_stats_matcher(client, type, para, &tklflags, tk))
+				{
+					*cnt += 1;
+					if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
+					{
+						sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
+						sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
+						return;
+					}
+				}
+			}
+		}
+	}
+
+	/* Then the normal entries... */
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tk = tklines[index]; tk; tk = tk->next)
+		{
+			if (type && tk->type != type)
+				continue;
+			if (tkl_stats_matcher(client, type, para, &tklflags, tk))
+			{
+				*cnt += 1;
+				if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
+				{
+					sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
+					sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
+					return;
+				}
+			}
+		}
+	}
+
+	if ((type == (TKL_SPAMF|TKL_GLOBAL)) && (!para || strcasecmp(para, "del")))
+	{
+		/* If requesting spamfilter stats and not spamfilter del, then suggest it. */
+		sendnotice(client, "Tip: if you are looking for an easy way to remove a spamfilter, run '/SPAMFILTER del'.");
+	}
+}
+
+/** Synchronize a TKL entry with the other server.
+ * @param sender  The sender (eg: &me).
+ * @param to      The remote server.
+ * @param tkl     The TKL entry.
+ */
+void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
+{
+	char typ;
+
+	if (!(tkl->type & TKL_GLOBAL))
+		return; /* nothing to sync */
+
+	typ = tkl_typetochar(tkl->type);
+
+	if (TKLIsServerBan(tkl))
+	{
+		sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld :%s", sender->name,
+			   add ? '+' : '-',
+			   typ,
+			   (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
+			   *tkl->ptr.serverban->usermask ? tkl->ptr.serverban->usermask : "*",
+			   tkl->ptr.serverban->hostmask, tkl->set_by,
+			   (long long)tkl->expire_at, (long long)tkl->set_at,
+			   tkl->ptr.serverban->reason);
+	} else
+	if (TKLIsNameBan(tkl))
+	{
+		sendto_one(to, NULL, ":%s TKL %c %c %c %s %s %lld %lld :%s", sender->name,
+			   add ? '+' : '-',
+			   typ,
+			   tkl->ptr.nameban->hold ? 'H' : '*',
+			   tkl->ptr.nameban->name,
+			   tkl->set_by,
+			   (long long)tkl->expire_at, (long long)tkl->set_at,
+			   tkl->ptr.nameban->reason);
+	} else
+	if (TKLIsSpamfilter(tkl))
+	{
+		sendto_one(to, NULL, ":%s TKL %c %c %s %c %s %lld %lld %lld %s %s :%s", sender->name,
+			   add ? '+' : '-',
+			   typ,
+			   spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
+			   banact_valtochar(tkl->ptr.spamfilter->action),
+			   tkl->set_by,
+			   (long long)tkl->expire_at, (long long)tkl->set_at,
+			   (long long)tkl->ptr.spamfilter->tkl_duration, tkl->ptr.spamfilter->tkl_reason,
+			   unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
+			   tkl->ptr.spamfilter->match->str);
+	} else
+	if (TKLIsBanException(tkl))
+	{
+		sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld %s :%s", sender->name,
+			   add ? '+' : '-',
+			   typ,
+			   (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
+			   *tkl->ptr.banexception->usermask ? tkl->ptr.banexception->usermask : "*",
+			   tkl->ptr.banexception->hostmask, tkl->set_by,
+			   (long long)tkl->expire_at, (long long)tkl->set_at,
+			   tkl->ptr.banexception->bantypes,
+			   tkl->ptr.banexception->reason);
+	} else
+	{
+		unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_SYNC_SEND_ENTRY", NULL,
+			   "[BUG] tkl_sync_send_entry() called, but unknown type: $tkl.type_string ($tkl_type_int)",
+			   log_data_tkl("tkl", tkl),
+			   log_data_integer("tkl_type_int", typ));
+		abort();
+	}
+}
+
+/** Broadcast a TKL entry.
+ * @param sender  The sender, eg &me
+ * @param skip    The client to skip, eg 'client' or NULL.
+ * @param tkl     The TKL entry to synchronize with the other servers.
+ */
+void tkl_broadcast_entry(int add, Client *sender, Client *skip, TKL *tkl)
+{
+	Client *acptr;
+
+	/* Silly fix for RPC calls that lead to broadcasts from this sender */
+	if (!IsUser(sender) && !IsServer(sender))
+		sender = &me;
+
+	list_for_each_entry(acptr, &server_list, special_node)
+	{
+		if (skip && acptr == skip->direction)
+			continue;
+
+		tkl_sync_send_entry(add, sender, acptr, tkl);
+	}
+}
+
+/** Synchronize all TKL entries with this server.
+ * @param client The server to synchronize with.
+ */
+void _tkl_sync(Client *client)
+{
+	TKL *tkl;
+	int index, index2;
+
+	/* First, hashed entries.. */
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				tkl_sync_send_entry(1, &me, client, tkl);
+			}
+		}
+	}
+
+	/* Then, regular entries.. */
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			tkl_sync_send_entry(1, &me, client, tkl);
+		}
+	}
+}
+
+/** Find a server ban TKL - only used to prevent duplicates and for deletion */
+TKL *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban)
+{
+	char tpe = tkl_typetochar(type);
+	TKL *head, *tkl;
+
+	if (!TKLIsServerBanType(type))
+		abort();
+
+	head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
+	for (tkl = head; tkl; tkl = tkl->next)
+	{
+		if (tkl->type == type)
+		{
+			if (!strcasecmp(tkl->ptr.serverban->hostmask, hostmask) &&
+			    !strcasecmp(tkl->ptr.serverban->usermask, usermask))
+			{
+				/* And an extra check for soft/hard ban mismatches.. */
+				if ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) == softban)
+					return tkl;
+			}
+		}
+	}
+	return NULL; /* Not found */
+}
+
+/** Find a ban exception TKL - only used to prevent duplicates and for deletion */
+TKL *_find_tkl_banexception(int type, char *usermask, char *hostmask, int softban)
+{
+	char tpe = tkl_typetochar(type);
+	TKL *head, *tkl;
+
+	if (!TKLIsBanExceptionType(type))
+		abort();
+
+	head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
+	for (tkl = head; tkl; tkl = tkl->next)
+	{
+		if (tkl->type == type)
+		{
+			if (!strcasecmp(tkl->ptr.banexception->hostmask, hostmask) &&
+			    !strcasecmp(tkl->ptr.banexception->usermask, usermask))
+			{
+				/* And an extra check for soft/hard ban mismatches.. */
+				if ((tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) == softban)
+					return tkl;
+			}
+		}
+	}
+	return NULL; /* Not found */
+}
+
+/** Find a name ban TKL (qline) - only used to prevent duplicates and for deletion */
+TKL *_find_tkl_nameban(int type, char *name, int hold)
+{
+	char tpe = tkl_typetochar(type);
+	TKL *tkl;
+
+	if (!TKLIsNameBanType(type))
+		abort();
+
+	for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
+	{
+		if ((tkl->type == type) && !strcasecmp(tkl->ptr.nameban->name, name))
+			return tkl;
+	}
+	return NULL; /* Not found */
+}
+
+/** Find a spamfilter TKL - only used to prevent duplicates and for deletion */
+TKL *_find_tkl_spamfilter(int type, char *match_string, BanAction action, unsigned short target)
+{
+	char tpe = tkl_typetochar(type);
+	TKL *tkl;
+
+	if (!TKLIsSpamfilterType(type))
+		abort();
+
+	for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
+	{
+		if ((type == tkl->type) &&
+		    !strcmp(match_string, tkl->ptr.spamfilter->match->str) &&
+		    (action == tkl->ptr.spamfilter->action) &&
+		    (target == tkl->ptr.spamfilter->target))
+		{
+			return tkl;
+		}
+	}
+	return NULL; /* Not found */
+}
+
+/** Send a notice to opers about the TKL that is being added */
+void _sendnotice_tkl_add(TKL *tkl)
+{
+	/* Don't show notices for temporary nick holds (issued by services) */
+	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
+		return;
+
+	if (TKLIsServerBan(tkl))
+	{
+		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))
+	{
+		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))
+	{
+		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))
+	{
+		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
+	{
+		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
+		           "[BUG] TKL added of unknown type, unhandled in sendnotice_tkl_add()!!!!");
+	}
+}
+
+/** Send a notice to opers about the TKL that is being deleted */
+void _sendnotice_tkl_del(char *removed_by, TKL *tkl)
+{
+	/* Don't show notices for temporary nick holds (issued by services) */
+	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
+		return;
+
+	if (TKLIsServerBan(tkl))
+	{
+		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))
+	{
+		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))
+	{
+		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))
+	{
+		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
+	{
+		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
+		           "[BUG] TKL removed of unknown type, unhandled in sendnotice_tkl_del()!!!!");
+	}
+}
+
+/** Called when a TKL is added by a remote user, local user, RPC user, ..
+ * (but not when a TKL is added via the config)
+ */
+void _tkl_added(Client *client, TKL *tkl)
+{
+	RunHook(HOOKTYPE_TKL_ADD, client, tkl);
+
+	sendnotice_tkl_add(tkl);
+
+	/* spamfilter 'warn' action is special */
+	if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
+		spamfilter_check_users(tkl);
+
+	/* Ban checking executes during run loop for efficiency */
+	loop.do_bancheck = 1;
+
+	if (tkl->type & TKL_GLOBAL)
+		tkl_broadcast_entry(1, client, client, tkl);
+}
+
+/** Add a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
+CMD_FUNC(cmd_tkl_add)
+{
+	TKL *tkl;
+	int type;
+	time_t expire_at, set_at;
+	const char *set_by;
+	char tkl_entry_exists = 0;
+
+	/* we rely on servers to be failsafe.. */
+	if (!IsServer(client) && !IsMe(client))
+		return;
+
+	if (parc < 9)
+		return;
+
+	type = tkl_chartotype(parv[2][0]);
+	if (!type)
+		return;
+
+	/* All TKL types have the following fields in common when adding:
+	 * parv[5]: set_by
+	 * parv[6]: expire_at
+	 * parv[7]: set_at
+	 * ... so we validate them here at the beginning.
+	 */
+
+	set_by = parv[5];
+	expire_at = atol(parv[6]);
+	set_at = atol(parv[7]);
+
+	/* Validate set and expiry time */
+	if ((set_at < 0) || !short_date(set_at, NULL))
+	{
+		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))
+	{
+		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;
+	}
+
+	/* Now comes type-specific validation
+	 * and we check if the TKL entry already exists and needs updating too.
+	 */
+
+	if (TKLIsServerBanType(type))
+	{
+		/* Validate server ban TKL fields */
+		int softban = 0;
+		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
+		 * linked servers are known to have sent this in the past.
+		 */
+		if (strchr(usermask, '@') || strchr(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;
+		}
+
+		/* In case of a soft ban, strip the percent sign early,
+		 * so parv[3] (username) is really the username without any prefix.
+		 * Set the 'softban' flag if this is the case.
+		 */
+		if (*usermask == '%')
+		{
+			usermask++;
+			softban = 1;
+		}
+
+		tkl = find_tkl_serverban(type, usermask, hostmask, softban);
+		if (tkl)
+		{
+			tkl_entry_exists = 1;
+		} else {
+			tkl = tkl_add_serverban(type, usermask, hostmask, reason,
+			                        set_by, expire_at, set_at, softban, 0);
+		}
+	} else
+	if (TKLIsBanExceptionType(type))
+	{
+		/* Validate ban exception TKL fields */
+		int softban = 0;
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
+		const char *bantypes = parv[8];
+		const char *reason;
+
+		if (parc < 10)
+			return;
+
+		reason = parv[9];
+
+		/* Some simple validation on usermask and hostmask:
+		 * may not contain an @. Yeah, some services or self-written
+		 * linked servers are known to have sent this in the past.
+		 */
+		if (strchr(usermask, '@') || strchr(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;
+		}
+
+		/* In case of a soft ban, strip the percent sign early,
+		 * so parv[3] (username) is really the username without any prefix.
+		 * Set the 'softban' flag if this is the case.
+		 */
+		if (*usermask == '%')
+		{
+			usermask++;
+			softban = 1;
+		}
+
+		/* At this moment we do not validate 'bantypes' since a missing
+		 * or wrong type does not cause harm anyway.
+		 */
+		tkl = find_tkl_banexception(type, usermask, hostmask, softban);
+		if (tkl)
+		{
+			tkl_entry_exists = 1;
+		} else {
+			tkl = tkl_add_banexception(type, usermask, hostmask, NULL, reason,
+			                           set_by, expire_at, set_at, softban, bantypes, 0);
+		}
+	} else
+	if (TKLIsNameBanType(type))
+	{
+		/* Validate name ban TKL fields */
+		int hold = 0;
+		const char *name = parv[4];
+		const char *reason = parv[8];
+
+		if (*parv[3] == 'H')
+			hold = 1;
+
+		tkl = find_tkl_nameban(type, name, hold);
+		if (tkl)
+		{
+			tkl_entry_exists = 1;
+		} else {
+			tkl = tkl_add_nameban(type, name, hold, reason, set_by, expire_at,
+			                      set_at, 0);
+		}
+	} else
+	if (TKLIsSpamfilterType(type))
+	{
+		/* Validate spamfilter-specific TKL fields */
+		MatchType match_method;
+		const char *match_string;
+		Match *m; /* compiled match_string */
+		time_t tkl_duration;
+		const char *tkl_reason;
+		BanAction action;
+		unsigned short target;
+		/* helper variables */
+		char *err;
+
+		if (parc < 12)
+		{
+			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];
+
+		match_method = unreal_match_method_strtoval(parv[10]);
+		if (match_method == 0)
+		{
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter '$spamfilter_string' has unknown 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)))
+		{
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_targets", parv[3]));
+			return;
+		}
+
+		if (!(action = banact_chartoval(*parv[4])))
+		{
+			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
+				"Invalid TKL entry from $client: "
+				"Spamfilter '$spamfilter_string' has unknown action '$spamfilter_action'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_action", parv[4]));
+			return;
+		}
+
+		tkl_duration = config_checkval(parv[8], CFG_TIME);
+		tkl_reason = parv[9];
+
+		tkl = find_tkl_spamfilter(type, match_string, action, target);
+
+		if (tkl)
+		{
+			tkl_entry_exists = 1;
+		} else {
+			m = unreal_create_match(match_method, match_string, &err);
+			if (!m)
+			{
+				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,
+			                         tkl_duration, tkl_reason, 0);
+		}
+	} else
+	{
+		/* Unhandled, should never happen */
+		abort();
+	}
+
+	if (!tkl)
+		return;
+
+	if (tkl_entry_exists)
+	{
+		/* Let's see if we need to update the existing entry.
+		 * Note that we only update common fields,
+		 * which is acceptable to me. -- Syzop
+		 */
+		if ((set_at < tkl->set_at) || (expire_at != tkl->expire_at) || strcmp(tkl->set_by, parv[5]))
+		{
+			/* here's how it goes:
+			 * set_at: oldest wins
+			 * expire_at: longest wins
+			 * set_by: highest strcmp wins
+			 *
+			 * We broadcast the result of this back to all servers except
+			 * sptr->direction, because that side will do the same thing and
+			 * send it back to his servers (except us)... no need for a
+			 * double networkwide flood ;p. -- Syzop
+			 */
+			tkl->set_at = MIN(tkl->set_at, set_at);
+
+			if (!tkl->expire_at || !expire_at)
+				tkl->expire_at = 0;
+			else
+				tkl->expire_at = MAX(tkl->expire_at, expire_at);
+
+			if (strcmp(tkl->set_by, parv[5]) < 0)
+				safe_strdup(tkl->set_by, parv[5]);
+
+			if (type & TKL_GLOBAL)
+				tkl_broadcast_entry(1, client, client, tkl);
+		}
+		return;
+	}
+
+	tkl_added(client, tkl);
+}
+
+/** Delete a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
+CMD_FUNC(cmd_tkl_del)
+{
+	TKL *tkl;
+	int type;
+	const char *removed_by;
+
+	if (!IsServer(client) && !IsMe(client))
+		return;
+
+	if (parc < 6)
+		return;
+
+	type = tkl_chartotype(parv[2][0]);
+	if (type == 0)
+		return;
+
+	removed_by = parv[5];
+
+	if (TKLIsServerBanType(type))
+	{
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
+		int softban = 0;
+
+		if (*usermask == '%')
+		{
+			usermask++;
+			softban = 1;
+		}
+
+		tkl = find_tkl_serverban(type, usermask, hostmask, softban);
+	}
+	else if (TKLIsBanExceptionType(type))
+	{
+		const char *usermask = parv[3];
+		const char *hostmask = parv[4];
+		int softban = 0;
+		/* other parameters are ignored */
+
+		if (*usermask == '%')
+		{
+			usermask++;
+			softban = 1;
+		}
+
+		tkl = find_tkl_banexception(type, usermask, hostmask, softban);
+	}
+	else if (TKLIsNameBanType(type))
+	{
+		int hold = 0;
+		const char *name = parv[4];
+
+		if (*parv[3] == 'H')
+			hold = 1;
+		tkl = find_tkl_nameban(type, name, hold);
+	}
+	else if (TKLIsSpamfilterType(type))
+	{
+		const char *match_string;
+		unsigned short target;
+		BanAction action;
+
+		if (parc < 9)
+		{
+			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)
+			match_string = parv[11];
+		else if (parc >= 11)
+			match_string = parv[10];
+		else
+			match_string = parv[8];
+
+		if (!(target = spamfilter_gettargets(parv[3], NULL)))
+		{
+			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
+				"Invalid TKL deletion request from $client: "
+				"Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'",
+				log_data_string("spamfilter_string", match_string),
+				log_data_string("spamfilter_targets", parv[3]));
+			return;
+		}
+
+		if (!(action = banact_chartoval(*parv[4])))
+		{
+			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
+				"Invalid TKL deletion request from $client: "
+				"Spamfilter '$spamfilter_string' has unknown 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);
+	} else
+	{
+		/* This can never happen, unless someone added a TKL type
+		 * to UnrealIRCd but forgot to add the removal code :D.
+		 */
+		abort();
+	}
+
+	if (!tkl)
+		return; /* Item not found, nothing to remove. */
+
+	if (tkl->flags & TKL_FLAG_CONFIG)
+		return; /* Item is in the configuration file (persistent) */
+
+	/* broadcast remove msg to opers... */
+	sendnotice_tkl_del(removed_by, tkl);
+
+	if (type & TKL_SHUN)
+		tkl_check_local_remove_shun(tkl);
+
+	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
+
+	if (type & TKL_GLOBAL)
+	{
+		/* This is a bit of a hack for #5629. Will consider real fix post-release. */
+		safe_strdup(tkl->set_by, removed_by);
+		tkl_broadcast_entry(0, client, client, tkl);
+	}
+
+	if (TKLIsBanException(tkl))
+	{
+		/* Since an exception has been removed we have to re-check if
+		 * any connected user is now matched by a ban.
+		 * Set flag here, actual checking takes place in main loop.
+		 */
+		loop.do_bancheck = 1;
+	}
+
+	tkl_del_line(tkl);
+}
+
+/** TKL command: server to server handling of *LINEs and SPAMFILTERs.
+ * HISTORY:
+ * This was originall called Timed KLines, but today it's
+ * used by various *line types eg: zline, gline, gzline, shun,
+ * but also by spamfilter etc...
+ * DOCUMENTATION
+ * See (also) https://www.unrealircd.org/docs/Server_protocol:TKL_command
+ * USAGE:
+ * This routine is used both internally by the ircd (to
+ * for example add local klines, zlines, etc) and over the
+ * network (glines, gzlines, spamfilter, etc).
+ *
+ *           serverban  serverban  spamfilter      spamfilter         sqline:    ban exception:
+ *           add:       remove:    remove in U4:   with TKLEXT2:
+ * parv[ 1]: +          -          -               +/-                +/-        +/-
+ * parv[ 2]: type       type       type            type               type       type
+ * parv[ 3]: user       user       target          target             hold       user
+ * parv[ 4]: host       host       action          action             host       host
+ * parv[ 5]: set_by     removedby  (un)set_by      set_by/unset_by    set_by     set_by
+ * parv[ 6]: expire_at             expire_at (0)   expire_at (0)      expire_at  expire_at
+ * parv[ 7]: set_at                set_at          set_at             set_at     set_at
+ * parv[ 8]: reason                regex           tkl duration       reason     except_type
+ * parv[ 9]:                                       tkl reason [A]                reason
+ * parv[10]:                                       match-type [B]
+ * parv[11]:                                       match-string [C]
+ *
+ * [A] tkl reason field must be escaped by caller [eg: use unreal_encodespace()
+ *     if cmd_tkl is called internally].
+ * [B] match-type must be one of: regex, simple.
+ * [C] Could be a regex or a regular string with wildcards, depending on [B]
+ */
+CMD_FUNC(_cmd_tkl)
+{
+	if (!IsServer(client) && !IsOper(client) && !IsMe(client))
+		return;
+
+	if (parc < 2)
+		return;
+
+	switch (*parv[1])
+	{
+		case '+':
+			CALL_CMD_FUNC(cmd_tkl_add);
+			break;
+		case '-':
+			CALL_CMD_FUNC(cmd_tkl_del);
+			break;
+		default:
+			break;
+	}
+}
+
+/** 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, const char **tkl_username, const char **tkl_hostname)
+{
+	static char username[USERLEN+1];
+	static char hostname[HOSTLEN+8];
+
+	if ((action == BAN_ACT_ZLINE) || (action == BAN_ACT_GZLINE))
+		ban_target = BAN_TARGET_IP; /* The only possible choice with ZLINE/GZLINE, other info is unavailable */
+
+	if (ban_target == BAN_TARGET_ACCOUNT)
+	{
+		if (IsLoggedIn(client) && (*client->user->account != ':'))
+		{
+			/* Place a ban on ~a:Accountname */
+			strlcpy(username, "~a:", sizeof(username));
+			strlcpy(hostname, client->user->account, sizeof(hostname));
+			*tkl_username = username;
+			*tkl_hostname = hostname;
+			return;
+		}
+		ban_target = BAN_TARGET_IP; /* fallback */
+	} else
+	if (ban_target == BAN_TARGET_CERTFP)
+	{
+		const char *fp = moddata_client_get(client, "certfp");
+		if (fp)
+		{
+			/* Place a ban on ~S:sha256sumofclientcertificate */
+			strlcpy(username, "~S:", sizeof(username));
+			strlcpy(hostname, fp, sizeof(hostname));
+			*tkl_username = username;
+			*tkl_hostname = hostname;
+			return;
+		}
+		ban_target = BAN_TARGET_IP; /* fallback */
+	}
+
+	/* Below we deal with the more common choices... */
+
+	/* First, set the username */
+	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));
+
+	/* Now set the host-portion of the TKL */
+	if (((ban_target == BAN_TARGET_HOST) || (ban_target == BAN_TARGET_USERHOST)) && client->user && *client->user->realhost)
+		strlcpy(hostname, client->user->realhost, sizeof(hostname));
+	else
+		strlcpy(hostname, GetIP(client), sizeof(hostname));
+
+	*tkl_username = username;
+	*tkl_hostname = hostname;
+}
+
+/** Take an action on the user, such as banning or killing.
+ * @author Bram Matthys (Syzop), 2003-present
+ * @param client     The client which is affected.
+ * @param action   The type of ban (one of BAN_ACT_*).
+ * @param reason   The ban reason.
+ * @param duration The ban duration in seconds.
+ * @note This function assumes that client is a locally connected user.
+ * @retval 1 if action is taken, 0 if user is exempted.
+ * @note Be sure to check IsDead(client) if return value is 1 and you are
+ *       considering to continue processing.
+ */
+int _place_host_ban(Client *client, BanAction action, char *reason, long duration)
+{
+	/* If this is a soft action and the user is logged in, then the ban does not apply.
+	 * NOTE: Actually in such a case it would be better if place_host_ban() would not
+	 * be called at all. Or at least, the caller should not take any action
+	 * (eg: the message should be delivered, the user may connect, etc..)
+	 * The following is more like secondary protection in case the caller forgets...
+	 */
+	if (IsSoftBanAction(action) && IsLoggedIn(client))
+		return 0;
+
+	switch(action)
+	{
+		case BAN_ACT_TEMPSHUN:
+			/* We simply mark this connection as shunned and do not add a ban record */
+			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:
+		case BAN_ACT_GLINE:
+		case BAN_ACT_SOFT_GLINE:
+		case BAN_ACT_ZLINE:
+		case BAN_ACT_KLINE:
+		case BAN_ACT_SOFT_KLINE:
+		case BAN_ACT_SHUN:
+		case BAN_ACT_SOFT_SHUN:
+		{
+			char ip[128], user[USERLEN+3], mo[100], mo2[100];
+			const char *tkllayer[9] = {
+				me.name,	/*0  server.name */
+				"+",		/*1  +|- */
+				"?",		/*2  type */
+				"*",		/*3  user */
+				NULL,		/*4  host */
+				NULL,
+				NULL,		/*6  expire_at */
+				NULL,		/*7  set_at */
+				NULL		/*8  reason */
+			};
+
+			ban_target_to_tkl_layer(iConf.automatic_ban_target, action, client, &tkllayer[3], &tkllayer[4]);
+
+			/* For soft bans we need to prefix the % in the username */
+			if (IsSoftBanAction(action))
+			{
+				char tmp[USERLEN+3];
+				snprintf(tmp, sizeof(tmp), "%%%s", tkllayer[3]);
+				strlcpy(user, tmp, sizeof(user));
+				tkllayer[3] = user;
+			}
+
+			if ((action == BAN_ACT_KLINE) || (action == BAN_ACT_SOFT_KLINE))
+				tkllayer[2] = "k";
+			else if (action == BAN_ACT_ZLINE)
+				tkllayer[2] = "z";
+			else if (action == BAN_ACT_GZLINE)
+				tkllayer[2] = "Z";
+			else if ((action == BAN_ACT_GLINE) || (action == BAN_ACT_SOFT_GLINE))
+				tkllayer[2] = "G";
+			else if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
+				tkllayer[2] = "s";
+			tkllayer[5] = me.name;
+			if (!duration)
+				strlcpy(mo, "0", sizeof(mo)); /* perm */
+			else
+				ircsnprintf(mo, sizeof(mo), "%lld", (long long)(duration + TStime()));
+			ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
+			tkllayer[6] = mo;
+			tkllayer[7] = mo2;
+			tkllayer[8] = reason;
+			cmd_tkl(&me, NULL, 9, tkllayer);
+			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
+			if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
+			{
+				find_shun(client);
+				return 1;
+			} /* else.. */
+			return find_tkline_match(client, 0);
+		}
+		case BAN_ACT_SOFT_KILL:
+		case BAN_ACT_KILL:
+		default:
+			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
+			exit_client(client, NULL, reason);
+			return 1;
+	}
+	return 0; /* no action taken (weird) */
+}
+
+/** This function compares two spamfilters ('one' and 'two') and will return
+ * a 'winner' based on which one has the strongest action.
+ * If both have equal action then some additional logic is applied simply
+ * to ensure we (almost) always return the same winner regardless of the
+ * order of the spamfilters (which may differ between servers).
+ */
+TKL *choose_winning_spamfilter(TKL *one, TKL *two)
+{
+	int n;
+
+	if (!TKLIsSpamfilter(one) || !TKLIsSpamfilter(two))
+		abort();
+
+	/* First, see if the action field differs... */
+	if (one->ptr.spamfilter->action != two->ptr.spamfilter->action)
+	{
+		/* We can simply compare the action. Highest (strongest) wins. */
+		if (one->ptr.spamfilter->action > two->ptr.spamfilter->action)
+			return one;
+		else
+			return two;
+	}
+
+	/* Ok, try comparing the regex then.. */
+	n = strcmp(one->ptr.spamfilter->match->str, two->ptr.spamfilter->match->str);
+	if (n < 0)
+		return one;
+	if (n > 0)
+		return two;
+
+	/* Hmm.. regex is identical. Try the 'reason' field. */
+	n = strcmp(one->ptr.spamfilter->tkl_reason, two->ptr.spamfilter->tkl_reason);
+	if (n < 0)
+		return one;
+	if (n > 0)
+		return two;
+
+	/* Hmm.. 'reason' is identical as well.
+	 * Make a final decision, could still be identical but would be unlikely.
+	 */
+	return (one->ptr.spamfilter->target > two->ptr.spamfilter->target) ? one : two;
+}
+
+/** Checks if 'target' is on the spamfilter exception list.
+ * RETURNS 1 if found in list, 0 if not.
+ */
+static int target_is_spamexcept(const char *target)
+{
+	SpamExcept *e;
+
+	for (e = iConf.spamexcept; e; e = e->next)
+	{
+		if (match_simple(e->name, target))
+			return 1;
+	}
+	return 0;
+}
+
+/** Make user join the virus channel.
+ * @param client  The user that was doing something bad.
+ * @param tk    The TKL entry that matched this user.
+ * @param type  The spamfilter type (SPAMF_*)
+ *              TODO: Looks redundant?
+ */
+int _join_viruschan(Client *client, TKL *tkl, int type)
+{
+	const char *xparv[3];
+	char chbuf[CHANNELLEN + 16], buf[2048];
+	Channel *channel;
+	int ret;
+
+	snprintf(buf, sizeof(buf), "0,%s", SPAMFILTER_VIRUSCHAN);
+	xparv[0] = NULL;
+	xparv[1] = buf;
+	xparv[2] = NULL;
+
+	/* RECURSIVE CAUTION in case we ever add blacklisted chans */
+	spamf_ugly_vchanoverride = 1;
+	do_cmd(client, NULL, "JOIN", 2, xparv);
+	spamf_ugly_vchanoverride = 0;
+
+	if (IsDead(client))
+		return 0; /* killed due to JOIN */
+
+	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);
+	if (channel)
+	{
+		MessageTag *mtags = NULL;
+		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, "o",
+		               0, SEND_ALL|SKIP_DEAF, mtags,
+		               ":%s NOTICE %s :%s", me.name, chbuf, buf);
+		free_message_tags(mtags);
+	}
+	SetVirus(client);
+	return 1;
+}
+
+/** match_spamfilter: executes the spamfilter on the input string.
+ * @param str		The text (eg msg text, notice text, part text, quit text, etc
+ * @param target	The spamfilter target (SPAMF_*)
+ * @param cmd		The command (eg: "PRIVMSG")
+ * @param destination	The destination as a text string (eg: "somenick", can be NULL.. eg for away)
+ * @param flags		Any flags (SPAMFLAG_*)
+ * @param rettkl	Pointer to an aTKLline struct, _used for special circumstances only_
+ * RETURN VALUE:
+ * 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, const char *str_in, int target, const char *cmd, const char *destination, int flags, TKL **rettkl)
+{
+	TKL *tkl;
+	TKL *winner_tkl = NULL;
+	const char *str;
+	int ret = -1;
+	char *reason = NULL;
+#ifdef SPAMFILTER_DETECTSLOW
+	struct rusage rnow, rprev;
+	long ms_past;
+#endif
+
+	if (rettkl)
+		*rettkl = NULL; /* initialize to NULL */
+
+	if (!cmd)
+		cmd = cmdname_by_spamftarget(target);
+
+	if (target == SPAMF_USER)
+		str = str_in;
+	else
+		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.
+	 */
+	if (!client->user || ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL) || IsULine(client))
+		return 0;
+
+	/* Client exempt from spamfilter checking?
+	 * Let's check that early: going through elines is likely faster than running the regex(es).
+	 */
+	if (find_tkl_exception(TKL_SPAMF, client))
+		return 0;
+
+	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
+	{
+		if (!(tkl->ptr.spamfilter->target & target))
+			continue;
+
+		if ((flags & SPAMFLAG_NOWARN) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN))
+			continue;
+
+		/* If the action is 'soft' (for non-logged in users only) then
+		 * don't bother running the spamfilter if the user is logged in.
+		 */
+		if (IsSoftBanAction(tkl->ptr.spamfilter->action) && IsLoggedIn(client))
+			continue;
+
+#ifdef SPAMFILTER_DETECTSLOW
+		memset(&rnow, 0, sizeof(rnow));
+		memset(&rprev, 0, sizeof(rnow));
+
+		getrusage(RUSAGE_SELF, &rprev);
+#endif
+
+		ret = unreal_match(tkl->ptr.spamfilter->match, str);
+
+#ifdef SPAMFILTER_DETECTSLOW
+		getrusage(RUSAGE_SELF, &rnow);
+
+		ms_past = ((rnow.ru_utime.tv_sec - rprev.ru_utime.tv_sec) * 1000) +
+		          ((rnow.ru_utime.tv_usec - rprev.ru_utime.tv_usec) / 1000);
+
+		if ((SPAMFILTER_DETECTSLOW_FATAL > 0) && (ms_past > SPAMFILTER_DETECTSLOW_FATAL))
+		{
+			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))
+		{
+			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! But.. perhaps it's on the exceptions list? */
+			if (!winner_tkl && destination && target_is_spamexcept(destination))
+				return 0; /* No problem! */
+
+			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
+			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
+				   log_data_tkl("tkl", tkl),
+				   log_data_string("command", cmd),
+				   log_data_string("_space", destination ? " " : ""),
+				   log_data_string("destination", destination ? destination : ""),
+				   log_data_string("str", str));
+
+			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)
+			{
+				winner_tkl = tkl;
+				break;
+			}
+
+			/* Otherwise.. we set 'winner_tkl' to the spamfilter with the strongest action. */
+			if (!winner_tkl)
+				winner_tkl = tkl;
+			else
+				winner_tkl = choose_winning_spamfilter(tkl, winner_tkl);
+
+			/* and continue.. */
+		}
+	}
+
+	tkl = winner_tkl;
+
+	if (!tkl)
+		return 0; /* NOMATCH, we are done */
+
+	/* Spamfilter matched, take action: */
+
+	reason = unreal_decodespace(tkl->ptr.spamfilter->tkl_reason);
+	if ((tkl->ptr.spamfilter->action == BAN_ACT_BLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_BLOCK))
+	{
+		switch(target)
+		{
+			case SPAMF_USERMSG:
+			case SPAMF_USERNOTICE:
+			{
+				char errmsg[512];
+				ircsnprintf(errmsg, sizeof(errmsg), "Message blocked: %s", reason);
+				sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg);
+				break;
+			}
+			case SPAMF_CHANNOTICE:
+				break; /* no replies to notices */
+			case SPAMF_CHANMSG:
+			{
+				sendto_one(client, NULL, ":%s 404 %s %s :Message blocked: %s",
+					me.name, client->name, destination, reason);
+				break;
+			}
+			case SPAMF_MTAG:
+			{
+				sendnumericfmt(client, ERR_CANNOTDOCOMMAND, "%s :Command blocked: %s",
+					cmd, reason);
+				break;
+			}
+			case SPAMF_DCC:
+			{
+				char errmsg[512];
+				ircsnprintf(errmsg, sizeof(errmsg), "DCC blocked: %s", reason);
+				sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg);
+				break;
+			}
+			case SPAMF_AWAY:
+				/* hack to deal with 'after-away-was-set-filters' */
+				if (client->user->away && !strcmp(str_in, client->user->away))
+				{
+					/* free away & broadcast the unset */
+					safe_free(client->user->away);
+					client->user->away = NULL;
+					sendto_server(client, 0, 0, NULL, ":%s AWAY", client->id);
+				}
+				break;
+			case SPAMF_TOPIC:
+				//...
+				sendnotice(client, "Setting of topic on %s to that text is blocked: %s",
+					destination, reason);
+				break;
+			default:
+				break;
+		}
+		return 1;
+	} else
+	if ((tkl->ptr.spamfilter->action == BAN_ACT_WARN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_WARN))
+	{
+		if ((target != SPAMF_USER) && (target != SPAMF_QUIT))
+			sendnumeric(client, RPL_SPAMCMDFWD, cmd, reason);
+		return 0;
+	} else
+	if ((tkl->ptr.spamfilter->action == BAN_ACT_DCCBLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_DCCBLOCK))
+	{
+		if (target == SPAMF_DCC)
+		{
+			sendnotice(client, "DCC to %s blocked: %s", destination, reason);
+			sendnotice(client, "*** You have been blocked from sending files, reconnect to regain permission to send files");
+			SetDCCBlock(client);
+		}
+		return 1;
+	} else
+	if ((tkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN))
+	{
+		if (IsVirus(client)) /* Already tagged */
+			return 0;
+
+		/* There's a race condition for SPAMF_USER, so 'rettk' is used for SPAMF_USER
+		 * when a user is currently connecting and filters are checked:
+		 */
+		if (!IsUser(client))
+		{
+			if (rettkl)
+				*rettkl = tkl;
+			return 1;
+		}
+
+		join_viruschan(client, tkl, target);
+		return 1;
+	} else
+	{
+		return place_host_ban(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration);
+	}
+
+	return 0; /* NOTREACHED */
+}
+
+/** Check message-tag spamfilters.
+ * @param client	The client
+ * @param mtags		Message tags sent by client
+ * @param cmd		Command to be executed (can be NULL)
+ * @retval Return 1 to stop processing the command (ignore it) or 0 to allow/continue as normal
+ */
+int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd)
+{
+	MessageTag *m;
+	char buf[4096];
+	char *str;
+
+	/* This is a shortcut: if there are no spamfilters present
+	 * on message tags then we can return immediately.
+	 * Saves a lot of CPU and it is quite likely too!
+	 */
+	if (mtag_spamfilters_present == 0)
+		return 0;
+
+	for (m = mtags; m; m = m->next)
+	{
+		if (m->value)
+		{
+			snprintf(buf, sizeof(buf), "%s=%s", m->name, m->value);
+			str = buf;
+		} else {
+			str = m->name;
+		}
+		if (match_spamfilter(client, str, SPAMF_MTAG, cmd, NULL, 0, NULL))
+			return 1;
+	}
+	return 0;
+}
+
+/** Updates 'mtag_spamfilters_present' based on if any spamfilters
+ * are present with the SPAMF_MTAG target.
+ */
+int check_mtag_spamfilters_present(void)
+{
+	TKL *tkl;
+
+	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
+	{
+		if (tkl->ptr.spamfilter->target & SPAMF_MTAG)
+		{
+			mtag_spamfilters_present = 1;
+			return 1;
+		}
+	}
+
+	mtag_spamfilters_present = 0;
+	return 0;
+}
+
+/** CIDR function to compare the first 'mask' bits.
+ * @author Taken from atheme
+ * @returns 1 if equal, 0 if not.
+ */
+static int comp_with_mask(void *addr, void *dest, u_int mask)
+{
+	if (memcmp(addr, dest, mask / 8) == 0)
+	{
+		int n = mask / 8;
+		int m = (0xffff << (8 - (mask % 8)));
+		if (mask % 8 == 0 || (((u_char *) addr)[n] & m) == (((u_char *) dest)[n] & m))
+		{
+			return (1);
+		}
+	}
+	return (0);
+}
+
+#define IPSZ 16
+
+/** Match a user against a mask.
+ * This will deal with 'nick!user@host', 'user@host' and just 'host'.
+ * We try to match the 'host' portion against the client IP, real host, etc...
+ * 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(const char *rmask, Client *client, int options)
+{
+	char mask[NICKLEN+USERLEN+HOSTLEN+8];
+	char clientip[IPSZ], maskip[IPSZ];
+	char *p = NULL;
+	char *nmask = NULL, *umask = NULL, *hmask = NULL;
+	int cidr = -1; /* CIDR length, -1 for no CIDR */
+
+	strlcpy(mask, rmask, sizeof(mask));
+
+	if ((options & MATCH_CHECK_EXTENDED) &&
+	    is_extended_server_ban(mask) &&
+	    client->user)
+	{
+		/* Check user properties / extbans style */
+		return _match_user_extended_server_ban(rmask, client);
+	}
+
+	if (!(options & MATCH_MASK_IS_UHOST))
+	{
+		p = strchr(mask, '!');
+		if (p)
+		{
+			*p++ = '\0';
+			if (!*mask)
+				return 0; /* NOMATCH: '!...' */
+			nmask = mask;
+			umask = p;
+
+			/* Could just as well check nick right now */
+			if (!match_simple(nmask, client->name))
+				return 0; /* NOMATCH: nick mask did not match */
+		}
+	}
+
+	if (!(options & (MATCH_MASK_IS_HOST)))
+	{
+		p = strchr(p ? p : mask, '@');
+		if (p)
+		{
+			char *client_username = (client->user && *client->user->username) ? client->user->username : client->ident;
+
+			*p++ = '\0';
+			if (!*p || !*mask)
+				return 0; /* NOMATCH: '...@' or '@...' */
+			hmask = p;
+			if (!umask)
+				umask = mask;
+
+			/* Check user portion right away */
+			if (!match_simple(umask, client_username))
+				return 0; /* NOMATCH: user mask did not match */
+		} else {
+			if (nmask)
+				return 0; /* NOMATCH: 'abc!def' (or even just 'abc!') */
+			hmask = mask;
+		}
+	} else {
+		hmask = mask;
+	}
+
+	/* If we get here then we have done checking nick / ident (if it was needed)
+	 * and now need to match the 'host' portion.
+	 */
+
+	/**** Check visible host ****/
+	if (options & MATCH_CHECK_VISIBLE_HOST)
+	{
+		char *hostname = client->user ? GetHost(client) : (MyUser(client) ? client->local->sockhost : NULL);
+		if (hostname && match_simple(hmask, hostname))
+			return 1; /* MATCH: visible host */
+	}
+
+	/**** Check cloaked host ****/
+	if (options & MATCH_CHECK_CLOAKED_HOST)
+	{
+		if (client->user && match_simple(hmask, client->user->cloakedhost))
+			return 1; /* MATCH: cloaked host */
+	}
+
+	/**** check on IP ****/
+	if (options & MATCH_CHECK_IP)
+	{
+		p = strchr(hmask, '/');
+		if (p)
+		{
+			*p++ = '\0';
+			cidr = atoi(p);
+			if (cidr <= 0)
+				return 0; /* NOMATCH: invalid CIDR */
+		}
+
+		if (strchr(hmask, '?') || strchr(hmask, '*'))
+		{
+			/* Wildcards */
+			if (client->ip && match_simple(hmask, client->ip))
+				return 1; /* MATCH (IP with wildcards) */
+		} else
+		if (strchr(hmask, ':'))
+		{
+			/* IPv6 hostmask */
+
+			/* We can actually return here on match/nomatch as we don't need to check the
+			 * virtual host and things like that since ':' can never be in a hostname.
+			 */
+			if (!client->ip || !strchr(client->ip, ':'))
+				return 0; /* NOMATCH: hmask is IPv6 address and client is not IPv6 */
+			if (!inet_pton(AF_INET6, client->ip, clientip))
+				return 0; /* NOMATCH: unusual failure */
+			if (!inet_pton(AF_INET6, hmask, maskip))
+				return 0; /* NOMATCH: invalid IPv6 IP in hostmask */
+
+			if (cidr < 0)
+				return comp_with_mask(clientip, maskip, 128); /* MATCH/NOMATCH by exact IP */
+
+			if (cidr > 128)
+				return 0; /* NOMATCH: invalid CIDR */
+
+			return comp_with_mask(clientip, maskip, cidr);
+		} else
+		{
+			/* Host is not IPv6 and does not contain wildcards.
+			 * So could be a literal IPv4 address or IPv4 CIDR.
+			 * NOTE: could also be neither (like a real hostname), so don't return 0 on nomatch,
+			 * in that case we should just continue...
+			 * The exception is CIDR. If we have CIDR mask then don't bother checking for
+			 * virtual hosts and things like that since '/' can never be in a hostname.
+			 */
+			if (client->ip && inet_pton(AF_INET, client->ip, clientip) && inet_pton(AF_INET, hmask, maskip))
+			{
+				if (cidr < 0)
+				{
+					if (comp_with_mask(clientip, maskip, 32))
+						return 1; /* MATCH: exact IP */
+				}
+				else if (cidr > 32)
+					return 0; /* NOMATCH: invalid CIDR */
+				else
+					return comp_with_mask(clientip, maskip, cidr); /* MATCH/NOMATCH by CIDR */
+			}
+		}
+	}
+
+	/**** Check real host ****/
+	if (options & MATCH_CHECK_REAL_HOST)
+	{
+		char *hostname = client->user ? client->user->realhost : (MyUser(client) ? client->local->sockhost : NULL);
+		if (hostname && match_simple(hmask, hostname))
+			return 1; /* MATCH: hostname match */
+	}
+
+	return 0; /* NOMATCH: nothing of the above matched */
+}
+
+/** Returns 1 if the user is allowed by any of the security groups in the named list.
+ * This is only used by security-group::security-group and
+ * security-group::exclude-security-group.
+ * @param client	Client to check
+ * @param l		The NameList
+ * @returns 1 if any of the security groups match, 0 if none of them matched.
+ */
+int _unreal_match_iplist(Client *client, NameList *l)
+{
+	char client_ipv6 = 0;
+	char clientip[IPSZ], maskip[IPSZ];
+
+	if (!client->ip)
+		return 0; /* unusual, maybe services? */
+
+	if (strchr(client->ip, ':'))
+	{
+		client_ipv6 = 1;
+		if (!inet_pton(AF_INET6, client->ip, clientip))
+			return 0; /* unusual failure */
+	} else {
+		if (!inet_pton(AF_INET, client->ip, clientip))
+			return 0; /* unusual failure */
+	}
+
+	for (; l; l = l->next)
+	{
+		char mask[512], *p;
+		int cidr = -1; /* CIDR length, -1 for no CIDR */
+
+		strlcpy(mask, l->name, sizeof(mask));
+		p = strchr(mask, '/');
+		if (p)
+		{
+			*p++ = '\0';
+			cidr = atoi(p);
+			if (cidr <= 0)
+				return 0; /* NOMATCH: invalid CIDR */
+		}
+
+		/* Three possible types: wildcard, ipv6, ipv4 */
+
+		if (strchr(mask, '*') || strchr(mask, '?'))
+		{
+			/* Wildcards */
+			if (match_simple(mask, client->ip))
+				return 1; /* MATCH by wildcard IP */
+		}
+		else if (strchr(mask, ':'))
+		{
+			/* IPv6 */
+			if (!client_ipv6)
+				continue; /* NOMATCH: client is IPv4 */
+			if (!inet_pton(AF_INET6, mask, maskip))
+				continue; /* NOMATCH: invalid IPv6 IP in mask */
+			if (cidr < 0)
+			{
+				/* Try to match by exact IP */
+				if (comp_with_mask(clientip, maskip, 128))
+					return 1; /* MATCH by exact IP */
+			} else
+			if (cidr > 128)
+			{
+				continue; /* NOMATCH: invalid CIDR */
+			} else
+			if (comp_with_mask(clientip, maskip, cidr))
+			{
+				return 1; /* MATCH by CIDR */
+			}
+		} else
+		{
+			/* IPv4 */
+			if (client_ipv6)
+				continue; /* NOMATCH: client is IPv6 */
+			if (!inet_pton(AF_INET, mask, maskip))
+				continue; /* NOMATCH: invalid IPv6 IP in mask */
+			if (cidr < 0)
+			{
+				/* Try to match by exact IP */
+				if (comp_with_mask(clientip, maskip, 32))
+					return 1; /* MATCH: by exact IP */
+			} else
+			if (cidr > 32)
+			{
+				continue; /* NOMATCH: invalid CIDR */
+			} else
+			if (comp_with_mask(clientip, maskip, cidr))
+			{
+				return 1; /* MATCH by CIDR */
+			}
+		}
+	}
+	return 0;
+}
+
+
+int _match_user_extended_server_ban(const char *banstr, Client *client)
+{
+	const char *nextbanstr;
+	Extban *extban;
+	BanContext *b;
+	int ret;
+
+	if (!is_extended_server_ban(banstr))
+		return 0; /* we should never have been called */
+
+	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) */
+	}
+
+	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/ircd/src/modules/tkldb.c b/ircd/src/modules/tkldb.c
@@ -0,0 +1,761 @@
+/*
+ * Stores active *-Lines (G-Lines etc) inside a .db file for persistency
+ * (C) Copyright 2019 Gottem 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 = {
+	"tkldb",
+	"1.10",
+	"Stores active TKL entries (*-Lines) persistently/across IRCd restarts",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+#define TKLDB_MAGIC 0x10101010
+/* Database version */
+#define TKLDB_VERSION 4999
+/* Save tkls to file every <this> seconds */
+#define TKLDB_SAVE_EVERY 300
+/* The very first save after boot, apply this delta, this
+ * so we don't coincide with other (potentially) expensive
+ * I/O events like saving channeldb.
+ */
+#define TKLDB_SAVE_EVERY_DELTA +15
+
+// #undef BENCHMARK
+/* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
+ * 100,000 zlines:
+ * - load db: 510 ms
+ * - save db:  72 ms
+ * Thus, saving does not take much time and can be done by a timer
+ * which executes every 5 minutes.
+ * Of course, exact figures will depend on the machine.
+ */
+
+#define FreeTKLRead() \
+ 	do { \
+		/* Some of these might be NULL */ \
+		if (tkl) \
+			free_tkl(tkl); \
+	} while(0)
+
+#define WARN_WRITE_ERROR(fname) \
+	do { \
+		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) \
+	do { \
+		if (!(x)) { \
+			config_warn("[tkldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+			unrealdb_close(db); \
+			FreeTKLRead(); \
+			return 0; \
+		} \
+	} while(0)
+
+#define W_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+#define IsMDErr(x, y, z) \
+	do { \
+		if (!(x)) { \
+			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
+			return MOD_FAILED; \
+		} \
+	} while(0)
+
+/* Structs */
+struct cfgstruct {
+	char *database;
+	char *db_secret;
+};
+
+/* Forward declarations */
+void tkldb_moddata_free(ModData *md);
+void setcfg(struct cfgstruct *cfg);
+void freecfg(struct cfgstruct *cfg);
+int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int tkldb_config_posttest(int *errs);
+int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(write_tkldb_evt);
+int write_tkldb(void);
+int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl);
+int read_tkldb(void);
+
+/* Globals variables */
+const uint32_t tkldb_version = TKLDB_VERSION;
+static struct cfgstruct cfg;
+static struct cfgstruct test;
+
+static long tkldb_next_event = 0;
+
+MOD_TEST()
+{
+	memset(&cfg, 0, sizeof(cfg));
+	memset(&test, 0, sizeof(test));
+	setcfg(&test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkldb_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, tkldb_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -9999);
+
+	LoadPersistentLong(modinfo, tkldb_next_event);
+
+	setcfg(&cfg);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkldb_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (!tkldb_next_event)
+	{
+		/* If this is the first time that our module is loaded, then
+		 * read the TKL DB and add all *-Lines.
+		 */
+		if (!read_tkldb())
+		{
+			char fname[512];
+			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
+			if (rename(cfg.database, fname) == 0)
+				config_warn("[tkldb] Existing database renamed to %s and starting a new one...", fname);
+			else
+				config_warn("[tkldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
+		}
+		tkldb_next_event = TStime() + TKLDB_SAVE_EVERY + TKLDB_SAVE_EVERY_DELTA;
+	}
+	EventAdd(modinfo->handle, "tkldb_write_tkldb", write_tkldb_evt, NULL, 1000, 0);
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (loop.terminating)
+		write_tkldb();
+	freecfg(&test);
+	freecfg(&cfg);
+	SavePersistentLong(modinfo, tkldb_next_event);
+	return MOD_SUCCESS;
+}
+
+void tkldb_moddata_free(ModData *md)
+{
+	if (md->i)
+		md->i = 0;
+}
+
+void setcfg(struct cfgstruct *cfg)
+{
+	// Default: data/tkl.db
+	safe_strdup(cfg->database, "tkl.db");
+	convert_to_absolute_path(&cfg->database, PERMDATADIR);
+}
+
+void freecfg(struct cfgstruct *cfg)
+{
+	safe_free(cfg->database);
+	safe_free(cfg->db_secret);
+}
+
+int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	// We are only interested in set::tkldb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "tkldb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: blank set::tkldb::%s without value", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		} else
+		if (!strcmp(cep->name, "database"))
+		{
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
+		} else
+		if (!strcmp(cep->name, "db-secret"))
+		{
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
+			{
+				config_error("%s:%i: set::tkldb::db-secret: %s", cep->file->filename, cep->line_number, err);
+				errors++;
+				continue;
+			}
+			safe_strdup(test.db_secret, cep->value);
+		} else
+		{
+			config_error("%s:%i: unknown directive set::tkldb::%s", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int tkldb_config_posttest(int *errs)
+{
+	int errors = 0;
+	char *errstr;
+
+	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+	{
+		config_error("[tkldb] %s", errstr);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	// We are only interested in set::tkldb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "tkldb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database"))
+			safe_strdup(cfg.database, cep->value);
+		else if (!strcmp(cep->name, "db-secret"))
+			safe_strdup(cfg.db_secret, cep->value);
+	}
+	return 1;
+}
+
+EVENT(write_tkldb_evt)
+{
+	if (tkldb_next_event > TStime())
+		return;
+	tkldb_next_event = TStime() + TKLDB_SAVE_EVERY;
+	write_tkldb();
+}
+
+int write_tkldb(void)
+{
+	char tmpfname[512];
+	UnrealDB *db;
+	uint64_t tklcount;
+	int index, index2;
+	TKL *tkl;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	// Write to a tempfile first, then rename it if everything succeeded
+	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+	if (!db)
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	W_SAFE(unrealdb_write_int32(db, TKLDB_MAGIC));
+	W_SAFE(unrealdb_write_int32(db, tkldb_version));
+
+	// Count the *-Lines
+	tklcount = 0;
+
+	// First the ones in the hash table
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				if (tkl->flags & TKL_FLAG_CONFIG)
+					continue; /* config entry */
+				tklcount++;
+			}
+		}
+	}
+	// Then the regular *-Lines
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (tkl->flags & TKL_FLAG_CONFIG)
+				continue; /* config entry */
+			tklcount++;
+		}
+	}
+	W_SAFE(unrealdb_write_int64(db, tklcount));
+
+	// Now write the actual *-Lines, first the ones in the hash table
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				if (tkl->flags & TKL_FLAG_CONFIG)
+					continue; /* config entry */
+				if (!write_tkline(db, tmpfname, tkl)) // write_tkline() closes the db on errors itself
+					return 0;
+			}
+		}
+	}
+	// Then the regular *-Lines
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (tkl->flags & TKL_FLAG_CONFIG)
+				continue; /* config entry */
+			if (!write_tkline(db, tmpfname, tkl))
+				return 0;
+		}
+	}
+
+	// Everything seems to have gone well, attempt to close and rename the tempfile
+	if (!unrealdb_close(db))
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(cfg.database);
+#endif
+	if (rename(tmpfname, cfg.database) < 0)
+	{
+		config_error("[tkldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
+		return 0;
+	}
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	config_status("[tkldb] Benchmark: SAVE DB: %lld microseconds",
+		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+#endif
+	return 1;
+}
+
+/** Write a TKL entry */
+int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl)
+{
+	char tkltype;
+	char buf[256];
+
+	/* First, write the common attributes */
+	tkltype = tkl_typetochar(tkl->type);
+	W_SAFE(unrealdb_write_char(db, tkltype)); // TKL char
+
+	W_SAFE(unrealdb_write_str(db, tkl->set_by));
+	W_SAFE(unrealdb_write_int64(db, tkl->set_at));
+	W_SAFE(unrealdb_write_int64(db, tkl->expire_at));
+
+	if (TKLIsServerBan(tkl))
+	{
+		char *usermask = tkl->ptr.serverban->usermask;
+		if (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)
+		{
+			snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.serverban->usermask);
+			usermask = buf;
+		}
+		W_SAFE(unrealdb_write_str(db, usermask));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->hostmask));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->reason));
+	} else
+	if (TKLIsBanException(tkl))
+	{
+		char *usermask = tkl->ptr.banexception->usermask;
+		if (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)
+		{
+			snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.banexception->usermask);
+			usermask = buf;
+		}
+		W_SAFE(unrealdb_write_str(db, usermask));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->hostmask));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->bantypes));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->reason));
+	} else
+	if (TKLIsNameBan(tkl))
+	{
+		char *hold = tkl->ptr.nameban->hold ? "H" : "*";
+		W_SAFE(unrealdb_write_str(db, hold));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->name));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->reason));
+	} else
+	if (TKLIsSpamfilter(tkl))
+	{
+		char *match_type = unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type);
+		char *target = spamfilter_target_inttostring(tkl->ptr.spamfilter->target);
+		char action = banact_valtochar(tkl->ptr.spamfilter->action);
+
+		W_SAFE(unrealdb_write_str(db, match_type));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->match->str));
+		W_SAFE(unrealdb_write_str(db, target));
+		W_SAFE(unrealdb_write_char(db, action));
+		W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->tkl_reason));
+		W_SAFE(unrealdb_write_int64(db, tkl->ptr.spamfilter->tkl_duration));
+	}
+
+	return 1;
+}
+
+/** Read all entries from the TKL db */
+int read_tkldb(void)
+{
+	UnrealDB *db;
+	TKL *tkl = NULL;
+	uint32_t magic = 0;
+	uint32_t version;
+	uint64_t cnt;
+	uint64_t tklcount = 0;
+	uint64_t v;
+	int added_cnt = 0;
+	char c;
+	char *str;
+
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+		{
+			/* Database does not exist. Could be first boot */
+			config_warn("[tkldb] No database present at '%s', will start a new one", cfg.database);
+			return 1;
+		} else
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+		{
+			/* Re-open as unencrypted */
+			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+			if (!db)
+			{
+				/* This should actually never happen, unless some weird I/O error */
+				config_warn("[tkldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
+				return 0;
+			}
+		} else
+		{
+			config_warn("[tkldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
+			return 0;
+		}
+	}
+
+	/* The database starts with a "magic value" - unless it's some old version or corrupt */
+	R_SAFE(unrealdb_read_int32(db, &magic));
+	if (magic != TKLDB_MAGIC)
+	{
+		config_warn("[tkldb] Database '%s' uses an old and unsupported format OR is corrupt", cfg.database);
+		config_status("If you are upgrading from UnrealIRCd 4 (or 5.0.0-alpha1) then we suggest you to "
+		              "delete the existing database. Just keep at least 1 server linked during the upgrade "
+		              "process to preserve your global *LINES and Spamfilters.");
+		unrealdb_close(db);
+		return 0;
+	}
+
+	/* Now do a version check */
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version < 4999)
+	{
+		config_warn("[tkldb] Database '%s' uses an unsupport - possibly old - format (%ld).", cfg.database, (long)version);
+		unrealdb_close(db);
+		return 0;
+	}
+	if (version > tkldb_version)
+	{
+		config_warn("[tkldb] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
+			cfg.database, (unsigned long)tkldb_version, (unsigned long)version);
+		unrealdb_close(db);
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_int64(db, &tklcount));
+
+	for (cnt = 0; cnt < tklcount; cnt++)
+	{
+		int do_not_add = 0;
+
+		tkl = safe_alloc(sizeof(TKL));
+
+		/* First, fetch the TKL type.. */
+		R_SAFE(unrealdb_read_char(db, &c));
+		tkl->type = tkl_chartotype(c);
+		if (!tkl->type)
+		{
+			/* We can't continue reading the DB if we don't know the TKL type,
+			 * since we don't know how long the entry will be, we can't skip it.
+			 * This is "impossible" anyway, unless we some day remove a TKL type
+			 * in core UnrealIRCd. In which case we should add some skipping code
+			 * here to gracefully handle that situation ;)
+			 */
+			config_warn("[tkldb] Invalid type '%c' encountered - STOPPED READING DATABASE!", tkl->type);
+			FreeTKLRead();
+			break; /* we MUST stop reading */
+		}
+
+		/* Read the common types (same for all TKLs) */
+		R_SAFE(unrealdb_read_str(db, &tkl->set_by));
+		R_SAFE(unrealdb_read_int64(db, &v));
+		tkl->set_at = v;
+		R_SAFE(unrealdb_read_int64(db, &v));
+		tkl->expire_at = v;
+
+		/* Save some CPU... if it's already expired then don't bother adding */
+		if (tkl->expire_at != 0 && tkl->expire_at <= TStime())
+			do_not_add = 1;
+
+		/* Now handle all the specific types */
+		if (TKLIsServerBan(tkl))
+		{
+			int softban = 0;
+
+			tkl->ptr.serverban = safe_alloc(sizeof(ServerBan));
+
+			/* Usermask - but taking into account that the
+			 * %-prefix means a soft ban.
+			 */
+			R_SAFE(unrealdb_read_str(db, &str));
+			if (*str == '%')
+			{
+				softban = 1;
+				safe_strdup(tkl->ptr.serverban->usermask, str+1);
+			} else {
+				safe_strdup(tkl->ptr.serverban->usermask, str);
+			}
+			safe_free(str);
+
+			/* And the other 2 fields.. */
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->hostmask));
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->reason));
+
+			if (find_tkl_serverban(tkl->type, tkl->ptr.serverban->usermask,
+			                       tkl->ptr.serverban->hostmask, softban))
+			{
+				do_not_add = 1;
+			}
+
+			if (!do_not_add)
+			{
+				tkl_add_serverban(tkl->type, tkl->ptr.serverban->usermask,
+				                  tkl->ptr.serverban->hostmask,
+				                  tkl->ptr.serverban->reason,
+				                  tkl->set_by, tkl->expire_at,
+				                  tkl->set_at, softban, 0);
+			}
+		} else
+		if (TKLIsBanException(tkl))
+		{
+			int softban = 0;
+
+			tkl->ptr.banexception = safe_alloc(sizeof(BanException));
+
+			/* Usermask - but taking into account that the
+			 * %-prefix means a soft ban.
+			 */
+			R_SAFE(unrealdb_read_str(db, &str));
+			if (*str == '%')
+			{
+				softban = 1;
+				safe_strdup(tkl->ptr.banexception->usermask, str+1);
+			} else {
+				safe_strdup(tkl->ptr.banexception->usermask, str);
+			}
+			safe_free(str);
+
+			/* And the other 3 fields.. */
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->hostmask));
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->bantypes));
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->reason));
+
+			if (find_tkl_banexception(tkl->type, tkl->ptr.banexception->usermask,
+			                          tkl->ptr.banexception->hostmask, softban))
+			{
+				do_not_add = 1;
+			}
+
+			if (!do_not_add)
+			{
+				tkl_add_banexception(tkl->type, tkl->ptr.banexception->usermask,
+				                     tkl->ptr.banexception->hostmask,
+				                     NULL,
+				                     tkl->ptr.banexception->reason,
+				                     tkl->set_by, tkl->expire_at,
+				                     tkl->set_at, softban,
+				                     tkl->ptr.banexception->bantypes,
+				                     0);
+			}
+		} else
+		if (TKLIsNameBan(tkl))
+		{
+			tkl->ptr.nameban = safe_alloc(sizeof(NameBan));
+
+			R_SAFE(unrealdb_read_str(db, &str));
+			if (*str == 'H')
+				tkl->ptr.nameban->hold = 1;
+			safe_free(str);
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->name));
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->reason));
+
+			if (find_tkl_nameban(tkl->type, tkl->ptr.nameban->name,
+			                     tkl->ptr.nameban->hold))
+			{
+				do_not_add = 1;
+			}
+
+			if (!do_not_add)
+			{
+				tkl_add_nameban(tkl->type, tkl->ptr.nameban->name,
+				                tkl->ptr.nameban->hold,
+				                tkl->ptr.nameban->reason,
+				                tkl->set_by, tkl->expire_at,
+				                tkl->set_at, 0);
+			}
+		} else
+		if (TKLIsSpamfilter(tkl))
+		{
+			int match_method;
+			char *err = NULL;
+
+			tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
+
+			/* Match method */
+			R_SAFE(unrealdb_read_str(db, &str));
+			match_method = unreal_match_method_strtoval(str);
+			if (!match_method)
+			{
+				config_warn("[tkldb] Unhandled spamfilter match method '%s' -- spamfilter entry not added", str);
+				do_not_add = 1;
+			}
+			safe_free(str);
+
+			/* Match string (eg: regex) */
+			R_SAFE(unrealdb_read_str(db, &str));
+			tkl->ptr.spamfilter->match = unreal_create_match(match_method, str, &err);
+			if (!tkl->ptr.spamfilter->match)
+			{
+				config_warn("[tkldb] Spamfilter '%s' does not compile: %s -- spamfilter entry not added", str, err);
+				do_not_add = 1;
+			}
+			safe_free(str);
+
+			/* Target (eg: cpn) */
+			R_SAFE(unrealdb_read_str(db, &str));
+			tkl->ptr.spamfilter->target = spamfilter_gettargets(str, NULL);
+			if (!tkl->ptr.spamfilter->target)
+			{
+				config_warn("[tkldb] Spamfilter '%s' without any valid targets (%s) -- spamfilter entry not added",
+					tkl->ptr.spamfilter->match->str, str);
+				do_not_add = 1;
+			}
+			safe_free(str);
+
+			/* Action */
+			R_SAFE(unrealdb_read_char(db, &c));
+			tkl->ptr.spamfilter->action = banact_chartoval(c);
+			if (!tkl->ptr.spamfilter->action)
+			{
+				config_warn("[tkldb] Spamfilter '%s' without valid action (%c) -- spamfilter entry not added",
+					tkl->ptr.spamfilter->match->str, c);
+				do_not_add = 1;
+			}
+
+			R_SAFE(unrealdb_read_str(db, &tkl->ptr.spamfilter->tkl_reason));
+			R_SAFE(unrealdb_read_int64(db, &v));
+			tkl->ptr.spamfilter->tkl_duration = v;
+
+			if (find_tkl_spamfilter(tkl->type, tkl->ptr.spamfilter->match->str,
+			                        tkl->ptr.spamfilter->action,
+			                        tkl->ptr.spamfilter->target))
+			{
+				do_not_add = 1;
+			}
+
+			if (!do_not_add)
+			{
+				tkl_add_spamfilter(tkl->type, tkl->ptr.spamfilter->target,
+				                   tkl->ptr.spamfilter->action,
+				                   tkl->ptr.spamfilter->match,
+				                   tkl->set_by, tkl->expire_at, tkl->set_at,
+				                   tkl->ptr.spamfilter->tkl_duration,
+				                   tkl->ptr.spamfilter->tkl_reason,
+				                   0);
+				/* tkl_add_spamfilter() does not copy the match but assign it.
+				 * so set to NULL here to avoid a read-after-free later on.
+				 */
+				tkl->ptr.spamfilter->match = NULL;
+			}
+		} else
+		{
+			config_warn("[tkldb] Unhandled type!! TKLDB is missing support for type %ld -- STOPPED reading db entries!", (long)tkl->type);
+			FreeTKLRead();
+			break; /* we MUST stop reading */
+		}
+
+		if (!do_not_add)
+			added_cnt++;
+
+		FreeTKLRead();
+	}
+
+	unrealdb_close(db);
+
+	if (added_cnt)
+		config_status("[tkldb] Re-added %d *-Lines", added_cnt);
+
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	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/ircd/src/modules/tline.c b/ircd/src/modules/tline.c
@@ -0,0 +1,87 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/tline.c
+ *   (C) 2022 Noisytoot & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+#define MSG_TLINE "TLINE"
+
+CMD_FUNC(cmd_tline);
+
+ModuleHeader MOD_HEADER
+  = {
+	"tline",
+	"1.0",
+	"TLINE command to show amount of clients matching a server ban mask",
+	"UnrealIRCd team",
+	"unrealircd-6",
+	};
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_TLINE, cmd_tline, 1, CMD_OPER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_tline
+**	parv[1] = mask to test
+*/
+CMD_FUNC(cmd_tline)
+{
+	Client *acptr;
+	int matching_lclients = 0;
+	int matching_clients = 0;
+
+	if ((parc < 1) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, MSG_TLINE);
+		return;
+	}
+
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (match_user(parv[1], acptr, MATCH_CHECK_REAL))
+		{
+			if (MyUser(acptr))
+				matching_lclients++;
+			matching_clients++;
+		}
+	}
+
+	sendnotice(client,
+	    "*** TLINE: Users matching mask '%s': global: %d/%d (%.2f%%), local: %d/%d (%.2f%%).",
+	    parv[1], matching_clients, irccounts.clients,
+	    (double)matching_clients / irccounts.clients * 100,
+	    matching_lclients, irccounts.me_clients,
+	    (double)matching_lclients / irccounts.me_clients * 100);
+}
diff --git a/ircd/src/modules/tls_antidos.c b/ircd/src/modules/tls_antidos.c
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ * 
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"tls_antidos",
+	"5.0",
+	"TLS Renegotiation DoS protection",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#define HANDSHAKE_LIMIT_COUNT 3
+#define HANDSHAKE_LIMIT_SECS 300
+
+typedef struct SAD SAD;
+struct SAD {
+	Client *client; /**< client */
+	time_t ts; /**< time */
+	int n; /**< number of times */
+};
+
+int tls_antidos_index = 0; /* slot# we acquire from OpenSSL. Hmm.. looks awfully similar to our moddata system ;) */
+
+/* Forward declaration */
+int tls_antidos_handshake(Client *client);
+
+void tls_antidos_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp);
+
+MOD_INIT()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, tls_antidos_handshake);
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
+	/* Note that we cannot be MOD_OPT_PERM_RELOADABLE as we use OpenSSL functions to register
+	 * an index and callback function.
+	 */
+	
+	tls_antidos_index = SSL_get_ex_new_index(0, "tls_antidos", NULL, NULL, tls_antidos_free);
+	
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Called upon handshake (and other events) */
+void ssl_info_callback(const SSL *ssl, int where, int ret)
+{
+	if (where & SSL_CB_HANDSHAKE_START)
+	{
+		SAD *e = SSL_get_ex_data(ssl, tls_antidos_index);
+		Client *client = e->client;
+		
+		if (IsServer(client) || IsDeadSocket(client))
+			return; /* if it's a server, or already pending to be killed off then we don't care */
+
+		if (e->ts < TStime() - HANDSHAKE_LIMIT_SECS)
+		{
+			e->ts = TStime();
+			e->n = 1;
+		} else {
+			e->n++;
+			if (e->n >= HANDSHAKE_LIMIT_COUNT)
+			{
+				unreal_log(ULOG_INFO, "flood", "TLS_HANDSHAKE_FLOOD", client, "TLS Handshake flood detected from $client.details -- killed");
+				dead_socket(client, "TLS Handshake flood detected");
+			}
+		}
+	}
+}
+
+/** Called when a client has just connected to us.
+ * This function is called quite quickly after accept(),
+ * in any case very likely before any data has been received.
+ */
+int tls_antidos_handshake(Client *client)
+{
+	if (client->local->ssl)
+	{
+		SAD *sad = safe_alloc(sizeof(SAD));
+		sad->client = client;
+		SSL_set_info_callback(client->local->ssl, ssl_info_callback);
+		SSL_set_ex_data(client->local->ssl, tls_antidos_index, sad);
+	}
+	return 0;
+}
+
+/** 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/ircd/src/modules/tls_cipher.c b/ircd/src/modules/tls_cipher.c
@@ -0,0 +1,118 @@
+/*
+ * Store TLS cipher in ModData
+ * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#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);
+int tls_json_expand_client(Client *client, int detail, json_t *j);
+
+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);
+
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, tls_json_expand_client);
+
+	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);
+}
+
+int tls_json_expand_client(Client *client, int detail, json_t *j)
+{
+	json_t *tls;
+	const char *str;
+
+	if (detail < 2)
+		return 0;
+
+	str = moddata_client_get(client, "tls_cipher");
+	if (!str)
+		return 0;
+
+	tls = json_object_get(j, "tls");
+	if (!tls)
+	{
+		tls = json_object();
+		json_object_set_new(j, "tls", tls);
+	}
+
+	json_object_set_new(tls, "cipher", json_string_unreal(str));
+
+	return 0;
+}
diff --git a/ircd/src/modules/topic.c b/ircd/src/modules/topic.c
@@ -0,0 +1,317 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/topic.c
+ *   (C) 2004-present 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_topic);
+
+#define MSG_TOPIC 	"TOPIC"
+
+ModuleHeader MOD_HEADER
+  = {
+	"topic",
+	"5.0",
+	"command /topic", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declarations */
+void _set_channel_topic(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_SET_CHANNEL_TOPIC, _set_channel_topic);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_TOPIC, cmd_topic, 4, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void topic_operoverride_msg(Client *client, Channel *channel, const char *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.
+ *
+ * Syntax for clients:
+ * parv[1] = channel
+ * parv[2] = new topic
+ *
+ * Syntax for server to server traffic:
+ * parv[1] = channel name
+ * parv[2] = topic nickname
+ * parv[3] = topic time
+ * parv[4] = topic text
+ */
+CMD_FUNC(cmd_topic)
+{
+	Channel *channel = NULL;
+	const char *topic = NULL;
+	const char *name, *tnick = client->name;
+	const char *errmsg = NULL;
+	time_t ttime = 0;
+	int i = 0;
+	Hook *h;
+	MessageTag *mtags = NULL;
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "TOPIC");
+		return;
+	}
+
+	name = parv[1];
+
+	channel = find_channel(parv[1]);
+	if (!channel)
+	{
+		sendnumeric(client, ERR_NOSUCHCHANNEL, name);
+		return;
+	}
+
+	if (parc > 2 || SecretChannel(channel))
+	{
+		if (!IsMember(client, channel) && !IsServer(client)
+		    && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL) && !IsULine(client))
+		{
+			sendnumeric(client, ERR_NOTONCHANNEL, name);
+			return;
+		}
+		if (parc > 2)
+			topic = parv[2];
+	}
+
+	if (parc > 4)
+	{
+		if (MyUser(client))
+		{
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "TOPIC", "Invalid parameters. Usage is TOPIC #channel :topic here");
+			return;
+		}
+		tnick = parv[2];
+		ttime = atol(parv[3]);
+		topic = parv[4];
+	}
+
+	/* Only asking for the topic */
+	if (!topic)
+	{
+		if (IsServer(client))
+			return; /* Servers must maintain state, not ask */
+
+		for (h = Hooks[HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(client,channel);
+			if (i != HOOK_CONTINUE)
+				break;
+		}
+
+		/* If you're not a member, and you can't view outside channel, deny */
+		if ((!IsMember(client, channel) && i == HOOK_DENY) ||
+		    (is_banned(client,channel,BANCHK_JOIN,NULL,NULL) &&
+		     !ValidatePermissionsForPath("channel:see:topic",client,NULL,channel,NULL)))
+		{
+			sendnumeric(client, ERR_NOTONCHANNEL, name);
+			return;
+		}
+
+		if (!channel->topic)
+			sendnumeric(client, RPL_NOTOPIC, channel->name);
+		else
+		{
+			sendnumeric(client, RPL_TOPIC, channel->name, channel->topic);
+			sendnumeric(client, RPL_TOPICWHOTIME, channel->name,
+			            channel->topic_nick, (long long)channel->topic_time);
+		}
+		return;
+	}
+
+	if (ttime && topic && (IsServer(client) || IsULine(client)))
+	{
+		if (!channel->topic_time || ttime > channel->topic_time || IsULine(client))
+		/* The IsUline is to allow services to use an old TS. Apparently
+		 * some services do this in their topic enforcement -- codemastr 
+		 */
+		{
+			/* Set the topic */
+			safe_strldup(channel->topic, topic, iConf.topic_length+1);
+			safe_strldup(channel->topic_nick, tnick, NICKLEN+USERLEN+HOSTLEN+5);
+			channel->topic_time = ttime;
+
+			new_message(client, recv_mtags, &mtags);
+			RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
+			sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
+			    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->name, channel->topic);
+			free_message_tags(mtags);
+		}
+		return;
+	}
+
+	/* Topic change. Either locally (check permissions!) or remote, check permissions: */
+	if (IsUser(client))
+	{
+		const char *newtopic = NULL;
+		const char *errmsg = NULL;
+		int ret = EX_ALLOW;
+		int operoverride = 0;
+
+		for (h = Hooks[HOOKTYPE_CAN_SET_TOPIC]; h; h = h->next)
+		{
+			int n = (*(h->func.intfunc))(client, channel, topic, &errmsg);
+
+			if (n == EX_DENY)
+			{
+				ret = n;
+			} else
+			if (n == EX_ALWAYS_DENY)
+			{
+				ret = n;
+				break;
+			}
+		}
+
+		if (ret == EX_ALWAYS_DENY)
+		{
+			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))
+			{
+				if (errmsg)
+					sendto_one(client, NULL, "%s", errmsg);
+				return; /* reject */
+			} else {
+				operoverride = 1; /* allow */
+			}
+		}
+
+		/* 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))
+			{
+				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->name, 0, NULL))
+				return;
+
+			for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_TOPIC]; tmphook; tmphook = tmphook->next) {
+				topic = (*(tmphook->func.stringfunc))(client, channel, topic);
+				if (!topic)
+					return;
+			}
+		}
+
+		/* At this point 'tnick' is set to client->name.
+		 * If set::topic-setter nick-user-host; is set
+		 * then we update it here to nick!user@host.
+		 */
+		if (iConf.topic_setter == SETTER_NICK_USER_HOST)
+			tnick = make_nick_user_host(client->name, client->user->username, GetHost(client));
+	}
+
+	_set_channel_topic(client, channel, recv_mtags, topic, tnick, ttime);
+}
+
+/** Set topic on a channel.
+ * @param client	The client setting the topic
+ * @param channel	The channel
+ * @param recv_mtags	Message tags
+ * @param topic		The new topic (TODO: this function does not support unsetting yet)
+ * @param set_by	Who set the topic (can be NULL, means client->name)
+ * @param set_at	When the topic was set (can be 0, means now)
+ */
+void _set_channel_topic(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at)
+{
+	MessageTag *mtags = NULL;
+
+	/* Set default values when needed */
+	if (set_by == NULL)
+		set_by = client->name;
+	if (set_at == 0)
+		set_at = TStime();
+
+	/* Set the topic */
+	safe_strldup(channel->topic, topic, iConf.topic_length+1);
+	safe_strldup(channel->topic_nick, set_by, NICKLEN+USERLEN+HOSTLEN+5);
+	channel->topic_time = set_at;
+
+	/* And broadcast the change - locally and remote */
+	new_message(client, recv_mtags, &mtags);
+	RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
+	sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
+	    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->name, channel->topic);
+	free_message_tags(mtags);
+}
diff --git a/ircd/src/modules/trace.c b/ircd/src/modules/trace.c
@@ -0,0 +1,234 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/trace.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_trace);
+
+#define MSG_TRACE 	"TRACE"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"trace",
+	"5.0",
+	"command /trace", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_TRACE, cmd_trace, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_trace
+**	parv[1] = servername
+*/
+CMD_FUNC(cmd_trace)
+{
+	int  i;
+	Client *acptr;
+	ConfigItem_class *cltmp;
+	const char *tname;
+	int  doall, link_s[MAXCONNECTIONS], link_u[MAXCONNECTIONS];
+	int  cnt = 0, wilds, dow;
+	time_t now;
+
+	/* This is one of the (few) commands that cannot be handled
+	 * by labeled-response because responses from multiple servers
+	 * are involved.
+	 */
+	labeled_response_inhibit = 1;
+
+	if (parc > 2)
+		if (hunt_server(client, NULL, "TRACE", 2, parc, parv))
+			return;
+
+	if (parc > 1)
+		tname = parv[1];
+	else
+		tname = me.name;
+
+	if (!ValidatePermissionsForPath("client:see:trace:global",client,NULL,NULL,NULL))
+	{
+		if (ValidatePermissionsForPath("client:see:trace:local",client,NULL,NULL,NULL))
+		{
+			/* local opers may not /TRACE remote servers! */
+			if (strcasecmp(tname, me.name))
+			{
+				sendnotice(client, "You can only /TRACE local servers as a locop");
+				sendnumeric(client, ERR_NOPRIVILEGES);
+				return;
+			}
+		} else {
+			sendnumeric(client, ERR_NOPRIVILEGES);
+			return;
+		}
+	}
+
+	switch (hunt_server(client, NULL, "TRACE", 1, parc, parv))
+	{
+	  case HUNTED_PASS:	/* note: gets here only if parv[1] exists */
+	  {
+		  Client *ac2ptr;
+
+		  ac2ptr = find_client(tname, NULL);
+		  sendnumeric(client, RPL_TRACELINK,
+		      version, debugmode, tname, ac2ptr->direction->name);
+		  return;
+	  }
+	  case HUNTED_ISME:
+		  break;
+	  default:
+		  return;
+	}
+
+	doall = (parv[1] && (parc > 1)) ? match_simple(tname, me.name) : TRUE;
+	wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
+	dow = wilds || doall;
+
+	for (i = 0; i < MAXCONNECTIONS; i++)
+		link_s[i] = 0, link_u[i] = 0;
+
+
+	if (doall) {
+		list_for_each_entry(acptr, &client_list, client_node)
+		{
+			if (acptr->direction->local->fd < 0)
+				continue;
+			if (IsUser(acptr))
+				link_u[acptr->direction->local->fd]++;
+			else if (IsServer(acptr))
+				link_s[acptr->direction->local->fd]++;
+		}
+	}
+
+	/* report all direct connections */
+
+	now = TStime();
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+	{
+		const char *name;
+		const char *class;
+
+		if (!ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL) && (acptr != client))
+			continue;
+		if (!doall && wilds && !match_simple(tname, acptr->name))
+			continue;
+		if (!dow && mycmp(tname, acptr->name))
+			continue;
+		name = get_client_name(acptr, FALSE);
+		class = acptr->local->class ? acptr->local->class->name : "default";
+		switch (acptr->status)
+		{
+			case CLIENT_STATUS_CONNECTING:
+				sendnumeric(client, RPL_TRACECONNECTING, class, name);
+				cnt++;
+				break;
+
+			case CLIENT_STATUS_HANDSHAKE:
+				sendnumeric(client, RPL_TRACEHANDSHAKE, class, name);
+				cnt++;
+				break;
+
+			case CLIENT_STATUS_ME:
+				break;
+
+			case CLIENT_STATUS_UNKNOWN:
+				sendnumeric(client, RPL_TRACEUNKNOWN, class, name);
+				cnt++;
+				break;
+
+			case CLIENT_STATUS_USER:
+				/* Only opers see users if there is a wildcard
+				 * but anyone can see all the opers.
+				 */
+				if (ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL) ||
+				    (!IsInvisible(acptr) && ValidatePermissionsForPath("client:see:trace",client,acptr,NULL,NULL)))
+				{
+					if (ValidatePermissionsForPath("client:see:trace",client,acptr,NULL,NULL) || ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL))
+						sendnumeric(client, RPL_TRACEOPERATOR,
+						    class, acptr->name,
+						    GetHost(acptr),
+						    (long long)(now - acptr->local->last_msg_received));
+					else
+						sendnumeric(client, RPL_TRACEUSER,
+						    class, acptr->name,
+						    acptr->user->realhost,
+						    (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->server->by) ?
+				    acptr->server->by : "*", "*", me.name,
+				    (long long)(now - acptr->local->last_msg_received));
+				cnt++;
+				break;
+
+			case CLIENT_STATUS_LOG:
+				sendnumeric(client, RPL_TRACELOG, LOGFILE, acptr->local->port);
+				cnt++;
+				break;
+
+			case CLIENT_STATUS_TLS_CONNECT_HANDSHAKE:
+				sendnumeric(client, RPL_TRACENEWTYPE, "TLS-Connect-Handshake", name);
+				cnt++;
+				break;
+
+			case CLIENT_STATUS_TLS_ACCEPT_HANDSHAKE:
+				sendnumeric(client, RPL_TRACENEWTYPE, "TLS-Accept-Handshake", name);
+				cnt++;
+				break;
+
+			default:	/* ...we actually shouldn't come here... --msa */
+				sendnumeric(client, RPL_TRACENEWTYPE, "<newtype>", name);
+				cnt++;
+				break;
+		}
+	}
+	/*
+	 * Add these lines to summarize the above which can get rather long
+	 * and messy when done remotely - Avalon
+	 */
+	if (!ValidatePermissionsForPath("client:see:trace",client,acptr,NULL,NULL) || !cnt)
+		return;
+
+	for (cltmp = conf_class; doall && cltmp; cltmp = cltmp->next)
+	/*	if (cltmp->clients > 0) */
+			sendnumeric(client, RPL_TRACECLASS, cltmp->name ? cltmp->name : "[noname]", cltmp->clients);
+}
diff --git a/ircd/src/modules/tsctl.c b/ircd/src/modules/tsctl.c
@@ -0,0 +1,74 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/tsctl.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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
+  = {
+	"tsctl",	/* Name of module */
+	"5.0", /* Version */
+	"command /tsctl", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+CMD_FUNC(cmd_tsctl);
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "TSCTL", cmd_tsctl, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+CMD_FUNC(cmd_tsctl)
+{
+	if (!ValidatePermissionsForPath("server:tsctl:view",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (MyUser(client) && (!parv[1] || strcasecmp(parv[1], "alltime")))
+	{
+		sendnotice(client, "/TSCTL now shows the time on all servers. You can no longer modify the time.");
+		parv[1] = "alltime";
+	}
+
+	if (parv[1] && !strcasecmp(parv[1], "alltime"))
+	{
+		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/ircd/src/modules/typing-indicator.c b/ircd/src/modules/typing-indicator.c
@@ -0,0 +1,105 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/typing-indicator.c
+ *   (C) 2020 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"typing-indicator",
+	"5.0",
+	"+typing client tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+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()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "+typing";
+	mtag.is_ok = ti_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "+draft/typing";
+	mtag.is_ok = ti_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_ti);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending the mtag is permitted to do so.
+ */
+int ti_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	/* Require a non-empty parameter */
+	if (BadPtr(value))
+		return 0;
+
+	/* These are the only valid values: */
+	if (!strcmp(value, "active") || !strcmp(value, "paused") || !strcmp(value, "done"))
+		return 1;
+
+	/* All the rest is considered illegal */
+	return 0;
+}
+
+void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m;
+
+	if (IsUser(client))
+	{
+		m = find_mtag(recv_mtags, "+typing");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+		m = find_mtag(recv_mtags, "+draft/typing");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+	}
+}
diff --git a/ircd/src/modules/umode2.c b/ircd/src/modules/umode2.c
@@ -0,0 +1,74 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/umode2.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_umode2);
+
+#define MSG_UMODE2 	"UMODE2"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"umode2",
+	"5.0",
+	"command /umode2", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_UMODE2, cmd_umode2, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+    cmd_umode2 added by Stskeeps
+    parv[1] - modes to change
+    Small wrapper to save bandwidth
+*/
+
+CMD_FUNC(cmd_umode2)
+{
+	const char *xparv[5] = {
+		client->name,
+		client->name,
+		parv[1],
+		(parc > 3) ? parv[3] : NULL,
+		NULL
+	};
+
+	if (!parv[1])
+		return;
+	cmd_umode(client, recv_mtags, (parc > 3) ? 4 : 3, xparv);
+}
diff --git a/ircd/src/modules/unreal_server_compat.c b/ircd/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 or later
+ *
+ * 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/ircd/src/modules/unsqline.c b/ircd/src/modules/unsqline.c
@@ -0,0 +1,74 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/unsqline.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   Moved to modules by Fish (Justin Hammond)
+ *
+ *   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_unsqline);
+
+#define MSG_UNSQLINE    "UNSQLINE"      /* UNSQLINE */
+
+ModuleHeader MOD_HEADER
+  = {
+	"unsqline",	/* Name of module */
+	"5.0", /* Version */
+	"command /unsqline", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_UNSQLINE, cmd_unsqline, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* cmd_unsqline
+**	parv[1] = nickmask
+*/
+CMD_FUNC(cmd_unsqline)
+{
+	const char *tkllayer[6] = {
+		me.name,           /*0  server.name */
+		"-",               /*1  - */
+		"Q",               /*2  Q   */
+		"*",               /*3  unused */
+		parv[1],           /*4  host */
+		client->name       /*5  whoremoved */
+	};
+
+	if (parc < 2)
+		return;
+
+	cmd_tkl(&me, NULL, 6, tkllayer);
+}
diff --git a/ircd/src/modules/user.c b/ircd/src/modules/user.c
@@ -0,0 +1,113 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/user.c
+ *   (C) 2005 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_user);
+
+#define MSG_USER 	"USER"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"user",
+	"5.0",
+	"command /user", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_USER, cmd_user, 4, CMD_UNREGISTERED);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** The USER command, together with NICK this will register a user.
+ * As per UnrealIRCd 5 this command is only available to local clients.
+ * Intraserver traffic is handled through the UID command.
+ *	parv[1] = username
+ *	parv[2] = client host name (ignored)
+ *	parv[3] = server host name (ignored)
+ *	parv[4] = real name / gecos
+ *
+ * NOTE: Be advised that multiple USER messages are possible,
+ *       hence, always check if a certain struct is already allocated... -- Syzop
+ */
+CMD_FUNC(cmd_user)
+{
+	const char *username;
+	const char *realname;
+	char *p;
+
+	if (!MyConnect(client) || IsServer(client))
+		return;
+
+	if (MyConnect(client) && (client->local->listener->options & LISTENER_SERVERSONLY))
+	{
+		exit_client(client, NULL, "This port is for servers only");
+		return;
+	}
+
+	if ((parc < 5) || BadPtr(parv[4]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "USER");
+		return;
+	}
+
+	username = parv[1];
+	realname = parv[4];
+	
+	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, 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))
+	{
+		/* NICK and no-spoof already received, now we have USER... */
+		if (USE_BAN_VERSION && MyConnect(client))
+		{
+			sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
+				me.name, client->name);
+		}
+		register_user(client);
+		return;
+	}
+}
diff --git a/ircd/src/modules/userhost-tag.c b/ircd/src/modules/userhost-tag.c
@@ -0,0 +1,111 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/userhost-tag.c
+ *   (C) 2020 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"userhost-tag",
+	"5.0",
+	"userhost message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_ACCOUNT_TAG = 0L;
+
+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()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/userhost";
+	mtag.is_ok = userhost_mtag_is_ok;
+	mtag.should_send_to_client = userhost_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_userhost);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending
+ * 'userhost-tag' is permitted to do so and uses a permitted
+ * syntax.
+ * We simply allow userhost-tag ONLY from servers and with any syntax.
+ */
+int userhost_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m;
+
+	if (IsUser(client))
+	{
+		MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/userhost");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+		} else {
+			char nuh[USERLEN+HOSTLEN+1];
+
+			snprintf(nuh, sizeof(nuh), "%s@%s", client->user->username, client->user->realhost);
+
+			m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, "unrealircd.org/userhost");
+			safe_strdup(m->value, nuh);
+		}
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int userhost_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target) || IsOper(target))
+		return 1;
+	return 0;
+}
diff --git a/ircd/src/modules/userhost.c b/ircd/src/modules/userhost.c
@@ -0,0 +1,115 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/userhost.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_userhost);
+
+#define MSG_USERHOST 	"USERHOST"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"userhost",
+	"5.0",
+	"command /userhost", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_USERHOST, cmd_userhost, 1, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * cmd_userhost added by Darren Reed 13/8/91 to aid clients and reduce
+ * the need for complicated requests like WHOIS. It returns user/host
+ * information only (no spurious AWAY labels or channels).
+ * Re-written by Dianora 1999
+ */
+/* Keep this at 5!!!! */
+#define MAXUSERHOSTREPLIES 5
+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;
+
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "USERHOST");
+		return;
+	}
+
+	/* The idea is to build up the response string out of pieces
+	 * none of this strlen() nonsense.
+	 * MAXUSERHOSTREPLIES * (NICKLEN*2+CHANNELLEN+USERLEN+HOSTLEN+30) is still << sizeof(buf)
+	 * 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';
+
+	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_user(cn, NULL)))
+		{
+			ircsnprintf(response[w], NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30,
+                            "%s%s=%c%s@%s",
+			    acptr->name,
+			    (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
+				? "*" : "",
+			    (acptr->user->away) ? '-' : '+',
+			    acptr->user->username,
+			    ((acptr != client) && !IsOper(client)
+			    && IsHidden(acptr) ? acptr->user->virthost :
+			    acptr->user->realhost));
+			w++;
+		}
+		if (p)
+			p++;
+		cn = p;
+	}
+
+	sendnumeric(client, RPL_USERHOST, response[0], response[1], response[2], response[3], response[4]);
+}
diff --git a/ircd/src/modules/userip-tag.c b/ircd/src/modules/userip-tag.c
@@ -0,0 +1,111 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/userip-tag.c
+ *   (C) 2020 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"userip-tag",
+	"5.0",
+	"userip message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_ACCOUNT_TAG = 0L;
+
+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()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/userip";
+	mtag.is_ok = userip_mtag_is_ok;
+	mtag.should_send_to_client = userip_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_userip);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending
+ * 'userip-tag' is permitted to do so and uses a permitted
+ * syntax.
+ * We simply allow userip-tag ONLY from servers and with any syntax.
+ */
+int userip_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m;
+
+	if (IsUser(client) && client->ip)
+	{
+		MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/userip");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+		} else {
+			char nuh[USERLEN+HOSTLEN+1];
+
+			snprintf(nuh, sizeof(nuh), "%s@%s", client->user->username, GetIP(client));
+
+			m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, "unrealircd.org/userip");
+			safe_strdup(m->value, nuh);
+		}
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int userip_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target) || IsOper(target))
+		return 1;
+	return 0;
+}
diff --git a/ircd/src/modules/userip.c b/ircd/src/modules/userip.c
@@ -0,0 +1,126 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/userip.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_userip);
+
+#define MSG_USERIP 	"USERIP"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"userip",
+	"5.0",
+	"command /userip", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_USERIP, cmd_userip, 1, CMD_USER);
+	ISupportAdd(modinfo->handle, "USERIP", NULL);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * cmd_userip is based on cmd_userhost
+ * cmd_userhost added by Darren Reed 13/8/91 to aid clients and reduce
+ * the need for complicated requests like WHOIS. It returns user/host
+ * information only (no spurious AWAY labels or channels).
+ * Re-written by Dianora 1999
+ */
+/* Keep this at 5!!!! */
+#define MAXUSERHOSTREPLIES 5
+CMD_FUNC(cmd_userip)
+{
+
+	char *p;		/* scratch end pointer */
+	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;
+
+	if (!MyUser(client))
+		return;
+		
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "USERIP");
+		return;
+	}
+
+	/* The idea is to build up the response string out of pieces
+	 * none of this strlen() nonsense.
+	 * MAXUSERHOSTREPLIES * (NICKLEN*2+CHANNELLEN+USERLEN+HOSTLEN+30) is still << sizeof(buf)
+	 * 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';
+
+	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_user(cn, NULL)))
+		{
+			if (!(ip = GetIP(acptr)))
+				ip = "<unknown>";
+			if (client != acptr && !ValidatePermissionsForPath("client:see:ip",client,acptr,NULL,NULL) && IsHidden(acptr))
+			{
+				make_cloakedhost(acptr, GetIP(acptr), ipbuf, sizeof(ipbuf));
+				ip = ipbuf;
+			}
+
+			ircsnprintf(response[w], NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30, "%s%s=%c%s@%s",
+			    acptr->name,
+			    (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
+				? "*" : "",
+			    (acptr->user->away) ? '-' : '+',
+			    acptr->user->username, ip);
+			w++;
+		}
+		if (p)
+			p++;
+		cn = p;
+	}
+
+	sendnumeric(client, RPL_USERIP, response[0], response[1], response[2], response[3], response[4]);
+}
diff --git a/ircd/src/modules/usermodes/Makefile.in b/ircd/src/modules/usermodes/Makefile.in
@@ -0,0 +1,54 @@
+#************************************************************************
+#*   IRC - Internet Relay Chat, src/modules/usermodes/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/dns.h \
+	../../include/resource.h ../../include/setup.h \
+	../../include/struct.h ../../include/sys.h \
+	../../include/types.h \
+	../../include/version.h ../../include/whowas.h
+
+R_MODULES=\
+	noctcp.so censor.so bot.so showwhois.so nokick.so servicebot.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)
+
+clean:
+	$(RM) -f *.o *.so *~ core
+
+%.so: %.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o $@ $<
diff --git a/ircd/src/modules/usermodes/bot.c b/ircd/src/modules/usermodes/bot.c
@@ -0,0 +1,105 @@
+/*
+ * Bot user mode (User mode +B)
+ * (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"
+
+#define IsBot(cptr)    (cptr->umodes & UMODE_BOT)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/bot",
+	"4.2",
+	"User Mode +B",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+long UMODE_BOT = 0L;
+
+/* Forward declarations */
+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()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'B', UMODE_GLOBAL, 0, NULL, &UMODE_BOT);
+	ISupportAdd(modinfo->handle, "BOT", "B");
+	
+	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, bot_whois);
+	HookAdd(modinfo->handle, HOOKTYPE_WHO_STATUS, 0, bot_who_status);
+	HookAdd(modinfo->handle, HOOKTYPE_UMODE_CHANGE, 0, bot_umode_change);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int bot_whois(Client *client, Client *target, NameValuePrioList **list)
+{
+	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 *client, Client *target, Channel *channel, Member *cm, const char *status, int cansee)
+{
+	if (IsBot(target))
+		return 'B';
+	
+	return 0;
+}
+
+int bot_umode_change(Client *client, long oldmode, long newmode)
+{
+	if ((newmode & UMODE_BOT) && !(oldmode & UMODE_BOT) && MyUser(client))
+	{
+		/* now +B */
+		const char *parv[2];
+		parv[0] = client->name;
+		parv[1] = NULL;
+		do_cmd(client, NULL, "BOTMOTD", 1, parv);
+	}
+
+	return 0;
+}
diff --git a/ircd/src/modules/usermodes/censor.c b/ircd/src/modules/usermodes/censor.c
@@ -0,0 +1,271 @@
+/*
+ * User Mode +G
+ * (C) Copyright 2005-current Bram Matthys and The UnrealIRCd team.
+ */
+
+#include "unrealircd.h"
+
+
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/censor",
+	"4.2",
+	"User Mode +G",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+
+long UMODE_CENSOR = 0L;
+
+#define IsCensored(x) (x->umodes & UMODE_CENSOR)
+
+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);
+
+ModuleInfo *ModInfo = NULL;
+
+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, const char *para);
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, censor_config_test);
+	return MOD_SUCCESS;
+}
+	
+MOD_INIT()
+{
+	ModInfo = modinfo;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	UmodeAdd(modinfo->handle, 'G', UMODE_GLOBAL, 0, NULL, &UMODE_CENSOR);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, censor_can_send_to_user);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, censor_stats_badwords_user);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, censor_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+MOD_UNLOAD()
+{
+ConfigItem_badword *badword, *next;
+
+	for (badword = conf_badword_message; badword; badword = next)
+	{
+		next = badword->next;
+		DelListItem(badword, conf_badword_message);
+		badword_config_free(badword);
+	}
+	return MOD_SUCCESS;
+}
+
+int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+	char has_word = 0, has_replace = 0, has_action = 0, action = 'r';
+
+	if (type != CONFIG_MAIN)
+		return 0;
+	
+	if (!ce || !ce->name || strcmp(ce->name, "badword"))
+		return 0; /* not interested */
+
+	if (!ce->value)
+	{
+		config_error("%s:%i: badword without type",
+			ce->file->filename, ce->line_number);
+		return 1;
+	}
+	else if (strcmp(ce->value, "message") && strcmp(ce->value, "all")) {
+/*			config_error("%s:%i: badword with unknown type",
+				ce->file->filename, ce->line_number); -- can't do that.. */
+		return 0; /* unhandled */
+	}
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (config_is_blankorempty(cep, "badword"))
+		{
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "word"))
+		{
+			const char *errbuf;
+			if (has_word)
+			{
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::word");
+				continue;
+			}
+			has_word = 1;
+			if ((errbuf = badword_config_check_regex(cep->value,1,1)))
+			{
+				config_error("%s:%i: badword::%s contains an invalid regex: %s",
+					cep->file->filename,
+					cep->line_number,
+					cep->name, errbuf);
+				errors++;
+			}
+		}
+		else if (!strcmp(cep->name, "replace"))
+		{
+			if (has_replace)
+			{
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::replace");
+				continue;
+			}
+			has_replace = 1;
+		}
+		else if (!strcmp(cep->name, "action"))
+		{
+			if (has_action)
+			{
+				config_warn_duplicate(cep->file->filename, 
+					cep->line_number, "badword::action");
+				continue;
+			}
+			has_action = 1;
+			if (!strcmp(cep->value, "replace"))
+				action = 'r';
+			else if (!strcmp(cep->value, "block"))
+				action = 'b';
+			else
+			{
+				config_error("%s:%d: Unknown badword::action '%s'",
+					cep->file->filename, cep->line_number,
+					cep->value);
+				errors++;
+			}
+				
+		}
+		else
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"badword", cep->name);
+			errors++;
+		}
+	}
+
+	if (!has_word)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"badword::word");
+		errors++;
+	}
+	if (has_action)
+	{
+		if (has_replace && action == 'b')
+		{
+			config_error("%s:%i: badword::action is block but badword::replace exists",
+				ce->file->filename, ce->line_number);
+			errors++;
+		}
+	}
+	
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+
+int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep, *word = NULL;
+	ConfigItem_badword *ca;
+
+	if (type != CONFIG_MAIN)
+		return 0;
+	
+	if (!ce || !ce->name || strcmp(ce->name, "badword"))
+		return 0; /* not interested */
+
+	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->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "action"))
+		{
+			if (!strcmp(cep->value, "block"))
+			{
+				ca->action = BADWORD_BLOCK;
+			}
+		}
+		else if (!strcmp(cep->name, "replace"))
+		{
+			safe_strdup(ca->replace, cep->value);
+		}
+		else if (!strcmp(cep->name, "word"))
+		{
+			word = cep;
+		}
+	}
+
+	badword_config_process(ca, word->value);
+
+	if (!strcmp(ce->value, "message"))
+	{
+		AddListItem(ca, conf_badword_message);
+	} else
+	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 */
+	}
+
+	return 1;
+}
+
+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, const char **text, const char **errmsg, SendType sendtype)
+{
+	int blocked = 0;
+
+	if (MyUser(client) && IsCensored(target))
+	{
+		*text = stripbadwords_message(*text, &blocked);
+		if (blocked)
+		{
+			*errmsg = "User does not accept private messages containing swearing";
+			return HOOK_DENY;
+		}
+	}
+
+	return HOOK_CONTINUE;
+}
+
+int censor_stats_badwords_user(Client *client, const char *para)
+{
+	ConfigItem_badword *words;
+
+	if (!para || !(!strcmp(para, "b") || !strcasecmp(para, "badword")))
+		return 0;
+
+	for (words = conf_badword_message; words; words = words->next)
+	{
+		sendtxtnumeric(client, "m %c %s%s%s %s", words->type & BADW_TYPE_REGEX ? 'R' : 'F',
+		           (words->type & BADW_TYPE_FAST_L) ? "*" : "", words->word,
+		           (words->type & BADW_TYPE_FAST_R) ? "*" : "",
+		           words->action == BADWORD_REPLACE ? (words->replace ? words->replace : "<censored>") : "");
+	}
+	return 1;
+}
diff --git a/ircd/src/modules/usermodes/noctcp.c b/ircd/src/modules/usermodes/noctcp.c
@@ -0,0 +1,86 @@
+/*
+ * Block user-to-user CTCP UnrealIRCd Module (User Mode +T)
+ * (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"
+
+CMD_FUNC(noctcp);
+
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/noctcp",
+	"4.2",
+	"User Mode +T",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+long UMODE_NOCTCP = 0L;
+
+#define IsNoCTCP(client)    (client->umodes & UMODE_NOCTCP)
+
+int noctcp_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
+
+MOD_TEST()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+CmodeInfo req;
+
+	UmodeAdd(modinfo->handle, 'T', UMODE_GLOBAL, 0, NULL, &UMODE_NOCTCP);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, noctcp_can_send_to_user);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+static int IsACTCP(const char *s)
+{
+	if (!s)
+		return 0;
+
+	if ((*s == '\001') && strncmp(&s[1], "ACTION ", 7) && strncmp(&s[1], "DCC ", 4))
+		return 1;
+
+	return 0;
+}
+
+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))
+	{
+		*errmsg = "User does not accept CTCPs";
+		return HOOK_DENY;
+	}
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/usermodes/nokick.c b/ircd/src/modules/usermodes/nokick.c
@@ -0,0 +1,100 @@
+/*
+ * Prevents you from being kicked (User mode +q)
+ * (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"
+
+#define IsNokick(client)    (client->umodes & UMODE_NOKICK)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/nokick",
+	"4.2",
+	"User Mode +q",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+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,
+                    const char *comment, const char *client_member_modes, const char *target_member_modes, const char **reject_reason);
+
+MOD_TEST()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'q', UMODE_GLOBAL, 1, umode_allow_unkickable_oper, &UMODE_NOKICK);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_KICK, 0, nokick_can_kick);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int umode_allow_unkickable_oper(Client *client, int what)
+{
+	if (MyUser(client))
+	{
+		if (IsOper(client) && ValidatePermissionsForPath("self:unkickablemode",client,NULL,NULL,NULL))
+			return 1;
+		return 0;
+	}
+	/* Always allow remotes: */
+	return 1;
+}
+
+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];
+
+	if (IsNokick(target) && !IsULine(client) && MyUser(client) && !ValidatePermissionsForPath("channel:override:kick:nokick",client,target,channel,NULL))
+	{
+		ircsnprintf(errmsg, sizeof(errmsg), ":%s %d %s %s :%s",
+		            me.name, ERR_CANNOTDOCOMMAND, client->name, "KICK",
+				   "user is unkickable (user mode +q)");
+
+		*reject_reason = errmsg;
+
+		sendnotice(target,
+			"*** umode q: %s tried to kick you from channel %s (%s)",
+			client->name, channel->name, comment);
+		
+		return EX_ALWAYS_DENY;
+	}
+
+	return EX_ALLOW;
+}
diff --git a/ircd/src/modules/usermodes/privacy.c b/ircd/src/modules/usermodes/privacy.c
@@ -0,0 +1,69 @@
+/*
+ * Privacy - hide channels in /WHOIS (User mode +p)
+ * (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"
+
+#define IsPrivacy(client)    (client->umodes & UMODE_PRIVACY)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/privacy",
+	"4.2",
+	"User Mode +p",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+long UMODE_PRIVACY = 0L;
+
+/* Forward declarations */
+int privacy_see_channel_in_whois(Client *client, Client *target, Channel *channel);
+                    
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'p', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_PRIVACY);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_SEE_CHANNEL_IN_WHOIS, 0, privacy_see_channel_in_whois);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* This hides channels in /WHOIS output, unless the requestor is in the same channel
+ * or some IRCOp is overriding.
+ */
+int privacy_see_channel_in_whois(Client *client, Client *target, Channel *channel)
+{
+	if (IsPrivacy(target) && !IsMember(client, channel))
+		return EX_DENY;
+	
+	return EX_ALLOW;
+}
diff --git a/ircd/src/modules/usermodes/privdeaf.c b/ircd/src/modules/usermodes/privdeaf.c
@@ -0,0 +1,59 @@
+/*
+ * usermode +D: makes it so you cannot receive private messages/notices
+ * except from opers, U-lines and servers. -- Syzop
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"usermodes/privdeaf",
+	"1.2",
+	"Private Messages Deaf (+D) -- by Syzop",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+static long UMODE_PRIVDEAF = 0;
+static Umode *UmodePrivdeaf = NULL;
+
+int privdeaf_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	UmodePrivdeaf = UmodeAdd(modinfo->handle, 'D', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_PRIVDEAF);
+	if (!UmodePrivdeaf)
+	{
+		/* I use config_error() here because it's printed to stderr in case of a load
+		 * on cmd line, and to all opers in case of a /rehash.
+		 */
+		config_error("privdeaf: Could not add usermode 'D': %s", ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	
+	 HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, privdeaf_can_send_to_user);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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))
+	{
+		*errmsg = "User does not accept private messages";
+		return HOOK_DENY;
+	}
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/usermodes/regonlymsg.c b/ircd/src/modules/usermodes/regonlymsg.c
@@ -0,0 +1,72 @@
+/*
+ * Recieve private messages only from registered users (User mode +R)
+ * (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"
+
+#define IsRegOnlyMsg(client)    (client->umodes & UMODE_REGONLYMSG)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/regonlymsg",
+	"4.2",
+	"User Mode +R",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+long UMODE_REGONLYMSG = 0L;
+
+/* Forward declarations */
+int regonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
+                    
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'R', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_REGONLYMSG);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, regonlymsg_can_send_to_user);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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?*text:NULL))
+			return HOOK_CONTINUE; /* bypass this restriction */
+
+		*errmsg = "You must identify to a registered nick to private message this user";
+		return HOOK_DENY;
+	}
+
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/usermodes/secureonlymsg.c b/ircd/src/modules/usermodes/secureonlymsg.c
@@ -0,0 +1,86 @@
+/*
+ * 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>
+ *
+ * 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 IsSecureOnlyMsg(client)    (client->umodes & UMODE_SECUREONLYMSG)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/secureonlymsg",
+	"4.2",
+	"User Mode +Z",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+long UMODE_SECUREONLYMSG = 0L;
+
+/* Forward declarations */
+int secureonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
+                    
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'Z', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_SECUREONLYMSG);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, secureonlymsg_can_send_to_user);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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?*text:NULL))
+			return HOOK_CONTINUE; /* bypass this restriction */
+
+		*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?*text:NULL))
+			return HOOK_CONTINUE; /* bypass this restriction */
+		
+		/* Similar to above but in this case we are +Z and are trying to message
+		 * 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 TLS and you are +Z";
+		return HOOK_DENY;
+	}
+
+	return HOOK_CONTINUE;
+}
diff --git a/ircd/src/modules/usermodes/servicebot.c b/ircd/src/modules/usermodes/servicebot.c
@@ -0,0 +1,144 @@
+/*
+ * Prevents you from being kicked (User mode +q)
+ * (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"
+
+#define IsServiceBot(client)    (client->umodes & UMODE_SERVICEBOT)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/servicebot",
+	"4.2",
+	"User Mode +S",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+long UMODE_SERVICEBOT = 0L;
+
+/* Forward declarations */
+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);
+int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
+                    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()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'S', UMODE_GLOBAL, 1, umode_allow_none, &UMODE_SERVICEBOT);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_KICK, 0, servicebot_can_kick);
+	HookAdd(modinfo->handle, HOOKTYPE_MODE_DEOP, 0, servicebot_mode_deop);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_KILL, 0, servicebot_pre_kill);
+	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, servicebot_whois);
+	HookAdd(modinfo->handle, HOOKTYPE_SEE_CHANNEL_IN_WHOIS, 0, servicebot_see_channel_in_whois);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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];
+
+	if (MyUser(client) && !IsULine(client) && IsServiceBot(target))
+	{
+		char errmsg2[NICKLEN+32];
+		snprintf(errmsg2, sizeof(errmsg2), "%s is a Service Bot", target->name);
+		
+		snprintf(errmsg, sizeof(errmsg), ":%s %d %s %s :%s",
+		         me.name, ERR_CANNOTDOCOMMAND, client->name, "KICK", errmsg2);
+
+		*reject_reason = errmsg;
+
+		return EX_DENY;
+	}
+
+	return EX_ALLOW;
+}
+
+int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
+                    u_int what, int modechar, const char *client_access, const char *target_access, const char **reject_reason)
+{
+	static char errmsg[NICKLEN+256];
+	
+	if (IsServiceBot(target) && MyUser(client) && !ValidatePermissionsForPath("services:servicebot:deop",client,target,channel,NULL) && (what == MODE_DEL))
+	{
+		snprintf(errmsg, sizeof(errmsg), ":%s %d %s %c :%s is a Service Bot",
+			me.name, ERR_CANNOTCHANGECHANMODE, client->name, (char)modechar, target->name);
+		
+		*reject_reason = errmsg;
+		
+		return EX_DENY;
+	}
+	
+	return EX_ALLOW;
+}
+
+int servicebot_pre_kill(Client *client, Client *target, const char *reason)
+{
+	if (IsServiceBot(target) && !(ValidatePermissionsForPath("services:servicebot:kill",client,target,NULL,NULL) || IsULine(client)))
+	{
+		sendnumeric(client, ERR_KILLDENY, target->name);
+		return EX_ALWAYS_DENY;
+	}
+	return EX_ALLOW;
+}
+
+int servicebot_whois(Client *client, Client *target, NameValuePrioList **list)
+{
+	int hideoper = (IsHideOper(target) && (client != target) && !IsOper(client)) ? 1 : 0;
+
+	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;
+}
+
+/* This hides the servicebot, even if you are in the same channel, unless oper overriding */
+int servicebot_see_channel_in_whois(Client *client, Client *target, Channel *channel)
+{
+	if (IsServiceBot(target))
+		return EX_DENY;
+	
+	return EX_ALLOW;
+}
diff --git a/ircd/src/modules/usermodes/showwhois.c b/ircd/src/modules/usermodes/showwhois.c
@@ -0,0 +1,76 @@
+/*
+ * Show when someone does a /WHOIS on you (User mode +W)
+ * (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"
+
+#define IsWhois(cptr)    (cptr->umodes & UMODE_SHOWWHOIS)
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"usermodes/showwhois",
+	"4.2",
+	"User Mode +W",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Global variables */
+long UMODE_SHOWWHOIS = 0L;
+
+/* Forward declarations */
+int showwhois_whois(Client *requester, Client *target, NameValuePrioList **list);
+
+MOD_TEST()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	UmodeAdd(modinfo->handle, 'W', UMODE_GLOBAL, 1, umode_allow_opers, &UMODE_SHOWWHOIS);
+	
+	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, showwhois_whois);
+	
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int showwhois_whois(Client *requester, Client *target, NameValuePrioList **list)
+{
+	if (IsWhois(target) && (requester != target))
+	{
+		sendnotice(target,
+			"*** %s (%s@%s) did a /whois on you.",
+			requester->name,
+			requester->user->username, requester->user->realhost);
+	}
+
+	return 0;
+}
diff --git a/ircd/src/modules/usermodes/wallops.c b/ircd/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/ircd/src/modules/vhost.c b/ircd/src/modules/vhost.c
@@ -0,0 +1,182 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/vhost.c
+ *   (C) 2000-2001 Carsten V. Munk 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"
+
+CMD_FUNC(cmd_vhost);
+
+/* Place includes here */
+#define MSG_VHOST       "VHOST"
+
+ModuleHeader MOD_HEADER
+  = {
+	"vhost",	/* Name of module */
+	"5.0", /* Version */
+	"command /vhost", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_VHOST, cmd_vhost, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+CMD_FUNC(cmd_vhost)
+{
+	ConfigItem_vhost *vhost;
+	char login[HOSTLEN+1];
+	const char *password;
+	char olduser[USERLEN+1];
+
+	if (!MyUser(client))
+		return;
+
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "VHOST");
+		return;
+
+	}
+
+	/* 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.
+	 */
+	strlcpy(login, parv[1], sizeof(login));
+
+	password = (parc > 2) ? parv[2] : "";
+
+	if (!(vhost = find_vhost(login)))
+	{
+		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 (!user_allowed_by_security_group(client, vhost->match))
+	{
+		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))
+	{
+		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;
+	}
+
+	/* Authentication passed, but.. there could still be other restrictions: */
+	switch (UHOST_ALLOWED)
+	{
+		case UHALLOW_NEVER:
+			if (MyUser(client))
+			{
+				sendnotice(client, "*** /vhost is disabled");
+				return;
+			}
+			break;
+		case UHALLOW_ALWAYS:
+			break;
+		case UHALLOW_NOCHANS:
+			if (MyUser(client) && client->user->joined)
+			{
+				sendnotice(client, "*** /vhost can not be used while you are on a channel");
+				return;
+			}
+			break;
+		case UHALLOW_REJOIN:
+			/* join sent later when the host has been changed */
+			break;
+	}
+
+	/* All checks passed, now let's go ahead and change the host */
+
+	userhost_save_current(client);
+
+	safe_strdup(client->user->virthost, vhost->virthost);
+	if (vhost->virtuser)
+	{
+		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);
+	}
+	client->umodes |= UMODE_HIDE;
+	client->umodes |= UMODE_SETHOST;
+	sendto_server(client, 0, 0, NULL, ":%s SETHOST %s", client->id, client->user->virthost);
+	sendto_one(client, NULL, ":%s MODE %s :+tx", client->name, client->name);
+	if (vhost->swhois)
+	{
+		SWhois *s;
+		for (s = vhost->swhois; s; s = s->next)
+			swhois_add(client, "vhost", -100, s->line, &me, NULL);
+	}
+	sendnotice(client, "*** Your vhost is now %s%s%s",
+		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/ircd/src/modules/watch-backend.c b/ircd/src/modules/watch-backend.c
@@ -0,0 +1,392 @@
+/*
+ *   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 = NULL;
+static char *siphashkey_watch = NULL;
+
+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",
+	"6.0.3",
+	"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;
+}
+
+void watch_generic_free(ModData *m)
+{
+	safe_free(m->ptr);
+}
+
+MOD_INIT()
+{	
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1); /* or do a complex memory freeing algorithm instead */
+
+	LoadPersistentPointer(modinfo, siphashkey_watch, watch_generic_free);
+	if (siphashkey_watch == NULL)
+	{
+		siphashkey_watch = safe_alloc(SIPHASH_KEY_LENGTH);
+		siphash_generate_key(siphashkey_watch);
+	}
+	LoadPersistentPointer(modinfo, watchTable, watch_generic_free);
+	if (watchTable == NULL)
+		watchTable = safe_alloc(sizeof(Watch *) * WATCH_HASH_TABLE_SIZE);
+
+	memset(&mreq, 0 , sizeof(mreq));
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	mreq.name = "watchCount",
+	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()
+{
+	SavePersistentPointer(modinfo, siphashkey_watch);
+	SavePersistentPointer(modinfo, watchTable);
+	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 = watchTable[hashv]))
+		while (watch && mycmp(watch->nick, nick))
+		 watch = watch->hnext;
+	
+	/* If found NULL (no header for this nick), make one... */
+	if (!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 = 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 = 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/ircd/src/modules/watch.c b/ircd/src/modules/watch.c
@@ -0,0 +1,429 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/watch.c
+ *   (C) 2005 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_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
+  = {
+	"watch",
+	"5.0",
+	"command /watch", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{	
+	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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * RPL_NOWON	- Online at the moment (Successfully added to WATCH-list)
+ * RPL_NOWOFF	- Offline at the moement (Successfully added to WATCH-list)
+ */
+static void show_watch(Client *client, char *name, int awaynotify)
+{
+	Client *target;
+
+	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,
+			    (long long)target->user->away_since);
+			return;
+		}
+		
+		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,
+		    (long long)target->lastnick);
+	}
+	else
+	{
+		sendnumeric(client, RPL_WATCHOFF, name, "*", "*", 0LL);
+	}
+}
+
+#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];
+	char buf[BUFSIZE];
+	Client *target;
+	char *s, *user;
+	char *p = NULL, *def = "l";
+	int awaynotify = 0;
+	int did_l=0, did_s=0;
+
+	if (!MyUser(client))
+		return;
+
+	if (parc < 2)
+	{
+		/*
+		 * Default to 'l' - list who's currently online
+		 */
+		parc = 2;
+		parv[1] = def;
+	}
+
+
+	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 */
+			
+		if (!strcmp(s, "A") && WATCH_AWAY_NOTIFICATION)
+			awaynotify = 1;
+
+		/*
+		 * Prefix of "+", they want to add a name to their WATCH
+		 * list.
+		 */
+		if (*s == '+')
+		{
+			if (!*(s+1))
+				continue;
+			if (do_nick_name(s + 1))
+			{
+				if (WATCHES(client) >= MAXWATCH)
+				{
+					sendnumeric(client, ERR_TOOMANYWATCH, s + 1);
+					continue;
+				}
+
+				watch_add(s + 1, client,
+					WATCH_FLAG_TYPE_WATCH | (awaynotify ? WATCH_FLAG_AWAYNOTIFY : 0)
+					);
+			}
+
+			show_watch(client, s + 1, awaynotify);
+			continue;
+		}
+
+		/*
+		 * Prefix of "-", coward wants to remove somebody from their
+		 * WATCH list.  So do it. :-)
+		 */
+		if (*s == '-')
+		{
+			if (!*(s+1))
+				continue;
+			watch_del(s + 1, client, WATCH_FLAG_TYPE_WATCH);
+			show_watch_removed(client, s + 1);
+			continue;
+		}
+
+		/*
+		 * Fancy "C" or "c", they want to nuke their WATCH list and start
+		 * over, so be it.
+		 */
+		if (*s == 'C' || *s == 'c')
+		{
+			watch_del_list(client, WATCH_FLAG_TYPE_WATCH);
+			continue;
+		}
+
+		/*
+		 * Now comes the fun stuff, "S" or "s" returns a status report of
+		 * their WATCH list.  I imagine this could be CPU intensive if its
+		 * done alot, perhaps an auto-lag on this?
+		 */
+		if ((*s == 'S' || *s == 's') && !did_s)
+		{
+			Link *lp;
+			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. This will also include
+			 * other WATCH types if present - we're not checking for
+			 * WATCH_FLAG_TYPE_*.
+			 */
+			watch = watch_get(client->name);
+			if (watch)
+				for (lp = watch->watch, count = 1;
+				    (lp = lp->next); count++)
+					;
+			sendnumeric(client, RPL_WATCHSTAT, WATCHES(client), count);
+
+			/*
+			 * Send a list of everybody in their WATCH list. Be careful
+			 * not to buffer overflow.
+			 */
+			lp = WATCH(client);
+			*buf = '\0';
+			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)
+				{
+					sendnumeric(client, RPL_WATCHLIST, buf);
+					*buf = '\0';
+					count = strlen(client->name) + strlen(me.name) + 10;
+				}
+				strcat(buf, " ");
+				strcat(buf, lp->value.wptr->nick);
+				count += (strlen(lp->value.wptr->nick) + 1);
+				
+				lp = lp->next;
+			}
+			if (*buf)
+				/* anything to send */
+				sendnumeric(client, RPL_WATCHLIST, buf);
+
+			sendnumeric(client, RPL_ENDOFWATCHLIST, *s);
+			continue;
+		}
+
+		/*
+		 * Well that was fun, NOT.  Now they want a list of everybody in
+		 * their WATCH list AND if they are online or offline? Sheesh,
+		 * greedy arn't we?
+		 */
+		if ((*s == 'L' || *s == 'l') && !did_l)
+		{
+			Link *lp = WATCH(client);
+
+			did_l = 1;
+
+			while (lp)
+			{
+				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,
+					    (long long)target->lastnick);
+				}
+				/*
+				 * But actually, only show them offline if its a capital
+				 * 'L' (full list wanted).
+				 */
+				else if (isupper(*s))
+					sendnumeric(client, RPL_NOWOFF,
+					    lp->value.wptr->nick, "*", "*",
+					    (long long)lp->value.wptr->lasttime);
+				lp = lp->next;
+			}
+
+			sendnumeric(client, RPL_ENDOFWATCHLIST, *s);
+
+			continue;
+		}
+
+		/*
+		 * Hmm.. unknown prefix character.. Ignore it. :-)
+		 */
+	}
+}
+
+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/ircd/src/modules/webirc.c b/ircd/src/modules/webirc.c
@@ -0,0 +1,482 @@
+/*
+ * WebIRC / CGI:IRC Support
+ * (C) Copyright 2006-.. 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"
+
+/* Types */
+typedef struct ConfigItem_webirc ConfigItem_webirc;
+
+typedef enum {
+	WEBIRC_PASS=1, WEBIRC_WEBIRC=2
+} WEBIRCType;
+
+struct ConfigItem_webirc {
+	ConfigItem_webirc *prev, *next;
+	ConfigFlag flag;
+	ConfigItem_mask *mask;
+	WEBIRCType type;
+	AuthConfig *auth;
+};
+
+/* Module header */
+ModuleHeader MOD_HEADER
+= {
+	"webirc",
+	"5.0",
+	"WebIRC/CGI:IRC Support",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Global variables */
+ModDataInfo *webirc_md = NULL;
+ConfigItem_webirc *conf_webirc = NULL;
+
+/* Forward declarations */
+CMD_FUNC(cmd_webirc);
+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);
+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);
+
+#define IsWEBIRC(x)			(moddata_client(x, webirc_md).l)
+#define IsWEBIRCSecure(x)	(moddata_client(x, webirc_md).l == 2)
+#define ClearWEBIRC(x)		do { moddata_client(x, webirc_md).l = 0; } while(0)
+#define SetWEBIRC(x)		do { moddata_client(x, webirc_md).l = 1; } while(0)
+#define SetWEBIRCSecure(x)	do { moddata_client(x, webirc_md).l = 2; } while(0)
+
+#define MSG_WEBIRC "WEBIRC"
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, webirc_config_test);
+	return MOD_SUCCESS;
+}
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "webirc";
+	mreq.type = MODDATATYPE_CLIENT;
+	mreq.serialize = webirc_md_serialize;
+	mreq.unserialize = webirc_md_unserialize;
+	mreq.free = webirc_md_free;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	webirc_md = ModDataAdd(modinfo->handle, mreq);
+	if (!webirc_md)
+	{
+		config_error("could not register webirc moddata");
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webirc_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PASS, 0, webirc_local_pass);
+	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, webirc_secure_connect);
+
+	CommandAdd(modinfo->handle, MSG_WEBIRC, cmd_webirc, MAXPARA, CMD_UNREGISTERED);
+		
+	return MOD_SUCCESS;
+}
+
+/** Called upon module load */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Called upon unload */
+MOD_UNLOAD()
+{
+	webirc_free_conf();
+	return MOD_SUCCESS;
+}
+
+void webirc_free_conf(void)
+{
+	ConfigItem_webirc *webirc_ptr, *next;
+
+	for (webirc_ptr = conf_webirc; webirc_ptr; webirc_ptr = next)
+	{
+		next = webirc_ptr->next;
+		delete_webircblock(webirc_ptr);
+	}
+	conf_webirc = NULL;
+}
+
+void delete_webircblock(ConfigItem_webirc *e)
+{
+	unreal_delete_masks(e->mask);
+	if (e->auth)
+		Auth_FreeAuthConfig(e->auth);
+	DelListItem(e, conf_webirc);
+	safe_free(e);
+}
+
+int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	ConfigEntry *cep;
+	int errors = 0;
+	char has_mask = 0; /* mandatory */
+	char has_password = 0; /* mandatory */
+	char has_type = 0; /* optional (used for dup checking) */
+	WEBIRCType webirc_type = WEBIRC_WEBIRC; /* the default */
+
+	if (type != CONFIG_MAIN)
+		return 0;
+	
+	if (!ce)
+		return 0;
+	
+	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->file->filename, ce->line_number);
+		*errs = 1;
+		return -1;
+	}
+
+	if (strcmp(ce->name, "webirc"))
+		return 0; /* not interested in non-webirc stuff.. */
+
+	/* Now actually go parse the webirc { } block */
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error_empty(cep->file->filename, cep->line_number,
+				"webirc", cep->name);
+			errors++;
+			continue;
+		}
+		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
+		{
+			if (cep->value || cep->items)
+				has_mask = 1;
+		}
+		else if (!strcmp(cep->name, "password"))
+		{
+			if (has_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->name, "type"))
+		{
+			if (has_type)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "webirc::type");
+			}
+			has_type = 1;
+			if (!strcmp(cep->value, "webirc"))
+				webirc_type = WEBIRC_WEBIRC;
+			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->file->filename, cep->line_number, cep->value);
+				errors++;
+			}
+		}
+		else
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"webirc", cep->name);
+			errors++;
+		}
+	}
+	if (!has_mask)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"webirc::mask");
+		errors++;
+	}
+
+	if (!has_password && (webirc_type == WEBIRC_WEBIRC))
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"webirc::password");
+		errors++;
+	}
+	
+	if (has_password && (webirc_type == WEBIRC_PASS))
+	{
+		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->file->filename, ce->line_number);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	ConfigItem_webirc *webirc = NULL;
+	
+	if (type != CONFIG_MAIN)
+		return 0;
+	
+	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->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
+			unreal_add_masks(&webirc->mask, cep);
+		else if (!strcmp(cep->name, "password"))
+			webirc->auth = AuthBlockToAuthConfig(cep);
+		else if (!strcmp(cep->name, "type"))
+		{
+			if (!strcmp(cep->value, "webirc"))
+				webirc->type = WEBIRC_WEBIRC;
+			else if (!strcmp(cep->value, "old"))
+				webirc->type = WEBIRC_PASS;
+			else
+				abort();
+		}
+	}
+	
+	AddListItem(webirc, conf_webirc);
+	
+	return 0;
+}
+
+const char *webirc_md_serialize(ModData *m)
+{
+	static char buf[32];
+	if (m->i == 0)
+		return NULL; /* not set */
+	snprintf(buf, sizeof(buf), "%d", m->i);
+	return buf;
+}
+
+void webirc_md_unserialize(const char *str, ModData *m)
+{
+	m->i = atoi(str);
+}
+
+void webirc_md_free(ModData *md)
+{
+	/* we have nothing to free actually, but we must set to zero */
+	md->l = 0;
+}
+
+ConfigItem_webirc *find_webirc(Client *client, const char *password, WEBIRCType type, char **errorstr)
+{
+	ConfigItem_webirc *e;
+	char *error = NULL;
+
+	for (e = conf_webirc; e; e = e->next)
+	{
+		if ((e->type == type) && unreal_mask_match(client, e->mask))
+		{
+			if (type == WEBIRC_WEBIRC)
+			{
+				/* Check password */
+				if (!Auth_Check(client, e->auth, password))
+					error = "CGI:IRC -- Invalid password";
+				else
+					return e; /* Found matching block, return straight away */
+			} else {
+				return e; /* The WEBIRC_PASS type has no password checking */
+			}
+		}
+	}
+
+	if (error)
+		*errorstr = error; /* Invalid password (this error was delayed) */
+	else
+		*errorstr = "CGI:IRC -- No access"; /* No match found */
+
+	return NULL;
+}
+
+#define WEBIRC_STRING     "WEBIRC_"
+#define WEBIRC_STRINGLEN  (sizeof(WEBIRC_STRING)-1)
+
+/* Does the CGI:IRC host spoofing work */
+void dowebirc(Client *client, const char *ip, const char *host, const char *options)
+{
+	char oldip[64];
+	char scratch[64];
+
+	if (IsWEBIRC(client))
+	{
+		exit_client(client, NULL, "Double CGI:IRC request (already identified)");
+		return;
+	}
+
+	if (host && !strcmp(ip, host))
+		host = NULL; /* host did not resolve, make it NULL */
+
+	/* STEP 1: Update client->local->ip
+	   inet_pton() returns 1 on success, 0 on bad input, -1 on bad AF */
+	if (!is_valid_ip(ip))
+	{
+		/* then we have an invalid IP */
+		exit_client(client, NULL, "Invalid IP address");
+		return;
+	}
+
+	/* STEP 2: Update GetIP() */
+	strlcpy(oldip, client->ip, sizeof(oldip));
+	safe_strdup(client->ip, ip);
+		
+	/* STEP 3: Update client->local->hostp */
+	/* (free old) */
+	if (client->local->hostp)
+	{
+		unreal_free_hostent(client->local->hostp);
+		client->local->hostp = NULL;
+	}
+	/* (create new) */
+	if (host && valid_host(host, 1))
+		client->local->hostp = unreal_create_hostent(host, client->ip);
+
+	/* STEP 4: Update sockhost
+	   Make sure that if this any IPv4 address is _not_ prefixed with
+	   "::ffff:" by using Inet_ia2p().
+	 */
+	// Hmm I ignored above warning. May be bad during transition period.
+	strlcpy(client->local->sockhost, client->ip, sizeof(client->local->sockhost));
+
+	SetWEBIRC(client);
+
+	if (options)
+	{
+		char optionsbuf[BUFSIZE];
+		char *name, *p = NULL, *p2;
+		strlcpy(optionsbuf, options, sizeof(optionsbuf));
+		for (name = strtoken(&p, optionsbuf, " "); name; name = strtoken(&p, NULL, " "))
+		{
+			p2 = strchr(name, '=');
+			if (p2)
+				*p2 = '\0';
+			if (!strcmp(name, "secure") && IsSecure(client))
+			{
+				/* The entire [client]--[webirc gw]--[server] chain is secure */
+				SetWEBIRCSecure(client);
+			}
+		}
+	}
+
+	RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
+}
+
+/* WEBIRC <pass> "cgiirc" <hostname> <ip> [:option1 [option2...]]*/
+CMD_FUNC(cmd_webirc)
+{
+	const char *ip, *host, *password, *options;
+	ConfigItem_webirc *e;
+	char *error = NULL;
+
+	if ((parc < 5) || BadPtr(parv[4]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "WEBIRC");
+		return;
+	}
+
+	password = parv[1];
+	host = !DONT_RESOLVE ? parv[3] : parv[4];
+	ip = parv[4];
+	options = parv[5]; /* can be NULL */
+
+	/* Check if allowed host */
+	e = find_webirc(client, password, WEBIRC_WEBIRC, &error);
+	if (!e)
+	{
+		exit_client(client, NULL, error);
+		return;
+	}
+
+	/* And do our job.. */
+	dowebirc(client, ip, host, options);
+}
+
+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)
+		{
+			/* Ok now we got that sorted out, proceed:
+			 * Syntax: WEBIRC_<ip>_<resolvedhostname>
+			 * The <resolvedhostname> has been checked ip->host AND host->ip by CGI:IRC itself
+			 * already so we trust it.
+			 */
+			ip = buf + WEBIRC_STRINGLEN;
+			host = strchr(ip, '_');
+			if (!host)
+			{
+				exit_client(client, NULL, "Invalid CGI:IRC IP received");
+				return HOOK_DENY;
+			}
+			*host++ = '\0';
+		
+			dowebirc(client, ip, host, NULL);
+			return HOOK_DENY;
+		}
+		/* fallthrough if not in webirc block.. */
+	}
+
+	return HOOK_CONTINUE; /* not webirc */
+}
+
+/** Called from register_user() right after setting user +z */
+int webirc_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 (IsWEBIRC(client) && IsSecureConnect(client) && !IsWEBIRCSecure(client))
+		client->umodes &= ~UMODE_SECURE;
+	return 0;
+}
diff --git a/ircd/src/modules/webredir.c b/ircd/src/modules/webredir.c
@@ -0,0 +1,201 @@
+/*
+ * webredir UnrealIRCd module
+ * (C) Copyright 2019 i <info@servx.org> and the UnrealIRCd team
+ *
+ * This module will 301-redirect any clients issuing GET's/POST's during pre-connect stage.
+ *
+ * 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(webredir);
+
+ModuleHeader MOD_HEADER
+  = {
+	"webredir",
+	"1.0",
+	"Do 301 redirect for HEAD/GET/POST/PUT commands", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+struct {
+	char *url;
+} cfg;
+
+static int nowebredir = 1;
+
+static void free_config(void);
+static void init_config(void);
+int webredir_config_posttest(int *errs);
+int webredir_config_test(ConfigFile *, ConfigEntry *, int, int *);
+int webredir_config_run(ConfigFile *, ConfigEntry *, int);
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, webredir_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, webredir_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	init_config();
+	CommandAdd(modinfo->handle, "HEAD", webredir, MAXPARA, CMD_UNREGISTERED);
+	CommandAdd(modinfo->handle, "GET", webredir, MAXPARA, CMD_UNREGISTERED);
+	CommandAdd(modinfo->handle, "POST", webredir, MAXPARA, CMD_UNREGISTERED);
+	CommandAdd(modinfo->handle, "PUT", webredir, MAXPARA, CMD_UNREGISTERED);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webredir_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (SHOWCONNECTINFO)
+	{
+		config_warn("I'm disabling set::options::show-connect-info for you "
+			    "as this setting is incompatible with the webredir module.");
+		SHOWCONNECTINFO = 0;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	free_config();
+	return MOD_SUCCESS;
+}
+
+static void init_config(void)
+{
+	memset(&cfg, 0, sizeof(cfg));
+}
+
+static void free_config(void)
+{
+	safe_free(cfg.url);
+
+	memset(&cfg, 0, sizeof(cfg)); /* needed! */
+}
+
+int webredir_config_posttest(int *errs)
+{
+	int errors = 0;
+
+	if (nowebredir)
+	{
+		config_error("set::webredir is missing!");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int webredir_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	int has_url = 0;
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+
+	/* We are only interrested in set::webredir... */
+	if (!ce || !ce->name || strcmp(ce->name, "webredir"))
+		return 0;
+
+	nowebredir = 0;
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: set::webredir::%s with no value",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		}
+		else if (!strcmp(cep->name, "url"))
+		{
+			if (!*cep->value || strchr(cep->value, ' '))
+			{
+				config_error("%s:%i: set::webredir::%s with empty value",
+					cep->file->filename, cep->line_number, cep->name);
+				errors++;
+			}
+			if (!strstr(cep->value, "://") || !strcmp(cep->value, "https://..."))
+			{
+				config_error("%s:%i: set::webredir::url needs to be a valid URL",
+					cep->file->filename, cep->line_number);
+				errors++;
+			}
+			if (has_url)
+			{
+				config_warn_duplicate(cep->file->filename,
+					cep->line_number, "set::webredir::url");
+				continue;
+			}
+			has_url = 1;
+		}
+		else
+		{
+			config_error("%s:%i: unknown directive set::webredir::%s",
+				cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		}
+	}
+
+	if (!has_url)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"set::webredir::url");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int webredir_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET)
+		return 0;
+	
+	/* We are only interrested in set::webredir... */
+	if (!ce || !ce->name || strcmp(ce->name, "webredir"))
+		return 0;
+	
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "url"))
+		{
+			safe_strdup(cfg.url, cep->value);
+		}
+	}
+	return 1;
+}
+
+CMD_FUNC(webredir)
+{
+	if (!MyConnect(client))
+		return;
+
+	sendto_one(client, NULL, "HTTP/1.1 301 Moved Permanently");
+	sendto_one(client, NULL, "Location: %s\r\n\r\n", cfg.url);
+	dead_socket(client, "Connection closed");
+}
diff --git a/ircd/src/modules/webserver.c b/ircd/src/modules/webserver.c
@@ -0,0 +1,675 @@
+/*
+ * Webserver
+ * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+   
+#include "unrealircd.h"
+#include "dns.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"webserver",
+	"1.0.0",
+	"Webserver",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#if CHAR_MIN < 0
+ #error "In UnrealIRCd char should always be unsigned. Check your compiler"
+#endif
+
+/* How many seconds to wait with closing after sending the response */
+#define WEB_CLOSE_TIME 1
+
+/* The "Server: xyz" in the response */
+#define WEB_SOFTWARE "UnrealIRCd"
+
+/* Macros */
+#define WEB(client)		((WebRequest *)moddata_client(client, webserver_md).ptr)
+#define WEBSERVER(client)	((client->local && client->local->listener) ? client->local->listener->webserver : NULL)
+#define reset_handshake_timeout(client, delta)  do { client->local->creationtime = TStime() - iConf.handshake_timeout + delta; } while(0)
+
+/* Forward declarations */
+int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
+int webserver_packet_in(Client *client, const char *readbuf, int *length);
+void webserver_mdata_free(ModData *m);
+int webserver_handle_packet(Client *client, const char *readbuf, int length);
+int webserver_handle_handshake(Client *client, const char *readbuf, int *length);
+int webserver_handle_request_header(Client *client, const char *readbuf, int *length);
+void _webserver_send_response(Client *client, int status, char *msg);
+void _webserver_close_client(Client *client);
+int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int length);
+
+/* Global variables */
+ModDataInfo *webserver_md;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_SEND_RESPONSE, _webserver_send_response);
+	EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_CLOSE_CLIENT, _webserver_close_client);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSERVER_HANDLE_BODY, _webserver_handle_body);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	//HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, webserver_packet_out);
+	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, webserver_packet_in);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "web";
+	mreq.serialize = NULL;
+	mreq.unserialize = NULL;
+	mreq.free = webserver_mdata_free;
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_CLIENT;
+	webserver_md = ModDataAdd(modinfo->handle, mreq);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** UnrealIRCd internals: free WebRequest object. */
+void webserver_mdata_free(ModData *m)
+{
+	WebRequest *wsu = (WebRequest *)m->ptr;
+	if (wsu)
+	{
+		safe_free(wsu->uri);
+		free_nvplist(wsu->headers);
+		safe_free(wsu->lefttoparse);
+		safe_free(wsu->request_buffer);
+		safe_free(m->ptr);
+	}
+}
+
+/** Outgoing packet hook.
+ * Do we need this?
+ */
+int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
+{
+	static char utf8buf[510];
+
+	if (MyConnect(to) && WEB(to))
+	{
+		// TODO: Inhibit all?
+		// Websocket can override though?
+		return 0;
+	}
+	return 0;
+}
+
+HttpMethod webserver_get_method(const char *buf)
+{
+	if (!strncmp(buf, "HEAD ", 5))
+		return HTTP_METHOD_HEAD;
+	if (!strncmp(buf, "GET ", 4))
+		return HTTP_METHOD_GET;
+	if (!strncmp(buf, "PUT ", 4))
+		return HTTP_METHOD_PUT;
+	if (!strncmp(buf, "POST ", 5))
+		return HTTP_METHOD_POST;
+	return HTTP_METHOD_NONE; /* invalid */
+}
+
+void webserver_possible_request(Client *client, const char *buf, int len)
+{
+	HttpMethod method;
+
+	if (len < 8)
+		return;
+
+	/* Probably redundant, but just to be sure, if already tagged, then don't change it! */
+	if (WEB(client))
+		return;
+
+	method = webserver_get_method(buf);
+	if (method == HTTP_METHOD_NONE)
+		return; /* invalid */
+
+	moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
+	WEB(client)->method = method;
+
+	/* Set some default values: */
+	WEB(client)->content_length = -1;
+	WEB(client)->config_max_request_buffer_size = 4096; /* 4k */
+}
+
+/** Incoming packet hook. This processes web requests.
+ * NOTE The different return values:
+ * -1 means: don't touch this client anymore, it has or might have been killed!
+ * 0 means: don't process this data, but you can read another packet if you want
+ * >0 means: process this data (regular IRC data, non-web stuff)
+ */
+int webserver_packet_in(Client *client, const char *readbuf, int *length)
+{
+	if ((client->local->traffic.messages_received == 0) && WEBSERVER(client))
+		webserver_possible_request(client, readbuf, *length);
+
+	if (!WEB(client))
+		return 1; /* "normal" IRC client */
+
+	if (WEB(client)->request_header_parsed)
+		return WEBSERVER(client)->handle_body(client, WEB(client), readbuf, *length);
+
+	/* else.. */
+	return webserver_handle_request_header(client, readbuf, length);
+}
+
+/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
+int webserver_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request)
+{
+	static char buf[4096], *nextptr;
+	char *p;
+	char *k = NULL, *v = NULL;
+	int foundlf = 0;
+
+	if (buffer)
+	{
+		/* Initialize */
+		if (len > sizeof(buf) - 1)
+			len = sizeof(buf) - 1;
+
+		memcpy(buf, buffer, len);
+		buf[len] = '\0';
+		nextptr = buf;
+	}
+
+	*end_of_request = 0;
+
+	p = nextptr;
+
+	if (!p)
+	{
+		*key = *value = NULL;
+		return 0; /* done processing data */
+	}
+
+	if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2))
+	{
+		*key = *value = NULL;
+		*end_of_request = 1;
+		return 0;
+	}
+
+	/* Note: p *could* point to the NUL byte ('\0') */
+
+	/* Special handling for GET line itself. */
+	if (webserver_get_method(p) != HTTP_METHOD_NONE)
+	{
+		k = "REQUEST";
+		p = strchr(p, ' ') + 1; /* space (0x20) is guaranteed to be there, see strncmp above */
+		v = p; /* SET VALUE */
+		nextptr = NULL; /* set to "we are done" in case next for loop fails */
+		for (; *p; p++)
+		{
+			if (*p == ' ')
+			{
+				*p = '\0'; /* terminate before "HTTP/1.X" part */
+			}
+			else if (*p == '\r')
+			{
+				*p = '\0'; /* eat silently, but don't consider EOL */
+			}
+			else if (*p == '\n')
+			{
+				*p = '\0';
+				nextptr = p+1; /* safe, there is data or at least a \0 there */
+				break;
+			}
+		}
+		*key = k;
+		*value = v;
+		return 1;
+	}
+
+	/* Header parsing starts here.
+	 * Example line "Host: www.unrealircd.org"
+	 */
+	k = p; /* SET KEY */
+
+	/* First check if the line contains a terminating \n. If not, don't process it
+	 * as it may have been a cut header.
+	 */
+	for (; *p; p++)
+	{
+		if (*p == '\n')
+		{
+			foundlf = 1;
+			break;
+		}
+	}
+
+	if (!foundlf)
+	{
+		*key = *value = NULL;
+		*lastloc = k;
+		return 0;
+	}
+
+	p = k;
+
+	for (; *p; p++)
+	{
+		if ((*p == '\n') || (*p == '\r'))
+		{
+			/* Reached EOL but 'value' not found */
+			*p = '\0';
+			break;
+		}
+		if (*p == ':')
+		{
+			*p++ = '\0';
+			if (*p++ != ' ')
+				break; /* missing mandatory space after ':' */
+
+			v = p; /* SET VALUE */
+			nextptr = NULL; /* set to "we are done" in case next for loop fails */
+			for (; *p; p++)
+			{
+				if (*p == '\r')
+				{
+					*p = '\0'; /* eat silently, but don't consider EOL */
+				}
+				else if (*p == '\n')
+				{
+					*p = '\0';
+					nextptr = p+1; /* safe, there is data or at least a \0 there */
+					break;
+				}
+			}
+			/* A key-value pair was succesfully parsed, return it */
+			*key = k;
+			*value = v;
+			return 1;
+		}
+	}
+
+	/* Fatal parse error */
+	*key = *value = NULL;
+	return 0;
+}
+
+/** Check if there is any data at the end of the request */
+char *find_end_of_request(char *header, int totalsize, int *remaining_bytes)
+{
+	char *nextframe1;
+	char *nextframe2;
+	char *nextframe = NULL;
+
+	// find first occurance, yeah this is just stupid, but it works.
+	nextframe1 = strstr(header, "\r\n\r\n"); // = +4
+	nextframe2 = strstr(header, "\n\n");     // = +2
+	if (nextframe1 && nextframe2)
+	{
+		if (nextframe1 < nextframe2)
+		{
+			nextframe = nextframe1 + 4;
+		} else {
+			nextframe = nextframe2 + 2;
+		}
+	} else
+	if (nextframe1)
+	{
+		nextframe = nextframe1 + 4;
+	} else
+	if (nextframe2)
+	{
+		nextframe = nextframe2 + 2;
+	}
+	if (nextframe)
+	{
+		*remaining_bytes = totalsize - (nextframe - header);
+		if (*remaining_bytes > 0)
+			return nextframe;
+	}
+	return NULL;
+}
+
+/** Handle HTTP request
+ * Yes, I'm going to assume that the header fits in one packet and one packet only.
+ */
+int webserver_handle_request_header(Client *client, const char *readbuf, int *length)
+{
+	char *key, *value;
+	int r, end_of_request;
+	static char netbuf[16384];
+	static char netbuf2[16384];
+	char *lastloc = NULL;
+	int n, maxcopy, nprefix=0;
+	int totalsize;
+
+	/* Totally paranoid: */
+	memset(netbuf, 0, sizeof(netbuf));
+	memset(netbuf2, 0, sizeof(netbuf2));
+
+	/** Frame re-assembling starts here **/
+	if (WEB(client)->lefttoparse)
+	{
+		strlcpy(netbuf, WEB(client)->lefttoparse, sizeof(netbuf));
+		nprefix = strlen(netbuf);
+	}
+	maxcopy = sizeof(netbuf) - nprefix - 1;
+	/* (Need to do some manual checking here as strlen() can't be safely used
+	 *  on readbuf. Same is true for strlncat since it uses strlen().)
+	 */
+	n = *length;
+	if (n > maxcopy)
+		n = maxcopy;
+	if (n <= 0)
+	{
+		webserver_close_client(client); // Oversized line
+		return -1;
+	}
+	memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
+	totalsize = n + nprefix;
+	netbuf[totalsize] = '\0';
+	memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc".
+	safe_free(WEB(client)->lefttoparse);
+
+	/** Now step through the lines.. **/
+	for (r = webserver_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
+	     r;
+	     r = webserver_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request))
+	{
+		if (BadPtr(value))
+			continue; /* skip empty values */
+
+		if (!strcasecmp(key, "REQUEST"))
+		{
+			safe_strdup(WEB(client)->uri, value);
+		} else
+		{
+			if (!strcasecmp(key, "Content-Length"))
+			{
+				WEB(client)->content_length = atoll(value);
+			} else
+			if (!strcasecmp(key, "Transfer-Encoding"))
+			{
+				if (!strcasecmp(value, "chunked"))
+					WEB(client)->transfer_encoding = TRANSFER_ENCODING_CHUNKED;
+			}
+			add_nvplist(&WEB(client)->headers, WEB(client)->num_headers, key, value);
+		}
+	}
+
+	if (end_of_request)
+	{
+		int n;
+		int remaining_bytes = 0;
+		char *nextframe;
+
+		/* Some sanity checks */
+		if (!WEB(client)->uri)
+		{
+			webserver_send_response(client, 400, "Malformed HTTP request");
+			return -1;
+		}
+
+		WEB(client)->request_header_parsed = 1;
+		n = WEBSERVER(client)->handle_request(client, WEB(client));
+		if ((n <= 0) || IsDead(client))
+			return n; /* byebye */
+		
+		/* There could be data directly after the request header (eg for
+		 * a POST or PUT), check for it here so it isn't lost.
+		 */
+		nextframe = find_end_of_request(netbuf2, totalsize, &remaining_bytes);
+		if (nextframe)
+			return WEBSERVER(client)->handle_body(client, WEB(client), nextframe, remaining_bytes);
+		return 0;
+	}
+
+	if (lastloc)
+	{
+		/* Last line was cut somewhere, save it for next round. */
+		safe_strdup(WEB(client)->lefttoparse, lastloc);
+	}
+	return 0; /* don't let UnrealIRCd process this */
+}
+
+/** Send a HTTP(S) response.
+ * @param client	Client to send to
+ * @param status	HTTP status code
+ * @param msg		The message body.
+ * @note if 'msgs' is NULL then don't close the connection.
+ */
+void _webserver_send_response(Client *client, int status, char *msg)
+{
+	char buf[512];
+	char *statusmsg = "???";
+
+	if (status == 200)
+		statusmsg = "OK";
+	else if (status == 201)
+		statusmsg = "Created";
+	else if (status == 500)
+		statusmsg = "Internal Server Error";
+	else if (status == 400)
+		statusmsg = "Bad Request";
+	else if (status == 401)
+		statusmsg = "Unauthorized";
+	else if (status == 403)
+		statusmsg = "Forbidden";
+	else if (status == 404)
+		statusmsg = "Not Found";
+	else if (status == 416)
+		statusmsg = "Range Not Satisfiable";
+
+	snprintf(buf, sizeof(buf),
+		"HTTP/1.1 %d %s\r\nServer: %s\r\nConnection: close\r\n\r\n",
+		status, statusmsg, WEB_SOFTWARE);
+	if (msg)
+	{
+		strlcat(buf, msg, sizeof(buf));
+		strlcat(buf, "\n", sizeof(buf));
+	}
+
+	dbuf_put(&client->local->sendQ, buf, strlen(buf));
+	if (msg)
+		webserver_close_client(client);
+}
+
+/** Close a web client softly, after data has been sent. */
+void _webserver_close_client(Client *client)
+{
+	send_queued(client);
+	if (DBufLength(&client->local->sendQ) == 0)
+	{
+		exit_client(client, NULL, "End of request");
+		//dead_socket(client, "");
+	} else {
+		send_queued(client);
+		reset_handshake_timeout(client, WEB_CLOSE_TIME);
+	}
+}
+
+int webserver_handle_body_append_buffer(Client *client, const char *buf, int len)
+{
+	/* Guard.. */
+	if (len <= 0)
+	{
+		dead_socket(client, "HTTP request error");
+		return 0;
+	}
+
+	if (WEB(client)->request_buffer)
+	{
+		long long newsize = WEB(client)->request_buffer_size + len + 1;
+		if (newsize > WEB(client)->config_max_request_buffer_size)
+		{
+			/* We would overflow */
+			unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
+			           "[webserver] Client $client: request body too large ($length)",
+			           log_data_integer("length", newsize));
+			dead_socket(client, "");
+			return 0;
+		}
+		WEB(client)->request_buffer = realloc(WEB(client)->request_buffer, newsize);
+	} else
+	{
+		if (len + 1 > WEB(client)->config_max_request_buffer_size)
+		{
+			/* We would overflow */
+			unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
+			           "[webserver] Client $client: request body too large ($length)",
+			           log_data_integer("length", len+1));
+			dead_socket(client, "");
+			return 0;
+		}
+		WEB(client)->request_buffer = malloc(len+1);
+	}
+	memcpy(WEB(client)->request_buffer + WEB(client)->request_buffer_size, buf, len);
+	WEB(client)->request_buffer_size += len;
+	WEB(client)->request_buffer[WEB(client)->request_buffer_size] = '\0';
+	return 1;
+}
+
+/** Handle HTTP body parsing, eg for a PUT request, concatting it all together.
+ * @param client	The client
+ * @param web		The WEB(client)
+ * @param readbuf	Packet in the read buffer
+ * @param pktsize	Packet size of the read buffer
+ * @return 1 to continue processing, 0 if client is killed.
+ */
+int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int pktsize)
+{
+	char *buf;
+	long long n;
+	char *free_this_buffer = NULL;
+
+	if (WEB(client)->transfer_encoding == TRANSFER_ENCODING_NONE)
+	{
+		if (!webserver_handle_body_append_buffer(client, readbuf, pktsize))
+			return 0;
+
+		if ((WEB(client)->content_length >= 0) &&
+		    (WEB(client)->request_buffer_size >= WEB(client)->content_length))
+		{
+			WEB(client)->request_body_complete = 1;
+		}
+		return 1;
+	}
+
+	/* Fill 'buf' nd set 'buflen' with what we had + what we have now.
+	 * Makes things easy.
+	 */
+	if (WEB(client)->lefttoparse)
+	{
+		n = WEB(client)->lefttoparselen + pktsize;
+		free_this_buffer = buf = safe_alloc(n);
+		memcpy(buf, WEB(client)->lefttoparse, WEB(client)->lefttoparselen);
+		memcpy(buf+WEB(client)->lefttoparselen, readbuf, pktsize);
+		safe_free(WEB(client)->lefttoparse);
+		WEB(client)->lefttoparselen = 0;
+	} else {
+		n = pktsize;
+		free_this_buffer = buf = safe_alloc(n);
+		memcpy(buf, readbuf, n);
+	}
+
+	/* Chunked transfers.. yayyyy.. */
+	while (n > 0)
+	{
+		if (WEB(client)->chunk_remaining > 0)
+		{
+			/* Eat it */
+			int eat = MIN(WEB(client)->chunk_remaining, n);
+			if (!webserver_handle_body_append_buffer(client, buf, eat))
+			{
+				/* fatal error such as size exceeded */
+				safe_free(free_this_buffer);
+				return 0;
+			}
+			n -= eat;
+			buf += eat;
+			WEB(client)->chunk_remaining -= eat;
+		} else
+		{
+			int gotlf = 0;
+			int i;
+
+			/* First check if it is a (trailing) empty line,
+			 * eg from a previous chunk. Skip over.
+			 */
+			if ((n >= 2) && !strncmp(buf, "\r\n", 2))
+			{
+				buf += 2;
+				n -= 2;
+			} else
+			if ((n >= 1) && !strncmp(buf, "\n", 1))
+			{
+				buf++;
+				n--;
+			}
+
+			/* Now we are (possibly) at the chunk size line,
+			 * this is or example '7f' + newline.
+			 * So first, check if we have a newline at all.
+			 */
+			for (i=0; i < n; i++)
+			{
+				if (buf[i] == '\n')
+				{
+					gotlf = 1;
+					break;
+				}
+			}
+			if (!gotlf)
+			{
+				/* The line telling us the chunk size is incomplete,
+				 * as it does not contain an \n. Wait for more data
+				 * from the network socket.
+				 */
+				if (n > 0)
+				{
+					/* Store what we have first.. */
+					WEB(client)->lefttoparselen = n;
+					WEB(client)->lefttoparse = safe_alloc(n);
+					memcpy(WEB(client)->lefttoparse, buf, n);
+				}
+				safe_free(free_this_buffer);
+				return 1; /* WE WANT MORE! */
+			}
+			buf[i] = '\0'; /* cut at LF */
+			i++; /* point to next data */
+			WEB(client)->chunk_remaining = strtol(buf, NULL, 16);
+			if (WEB(client)->chunk_remaining < 0)
+			{
+				unreal_log(ULOG_WARNING, "webserver", "WEB_NEGATIVE_CHUNK", client,
+				           "Webrequest from $client: Negative chunk encountered");
+				safe_free(free_this_buffer);
+				dead_socket(client, "");
+				return 0;
+			}
+			if (WEB(client)->chunk_remaining == 0)
+			{
+				/* DONE! */
+				WEB(client)->request_body_complete = 1;
+				safe_free(free_this_buffer);
+				return 1;
+			}
+			buf += i;
+			n -= i;
+		}
+	}
+
+	safe_free(free_this_buffer);
+	return 1;
+}
diff --git a/ircd/src/modules/websocket.c b/ircd/src/modules/websocket.c
@@ -0,0 +1,688 @@
+/*
+ * websocket - WebSocket support (RFC6455)
+ * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ * This module was sponsored by Aberrant Software Inc.
+ */
+   
+#include "unrealircd.h"
+#include "dns.h"
+
+#define WEBSOCKET_VERSION "1.1.0"
+
+ModuleHeader MOD_HEADER
+  = {
+	"websocket",
+	WEBSOCKET_VERSION,
+	"WebSocket support (RFC6455)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#if CHAR_MIN < 0
+ #error "In UnrealIRCd char should always be unsigned. Check your compiler"
+#endif
+
+#ifndef WEBSOCKET_SEND_BUFFER_SIZE
+ #define WEBSOCKET_SEND_BUFFER_SIZE 16384
+#endif
+
+#define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
+
+#define WEBSOCKET_PORT(client)	((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
+#define WEBSOCKET_TYPE(client)	(WSU(client)->type)
+
+/* 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_posttest(int *);
+int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
+int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
+int websocket_handle_handshake(Client *client, const char *readbuf, int *length);
+int websocket_handshake_send_response(Client *client);
+int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
+int websocket_secure_connect(Client *client);
+struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input);
+int websocket_ip_compare(const char *ip1, const char *ip2);
+int websocket_handle_request(Client *client, WebRequest *web);
+
+/* Global variables */
+ModDataInfo *websocket_md;
+static int ws_text_mode_available = 1;
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, websocket_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, websocket_config_posttest);
+
+	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
+	if (!websocket_md)
+		config_warn("The 'websocket_common' module is not loaded, even though it was promised to be ???");
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, websocket_config_run_ex);
+	HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, websocket_packet_out);
+	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, websocket_secure_connect);
+
+	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (non_utf8_nick_chars_in_use || (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY))
+		ws_text_mode_available = 0;
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+	int has_type = 0;
+	static char errored_once_nick = 0;
+
+	if (type != CONFIG_LISTEN_OPTIONS)
+		return 0;
+
+	/* We are only interrested in listen::options::websocket.. */
+	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "type"))
+		{
+			CheckNull(cep);
+			has_type = 1;
+			if (!strcmp(cep->value, "text"))
+			{
+				if (non_utf8_nick_chars_in_use && !errored_once_nick)
+				{
+					/* This one is a hard error, since the consequences are grave */
+					config_error("You have a websocket listener with type 'text' AND your set::allowed-nickchars contains at least one non-UTF8 character set.");
+					config_error("This is a very BAD idea as this makes your websocket vulnerable to UTF8 conversion attacks. "
+					             "This can cause things like unkickable users and 'ghosts' for websocket users.");
+					config_error("You have 4 options: 1) Remove the websocket listener, 2) Use websocket type 'binary', "
+					             "3) Remove the non-UTF8 character set from set::allowed-nickchars, 4) Replace the non-UTF8 with an UTF8 character set in set::allowed-nickchars");
+					config_error("For more details see https://www.unrealircd.org/docs/WebSocket_support#websockets-and-non-utf8");
+					errored_once_nick = 1;
+					errors++;
+				}
+			}
+			else if (!strcmp(cep->value, "binary"))
+			{
+			}
+			else
+			{
+				config_error("%s:%i: listen::options::websocket::type must be either 'binary' or 'text' (not '%s')",
+					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->file->filename, cep->line_number, cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	if (!has_type)
+	{
+		config_error("%s:%i: websocket set, but type unspecified. Use something like: listen { ip *; port 443; websocket { type text; } }",
+			ce->file->filename, ce->line_number);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
+{
+	ConfigEntry *cep, *cepp;
+	ConfigItem_listen *l;
+	static char warned_once_channel = 0;
+
+	if (type != CONFIG_LISTEN_OPTIONS)
+		return 0;
+
+	/* We are only interrested in listen::options::websocket.. */
+	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
+		return 0;
+
+	l = (ConfigItem_listen *)ptr;
+	l->webserver = safe_alloc(sizeof(WebServer));
+	l->webserver->handle_request = websocket_handle_request;
+	l->webserver->handle_body = websocket_handle_body_websocket;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "type"))
+		{
+			if (!strcmp(cep->value, "binary"))
+				l->websocket_options = WEBSOCKET_TYPE_BINARY;
+			else if (!strcmp(cep->value, "text"))
+			{
+				l->websocket_options = WEBSOCKET_TYPE_TEXT;
+				if ((tempiConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY) && !warned_once_channel)
+				{
+					/* This one is a warning, since the consequences are less grave than with nicks */
+					config_warn("You have a websocket listener with type 'text' AND your set::allowed-channelchars is set to 'any'.");
+					config_warn("This is not a recommended combination as this makes your websocket vulnerable to UTF8 conversion attacks. "
+					            "This can cause things like unpartable channels for websocket users.");
+					config_warn("It is highly recommended that you use set { allowed-channelchars utf8; }");
+					config_warn("For more details see https://www.unrealircd.org/docs/WebSocket_support#websockets-and-non-utf8");
+					warned_once_channel = 1;
+				}
+			}
+		} else if (!strcmp(cep->name, "forward"))
+		{
+			safe_strdup(l->websocket_forward, cep->value);
+		}
+	}
+	return 1;
+}
+
+int websocket_config_posttest(int *errs)
+{
+	int errors = 0;
+	char webserver_module = 1, websocket_common_module = 1;
+
+	if (!is_module_loaded("webserver"))
+	{
+		config_error("The 'websocket' module requires the 'webserver' module to be loaded, otherwise websocket connections will not work!");
+		webserver_module = 0;
+		errors++;
+	}
+
+	if (!is_module_loaded("websocket_common"))
+	{
+		config_error("The 'websocket' module requires the 'websocket_common' module to be loaded, otherwise websocket connections will not work!");
+		websocket_common_module = 0;
+		errors++;
+	}
+
+	/* Is nicer for the admin when these are grouped... */
+	if (!webserver_module)
+		config_error("Add the following line to your config file: loadmodule \"webserver\";");
+	if (!websocket_common_module)
+		config_error("Add the following line to your config file: loadmodule \"websocket_common\";");
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+/* Add LF (if needed) to a buffer. Max 4K. */
+void add_lf_if_needed(char **buf, int *len)
+{
+	static char newbuf[MAXLINELENGTH];
+	char *b = *buf;
+	int l = *len;
+
+	if (l <= 0)
+		return; /* too short */
+
+	if (b[l - 1] == '\n')
+		return; /* already contains \n */
+
+	if (l >= sizeof(newbuf)-2)
+		l = sizeof(newbuf)-2; /* cut-off if necessary */
+
+	memcpy(newbuf, b, l);
+	newbuf[l] = '\n';
+	newbuf[l + 1] = '\0'; /* not necessary, but I like zero termination */
+	l++;
+	*buf = newbuf; /* new buffer */
+	*len = l; /* new length */
+}
+
+/** Called on decoded websocket frame (INPUT).
+ * Should contain exactly 1 IRC line (command)
+ */
+int websocket_irc_callback(Client *client, char *buf, int len)
+{
+	add_lf_if_needed(&buf, &len);
+	if (!process_packet(client, buf, len, 1)) /* Let UnrealIRCd handle this as usual */
+		return 0; /* client killed */
+	return 1;
+}
+
+int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
+{
+	return websocket_handle_websocket(client, web, readbuf2, length2, websocket_irc_callback);
+}
+
+/** Outgoing packet hook.
+ * This transforms the output to be Websocket-compliant, if necessary.
+ */
+int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
+{
+	static char utf8buf[510];
+
+	if (MyConnect(to) && !IsRPC(to) && websocket_md && WSU(to) && WSU(to)->handshake_completed)
+	{
+		if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
+			websocket_create_packet(WSOP_BINARY, msg, length);
+		else if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_TEXT)
+		{
+			/* Some more conversions are needed */
+			char *safe_msg = unrl_utf8_make_valid(*msg, utf8buf, sizeof(utf8buf), 1);
+			*msg = safe_msg;
+			*length = *msg ? strlen(safe_msg) : 0;
+			websocket_create_packet(WSOP_TEXT, msg, length);
+		}
+		return 0;
+	}
+	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;
+}
+
+/** We got a HTTP(S) request and we need to check if we can upgrade the connection
+ * to a websocket connection.
+ */
+int websocket_handle_request(Client *client, WebRequest *web)
+{
+	NameValuePrioList *r;
+	const char *key, *value;
+
+	/* Allocate a new WebSocketUser struct for this session */
+	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
+	/* ...and set the default protocol (text or binary) */
+	WSU(client)->type = client->local->listener->websocket_options;
+
+	/** Now step through the lines.. **/
+	for (r = web->headers; r; r = r->next)
+	{
+		key = r->name;
+		value = r->value;
+		if (!strcasecmp(key, "Sec-WebSocket-Key"))
+		{
+			if (strchr(value, ':'))
+			{
+				/* This would cause unserialization issues. Should be base64 anyway */
+				webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
+				return -1;
+			}
+			safe_strdup(WSU(client)->handshake_key, value);
+		} else
+		if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
+		{
+			/* Save it here, will be processed later */
+			safe_strdup(WSU(client)->sec_websocket_protocol, value);
+		} else
+		if (!strcasecmp(key, "Forwarded"))
+		{
+			/* will be processed later too */
+			safe_strdup(WSU(client)->forwarded, value);
+		}
+	}
+
+	/** Finally, validate the websocket request (handshake) and proceed or reject. */
+
+	/* Not websocket and webredir loaded? Let that module serve a redirect. */
+	if (!WSU(client)->handshake_key)
+	{
+		if (is_module_loaded("webredir"))
+		{
+			const char *parx[2] = { NULL, NULL };
+			do_cmd(client, NULL, "GET", 1, parx);
+		}
+		webserver_send_response(client, 404, "This port is for IRC WebSocket only");
+		return 0;
+	}
+
+	/* Sec-WebSocket-Protocol (optional) */
+	if (WSU(client)->sec_websocket_protocol)
+	{
+		char *p = NULL, *name;
+		int negotiated = 0;
+
+		for (name = strtoken(&p, WSU(client)->sec_websocket_protocol, ",");
+		     name;
+		     name = strtoken(&p, NULL, ","))
+		{
+			skip_whitespace(&name);
+			if (!strcmp(name, "binary.ircv3.net"))
+			{
+				negotiated = WEBSOCKET_TYPE_BINARY;
+				break; /* First hit wins */
+			} else
+			if (!strcmp(name, "text.ircv3.net") && ws_text_mode_available)
+			{
+				negotiated = WEBSOCKET_TYPE_TEXT;
+				break; /* First hit wins */
+			}
+		}
+		if (negotiated == WEBSOCKET_TYPE_BINARY)
+		{
+			WSU(client)->type = WEBSOCKET_TYPE_BINARY;
+			safe_strdup(WSU(client)->sec_websocket_protocol, "binary.ircv3.net");
+		} else
+		if (negotiated == WEBSOCKET_TYPE_TEXT)
+		{
+			WSU(client)->type = WEBSOCKET_TYPE_TEXT;
+			safe_strdup(WSU(client)->sec_websocket_protocol, "text.ircv3.net");
+		} else
+		{
+			/* Negotiation failed, fallback to the default (don't set it here) */
+			safe_free(WSU(client)->sec_websocket_protocol);
+		}
+	}
+
+	/* Check forwarded header (by k4be) */
+	if (WSU(client)->forwarded)
+	{
+		struct HTTPForwardedHeader *forwarded;
+		char oldip[64];
+
+		/* check for source ip */
+		if (BadPtr(client->local->listener->websocket_forward) || !websocket_ip_compare(client->local->listener->websocket_forward, client->ip))
+		{
+			unreal_log(ULOG_WARNING, "websocket", "UNAUTHORIZED_FORWARDED_HEADER", client, "Received unauthorized Forwarded header from $ip", log_data_string("ip", client->ip));
+			webserver_send_response(client, 403, "Forwarded: no access");
+			return 0;
+		}
+		/* parse the header */
+		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));
+			webserver_send_response(client, 400, "Forwarded: invalid IP");
+			return 0;
+		}
+		/* store data */
+		WSU(client)->secure = forwarded->secure;
+		strlcpy(oldip, client->ip, sizeof(oldip));
+		safe_strdup(client->ip, forwarded->ip);
+		/* Update client->local->hostp */
+		strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
+		/* (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 */
+			}
+		}
+		RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
+	}
+
+	websocket_handshake_send_response(client);
+	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) && websocket_md && WSU(client) && WSU(client)->forwarded && !WSU(client)->secure)
+		client->umodes &= ~UMODE_SECURE;
+	return 0;
+}
+
+/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
+int websocket_handshake_send_response(Client *client)
+{
+	char buf[512], hashbuf[64];
+	char sha1out[20]; /* 160 bits */
+
+	WSU(client)->handshake_completed = 1;
+
+	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
+	sha1hash_binary(sha1out, buf, strlen(buf));
+	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
+
+	snprintf(buf, sizeof(buf),
+	         "HTTP/1.1 101 Switching Protocols\r\n"
+	         "Upgrade: websocket\r\n"
+	         "Connection: Upgrade\r\n"
+	         "Sec-WebSocket-Accept: %s\r\n",
+	         hashbuf);
+
+	if (WSU(client)->sec_websocket_protocol)
+	{
+		/* using strlen() is safe here since above buffer will not
+		 * cause it to be >=512 and thus we won't get into negatives.
+		 */
+		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
+		         "Sec-WebSocket-Protocol: %s\r\n",
+		         WSU(client)->sec_websocket_protocol);
+	}
+
+	strlcat(buf, "\r\n", sizeof(buf));
+
+	/* Caution: we bypass sendQ flood checking by doing it this way.
+	 * Risk is minimal, though, as we only permit limited text only
+	 * once per session.
+	 */
+	dbuf_put(&client->local->sendQ, buf, strlen(buf));
+	send_queued(client);
+
+	return 0;
+}
+
+/** 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/ircd/src/modules/websocket_common.c b/ircd/src/modules/websocket_common.c
@@ -0,0 +1,523 @@
+/*
+ * websocket_common - Common WebSocket functions (RFC6455)
+ * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ * The websocket module was sponsored by Aberrant Software Inc.
+ */
+   
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"websocket_common",
+	"6.0.0",
+	"WebSocket support (RFC6455)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#if CHAR_MIN < 0
+ #error "In UnrealIRCd char should always be unsigned. Check your compiler"
+#endif
+
+#ifndef WEBSOCKET_SEND_BUFFER_SIZE
+ #define WEBSOCKET_SEND_BUFFER_SIZE 16384
+#endif
+
+#define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
+
+/* used to parse http Forwarded header (RFC 7239) */
+#define IPLEN 48
+#define FHEADER_NAMELEN	20
+
+struct HTTPForwardedHeader
+{
+	int secure;
+	char hostname[HOSTLEN+1];
+	char ip[IPLEN+1];
+};
+
+/* Forward declarations - public functions */
+int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
+int _websocket_create_packet(int opcode, char **buf, int *len);
+int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
+int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
+/* Forward declarations - other */
+int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
+int websocket_handle_packet_ping(Client *client, const char *buf, int len);
+int websocket_handle_packet_pong(Client *client, const char *buf, int len);
+int websocket_send_pong(Client *client, const char *buf, int len);
+const char *websocket_mdata_serialize(ModData *m);
+void websocket_mdata_unserialize(const char *str, ModData *m);
+void websocket_mdata_free(ModData *m);
+
+/* Global variables */
+ModDataInfo *websocket_md;
+static int ws_text_mode_available = 1;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);
+
+	/* Init first, since we manage sockets */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "websocket";
+	mreq.serialize = websocket_mdata_serialize;
+	mreq.unserialize = websocket_mdata_unserialize;
+	mreq.free = websocket_mdata_free;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	websocket_md = ModDataAdd(modinfo->handle, mreq);
+
+	/* Unload last, since we manage sockets */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
+{
+	int n;
+	char *ptr;
+	int length;
+	int length1 = WSU(client)->lefttoparselen;
+	char readbuf[MAXLINELENGTH];
+
+	length = length1 + length2;
+	if (length > sizeof(readbuf)-1)
+	{
+		dead_socket(client, "Illegal buffer stacking/Excess flood");
+		return 0;
+	}
+
+	if (length1 > 0)
+		memcpy(readbuf, WSU(client)->lefttoparse, length1);
+	memcpy(readbuf+length1, readbuf2, length2);
+
+	safe_free(WSU(client)->lefttoparse);
+	WSU(client)->lefttoparselen = 0;
+
+	ptr = readbuf;
+	do {
+		n = websocket_handle_packet(client, ptr, length, callback);
+		if (n < 0)
+			return -1; /* killed -- STOP processing */
+		if (n == 0)
+		{
+			/* Short read. Stop processing for now, but save data for next time */
+			safe_free(WSU(client)->lefttoparse);
+			WSU(client)->lefttoparse = safe_alloc(length);
+			WSU(client)->lefttoparselen = length;
+			memcpy(WSU(client)->lefttoparse, ptr, length);
+			return 0;
+		}
+		length -= n;
+		ptr += n;
+		if (length < 0)
+			abort(); /* less than 0 is impossible */
+	} while(length > 0);
+
+	return 0;
+}
+
+/** WebSocket packet handler.
+ * For more information on the format, check out page 28 of RFC6455.
+ * @returns The number of bytes processed (the size of the frame)
+ *          OR 0 to indicate a possible short read (want more data)
+ *          OR -1 in case of an error.
+ */
+int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len))
+{
+	char opcode; /**< Opcode */
+	char masked; /**< Masked */
+	int len; /**< Length of the packet */
+	char maskkey[4]; /**< Key used for masking */
+	const char *p;
+	int total_packet_size;
+	char *payload = NULL;
+	static char payloadbuf[READBUF_SIZE];
+	int maskkeylen = 4;
+
+	if (length < 4)
+	{
+		/* WebSocket packet too short */
+		return 0;
+	}
+
+	/* fin    = readbuf[0] & 0x80; -- unused */
+	opcode = readbuf[0] & 0x7F;
+	masked = readbuf[1] & 0x80;
+	len    = readbuf[1] & 0x7F;
+	p = &readbuf[2]; /* point to next element */
+
+	/* actually 'fin' is unused.. we don't care. */
+
+	/* Masked. According to RFC6455 page 29:
+	 * "All frames sent from client to server have this bit set to 1."
+	 * But in practice i see that for PONG this may not always be
+	 * true, so let's make an exception for that...
+	 */
+	if (!masked && (opcode != WSOP_PONG))
+	{
+		dead_socket(client, "WebSocket packet not masked");
+		return -1; /* Having the masked bit set is required (RFC6455 p29) */
+	}
+
+	if (!masked)
+		maskkeylen = 0;
+
+	if (len == 127)
+	{
+		dead_socket(client, "WebSocket packet with insane size");
+		return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
+	}
+
+	total_packet_size = len + 2 + maskkeylen; /* 2 for header, 4 for mask key, rest for payload */
+
+	/* Early (minimal) length check */
+	if (length < total_packet_size)
+	{
+		/* WebSocket frame too short */
+		return 0;
+	}
+
+	/* Len=126 is special. It indicates the data length is actually "126 or more" */
+	if (len == 126)
+	{
+		/* Extended payload length (16 bit). For packets of >=126 bytes */
+		len = (readbuf[2] << 8) + readbuf[3];
+		if (len < 126)
+		{
+			dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
+			return -1; /* This is a violation (not a short read), see page 29 */
+		}
+		p += 2; /* advance pointer 2 bytes */
+
+		/* Need to check the length again, now it has changed: */
+		if (length < len + 4 + maskkeylen)
+		{
+			/* WebSocket frame too short */
+			return 0;
+		}
+		/* And update the packet size */
+		total_packet_size = len + 4 + maskkeylen; /* 4 for header, 4 for mask key, rest for payload */
+	}
+
+	if (masked)
+	{
+		memcpy(maskkey, p, maskkeylen);
+		p+= maskkeylen;
+	}
+
+	if (len > 0)
+	{
+		memcpy(payloadbuf, p, len);
+		payload = payloadbuf;
+	} /* else payload is NULL */
+
+	if (masked && (len > 0))
+	{
+		/* Unmask this thing (page 33, section 5.3) */
+		int n;
+		char v;
+		char *p;
+		for (p = payload, n = 0; n < len; n++)
+		{
+			v = *p;
+			*p++ = v ^ maskkey[n % 4];
+		}
+	}
+
+	switch(opcode)
+	{
+		case WSOP_CONTINUATION:
+		case WSOP_TEXT:
+		case WSOP_BINARY:
+			if (len > 0)
+			{
+				if (!callback(client, payload, len))
+					return -1; /* fatal error occured (such as flood kill) */
+			}
+			return total_packet_size;
+
+		case WSOP_CLOSE:
+			dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
+			return -1;
+
+		case WSOP_PING:
+			if (websocket_handle_packet_ping(client, payload, len) < 0)
+				return -1;
+			return total_packet_size;
+
+		case WSOP_PONG:
+			if (websocket_handle_packet_pong(client, payload, len) < 0)
+				return -1;
+			return total_packet_size;
+
+		default:
+			dead_socket(client, "WebSocket: Unknown opcode");
+			return -1;
+	}
+
+	return -1; /* NOTREACHED */
+}
+
+int websocket_handle_packet_ping(Client *client, const char *buf, int len)
+{
+	if (len > 500)
+	{
+		dead_socket(client, "WebSocket: oversized PING request");
+		return -1;
+	}
+	websocket_send_pong(client, buf, len);
+	add_fake_lag(client, 1000); /* lag penalty of 1 second */
+	return 0;
+}
+
+int websocket_handle_packet_pong(Client *client, const char *buf, int len)
+{
+	/* We only care about pongs for RPC websocket connections.
+	 * Also, we don't verify the content, actually,
+	 * so don't use this for security like a pingpong cookie.
+	 */
+	if (IsRPC(client))
+	{
+		client->local->last_msg_received = TStime();
+		ClearPingSent(client);
+	}
+	return 0;
+}
+
+/** Create a simple websocket packet that is ready to be sent.
+ * This is the simple version that is used ONLY for WSOP_PONG,
+ * as it does not take \r\n into account.
+ */
+int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
+{
+	static char sendbuf[8192];
+
+	sendbuf[0] = opcode | 0x80; /* opcode & final */
+
+	if (*len > sizeof(sendbuf) - 8)
+		return -1; /* should never happen (safety) */
+
+	if (*len < 126)
+	{
+		/* Short payload */
+		sendbuf[1] = (char)*len;
+		memcpy(&sendbuf[2], *buf, *len);
+		*buf = sendbuf;
+		*len += 2;
+	} else {
+		/* Long payload */
+		sendbuf[1] = 126;
+		sendbuf[2] = (char)((*len >> 8) & 0xFF);
+		sendbuf[3] = (char)(*len & 0xFF);
+		memcpy(&sendbuf[4], *buf, *len);
+		*buf = sendbuf;
+		*len += 4;
+	}
+	return 0;
+}
+
+/** Create a websocket packet that is ready to be send.
+ * This version takes into account stripping off \r and \n,
+ * and possibly multi line due to labeled-response.
+ * It is used for WSOP_TEXT and WSOP_BINARY.
+ * The end result is one or more websocket frames,
+ * all in a single packet *buf with size *len.
+ *
+ * This is the version that uses the specified buffer,
+ * it is used from the JSON-RPC code,
+ * and indirectly from websocket_create_packet().
+ */
+int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
+{
+	char *s = *buf; /* points to start of current line */
+	char *s2; /* used for searching of end of current line */
+	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
+	int bytes_to_copy;
+	char newline;
+	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
+	int bytes_in_sendbuf = 0;
+	int bytes_single_frame;
+
+	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
+	if (*len == 0)
+		return -1;
+
+	do {
+		/* Find next \r or \n */
+		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
+		{
+			if ((*s2 == '\n') || (*s2 == '\r'))
+				break;
+		}
+
+		/* Now 's' points to start of line and 's2' points to beyond end of the line
+		 * (either at \r, \n or beyond the buffer).
+		 */
+		bytes_to_copy = s2 - s;
+
+		if (bytes_to_copy < 126)
+			bytes_single_frame = 2 + bytes_to_copy;
+		else if (bytes_to_copy < 65536)
+			bytes_single_frame = 4 + bytes_to_copy;
+		else
+			bytes_single_frame = 10 + bytes_to_copy;
+
+		if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
+		{
+			/* Overflow. This should never happen. */
+			unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
+			           "[BUG] [websocket] Overflow prevented in _websocket_create_packet(): "
+			           "$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
+			           log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
+			           log_data_integer("bytes_single_frame", bytes_single_frame),
+			           log_data_integer("sendbuf_size", sendbufsize));
+			return -1;
+		}
+
+		/* Create the new frame */
+		o[0] = opcode | 0x80; /* opcode & final */
+
+		if (bytes_to_copy < 126)
+		{
+			/* Short payload */
+			o[1] = (char)bytes_to_copy;
+			memcpy(&o[2], s, bytes_to_copy);
+		} else
+		if (bytes_to_copy < 65536)
+		{
+			/* Long payload */
+			o[1] = 126;
+			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
+			o[3] = (char)(bytes_to_copy & 0xFF);
+			memcpy(&o[4], s, bytes_to_copy);
+		} else {
+			/* Longest payload */
+			// XXX: yeah we don't support sending more than 4GB.
+			o[1] = 127;
+			o[2] = 0;
+			o[3] = 0;
+			o[4] = 0;
+			o[5] = 0;
+			o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
+			o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
+			o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
+			o[9] = (char)(bytes_to_copy & 0xFF);
+			memcpy(&o[10], s, bytes_to_copy);
+		}
+
+		/* Advance destination pointer and counter */
+		o += bytes_single_frame;
+		bytes_in_sendbuf += bytes_single_frame;
+
+		/* Advance source pointer and skip all trailing \n and \r */
+		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
+	} while(s <= lastbyte);
+
+	*buf = sendbuf;
+	*len = bytes_in_sendbuf;
+	return 0;
+}
+
+/** Create a websocket packet that is ready to be send.
+ * This version takes into account stripping off \r and \n,
+ * and possibly multi line due to labeled-response.
+ * It is used for WSOP_TEXT and WSOP_BINARY.
+ * The end result is one or more websocket frames,
+ * all in a single packet *buf with size *len.
+ *
+ * This is the version that uses a static sendbuf buffer,
+ * it is used from IRC websockets.
+ */
+int _websocket_create_packet(int opcode, char **buf, int *len)
+{
+	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
+	return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
+}
+
+/** Create and send a WSOP_PONG frame */
+int websocket_send_pong(Client *client, const char *buf, int len)
+{
+	const char *b = buf;
+	int l = len;
+
+	if (_websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
+		return -1;
+
+	if (DBufLength(&client->local->sendQ) > get_sendq(client))
+	{
+		dead_socket(client, "Max SendQ exceeded");
+		return -1;
+	}
+
+	dbuf_put(&client->local->sendQ, b, l);
+	send_queued(client);
+	return 0;
+}
+
+/** UnrealIRCd internals: free WebSocketUser object. */
+void websocket_mdata_free(ModData *m)
+{
+	WebSocketUser *wsu = (WebSocketUser *)m->ptr;
+	if (wsu)
+	{
+		safe_free(wsu->handshake_key);
+		safe_free(wsu->lefttoparse);
+		safe_free(wsu->sec_websocket_protocol);
+		safe_free(wsu->forwarded);
+		safe_free(m->ptr);
+	}
+}
+
+/** This only serializes wsu->type atm */
+const char *websocket_mdata_serialize(ModData *m)
+{
+	static char buf[32];
+	WebSocketUser *wsu = m->ptr;
+
+	if (!wsu)
+		return NULL; /* not set */
+
+	snprintf(buf, sizeof(buf), "%d", wsu->type);
+	return buf;
+}
+
+/** This only sets wsu->type atm */
+void websocket_mdata_unserialize(const char *str, ModData *m)
+{
+	WebSocketUser *wsu;
+	if (m->ptr)
+		websocket_mdata_free(m);
+	if (BadPtr(str))
+		return; /* empty/freed */
+	m->ptr = wsu = safe_alloc(sizeof(WebSocketUser));
+	wsu->type = atoi(str);
+}
diff --git a/ircd/src/modules/who_old.c b/ircd/src/modules/who_old.c
@@ -0,0 +1,842 @@
+/*   src/modules/who_old.c
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *                      University of Oulu, Computing Center
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free softwmare; 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.
+ */
+
+/* rewritten 06/02 by larne, the old one was unreadable. */
+/* changed indentation + some parts rewritten by Syzop. */
+
+#include "unrealircd.h"
+
+CMD_FUNC(cmd_who);
+
+/* Place includes here */
+#define MSG_WHO 	"WHO"
+
+ModuleHeader MOD_HEADER
+  = {
+	"who_old",	/* Name of module */
+	"5.0", /* Version */
+	"command /who (old version)", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	if (!CommandAdd(modinfo->handle, MSG_WHO, cmd_who, MAXPARA, CMD_USER))
+	{
+		config_warn("You cannot load both the cmd_whox and cmd_who module. You should ONLY load the cmd_whox module.");
+		return MOD_FAILED;
+	}
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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, 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 */
+#define WF_ONCHANNEL 0x02 /**< we're on the channel we're /who'ing */
+#define WF_WILDCARD  0x04 /**< a wildcard /who */
+#define WF_REALHOST  0x08 /**< want real hostnames */
+#define WF_IP	     0x10 /**< want IP addresses */
+
+static int who_flags;
+
+#define WHO_CANTSEE 0x01 /**< set if we can't see them */
+#define WHO_CANSEE  0x02 /**< set if we can */
+#define WHO_OPERSEE 0x04 /**< set if we only saw them because we're an oper */
+
+#define FVC_HIDDEN  0x01
+
+#define WHO_WANT 1
+#define WHO_DONTWANT 2
+#define WHO_DONTCARE 0
+
+struct {
+	int want_away;
+	int want_channel;
+	const char *channel; /**< if they want one */
+	int want_gecos;
+	const char *gecos;
+	int want_server;
+	const char *server;
+	int want_host;
+	const char *host;
+	int want_nick;
+	const char *nick;
+	int want_user;
+	const char *user;
+	int want_ip;
+	const char *ip;
+	int want_port;
+	int port;
+	int want_umode;
+	int umodes_dontwant;
+	int umodes_want;
+	int common_channels_only;
+} wfl;
+
+/** The /who command: retrieves information from users. */
+CMD_FUNC(cmd_who)
+{
+	Channel *target_channel;
+	const char *mask = parv[1];
+	char maskbuf[512];
+	int i = 0;
+
+	if (!MyUser(client))
+		return;
+
+	who_flags = 0;
+	memset(&wfl, 0, sizeof(wfl));
+
+	if (parc > 1)
+	{
+		i = parse_who_options(client, parc - 1, parv + 1);
+		if (i < 0)
+		{
+			sendnumeric(client, RPL_ENDOFWHO, mask);
+			return;
+		}
+	}
+
+	if (parc-i < 2 || strcmp(parv[1 + i], "0") == 0)
+		mask = "*";
+	else
+		mask = parv[1 + i];
+
+	if (!i && parc > 2 && *parv[2] == 'o')
+		who_flags |= WF_OPERONLY;
+
+	/* Pfff... collapse... hate it! */
+	strlcpy(maskbuf, mask, sizeof(maskbuf));
+	collapse(maskbuf);
+	mask = maskbuf;
+
+	if (*mask == '\0')
+	{
+		/* no mask given */
+		sendnumeric(client, RPL_ENDOFWHO, "*");
+		return;
+	}
+
+	if ((target_channel = find_channel(mask)) != NULL)
+	{
+		do_channel_who(client, target_channel, mask);
+		sendnumeric(client, RPL_ENDOFWHO, mask);
+		return;
+	}
+
+	if (wfl.channel && wfl.want_channel == WHO_WANT && 
+	    (target_channel = find_channel(wfl.channel)) != NULL)
+	{
+		do_channel_who(client, target_channel, mask);
+		sendnumeric(client, RPL_ENDOFWHO, mask);
+		return;
+	}
+	else
+	{
+		do_other_who(client, mask);
+		sendnumeric(client, RPL_ENDOFWHO, mask);
+		return;
+	}
+
+	return;
+}
+
+static void who_sendhelp(Client *client)
+{
+  char *who_help[] = {
+    "/WHO [+|-][achmnsuM] [args]",
+    "Flags are specified like channel modes, the flags chmnsu all have arguments",
+    "Flags are set to a positive check by +, a negative check by -",
+    "The flags work as follows:",
+    "Flag a: user is away",
+    "Flag c <channel>:       user is on <channel>,",
+    "                        no wildcards accepted",
+    "Flag h <host>:          user has string <host> in their hostname,",
+    "                        wildcards accepted",
+    "Flag m <usermodes>:     user has <usermodes> set, only",
+    "                        O/o/C/A/a/N/B are allowed",
+    "Flag n <nick>:          user has string <nick> in their nickname,",
+    "                        wildcards accepted",
+    "Flag s <server>:        user is on server <server>,",
+    "                        wildcards not accepted",
+    "Flag u <user>:          user has string <user> in their username,",
+    "                        wildcards accepted",
+    "Behavior flags:",
+    "Flag M: check for user in channels I am a member of",
+    NULL
+  };
+
+  char *who_oper_help[] = {
+    "/WHO [+|-][acghimnsuMRI] [args]",
+    "Flags are specified like channel modes, the flags chigmnsu all have arguments",
+    "Flags are set to a positive check by +, a negative check by -",
+    "The flags work as follows:",
+    "Flag a: user is away",
+    "Flag c <channel>:       user is on <channel>,",
+    "                        no wildcards accepted",
+    "Flag g <gcos/realname>: user has string <gcos> in their GCOS,",
+    "                        wildcards accepted",
+    "Flag h <host>:          user has string <host> in their hostname,",
+    "                        wildcards accepted",
+    "Flag i <ip>:            user has string <ip> in their IP address,",
+    "                        wildcards accepted",
+    "Flag p <port>:          user is connecting on port <port>,",
+    "                        local connections only",
+    "Flag m <usermodes>:     user has <usermodes> set",
+    "Flag n <nick>:          user has string <nick> in their nickname,",
+    "                        wildcards accepted",
+    "Flag s <server>:        user is on server <server>,",
+    "                        wildcards not accepted",
+    "Flag u <user>:          user has string <user> in their username,",
+    "                        wildcards accepted",
+    "Behavior flags:",
+    "Flag M: check for user in channels I am a member of",
+    "Flag R: show users' real hostnames",
+    "Flag I: show users' IP addresses",
+    NULL
+  };
+  char **s;
+
+	if (IsOper(client))
+		s = who_oper_help;
+	else
+		s = who_help;
+
+	for (; *s; s++)
+		sendnumeric(client, RPL_LISTSYNTAX, *s);
+}
+
+#define WHO_ADD 1
+#define WHO_DEL 2
+
+static int parse_who_options(Client *client, int argc, const char **argv)
+{
+	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. */
+
+/** function requiress a parameter: check if there's one, if not: return -1. */
+#define REQUIRE_PARAM() { if (i >= argc) { \
+                           who_sendhelp(client); \
+                           return -1; \
+                      } } while(0);
+/** set option 'x' depending on 'what' (add/want or del/dontwant) */
+#define SET_OPTION(x) { if (what == WHO_ADD) \
+                           x = WHO_WANT; \
+                      else \
+                           x = WHO_DONTWANT; \
+                      } while(0);
+/** Eat a param, set the param in memory and set the option to want or dontwant */
+#define DOIT(x,y) { REQUIRE_PARAM(); x = argv[i]; SET_OPTION(y); i++; } while(0);
+
+	if (*s != '-' && *s != '+')
+		return 0;
+
+	while (*s)
+ 	{
+		switch (*s)
+		{
+			case '+':
+	  			what = WHO_ADD;
+	  			break;
+			case '-':
+				what = WHO_DEL;
+				break;
+			case 'a':
+				SET_OPTION(wfl.want_away);
+				break;
+			case 'c':
+				DOIT(wfl.channel, wfl.want_channel);
+				break;
+			case 'g':
+				REQUIRE_PARAM()
+				if (!IsOper(client))
+					break; /* oper-only */
+				wfl.gecos = argv[i];
+				SET_OPTION(wfl.want_gecos);
+				i++;
+				break;
+			case 's':
+				DOIT(wfl.server, wfl.want_server);
+				break;
+			case 'h':
+				DOIT(wfl.host, wfl.want_host);
+				break;
+			case 'i':
+				REQUIRE_PARAM()
+				if (!IsOper(client))
+					break; /* oper-only */
+				wfl.ip = argv[i];
+				SET_OPTION(wfl.want_ip);
+				i++;
+				break;
+			case 'n':
+				DOIT(wfl.nick, wfl.want_nick);
+				break;
+			case 'u':
+				DOIT(wfl.user, wfl.want_user);
+				break;
+			case 'm':
+				REQUIRE_PARAM()
+				{
+					const char *s = argv[i];
+					int *umodes;
+
+					if (what == WHO_ADD)
+						umodes = &wfl.umodes_want;
+					else
+						umodes = &wfl.umodes_dontwant;
+
+					*umodes = set_usermode(s);
+
+					if (!IsOper(client))
+						*umodes = *umodes & UMODE_OPER; /* these are usermodes regular users may search for. just oper now. */
+					if (*umodes == 0)
+						return -1;
+				}
+				i++;
+				break;
+			case 'p':
+				REQUIRE_PARAM()
+				if (!IsOper(client))
+					break; /* oper-only */
+				wfl.port = atoi(argv[i]);
+				SET_OPTION(wfl.want_port);
+				i++;
+				break;
+			case 'M':
+				SET_OPTION(wfl.common_channels_only);
+				break;
+			case 'R':
+				if (!IsOper(client))
+					break;
+				if (what == WHO_ADD)
+					who_flags |= WF_REALHOST;
+				else
+					who_flags &= ~WF_REALHOST;
+				break;
+			case 'I':
+				if (!IsOper(client))
+					break;
+				if (what == WHO_ADD)
+					who_flags |= WF_IP;
+				
+				else
+					who_flags &= ~WF_IP;
+				break;
+			default:
+				who_sendhelp(client);
+				return -1;
+		}
+		s++;
+    }
+
+  return i;
+#undef REQUIRE_PARAM
+#undef SET_OPTION
+#undef DOIT
+}
+
+static int can_see(Client *requester, Client *target, Channel *channel)
+{
+	int ret = 0;
+	char has_common_chan = 0;
+
+	do {
+		/* can only see people */
+		if (!IsUser(target))
+			return WHO_CANTSEE;
+
+		/* can only see opers if thats what they want */
+		if (who_flags & WF_OPERONLY)
+		{
+			if (!IsOper(target))
+				return ret | WHO_CANTSEE;
+			if (IsHideOper(target)) {
+				if (IsOper(requester))
+					ret |= WHO_OPERSEE;
+				else
+					return ret | WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people who are away */
+		if ((wfl.want_away == WHO_WANT && !target->user->away) ||
+		    (wfl.want_away == WHO_DONTWANT && target->user->away))
+			return WHO_CANTSEE;
+
+		/* if they only want people on a certain channel. */
+		if (wfl.want_channel != WHO_DONTCARE)
+ 		{
+			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))
+				return WHO_CANTSEE;
+			if ((wfl.want_channel == WHO_DONTWANT) && IsMember(target, chan))
+				return WHO_CANTSEE;
+		}
+
+		/* if they only want people with a certain gecos */
+		if (wfl.want_gecos != WHO_DONTCARE)
+		{
+			if (((wfl.want_gecos == WHO_WANT) && !match_simple(wfl.gecos, target->info)) ||
+			    ((wfl.want_gecos == WHO_DONTWANT) && match_simple(wfl.gecos, target->info)))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people with a certain server */
+		if (wfl.want_server != WHO_DONTCARE)
+		{
+			if (((wfl.want_server == WHO_WANT) && strcasecmp(wfl.server, target->user->server)) ||
+			    ((wfl.want_server == WHO_DONTWANT) && !strcasecmp(wfl.server, target->user->server)))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people with a certain host */
+		if (wfl.want_host != WHO_DONTCARE)
+		{
+			char *host;
+
+			if (IsOper(requester))
+				host = target->user->realhost;
+			else
+				host = GetHost(target);
+
+			if (((wfl.want_host == WHO_WANT) && !match_simple(wfl.host, host)) ||
+			    ((wfl.want_host == WHO_DONTWANT) && match_simple(wfl.host, host)))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people with a certain IP */
+		if (wfl.want_ip != WHO_DONTCARE)
+		{
+			char *ip;
+
+			ip = target->ip;
+			if (!ip)
+				return WHO_CANTSEE;
+
+			if (((wfl.want_ip == WHO_WANT) && !match_simple(wfl.ip, ip)) ||
+			    ((wfl.want_ip == WHO_DONTWANT) && match_simple(wfl.ip, ip)))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people connecting on a certain port */
+		if (wfl.want_port != WHO_DONTCARE)
+		{
+			int port;
+			
+			if (!MyUser(target))
+				return WHO_CANTSEE;
+
+			port = target->local->listener->port;
+
+			if (((wfl.want_port == WHO_WANT) && wfl.port != port) ||
+			    ((wfl.want_port == WHO_DONTWANT) && wfl.port == port))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people with a certain nick.. */
+		if (wfl.want_nick != WHO_DONTCARE)
+		{
+			if (((wfl.want_nick == WHO_WANT) && !match_simple(wfl.nick, target->name)) ||
+			    ((wfl.want_nick == WHO_DONTWANT) && match_simple(wfl.nick, target->name)))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people with a certain username */
+		if (wfl.want_user != WHO_DONTCARE)
+		{
+			if (((wfl.want_user == WHO_WANT) && !match_simple(wfl.user, target->user->username)) ||
+			    ((wfl.want_user == WHO_DONTWANT) && match_simple(wfl.user, target->user->username)))
+			{
+				return WHO_CANTSEE;
+			}
+		}
+
+		/* if they only want people with a certain umode */
+		if (wfl.umodes_want)
+		{
+			if (!(target->umodes & wfl.umodes_want) || (!IsOper(requester) && (target->umodes & UMODE_HIDEOPER)))
+				return WHO_CANTSEE;
+		}
+
+		if (wfl.umodes_dontwant)
+		{
+			if ((target->umodes & wfl.umodes_dontwant) && (!(target->umodes & UMODE_HIDEOPER) || IsOper(requester)))
+				return WHO_CANTSEE;
+		}
+
+		/* if they only want common channels */
+		if (wfl.common_channels_only)
+		{
+			if (!has_common_channels(requester, target))
+				return WHO_CANTSEE;
+			has_common_chan = 1;
+		}
+
+		if (channel)
+		{
+			int member = who_flags & WF_ONCHANNEL;
+
+			if (SecretChannel(channel) || HiddenChannel(channel))
+			{
+				/* if they aren't on it.. they can't see it */
+				if (!(who_flags & WF_ONCHANNEL))
+					break;
+			}
+			if (IsInvisible(target) && !member)
+				break;
+
+			if (!user_can_see_member(requester, target, channel))
+				break; /* invisible (eg: due to delayjoin) */
+		}
+		else
+		{
+			/* a user/mask who */
+
+			/* If the common channel info hasn't been set, set it now */
+			if (!wfl.common_channels_only)
+				has_common_chan = has_common_channels(requester, target);
+
+			if (IsInvisible(target) && !has_common_chan)
+			{
+				/* don't show them unless it's an exact match 
+				   or it is the user requesting the /who */
+				if ((who_flags & WF_WILDCARD) && requester != target)
+					break;
+			}
+		}
+
+		/* phew.. show them. */
+		return WHO_CANSEE;
+	} while (0);
+
+	/* if we get here, it's oper-dependant. */
+	if (IsOper(requester))
+		return ret | WHO_OPERSEE | WHO_CANSEE;
+	else
+	{
+		if (requester == target)
+			return ret | WHO_CANSEE;
+		else
+			return ret | WHO_CANTSEE;
+	}
+}
+
+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))
+		who_flags |= WF_ONCHANNEL;
+
+	for (cm = channel->members; cm; cm = cm->next)
+	{
+		Client *acptr = cm->client;
+		char status[32];
+		int cansee;
+		if ((cansee = can_see(client, acptr, channel)) & WHO_CANTSEE)
+			continue;
+
+		make_who_status(client, acptr, channel, cm, status, cansee);
+		send_who_reply(client, acptr, channel->name, status, "");
+    }
+}
+
+static void make_who_status(Client *client, Client *acptr, Channel *channel, 
+			    Member *cm, char *status, int cansee)
+{
+	int i = 0;
+	Hook *h;
+
+	if (acptr->user->away)
+		status[i++] = 'G';
+	else
+		status[i++] = 'H';
+
+	if (IsRegNick(acptr))
+		status[i++] = 'r';
+
+	if (IsSecureConnect(acptr))
+		status[i++] = 's';
+
+	for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
+	{
+		int ret = (*(h->func.intfunc))(client, acptr, channel, cm, status, cansee);
+		if (ret != 0)
+			status[i++] = (char)ret;
+	}
+	
+	if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
+		status[i++] = '*';
+
+	if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client)))
+		status[i++] = '!';
+  
+	if (cansee & WHO_OPERSEE)
+		status[i++] = '?';
+
+	if (cm)
+	{
+		if (HasCapability(client, "multi-prefix"))
+		{
+			/* 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, const char *mask)
+{
+int oper = IsOper(client);
+
+	if (strchr(mask, '*') || strchr(mask, '?'))
+	{
+		int i = 0;
+		/* go through all users.. */
+		Client *acptr;
+		who_flags |= WF_WILDCARD;
+
+		list_for_each_entry(acptr, &client_list, client_node)
+		{
+		int cansee;
+		char status[20];
+		const char *channel;
+		int flg;
+
+			if (!IsUser(acptr))
+				continue;
+			if (!oper) {
+				/* non-opers can only search on nick here */
+				if (!match_simple(mask, acptr->name))
+					continue;
+			} else {
+				/* opers can search on name, ident, virthost, ip and realhost.
+				 * Yes, I like readable if's -- Syzop.
+				 */
+				if (match_simple(mask, acptr->name) || match_simple(mask, acptr->user->realhost) ||
+				    match_simple(mask, acptr->user->username))
+					goto matchok;
+				if (IsHidden(acptr) && match_simple(mask, acptr->user->virthost))
+					goto matchok;
+				if (acptr->ip && match_simple(mask, acptr->ip))
+					goto matchok;
+				/* nothing matched... */
+				continue;
+			}
+matchok:
+			if ((cansee = can_see(client, acptr, NULL)) & WHO_CANTSEE)
+				continue;
+			if (WHOLIMIT && !IsOper(client) && ++i > WHOLIMIT)
+			{
+				sendnumeric(client, ERR_WHOLIMEXCEED, WHOLIMIT);
+				return;
+			}
+
+			channel = first_visible_channel(client, acptr, &flg);
+			make_who_status(client, acptr, NULL, NULL, status, cansee);
+			send_who_reply(client, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : "");
+		}
+	}
+	else
+	{
+		/* just a single client (no wildcards detected) */
+		Client *acptr = find_client(mask, NULL);
+		int cansee;
+		char status[20];
+		const char *channel;
+		int flg;
+
+		if (!acptr)
+			return;
+
+		if ((cansee = can_see(client, acptr, NULL)) == WHO_CANTSEE)
+			return;
+
+		channel = first_visible_channel(client, acptr, &flg);
+		make_who_status(client, acptr, NULL, NULL, status, cansee);
+		send_who_reply(client, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : "");
+	}
+}
+
+static void send_who_reply(Client *client, Client *acptr, 
+			   const char *channel, const char *status, const char *xstat)
+{
+	char *stat;
+	const char *host;
+	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
+
+	stat = safe_alloc(strlen(status) + strlen(xstat) + 1);
+	sprintf(stat, "%s%s", status, xstat);
+
+	if (IsOper(client))
+	{
+		if (who_flags & WF_REALHOST)
+			host = acptr->user->realhost;
+		else if (who_flags & WF_IP)
+			host = (acptr->ip ? acptr->ip : acptr->user->realhost);
+		else
+			host = GetHost(acptr);
+	}
+	else
+		host = GetHost(acptr);
+					
+
+	if (IsULine(acptr) && !IsOper(client) && !ValidatePermissionsForPath("server:info:map:ulines",client,acptr,NULL,NULL) && HIDE_ULINES)
+	{
+	        sendnumeric(client, RPL_WHOREPLY,
+        	     channel,       /* channel name */
+	             acptr->user->username, /* user name */
+        	     host,		    /* hostname */
+	             "hidden",              /* let's hide the server from normal users if the server is a uline and HIDE_ULINES is on */
+        	     acptr->name,           /* nick */
+	             stat,                  /* status */
+        	     0,                     /* hops (hidden) */
+	             acptr->info            /* realname */
+             	);
+
+	} else {
+		sendnumeric(client, RPL_WHOREPLY,
+		     channel,       /* channel name */
+		     acptr->user->username,      /* user name */
+		     host,		         /* hostname */
+		     acptr->user->server,        /* server name */
+		     acptr->name,                /* nick */
+		     stat,                       /* status */
+		     flat ? 0 : acptr->hopcount, /* hops */ 
+		     acptr->info                 /* realname */
+		     );
+	}
+	safe_free(stat);
+}
+
+static const char *first_visible_channel(Client *client, Client *acptr, int *flg)
+{
+	Membership *lp;
+
+	*flg = 0;
+
+	for (lp = acptr->user->channel; lp; lp = lp->next)
+	{
+		Channel *channel = lp->channel;
+		Hook *h;
+		int ret = EX_ALLOW;
+		int operoverride = 0;
+		int showchannel = 0;
+		
+		/* Note that the code below is almost identical to the one in /WHOIS */
+
+		if (ShowChannel(client, channel))
+			showchannel = 1;
+
+		for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next)
+		{
+			int n = (*(h->func.intfunc))(client, acptr, channel);
+			/* Hook return values:
+			 * EX_ALLOW means 'yes is ok, as far as modules are concerned'
+			 * EX_DENY means 'hide this channel, unless oper overriding'
+			 * EX_ALWAYS_DENY means 'hide this channel, always'
+			 * ... with the exception that we always show the channel if you /WHOIS yourself
+			 */
+			if (n == EX_DENY)
+			{
+				ret = EX_DENY;
+			}
+			else if (n == EX_ALWAYS_DENY)
+			{
+				ret = EX_ALWAYS_DENY;
+				break;
+			}
+		}
+		
+		if (ret == EX_DENY)
+			showchannel = 0;
+		
+		if (!showchannel && (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,channel,NULL) || ValidatePermissionsForPath("channel:see:whois",client,NULL,channel,NULL)))
+		{
+			showchannel = 1; /* OperOverride */
+			operoverride = 1;
+		}
+		
+		if ((ret == EX_ALWAYS_DENY) && (acptr != client))
+			continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */
+
+		if (acptr == client)
+			showchannel = 1;
+
+		if (operoverride)
+			*flg |= FVC_HIDDEN;
+
+		if (showchannel)
+			return channel->name;
+	}
+
+	/* no channels that they can see */
+	return "*";
+}
diff --git a/ircd/src/modules/whois.c b/ircd/src/modules/whois.c
@@ -0,0 +1,663 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/whois.c
+ *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
+ *   (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
+ *   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"
+
+/* Structs */
+ModuleHeader MOD_HEADER
+  = {
+	"whois",	/* Name of module */
+	"5.0", /* Version */
+	"command /whois", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+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 */
+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()
+{
+	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;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+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;
+}
+
+/* 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("security-groups", 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;
+	char *p = NULL;
+	int len, mlen;
+	char querybuf[BUFSIZE];
+	char buf[BUFSIZE];
+	int ntargets = 0;
+	int maxtargets = max_targets_for_command("WHOIS");
+
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NONICKNAMEGIVEN);
+		return;
+	}
+
+	if (parc > 2)
+	{
+		if (hunt_server(client, recv_mtags, "WHOIS", 1, parc, parv) != HUNTED_ISME)
+			return;
+		parv[1] = parv[2];
+	}
+
+	strlcpy(querybuf, parv[1], sizeof(querybuf));
+
+	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))
+		{
+			sendnumeric(client, ERR_TOOMANYTARGETS, nick, maxtargets, "WHOIS");
+			break;
+		}
+
+		/* We do not support "WHOIS *" */
+		wilds = (strchr(nick, '?') || strchr(nick, '*'));
+		if (wilds)
+			continue;
+
+		target = find_user(nick, NULL);
+		if (!target)
+		{
+			sendnumeric(client, ERR_NOSUCHNICK, nick);
+			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.
+		 */
+
+		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);
+		}
+
+		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 (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);
+		}
+
+		/* 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;
+				int ret = EX_ALLOW;
+				int operoverride = 0;
+				
+				channel = lp->channel;
+				showchannel = 0;
+
+				if (ShowChannel(client, channel))
+					showchannel = 1;
+
+				for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next)
+				{
+					int n = (*(h->func.intfunc))(client, target, channel);
+					/* Hook return values:
+					 * EX_ALLOW means 'yes is ok, as far as modules are concerned'
+					 * EX_DENY means 'hide this channel, unless oper overriding'
+					 * EX_ALWAYS_DENY means 'hide this channel, always'
+					 * ... with the exception that we always show the channel if you /WHOIS yourself
+					 */
+					if (n == EX_DENY)
+					{
+						ret = EX_DENY;
+					}
+					else if (n == EX_ALWAYS_DENY)
+					{
+						ret = EX_ALWAYS_DENY;
+						break;
+					}
+				}
+				
+				if (ret == EX_DENY)
+					showchannel = 0;
+				
+				/* 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;
+				}
+				
+				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). */
+
+				/* 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)
+				{
+					if (len + strlen(channel->name) > (size_t)BUFSIZE - 4 - mlen)
+					{
+						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;
+					}
+
+					if (operoverride)
+					{
+						/* '?' and '!' both mean we can see the channel in /WHOIS and normally wouldn't,
+						 * but there's still a slight difference between the two...
+						 */
+						if (!PubChannel(channel))
+						{
+							/* '?' means it's a secret/private channel (too) */
+							*(buf + len++) = '?';
+						}
+						else
+						{
+							/* public channel but hidden in WHOIS (umode +p, service bot, etc) */
+							*(buf + len++) = '!';
+						}
+					}
+
+					if (!MyUser(client) || !HasCapability(client, "multi-prefix"))
+					{
+						/* Standard NAMES reply (single character) */
+						char c = mode_to_prefix(*lp->member_modes);
+						if (c)
+							*(buf + len++) = c;
+					}
+					else
+					{
+						/* 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->name);
+					len += strlen(channel->name);
+					strcat(buf + len, " ");
+					len++;
+				}
+			}
+
+			if (buf[0] != '\0')
+			{
+				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) &&
+		    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 && (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)
+		{
+			policy = whois_get_policy(client, target, "oper");
+			if (policy == WHOIS_CONFIG_DETAILS_FULL)
+			{
+				const char *operlogin = get_operlogin(target);
+				const char *operclass = get_operclass(target);
+
+				if (operlogin && operclass)
+				{
+					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)
+		{
+			policy = whois_get_policy(client, target, "secure");
+			if (policy == WHOIS_CONFIG_DETAILS_LIMITED)
+			{
+				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");
+				}
+			}
+		}
+
+		/* The following code deals with security-groups */
+		policy = whois_get_policy(client, target, "security-groups");
+		if ((policy > WHOIS_CONFIG_DETAILS_NONE) && !IsULine(target))
+		{
+			SecurityGroup *s;
+			int security_groups_whois_lines = 0;
+
+			mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name) + strlen("is in security-groups: ");
+
+			if (user_allowed_by_security_group_name(target, "known-users"))
+				strlcpy(buf, "known-users,", sizeof(buf));
+			else
+				strlcpy(buf, "unknown-users,", sizeof(buf));
+			len = strlen(buf);
+
+			for (s = securitygroups; s; s = s->next)
+			{
+				if (len + strlen(s->name) > (size_t)BUFSIZE - 4 - mlen)
+				{
+					buf[len-1] = '\0';
+					add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups",
+					                        target, RPL_WHOISSPECIAL,
+								"%s :is in security-groups: %s", target->name, buf);
+					security_groups_whois_lines++;
+					*buf = '\0';
+					len = 0;
+				}
+				if (strcmp(s->name, "known-users") && user_allowed_by_security_group(target, s))
+				{
+					strcpy(buf + len, s->name);
+					len += strlen(buf+len);
+					strcpy(buf + len, ",");
+					len++;
+				}
+			}
+
+			if (*buf)
+			{
+				buf[len-1] = '\0';
+				add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups",
+				                        client, RPL_WHOISSPECIAL,
+							"%s :is in security-groups: %s", target->name, buf);
+				security_groups_whois_lines++;
+			}
+		}
+		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 && (whois_get_policy(client, target, "swhois") > WHOIS_CONFIG_DETAILS_NONE))
+		{
+			SWhois *s;
+			int swhois_lines = 0;
+
+			for (s = target->user->swhois; s; s = s->next)
+			{
+				if (hideoper && !IsOper(client) && s->setby && !strcmp(s->setby, "oper"))
+					continue; /* hide oper-based swhois entries */
+				add_nvplist_numeric(&list, 100000+swhois_lines, "swhois", client, RPL_WHOISSPECIAL,
+				                    target->name, s->line);
+				swhois_lines++;
+			}
+		}
+
+		/* 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);
+		}
+
+		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 ((policy == WHOIS_CONFIG_DETAILS_FULL) ||
+			    ((policy == WHOIS_CONFIG_DETAILS_LIMITED) && !hide_idle_time(client, target)))
+			{
+				add_nvplist_numeric(&list, 500000, "idle", client, RPL_WHOISIDLE,
+				                    target->name,
+				                    (long long)(TStime() - target->local->idle_since),
+				                    (long long)target->local->creationtime);
+			}
+		}
+
+		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/ircd/src/modules/whowas.c b/ircd/src/modules/whowas.c
@@ -0,0 +1,133 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/out.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_whowas);
+
+#define MSG_WHOWAS 	"WHOWAS"	
+
+ModuleHeader MOD_HEADER
+  = {
+	"whowas",
+	"5.0",
+	"command /whowas", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_WHOWAS, cmd_whowas, MAXPARA, CMD_USER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* externally defined functions */
+extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
+
+/*
+** cmd_whowas
+**      parv[1] = nickname queried
+*/
+CMD_FUNC(cmd_whowas)
+{
+	char request[BUFSIZE];
+	WhoWas *temp;
+	int  cur = 0;
+	int  max = -1, found = 0;
+	char *p, *nick;
+
+	if (parc < 2)
+	{
+		sendnumeric(client, ERR_NONICKNAMEGIVEN);
+		return;
+	}
+
+	if (parc > 2)
+		max = atoi(parv[2]);
+
+	if (parc > 3)
+	{
+		if (hunt_server(client, recv_mtags, "WHOWAS", 3, parc, parv))
+			return; /* Not for us */
+	}
+
+	if (!MyConnect(client) && (max > 20))
+		max = 20;
+
+	strlcpy(request, parv[1], sizeof(request));
+	p = strchr(request, ',');
+	if (p)
+		*p = '\0'; /* cut off at first */
+
+	nick = request;
+	temp = WHOWASHASH[hash_whowas_name(nick)];
+	found = 0;
+	for (; temp; temp = temp->next)
+	{
+		if (!mycmp(nick, temp->name))
+		{
+			sendnumeric(client, RPL_WHOWASUSER, temp->name,
+			    temp->username,
+			    BadPtr(temp->virthost) ? temp->hostname : temp->virthost,
+			    temp->realname);
+			if (!BadPtr(temp->ip) && ValidatePermissionsForPath("client:see:ip",client,NULL,NULL,NULL))
+			{
+				sendnumericfmt(client, RPL_WHOISHOST, "%s :was connecting from %s@%s %s",
+					temp->name,
+					temp->username, temp->hostname,
+					temp->ip ? temp->ip : "");
+			}
+			if (IsOper(client) && !BadPtr(temp->account))
+			{
+				sendnumericfmt(client, RPL_WHOISLOGGEDIN, "%s %s :was logged in as",
+					temp->name,
+					temp->account);
+			}
+			if (!((find_uline(temp->servername)) && !IsOper(client) && HIDE_ULINES))
+			{
+				sendnumeric(client, RPL_WHOISSERVER, temp->name, temp->servername,
+				            myctime(temp->logoff));
+			}
+			cur++;
+			found++;
+		}
+		if (max > 0 && cur >= max)
+			break;
+	}
+	if (!found)
+		sendnumeric(client, ERR_WASNOSUCHNICK, nick);
+
+	sendnumeric(client, RPL_ENDOFWHOWAS, request);
+}
diff --git a/ircd/src/modules/whowasdb.c b/ircd/src/modules/whowasdb.c
@@ -0,0 +1,641 @@
+/*
+ * Stores WHOWAS history in a .db file
+ * (C) Copyright 2023 Syzop
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER = {
+	"whowasdb",
+	"1.0",
+	"Stores and retrieves WHOWAS history",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Our header */
+#define WHOWASDB_HEADER		0x57484F57
+/* Database version */
+#define WHOWASDB_VERSION 100
+/* Save whowas of users to file every <this> seconds */
+#define WHOWASDB_SAVE_EVERY 300
+/* The very first save after boot, apply this delta, this
+ * so we don't coincide with other (potentially) expensive
+ * I/O events like saving tkldb.
+ */
+#define WHOWASDB_SAVE_EVERY_DELTA -60
+
+#define MAGIC_WHOWASDB_START	0x11111111
+#define MAGIC_WHOWASDB_END		0x22222222
+
+// #undef BENCHMARK
+
+/* Yeah, W_SAFE_PROPERTY raises "the address .. will always evaluate to true" warnings,
+ * disabling it in the entire file for now...
+ */
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Waddress"
+#endif
+
+#define WARN_WRITE_ERROR(fname) \
+	do { \
+		unreal_log(ULOG_ERROR, "whowasdb", "WHOWASDB_FILE_WRITE_ERROR", NULL, \
+			   "[whowasdb] Error writing to temporary database file $filename: $system_error", \
+			   log_data_string("filename", fname), \
+			   log_data_string("system_error", unrealdb_get_error_string())); \
+	} while(0)
+
+#define W_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+#define W_SAFE_PROPERTY(db, x, y) \
+	do { \
+		if (x && y && (!unrealdb_write_str(db, x) || !unrealdb_write_str(db, y))) \
+		{ \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+#define IsMDErr(x, y, z) \
+	do { \
+		if (!(x)) { \
+			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
+			return MOD_FAILED; \
+		} \
+	} while(0)
+
+/* Structs */
+struct cfgstruct {
+	char *database;
+	char *db_secret;
+};
+
+/* Forward declarations */
+void whowasdb_moddata_free(ModData *md);
+void setcfg(struct cfgstruct *cfg);
+void freecfg(struct cfgstruct *cfg);
+int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int whowasdb_config_posttest(int *errs);
+int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(write_whowasdb_evt);
+int write_whowasdb(void);
+int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e);
+int read_whowasdb(void);
+
+/* External variables */
+extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
+extern MODVAR int whowas_next;
+
+/* Global variables */
+static uint32_t whowasdb_version = WHOWASDB_VERSION;
+static struct cfgstruct cfg;
+static struct cfgstruct test;
+
+static long whowasdb_next_event = 0;
+
+MOD_TEST()
+{
+	memset(&cfg, 0, sizeof(cfg));
+	memset(&test, 0, sizeof(test));
+	setcfg(&test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whowasdb_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, whowasdb_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	LoadPersistentLong(modinfo, whowasdb_next_event);
+
+	setcfg(&cfg);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whowasdb_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (!whowasdb_next_event)
+	{
+		/* If this is the first time that our module is loaded, then read the database. */
+		if (!read_whowasdb())
+		{
+			char fname[512];
+			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
+			if (rename(cfg.database, fname) == 0)
+				config_warn("[whowasdb] Existing database renamed to %s and starting a new one...", fname);
+			else
+				config_warn("[whowasdb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
+		}
+		whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY + WHOWASDB_SAVE_EVERY_DELTA;
+	}
+	EventAdd(modinfo->handle, "whowasdb_write_whowasdb", write_whowasdb_evt, NULL, 1000, 0);
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (loop.terminating)
+		write_whowasdb();
+	freecfg(&test);
+	freecfg(&cfg);
+	SavePersistentLong(modinfo, whowasdb_next_event);
+	return MOD_SUCCESS;
+}
+
+void whowasdb_moddata_free(ModData *md)
+{
+	if (md->i)
+		md->i = 0;
+}
+
+void setcfg(struct cfgstruct *cfg)
+{
+	// Default: data/whowas.db
+	safe_strdup(cfg->database, "whowas.db");
+	convert_to_absolute_path(&cfg->database, PERMDATADIR);
+}
+
+void freecfg(struct cfgstruct *cfg)
+{
+	safe_free(cfg->database);
+	safe_free(cfg->db_secret);
+}
+
+int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	// We are only interested in set::whowasdb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "whowasdb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: blank set::whowasdb::%s without value", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		} else
+		if (!strcmp(cep->name, "database"))
+		{
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
+		} else
+		if (!strcmp(cep->name, "db-secret"))
+		{
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
+			{
+				config_error("%s:%i: set::whowasdb::db-secret: %s", cep->file->filename, cep->line_number, err);
+				errors++;
+				continue;
+			}
+			safe_strdup(test.db_secret, cep->value);
+		} else
+		{
+			config_error("%s:%i: unknown directive set::whowasdb::%s", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int whowasdb_config_posttest(int *errs)
+{
+	int errors = 0;
+	char *errstr;
+
+	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+	{
+		config_error("[whowasdb] %s", errstr);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	// We are only interested in set::whowasdb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "whowasdb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database"))
+			safe_strdup(cfg.database, cep->value);
+		else if (!strcmp(cep->name, "db-secret"))
+			safe_strdup(cfg.db_secret, cep->value);
+	}
+	return 1;
+}
+
+EVENT(write_whowasdb_evt)
+{
+	if (whowasdb_next_event > TStime())
+		return;
+	whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY;
+	write_whowasdb();
+}
+
+int count_whowas_and_user_entries(void)
+{
+	int i;
+	int cnt = 0;
+	Client *client;
+
+	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		WhoWas *e = &WHOWAS[i];
+		if (e->name)
+			cnt++;
+	}
+
+	list_for_each_entry(client, &client_list, client_node)
+		if (IsUser(client))
+			cnt++;
+
+	return cnt;
+}
+
+int write_whowasdb(void)
+{
+	char tmpfname[512];
+	UnrealDB *db;
+	WhoWas *e;
+	Client *client;
+	int cnt, i;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	// Write to a tempfile first, then rename it if everything succeeded
+	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+	if (!db)
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	W_SAFE(unrealdb_write_int32(db, WHOWASDB_HEADER));
+	W_SAFE(unrealdb_write_int32(db, whowasdb_version));
+
+	cnt = count_whowas_and_user_entries();
+	W_SAFE(unrealdb_write_int64(db, cnt));
+
+	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		WhoWas *e = &WHOWAS[i];
+		if (e->name)
+		{
+			if (!write_whowas_entry(db, tmpfname, e))
+				return 0;
+		}
+	}
+
+	/* Add all the currently connected users to WHOWAS history (as if they left just now) */
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (IsUser(client))
+		{
+			WhoWas *e = safe_alloc(sizeof(WhoWas));
+			int ret;
+
+			create_whowas_entry(client, e, WHOWAS_EVENT_SERVER_TERMINATING);
+			ret = write_whowas_entry(db, tmpfname, e);
+			free_whowas_fields(e);
+			safe_free(e);
+
+			if (ret == 0)
+				return 0;
+		}
+	}
+
+
+	// Everything seems to have gone well, attempt to close and rename the tempfile
+	if (!unrealdb_close(db))
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(cfg.database);
+#endif
+	if (rename(tmpfname, cfg.database) < 0)
+	{
+		config_error("[whowasdb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
+		return 0;
+	}
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	config_status("[whowasdb] Benchmark: SAVE DB: %ld microseconds",
+		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
+#endif
+	return 1;
+}
+
+int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e)
+{
+	char connected_since[64];
+	char logontime[64];
+	char logofftime[64];
+	char event[16];
+
+	snprintf(connected_since, sizeof(connected_since), "%lld", (long long)e->connected_since);
+	snprintf(logontime, sizeof(logontime), "%lld", (long long)e->logon);
+	snprintf(logofftime, sizeof(logofftime), "%lld", (long long)e->logoff);
+	snprintf(event, sizeof(event), "%d", e->event);
+
+	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_START));
+	W_SAFE_PROPERTY(db, "nick", e->name);
+	W_SAFE_PROPERTY(db, "event", event);
+	W_SAFE_PROPERTY(db, "connected_since", connected_since);
+	W_SAFE_PROPERTY(db, "logontime", logontime);
+	W_SAFE_PROPERTY(db, "logofftime", logofftime);
+	W_SAFE_PROPERTY(db, "username", e->username);
+	W_SAFE_PROPERTY(db, "hostname", e->hostname);
+	W_SAFE_PROPERTY(db, "ip", e->ip);
+	W_SAFE_PROPERTY(db, "realname", e->realname);
+	W_SAFE_PROPERTY(db, "server", e->servername);
+	W_SAFE_PROPERTY(db, "virthost", e->virthost);
+	W_SAFE_PROPERTY(db, "account", e->account);
+	W_SAFE_PROPERTY(db, "end", "");
+	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_END));
+	return 1;
+}
+
+#define FreeWhowasEntry() \
+ 	do { \
+		/* Some of these might be NULL */ \
+		safe_free(key); \
+		safe_free(value); \
+		safe_free(nick); \
+		safe_free(username); \
+		safe_free(hostname); \
+		safe_free(ip); \
+		safe_free(realname); \
+		connected_since = logontime = logofftime = 0; \
+		event = 0; \
+		safe_free(server); \
+		safe_free(virthost); \
+		safe_free(account); \
+	} while(0)
+
+#define R_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			config_warn("[whowasdb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+			unrealdb_close(db); \
+			FreeWhowasEntry(); \
+			return 0; \
+		} \
+	} while(0)
+
+int read_whowasdb(void)
+{
+	UnrealDB *db;
+	uint32_t version;
+	int added = 0;
+	int i;
+	uint64_t count = 0;
+	uint32_t magic;
+	char *key = NULL;
+	char *value = NULL;
+	char *nick = NULL;
+	char *username = NULL;
+	char *hostname = NULL;
+	char *ip = NULL;
+	char *realname = NULL;
+	long long connected_since = 0;
+	long long logontime = 0;
+	long long logofftime = 0;
+	int event = 0;
+	char *server = NULL;
+	char *virthost = NULL;
+	char *account = NULL;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+		{
+			/* Database does not exist. Could be first boot */
+			config_warn("[whowasdb] No database present at '%s', will start a new one", cfg.database);
+			return 1;
+		} else
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+		{
+			/* Re-open as unencrypted */
+			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+			if (!db)
+			{
+				/* This should actually never happen, unless some weird I/O error */
+				config_warn("[whowasdb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
+				return 0;
+			}
+		} else
+		{
+			config_warn("[whowasdb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
+			return 0;
+		}
+	}
+
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version != WHOWASDB_HEADER)
+	{
+		config_warn("[whowasdb] Database '%s' is not a whowas db (incorrect header)", cfg.database);
+		unrealdb_close(db);
+		return 0;
+	}
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version > whowasdb_version)
+	{
+		config_warn("[whowasdb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, whowasdb_version, version);
+		unrealdb_close(db);
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_int64(db, &count));
+
+	for (i=1; i <= count; i++)
+	{
+		// Variables
+		key = value = NULL;
+		nick = username = hostname = ip = realname = virthost = account = server = NULL;
+		connected_since = logontime = logofftime = 0;
+		event = 0;
+
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic != MAGIC_WHOWASDB_START)
+		{
+			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic start is 0x%x. Further reading aborted.", cfg.database, magic);
+			break;
+		}
+		while(1)
+		{
+			R_SAFE(unrealdb_read_str(db, &key));
+			R_SAFE(unrealdb_read_str(db, &value));
+			if (!strcmp(key, "nick"))
+			{
+				safe_strdup(nick, value);
+			} else
+			if (!strcmp(key, "username"))
+			{
+				safe_strdup(username, value);
+			} else
+			if (!strcmp(key, "hostname"))
+			{
+				safe_strdup(hostname, value);
+			} else
+			if (!strcmp(key, "ip"))
+			{
+				safe_strdup(ip, value);
+			} else
+			if (!strcmp(key, "realname"))
+			{
+				safe_strdup(realname, value);
+			} else
+			if (!strcmp(key, "connected_since"))
+			{
+				connected_since = atoll(value);
+				safe_free(value);
+			} else
+			if (!strcmp(key, "logontime"))
+			{
+				logontime = atoll(value);
+				safe_free(value);
+			} else
+			if (!strcmp(key, "logofftime"))
+			{
+				logofftime = atoll(value);
+				safe_free(value);
+			} else
+			if (!strcmp(key, "event"))
+			{
+				event = atoi(value);
+				if ((event < WHOWAS_LOWEST_EVENT) || (event > WHOWAS_HIGHEST_EVENT))
+					event = WHOWAS_EVENT_QUIT; /* safety */
+				safe_free(value);
+			} else
+			if (!strcmp(key, "server"))
+			{
+				safe_strdup(server, value);
+			} else
+			if (!strcmp(key, "virthost"))
+			{
+				safe_strdup(virthost, value);
+			} else
+			if (!strcmp(key, "account"))
+			{
+				safe_strdup(account, value);
+			} else
+			if (!strcmp(key, "end"))
+			{
+				safe_free(key);
+				safe_free(value);
+				break; /* DONE! */
+			}
+			safe_free(key);
+			safe_free(value);
+		}
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic != MAGIC_WHOWASDB_END)
+		{
+			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic end is 0x%x. Further reading aborted.", cfg.database, magic);
+			FreeWhowasEntry();
+			break;
+		}
+
+		if (nick && username && hostname && realname)
+		{
+			WhoWas *e = &WHOWAS[whowas_next];
+			if (e->hashv != -1)
+				free_whowas_fields(e);
+			/* Set values */
+			//unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_READ_RECORD", NULL,
+			//           "[whowasdb] Adding '$nick'...",
+			//           log_data_string("nick", nick));
+			e->hashv = hash_whowas_name(nick);
+			e->event = event;
+			e->connected_since = connected_since;
+			e->logon = logontime;
+			e->logoff = logofftime;
+			safe_strdup(e->name, nick);
+			safe_strdup(e->username, username);
+			safe_strdup(e->hostname, hostname);
+			safe_strdup(e->ip, ip);
+			if (virthost)
+				safe_strdup(e->virthost, virthost);
+			else
+				safe_strdup(e->virthost, "");
+			e->servername = find_or_add(server); /* scache */
+			safe_strdup(e->realname, realname);
+			safe_strdup(e->account, account);
+			e->online = NULL;
+			/* Server is special - scache shit */
+			/* Add to hash table */
+			add_whowas_to_list(&WHOWASHASH[e->hashv], e);
+			/* And advance pointer (well, integer) */
+			whowas_next++;
+			if (whowas_next == NICKNAMEHISTORYLENGTH)
+				whowas_next = 0;
+		}
+
+		FreeWhowasEntry();
+		added++;
+	}
+
+	unrealdb_close(db);
+
+	if (added)
+		config_status("[whowasdb] Added %d WHOWAS items", added);
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_BENCHMARK", NULL,
+	           "[whowasdb] Benchmark: LOAD DB: $time_msec microseconds",
+	           log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+#endif
+	return 1;
+}
+#undef FreeWhowasEntry
+#undef R_SAFE
diff --git a/ircd/src/modules/whox.c b/ircd/src/modules/whox.c
@@ -0,0 +1,988 @@
+/* cmd_whox.c / WHOX.
+ * based on code from charybdis and ircu.
+ * was originally made for tircd and modified to work with u4.
+ * - 2018 i <ircd@servx.org>
+ * - 2019 Bram Matthys (Syzop) <syzop@vulnscan.org>
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"whox",
+	"5.0",
+	"command /who",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+
+/* Defines */
+#define FIELD_CHANNEL	0x0001
+#define FIELD_HOP	0x0002
+#define FIELD_FLAGS	0x0004
+#define FIELD_HOST	0x0008
+#define FIELD_IP	0x0010
+#define FIELD_IDLE	0x0020
+#define FIELD_NICK	0x0040
+#define FIELD_INFO	0x0080
+#define FIELD_SERVER	0x0100
+#define FIELD_QUERYTYPE	0x0200 /* cookie for client */
+#define FIELD_USER	0x0400
+#define FIELD_ACCOUNT	0x0800
+#define FIELD_OPLEVEL	0x1000 /* meaningless and stupid, but whatever */
+#define FIELD_REALHOST	0x2000
+#define FIELD_MODES	0x4000
+#define FIELD_REPUTATION	0x8000
+
+#define WMATCH_NICK	0x0001
+#define WMATCH_USER	0x0002
+#define WMATCH_OPER	0x0004
+#define WMATCH_HOST	0x0008
+#define WMATCH_INFO	0x0010
+#define WMATCH_SERVER	0x0020
+#define WMATCH_ACCOUNT	0x0040
+#define WMATCH_IP	0x0080
+#define WMATCH_MODES	0x0100
+#define WMATCH_CONTIME	0x0200
+
+#define RPL_WHOSPCRPL	354
+
+#define WHO_ADD 1
+#define WHO_DEL 0
+
+#define HasField(x, y) ((x)->fields & (y))
+#define IsMatch(x, y) ((x)->matchsel & (y))
+
+#define IsMarked(x)           (moddata_client(x, whox_md).l)
+#define SetMark(x)            do { moddata_client(x, whox_md).l = 1; } while(0)
+#define ClearMark(x)          do { moddata_client(x, whox_md).l = 0; } while(0)
+
+/* Structs */
+struct who_format
+{
+	int fields;
+	int matchsel;
+	int umodes;
+	int noumodes;
+	const char *querytype;
+	int show_realhost;
+	int show_ip;
+	time_t contimemin;
+	time_t contimemax;
+};
+
+/* Global variables */
+ModDataInfo *whox_md = NULL;
+
+/* Forward declarations */
+CMD_FUNC(cmd_whox);
+static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt);
+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, 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()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	if (!CommandAdd(modinfo->handle, "WHO", cmd_whox, MAXPARA, CMD_USER))
+	{
+		config_warn("You cannot load both cmd_whox and cmd_who. You should ONLY load the cmd_whox module.");
+		return MOD_FAILED;
+	}
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "whox";
+	mreq.type = MODDATATYPE_CLIENT;
+	mreq.serialize = whox_md_serialize;
+	mreq.unserialize = whox_md_unserialize;
+	mreq.free = whox_md_free;
+	mreq.sync = 0;
+	whox_md = ModDataAdd(modinfo->handle, mreq);
+	if (!whox_md)
+	{
+		config_error("could not register whox moddata");
+		return MOD_FAILED;
+	}
+
+	ISupportAdd(modinfo->handle, "WHOX", NULL);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** whox module data operations: serialize (rare) */
+const char *whox_md_serialize(ModData *m)
+{
+	static char buf[32];
+	if (m->i == 0)
+		return NULL; /* not set */
+	snprintf(buf, sizeof(buf), "%d", m->i);
+	return buf;
+}
+
+/** whox module data operations: unserialize (rare) */
+void whox_md_unserialize(const char *str, ModData *m)
+{
+	m->i = atoi(str);
+}
+
+/** whox module data operations: free */
+void whox_md_free(ModData *md)
+{
+	/* we have nothing to free actually, but we must set to zero */
+	md->l = 0;
+}
+
+/** cmd_whox: standardized "extended" version of WHO.
+ * The good thing about WHOX is that it allows the client to define what
+ * output they want to see. Another good thing is that it is standardized
+ * (ok, actually it isn't... but hey!).
+ * The bad thing is that it's a lot less flexible than the original WHO
+ * we had in UnrealIRCd in 3.2.x and 4.0.x and that the WHOX spec defines
+ * that there are two ways to specify a mask (neither one is logical):
+ * WHO <mask> <flags>
+ * WHO <mask> <flags> <mask2>
+ * In case the latter is present then mask2 overrides the mask
+ * (and the original 'mask' is ignored)
+ * Yes, this indeed damn ugly. It shows (yet again) that they should never
+ * have put the mask before the flags.. what kind of logic was that?
+ * I wonder if the person who invented this also writes C functions like:
+ * int (char *param)func
+ * or types:
+ * cp mode,time=--preserve
+ * or creates configuration files like:
+ * Syzop oper {
+ * 5 maxlogins;
+ * I mean... really...? -- Syzop
+ * So, we will try to abide to the WHOX spec as much as possible but
+ * try to warn our users in case they use the 'original' UnrealIRCd
+ * WHO syntax which was WHO [+-]<flags> <mask>.
+ * Note that we won't catch all cases, but we do our best.
+ * When in doubt, we will assume WHOX so to not break the spec
+ * (which again.. doesn't exist... but hey..)
+ */
+CMD_FUNC(cmd_whox)
+{
+	char *mask;
+	const char *orig_mask;
+	char ch; /* Scratch char register */
+	const char *p; /* Scratch char pointer */
+	int member;
+	int operspy = 0;
+	struct who_format fmt;
+	const char *s;
+	char maskcopy[BUFSIZE];
+	Membership *lp;
+	Client *acptr;
+
+	memset(&fmt, 0, sizeof(fmt));
+
+	if (!MyUser(client))
+		return;
+
+	if ((parc < 2))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "WHO");
+		return;
+	}
+
+	if ((parc > 3) && parv[3])
+		orig_mask = parv[3];
+	else
+		orig_mask = parv[1];
+
+	if (!convert_classical_who_request(client, &parc, parv, &orig_mask, &fmt))
+		return;
+
+	/* Evaluate the flags now, we consider the second parameter
+	 * as "matchFlags%fieldsToInclude,querytype" */
+
+	if ((parc > 2) && parv[2] && *parv[2])
+	{
+		p = parv[2];
+		while (((ch = *(p++))) && (ch != '%') && (ch != ','))
+		{
+			switch (ch)
+			{
+				case 'o': fmt.matchsel |= WMATCH_OPER; continue;
+				case 'n': fmt.matchsel |= WMATCH_NICK; continue;
+				case 'u': fmt.matchsel |= WMATCH_USER; continue;
+				case 'h': fmt.matchsel |= WMATCH_HOST; continue;
+				case 'i': fmt.matchsel |= WMATCH_IP; continue;
+				case 'r': fmt.matchsel |= WMATCH_INFO; continue;
+				case 's': fmt.matchsel |= WMATCH_SERVER; continue;
+				case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue;
+				case 'm': fmt.matchsel |= WMATCH_MODES; continue;
+				case 't': fmt.matchsel |= WMATCH_CONTIME; continue;
+				case 'R':
+					if (IsOper(client))
+						fmt.show_realhost = 1;
+					continue;
+				case 'I':
+					if (IsOper(client))
+						fmt.show_ip = 1;
+					continue;
+			}
+		}
+	}
+
+	if ((parc > 2) && (s = strchr(parv[2], '%')) != NULL)
+	{
+		s++;
+		for (; *s != '\0'; s++)
+		{
+			switch (*s)
+			{
+				case 'c': fmt.fields |= FIELD_CHANNEL; break;
+				case 'd': fmt.fields |= FIELD_HOP; break;
+				case 'f': fmt.fields |= FIELD_FLAGS; break;
+				case 'h': fmt.fields |= FIELD_HOST; break;
+				case 'H': fmt.fields |= FIELD_REALHOST; break;
+				case 'i': fmt.fields |= FIELD_IP; break;
+				case 'l': fmt.fields |= FIELD_IDLE; break;
+				case 'n': fmt.fields |= FIELD_NICK; break;
+				case 'r': fmt.fields |= FIELD_INFO; break;
+				case 's': fmt.fields |= FIELD_SERVER; break;
+				case 't': fmt.fields |= FIELD_QUERYTYPE; break;
+				case 'u': fmt.fields |= FIELD_USER; break;
+				case 'a': fmt.fields |= FIELD_ACCOUNT; break;
+				case 'm': fmt.fields |= FIELD_MODES; break;
+				case 'o': fmt.fields |= FIELD_OPLEVEL; break;
+				case 'R': fmt.fields |= FIELD_REPUTATION; break;
+				case ',':
+					s++;
+					fmt.querytype = s;
+					s += strlen(s);
+					s--;
+					break;
+			}
+		}
+		if (BadPtr(fmt.querytype) || (strlen(fmt.querytype) > 3))
+			fmt.querytype = "0";
+	}
+
+	strlcpy(maskcopy, orig_mask, sizeof maskcopy);
+	mask = maskcopy;
+
+	collapse(mask);
+
+	if ((ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) &&
+	     ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL)))
+	{
+		operspy = 1;
+	}
+
+	if (fmt.matchsel & WMATCH_MODES)
+	{
+		char *s = mask;
+		int *umodes;
+		int what = WHO_ADD;
+	
+		while (*s)
+		{
+			Umode *um;
+
+			switch (*s)
+			{
+				case '+':
+					what = WHO_ADD;
+					s++;
+					break;
+				case '-':
+					what = WHO_DEL;
+					s++;
+					break;
+			}
+
+			if (!*s)
+				break;
+
+			if (what == WHO_ADD)
+				umodes = &fmt.umodes;
+			else
+				umodes = &fmt.noumodes;
+
+			for (um = usermodes; um; um = um->next)
+			{
+				if (um->letter == *s)
+				{
+					*umodes |= um->mode;
+					break;
+				}
+			}
+			s++;
+		}
+
+		if (!IsOper(client))
+		{
+			/* these are usermodes regular users may search for. just oper now. */
+			fmt.umodes &= UMODE_OPER;
+			fmt.noumodes &= UMODE_OPER;
+		}
+	}
+
+	/* match connect time */
+	if (fmt.matchsel & WMATCH_CONTIME)
+	{
+		char *s = mask;
+		time_t currenttime = TStime();
+
+		fmt.contimemin = 0;
+		fmt.contimemax = 0;
+
+		switch (*s)
+		{
+			case '<':
+				if (*s++)
+					fmt.contimemin = currenttime - config_checkval(s, CFG_TIME);
+				break;
+			case '>':
+				if (*s++)
+					fmt.contimemax = currenttime - config_checkval(s, CFG_TIME);
+				break;
+		}
+	}
+
+	/* '/who #some_channel' */
+	if (IsChannelName(mask))
+	{
+		Channel *channel = NULL;
+
+		/* List all users on a given channel */
+		if ((channel = find_channel(orig_mask)) != NULL)
+		{
+			if (IsMember(client, channel) || operspy)
+				do_who_on_channel(client, channel, 1, operspy, &fmt);
+			else if (!SecretChannel(channel))
+				do_who_on_channel(client, channel, 0, operspy, &fmt);
+		}
+
+		sendnumeric(client, RPL_ENDOFWHO, mask);
+		return;
+	}
+
+	if (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) ||
+                ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL))
+	{
+		operspy = 1;
+	}
+
+	/* '/who 0' for a global list.  this forces clients to actually
+	 * request a full list.  I presume its because of too many typos
+	 * with "/who" ;) --fl
+	 */
+	if (!strcmp(mask, "0"))
+		who_global(client, NULL, 0, &fmt);
+	else
+		who_global(client, mask, operspy, &fmt);
+
+	sendnumeric(client, RPL_ENDOFWHO, mask);
+}
+
+/* do_match
+ * inputs	- pointer to client requesting who
+ *		- pointer to client to do who on
+ *		- char * mask to match
+ *		- format options
+ * output	- 1 if match, 0 if no match
+ * side effects	- NONE
+ */
+static int do_match(Client *client, Client *acptr, char *mask, struct who_format *fmt)
+{
+	if (mask == NULL)
+		return 1;
+
+	/* default */
+	if (fmt->matchsel == 0 && (match_simple(mask, acptr->name) ||
+		match_simple(mask, acptr->user->username) ||
+		match_simple(mask, GetHost(acptr)) ||
+		(IsOper(client) &&
+		(match_simple(mask, acptr->user->realhost) ||
+		(acptr->ip &&
+		match_simple(mask, acptr->ip))))))
+	{
+		return 1;
+	}
+
+	/* match nick */
+	if (IsMatch(fmt, WMATCH_NICK) && match_simple(mask, acptr->name))
+		return 1;
+
+	/* match username */
+	if (IsMatch(fmt, WMATCH_USER) && match_simple(mask, acptr->user->username))
+		return 1;
+
+	/* match server */
+	if (IsMatch(fmt, WMATCH_SERVER) && IsOper(client) && match_simple(mask, acptr->user->server))
+		return 1;
+
+	/* match hostname */
+	if (IsMatch(fmt, WMATCH_HOST) && (match_simple(mask, GetHost(acptr)) ||
+		(IsOper(client) && (match_simple(mask, acptr->user->realhost) ||
+		(acptr->ip && match_simple(mask, acptr->ip))))))
+	{
+		return 1;
+	}
+
+	/* match realname */
+	if (IsMatch(fmt, WMATCH_INFO) && match_simple(mask, acptr->info))
+		return 1;
+
+	/* match ip address */
+	if (IsMatch(fmt, WMATCH_IP) && IsOper(client) && acptr->ip &&
+		match_user(mask, acptr, MATCH_CHECK_IP))
+		return 1;
+
+	/* match account */
+	if (IsMatch(fmt, WMATCH_ACCOUNT) && IsLoggedIn(acptr) && match_simple(mask, acptr->user->account))
+	{
+		return 1;
+	}
+
+	/* match usermodes */
+	if (IsMatch(fmt, WMATCH_MODES) && (fmt->umodes || fmt->noumodes))
+	{
+		long umodes = acptr->umodes;
+		if ((acptr->umodes & UMODE_HIDEOPER) && !IsOper(client))
+			umodes &= ~UMODE_OPER; /* pretend -o if +H */
+		/* Now check 'umodes' (not acptr->umodes),
+		 * If multiple conditions are specified it is an
+		 * AND condition and not an OR.
+		 */
+		if (((umodes & fmt->umodes) == fmt->umodes) &&
+		    ((umodes & fmt->noumodes) == 0))
+		{
+			return 1;
+		}
+	}
+
+	/* match connect time */
+	if (IsMatch(fmt, WMATCH_CONTIME) && MyConnect(acptr) && (fmt->contimemin || fmt->contimemax))
+	{
+		if (fmt->contimemin && (acptr->local->creationtime > fmt->contimemin))
+			return 1;
+
+		if (fmt->contimemax && (acptr->local->creationtime < fmt->contimemax))
+			return 1;
+	}
+
+	return 0;
+}
+
+/* who_common_channel
+ * inputs		- pointer to client requesting who
+ *			- pointer to channel.
+ *			- char * mask to match
+ *			- int if oper on a server or not
+ *			- pointer to int maxmatches
+ *			- format options
+ * output		- NONE
+ * side effects 	- lists matching clients on specified channel,
+ *			  marks matched clients.
+ *
+ * NOTE: only call this from who_global() due to client marking!
+ */
+
+static void who_common_channel(Client *client, Channel *channel,
+	char *mask, int *maxmatches, struct who_format *fmt)
+{
+	Member *cm = channel->members;
+	Client *acptr;
+	Hook *h;
+	int i = 0;
+
+	for (cm = channel->members; cm; cm = cm->next)
+	{
+		acptr = cm->client;
+
+		if (IsMarked(acptr))
+			continue;
+
+		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
+			continue;
+
+		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(acptr,channel);
+			if (i != 0)
+				break;
+		}
+
+		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 (do_match(client, acptr, mask, fmt))
+			{
+				do_who(client, acptr, NULL, fmt);
+				--(*maxmatches);
+			}
+		}
+	}
+}
+
+/*
+ * who_global
+ *
+ * inputs		- pointer to client requesting who
+ *			- char * mask to match
+ *			- int if oper on a server or not
+ *			- int if operspy or not
+ *			- format options
+ * output		- NONE
+ * side effects		- do a global scan of all clients looking for match
+ *			  this is slightly expensive on EFnet ...
+ *			  marks assumed cleared for all clients initially
+ *			  and will be left cleared on return
+ */
+
+static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt)
+{
+	Client *hunted = NULL;
+	Client *acptr;
+	int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
+
+	/* If searching for a nick explicitly, then include it later on in the result: */
+	if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0)))
+		hunted = find_user(mask, NULL);
+
+	/* Initialize the markers to zero */
+	list_for_each_entry(acptr, &client_list, client_node)
+		ClearMark(acptr);
+
+	/* First, if not operspy, then list all matching clients on common channels */
+	if (!operspy)
+	{
+		Membership *lp;
+
+		for (lp = client->user->channel; lp; lp = lp->next)
+			who_common_channel(client, lp->channel, mask, &maxmatches, fmt);
+	}
+
+	/* Second, list all matching visible clients. */
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (!IsUser(acptr))
+			continue;
+
+		if (IsInvisible(acptr) && !operspy && (client != acptr) && (acptr != hunted))
+			continue;
+
+		if (IsMarked(acptr))
+			continue;
+
+		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
+			continue;
+
+		if (maxmatches > 0)
+		{
+			if (do_match(client, acptr, mask, fmt))
+ 			{
+				do_who(client, acptr, NULL, fmt);
+				--maxmatches;
+ 			}
+ 		}
+	}
+
+	if (maxmatches <= 0)
+		sendnumeric(client, ERR_TOOMANYMATCHES, "WHO", "output too large, truncated");
+}
+
+/*
+ * do_who_on_channel
+ *
+ * inputs		- pointer to client requesting who
+ *			- pointer to channel to do who on
+ *			- The "real name" of this channel
+ *			- int if client is a server oper or not
+ *			- int if client is member or not
+ *			- format options
+ * output		- NONE
+ * side effects		- do a who on given channel
+ */
+
+static void do_who_on_channel(Client *client, Channel *channel,
+	int member, int operspy, struct who_format *fmt)
+{
+	Member *cm = channel->members;
+	Hook *h;
+	int i = 0;
+
+	for (cm = channel->members; cm; cm = cm->next)
+	{
+		Client *acptr = cm->client;
+
+		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
+			continue;
+
+		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
+		{
+			i = (*(h->func.intfunc))(acptr,channel);
+			if (i != 0)
+				break;
+		}
+
+		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))
+			do_who(client, acptr, channel, fmt);
+	}
+}
+
+/*
+ * append_format
+ *
+ * inputs		- pointer to buffer
+ *			- size of buffer
+ *			- pointer to position
+ *			- format string
+ *			- arguments for format
+ * output		- NONE
+ * side effects 	- position incremented, possibly beyond size of buffer
+ *			  this allows detecting overflow
+ */
+
+static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...)
+{
+	size_t max, result;
+	va_list ap;
+
+	max = *pos >= bufsize ? 0 : bufsize - *pos;
+	va_start(ap, fmt);
+	result = vsnprintf(buf + *pos, max, fmt, ap);
+	va_end(ap);
+	*pos += result;
+}
+
+/*
+ * show_ip()		- asks if the true IP should be shown when source is
+ *			  asking for info about target
+ *
+ * Inputs		- client who is asking
+ *			- acptr who do we want the info on
+ * Output		- returns 1 if clear IP can be shown, otherwise 0
+ * Side Effects		- none
+ */
+
+static int show_ip(Client *client, Client *acptr)
+{
+	if (IsServer(acptr))
+		return 0;
+	else if ((client != NULL) && (MyConnect(client) && !IsOper(client)) && (client == acptr))
+		return 1;
+	else if (IsHidden(acptr) && ((client != NULL) && !IsOper(client)))
+		return 0;
+	else
+		return 1;
+}
+
+/*
+ * do_who
+ *
+ * inputs	- pointer to client requesting who
+ *		- pointer to client to do who on
+ *		- channel or NULL
+ *		- format options
+ * output	- NONE
+ * side effects - do a who on given person
+ */
+
+static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt)
+{
+	char status[20];
+	char str[510 + 1];
+	size_t pos;
+	int hide = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
+	int i = 0;
+	Hook *h;
+
+	if (acptr->user->away)
+ 		status[i++] = 'G';
+ 	else
+ 		status[i++] = 'H';
+
+	if (IsRegNick(acptr))
+		status[i++] = 'r';
+
+	if (IsSecureConnect(acptr))
+		status[i++] = 's';
+
+	for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
+	{
+		int ret = (*(h->func.intfunc))(client, acptr, NULL, NULL, status, 0);
+		if (ret != 0)
+			status[i++] = (char)ret;
+	}
+
+	if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
+		status[i++] = '*';
+
+	if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client)))
+		status[i++] = '!';
+
+	if (channel)
+	{
+		Membership *lp;
+
+		if ((lp = find_membership_link(acptr->user->channel, channel)))
+		{
+			if (!(fmt->fields || HasCapability(client, "multi-prefix")))
+			{
+				/* 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) */
+				strcpy(&status[i], modes_to_prefix(lp->member_modes));
+				i += strlen(&status[i]);
+			}
+		}
+	}
+
+	status[i] = '\0';
+ 
+	if (fmt->fields == 0)
+	{
+		char *host;
+		if (fmt->show_realhost)
+			host = acptr->user->realhost;
+		else if (fmt->show_ip)
+			host = GetIP(acptr);
+		else
+			host = GetHost(acptr);
+		sendnumeric(client, RPL_WHOREPLY,
+			channel ? channel->name : "*",
+			acptr->user->username, host,
+			hide ? "*" : acptr->user->server,
+			acptr->name, status, hide ? 0 : acptr->hopcount, acptr->info);
+	} else
+	{
+		str[0] = '\0';
+		pos = 0;
+		append_format(str, sizeof str, &pos, ":%s %d %s", me.name, RPL_WHOSPCRPL, client->name);
+		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->name : "*");
+		if (HasField(fmt, FIELD_USER))
+			append_format(str, sizeof str, &pos, " %s", acptr->user->username);
+		if (HasField(fmt, FIELD_IP))
+		{
+			if (show_ip(client, acptr) && acptr->ip)
+				append_format(str, sizeof str, &pos, " %s", acptr->ip);
+			else
+				append_format(str, sizeof str, &pos, " %s", "255.255.255.255");
+		}
+
+		if (HasField(fmt, FIELD_HOST) || HasField(fmt, FIELD_REALHOST))
+		{
+			if (IsOper(client) && HasField(fmt, FIELD_REALHOST))
+				append_format(str, sizeof str, &pos, " %s", acptr->user->realhost);
+			else
+				append_format(str, sizeof str, &pos, " %s", GetHost(acptr));
+		}
+
+		if (HasField(fmt, FIELD_SERVER))
+			append_format(str, sizeof str, &pos, " %s", hide ? "*" : acptr->user->server);
+		if (HasField(fmt, FIELD_NICK))
+			append_format(str, sizeof str, &pos, " %s", acptr->name);
+		if (HasField(fmt, FIELD_FLAGS))
+			append_format(str, sizeof str, &pos, " %s", status);
+		if (HasField(fmt, FIELD_MODES))
+		{
+			if (IsOper(client))
+			{
+				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->idle_since) : 0));
+		}
+		if (HasField(fmt, FIELD_ACCOUNT))
+			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 && check_channel_access(acptr, channel, "hoaq")) ? "999" : "n/a");
+		if (HasField(fmt, FIELD_REPUTATION))
+		{
+			if (IsOper(client))
+				append_format(str, sizeof str, &pos, " %d", GetReputation(acptr));
+			else
+				append_format(str, sizeof str, &pos, " %s", "*");
+		}
+		if (HasField(fmt, FIELD_INFO))
+			append_format(str, sizeof str, &pos, " :%s", acptr->info);
+
+		sendto_one(client, NULL, "%s", str);
+	}
+}
+
+/* Yeah, this is fun. Thank you WHOX !!! */
+static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt)
+{
+	const char *p;
+	static char pbuf1[512], pbuf2[512];
+	int points;
+
+	/* Figure out if the user is doing a 'classical' UnrealIRCd request,
+	 * which can be recognized as follows:
+	 * 1) Always a + or - as 1st character for the 1st parameter.
+	 * 2) Unlikely to have a % (percent sign) in the 2nd parameter
+	 * 3) Unlikely to have a 3rd parameter
+	 * On the other hand WHOX requests are:
+	 * 4) never going to have a '*', '?' or '.' as 2nd parameter
+	 * 5) never going to have a '+' or '-' as 1st character in 2nd parameter
+	 * Additionally, WHOX requests are useless - and thus unlikely -
+	 * to search for a mask mask starting with + or -  except when:
+	 * 6) searching for 'm' (mode)
+	 * 7) or 'r' (info, realname)
+	 * ..for which this would be a meaningful request.
+	 * The end result is that we can do quite some useful heuristics
+	 * except for some corner cases.
+	 */
+	if (((*parv[1] == '+') || (*parv[1] == '-')) &&
+	    (!parv[2] || !strchr(parv[2], '%')) &&
+	    (*parc < 4))
+	{
+		/* Conditions 1-3 of above comment are met, now we deal
+		 * with conditions 4-7.
+		 */
+		if (parv[2] &&
+		    !strchr(parv[2], '*') && !strchr(parv[2], '?') &&
+		    !strchr(parv[2], '.') &&
+		    !strchr(parv[2], '+') && !strchr(parv[2], '-') &&
+		    (strchr(parv[2], 'm') || strchr(parv[2], 'r')))
+		{
+			/* 'WHO +something m" or even
+			 * 'WHO +c #something' (which includes 'm')
+			 * could mean either WHO or WHOX style
+			 */
+		} else {
+			/* If we get here then it's an classical
+			 * UnrealIRCd-style WHO request which has
+			 * the order: WHO <options> <mask>
+			 */
+			char oldrequest[256];
+			snprintf(oldrequest, sizeof(oldrequest), "WHO %s%s%s",
+			         parv[1], parv[2] ? " " : "", parv[2] ? parv[2] : "");
+			if (parv[2])
+			{
+				const char *swap = parv[1];
+				parv[1] = parv[2];
+				parv[2] = swap;
+			} else {
+				/* A request like 'WHO +I' or 'WHO +R' */
+				parv[2] = parv[1];
+				parv[1] = "*";
+				parv[3] = NULL;
+				*parc = 3;
+			}
+
+			/* Ok, that was the first step, now we need to convert the
+			 * flags since they have changed a little as well:
+			 * Flag a: user is away                                            << no longer exists
+			 * Flag c <channel>: user is on <channel>                          << no longer exists
+			 * Flag g <gcos/realname>: user has string <gcos> in his/her GCOS  << now called 'r'
+			 * Flag h <host>: user has string <host> in his/her hostname       << no change
+			 * Flag i <ip>: user has string <ip> in his/her IP address         << no change
+			 * Flag m <usermodes>: user has <usermodes> set                    << behavior change
+			 * Flag n <nick>: user has string <nick> in his/her nickname       << no change
+			 * Flag s <server>: user is on server <server>                     << no change
+			 * Flag u <user>: user has string <user> in his/her username       << no change
+			 * Behavior flags:
+			 * Flag M: check for user in channels I am a member of             << no longer exists
+			 * Flag R: show users' real hostnames                              << no change (re-added)
+			 * Flag I: show users' IP addresses                                << no change (re-added)
+			 */
+
+			if (strchr(parv[2], 'a'))
+			{
+				sendnotice(client, "WHO request '%s' failed: flag 'a' no longer exists with WHOX.", oldrequest);
+				return 0;
+			}
+			if (strchr(parv[2], 'c'))
+			{
+				sendnotice(client, "WHO request '%s' failed: flag 'c' no longer exists with WHOX.", oldrequest);
+				return 0;
+			}
+			if (strchr(parv[2], 'g'))
+			{
+				char *w;
+				strlcpy(pbuf2, parv[2], sizeof(pbuf2));
+				for (w = pbuf2; *w; w++)
+				{
+					if (*w == 'g')
+					{
+						*w = 'r';
+						break;
+					}
+				}
+				parv[2] = pbuf2;
+			}
+
+			/* "WHO -m xyz" (now: xyz -m) should become "WHO -xyz m"
+			 * Wow, this seems overly complex, but okay...
+			 */
+			points = 0;
+			for (p = parv[2]; *p; p++)
+			{
+				if (*p == '-')
+					points = 1;
+				else if (*p == '+')
+					points = 0;
+				else if (points && (*p == 'm'))
+				{
+					points = 2;
+					break;
+				}
+			}
+			if (points == 2)
+			{
+				snprintf(pbuf1, sizeof(pbuf1), "-%s", parv[1]);
+				parv[1] = pbuf1;
+			}
+
+			if ((*parv[2] == '+') || (*parv[2] == '-'))
+				parv[2] = parv[2]+1; /* strip '+'/'-' prefix, which does not exist in WHOX */
+
+			sendnotice(client, "WHO request '%s' changed to match new WHOX syntax: 'WHO %s %s'",
+				oldrequest, parv[1], parv[2]);
+			*orig_mask = parv[1];
+		}
+	}
+	return 1;
+}
diff --git a/ircd/src/openssl_hostname_validation.c b/ircd/src/openssl_hostname_validation.c
@@ -0,0 +1,402 @@
+/* This file contains both code from cURL and hostname
+ * validation code from the ssl conservatory (in that order).
+ *
+ * The goal is that all this code won't be needed anymore once
+ * everyone is running a recent OpenSSL/LibreSSL that has
+ * X509_check_host(). Until that time, unfortunately, we need
+ * these 400+ lines to do just that... -- Syzop, Sep/2017
+ */
+
+// Get rid of OSX 10.7 and greater deprecation warnings.
+#if defined(__APPLE__) && defined(__clang__)
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+#include <openssl/x509v3.h>
+#include <openssl/ssl.h>
+#include <string.h>
+
+#include "openssl_hostname_validation.h"
+
+#define HOSTNAME_MAX_SIZE 255
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
+#define ASN1_STRING_get0_data ASN1_STRING_data
+#endif
+
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+/* This file is an amalgamation of hostcheck.c and most of rawstr.c
+   from cURL.  The contents of the COPYING file mentioned above are:
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1996 - 2013, Daniel Stenberg, <daniel@haxx.se>.
+
+All rights reserved.
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright
+notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall not
+be used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization of the copyright holder.
+*/
+
+/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because
+   its behavior is altered by the current locale. */
+static char Curl_raw_toupper(char in)
+{
+  switch (in) {
+  case 'a':
+    return 'A';
+  case 'b':
+    return 'B';
+  case 'c':
+    return 'C';
+  case 'd':
+    return 'D';
+  case 'e':
+    return 'E';
+  case 'f':
+    return 'F';
+  case 'g':
+    return 'G';
+  case 'h':
+    return 'H';
+  case 'i':
+    return 'I';
+  case 'j':
+    return 'J';
+  case 'k':
+    return 'K';
+  case 'l':
+    return 'L';
+  case 'm':
+    return 'M';
+  case 'n':
+    return 'N';
+  case 'o':
+    return 'O';
+  case 'p':
+    return 'P';
+  case 'q':
+    return 'Q';
+  case 'r':
+    return 'R';
+  case 's':
+    return 'S';
+  case 't':
+    return 'T';
+  case 'u':
+    return 'U';
+  case 'v':
+    return 'V';
+  case 'w':
+    return 'W';
+  case 'x':
+    return 'X';
+  case 'y':
+    return 'Y';
+  case 'z':
+    return 'Z';
+  }
+  return in;
+}
+
+/*
+ * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant
+ * to be locale independent and only compare strings we know are safe for
+ * this.  See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for
+ * some further explanation to why this function is necessary.
+ *
+ * The function is capable of comparing a-z case insensitively even for
+ * non-ascii.
+ */
+
+static int Curl_raw_equal(const char *first, const char *second)
+{
+  while(*first && *second) {
+    if (Curl_raw_toupper(*first) != Curl_raw_toupper(*second))
+      /* get out of the loop as soon as they don't match */
+      break;
+    first++;
+    second++;
+  }
+  /* we do the comparison here (possibly again), just to make sure that if the
+     loop above is skipped because one of the strings reached zero, we must not
+     return this as a successful match */
+  return (Curl_raw_toupper(*first) == Curl_raw_toupper(*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)) {
+      break;
+    }
+    max--;
+    first++;
+    second++;
+  }
+  if (0 == max)
+    return 1; /* they are equal this far */
+
+  return Curl_raw_toupper(*first) == Curl_raw_toupper(*second);
+}
+
+/*
+ * Match a hostname against a wildcard pattern.
+ * E.g.
+ *  "foo.host.com" matches "*.host.com".
+ *
+ * We use the matching rule described in RFC6125, section 6.4.3.
+ * http://tools.ietf.org/html/rfc6125#section-6.4.3
+ */
+
+static int hostmatch(const char *hostname, const char *pattern)
+{
+  const char *pattern_label_end, *pattern_wildcard, *hostname_label_end;
+  int wildcard_enabled;
+  size_t prefixlen, suffixlen;
+  pattern_wildcard = strchr(pattern, '*');
+  if (pattern_wildcard == NULL)
+    return Curl_raw_equal(pattern, hostname) ?
+      CURL_HOST_MATCH : CURL_HOST_NOMATCH;
+
+  /* We require at least 2 dots in pattern to avoid too wide wildcard
+     match. */
+  wildcard_enabled = 1;
+  pattern_label_end = strchr(pattern, '.');
+  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)
+    return Curl_raw_equal(pattern, hostname) ?
+      CURL_HOST_MATCH : CURL_HOST_NOMATCH;
+
+  hostname_label_end = strchr(hostname, '.');
+  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)
+    return CURL_HOST_NOMATCH;
+
+  prefixlen = pattern_wildcard - pattern;
+  suffixlen = pattern_label_end - (pattern_wildcard+1);
+  return Curl_raw_nequal(pattern, hostname, prefixlen) &&
+    Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen,
+                    suffixlen) ?
+    CURL_HOST_MATCH : CURL_HOST_NOMATCH;
+}
+
+int Curl_cert_hostcheck(const char *match_pattern, const char *hostname)
+{
+  if (!match_pattern || !*match_pattern ||
+      !hostname || !*hostname) /* sanity check */
+    return 0;
+
+  if (Curl_raw_equal(hostname, match_pattern)) /* trivial case */
+    return 1;
+
+  if (hostmatch(hostname,match_pattern) == CURL_HOST_MATCH)
+    return 1;
+  return 0;
+}
+/* End of cURL related functions */
+
+/* Start of host validation code */
+/* Obtained from: https://github.com/iSECPartners/ssl-conservatory */
+
+/*
+Copyright (C) 2012, iSEC Partners.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/*
+ * Helper functions to perform basic hostname validation using OpenSSL.
+ *
+ * Please read "everything-you-wanted-to-know-about-openssl.pdf" before
+ * attempting to use this code. This whitepaper describes how the code works,
+ * how it should be used, and what its limitations are.
+ *
+ * Author:  Alban Diquet
+ * License: See LICENSE
+ *
+ */
+
+/**
+* Tries to find a match for hostname in the certificate's Common Name field.
+*
+* Returns MatchFound if a match was found.
+* Returns MatchNotFound if no matches were found.
+* Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
+* Returns Error if the Common Name could not be extracted.
+*/
+static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
+        int common_name_loc = -1;
+        X509_NAME_ENTRY *common_name_entry = NULL;
+        ASN1_STRING *common_name_asn1 = NULL;
+        const char *common_name_str = NULL;
+
+        // Find the position of the CN field in the Subject field of the certificate
+        common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
+        if (common_name_loc < 0) {
+                return Error;
+        }
+
+        // Extract the CN field
+        common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
+        if (common_name_entry == NULL) {
+                return Error;
+        }
+
+        // Convert the CN field to a C string
+        common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
+        if (common_name_asn1 == NULL) {
+                return Error;
+        }
+        common_name_str = (char *) ASN1_STRING_get0_data(common_name_asn1);
+
+        // Make sure there isn't an embedded NUL character in the CN
+        if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
+                return MalformedCertificate;
+        }
+
+        // Compare expected hostname with the CN
+        if (Curl_cert_hostcheck(common_name_str, hostname) == CURL_HOST_MATCH) {
+                return MatchFound;
+        }
+        else {
+                return MatchNotFound;
+        }
+}
+
+
+/**
+* Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
+*
+* Returns MatchFound if a match was found.
+* Returns MatchNotFound if no matches were found.
+* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
+* Returns NoSANPresent if the SAN extension was not present in the certificate.
+*/
+static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
+        HostnameValidationResult result = MatchNotFound;
+        int i;
+        int san_names_nb = -1;
+        STACK_OF(GENERAL_NAME) *san_names = NULL;
+
+        // Try to extract the names within the SAN extension from the certificate
+        san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
+        if (san_names == NULL) {
+                return NoSANPresent;
+        }
+        san_names_nb = sk_GENERAL_NAME_num(san_names);
+
+        // Check each name within the extension
+        for (i=0; i<san_names_nb; i++) {
+                const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
+
+                if (current_name->type == GEN_DNS) {
+                        // Current name is a DNS name, let's check it
+                        const char *dns_name = (char *) ASN1_STRING_get0_data(current_name->d.dNSName);
+
+                        // Make sure there isn't an embedded NUL character in the DNS name
+                        if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
+                                result = MalformedCertificate;
+                                break;
+                        }
+                        else { // Compare expected hostname with the DNS name
+                                if (Curl_cert_hostcheck(dns_name, hostname)
+                                    == CURL_HOST_MATCH) {
+                                        result = MatchFound;
+                                        break;
+                                }
+                        }
+                }
+        }
+        sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
+
+        return result;
+}
+
+
+/**
+* Validates the server's identity by looking for the expected hostname in the
+* server's certificate. As described in RFC 6125, it first tries to find a match
+* in the Subject Alternative Name extension. If the extension is not present in
+* the certificate, it checks the Common Name instead.
+*
+* Returns MatchFound if a match was found.
+* Returns MatchNotFound if no matches were found.
+* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
+* Returns Error if there was an error.
+*/
+HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert) {
+        HostnameValidationResult result;
+
+        if ((hostname == NULL) || (server_cert == NULL))
+                return Error;
+
+        // First try the Subject Alternative Names extension
+        result = matches_subject_alternative_name(hostname, server_cert);
+        if (result == NoSANPresent) {
+                // Extension was not found: try the Common Name
+                result = matches_common_name(hostname, server_cert);
+        }
+
+        return result;
+}
diff --git a/ircd/src/operclass.c b/ircd/src/operclass.c
@@ -0,0 +1,348 @@
+/** Oper classes code.
+ * (C) Copyright 2015-present tmcarthur and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+typedef struct OperClassPathNode OperClassPathNode;
+typedef struct OperClassCallbackNode OperClassCallbackNode;
+
+struct OperClassPathNode
+{
+	OperClassPathNode *prev,*next;
+	OperClassPathNode *children;
+	char *identifier;
+	OperClassCallbackNode *callbacks;
+};
+
+struct OperClassCallbackNode
+{
+	OperClassCallbackNode *prev, *next;
+	OperClassPathNode *parent;
+	OperClassEntryEvalCallback callback;
+};
+
+struct OperClassValidator
+{
+	Module *owner;
+	OperClassCallbackNode *node;
+};
+
+OperClassACLPath *OperClass_parsePath(const char *path);
+void OperClass_freePath(OperClassACLPath *path);
+OperClassPathNode *OperClass_findPathNodeForIdentifier(char *identifier, OperClassPathNode *head);
+
+OperClassPathNode *rootEvalNode = NULL;
+
+OperClassValidator *OperClassAddValidator(Module *module, char *pathStr, OperClassEntryEvalCallback callback)
+{
+	OperClassPathNode *node,*nextNode;
+	OperClassCallbackNode *callbackNode;
+	OperClassValidator *validator; 
+	OperClassACLPath *path = OperClass_parsePath(pathStr);
+
+	if (!rootEvalNode)
+	{
+		rootEvalNode = safe_alloc(sizeof(OperClassPathNode));
+	}
+
+	node = rootEvalNode;
+
+	while (path)
+	{
+		nextNode = OperClass_findPathNodeForIdentifier(path->identifier,node->children);
+		if (!nextNode)
+		{
+			nextNode = safe_alloc(sizeof(OperClassPathNode));
+			safe_strdup(nextNode->identifier, path->identifier);
+			AddListItem(nextNode,node->children);
+		}
+		node = nextNode;
+		path = path->next;
+	}
+
+	callbackNode = safe_alloc(sizeof(OperClassCallbackNode));
+	callbackNode->callback = callback;
+	callbackNode->parent = node;	
+	AddListItem(callbackNode,node->callbacks);
+
+	validator = safe_alloc(sizeof(OperClassValidator));
+	validator->node = callbackNode;	
+	validator->owner = module;
+
+	if (module)
+	{
+		ModuleObject *mobj = safe_alloc(sizeof(ModuleObject));
+		mobj->object.validator = validator;
+		mobj->type = MOBJ_VALIDATOR;
+		AddListItem(mobj, module->objects);
+		module->errorcode = MODERR_NOERROR;
+	}
+
+	OperClass_freePath(path);
+
+	return validator;
+}
+
+void OperClassValidatorDel(OperClassValidator *validator)
+{
+	if (validator->owner)
+	{
+		ModuleObject *mdobj;
+		for (mdobj = validator->owner->objects; mdobj; mdobj = mdobj->next)
+		{
+			if ((mdobj->type == MOBJ_VALIDATOR) && (mdobj->object.validator == validator))
+			{
+				DelListItem(mdobj, validator->owner->objects);
+				safe_free(mdobj);
+				break;
+			}
+		}
+		validator->owner = NULL;
+	}
+	
+	/* Technically, the below leaks memory if you don't re-register
+	 * another validator at same path, but it is cheaper than walking
+	 * back up and doing cleanup in practice, since this tree is very small
+	 */
+	DelListItem(validator->node,validator->node->parent->callbacks);
+	safe_free(validator->node);
+	safe_free(validator);	
+}
+
+OperClassACLPath *OperClass_parsePath(const char *path)
+{
+	char *pathCopy = raw_strdup(path);
+	OperClassACLPath *pathHead = NULL;
+	OperClassACLPath *tmpPath;
+	char *str = strtok(pathCopy,":");
+	while (str)
+	{
+		tmpPath = safe_alloc(sizeof(OperClassACLPath));
+		safe_strdup(tmpPath->identifier, str);
+		AddListItem(tmpPath,pathHead);
+		str = strtok(NULL,":");
+	}
+
+	while (pathHead->next)
+	{
+		tmpPath = pathHead->next;
+		pathHead->next = pathHead->prev;
+		pathHead->prev = tmpPath;
+		pathHead = tmpPath;
+	}
+	pathHead->next = pathHead->prev;
+	pathHead->prev = NULL;	
+
+	safe_free(pathCopy);
+	return pathHead;
+}
+
+void OperClass_freePath(OperClassACLPath *path)
+{
+	OperClassACLPath *next;
+	while (path)
+	{
+		next = path->next;
+		safe_free(path->identifier);
+		safe_free(path);
+		path = next;
+	}	
+}
+
+OperClassACL *OperClass_FindACL(OperClassACL *acl, char *name)
+{
+	for (;acl;acl = acl->next)
+	{
+		if (!strcmp(acl->name,name))
+		{ 
+			return acl;
+		}
+	}
+	return NULL;
+}
+
+OperClassPathNode *OperClass_findPathNodeForIdentifier(char *identifier, OperClassPathNode *head)
+{
+	for (; head; head = head->next)
+	{
+		if (!strcmp(head->identifier,identifier))
+		{
+			return head;
+		}
+	}
+	return NULL;
+}
+
+unsigned char OperClass_evaluateACLEntry(OperClassACLEntry *entry, OperClassACLPath *path, OperClassCheckParams *params)
+{
+	OperClassPathNode *node = rootEvalNode;	
+	OperClassCallbackNode *callbackNode = NULL;
+	unsigned char eval = 0;
+
+	/* If no variables, always match */
+	if (!entry->variables)
+	{
+		return 1;
+	}
+
+	/* Go as deep as possible */
+	while (path->next && node)
+	{
+		node = OperClass_findPathNodeForIdentifier(path->identifier,node);	
+		/* If we can't find a node we need, and we have vars, no match */
+		if (!node)
+		{
+			return 0;
+		}
+		node = node->children;
+		path = path->next;
+	}
+
+	/* If no evals for full path, no match */
+	if (path->next)
+	{
+		return 0;
+	}
+
+
+	/* We have a valid node, execute all callback nodes */
+	for (callbackNode = node->callbacks; callbackNode; callbackNode = callbackNode->next)
+	{
+		eval = callbackNode->callback(entry->variables,params);
+	}
+
+	return eval;	
+}
+
+OperPermission ValidatePermissionsForPathEx(OperClassACL *acl, OperClassACLPath *path, OperClassCheckParams *params)
+{
+	/** Evaluate into ACL struct as deep as possible **/
+	OperClassACLPath *basePath = path;
+	OperClassACL *tmp;
+	OperClassACLEntry *entry;
+	unsigned char allow = 0;
+	unsigned char deny = 0;
+	unsigned char aclNotFound = 0;
+
+	path = path->next; /* Avoid first level since we have resolved it */
+	while (path && acl->acls)
+	{
+		tmp = OperClass_FindACL(acl->acls,path->identifier);
+		if (!tmp)
+		{
+			aclNotFound = 1;
+			break;
+		}
+		path = path->next;
+		acl = tmp;
+	}
+	/** If node does not exist, but most specific one has other ACLs, deny **/
+	if (acl->acls && aclNotFound)
+	{
+		return OPER_DENY;
+	}
+
+	/** If node exists for this but has no ACL entries, allow **/
+	if (!acl->entries)
+	{
+		return OPER_ALLOW;
+	}
+	/** Process entries **/
+	for (entry = acl->entries; entry; entry = entry->next)
+	{
+		unsigned char result;
+		/* Short circuit if we already have valid block */
+		if (entry->type == OPERCLASSENTRY_ALLOW && allow)
+			continue;
+		if (entry->type == OPERCLASSENTRY_DENY && deny)
+			continue;
+
+		result = OperClass_evaluateACLEntry(entry,basePath,params);
+		if (entry->type == OPERCLASSENTRY_ALLOW)
+		{
+			allow = result;
+		}
+		else
+		{
+			deny = result;
+		}
+	}
+
+	/** We only permit if an allow matched AND no deny matched **/
+	if (allow && !deny)
+	{
+		return OPER_ALLOW;
+	}
+
+	return OPER_DENY;
+}
+
+OperPermission ValidatePermissionsForPath(const char *path, Client *client, Client *victim, Channel *channel, const void *extra)
+{
+	ConfigItem_oper *ce_oper;
+	const char *operclass;
+	ConfigItem_operclass *ce_operClass;
+	OperClass *oc = NULL;
+	OperClassACLPath *operPath;
+
+	if (!client)
+		return OPER_DENY;
+
+	/* Trust Servers, U-Lines and remote opers */
+	if (IsServer(client) || IsMe(client) || IsULine(client) || (IsOper(client) && !MyUser(client)))
+		return OPER_ALLOW;
+
+	if (!IsOper(client))
+		return OPER_DENY;
+
+	ce_oper = find_oper(client->user->operlogin);
+	if (!ce_oper)
+	{
+		operclass = moddata_client_get(client, "operclass");
+		if (!operclass)
+			return OPER_DENY;
+	} else
+	{
+		operclass = ce_oper->operclass;
+	}
+
+	ce_operClass = find_operclass(operclass);
+	if (!ce_operClass)
+		return OPER_DENY;
+
+	oc = ce_operClass->classStruct;
+	operPath = OperClass_parsePath(path);
+	while (oc && operPath)
+	{
+		OperClassACL *acl = OperClass_FindACL(oc->acls,operPath->identifier);
+		if (acl)
+		{
+			OperPermission perm;
+			OperClassCheckParams *params = safe_alloc(sizeof(OperClassCheckParams));
+			params->client = client;
+			params->victim = victim;
+			params->channel = channel;
+			params->extra = extra;
+			
+			perm = ValidatePermissionsForPathEx(acl, operPath, params);
+			OperClass_freePath(operPath);
+			safe_free(params);
+			return perm;
+		}
+		if (!oc->ISA)
+		{
+			break;
+		}
+		ce_operClass = find_operclass(oc->ISA);
+		if (ce_operClass)
+		{
+			oc = ce_operClass->classStruct;
+		} else {
+			break; /* parent not found */
+		}
+	}
+	OperClass_freePath(operPath);
+	return OPER_DENY;
+}
diff --git a/ircd/src/parse.c b/ircd/src/parse.c
@@ -0,0 +1,782 @@
+/************************************************************************
+ *   Unreal Internet Relay Chat Daemon, src/parse.c
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *                      University of Oulu, Computing Center
+ *
+ *   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 Main line parsing functions - for incoming lines from clients.
+ */
+#include "unrealircd.h"
+
+/** Last (or current) command that we processed. Useful for post-mortem. */
+char backupbuf[8192];
+
+static char *para[MAXPARA + 2];
+
+/* Forward declarations of functions that are local (static) */
+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, 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);
+
+/** Put a packet in the client receive queue and process the data (if
+ * the 'fake lag' rules permit doing so).
+ * @param client      The client
+ * @param readbuf     The read buffer
+ * @param length      The length of the data
+ * @param killsafely  If 1 then we may call exit_client() if the client
+ *                    is flooding. If 0 then we use dead_socket().
+ * @returns 1 in normal circumstances, 0 if client was killed.
+ * @note  If killsafely is 1 and the return value is 0 then
+ *        the client was killed - IsDead() is true.
+ *        If this is a problem, then set killsafely to 0 when calling.
+ */
+int process_packet(Client *client, char *readbuf, int length, int killsafely)
+{
+	dbuf_put(&client->local->recvQ, readbuf, length);
+
+	/* parse some of what we have (inducing fakelag, etc) */
+	parse_client_queued(client);
+
+	/* We may be killed now, so check for it.. */
+	if (IsDead(client))
+		return 0;
+
+	/* flood from unknown connection */
+	if (IsUnknown(client) && (DBufLength(&client->local->recvQ) > iConf.handshake_data_flood_amount))
+	{
+		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
+			dead_socket(client, "Handshake data flood detected");
+		return 0;
+	}
+
+	/* excess flood check */
+	if (IsUser(client) && 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
+			dead_socket(client, "Excess Flood");
+		return 0;
+	}
+
+	return 1;
+}
+
+/** Parse any queued data for 'client', if permitted.
+ * @param client	The client.
+ */
+void parse_client_queued(Client *client)
+{
+	int dolen = 0;
+	char buf[READBUFSIZE];
+
+	if (IsDNSLookup(client))
+		return; /* we delay processing of data until the host is resolved */
+
+	if (IsIdentLookup(client))
+		return; /* we delay processing of data until identd has replied */
+
+	if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) &&
+	    !IsNoHandshakeDelay(client) &&
+	    !IsUnixSocket(client) &&
+	    (TStime() - client->local->creationtime < iConf.handshake_delay))
+	{
+		return; /* we delay processing of data until set::handshake-delay is reached */
+	}
+
+	while (DBufLength(&client->local->recvQ) && !client_lagged_up(client))
+	{
+		dolen = dbuf_getmsg(&client->local->recvQ, buf);
+
+		if (dolen == 0)
+			return;
+
+		dopacket(client, buf, dolen);
+		
+		if (IsDead(client))
+			return;
+	}
+}
+
+/*
+** dopacket
+**	client - pointer to client structure for which the buffer data
+**	       applies.
+**	buffer - pointr to the buffer containing the newly read data
+**	length - number of valid bytes of data in the buffer
+**
+** Note:
+**	It is implicitly assumed that dopacket is called only
+**	with client of "local" variation, which contains all the
+**	necessary fields (buffer etc..)
+**
+** Rewritten for linebufs, 19th May 2013. --kaniini
+*/
+void dopacket(Client *client, char *buffer, int length)
+{
+	client->local->traffic.bytes_received += length;
+	me.local->traffic.bytes_received += length;
+
+	client->local->traffic.messages_received++;
+	me.local->traffic.messages_received++;
+
+	parse(client, buffer, length);
+}
+
+
+/** Parse an incoming line.
+ * A line was received previously, buffered via dbuf, now popped from the dbuf stack,
+ * and we should now process it.
+ * @param cptr    The client from which the message was received
+ * @param buffer  The buffer
+ * @param length  The length of the buffer
+ * @note parse() cannot not be called recusively by any other functions!
+ */
+void parse(Client *cptr, char *buffer, int length)
+{
+	Hook *h;
+	Client *from = cptr;
+	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.
+	 * This, while all the rest of the IRCd code assumes a maximum length
+	 * of BUFSIZE, which is 512 (including NUL byte).
+	 */
+	for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
+	{
+		(*(h->func.intfunc))(from, &me, NULL, &buffer, &length);
+		if (!buffer)
+			return;
+	}
+
+	if (IsDeadSocket(cptr))
+		return;
+
+	if ((cptr->local->traffic.bytes_received >= iConf.handshake_data_flood_amount) && IsUnknown(cptr))
+	{
+		unreal_log(ULOG_INFO, "flood", "HANDSHAKE_DATA_FLOOD", cptr,
+		           "Handshake data flood detected from $client.details [$client.ip]");
+		ban_handshake_data_flooder(cptr);
+		return;
+	}
+
+	/* This stores the last executed command in 'backupbuf', useful for debugging crashes */
+	strlcpy(backupbuf, buffer, sizeof(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 */
+	for (i = 0; i < MAXPARA+2; i++)
+		para[i] = (char *)DEADBEEF_ADDR;
+
+	/* First, skip any whitespace */
+	for (ch = buffer; *ch == ' '; ch++)
+		;
+
+	/* 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, mtags_bytes, ch);
+
+	if (IsDead(cptr))
+		RunHook(HOOKTYPE_POST_COMMAND, NULL, mtags, ch);
+	else
+		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 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, int mtags_bytes, char *ch)
+{
+	Client *from = cptr;
+	char *s;
+	int len, i, numeric = 0, paramcount;
+#ifdef DEBUGMODE
+	time_t then, ticks;
+	int retval;
+#endif
+	RealCommand *cmptr = NULL;
+	int bytes;
+
+	*fromptr = cptr; /* The default, unless a source is specified (and permitted) */
+
+	/* The remaining part should never be more than 510 bytes
+	 * (that is 512 minus CR LF, as specified in RFC1459 section 2.3).
+	 * If it is too long, then we cut it off here.
+	 */
+	if (strlen(ch) > 510)
+	{
+		ch[510] = '\0';
+	}
+
+	para[0] = (char *)DEADBEEF_ADDR; /* helps us catch bugs :) */
+
+	if (*ch == ':' || *ch == '@')
+	{
+		char sender[HOSTLEN + 1];
+		s = sender;
+		*s = '\0';
+
+		/* Deal with :sender ... */
+		for (++ch, i = 0; *ch && *ch != ' '; ++ch)
+		{
+			if (s < sender + sizeof(sender) - 1)
+				*s++ = *ch;
+		}
+		*s = '\0';
+
+		/* For servers we lookup the sender and change 'from' accordingly.
+		 * For other clients we ignore the sender.
+		 */
+		if (*sender && IsServer(cptr))
+		{
+			from = find_client(sender, NULL);
+
+			if (!from && strchr(sender, '@'))
+				from = hash_find_nickatserver(sender, NULL);
+
+			/* Sender not found. Possibly a ghost, so kill it.
+			 * This can happen in normal circumstances. For example
+			 * in case of A-B-C where we are B. If a KILL came from C
+			 * for a client on A and we processed it at B, then until
+			 * A has processed it we may still receive messages from A
+			 * about it's soon-to-be-killed-client (all due to lag).
+			 */
+			if (!from)
+			{
+				ircstats.is_unpf++;
+				remove_unknown(cptr, sender);
+				return;
+			}
+			/* This is more severe. The server gave a source of a client
+			 * that cannot exist from that direction.
+			 * Eg in case of a topology of A-B-C-D and we are B,
+			 * we got a message from A with ":D MODE...".
+			 * In that case we send a SQUIT to that direction telling to
+			 * unlink D from that side. This will likely lead to a
+			 * problematic situation, though.
+			 * This is, by the way, also why we try to prevent this situation
+			 * in the first place by using PROTOCTL SERVERS=...
+			 * in which case we reject such a flawed link very early
+			 * in the server handshake process. -- Syzop
+			 */
+			if (from->direction != cptr)
+			{
+				ircstats.is_wrdi++;
+				cancel_clients(cptr, from, ch);
+				return;
+			}
+			*fromptr = from; /* Update source client */
+		}
+		while (*ch == ' ')
+			ch++;
+	}
+
+	RunHook(HOOKTYPE_PRE_COMMAND, from, mtags, ch);
+
+	if (*ch == '\0')
+	{
+		if (!IsServer(cptr))
+			cptr->local->fake_lag++; /* 1s fake lag */
+		return;
+	}
+
+	/* Recalculate string length, now that we have skipped the sender */
+	bytes = strlen(ch);
+
+	/* Now let's figure out the command (or numeric)... */
+	s = strchr(ch, ' ');	/* s -> End of the command code */
+	len = (s) ? (s - ch) : 0;
+
+	if (len == 3 && isdigit(*ch) && isdigit(*(ch + 1)) && isdigit(*(ch + 2)))
+	{
+		/* Numeric (eg: 311) */
+		cmptr = NULL;
+		numeric = (*ch - '0') * 100 + (*(ch + 1) - '0') * 10 + (*(ch + 2) - '0');
+		paramcount = MAXPARA;
+		ircstats.is_num++;
+		parse_addlag(cptr, bytes, mtags_bytes);
+	}
+	else
+	{
+		/* Command (eg: PRIVMSG) */
+		int flags = 0;
+		if (s)
+			*s++ = '\0';
+
+		/* Set the appropriate flags for the command lookup */
+		if (!IsRegistered(from))
+			flags |= CMD_UNREGISTERED;
+		if (IsUser(from))
+			flags |= CMD_USER;
+		if (IsServer(from))
+			flags |= CMD_SERVER;
+		if (IsShunned(from))
+			flags |= CMD_SHUN;
+		if (IsVirus(from))
+			flags |= CMD_VIRUS;
+		if (IsOper(from))
+			flags |= CMD_OPER;
+		if (IsControl(from))
+			flags |= CMD_CONTROL;
+		cmptr = find_command(ch, flags);
+		if (!cmptr || !(cmptr->flags & CMD_NOLAG))
+		{
+			/* Add fake lag (doing this early in the code, so we don't forget) */
+			parse_addlag(cptr, bytes, mtags_bytes);
+		}
+		if (!cmptr)
+		{
+			if (IsControl(from))
+			{
+				sendto_one(from, NULL, "ERROR UNKNOWN_COMMAND: %s", ch);
+				sendto_one(from, NULL, "END 1");
+				return;
+			}
+			/* Don't send error messages in response to NOTICEs
+			 * in pre-connection state.
+			 */
+			if (!IsRegistered(cptr) && strcasecmp(ch, "NOTICE"))
+			{
+				sendnumericfmt(from, ERR_NOTREGISTERED, ":You have not registered");
+				return;
+			}
+			/* If the user is shunned then don't send anything back in case
+			 * of an unknown command, since we want to save data.
+			 */
+			if (IsShunned(cptr))
+				return;
+				
+			if (ch[0] != '\0')
+			{
+				if (IsUser(from))
+				{
+					sendto_one(from, NULL, ":%s %d %s %s :Unknown command",
+					                       me.name, ERR_UNKNOWNCOMMAND,
+					                       from->name, ch);
+				}
+			}
+			ircstats.is_unco++;
+			return;
+		}
+		if (cmptr->flags != 0) { /* temporary until all commands are updated */
+
+		/* Logic in comparisons below is a bit complicated, see notes */
+
+		/* If you're a user, and this command does not permit users or opers, deny */
+		if ((flags & CMD_USER) && !(cmptr->flags & CMD_USER) && !(cmptr->flags & CMD_OPER))
+		{
+			if (cmptr->flags & CMD_UNREGISTERED)
+				sendnumeric(cptr, ERR_ALREADYREGISTRED); /* only for unregistered phase */
+			else
+				sendnumeric(cptr, ERR_NOTFORUSERS, cmptr->cmd); /* really never for users */
+			return;
+		}
+
+		/* If you're a server, but command doesn't want servers, deny */
+		if ((flags & CMD_SERVER) && !(cmptr->flags & CMD_SERVER))
+			return;
+		}
+
+		/* If you're a user, but not an operator, and this requires operators, deny */
+		if ((cmptr->flags & CMD_OPER) && (flags & CMD_USER) && !(flags & CMD_OPER))
+		{
+			sendnumeric(cptr, ERR_NOPRIVILEGES);
+			return;
+		}
+		paramcount = cmptr->parameters;
+		cmptr->bytes += bytes;
+	}
+	/*
+	   ** Must the following loop really be so devious? On
+	   ** surface it splits the message to parameters from
+	   ** blank spaces. But, if paramcount has been reached,
+	   ** the rest of the message goes into this last parameter
+	   ** (about same effect as ":" has...) --msa
+	 */
+
+	/* Note initially true: s==NULL || *(s-1) == '\0' !! */
+
+	i = 0;
+	if (s)
+	{
+		/*
+		if (paramcount > MAXPARA)
+			paramcount = MAXPARA;
+		We now use functions to create commands, so we can just check this 
+		once when the command is created rather than each time the command
+		is used -- codemastr
+		*/
+		for (;;)
+		{
+			/*
+			   ** Never "FRANCE " again!! ;-) Clean
+			   ** out *all* blanks.. --msa
+			 */
+			while (*s == ' ')
+				*s++ = '\0';
+
+			if (*s == '\0')
+				break;
+			if (*s == ':')
+			{
+				/*
+				   ** The rest is single parameter--can
+				   ** include blanks also.
+				 */
+				para[++i] = s + 1;
+				break;
+			}
+			para[++i] = s;
+			if (i >= paramcount)
+				break;
+			for (; *s != ' ' && *s; s++)
+				;
+		}
+	}
+	para[++i] = NULL;
+
+	/* Check if one of the message tags are rejected by spamfilter */
+	if (MyConnect(from) && !IsServer(from) && match_spamfilter_mtags(from, mtags, cmptr ? cmptr->cmd : NULL))
+		return;
+
+	if (cmptr == NULL)
+	{
+		do_numeric(numeric, from, mtags, i, (const char **)para);
+		return;
+	}
+	cmptr->count++;
+	if (IsUser(cptr) && (cmptr->flags & CMD_RESETIDLE))
+		cptr->local->idle_since = TStime();
+
+	/* Now ready to execute the command */
+#ifndef DEBUGMODE
+	if (cmptr->flags & CMD_ALIAS)
+	{
+		(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
+	} else {
+		if (!cmptr->overriders)
+			(*cmptr->func) (from, mtags, i, (const char **)para);
+		else
+			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
+	}
+#else
+	then = clock();
+	if (cmptr->flags & CMD_ALIAS)
+	{
+		(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
+	} else {
+		if (!cmptr->overriders)
+			(*cmptr->func) (from, mtags, i, (const char **)para);
+		else
+			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
+	}
+	if (!IsDead(cptr))
+	{
+		ticks = (clock() - then);
+		if (IsServer(cptr))
+			cmptr->rticks += ticks;
+		else
+			cmptr->lticks += ticks;
+	}
+#endif
+}
+
+/** Ban user that is "flooding from an unknown connection".
+ * This is basically a client sending lots of data but not registering.
+ * Note that "lots" in terms of IRC is a few KB's, since more is rather unusual.
+ * @param client The client.
+ */
+static void ban_handshake_data_flooder(Client *client)
+{
+	if (find_tkl_exception(TKL_HANDSHAKE_DATA_FLOOD, client))
+	{
+		/* If the user is exempt we will still KILL the client, since it is
+		 * clearly misbehaving. We just won't ZLINE the host, so it won't
+		 * affect any other connections from the same IP address.
+		 */
+		exit_client(client, NULL, "Handshake data flood detected");
+	}
+	else
+	{
+		/* place_host_ban also takes care of removing any other clients with same host/ip */
+		place_host_ban(client, iConf.handshake_data_flood_ban_action, "Handshake data flood detected", iConf.handshake_data_flood_ban_time);
+	}
+}
+
+/** Add "fake lag" if needed.
+ * The main purpose of fake lag is to create artificial lag when
+ * processing incoming data from the client. So, if a client sends
+ * a lot of commands, then next command will be processed at a rate
+ * of 1 per second, or even slower. The exact algorithm is defined in this function.
+ *
+ * Servers are exempt from fake lag, so are IRCOps and clients tagged as
+ * 'no fake lag' by services (rarely used). Finally, there is also an
+ * option called class::options::nofakelag which exempts fakelag.
+ * Exemptions should be granted with extreme care, since a client will
+ * 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 command_bytes	Command length in bytes (excluding message tagss)
+ * @param mtags_bytes	Length of message tags in bytes
+ */
+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))
+	{
+		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
+ * @returns 1 if client is lagged up and data should not be parsed, 0 otherwise.
+ */
+static int client_lagged_up(Client *client)
+{
+	if (client->status < CLIENT_STATUS_UNKNOWN)
+		return 0;
+	if (IsServer(client))
+		return 0;
+	if (ValidatePermissionsForPath("immune:lag",client,NULL,NULL,NULL))
+		return 0;
+	if (client->local->fake_lag - TStime() < 10)
+		return 0;
+	return 1;
+}
+
+
+/** Numeric received from a connection.
+ * @param numeric     The numeric code (range 000-999)
+ * @param cptr        The client
+ * @param recv_mtags  Received message tags
+ * @param parc        Parameter count
+ * @param parv        Parameters
+ * @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, 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->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.
+		 */
+
+		/* STARTTLS: unknown command */
+		if ((numeric == 451) && (parc > 2) && strstr(parv[1], "STARTTLS"))
+		{
+			if (client->server->conf && (client->server->conf->outgoing.options & CONNECT_INSECURE))
+				start_server_handshake(client);
+			else
+				reject_insecure_server(client);
+			return 0;
+		}
+
+		/* STARTTLS failed */
+		if (numeric == 691)
+		{
+			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;
+		}
+
+		/* STARTTLS OK */
+		if (numeric == 670)
+		{
+			int ret = client_starttls(client);
+			if (ret < 0)
+			{
+				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;
+			}
+			/* We don't call start_server_handshake() here. First the TLS handshake will
+			 * be completed, then completed_connection() will be called for a second time,
+			 * which will call completed_connection() from there.
+			 */
+			return 0;
+		}
+	}
+
+	/* Other than the (strange) code from above, we actually
+	 * don't process numerics from non-servers. So return here.
+	 */
+	if ((parc < 2) || BadPtr(parv[1]) || !IsServer(client))
+		return 0;
+
+	/* Remap low number numerics. */
+	if (numeric < 100)
+		numeric += 100;
+
+	/* Convert parv[] back to a string 'buffer', since that is
+	 * what we use in the sendto_* functions below.
+	 */
+	concat_params(buffer, sizeof(buffer), parc, parv);
+
+	/* Now actually process the numeric, IOTW: send it on */
+	strlcpy(targets, parv[1], sizeof(targets));
+	for (nick = strtoken(&p, targets, ","); nick; nick = strtoken(&p, NULL, ","))
+	{
+		if ((acptr = find_client(nick, NULL)))
+		{
+			if (!IsMe(acptr) && IsUser(acptr))
+			{
+				if (MyConnect(acptr) && isdigit(*nick))
+				{
+					/* Hack to prevent leaking UID */
+					char *skip = strchr(buffer, ' ');
+					if (skip)
+					{
+						sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s %s",
+						    client->name, numeric, acptr->name, skip+1);
+					} /* else.. malformed (no content) */
+				} else {
+					sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
+					    client->name, numeric, buffer);
+				}
+			}
+			else if (IsServer(acptr) && acptr->direction != client->direction)
+				sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
+				    client->name, numeric, buffer);
+		}
+		else if ((acptr = find_server_quick(nick)))
+		{
+			if (!IsMe(acptr) && acptr->direction != client->direction)
+				sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
+				    client->name, numeric, buffer);
+		}
+		else if ((channel = find_channel(nick)))
+		{
+			sendto_channel(channel, client, client->direction,
+			               0, 0, SEND_ALL, recv_mtags,
+			               ":%s %d %s", client->name, numeric, buffer);
+		}
+	}
+
+	return 0;
+}
+
+// FIXME: aren't we exiting the wrong client?
+static void cancel_clients(Client *cptr, Client *client, char *cmd)
+{
+	if (IsServer(cptr) || IsServer(client) || IsMe(client))
+		return;
+	exit_client(cptr, NULL, "Fake prefix");
+}
+
+static void remove_unknown(Client *client, char *sender)
+{
+	if (!IsRegistered(client) || IsUser(client))
+		return;
+	/*
+	 * Not from a server so don't need to worry about it.
+	 */
+	if (!IsServer(client))
+		return;
+
+	/*
+	 * 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
+	 */
+	if ((isdigit(*sender) && strlen(sender) <= SIDLEN) || strchr(sender, '.'))
+		sendto_one(client, NULL, ":%s SQUIT %s :Unknown prefix (%s) from %s",
+		    me.id, sender, sender, client->name);
+	else
+		sendto_one(client, NULL, ":%s KILL %s :Ghost user", me.id, sender);
+}
diff --git a/ircd/src/proc_io_client.c b/ircd/src/proc_io_client.c
@@ -0,0 +1,199 @@
+/************************************************************************
+ *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_client.c
+ *   (c) 2022- Bram Matthys and The UnrealIRCd team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers. 
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief Inter-process I/O
+ */
+#include "unrealircd.h"
+
+int procio_client_connect(const char *file)
+{
+	int fd;
+	struct sockaddr_un addr;
+
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (fd < 0)
+	{
+#ifdef _WIN32
+		fprintf(stderr, "Your Windows version does not support UNIX sockets, "
+		                "so cannot communicate to UnrealIRCd.\n"
+		                "Windows 10 version 1803 (April 2018) or later is needed.\n");
+#else
+		fprintf(stderr, "Cannot communicate to UnrealIRCd: %s\n"
+		                "Perhaps your operating system does not support UNIX Sockets?\n",
+				strerror(ERRNO));
+#endif
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	strlcpy(addr.sun_path, file, sizeof(addr.sun_path));
+
+	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+	{
+		fprintf(stderr, "Could not connect to '%s': %s\n",
+			CONTROLFILE, strerror(errno));
+		fprintf(stderr, "The IRC server does not appear to be running.\n");
+		close(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+int procio_send(int fd, const char *command)
+{
+	char buf[512];
+	int n;
+	snprintf(buf, sizeof(buf), "%s\r\n", command);
+	n = strlen(buf);
+	if (send(fd, buf, n, 0) != n)
+		return 0;
+	return 1;
+}
+
+const char *recolor_logs(const char *str)
+{
+	static char retbuf[2048];
+	char buf[2048], *p;
+	const char *color = NULL;
+
+	strlcpy(buf, str, sizeof(buf));
+	p = strchr(buf, ' ');
+	if ((*str != '[') || !p)
+		return str;
+	*p++ = '\0';
+
+	if (!strcmp(buf, "[debug]"))
+		color = log_level_terminal_color(ULOG_DEBUG);
+	else if (!strcmp(buf, "[info]"))
+		color = log_level_terminal_color(ULOG_INFO);
+	else if (!strcmp(buf, "[warning]"))
+		color = log_level_terminal_color(ULOG_WARNING);
+	else if (!strcmp(buf, "[error]"))
+		color = log_level_terminal_color(ULOG_ERROR);
+	else if (!strcmp(buf, "[fatal]"))
+		color = log_level_terminal_color(ULOG_FATAL);
+	else
+		color = log_level_terminal_color(ULOG_INVALID);
+
+	snprintf(retbuf, sizeof(retbuf), "%s%s%s %s",
+	         color, buf, TERMINAL_COLOR_RESET, p);
+	return retbuf;
+}
+
+const char *recolor_split(const char *str)
+{
+	static char retbuf[2048];
+	char buf[2048], *p;
+	const char *color = NULL;
+
+	strlcpy(buf, str, sizeof(buf));
+	p = strchr(buf, ' ');
+	if (!p)
+		return str;
+	*p++ = '\0';
+
+	snprintf(retbuf, sizeof(retbuf), "%s%s %s%s%s",
+	         "\033[92m", buf,
+	         "\033[93m", p,
+	         TERMINAL_COLOR_RESET);
+	return retbuf;
+}
+
+int procio_client(const char *command, int auto_color_logs)
+{
+	int fd;
+	char buf[READBUFSIZE];
+	int n;
+	dbuf queue;
+
+	if (auto_color_logs && !terminal_supports_color())
+		auto_color_logs = 0;
+
+	fd = procio_client_connect(CONTROLFILE);
+	if (fd < 0)
+		return -1;
+
+	/* Expect the welcome message */
+	memset(buf, 0, sizeof(buf));
+	n = recv(fd, buf, sizeof(buf), 0);
+	if ((n < 0) || strncmp(buf, "READY", 4))
+	{
+		fprintf(stderr, "Error while communicating to IRCd via '%s': %s\n"
+		                "Maybe the IRC server is not running?\n",
+		                CONTROLFILE, strerror(errno));
+		close(fd);
+		return -1;
+	}
+
+	if (!procio_send(fd, command))
+	{
+		fprintf(stderr, "Error while sending command to IRCd via '%s'. Strange!\n",
+		                CONTROLFILE);
+		close(fd);
+		return -1;
+	}
+
+	*buf = '\0';
+	dbuf_queue_init(&queue);
+	while(1)
+	{
+		n = recv(fd, buf, sizeof(buf)-1, 0);
+		if (n <= 0)
+			break;
+		buf[n] = '\0'; /* terminate the string */
+		dbuf_put(&queue, buf, n);
+
+		/* And try to read all complete lines: */
+		do
+		{
+			n = dbuf_getmsg(&queue, buf);
+			if (n > 0)
+			{
+				if (!strncmp(buf, "REPLY ", 6))
+				{
+					char *reply = buf+6;
+					if (auto_color_logs == 0)
+						printf("%s\n", reply);
+					else if (auto_color_logs == 1)
+						printf("%s\n", recolor_logs(reply));
+					else
+						printf("%s\n", recolor_split(reply));
+				} else
+				if (!strncmp(buf, "END ", 4))
+				{
+					int exitcode = atoi(buf+4);
+					close(fd);
+					return exitcode;
+				}
+			}
+		} while(n > 0);
+	}
+
+	/* IRCd hung up without saying goodbye, possibly problematic,
+	 * or at least we cannot determine, so exit with status 66.
+	 */
+	close(fd);
+	return 66;
+}
diff --git a/ircd/src/proc_io_server.c b/ircd/src/proc_io_server.c
@@ -0,0 +1,191 @@
+/************************************************************************
+ *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_server.c
+ *   (c) 2022- Bram Matthys and The UnrealIRCd team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers. 
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief Inter-process I/O
+ */
+#include "unrealircd.h"
+#include <ares.h>
+
+/* Forward declarations */
+CMD_FUNC(procio_status);
+CMD_FUNC(procio_modules);
+CMD_FUNC(procio_rehash);
+CMD_FUNC(procio_exit);
+CMD_FUNC(procio_help);
+void start_of_control_client_handshake(Client *client);
+int procio_accept(Client *client);
+
+/** Create the unrealircd.ctl socket (server-side) */
+void add_proc_io_server(void)
+{
+	ConfigItem_listen *listener;
+
+#ifdef _WIN32
+	/* Ignore silently on Windows versions older than W10 build 17061 */
+	if (!unix_sockets_capable())
+		return;
+#endif
+	listener = safe_alloc(sizeof(ConfigItem_listen));
+	safe_strdup(listener->file, CONTROLFILE);
+	listener->socket_type = SOCKET_TYPE_UNIX;
+	listener->options = LISTENER_CONTROL|LISTENER_NO_CHECK_CONNECT_FLOOD|LISTENER_NO_CHECK_ZLINED;
+	listener->start_handshake = start_of_control_client_handshake;
+	listener->fd = -1;
+	AddListItem(listener, conf_listen);
+	if (add_listener(listener) == -1)
+		exit(-1);
+	CommandAdd(NULL, "STATUS", procio_status, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "MODULES", procio_modules, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "REHASH", procio_rehash, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "EXIT", procio_exit, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "HELP", procio_help, MAXPARA, CMD_CONTROL);
+	HookAdd(NULL, HOOKTYPE_ACCEPT, -1000000, procio_accept);
+}
+
+int procio_accept(Client *client)
+{
+	if (client->local->listener->options & LISTENER_CONTROL)
+	{
+		irccounts.unknown--;
+		client->status = CLIENT_STATUS_CONTROL;
+		list_del(&client->lclient_node);
+		list_add(&client->lclient_node, &control_list);
+	}
+	return 0;
+}
+
+/** Start of "control channel" client handshake - this is minimal
+ * @param client	The client
+ */
+void start_of_control_client_handshake(Client *client)
+{
+	sendto_one(client, NULL, "READY %s %s", me.name, version);
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+CMD_FUNC(procio_status)
+{
+	sendto_one(client, NULL, "REPLY servername %s", me.name);
+	sendto_one(client, NULL, "REPLY unrealircd_version %s", version);
+	sendto_one(client, NULL, "REPLY libssl_version %s", SSLeay_version(SSLEAY_VERSION));
+	sendto_one(client, NULL, "REPLY libsodium_version %s", sodium_version_string());
+#ifdef USE_LIBCURL
+	sendto_one(client, NULL, "REPLY libcurl_version %s", curl_version());
+#endif
+	sendto_one(client, NULL, "REPLY libcares_version %s", ares_version(NULL));
+	sendto_one(client, NULL, "REPLY libpcre2_version %s", pcre2_version());
+#if JANSSON_VERSION_HEX >= 0x020D00
+	sendto_one(client, NULL, "REPLY libjansson %s\n", jansson_version_str());
+#endif
+	sendto_one(client, NULL, "REPLY global_clients %ld", (long)irccounts.clients);
+	sendto_one(client, NULL, "REPLY local_clients %ld", (long)irccounts.me_clients);
+	sendto_one(client, NULL, "REPLY operators %ld", (long)irccounts.operators);
+	sendto_one(client, NULL, "REPLY servers %ld", (long)irccounts.servers);
+	sendto_one(client, NULL, "REPLY channels %ld", (long)irccounts.channels);
+	sendto_one(client, NULL, "END 0");
+}
+
+extern MODVAR Module *Modules;
+CMD_FUNC(procio_modules)
+{
+	char tmp[1024];
+	Module *m;
+
+	for (m = Modules; m; m = m->next)
+	{
+		tmp[0] = '\0';
+		if (m->flags & MODFLAG_DELAYED)
+			strlcat(tmp, "[Unloading] ", sizeof(tmp));
+		if (m->options & MOD_OPT_PERM_RELOADABLE)
+			strlcat(tmp, "[PERM-BUT-RELOADABLE] ", sizeof(tmp));
+		if (m->options & MOD_OPT_PERM)
+			strlcat(tmp, "[PERM] ", sizeof(tmp));
+		if (!(m->options & MOD_OPT_OFFICIAL))
+			strlcat(tmp, "[3RD] ", sizeof(tmp));
+		sendto_one(client, NULL, "REPLY %s %s - %s - by %s %s",
+		           m->header->name,
+		           m->header->version,
+		           m->header->description,
+		           m->header->author,
+		           tmp);
+	}
+	sendto_one(client, NULL, "END 0");
+}
+
+CMD_FUNC(procio_rehash)
+{
+	if (loop.rehashing)
+	{
+		sendto_one(client, NULL, "REPLY ERROR: A rehash is already in progress");
+		sendto_one(client, NULL, "END 1");
+		return;
+	}
+	
+
+	if (parv[1] && !strcmp(parv[1], "-tls"))
+	{
+		int ret;
+		SetMonitorRehash(client);
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", NULL, "Reloading all TLS related data (./unrealircd reloadtls)");
+		ret = reinit_tls();
+		sendto_one(client, NULL, "END %d", ret == 0 ? -1 : 0);
+		ClearMonitorRehash(client);
+	} else {
+		SetMonitorRehash(client);
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]");
+		request_rehash(client);
+		/* completion will go via procio_post_rehash() */
+	}
+}
+
+CMD_FUNC(procio_exit)
+{
+	sendto_one(client, NULL, "END 0");
+	exit_client(client, NULL, "");
+}
+
+CMD_FUNC(procio_help)
+{
+	sendto_one(client, NULL, "REPLY Commands available:");
+	sendto_one(client, NULL, "REPLY EXIT");
+	sendto_one(client, NULL, "REPLY HELP");
+	sendto_one(client, NULL, "REPLY REHASH");
+	sendto_one(client, NULL, "REPLY STATUS");
+	sendto_one(client, NULL, "REPLY MODULES");
+	sendto_one(client, NULL, "END 0");
+}
+
+/** Called upon REHASH completion (with or without failure) */
+void procio_post_rehash(int failure)
+{
+	Client *client;
+
+	list_for_each_entry(client, &control_list, lclient_node)
+	{
+		if (IsMonitorRehash(client))
+		{
+			sendto_one(client, NULL, "END %d", failure);
+			ClearMonitorRehash(client);
+		}
+	}
+}
diff --git a/ircd/src/random.c b/ircd/src/random.c
@@ -0,0 +1,520 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, random.c
+ *   (C) 2004-2019 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.
+ *
+ */
+
+#include "unrealircd.h"
+
+#ifndef HAVE_ARC4RANDOM
+
+#define KEYSTREAM_ONLY
+
+/* chacha_private.h from openssh/openssh-portable
+ * "chacha-merged.c version 20080118
+ *  D. J. Bernstein
+ *  Public domain."
+ */
+
+/* $OpenBSD: chacha_private.h,v 1.2 2013/10/04 07:02:27 djm Exp $ */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+  u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+  (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+  (((u32)((p)[0])      ) | \
+   ((u32)((p)[1]) <<  8) | \
+   ((u32)((p)[2]) << 16) | \
+   ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+  do { \
+    (p)[0] = U8V((v)      ); \
+    (p)[1] = U8V((v) >>  8); \
+    (p)[2] = U8V((v) >> 16); \
+    (p)[3] = U8V((v) >> 24); \
+  } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits)
+{
+  const char *constants;
+
+  x->input[4] = U8TO32_LITTLE(k + 0);
+  x->input[5] = U8TO32_LITTLE(k + 4);
+  x->input[6] = U8TO32_LITTLE(k + 8);
+  x->input[7] = U8TO32_LITTLE(k + 12);
+  if (kbits == 256) { /* recommended */
+    k += 16;
+    constants = sigma;
+  } else { /* kbits == 128 */
+    constants = tau;
+  }
+  x->input[8] = U8TO32_LITTLE(k + 0);
+  x->input[9] = U8TO32_LITTLE(k + 4);
+  x->input[10] = U8TO32_LITTLE(k + 8);
+  x->input[11] = U8TO32_LITTLE(k + 12);
+  x->input[0] = U8TO32_LITTLE(constants + 0);
+  x->input[1] = U8TO32_LITTLE(constants + 4);
+  x->input[2] = U8TO32_LITTLE(constants + 8);
+  x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void
+chacha_ivsetup(chacha_ctx *x,const u8 *iv)
+{
+  x->input[12] = 0;
+  x->input[13] = 0;
+  x->input[14] = U8TO32_LITTLE(iv + 0);
+  x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+  u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+  u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+  u8 *ctarget = NULL;
+  u8 tmp[64];
+  u_int i;
+
+  if (!bytes) return;
+
+  j0 = x->input[0];
+  j1 = x->input[1];
+  j2 = x->input[2];
+  j3 = x->input[3];
+  j4 = x->input[4];
+  j5 = x->input[5];
+  j6 = x->input[6];
+  j7 = x->input[7];
+  j8 = x->input[8];
+  j9 = x->input[9];
+  j10 = x->input[10];
+  j11 = x->input[11];
+  j12 = x->input[12];
+  j13 = x->input[13];
+  j14 = x->input[14];
+  j15 = x->input[15];
+
+  for (;;) {
+    if (bytes < 64) {
+      for (i = 0;i < bytes;++i) tmp[i] = m[i];
+      m = tmp;
+      ctarget = c;
+      c = tmp;
+    }
+    x0 = j0;
+    x1 = j1;
+    x2 = j2;
+    x3 = j3;
+    x4 = j4;
+    x5 = j5;
+    x6 = j6;
+    x7 = j7;
+    x8 = j8;
+    x9 = j9;
+    x10 = j10;
+    x11 = j11;
+    x12 = j12;
+    x13 = j13;
+    x14 = j14;
+    x15 = j15;
+    for (i = 20;i > 0;i -= 2) {
+      QUARTERROUND( x0, x4, x8,x12)
+      QUARTERROUND( x1, x5, x9,x13)
+      QUARTERROUND( x2, x6,x10,x14)
+      QUARTERROUND( x3, x7,x11,x15)
+      QUARTERROUND( x0, x5,x10,x15)
+      QUARTERROUND( x1, x6,x11,x12)
+      QUARTERROUND( x2, x7, x8,x13)
+      QUARTERROUND( x3, x4, x9,x14)
+    }
+    x0 = PLUS(x0,j0);
+    x1 = PLUS(x1,j1);
+    x2 = PLUS(x2,j2);
+    x3 = PLUS(x3,j3);
+    x4 = PLUS(x4,j4);
+    x5 = PLUS(x5,j5);
+    x6 = PLUS(x6,j6);
+    x7 = PLUS(x7,j7);
+    x8 = PLUS(x8,j8);
+    x9 = PLUS(x9,j9);
+    x10 = PLUS(x10,j10);
+    x11 = PLUS(x11,j11);
+    x12 = PLUS(x12,j12);
+    x13 = PLUS(x13,j13);
+    x14 = PLUS(x14,j14);
+    x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+    x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+    x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+    x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+    x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+    x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+    x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+    x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+    x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+    x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+    x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+    x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+    x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+    x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+    x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+    x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+    x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+    j12 = PLUSONE(j12);
+    if (!j12) {
+      j13 = PLUSONE(j13);
+      /* stopping at 2^70 bytes per nonce is user's responsibility */
+    }
+
+    U32TO8_LITTLE(c + 0,x0);
+    U32TO8_LITTLE(c + 4,x1);
+    U32TO8_LITTLE(c + 8,x2);
+    U32TO8_LITTLE(c + 12,x3);
+    U32TO8_LITTLE(c + 16,x4);
+    U32TO8_LITTLE(c + 20,x5);
+    U32TO8_LITTLE(c + 24,x6);
+    U32TO8_LITTLE(c + 28,x7);
+    U32TO8_LITTLE(c + 32,x8);
+    U32TO8_LITTLE(c + 36,x9);
+    U32TO8_LITTLE(c + 40,x10);
+    U32TO8_LITTLE(c + 44,x11);
+    U32TO8_LITTLE(c + 48,x12);
+    U32TO8_LITTLE(c + 52,x13);
+    U32TO8_LITTLE(c + 56,x14);
+    U32TO8_LITTLE(c + 60,x15);
+
+    if (bytes <= 64) {
+      if (bytes < 64) {
+        for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+      }
+      x->input[12] = j12;
+      x->input[13] = j13;
+      return;
+    }
+    bytes -= 64;
+    c += 64;
+#ifndef KEYSTREAM_ONLY
+    m += 64;
+#endif
+  }
+}
+
+/* The following is taken from arc4random.c
+ * from openssh/openssh-portable/, which has an ISC license,
+ * which is GPL compatible.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/crypto/arc4random.c */
+/* $OpenBSD: arc4random.c,v 1.25 2013/10/01 18:34:57 markus Exp $	*/
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Modified for UnrealIRCd by Bram Matthys ("Syzop") in 2019.
+ * Things like taking out #if (n)def's for openssl (which we always
+ * compile with), re-indenting, removing various stuff, etc.
+ */
+
+
+
+#define KEYSZ	32
+#define IVSZ	8
+#define BLOCKSZ	64
+#define RSBUFSZ	(16*BLOCKSZ)
+static int rs_initialized;
+static chacha_ctx rs;		/* chacha context for random keystream */
+static u_char rs_buf[RSBUFSZ];	/* keystream blocks */
+static size_t rs_have;		/* valid bytes at end of rs_buf */
+static size_t rs_count;		/* bytes till reseed */
+
+static inline void _rs_rekey(u_char *dat, size_t datlen);
+
+static inline void _rs_init(u_char *buf, size_t n)
+{
+	if (n < KEYSZ + IVSZ)
+		return;
+	chacha_keysetup(&rs, buf, KEYSZ * 8, 0);
+	chacha_ivsetup(&rs, buf + KEYSZ);
+}
+
+static void _rs_stir(void)
+{
+	u_char rnd[KEYSZ + IVSZ];
+
+	if (RAND_bytes(rnd, sizeof(rnd)) <= 0)
+	{
+		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();
+	}
+
+	if (!rs_initialized) {
+		rs_initialized = 1;
+		_rs_init(rnd, sizeof(rnd));
+	} else
+		_rs_rekey(rnd, sizeof(rnd));
+#ifdef HAVE_EXPLICIT_BZERO
+	explicit_bzero(rnd, sizeof(rnd));
+#else
+	/* Not terribly important in this case */
+	memset(rnd, 0, sizeof(rnd));
+#endif
+	/* invalidate rs_buf */
+	rs_have = 0;
+	memset(rs_buf, 0, RSBUFSZ);
+
+	rs_count = 1600000;
+}
+
+static inline void _rs_stir_if_needed(size_t len)
+{
+	if (rs_count <= len || !rs_initialized) {
+		_rs_stir();
+	} else
+		rs_count -= len;
+}
+
+static inline void _rs_rekey(u_char *dat, size_t datlen)
+{
+#ifndef KEYSTREAM_ONLY
+	memset(rs_buf, 0,RSBUFSZ);
+#endif
+	/* fill rs_buf with the keystream */
+	chacha_encrypt_bytes(&rs, rs_buf, rs_buf, RSBUFSZ);
+	/* mix in optional user provided data */
+	if (dat) {
+		size_t i, m;
+
+		m = MIN(datlen, KEYSZ + IVSZ);
+		for (i = 0; i < m; i++)
+			rs_buf[i] ^= dat[i];
+	}
+	/* immediately reinit for backtracking resistance */
+	_rs_init(rs_buf, KEYSZ + IVSZ);
+	memset(rs_buf, 0, KEYSZ + IVSZ);
+	rs_have = RSBUFSZ - KEYSZ - IVSZ;
+}
+
+static inline void _rs_random_buf(void *_buf, size_t n)
+{
+	u_char *buf = (u_char *)_buf;
+	size_t m;
+
+	_rs_stir_if_needed(n);
+	while (n > 0) {
+		if (rs_have > 0) {
+			m = MIN(n, rs_have);
+			memcpy(buf, rs_buf + RSBUFSZ - rs_have, m);
+			memset(rs_buf + RSBUFSZ - rs_have, 0, m);
+			buf += m;
+			n -= m;
+			rs_have -= m;
+		}
+		if (rs_have == 0)
+			_rs_rekey(NULL, 0);
+	}
+}
+
+static inline void _rs_random_u32(uint32_t *val)
+{
+	_rs_stir_if_needed(sizeof(*val));
+	if (rs_have < sizeof(*val))
+		_rs_rekey(NULL, 0);
+	memcpy(val, rs_buf + RSBUFSZ - rs_have, sizeof(*val));
+	memset(rs_buf + RSBUFSZ - rs_have, 0, sizeof(*val));
+	rs_have -= sizeof(*val);
+	return;
+}
+
+void arc4random_stir(void)
+{
+	_rs_stir();
+}
+
+void arc4random_doaddrandom(u_char *dat, int datlen)
+{
+	int m;
+
+	if (!rs_initialized)
+		_rs_stir();
+	while (datlen > 0) {
+		m = MIN(datlen, KEYSZ + IVSZ);
+		_rs_rekey(dat, m);
+		dat += m;
+		datlen -= m;
+	}
+}
+
+#endif /* !HAVE_ARC4RANDOM */
+
+/* UnrealIRCd-specific functions follow */
+
+static void arc4_addrandom(void *dat, int datlen)
+{
+	arc4random_doaddrandom((unsigned char *)dat, datlen);
+	return;
+}
+
+void add_entropy_configfile(struct stat *st, char *buf)
+{
+	char sha256buf[SHA256_DIGEST_LENGTH];
+
+	arc4_addrandom(&st->st_size, sizeof(st->st_size));
+	arc4_addrandom(&st->st_mtime, sizeof(st->st_mtime));
+	sha256hash_binary(sha256buf, buf, strlen(buf));
+	arc4_addrandom(sha256buf, sizeof(sha256buf));
+}
+
+/*
+ * init_random, written by Syzop.
+ * This function tries to initialize the arc4 random number generator securely.
+ */
+void init_random()
+{
+	struct {
+#ifndef _WIN32
+		struct timeval nowt;		/* time */
+		char rnd[32];			/* /dev/urandom */
+#else
+		struct _timeb nowt;		/* time */
+		MEMORYSTATUS mstat;		/* memory status */
+#endif
+	} rdat;
+
+#ifndef _WIN32
+	int fd;
+#endif
+
+	_rs_stir();
+
+	/* Grab OS specific "random" data */
+#ifndef _WIN32
+	gettimeofday(&rdat.nowt, NULL);
+	fd = open("/dev/urandom", O_RDONLY);
+	if (fd >= 0)
+	{
+		int n = read(fd, &rdat.rnd, sizeof(rdat.rnd));
+		close(fd);
+	}
+#else
+	_ftime(&rdat.nowt);
+	GlobalMemoryStatus (&rdat.mstat);
+#endif	
+
+	arc4_addrandom(&rdat, sizeof(rdat));
+
+	/* NOTE: addtional entropy is added by add_entropy_* function(s) */
+}
+
+uint32_t arc4random(void)
+{
+	uint32_t val;
+
+	_rs_random_u32(&val);
+	return val;
+}
+
+/** Get 8 bits (1 byte) of randomness */
+u_char getrandom8()
+{
+	return arc4random() & 0xff;
+}
+
+/** Get 16 bits (2 bytes) of randomness */
+uint16_t getrandom16()
+{
+	return arc4random() & 0xffff;
+}
+
+/** Get 32 bits (4 bytes) of randomness */
+uint32_t getrandom32()
+{
+	return arc4random();
+}
+
+/** Generate an alphanumeric string (eg: XzHe5G).
+ * @param buf      The buffer
+ * @param numbytes The number of random bytes.
+ * @note  Note that numbytes+1 bytes are written to buf.
+ *        In other words, numbytes does NOT include the NUL byte.
+ */
+void gen_random_alnum(char *buf, int numbytes)
+{
+	for (; numbytes > 0; numbytes--)
+	{
+		unsigned char v = getrandom8() % (26+26+10);
+		if (v < 26)
+			*buf++ = 'a'+v;
+		else if (v < 52)
+			*buf++ = 'A'+(v-26);
+		else
+			*buf++ = '0'+(v-52);
+	}
+	*buf = '\0';
+}
diff --git a/ircd/src/scache.c b/ircd/src/scache.c
@@ -0,0 +1,99 @@
+/* License: GPLv1 */
+
+/** @file
+ * @brief String cache - only used for server names.
+ */
+
+#include "unrealircd.h"
+
+/*
+ * ircd used to store full servernames in User as well as in the
+ * whowas info.  there can be some 40k such structures alive at any
+ * given time, while the number of unique server names a server sees in
+ * its lifetime is at most a few hundred.  by tokenizing server names
+ * internally, the server can easily save 2 or 3 megs of RAM.
+ * -orabidoo
+ */
+/*
+ * I could have tucked this code into hash.c I suppose but lets keep it
+ * separate for now -Dianora
+ */
+
+#define SCACHE_HASH_SIZE 257
+
+typedef struct SCACHE SCACHE;
+struct SCACHE {
+	char name[HOSTLEN + 1];
+	SCACHE *next;
+};
+
+static SCACHE *scache_hash[SCACHE_HASH_SIZE];
+
+/*
+ * renamed to keep it consistent with the other hash functions -Dianora 
+ */
+/*
+ * orabidoo had named it init_scache_hash(); 
+ */
+
+void clear_scache_hash_table(void)
+{
+	memset((char *)scache_hash, '\0', sizeof(scache_hash));
+}
+
+static int hash(char *string)
+{
+	int  hash_value;
+
+	hash_value = 0;
+	while (*string)
+		hash_value += (*string++ & 0xDF);
+
+	return hash_value % SCACHE_HASH_SIZE;
+}
+
+/** Add a string to the string cache.
+ * this takes a server name, and returns a pointer to the same string
+ * (up to case) in the server name token list, adding it to the list if
+ * it's not there.  care must be taken not to call this with
+ * user-supplied arguments that haven't been verified to be a valid,
+ * existing, servername.  use the hash in list.c for those.  -orabidoo
+ * @param name	A valid server name
+ * @returns Pointer to the server name
+ */
+char *find_or_add(char *name)
+{
+	int  hash_index;
+	SCACHE *ptr, *newptr;
+
+	ptr = scache_hash[hash_index = hash(name)];
+	while (ptr)
+	{
+		if (!mycmp(ptr->name, name))
+		{
+			return (ptr->name);
+		}
+		else
+		{
+			ptr = ptr->next;
+		}
+	}
+
+	/*
+	 * not found -- add it 
+	 */
+	if ((ptr = scache_hash[hash_index]))
+	{
+		newptr = scache_hash[hash_index] = safe_alloc(sizeof(SCACHE));
+		strlcpy(newptr->name, name, sizeof(newptr->name));
+		newptr->next = ptr;
+		return (newptr->name);
+	}
+	else
+	{
+		ptr = scache_hash[hash_index] = safe_alloc(sizeof(SCACHE));
+		strlcpy(ptr->name, name, sizeof(newptr->name));
+		ptr->next = (SCACHE *) NULL;
+		return (ptr->name);
+	}
+}
diff --git a/ircd/src/securitygroup.c b/ircd/src/securitygroup.c
@@ -0,0 +1,864 @@
+/*
+ * Mask & security-group routines.
+ * (C) Copyright 2015-.. 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 2
+ * of the License, 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.
+ */
+
+#include "unrealircd.h"
+
+/* Global variables */
+SecurityGroup *securitygroups = NULL;
+
+/** Free all masks in the mask list */
+void unreal_delete_masks(ConfigItem_mask *m)
+{
+	ConfigItem_mask *m_next;
+
+	for (; m; m = m_next)
+	{
+		m_next = m->next;
+
+		safe_free(m->mask);
+
+		safe_free(m);
+	}
+}
+
+/** Internal function to add one individual mask to the list */
+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->value)
+		safe_strdup(m->mask, ce->value);
+	else
+		safe_strdup(m->mask, ce->name);
+
+	add_ListItem((ListStruct *)m, (ListStruct **)head);
+}
+
+/** Add mask entries from config */
+void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce)
+{
+	if (ce->items)
+	{
+		ConfigEntry *cep;
+		for (cep = ce->items; cep; cep = cep->next)
+			unreal_add_mask(head, cep);
+	} else
+	{
+		unreal_add_mask(head, ce);
+	}
+}
+
+/** 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)
+{
+	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_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
+			{
+				retval = 1;
+				break;
+			}
+		}
+	}
+
+	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;
+}
+
+#define CheckNullX(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); *errors = *errors + 1; return 0; }
+int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors)
+{
+	ConfigEntry *cepp;
+
+	if (!strcmp(cep->name, "webirc") || !strcmp(cep->name, "exclude-webirc"))
+	{
+		CheckNullX(cep);
+	} else
+	if (!strcmp(cep->name, "websocket") || !strcmp(cep->name, "exclude-websocket"))
+	{
+		CheckNullX(cep);
+	} else
+	if (!strcmp(cep->name, "identified") || !strcmp(cep->name, "exclude-identified"))
+	{
+		CheckNullX(cep);
+	} else
+	if (!strcmp(cep->name, "tls") || !strcmp(cep->name, "exclude-tls"))
+	{
+		CheckNullX(cep);
+	} else
+	if (!strcmp(cep->name, "reputation-score") || !strcmp(cep->name, "exclude-reputation-score"))
+	{
+		const char *str = cep->value;
+		int v;
+		CheckNullX(cep);
+		if (*str == '<')
+			str++;
+		v = atoi(str);
+		if ((v < 1) || (v > 10000))
+		{
+			config_error("%s:%i: %s needs to be a value of 1-10000",
+				cep->file->filename, cep->line_number, cep->name);
+			*errors = *errors + 1;
+		}
+	} else
+	if (!strcmp(cep->name, "connect-time") || !strcmp(cep->name, "exclude-connect-time"))
+	{
+		const char *str = cep->value;
+		long v;
+		CheckNullX(cep);
+		if (*str == '<')
+			str++;
+		v = config_checkval(str, CFG_TIME);
+		if (v < 1)
+		{
+			config_error("%s:%i: %s needs to be a time value (and more than 0 seconds)",
+				cep->file->filename, cep->line_number, cep->name);
+			*errors = *errors + 1;
+		}
+	} else
+	if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "exclude-mask"))
+	{
+		for (cepp = cep->items; cepp; cepp = cepp->next)
+		{
+			if (!strcmp(cepp->name, "mask"))
+				continue;
+			if (cepp->items || cepp->value)
+			{
+				config_error("%s:%i: security-group::mask should contain hostmasks only. "
+				             "Perhaps you meant to use it in security-group { %s ... } directly?",
+				             cepp->file->filename, cepp->line_number,
+				             cepp->name);
+				*errors = *errors + 1;
+			}
+		}
+	} else
+	if (!strcmp(cep->name, "ip"))
+	{
+	} else
+	if (!strcmp(cep->name, "security-group") || !strcmp(cep->name, "exclude-security-group"))
+	{
+	} else
+	{
+		/* Let's see if an extended server ban exists for this item... */
+		Extban *extban;
+		if (!strncmp(cep->name, "exclude-", 8))
+			extban = findmod_by_bantype_raw(cep->name+8, strlen(cep->name+8));
+		else
+			extban = findmod_by_bantype_raw(cep->name, strlen(cep->name));
+		if (extban && (extban->options & EXTBOPT_TKL) && (extban->is_banned_events & BANCHK_TKL))
+		{
+			test_extended_list(extban, cep, errors);
+			return 1; /* Yup, handled */
+		}
+		return 0; /* Unhandled: unknown item for us */
+	}
+	return 1; /* Handled, but there could be errors */
+}
+
+int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	/* (If there is only a ce->value, trust that it is OK) */
+
+	/* Test ce->items... */
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		/* Only complain about things with values,
+		 * as valueless things like "10.0.0.0/8" are treated as a mask.
+		 */
+		if (!test_match_item(conf, cep, &errors) && cep->value)
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				ce->name, cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	*errors_out = *errors_out + errors;
+	return errors ? 0 : 1;
+}
+
+#define tmbbw_is_wildcard(x)	(!strcmp(x, "*") || !strcmp(x, "*@*"))
+int test_match_block_too_broad(ConfigFile *conf, ConfigEntry *ce)
+{
+	ConfigEntry *cep, *cepp;
+
+	// match *;
+	if (ce->value && tmbbw_is_wildcard(ce->value))
+		return 1;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		// match { *; }
+		if (!cep->value && tmbbw_is_wildcard(cep->name))
+			return 1;
+		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "ip"))
+		{
+			// match { mask *; }
+			if (cep->value && tmbbw_is_wildcard(cep->value))
+				return 1;
+			// match { mask { *; } }
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+				if (tmbbw_is_wildcard(cepp->name))
+					return 1;
+		}
+	}
+
+	return 0;
+}
+
+int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	/* First, check the name of the security group */
+	if (!ce->value)
+	{
+		config_error("%s:%i: security-group block needs a name, eg: security-group web-users {",
+			ce->file->filename, ce->line_number);
+		errors++;
+	} else {
+		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->file->filename, ce->line_number);
+			errors++;
+			return errors;
+		}
+		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->file->filename, ce->line_number, ce->value);
+			errors++;
+		}
+	}
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!test_match_item(conf, cep, &errors))
+		{
+			config_error_unknown(cep->file->filename, cep->line_number,
+				"security-group", cep->name);
+			errors++;
+			continue;
+		}
+	}
+
+	return errors;
+}
+
+int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
+{
+	int errors = 0; /* unused */
+	SecurityGroup *s = *block;
+
+	/* The following code is there so we don't create a security group
+	 * unless there is actually a valid config item for it encountered.
+	 * This so the security group '*s' can stay NULL if there are zero
+	 * items, so we don't waste any CPU if it is unused.
+	 */
+	if (*block == NULL)
+	{
+		/* Yeah we call a TEST routine from a CONFIG RUN routine ;). */
+		if (!test_match_item(conf, cep, &errors))
+			return 0; /* not for us */
+		/* If we are still here then we must create the security group */
+		*block = s = safe_alloc(sizeof(SecurityGroup));
+	}
+
+	if (!strcmp(cep->name, "webirc"))
+		s->webirc = config_checkval(cep->value, CFG_YESNO);
+	if (!strcmp(cep->name, "websocket"))
+		s->websocket = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "identified"))
+		s->identified = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "tls"))
+		s->tls = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "reputation-score"))
+	{
+		if (*cep->value == '<')
+			s->reputation_score = 0 - atoi(cep->value+1);
+		else
+			s->reputation_score = atoi(cep->value);
+	}
+	else if (!strcmp(cep->name, "connect-time"))
+	{
+		if (*cep->value == '<')
+			s->connect_time = 0 - config_checkval(cep->value+1, CFG_TIME);
+		else
+			s->connect_time = config_checkval(cep->value, CFG_TIME);
+	}
+	else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask"))
+	{
+		unreal_add_masks(&s->mask, cep);
+	}
+	else if (!strcmp(cep->name, "ip"))
+	{
+		unreal_add_names(&s->ip, cep);
+	}
+	else if (!strcmp(cep->name, "security-group"))
+	{
+		unreal_add_names(&s->security_group, cep);
+	}
+	else if (!strcmp(cep->name, "exclude-webirc"))
+		s->exclude_webirc = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "exclude-websocket"))
+		s->exclude_websocket = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "exclude-identified"))
+		s->exclude_identified = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "exclude-tls"))
+		s->exclude_tls = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "exclude-reputation-score"))
+	{
+		if (*cep->value == '<')
+			s->exclude_reputation_score = 0 - atoi(cep->value+1);
+		else
+			s->exclude_reputation_score = atoi(cep->value);
+	}
+	else if (!strcmp(cep->name, "exclude-mask"))
+	{
+		unreal_add_masks(&s->exclude_mask, cep);
+	}
+	else if (!strcmp(cep->name, "exclude-security-group"))
+	{
+		unreal_add_names(&s->security_group, cep);
+	}
+	else
+	{
+		/* Let's see if an extended server ban exists for this item... this needs to be LAST! */
+		Extban *extban;
+		const char *name = cep->name;
+
+		if (!strncmp(cep->name, "exclude-", 8))
+		{
+			/* Extended (exclusive) ? */
+			name = cep->name + 8;
+			if (findmod_by_bantype_raw(name, strlen(name)))
+				unreal_add_name_values(&s->exclude_extended, name, cep);
+			else
+				return 0; /* Unhandled */
+		} else {
+			/* Extended (inclusive) */
+			if (findmod_by_bantype_raw(name, strlen(name)))
+				unreal_add_name_values(&s->extended, name, cep);
+			else
+				return 0; /* Unhandled */
+		}
+	}
+
+	/* And update the printable list */
+	if (cep->items)
+	{
+		ConfigEntry *cep2;
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
+			add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep2->name);
+	} else {
+		add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value);
+	}
+
+	return 1; /* Handled by us (guaranteed earlier) */
+}
+
+int conf_match_block(ConfigFile *conf, ConfigEntry *ce, SecurityGroup **block)
+{
+	ConfigEntry *cep;
+	SecurityGroup *s = *block;
+
+	if (*block == NULL)
+		*block = s = safe_alloc(sizeof(SecurityGroup));
+
+	/* Check for simple form: match *; / mask *; */
+	if (ce->value)
+	{
+		unreal_add_masks(&s->mask, ce);
+		add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", ce->value);
+	}
+
+	/* Check for long form: match { .... } / mask { .... } */
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!conf_match_item(conf, cep, &s) && !cep->value && !cep->items)
+		{
+			/* Valueless? Then it must be a mask like 10.0.0.0/8 */
+			unreal_add_masks(&s->mask, cep);
+			add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", cep->name);
+		}
+	}
+	return 1;
+}
+
+int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
+{
+	ConfigEntry *cep;
+	SecurityGroup *s = add_security_group(ce->value, 1);
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "priority"))
+		{
+			s->priority = atoi(cep->value);
+			DelListItem(s, securitygroups);
+			AddListItemPrio(s, securitygroups, s->priority);
+		} else
+			conf_match_item(conf, cep, &s);
+	}
+	return 1;
+}
+
+/** Check if the name of the security-group contains only valid characters.
+ * @param name	The name of the group
+ * @returns 1 if name is valid, 0 if not (eg: illegal characters)
+ */
+int security_group_valid_name(const char *name)
+{
+	const char *p;
+
+	if (strlen(name) > SECURITYGROUPLEN)
+		return 0; /* Too long */
+
+	for (p = name; *p; p++)
+	{
+		if (!isalnum(*p) && !strchr("_-", *p))
+			return 0; /* Character not allowed */
+	}
+	return 1;
+}
+
+/** Find a security-group.
+ * @param name	The name of the security group
+ * @returns A SecurityGroup struct, or NULL if not found.
+ */
+SecurityGroup *find_security_group(const char *name)
+{
+	SecurityGroup *s;
+	for (s = securitygroups; s; s = s->next)
+		if (!strcasecmp(name, s->name))
+			return s;
+	return NULL;
+}
+
+/** Checks if a security-group exists.
+ * This function takes the 'unknown-users' magic group into account as well.
+ * @param name	The name of the security group
+ * @returns 1 if it exists, 0 if not
+ */
+int security_group_exists(const char *name)
+{
+	if (!strcmp(name, "unknown-users") || find_security_group(name))
+		return 1;
+	return 0;
+}
+
+/** Add a new security-group and add it to the list, but search for existing one first.
+ * @param name	The name of the security group
+ * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list)
+ */
+SecurityGroup *add_security_group(const char *name, int priority)
+{
+	SecurityGroup *s = find_security_group(name);
+
+	/* Existing? */
+	if (s)
+		return s;
+
+	/* Otherwise, create a new entry */
+	s = safe_alloc(sizeof(SecurityGroup));
+	strlcpy(s->name, name, sizeof(s->name));
+	s->priority = priority;
+	AddListItemPrio(s, securitygroups, priority);
+	return s;
+}
+
+/** Free a SecurityGroup struct */
+void free_security_group(SecurityGroup *s)
+{
+	if (s == NULL)
+		return;
+	unreal_delete_masks(s->mask);
+	unreal_delete_masks(s->exclude_mask);
+	free_entire_name_list(s->security_group);
+	free_entire_name_list(s->exclude_security_group);
+	free_entire_name_list(s->ip);
+	free_entire_name_list(s->exclude_ip);
+	free_nvplist(s->extended);
+	free_nvplist(s->exclude_extended);
+	free_nvplist(s->printable_list);
+	safe_free(s);
+}
+
+/** Initialize the default security-group blocks */
+void set_security_group_defaults(void)
+{
+	SecurityGroup *s, *s_next;
+
+	/* First free all security groups */
+	for (s = securitygroups; s; s = s_next)
+	{
+		s_next = s->next;
+		free_security_group(s);
+	}
+	securitygroups = NULL;
+
+	/* Default group: webirc */
+	s = add_security_group("webirc-users", 50);
+	s->webirc = 1;
+
+	/* Default group: websocket */
+	s = add_security_group("websocket-users", 51);
+	s->websocket = 1;
+
+	/* Default group: known-users */
+	s = add_security_group("known-users", 100);
+	s->identified = 1;
+	s->reputation_score = 25;
+	s->webirc = 0;
+
+	/* Default group: tls-and-known-users */
+	s = add_security_group("tls-and-known-users", 200);
+	s->identified = 1;
+	s->reputation_score = 25;
+	s->webirc = 0;
+	s->tls = 1;
+
+	/* Default group: tls-users */
+	s = add_security_group("tls-users", 300);
+	s->tls = 1;
+}
+
+int user_matches_extended_list(Client *client, NameValuePrioList *e)
+{
+	Extban *extban;
+	BanContext b;
+
+	for (; e; e = e->next)
+	{
+		extban = findmod_by_bantype_raw(e->name, strlen(e->name));
+		if (!extban ||
+		    !(extban->options & EXTBOPT_TKL) ||
+		    !(extban->is_banned_events & BANCHK_TKL))
+		{
+			continue; /* extban not found or of incorrect type */
+		}
+
+		memset(&b, 0, sizeof(BanContext));
+		b.client = client;
+		b.banstr = e->value;
+		b.ban_check_types = BANCHK_TKL;
+		if (extban->is_banned(&b))
+			return 1;
+	}
+
+	return 0;
+}
+
+int test_extended_list(Extban *extban, ConfigEntry *cep, int *errors)
+{
+	BanContext b;
+
+	if (cep->value)
+	{
+		memset(&b, 0, sizeof(BanContext));
+		b.banstr = cep->value;
+		b.ban_check_types = BANCHK_TKL;
+		b.what = MODE_ADD;
+		if (!extban->conv_param(&b, extban))
+		{
+			config_error("%s:%i: %s has an invalid value",
+			             cep->file->filename, cep->line_number, cep->name);
+			*errors = *errors + 1;
+			return 0;
+		}
+	}
+
+	for (cep = cep->items; cep; cep = cep->next)
+	{
+		memset(&b, 0, sizeof(BanContext));
+		b.banstr = cep->name;
+		b.ban_check_types = BANCHK_TKL;
+		b.what = MODE_ADD;
+		if (!extban->conv_param(&b, extban))
+		{
+			config_error("%s:%i: %s has an invalid value",
+			             cep->file->filename, cep->line_number, cep->name);
+			*errors = *errors + 1;
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/** Returns 1 if the user is allowed by any of the security groups in the named list.
+ * This is only used by security-group::security-group and
+ * security-group::exclude-security-group.
+ * @param client	Client to check
+ * @param l		The NameList
+ * @returns 1 if any of the security groups match, 0 if none of them matched.
+ */
+int user_allowed_by_security_group_list(Client *client, NameList *l)
+{
+	for (; l; l = l->next)
+		if (user_allowed_by_security_group_name(client, l->name))
+			return 1;
+	return 0;
+}
+
+/** Returns 1 if the user is OK as far as the security-group is concerned.
+ * @param client	The client to check
+ * @param s		The security-group to check against
+ * @retval 1 if user is allowed by security-group, 0 if not.
+ */
+int user_allowed_by_security_group(Client *client, SecurityGroup *s)
+{
+	static int recursion_security_group = 0;
+
+	/* Allow NULL securitygroup, makes it easier in the code elsewhere */
+	if (!s)
+		return 0;
+
+	if (recursion_security_group > 8)
+	{
+		unreal_log(ULOG_WARNING, "main", "SECURITY_GROUP_LOOP_DETECTED", client,
+		           "Loop detected while processing security-group '$security_group' -- "
+		           "are you perhaps referencing a security-group from a security-group?",
+		           log_data_string("security_group", s->name));
+		return 0;
+	}
+	recursion_security_group++;
+
+	/* DO NOT USE 'return' IN CODE BELOW!!!!!!!!!
+	 * - use 'goto user_not_allowed' to reject
+	 * - use 'goto user_allowed' to accept
+	 */
+
+	/* Process EXCLUSION criteria first... */
+	if (s->exclude_identified && IsLoggedIn(client))
+		goto user_not_allowed;
+	if (s->exclude_webirc && moddata_client_get(client, "webirc"))
+		goto user_not_allowed;
+	if (s->exclude_websocket && moddata_client_get(client, "websocket"))
+		goto user_not_allowed;
+	if ((s->exclude_reputation_score > 0) && (GetReputation(client) >= s->exclude_reputation_score))
+		goto user_not_allowed;
+	if ((s->exclude_reputation_score < 0) && (GetReputation(client) < 0 - s->exclude_reputation_score))
+		goto user_not_allowed;
+	if (s->exclude_connect_time != 0)
+	{
+		long connect_time = get_connected_time(client);
+		if ((s->exclude_connect_time > 0) && (connect_time >= s->exclude_connect_time))
+			goto user_not_allowed;
+		if ((s->exclude_connect_time < 0) && (connect_time < 0 - s->exclude_connect_time))
+			goto user_not_allowed;
+	}
+	if (s->exclude_tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
+		goto user_not_allowed;
+	if (s->exclude_mask && unreal_mask_match(client, s->exclude_mask))
+		goto user_not_allowed;
+	if (s->exclude_ip && unreal_match_iplist(client, s->exclude_ip))
+		goto user_not_allowed;
+	if (s->exclude_security_group && user_allowed_by_security_group_list(client, s->exclude_security_group))
+		goto user_not_allowed;
+	if (s->exclude_extended && user_matches_extended_list(client, s->exclude_extended))
+		goto user_not_allowed;
+
+	/* Then process INCLUSION criteria... */
+	if (s->identified && IsLoggedIn(client))
+		goto user_allowed;
+	if (s->webirc && moddata_client_get(client, "webirc"))
+		goto user_allowed;
+	if (s->websocket && moddata_client_get(client, "websocket"))
+		goto user_allowed;
+	if ((s->reputation_score > 0) && (GetReputation(client) >= s->reputation_score))
+		goto user_allowed;
+	if ((s->reputation_score < 0) && (GetReputation(client) < 0 - s->reputation_score))
+		goto user_allowed;
+	if (s->connect_time != 0)
+	{
+		long connect_time = get_connected_time(client);
+		if ((s->connect_time > 0) && (connect_time >= s->connect_time))
+			goto user_allowed;
+		if ((s->connect_time < 0) && (connect_time < 0 - s->connect_time))
+			goto user_allowed;
+	}
+	if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
+		goto user_allowed;
+	if (s->mask && unreal_mask_match(client, s->mask))
+		goto user_allowed;
+	if (s->ip && unreal_match_iplist(client, s->ip))
+		goto user_allowed;
+	if (s->security_group && user_allowed_by_security_group_list(client, s->security_group))
+		goto user_allowed;
+	if (s->extended && user_matches_extended_list(client, s->extended))
+		goto user_allowed;
+
+user_not_allowed:
+	recursion_security_group--;
+	return 0;
+
+user_allowed:
+	recursion_security_group--;
+	return 1;
+}
+
+/** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version.
+ * @param client	The client to check
+ * @param secgroupname	The name of the security-group to check against
+ * @retval 1 if user is allowed by security-group, 0 if not.
+ */
+int user_allowed_by_security_group_name(Client *client, const char *secgroupname)
+{
+	SecurityGroup *s;
+
+	/* Handle the magical 'unknown-users' case. */
+	if (!strcmp(secgroupname, "unknown-users"))
+	{
+		/* This is simply the inverse of 'known-users' */
+		s = find_security_group("known-users");
+		if (!s)
+			return 0; /* that's weird!? pretty impossible. */
+		return !user_allowed_by_security_group(client, s);
+	}
+
+	/* Find the group and evaluate it */
+	s = find_security_group(secgroupname);
+	if (!s)
+		return 0; /* security group not found: no match */
+	return user_allowed_by_security_group(client, s);
+}
+
+/** 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;
+}
diff --git a/ircd/src/send.c b/ircd/src/send.c
@@ -0,0 +1,1193 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/send.c
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *		      University of Oulu, Computing Center
+ *
+ *   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.
+ */
+
+/* send.c 2.32 2/28/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
+
+/** @file
+ * @brief The sending functions to users, channels, servers.
+ */
+
+#include "unrealircd.h"
+
+/* 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) __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)
+
+/* These are two local (static) buffers used by the various send functions */
+static char sendbuf[2048];
+static char sendbuf2[MAXLINELENGTH];
+
+/** This is used to ensure no duplicate messages are sent
+ * to the same server uplink/direction. In send functions
+ * that deliver to multiple users or servers the value is
+ * increased by 1 and then for each delivery in the loop
+ * it is checked if to->direction->local->serial == current_serial
+ * and if so, sending is skipped.
+ */
+MODVAR int  current_serial;
+
+/** Mark the socket as "dead".
+ * This is used when exit_client() cannot be used from the
+ * current code because doing so would be (too) unexpected.
+ * The socket is closed later in the main loop.
+ * NOTE: this function is becoming less important, now that
+ *       exit_client() will not actively free the client.
+ *       Still, sometimes we need to use dead_socket()
+ *       since we don't want to be doing IsDead() checks after
+ *       each and every sendto...().
+ * @param to		Client to mark as dead
+ * @param notice	The quit reason to use
+ */
+int dead_socket(Client *to, const char *notice)
+{
+	DBufClear(&to->local->recvQ);
+	DBufClear(&to->local->sendQ);
+
+	if (IsDeadSocket(to))
+		return -1; /* already pending to be closed */
+
+	SetDeadSocket(to);
+
+	/* We may get here because of the 'CPR' in check_deadsockets().
+	 * In which case, we return -1 as well.
+	 */
+	if (to->local->error_str)
+		return -1; /* don't overwrite & don't send multiple times */
+	
+	if (!IsUser(to) && !IsUnknown(to) && !IsRPC(to) && !IsControl(to) && !IsClosing(to))
+	{
+		/* 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;
+}
+
+/** This is a callback function from the event loop.
+ * All it does is call send_queued().
+ */
+void send_queued_cb(int fd, int revents, void *data)
+{
+	Client *to = data;
+
+	if (IsDeadSocket(to))
+		return;
+
+	send_queued(to);
+}
+
+/** This function is called when queued data might be ready to be
+ * sent to the client. It is called from the event loop and also
+ * a couple of other places (such as when closing the connection).
+ */
+int send_queued(Client *to)
+{
+	int  len, rlen;
+	dbufbuf *block;
+	int want_read;
+
+	/* We NEVER write to dead sockets. */
+	if (IsDeadSocket(to))
+		return -1;
+
+	while (DBufLength(&to->local->sendQ) > 0)
+	{
+		block = container_of(to->local->sendQ.dbuf_list.next, dbufbuf, dbuf_node);
+		len = block->size;
+
+		/* Deliver it and check for fatal error.. */
+		if ((rlen = deliver_it(to, block->data, len, &want_read)) < 0)
+		{
+			char buf[256];
+			snprintf(buf, 256, "Write error: %s", STRERROR(ERRNO));
+			return dead_socket(to, buf);
+		}
+		dbuf_delete(&to->local->sendQ, rlen);
+		if (want_read)
+		{
+			/* SSL_write indicated that it cannot write data at this
+			 * time and needs to READ data first. Let's stop talking
+			 * to the user and ask to notify us when there's data
+			 * to read.
+			 */
+			fd_setselect(to->local->fd, FD_SELECT_READ, send_queued_cb, to);
+			fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to);
+			break;
+		}
+		/* Restore handling of reads towards read_packet(), since
+		 * it may be overwritten in an earlier call to send_queued(),
+		 * to handle reads by send_queued_cb(), see directly above.
+		 */
+		fd_setselect(to->local->fd, FD_SELECT_READ, read_packet, to);
+		if (rlen < len)
+		{
+			/* incomplete write due to EWOULDBLOCK, reschedule */
+			fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to);
+			break;
+		}
+	}
+	
+	/* Nothing left to write, stop asking for write-ready notification. */
+	if ((DBufLength(&to->local->sendQ) == 0) && (to->local->fd >= 0))
+		fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to);
+
+	return (IsDeadSocket(to)) ? -1 : 0;
+}
+
+/** Mark "to" with "there is data to be send" */
+void mark_data_to_send(Client *to)
+{
+	if (!IsDeadSocket(to) && (to->local->fd >= 0) && (DBufLength(&to->local->sendQ) > 0))
+	{
+		fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to);
+	}
+}
+
+/** Send data to clients, servers, channels, IRCOps, etc.
+ * There are a lot of send functions. The most commonly functions
+ * are: sendto_one() to send to an individual user,
+ * sendnumeric() to send a numeric to an individual user
+ * and sendto_channel() to send a message to a channel.
+ * @defgroup SendFunctions Send functions
+ * @{
+ */
+
+/** Send a message to a single client.
+ * This function is used quite a lot, after sendnumeric() it is the most-used send function.
+ * @param to		The client to send to
+ * @param mtags		Any message tags associated with this message (can be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @section sendto_one_examples Examples
+ * @subsection sendto_one_mode_r Send "MODE -r"
+ * This will send the `:serv.er.name MODE yournick -r` message.
+ * Note that it will send only this message to illustrate the sendto_one() function.
+ * It does *not* set anyone actually -r.
+ * @code
+ * sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name);
+ * @endcode
+ */
+void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	va_start(vl, pattern);
+	vsendto_one(to, mtags, pattern, vl);
+	va_end(vl);
+}
+
+/** Send a message to a single client - va_list variant.
+ * This function is similar to sendto_one() except that it
+ * doesn't use varargs but uses a va_list instead.
+ * Generally this function is NOT used outside send.c, so not by modules.
+ * @param to		The client to send to
+ * @param mtags		Any message tags associated with this message (can be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param vl		Format string parameters.
+ */
+void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl)
+{
+	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)-3, pattern, vl);
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
+	if (BadPtr(mtags_str))
+	{
+		/* Simple message without message tags */
+		sendbufto_one(to, sendbuf, 0);
+	} else {
+		/* Message tags need to be prepended */
+		snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
+		sendbufto_one(to, sendbuf2, 0);
+	}
+}
+
+
+/** Send a line buffer to the client.
+ * This function is used (usually indirectly) for pretty much all
+ * cases where a line needs to be sent to a client.
+ * @param to    The client to which the buffer should be send.
+ * @param msg   The message.
+ * @param quick Normally set to 0, see the notes.
+ * @note
+ * - Neither 'to' or 'msg' may be NULL.
+ * - If quick is set to 0 then the length is calculated,
+ *   the string is cut off at 510 bytes if needed, and
+ *   CR+LF is added if needed.
+ *   If quick is >0 then it is assumed the message already
+ *   is within boundaries and passed all safety checks and
+ *   contains CR+LF at the end. This if, for example, used in
+ *   channel broadcasts to save some CPU cycles. It is NOT
+ *   recommended as normal usage since you need to be very
+ *   careful to take everything into account, including side-
+ *   effects not mentioned here.
+ */
+void sendbufto_one(Client *to, char *msg, unsigned int quick)
+{
+	int len;
+	Hook *h;
+	Client *intended_to = to;
+	
+	if (to->direction)
+		to = to->direction;
+	if (IsDeadSocket(to))
+		return;		/* This socket has already
+				   been marked as dead */
+	if (to->local->fd < 0)
+	{
+		/* This is normal when 'to' was being closed (via exit_client
+		 *  and close_connection) --Run
+		 */
+		return;
+	}
+
+	/* Unless 'quick' is set, we do some safety checks,
+	 * cut the string off at the appropriate place and add
+	 * CR+LF if needed (nearly always).
+	 */
+	if (!quick)
+	{
+		char *p = msg;
+		if (*msg == '@')
+		{
+			/* The message includes one or more message tags:
+			 * Spec-wise the rules allow about 8K for message tags
+			 * (MAXTAGSIZE) and then 512 bytes for
+			 * the remainder of the message (BUFSIZE).
+			 */
+			p = strchr(msg+1, ' ');
+			if (!p)
+			{
+				unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_MALFORMED_MSG", to,
+				           "Malformed message to $client: $buf",
+				           log_data_string("buf", msg));
+				return;
+			}
+			if (p - msg > MAXTAGSIZE)
+			{
+				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 */
+		}
+		len = strlen(p);
+		if (!len || (p[len - 1] != '\n'))
+		{
+			if (len > 510)
+				len = 510;
+			p[len++] = '\r';
+			p[len++] = '\n';
+			p[len] = '\0';
+		}
+		len = strlen(msg); /* (note: we could use pointer jugling to avoid a strlen here) */
+	} else {
+		len = quick;
+	}
+
+	if (len >= MAXLINELENGTH)
+	{
+		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))
+	{
+		char tmp_msg[500];
+
+		strlcpy(tmp_msg, msg, sizeof(tmp_msg));
+		stripcrlf(tmp_msg);
+		unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_ME_MESSAGE", to,
+			   "Trying to send data to myself: $buf",
+			   log_data_string("buf", tmp_msg));
+		return;
+	}
+
+	for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
+	{
+		(*(h->func.intfunc))(&me, to, intended_to, &msg, &len);
+		if (!msg)
+			return;
+	}
+
+#if defined(RAWCMDLOGGING)
+	{
+		char copy[512], *p;
+		strlcpy(copy, msg, len > sizeof(copy) ? sizeof(copy) : len);
+		p = strchr(copy, '\n');
+		if (p) *p = '\0';
+		p = strchr(copy, '\r');
+		if (p) *p = '\0';
+		unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_OUT", to,
+		           "-> $client: $data",
+		           log_data_string("data", copy));
+	}
+#endif
+
+	if (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;
+	}
+
+	dbuf_put(&to->local->sendQ, msg, len);
+
+	/*
+	 * Update statistics. The following is slightly incorrect
+	 * because it counts messages even if queued, but bytes
+	 * 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->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
+	 * that there is data to send.
+	 */
+	if (IsControl(to))
+		send_queued(to); /* send this one ASAP */
+	else
+		mark_data_to_send(to);
+}
+
+/** A single function to send data to a channel.
+ * Previously there were 6 different functions to send channel data,
+ * now there is 1 single function. This also means that you most
+ * likely will pass NULL or 0 as some parameters.
+ * @param channel       The channel to send to
+ * @param from          The source of the message
+ * @param skip          The client to skip (can be NULL).
+ *                      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
+ *                     assume the server there will handle it)
+ * @param sendflags   Determines whether to send the message to local/remote users
+ * @param mtags       The message tags to attach to this message
+ * @param pattern     The pattern (eg: ":%s PRIVMSG %s :%s")
+ * @param ...         The parameters for the pattern.
+ * @note For all channel messages, it is important to attach the correct
+ *       message tags (mtags) via a new_message() call, as can be seen
+ *       in the example.
+ * @section sendto_channel_examples Examples
+ * @subsection sendto_channel_privmsg Send a PRIVMSG to a channel
+ * This command will send the message "Hello everyone!!!" to the channel when executed.
+ * @code
+ * CMD_FUNC(cmd_sayhello)
+ * {
+ *     MessageTag *mtags = NULL;
+ *     Channel *channel = NULL;
+ *     if ((parc < 2) || BadPtr(parv[1]))
+ *     {
+ *         sendnumeric(client, ERR_NEEDMOREPARAMS, "SAYHELLO");
+ *         return;
+ *     }
+ *     channel = find_channel(parv[1]);
+ *     if (!channel)
+ *     {
+ *         sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+ *         return;
+ *     }
+ *     new_message(client, recv_mtags, &mtags);
+ *     sendto_channel(channel, client, client->direction, NULL, 0,
+ *                    SEND_LOCAL|SEND_REMOTE, mtags,
+ *                    ":%s PRIVMSG %s :Hello everyone!!!",
+ *                    client->name, channel->name);
+ *     free_message_tags(mtags);
+ * }
+ * @endcode
+ */
+void sendto_channel(Channel *channel, Client *from, Client *skip,
+                    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)
+	{
+		acptr = lp->client;
+
+		/* Skip sending to 'skip' */
+		if ((acptr == skip) || (acptr->direction == skip))
+			continue;
+		/* Don't send to deaf clients (unless 'senddeaf' is set) */
+		if (IsDeaf(acptr) && (sendflags & SKIP_DEAF))
+			continue;
+		/* Don't send to NOCTCP clients */
+		if (has_user_mode(acptr, 'T') && (sendflags & SKIP_CTCP))
+			continue;
+		/* 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;
+
+		if (MyUser(acptr))
+		{
+			/* Local client */
+			if (sendflags & SEND_LOCAL)
+			{
+				va_start(vl, pattern);
+				vsendto_prefix_one(acptr, from, mtags, pattern, vl);
+				va_end(vl);
+			}
+		}
+		else
+		{
+			/* Remote client */
+			if (sendflags & SEND_REMOTE)
+			{
+				/* Message already sent to remote link? */
+				if (acptr->direction->local->serial != current_serial)
+				{
+					va_start(vl, pattern);
+					vsendto_prefix_one(acptr, from, mtags, pattern, vl);
+					va_end(vl);
+
+					acptr->direction->local->serial = current_serial;
+				}
+			}
+		}
+	}
+
+	if (sendflags & SEND_REMOTE)
+	{
+		/* For the remaining uplinks that we have not sent a message to yet...
+		 * broadcast-channel-messages=never: don't send it to them
+		 * broadcast-channel-messages=always: always send it to them
+		 * broadcast-channel-messages=auto: send it to them if the channel is set +H (history)
+		 */
+
+		if ((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_ALWAYS) ||
+		    ((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_AUTO) && has_channel_mode(channel, 'H')))
+		{
+			list_for_each_entry(acptr, &server_list, special_node)
+			{
+				if ((acptr == skip) || (acptr->direction == skip))
+					continue; /* still obey this rule.. */
+				if (acptr->direction->local->serial != current_serial)
+				{
+					va_start(vl, pattern);
+					vsendto_prefix_one(acptr, from, mtags, pattern, vl);
+					va_end(vl);
+
+					acptr->direction->local->serial = current_serial;
+				}
+			}
+		}
+	}
+}
+
+/** Send a message to a server, taking into account server options if needed.
+ * @param one		The client to skip (can be NULL)
+ * @param servercaps	Server capabilities which must be present (OR'd together, if multiple)
+ * @param noservercaps	Server capabilities which must NOT be present (OR'd together, if multiple)
+ * @param mtags		The message tags to attach to this message.
+ * @param format	The format string / pattern, such as ":%s NICK %s".
+ * @param ...		The parameters for the format string
+ */
+void sendto_server(Client *one, unsigned long servercaps, unsigned long noservercaps, MessageTag *mtags, FORMAT_STRING(const char *format), ...)
+{
+	Client *acptr;
+
+	/* noone to send to.. */
+	if (list_empty(&server_list))
+		return;
+
+	list_for_each_entry(acptr, &server_list, special_node)
+	{
+		va_list vl;
+
+		if (one && acptr == one->direction)
+			continue;
+
+		if (servercaps && !CHECKSERVERPROTO(acptr, servercaps))
+			continue;
+
+		if (noservercaps && CHECKSERVERPROTO(acptr, noservercaps))
+			continue;
+
+		va_start(vl, format);
+		vsendto_one(acptr, mtags, format, vl);
+		va_end(vl);
+	}
+}
+
+/** Send a message to all local users on all channels where
+ * the user 'user' is on.
+ * This is used for events such as a nick change and quit.
+ * @param user        The user and source of the message.
+ * @param skip        The client to skip (can be NULL)
+ * @param clicap      Client capability the recipient should have
+ *                    (this only works for local clients, we will
+ *                     always send the message to remote clients and
+ *                     assume the server there will handle it)
+ * @param mtags       The message tags to attach to this message.
+ * @param pattern     The pattern (eg: ":%s NICK %s").
+ * @param ...         The parameters for the pattern.
+ */
+void sendto_local_common_channels(Client *user, Client *skip, long clicap, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Membership *channels;
+	Member *users;
+	Client *acptr;
+
+	/* We now create the buffer _before_ we send it to the clients. -- Syzop */
+	*sendbuf = '\0';
+	va_start(vl, pattern);
+	vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, user, pattern, vl);
+	va_end(vl);
+
+	++current_serial;
+
+	if (user->user)
+	{
+		for (channels = user->user->channel; channels; channels = channels->next)
+		{
+			for (users = channels->channel->members; users; users = users->next)
+			{
+				acptr = users->client;
+
+				if (!MyConnect(acptr))
+					continue; /* only process local clients */
+
+				if (acptr->local->serial == current_serial)
+					continue; /* message already sent to this client */
+
+				if (clicap && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
+					continue; /* client does not have the specified capability */
+
+				if (acptr == skip)
+					continue; /* the one to skip */
+
+				if (!user_can_see_member(acptr, user, channels->channel))
+					continue; /* the sending user (quit'ing or nick changing) is 'invisible' -- skip */
+
+				acptr->local->serial = current_serial;
+				sendto_one(acptr, mtags, "%s", sendbuf);
+			}
+		}
+	}
+}
+
+/** Send a QUIT message to all local users on all channels where
+ * the user 'user' is on.
+ * This is used for events such as a nick change and quit.
+ * @param user        The user and source of the message.
+ * @param skip        The client to skip (can be NULL)
+ * @param clicap      Client capability the recipient should have
+ *                    (this only works for local clients, we will
+ *                     always send the message to remote clients and
+ *                     assume the server there will handle it)
+ * @param mtags       The message tags to attach to this message.
+ * @param pattern     The pattern (eg: ":%s NICK %s").
+ * @param ...         The parameters for the pattern.
+ */
+void quit_sendto_local_common_channels(Client *user, MessageTag *mtags, const char *reason)
+{
+	va_list vl;
+	Membership *channels;
+	Member *users;
+	Client *acptr;
+	char sender[512];
+	MessageTag *m;
+	const char *real_quit_reason = NULL;
+
+	m = find_mtag(mtags, "unrealircd.org/real-quit-reason");
+	if (m && m->value)
+		real_quit_reason = m->value;
+
+	if (IsUser(user))
+	{
+		snprintf(sender, sizeof(sender), "%s!%s@%s",
+		         user->name, user->user->username, GetHost(user));
+	} else {
+		strlcpy(sender, user->name, sizeof(sender));
+	}
+
+	++current_serial;
+
+	if (user->user)
+	{
+		for (channels = user->user->channel; channels; channels = channels->next)
+		{
+			for (users = channels->channel->members; users; users = users->next)
+			{
+				acptr = users->client;
+
+				if (!MyConnect(acptr))
+					continue; /* only process local clients */
+
+				if (acptr->local->serial == current_serial)
+					continue; /* message already sent to this client */
+
+				if (!user_can_see_member(acptr, user, channels->channel))
+					continue; /* the sending user (QUITing) is 'invisible' -- skip */
+
+				acptr->local->serial = current_serial;
+				if (!reason)
+					sendto_one(acptr, mtags, ":%s QUIT", sender);
+				else if (!IsOper(acptr) || !real_quit_reason)
+					sendto_one(acptr, mtags, ":%s QUIT :%s", sender, reason);
+				else
+					sendto_one(acptr, mtags, ":%s QUIT :%s", sender, real_quit_reason);
+			}
+		}
+	}
+}
+
+/*
+** send a msg to all ppl on servers/hosts that match a specified mask
+** (used for enhanced PRIVMSGs)
+**
+** addition -- Armin, 8jun90 (gruner@informatik.tu-muenchen.de)
+*/
+
+static int match_it(Client *one, const char *mask, int what)
+{
+	switch (what)
+	{
+		case MATCH_HOST:
+			return match_simple(mask, one->user->realhost);
+		case MATCH_SERVER:
+		default:
+			return match_simple(mask, one->user->server);
+	}
+}
+
+/** Send to all clients which match the mask.
+ * This function is rarely used.
+ * @param one		The client to skip
+ * @param from		The sender
+ * @param mask		The mask
+ * @param what		One of MATCH_HOST or MATCH_SERVER
+ * @param mtags		Message tags associated with the message
+ * @param pattern	Format string
+ * @param ...		Parameters to the format string
+ */
+void sendto_match_butone(Client *one, Client *from, const char *mask, int what,
+                         MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Client *acptr;
+	char cansendlocal, cansendglobal;
+
+	if (MyConnect(from))
+	{
+		cansendlocal = (ValidatePermissionsForPath("chat:notice:local",from,NULL,NULL,NULL)) ? 1 : 0;
+		cansendglobal = (ValidatePermissionsForPath("chat:notice:global",from,NULL,NULL,NULL)) ? 1 : 0;
+	}
+	else
+		cansendlocal = cansendglobal = 1;
+
+	/* To servers... */
+	if (cansendglobal)
+	{
+		char buf[512];
+
+		va_start(vl, pattern);
+		ircvsnprintf(buf, sizeof(buf), pattern, vl);
+		va_end(vl);
+
+		sendto_server(one, 0, 0, mtags, "%s", buf);
+	}
+
+	/* To local clients... */
+	if (cansendlocal)
+	{
+		list_for_each_entry(acptr, &lclient_list, lclient_node)
+		{
+			if (!IsMe(acptr) && (acptr != one) && IsUser(acptr) && match_it(acptr, mask, what))
+			{
+				va_start(vl, pattern);
+				vsendto_prefix_one(acptr, from, mtags, pattern, vl);
+				va_end(vl);
+			}
+		}
+	}
+}
+
+/** Send a message to all locally connected users with specified user mode.
+ * @param umodes	The umode that the recipient should have set (one of UMODE_)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_umode(int umodes, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Client *acptr;
+	char nbuf[1024];
+
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+		if (IsUser(acptr) && (acptr->umodes & umodes) == umodes)
+		{
+			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 users with specified user mode (local & remote users).
+ * @param umodes	The umode that the recipient should have set (one of UMODE_*)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Client *acptr;
+	Umode *um;
+	char nbuf[1024];
+	char modestr[128];
+	char *p;
+
+	/* Convert 'umodes' (int) to 'modestr' (string) */
+	get_usermode_string_raw_r(umodes, modestr, sizeof(modestr));
+
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+	{
+		if (IsUser(acptr) && (acptr->umodes & umodes) == umodes)
+		{
+			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);
+		} else
+		if (IsServer(acptr) && *modestr)
+		{
+			snprintf(nbuf, sizeof(nbuf), ":%s SENDUMODE %s :%s", me.id, modestr, pattern);
+			va_start(vl, pattern);
+			vsendto_one(acptr, NULL, nbuf, vl);
+			va_end(vl);
+		}
+	}
+}
+
+/** 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, const char *token)
+{
+	Client *client;
+	ClientCapability *clicap = ClientCapabilityFindReal(token);
+	long CAP_NOTIFY = ClientCapabilityBit("cap-notify");
+
+	list_for_each_entry(client, &lclient_list, lclient_node)
+	{
+		if (HasCapabilityFast(client, CAP_NOTIFY))
+		{
+			if (add)
+			{
+				const char *args = NULL;
+				if (clicap)
+				{
+					if (clicap->visible && !clicap->visible(client))
+						continue; /* invisible CAP, so don't announce it */
+					if (clicap->parameter && (client->local->cap_protocol >= 302))
+						args = clicap->parameter(client);
+				}
+				if (!args)
+				{
+					sendto_one(client, NULL, ":%s CAP %s NEW :%s",
+						me.name, (*client->name ? client->name : "*"), token);
+				} else {
+					sendto_one(client, NULL, ":%s CAP %s NEW :%s=%s",
+						me.name, (*client->name ? client->name : "*"), token, args);
+				}
+			} else {
+				sendto_one(client, NULL, ":%s CAP %s DEL :%s",
+					me.name, (*client->name ? client->name : "*"), token);
+			}
+		}
+	}
+}
+
+/* Prepare buffer based on format string and 'from' for LOCAL delivery.
+ * The prefix (:<something>) will be expanded to :nick!user@host if 'from'
+ * is a person, taking into account the rules for hidden/cloaked host.
+ * NOTE: Do not send this prepared buffer to remote clients or servers,
+ *       they do not want or need the expanded prefix. In that case, simply
+ *       use ircvsnprintf() directly.
+ */
+static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl)
+{
+	int len;
+
+	/* This expands the ":%s " part of the pattern
+	 * into ":nick!user@host ".
+	 * In case of a non-person (server) it doesn't do
+	 * anything since no expansion is needed.
+	 */
+	if (from && from->user && !strncmp(pattern, ":%s ", 4))
+	{
+		va_arg(vl, char *); /* eat first parameter */
+
+		*buf = ':';
+		strlcpy(buf+1, from->name, buflen-1);
+
+		if (IsUser(from))
+		{
+			char *username = from->user->username;
+			char *host = GetHost(from);
+
+			if (*username)
+			{
+				strlcat(buf, "!", buflen);
+				strlcat(buf, username, buflen);
+			}
+			if (*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
+	{
+		ircvsnprintf(buf, buflen, pattern, vl);
+	}
+
+	len = strlen(buf);
+	ADD_CRLF(buf, len);
+	return len;
+}
+
+/** Send a message to a client, expand the sender prefix.
+ * This is similar to sendto_one() except that it will expand the source part :%s
+ * to :nick!user@host if needed, while with sendto_one() it will be :nick.
+ * @param to		The client to send to
+ * @param mtags		Any message tags associated with this message (can be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	va_start(vl, pattern);
+	vsendto_prefix_one(to, from, mtags, pattern, vl);
+	va_end(vl);
+}
+
+/** Send a message to a single client, expand the sender prefix - va_list variant.
+ * This is similar to vsendto_one() except that it will expand the source part :%s
+ * to :nick!user@host if needed, while with sendto_one() it will be :nick.
+ * This function is also similar to sendto_prefix_one(), but this is the va_list
+ * variant.
+ * @param to		The client to send to
+ * @param mtags		Any message tags associated with this message (can be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl)
+{
+	const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
+
+	if (to && from && MyUser(to) && from->user)
+		vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf)-3, from, pattern, vl);
+	else
+		ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
+
+	if (BadPtr(mtags_str))
+	{
+		/* Simple message without message tags */
+		sendbufto_one(to, sendbuf, 0);
+	} else {
+		/* Message tags need to be prepended */
+		snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
+		sendbufto_one(to, sendbuf2, 0);
+	}
+}
+
+/** 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, MessageTag *mtags, Client *client, const char *umodes)
+{
+	Client *acptr;
+
+	list_for_each_entry(acptr, &server_list, special_node)
+	{
+		if (one && acptr == one->direction)
+			continue;
+		
+		sendto_one_nickcmd(acptr, mtags, client, umodes);
+	}
+}
+
+/** Introduce user to a server.
+ * @param server  Server to send to (locally connected!)
+ * @param client  Client to introduce
+ * @param umodes  User modes of client
+ */
+void sendto_one_nickcmd(Client *server, MessageTag *mtags, Client *client, const char *umodes)
+{
+	char *vhost;
+	char mtags_generated = 0;
+
+	if (!*umodes)
+		umodes = "+";
+
+	if (SupportVHP(server))
+	{
+		if (IsHidden(client))
+			vhost = client->user->virthost;
+		else
+			vhost = client->user->realhost;
+	}
+	else
+	{
+		if (IsHidden(client) && client->umodes & UMODE_SETHOST)
+			vhost = client->user->virthost;
+		else
+			vhost = "*";
+	}
+
+	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->uplink->id, client->name, client->hopcount,
+		(long long)client->lastnick,
+		client->user->username, client->user->realhost, client->id,
+		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
+ * has a % in their nick, which is a safe assumption since % is illegal.
+ */
+ 
+/** Send a server notice to a client.
+ * @param to		The client to send to
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...)
+{
+	static char realpattern[1024];
+	va_list vl;
+	char *name = *to->name ? to->name : "*";
+
+	ircsnprintf(realpattern, sizeof(realpattern), ":%s NOTICE %s :%s", me.name, name, pattern);
+
+	va_start(vl, pattern);
+	vsendto_one(to, NULL, realpattern, vl);
+	va_end(vl);
+}
+
+/** Send MultiLine list as a notice, one for each line.
+ * @param client	The client to send to
+ * @param m		The MultiLine list.
+ */
+void sendnotice_multiline(Client *client, MultiLine *m)
+{
+	for (; m; m = m->next)
+		sendnotice(client, "%s", m->line);
+}
+
+/** Send numeric message to a client - format to user specific needs.
+ * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
+ * followed by the pattern and format string you choose.
+ * @param to		The recipient
+ * @param mtags     NULL, or NULL-terminated array of message tags
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
+ */
+void sendtaggednumericfmt(Client *to, MessageTag *mtags, 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);
+	vsendto_one(to, mtags, realpattern, vl);
+	va_end(vl);
+}
+
+/** Send text numeric message to a client (RPL_TEXT).
+ * Because this generic output numeric is commonly used it got a special function for it.
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
+ */
+void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
+{
+	static char realpattern[1024];
+	va_list vl;
+
+	ircsnprintf(realpattern, sizeof(realpattern), ":%s %d %s :%s", me.name, RPL_TEXT, to->name, pattern);
+
+	va_start(vl, pattern);
+	vsendto_one(to, NULL, realpattern, vl);
+	va_end(vl);
+}
+
+/** 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
+ * Z-Line check via the codepath to banned_client().
+ * YOU SHOULD NEVER USE THIS FUNCTION.
+ * If you want to send raw data (without formatting) to a client
+ * then have a look at sendbufto_one() instead.
+ *
+ * Side-effects:
+ * Too many to list here. Only in the early accept code the
+ * "if's" and side-effects are under control.
+ *
+ * By the way, did I already mention that you SHOULD NOT USE THIS
+ * FUNCTION? ;)
+ */
+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);
+	va_end(vl);
+	(void)send(user->local->fd, sendbuf, sendlen, 0);
+}
+
+/** @} */
diff --git a/ircd/src/serv.c b/ircd/src/serv.c
@@ -0,0 +1,1264 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/serv.c
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *                      University of Oulu, Computing Center
+ *
+ *   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 Server-related functions
+ */
+
+/* s_serv.c 2.55 2/7/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
+
+#include "unrealircd.h"
+#include <ares.h>
+#ifndef _WIN32
+/* for uname(), is POSIX so should be OK... */
+#include <sys/utsname.h>
+#endif
+
+MODVAR int  max_connection_count = 1, max_client_count = 1;
+extern int do_garbage_collect;
+/* We need all these for cached MOTDs -- codemastr */
+extern char *buildid;
+MOTDFile opermotd;
+MOTDFile rules;
+MOTDFile motd;
+MOTDFile svsmotd;
+MOTDFile botmotd;
+MOTDFile smotd;
+
+/** Hash list of TKL entries */
+MODVAR TKL *tklines[TKLISTLEN];
+/** 2D hash list of TKL entries + IP address */
+MODVAR TKL *tklines_ip_hash[TKLIPHASHLEN1][TKLIPHASHLEN2];
+int MODVAR spamf_ugly_vchanoverride = 0;
+
+void read_motd(const char *filename, MOTDFile *motd);
+void do_read_motd(const char *filename, MOTDFile *themotd);
+
+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
+ * @param command	The command (eg: "NOTICE")
+ * @param server	This indicates parv[server] contains the destination
+ * @param parc		Parameter count (MAX 8!!)
+ * @param parv		Parameter values (MAX 8!!)
+ * @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, const char *command, int server, int parc, const char *parv[])
+{
+	Client *acptr;
+	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]))
+		return HUNTED_ISME;
+
+	acptr = find_client(parv[server], NULL);
+
+	/* find_client() may find a variety of clients. Only servers/persons please, no 'unknowns'. */
+	if (acptr && MyConnect(acptr) && !IsMe(acptr) && !IsUser(acptr) && !IsServer(acptr))
+		acptr = NULL;
+
+	if (!acptr)
+	{
+		sendnumeric(client, ERR_NOSUCHSERVER, parv[server]);
+		return HUNTED_NOSUCH;
+	}
+
+	if (IsMe(acptr) || MyUser(acptr))
+		return HUNTED_ISME;
+
+	/* Never send the message back from where it came from */
+	if (acptr->direction == client->direction)
+	{
+		sendnumeric(client, ERR_NOSUCHSERVER, parv[server]);
+		return HUNTED_NOSUCH;
+	}
+
+	/* This puts all parv[] arguments in 'buf'
+	 * Taken from concat_params() but this one is
+	 * with parv[server] magic replacement.
+	 */
+	*buf = '\0';
+	for (i = 1; i < parc; i++)
+	{
+		const char *param = parv[i];
+
+		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));
+	}
+
+	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)
+{
+	static char buf[1024];
+	struct utsname osinf;
+	char *p;
+
+	memset(&osinf, 0, sizeof(osinf));
+	if (uname(&osinf) != 0)
+		return "<unknown>";
+	snprintf(buf, sizeof(buf), "%s %s %s %s %s",
+		osinf.sysname,
+		osinf.nodename,
+		osinf.release,
+		osinf.version,
+		osinf.machine);
+	/* get rid of cr/lf */
+	for (p=buf; *p; p++)
+		if ((*p == '\n') || (*p == '\r'))
+		{
+			*p = '\0';
+			break;
+		}
+	return buf;
+}
+#endif
+
+/** Helper function to send version strings */
+void send_version(Client *client, int remote)
+{
+	int i;
+
+	for (i = 0; ISupportStrings[i]; i++)
+	{
+		if (remote)
+			sendnumeric(client, RPL_REMOTEISUPPORT, ISupportStrings[i]);
+		else
+			sendnumeric(client, RPL_ISUPPORT, ISupportStrings[i]);
+	}
+}
+
+/** VERSION command:
+ * Syntax: VERSION [server]
+ */
+CMD_FUNC(cmd_version)
+{
+	/* Only allow remote VERSIONs if registered -- Syzop */
+	if (!IsUser(client) && !IsServer(client))
+	{
+		send_version(client, 0);
+		return;
+	}
+
+	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"),
+			    extraflags ? extraflags : "",
+			    tainted ? "3" : "",
+			    (ValidatePermissionsForPath("server:info",client,NULL,NULL,NULL) ? MYOSNAME : "*"),
+			    UnrealProtocol);
+		if (ValidatePermissionsForPath("server:info",client,NULL,NULL,NULL))
+		{
+			sendnotice(client, "%s", SSLeay_version(SSLEAY_VERSION));
+			sendnotice(client, "libsodium %s", sodium_version_string());
+#ifdef USE_LIBCURL
+			sendnotice(client, "%s", curl_version());
+#endif
+			sendnotice(client, "c-ares %s", ares_version(NULL));
+			sendnotice(client, "%s", pcre2_version());
+#if JANSSON_VERSION_HEX >= 0x020D00
+			sendnotice(client, "jansson %s\n", jansson_version_str());
+#endif
+		}
+		if (MyUser(client))
+			send_version(client,0);
+		else
+			send_version(client,1);
+	}
+}
+
+char *num = NULL;
+
+/** Send all our PROTOCTL messages to remote server.
+ * We send multiple PROTOCTL's since 4.x. If this breaks your services
+ * because you fail to maintain PROTOCTL state, then fix them!
+ */
+void send_proto(Client *client, ConfigItem_link *aconf)
+{
+	ISupport *prefix = ISupportFind("PREFIX");
+
+	/* CAUTION: If adding a token to an existing PROTOCTL line below,
+	 *          then ensure that MAXPARA is not reached!
+	 */
+
+	/* First line */
+	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 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 */
+	sendto_one(client, NULL, "PROTOCTL NICKCHARS=%s CHANNELCHARS=%s",
+		charsys_get_current_languages(),
+		allowed_channelchars_valtostr(iConf.allowed_channelchars));
+}
+
+#ifndef IRCDTOTALVERSION
+#define IRCDTOTALVERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5 PATCH6 PATCH7 PATCH8 PATCH9
+#endif
+
+/** Special filter for remote commands */
+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]))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return 1; /* STOP */
+	}
+
+	/* same as above, but in case an old server forwards a request to us: we ignore it */
+	if (!MyUser(client) && !ValidatePermissionsForPath("server:remote",client,NULL,NULL,NULL))
+		return 1; /* STOP (return) */
+	
+	return 0; /* Continue */
+}
+
+/** Output for /INFO */
+char *unrealinfo[] =
+{
+	"This release was brought to you by the following people:",
+	"",
+	"Head coder:",
+	"* Bram Matthys (Syzop) <syzop@unrealircd.org>",
+	"",
+	"Coders:",
+	"* Krzysztof Beresztant (k4be) <k4be@unrealircd.org>",
+	"* Gottem <gottem@unrealircd.org>",
+	"* i <i@unrealircd.org>",
+	"",
+	"Past UnrealIRCd 4.x coders/contributors:",
+	"* Heero, binki, nenolod, ..",
+	"",
+	"Past UnrealIRCd 3.2.x coders/contributors:",
+	"* Stskeeps (ret. head coder / project leader)",
+	"* codemastr (ret. u3.2 head coder)",
+	"* aquanight, WolfSage, ..",
+	"* McSkaf, Zogg, NiQuiL, chasm, llthangel, nighthawk, ..",
+	NULL
+};
+
+/** Send /INFO output */
+void cmd_info_send(Client *client)
+{
+	char **text = unrealinfo;
+
+	sendnumericfmt(client, RPL_INFO, ":========== %s ==========", IRCDTOTALVERSION);
+
+	while (*text)
+		sendnumericfmt(client, RPL_INFO, ":| %s", *text++);
+
+	sendnumericfmt(client, RPL_INFO, ":|");
+	sendnumericfmt(client, RPL_INFO, ":|");
+	sendnumericfmt(client, RPL_INFO, ":| Credits - Type /CREDITS");
+	sendnumericfmt(client, RPL_INFO, ":|");
+	sendnumericfmt(client, RPL_INFO, ":| This is an UnrealIRCd-style server");
+	sendnumericfmt(client, RPL_INFO, ":| If you find any bugs, please report them at:");
+	sendnumericfmt(client, RPL_INFO, ":|  https://bugs.unrealircd.org/");
+	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->creationtime));
+	sendnumericfmt(client, RPL_INFO, ":ReleaseID (%s)", buildid);
+	sendnumeric(client, RPL_ENDOFINFO);
+}
+
+/** The INFO command.
+ * Syntax: INFO [server]
+ */
+CMD_FUNC(cmd_info)
+{
+	if (remotecmdfilter(client, parc, parv))
+		return;
+
+	if (hunt_server(client, recv_mtags, "INFO", 1, parc, parv) == HUNTED_ISME)
+		cmd_info_send(client);
+}
+
+/** LICENSE command
+ * Syntax: LICENSE [server]
+ */
+CMD_FUNC(cmd_license)
+{
+	char **text = gnulicense;
+
+	if (remotecmdfilter(client, parc, parv))
+		return;
+
+	if (hunt_server(client, recv_mtags, "LICENSE", 1, parc, parv) == HUNTED_ISME)
+	{
+		while (*text)
+			sendnumeric(client, RPL_INFO, *text++);
+
+		sendnumeric(client, RPL_INFO, "");
+		sendnumeric(client, RPL_ENDOFINFO);
+	}
+}
+
+/** CREDITS command
+ * Syntax: CREDITS [servername]
+ */
+CMD_FUNC(cmd_credits)
+{
+	char **text = unrealcredits;
+
+	if (remotecmdfilter(client, parc, parv))
+		return;
+
+	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->creationtime));
+		sendnumeric(client, RPL_ENDOFINFO);
+	}
+}
+
+/** 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;
+
+	*p = '\0';
+	*p++ = '[';
+	if (IsListening(client))
+	{
+		if (client->umodes & LISTENER_NORMAL)
+			*p++ = '*';
+		if (client->umodes & LISTENER_SERVERSONLY)
+			*p++ = 'S';
+		if (client->umodes & LISTENER_CLIENTSONLY)
+			*p++ = 'C';
+		if (client->umodes & LISTENER_TLS)
+			*p++ = 's';
+	}
+	else
+	{
+		if (IsTLS(client))
+			*p++ = 's';
+	}
+	*p++ = ']';
+	*p++ = '\0';
+	return buf;
+}
+
+/** ERROR command - used by servers to indicate errors.
+ * Syntax: ERROR :<reason>
+ */
+CMD_FUNC(cmd_error)
+{
+	const char *para;
+
+	if (!MyConnect(client))
+		return;
+
+	para = (parc > 1 && *parv[1] != '\0') ? parv[1] : "<>";
+
+	/* 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->server)
+		return;
+
+	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) */
+EVENT(save_tunefile)
+{
+	FILE *tunefile;
+
+	tunefile = fopen(conf_files->tune_file, "w");
+	if (!tunefile)
+	{
+		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");
+	fprintf(tunefile, "%d\n", irccounts.me_max);
+	fclose(tunefile);
+}
+
+/** Load the tunefile (such as: highest seen connection count) */
+void load_tunefile(void)
+{
+	FILE *tunefile;
+	char buf[1024];
+
+	tunefile = fopen(conf_files->tune_file, "r");
+	if (!tunefile)
+		return;
+	/* 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);
+}
+
+/** Rehash motd and rule files (motd_file/rules_file and all tld entries). */
+void rehash_motdrules()
+{
+ConfigItem_tld *tlds;
+
+	reread_motdsandrules();
+	for (tlds = conf_tld; tlds; tlds = tlds->next)
+	{
+		/* read_motd() accepts NULL in first arg and acts sanely */
+		read_motd(tlds->motd_file, &tlds->motd);
+		read_motd(tlds->rules_file, &tlds->rules);
+		read_motd(tlds->smotd_file, &tlds->smotd);
+		read_motd(tlds->opermotd_file, &tlds->opermotd);
+		read_motd(tlds->botmotd_file, &tlds->botmotd);
+	}
+}
+
+/** Rehash motd and rules (only the default files) */
+void reread_motdsandrules()
+{
+	read_motd(conf_files->motd_file, &motd);
+	read_motd(conf_files->rules_file, &rules);
+	read_motd(conf_files->smotd_file, &smotd);
+	read_motd(conf_files->botmotd_file, &botmotd);
+	read_motd(conf_files->opermotd_file, &opermotd);
+	read_motd(conf_files->svsmotd_file, &svsmotd);
+}
+
+extern void reinit_resolver(Client *client);
+
+/** REHASH command - reload configuration file on server(s).
+ * Syntax: see HELPOP REHASH
+ */
+CMD_FUNC(cmd_rehash)
+{
+	int x;
+
+	/* This is one of the (few) commands that cannot be handled
+	 * by labeled-response accurately in all circumstances.
+	 */
+	labeled_response_inhibit = 1;
+
+	if (!ValidatePermissionsForPath("server:rehash",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if ((parc < 3) || BadPtr(parv[2])) {
+		/* If the argument starts with a '-' (like -motd, -opermotd, etc) then it's
+		 * assumed not to be a server. -- Syzop
+		 */
+		if (parv[1] && (parv[1][0] == '-'))
+			x = HUNTED_ISME;
+		else
+			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, "REHASH", 1, parc, parv);
+		}
+	}
+	if (x != HUNTED_ISME)
+		return; /* Now forwarded or server didnt exist */
+
+	if (!MyConnect(client))
+	{
+#ifndef REMOTE_REHASH
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+#endif
+		if (parv[2] == NULL)
+		{
+			if (loop.rehashing)
+			{
+				sendnotice(client, "A rehash is already in progress");
+				return;
+			}
+			remote_rehash_client = client;
+			/* fallthrough... so we deal with this the same way as local rehashes */
+		}
+		parv[1] = parv[2];
+	} else {
+		/* Ok this is in an 'else' because it should be only executed for local clients,
+		 * but it's totally unrelated to the above ;).
+		 */
+		if (parv[1] && match_simple("-glob*", parv[1]))
+		{
+			/* /REHASH -global [options] */
+			Client *acptr;
+			
+			/* Shift parv's to the left */
+			parv[1] = parv[2];
+			parv[2] = NULL;
+			parc--;
+			if (parv[1] && *parv[1] != '-')
+			{
+				sendnotice(client, "You cannot specify a server name after /REHASH -global, for obvious reasons");
+				return;
+			}
+			/* Broadcast it in an inefficient, but backwards compatible way. */
+			list_for_each_entry(acptr, &global_server_list, client_node)
+			{
+				if (acptr == &me)
+					continue;
+				sendto_one(acptr, NULL, ":%s REHASH %s %s",
+					client->name,
+					acptr->name,
+					parv[1] ? parv[1] : "-all");
+			}
+			/* Don't return, continue, because we need to REHASH ourselves as well. */
+		}
+	}
+
+	if (!BadPtr(parv[1]) && strcasecmp(parv[1], "-all"))
+	{
+		if (*parv[1] == '-')
+		{
+			if (!strncasecmp("-gar", parv[1], 4))
+			{
+				loop.do_garbage_collect = 1;
+				RunHook(HOOKTYPE_REHASHFLAG, client, parv[1]);
+				return;
+			}
+			if (!strncasecmp("-dns", parv[1], 4))
+			{
+				reinit_resolver(client);
+				return;
+			}
+			if (match_simple("-ssl*", parv[1]) || match_simple("-tls*", parv[1]))
+			{
+				unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", client, "Reloading all TLS related data. [by: $client.details]");
+				reinit_tls();
+				return;
+			}
+			RunHook(HOOKTYPE_REHASHFLAG, client, parv[1]);
+			return;
+		}
+	}
+	else
+	{
+		if (loop.rehashing)
+		{
+			sendnotice(client, "ERROR: A rehash is already in progress");
+			return;
+		}
+		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);
+	request_rehash(client);
+}
+
+/** RESTART command - restart the server (discouraged command)
+ * parv[1] - password *OR* reason if no drpass { } block exists
+ * parv[2] - reason for restart (optional & only if drpass block exists)
+ */
+CMD_FUNC(cmd_restart)
+{
+	const char *reason = parv[1];
+	Client *acptr;
+
+	if (!MyUser(client))
+		return;
+
+	/* Check permissions */
+	if (!ValidatePermissionsForPath("server:restart",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	/* Syntax: /restart */
+	if (parc == 1)
+	{
+		if (conf_drpass)
+		{
+			sendnumeric(client, ERR_NEEDMOREPARAMS, "RESTART");
+			return;
+		}
+	} else
+	if (parc >= 2)
+	{
+		/* Syntax: /restart <pass> [reason] */
+		if (conf_drpass)
+		{
+			if (!Auth_Check(client, conf_drpass->restartauth, parv[1]))
+			{
+				sendnumeric(client, ERR_PASSWDMISMATCH);
+				return;
+			}
+			reason = parv[2];
+		}
+	}
+
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+	{
+		if (IsUser(acptr))
+			sendnotice(acptr, "Server Restarted by %s", client->name);
+		else if (IsServer(acptr))
+			sendto_one(acptr, NULL, ":%s ERROR :Restarted by %s: %s",
+			    me.name, get_client_name(client, TRUE), reason ? reason : "No reason");
+	}
+
+	server_reboot(reason ? reason : "No reason");
+}
+
+/** Send short message of the day to the client */
+void short_motd(Client *client)
+{
+	ConfigItem_tld *tld;
+	MOTDFile *themotd;
+	MOTDLine *motdline;
+	struct tm *tm;
+	char is_short;
+
+	tm = NULL;
+	is_short = 1;
+
+	tld = find_tld(client);
+
+	/*
+	* Try different sources of short MOTDs, falling back to the
+	* long MOTD.
+	*/
+	themotd = &smotd;
+	if (tld && tld->smotd.lines)
+		themotd = &tld->smotd;
+
+	/* try long MOTDs */
+	if (!themotd->lines)
+	{
+		is_short = 0;
+		if (tld && tld->motd.lines)
+			themotd = &tld->motd;
+		else
+			themotd = &motd;
+	}
+
+	if (!themotd->lines)
+	{
+		sendnumeric(client, ERR_NOMOTD);
+		return;
+	}
+	if (themotd->last_modified.tm_year)
+	{
+		tm = &themotd->last_modified; /* for readability */
+		sendnumeric(client, RPL_MOTDSTART, me.name);
+		sendnumericfmt(client, RPL_MOTD, ":- %d/%d/%d %d:%02d", tm->tm_mday, tm->tm_mon + 1,
+		               1900 + tm->tm_year, tm->tm_hour, tm->tm_min);
+	}
+	if (is_short)
+	{
+		sendnumeric(client, RPL_MOTD, "This is the short MOTD. To view the complete MOTD type /motd");
+		sendnumeric(client, RPL_MOTD, "");
+	}
+
+	motdline = NULL;
+	if (themotd)
+		motdline = themotd->lines;
+	while (motdline)
+	{
+		sendnumeric(client, RPL_MOTD, motdline->line);
+		motdline = motdline->next;
+	}
+
+	if (!is_short)
+	{
+		/* If the admin does not use a short MOTD then we append the SVSMOTD here...
+		 * If we did show a short motd then we don't append SVSMOTD,
+		 * since they want to keep it short.
+		 */
+		motdline = svsmotd.lines;
+		while (motdline)
+		{
+			sendnumeric(client, RPL_MOTD, motdline->line);
+			motdline = motdline->next;
+		}
+	}
+
+	sendnumeric(client, RPL_ENDOFMOTD);
+}
+
+/** Read motd-like file, used for rules/motd/botmotd/opermotd/etc.
+ * @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)
+{
+	FILE *fd;
+	struct tm *tm_tmp;
+	time_t modtime;
+
+	char line[512];
+	char *tmp;
+
+	MOTDLine *last, *temp;
+
+	free_motd(themotd);
+
+	if (!filename)
+		return;
+
+	fd = fopen(filename, "r");
+	if (!fd)
+		return;
+
+	/* record file modification time */
+	modtime = unreal_getfilemodtime(filename);
+	tm_tmp = localtime(&modtime);
+	memcpy(&themotd->last_modified, tm_tmp, sizeof(struct tm));
+
+	last = NULL;
+	while (fgets(line, sizeof(line), fd))
+	{
+		if ((tmp = strchr(line, '\n')))
+			*tmp = '\0';
+		if ((tmp = strchr(line, '\r')))
+			*tmp = '\0';
+		
+		if (strlen(line) > 510)
+			line[510] = '\0';
+
+		temp = safe_alloc(sizeof(MOTDLine));
+		safe_strdup(temp->line, line);
+
+		if (last)
+			last->next = temp;
+		else
+			/* handle the special case of the first line */
+			themotd->lines = temp;
+
+		last = temp;
+	}
+	/* the file could be zero bytes long? */
+	if (last)
+		last->next = NULL;
+
+	fclose(fd);
+	
+	return;
+}
+
+/** Free the contents of a MOTDFile structure.
+ * The MOTDFile structure itself should be statically
+ * allocated and deallocated. If the caller wants, it must
+ * manually free the MOTDFile structure itself.
+ */
+void free_motd(MOTDFile *themotd)
+{
+	MOTDLine *next, *motdline;
+
+	if (!themotd)
+		return;
+
+	for (motdline = themotd->lines; motdline; motdline = next)
+	{
+		next = motdline->next;
+		safe_free(motdline->line);
+		safe_free(motdline);
+	}
+
+	themotd->lines = NULL;
+	memset(&themotd->last_modified, '\0', sizeof(struct tm));
+}
+
+/** DIE command - terminate the server
+ * DIE [password]
+ */
+CMD_FUNC(cmd_die)
+{
+	Client *acptr;
+
+	if (!MyUser(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server:die",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	if (conf_drpass)	/* See if we have and DIE/RESTART password */
+	{
+		if (parc < 2)	/* And if so, require a password :) */
+		{
+			sendnumeric(client, ERR_NEEDMOREPARAMS, "DIE");
+			return;
+		}
+		if (!Auth_Check(client, conf_drpass->dieauth, parv[1]))
+		{
+			sendnumeric(client, ERR_PASSWDMISMATCH);
+			return;
+		}
+	}
+
+	/* Let the +s know what is going on */
+	unreal_log(ULOG_INFO, "main", "UNREALIRCD_STOP", client,
+	           "Terminating server by request of $client.details");
+
+	list_for_each_entry(acptr, &lclient_list, lclient_node)
+	{
+		if (IsUser(acptr))
+			sendnotice(acptr, "Server Terminated by %s", 
+				client->name);
+		else if (IsServer(acptr))
+			sendto_one(acptr, NULL, ":%s ERROR :Terminated by %s",
+			    me.name, get_client_name(client, TRUE));
+	}
+
+	s_die();
+}
+
+/** Server list (network) of pending connections */
+PendingNet *pendingnet = NULL;
+
+/** Add server list (network) from 'client' connection */
+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;
+
+	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
+	{
+		if (!*name)
+			continue;
+		
+		srv = safe_alloc(sizeof(PendingServer));
+		strlcpy(srv->sid, name, sizeof(srv->sid));
+		AddListItem(srv, net->servers);
+	}
+	
+	AddListItem(net, pendingnet);
+}
+
+/** Free server list (network) previously added by 'client' */
+void free_pending_net(Client *client)
+{
+	PendingNet *net, *net_next;
+	PendingServer *srv, *srv_next;
+	
+	for (net = pendingnet; net; net = net_next)
+	{
+		net_next = net->next;
+		if (net->client == client)
+		{
+			for (srv = net->servers; srv; srv = srv_next)
+			{
+				srv_next = srv->next;
+				safe_free(srv);
+			}
+			DelListItem(net, pendingnet);
+			safe_free(net);
+			/* Don't break, there can be multiple objects */
+		}
+	}
+}
+
+/** Find SID in any server list (network) that is pending, except 'exempt' */
+PendingNet *find_pending_net_by_sid_butone(const char *sid, Client *exempt)
+{
+	PendingNet *net;
+	PendingServer *srv;
+
+	if (BadPtr(sid))
+		return NULL;
+
+	for (net = pendingnet; net; net = net->next)
+	{
+		if (net->client == exempt)
+			continue;
+		for (srv = net->servers; srv; srv = srv->next)
+			if (!strcmp(srv->sid, sid))
+				return net;
+	}
+	return NULL;
+}
+
+/** Search the pending connections list for any identical sids */
+Client *find_pending_net_duplicates(Client *cptr, Client **srv, char **sid)
+{
+	PendingNet *net, *other;
+	PendingServer *s;
+
+	*srv = NULL;
+	*sid = NULL;
+	
+	for (net = pendingnet; net; net = net->next)
+	{
+		if (net->client != cptr)
+			continue;
+		/* Ok, found myself */
+		for (s = net->servers; s; s = s->next)
+		{
+			char *curr_sid = s->sid;
+			other = find_pending_net_by_sid_butone(curr_sid, cptr);
+			if (other)
+			{
+				*srv = net->client;
+				*sid = s->sid;
+				return other->client; /* Found another (pending) server with identical numeric */
+			}
+		}
+	}
+	
+	return NULL;
+}
+
+/** Like find_pending_net_duplicates() but the other way around? Eh.. */
+Client *find_non_pending_net_duplicates(Client *client)
+{
+	PendingNet *net;
+	PendingServer *s;
+	Client *acptr;
+
+	for (net = pendingnet; net; net = net->next)
+	{
+		if (net->client != client)
+			continue;
+		/* Ok, found myself */
+		for (s = net->servers; s; s = s->next)
+		{
+			acptr = find_server(s->sid, NULL);
+			if (acptr)
+				return acptr; /* Found another (fully CONNECTED) server with identical numeric */
+		}
+	}
+	
+	return NULL;
+}
+
+/** Parse CHANMODES= in PROTOCTL */
+void parse_chanmodes_protoctl(Client *client, const char *str)
+{
+	char *modes, *p;
+	char copy[256];
+
+	strlcpy(copy, str, sizeof(copy));
+
+	modes = strtoken(&p, copy, ",");
+	if (modes)
+	{
+		safe_strdup(client->server->features.chanmodes[0], modes);
+		modes = strtoken(&p, NULL, ",");
+		if (modes)
+		{
+			safe_strdup(client->server->features.chanmodes[1], modes);
+			modes = strtoken(&p, NULL, ",");
+			if (modes)
+			{
+				safe_strdup(client->server->features.chanmodes[2], modes);
+				modes = strtoken(&p, NULL, ",");
+				if (modes)
+				{
+					safe_strdup(client->server->features.chanmodes[3], modes);
+				}
+			}
+		}
+	}
+}
+
+static char previous_langsinuse[512];
+static int previous_langsinuse_ready = 0;
+
+/** Check the nick character system (set::allowed-nickchars) for changes.
+ * If there are changes, then we broadcast the new PROTOCTL NICKCHARS= to all servers.
+ */
+void charsys_check_for_changes(void)
+{
+	const char *langsinuse = charsys_get_current_languages();
+	/* already called by charsys_finish() */
+	safe_strdup(me.server->features.nickchars, langsinuse);
+
+	if (!previous_langsinuse_ready)
+	{
+		previous_langsinuse_ready = 1;
+		strlcpy(previous_langsinuse, langsinuse, sizeof(previous_langsinuse));
+		return; /* not booted yet. then we are done here. */
+	}
+
+	if (strcmp(langsinuse, previous_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);
+	}
+
+	strlcpy(previous_langsinuse, langsinuse, sizeof(previous_langsinuse));
+}
+
+/** Check if supplied server name is valid, that is: does not contain forbidden characters etc */
+int valid_server_name(const char *name)
+{
+	const char *p;
+
+	if (!valid_host(name, 0))
+		return 0; /* invalid hostname */
+
+	if (!strchr(name, '.'))
+		return 0; /* no dot */
+
+	return 1;
+}
+
+/** Check if the supplied name is a valid SID, as in: syntax. */
+int valid_sid(const char *name)
+{
+	if (strlen(name) != 3)
+		return 0;
+	if (!isdigit(*name))
+		return 0;
+	if (!isdigit(name[1]) && !isupper(name[1]))
+		return 0;
+	if (!isdigit(name[2]) && !isupper(name[2]))
+		return 0;
+	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)
+{
+	memset(tklines, 0, sizeof(tklines));
+	memset(tklines_ip_hash, 0, sizeof(tklines_ip_hash));
+}
+
+/** 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 *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,
+					   client->server->conf->outgoing.file
+					   ? "Unable to link with server $client [$link_block.file]: $tls_error_string"
+					   : "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,
+					   client->server->conf->outgoing.file
+					   ? "Unable to link with server $client [$link_block.file]: $socket_error"
+					   : "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)
+{
+	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");
+}
+
+/** 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 (!aconf)
+	{
+		/* Should be impossible. */
+		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_HANDSHAKE", client,
+		           "Lost configuration while connecting to $client.details");
+		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
+	 */
+}
diff --git a/ircd/src/socket.c b/ircd/src/socket.c
@@ -0,0 +1,1390 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/socket.c
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *                      University of Oulu, Computing Center
+ *
+ *   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 Socket functions such as reading, writing, connecting.
+ *
+ * The actual data parsing functions (for incoming data) are in
+ * src/parse.c.
+ */
+
+#include "unrealircd.h"
+#include "dns.h"
+
+int OpenFiles = 0;    /* GLOBAL - number of files currently open */
+int readcalls = 0;
+
+void completed_connection(int, int, void *);
+void set_sock_opts(int, Client *, SocketType);
+void set_ipv6_opts(int);
+void close_listener(ConfigItem_listen *listener);
+static char readbuf[BUFSIZE];
+char zlinebuf[BUFSIZE];
+extern char *version;
+MODVAR time_t last_allinuse = 0;
+
+void start_of_normal_client_handshake(Client *client);
+void proceed_normal_client_handshake(Client *client, struct hostent *he);
+
+/** Close all connections - only used when we terminate the server (eg: /DIE or SIGTERM) */
+void close_connections(void)
+{
+	Client *client;
+
+	list_for_each_entry(client, &lclient_list, lclient_node)
+	{
+		if (client->local->fd >= 0)
+		{
+			fd_close(client->local->fd);
+			client->local->fd = -2;
+		}
+	}
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (client->local->fd >= 0)
+		{
+			fd_close(client->local->fd);
+			client->local->fd = -2;
+		}
+
+		if (client->local->authfd >= 0)
+		{
+			fd_close(client->local->authfd);
+			client->local->fd = -1;
+		}
+	}
+
+	list_for_each_entry(client, &control_list, lclient_node)
+	{
+		if (client->local->fd >= 0)
+		{
+			fd_close(client->local->fd);
+			client->local->fd = -2;
+		}
+	}
+
+	close_unbound_listeners();
+
+	OpenFiles = 0;
+
+#ifdef _WIN32
+	WSACleanup();
+#endif
+}
+
+/** Accept an incoming connection.
+ * @param listener	The listen { } block configuration data.
+ * @returns 1 if the connection was accepted (even if it was rejected),
+ * 0 if there is no more work to do (accept returned an error).
+ */
+static int listener_accept_wrapper(ConfigItem_listen *listener)
+{
+	int cli_fd;
+
+	if ((cli_fd = fd_accept(listener->fd)) < 0)
+	{
+		if ((ERRNO != P_EWOULDBLOCK) && (ERRNO != P_ECONNABORTED))
+		{
+			/* Trouble! accept() returns a strange error.
+			 * Previously in such a case we would just log/broadcast the error and return,
+			 * causing this message to be triggered at a rate of XYZ per second (100% CPU).
+			 * Now we close & re-start the listener.
+			 * Of course the underlying cause of this issue should be investigated, as this
+			 * is very much a workaround.
+			 */
+			if (listener->file)
+			{
+				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on file $file: $socket_error",
+					   log_data_socket_error(listener->fd),
+					   log_data_string("file", listener->file));
+			} else {
+				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error",
+					   log_data_socket_error(listener->fd),
+					   log_data_string("listen_ip", listener->ip),
+					   log_data_integer("listen_port", listener->port));
+			}
+			close_listener(listener);
+			start_listeners();
+		}
+		return 0;
+	}
+
+	ircstats.is_ac++;
+
+	set_sock_opts(cli_fd, NULL, listener->socket_type);
+
+	/* Allow connections to the control socket, even if maxclients is reached */
+	if (listener->options & LISTENER_CONTROL)
+	{
+		/* ... but not unlimited ;) */
+		if ((++OpenFiles >= maxclients+(CLIENTS_RESERVE/2)) || (cli_fd >= maxclients+(CLIENTS_RESERVE/2)))
+		{
+			ircstats.is_ref++;
+			if (last_allinuse < TStime() - 15)
+			{
+				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
+					   log_data_string("file", listener->file));
+				last_allinuse = TStime();
+			}
+			fd_close(cli_fd);
+			--OpenFiles;
+			return 1;
+		}
+	} else
+	{
+		if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients))
+		{
+			ircstats.is_ref++;
+			if (last_allinuse < TStime() - 15)
+			{
+				if (listener->file)
+				{
+					unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
+						   log_data_string("file", listener->file));
+				} else {
+					unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use",
+						   log_data_string("listen_ip", listener->ip),
+						   log_data_integer("listen_port", listener->port));
+				}
+				last_allinuse = TStime();
+			}
+
+			(void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0);
+
+			fd_close(cli_fd);
+			--OpenFiles;
+			return 1;
+		}
+	}
+
+	/* add_connection() may fail. we just don't care. */
+	add_connection(listener, cli_fd);
+	return 1;
+}
+
+/** Accept an incoming connection.
+ * @param listener_fd	The file descriptor of a listen() socket.
+ * @param data		The listen { } block configuration data.
+ */
+static void listener_accept(int listener_fd, int revents, void *data)
+{
+	int i;
+
+	/* Accept clients, but only up to a maximum in each run,
+	 * as to allow some CPU available to existing clients.
+	 * Better refuse or lag a few new clients than become
+	 * unresponse to existing clients.
+	 */
+	for (i=0; i < 100; i++)
+		if (!listener_accept_wrapper((ConfigItem_listen *)data))
+			break;
+}
+
+int unreal_listen_inet(ConfigItem_listen *listener)
+{
+	const char *ip = listener->ip;
+	int port = listener->port;
+
+	if (BadPtr(ip))
+		ip = "*";
+
+	if (*ip == '*')
+	{
+		if (listener->socket_type == SOCKET_TYPE_IPV6)
+			ip = "::";
+		else
+			ip = "0.0.0.0";
+	}
+
+	/* At first, open a new socket */
+	if (listener->fd >= 0)
+		abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
+
+	if (port == 0)
+		abort(); /* Impossible as well, right? */
+
+	listener->fd = fd_socket(listener->socket_type == SOCKET_TYPE_IPV6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket");
+	if (listener->fd < 0)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
+		           "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)
+	{
+		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;
+		return -1;
+	}
+
+	set_sock_opts(listener->fd, NULL, listener->socket_type);
+
+	if (!unreal_bind(listener->fd, ip, port, listener->socket_type))
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
+		           "Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
+		           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;
+		return -1;
+	}
+
+	if (listen(listener->fd, LISTEN_SIZE) < 0)
+	{
+		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;
+		return -1;
+	}
+
+#ifdef TCP_DEFER_ACCEPT
+	if (listener->options & LISTENER_DEFER_ACCEPT)
+	{
+		int yes = 1;
+
+		(void)setsockopt(listener->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &yes, sizeof(int));
+	}
+#endif
+
+#ifdef SO_ACCEPTFILTER
+	if (listener->options & LISTENER_DEFER_ACCEPT)
+	{
+		struct accept_filter_arg afa;
+
+		memset(&afa, '\0', sizeof afa);
+		strlcpy(afa.af_name, "dataready", sizeof afa.af_name);
+		(void)setsockopt(listener->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof afa);
+	}
+#endif
+
+	fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
+
+	return 0;
+}
+
+int unreal_listen_unix(ConfigItem_listen *listener)
+{
+	if (listener->socket_type != SOCKET_TYPE_UNIX)
+		abort(); /* "impossible" */
+
+	/* At first, open a new socket */
+	if (listener->fd >= 0)
+		abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
+
+	listener->fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Listener socket (UNIX)");
+	if (listener->fd < 0)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
+		           "Could not create UNIX domain socket for $file: $socket_error",
+			   log_data_socket_error(-1),
+			   log_data_string("file", listener->file));
+		return -1;
+	}
+
+	if (++OpenFiles >= maxclients)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_ERROR_MAXCLIENTS", NULL,
+		           "Could not create UNIX domain socket for $file: all connections in use",
+		           log_data_string("file", listener->file));
+		fd_close(listener->fd);
+		listener->fd = -1;
+		--OpenFiles;
+		return -1;
+	}
+
+	set_sock_opts(listener->fd, NULL, listener->socket_type);
+
+	if (!unreal_bind(listener->fd, listener->file, listener->mode, SOCKET_TYPE_UNIX))
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
+		           "Could not listen on UNIX domain socket $file: $socket_error",
+		           log_data_socket_error(listener->fd),
+		           log_data_string("file", listener->file));
+		fd_close(listener->fd);
+		listener->fd = -1;
+		--OpenFiles;
+		return -1;
+	}
+
+	if (listen(listener->fd, LISTEN_SIZE) < 0)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_LISTEN_ERROR", NULL,
+		           "Could not listen on UNIX domain socket $file: $socket_error",
+		           log_data_socket_error(listener->fd),
+		           log_data_string("file", listener->file));
+		fd_close(listener->fd);
+		listener->fd = -1;
+		--OpenFiles;
+		return -1;
+	}
+
+	fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
+
+	return 0;
+}
+
+/** Create a listener port.
+ * @param listener	The listen { } block configuration
+ * @returns 0 on success and <0 on error. Yeah, confusing.
+ */
+int unreal_listen(ConfigItem_listen *listener)
+{
+	if ((listener->socket_type == SOCKET_TYPE_IPV4) || (listener->socket_type == SOCKET_TYPE_IPV6))
+		return unreal_listen_inet(listener);
+	return unreal_listen_unix(listener);
+}
+
+/** Activate a listen { } block */
+int add_listener(ConfigItem_listen *listener)
+{
+	if (unreal_listen(listener))
+	{
+		/* Error is already handled upstream */
+		listener->fd = -2;
+	}
+
+	if (listener->fd >= 0)
+	{
+		listener->options |= LISTENER_BOUND;
+		return 1;
+	}
+	else
+	{
+		listener->fd = -1;
+		return -1;
+	}
+}
+
+/** Close the listener socket, but do not free it (yet).
+ * This will only close the socket so no new clients are accepted.
+ * It also marks the listener as no longer "bound".
+ * Once the last client exits the listener will actually be freed.
+ * @param listener	The listen { } block.
+ */
+void close_listener(ConfigItem_listen *listener)
+{
+	if (listener->fd >= 0)
+	{
+		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 TLS context, since it is only
+	 * used for new connections, which we no longer accept.
+	 */
+	if (listener->ssl_ctx)
+	{
+		SSL_CTX_free(listener->ssl_ctx);
+		listener->ssl_ctx = NULL;
+	}
+}
+
+/** Close all listeners that were pending to be closed. */
+void close_unbound_listeners(void)
+{
+	ConfigItem_listen *aconf, *aconf_next;
+
+	/* close all 'extra' listening ports we have */
+	for (aconf = conf_listen; aconf != NULL; aconf = aconf_next)
+	{
+		aconf_next = aconf->next;
+		if (aconf->flag.temporary)
+			close_listener(aconf);
+	}
+}
+
+int maxclients = 1024 - CLIENTS_RESERVE;
+
+/** Check the maximum number of sockets (users) that we can handle - called on startup.
+ */
+void check_user_limit(void)
+{
+#ifdef RLIMIT_FD_MAX
+	struct rlimit limit;
+	long m;
+
+	if (!getrlimit(RLIMIT_FD_MAX, &limit))
+	{
+		if (limit.rlim_max < MAXCONNECTIONS)
+			m = limit.rlim_max;
+		else
+			m = MAXCONNECTIONS;
+
+		/* Adjust soft limit (if necessary, which is often the case) */
+		if (m != limit.rlim_cur)
+		{
+			limit.rlim_cur = limit.rlim_max = m;
+			if (setrlimit(RLIMIT_FD_MAX, &limit) == -1)
+			{
+				/* HACK: if it's mac os X then don't error... */
+#ifndef OSXTIGER
+				fprintf(stderr, "error setting maximum number of open files to %ld\n",
+					(long)limit.rlim_cur);
+				exit(-1);
+#endif // OSXTIGER
+			}
+		}
+		/* This can only happen if it is due to resource limits (./Config already rejects <100) */
+		if (m < 100)
+		{
+			fprintf(stderr, "\nERROR: Your OS has a limit placed on this account.\n"
+			                "This machine only allows UnrealIRCd to handle a maximum of %ld open connections/files, which is VERY LOW.\n"
+			                "Please check with your system administrator to bump this limit.\n"
+			                "The recommended ulimit -n setting is at least 1024 and "
+			                "preferably 4096.\n"
+			                "Note that this error is often seen on small web shells that are not meant for running IRC servers.\n",
+			                m);
+			exit(-1);
+		}
+		maxclients = m - CLIENTS_RESERVE;
+	}
+#endif // RLIMIT_FD_MAX
+
+#ifndef _WIN32
+#ifdef BACKEND_SELECT
+	if (MAXCONNECTIONS > FD_SETSIZE)
+	{
+		fprintf(stderr, "MAXCONNECTIONS (%d) is higher than FD_SETSIZE (%d)\n", MAXCONNECTIONS, FD_SETSIZE);
+		fprintf(stderr, "You should not see this error on Linux or FreeBSD\n");
+		fprintf(stderr, "You might need to recompile the IRCd and answer a lower value to the MAXCONNECTIONS question in ./Config\n");
+		exit(-1);
+	}
+#endif
+#endif
+#ifdef _WIN32
+	maxclients = MAXCONNECTIONS - CLIENTS_RESERVE;
+#endif
+}
+
+/** Initialize some systems - called on startup */
+void init_sys(void)
+{
+#ifndef _WIN32
+	/* Create new session / set process group */
+	(void)setsid();
+#endif
+
+	init_resolver(1);
+	return;
+}
+
+/** Replace a file descriptor (*NIX only).
+ * See close_std_descriptors() as for why.
+ * @param oldfd: the old FD to close and re-use
+ * @param name: descriptive string of the old fd, eg: "stdin".
+ * @param mode: an open() mode, such as O_WRONLY.
+ */
+void replacefd(int oldfd, char *name, int mode)
+{
+#ifndef _WIN32
+	int newfd = open("/dev/null", mode);
+	if (newfd < 0)
+	{
+		fprintf(stderr, "Warning: could not open /dev/null\n");
+		return;
+	}
+	if (oldfd < 0)
+	{
+		fprintf(stderr, "Warning: could not replace %s (invalid fd)\n", name);
+		return;
+	}
+	if (dup2(newfd, oldfd) < 0)
+	{
+		fprintf(stderr, "Warning: could not replace %s (dup2 error)\n", name);
+		return;
+	}
+#endif
+}
+
+/** Mass close standard file descriptors (stdin, stdout, stderr).
+ * We used to really just close them here (or in init_sys() actually),
+ * making the fd's available for other purposes such as internet sockets.
+ * For safety we now dup2() them to /dev/null. This in case someone
+ * accidentally does a fprintf(stderr,..) somewhere in the code or some
+ * library outputs error messages to stderr (such as libc with heap
+ * errors). We don't want any IRC client to receive such a thing!
+ */
+void close_std_descriptors(void)
+{
+#if !defined(_WIN32) && !defined(NOCLOSEFD)
+	replacefd(fileno(stdin), "stdin", O_RDONLY);
+	replacefd(fileno(stdout), "stdout", O_WRONLY);
+	replacefd(fileno(stderr), "stderr", O_WRONLY);
+#endif
+}
+
+/** Do an ident lookup if necessary.
+ * @param client	The incoming client
+ */
+void consider_ident_lookup(Client *client)
+{
+	char buf[BUFSIZE];
+
+	/* If ident checking is disabled or it's an outgoing connect, then no ident check */
+	if ((IDENT_CHECK == 0) || (client->server && IsHandshake(client)) || IsUnixSocket(client))
+	{
+		ClearIdentLookupSent(client);
+		ClearIdentLookup(client);
+		return;
+	}
+	RunHook(HOOKTYPE_IDENT_LOOKUP, client);
+
+	return;
+}
+
+
+/** Called when TCP/IP connection is established (outgoing server connect) */
+void completed_connection(int fd, int revents, void *data)
+{
+	Client *client = data;
+	ConfigItem_link *aconf = client->server ? client->server->conf : NULL;
+
+	if (IsHandshake(client))
+	{
+		/* Due to delayed unreal_tls_connect call */
+		start_server_handshake(client);
+		fd_setselect(fd, FD_SELECT_READ, read_packet, client);
+		return;
+	}
+
+	SetHandshake(client);
+
+	if (!aconf)
+	{
+		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_CONNECT", client,
+		           "Lost configuration while connecting to $client.details");
+		return;
+	}
+
+	if (!client->local->ssl && !(aconf->outgoing.options & CONNECT_INSECURE))
+	{
+		sendto_one(client, NULL, "STARTTLS");
+	} else
+	{
+		start_server_handshake(client);
+	}
+
+	if (!IsDeadSocket(client))
+		consider_ident_lookup(client);
+
+	fd_setselect(fd, FD_SELECT_READ, read_packet, client);
+}
+
+/** Close the physical connection.
+ * @param client	The client connection to close (LOCAL!)
+ */
+void close_connection(Client *client)
+{
+	RunHook(HOOKTYPE_CLOSE_CONNECTION, client);
+	/* This function must make MyConnect(client) == FALSE,
+	 * and set client->direction == NULL.
+	 */
+	if (IsServer(client))
+	{
+		ircstats.is_sv++;
+		ircstats.is_sti += TStime() - client->local->creationtime;
+	}
+	else if (IsUser(client))
+	{
+		ircstats.is_cl++;
+		ircstats.is_cti += TStime() - client->local->creationtime;
+	}
+	else
+		ircstats.is_ni++;
+
+	/*
+	 * remove outstanding DNS queries.
+	 */
+	unrealdns_delreq_bycptr(client);
+
+	if (client->local->authfd >= 0)
+	{
+		fd_close(client->local->authfd);
+		client->local->authfd = -1;
+		--OpenFiles;
+	}
+
+	if (client->local->fd >= 0)
+	{
+		send_queued(client);
+		if (IsTLS(client) && client->local->ssl) {
+			SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
+			SSL_smart_shutdown(client->local->ssl);
+			SSL_free(client->local->ssl);
+			client->local->ssl = NULL;
+		}
+		fd_close(client->local->fd);
+		client->local->fd = -2;
+		--OpenFiles;
+		DBufClear(&client->local->sendQ);
+		DBufClear(&client->local->recvQ);
+	}
+
+	client->direction = NULL;
+}
+
+/** Set IPv6 socket options, if possible. */
+void set_ipv6_opts(int fd)
+{
+#if defined(IPV6_V6ONLY)
+	int opt = 1;
+	(void)setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&opt, sizeof(opt));
+#endif
+}
+
+/** This sets the *OS* socket buffers.
+ * This shouldn't be needed anymore, but I've left the function here.
+ */
+void set_socket_buffers(int fd, int rcvbuf, int sndbuf)
+{
+	int opt;
+
+	opt = rcvbuf;
+	setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&opt, sizeof(opt));
+
+	opt = sndbuf;
+	setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&opt, sizeof(opt));
+}
+
+/** Set the appropriate socket options */
+void set_sock_opts(int fd, Client *client, SocketType socket_type)
+{
+	int opt;
+
+	if (socket_type == SOCKET_TYPE_IPV6)
+		set_ipv6_opts(fd);
+
+	if ((socket_type == SOCKET_TYPE_IPV4) || (socket_type == SOCKET_TYPE_IPV6))
+	{
+#ifdef SO_REUSEADDR
+		opt = 1;
+		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0)
+		{
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not setsockopt(SO_REUSEADDR): $socket_error",
+				   log_data_socket_error(-1));
+		}
+#endif
+
+#if defined(SO_USELOOPBACK) && !defined(_WIN32)
+		opt = 1;
+		if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (void *)&opt, sizeof(opt)) < 0)
+		{
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not setsockopt(SO_USELOOPBACK): $socket_error",
+				   log_data_socket_error(-1));
+		}
+#endif
+
+	}
+
+	/* The following code applies to all socket types: IPv4, IPv6, UNIX domain sockets */
+
+	/* Set to non blocking: */
+#if !defined(_WIN32)
+	if ((opt = fcntl(fd, F_GETFL, 0)) == -1)
+	{
+		if (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)
+		{
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not get socket options (F_SETFL): $socket_error",
+				   log_data_socket_error(-1));
+		}
+	}
+#else
+	opt = 1;
+	if (ioctlsocket(fd, FIONBIO, &opt) < 0)
+	{
+		if (client)
+		{
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not ioctlsocket FIONBIO: $socket_error",
+				   log_data_socket_error(-1));
+		}
+	}
+#endif
+}
+
+/** Returns 1 if using a loopback IP (127.0.0.1) or
+ * using a local IP number on the same machine (effectively the same;
+ * no network traffic travels outside this machine).
+ * @param ip	The IP address to check
+ * @returns 1 if loopback, 0 if not.
+ */
+int is_loopback_ip(char *ip)
+{
+	ConfigItem_listen *e;
+
+	if (!strcmp(ip, "127.0.0.1") || !strcmp(ip, "0:0:0:0:0:0:0:1") || !strcmp(ip, "0:0:0:0:0:ffff:127.0.0.1"))
+		return 1;
+
+	for (e = conf_listen; e; e = e->next)
+	{
+		if ((e->options & LISTENER_BOUND) && e->ip && !strcmp(ip, e->ip))
+			return 1;
+	}
+	return 0;
+}
+
+/** Retrieve the remote IP address and port of a socket.
+ * @param client	Client to check
+ * @param fd		File descriptor
+ * @param port		Remote port (will be written)
+ * @returns The IP address
+ */
+const char *getpeerip(Client *client, int fd, int *port)
+{
+	static char ret[HOSTLEN+1];
+
+	if (IsIPV6(client))
+	{
+		struct sockaddr_in6 addr;
+		int len = sizeof(addr);
+
+		if (getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
+			return NULL;
+		*port = ntohs(addr.sin6_port);
+		return inetntop(AF_INET6, &addr.sin6_addr.s6_addr, ret, sizeof(ret));
+	} else
+	{
+		struct sockaddr_in addr;
+		int len = sizeof(addr);
+
+		if (getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
+			return NULL;
+		*port = ntohs(addr.sin_port);
+		return inetntop(AF_INET, &addr.sin_addr.s_addr, ret, sizeof(ret));
+	}
+}
+
+/** Process the incoming connection which has just been accepted.
+ * This creates a client structure for the user.
+ * The sockhost field is initialized with the ip# of the host.
+ * The client is added to the linked list of clients but isnt added to any
+ * hash tables yuet since it doesnt have a name.
+ * @param listener	The listen { } block on which the client was accepted.
+ * @param fd		The file descriptor of the client
+ * @returns The new client, or NULL in case of trouble.
+ * @note  When NULL is returned, the client at socket 'fd' will be
+ *        closed by this function and OpenFiles is adjusted appropriately.
+ */
+Client *add_connection(ConfigItem_listen *listener, int fd)
+{
+	Client *client;
+	const char *ip;
+	int port = 0;
+	Hook *h;
+
+	client = make_client(NULL, &me);
+	client->local->socket_type = listener->socket_type;
+	client->local->listener = listener;
+	client->local->listener->clients++;
+
+	if (listener->socket_type == SOCKET_TYPE_UNIX)
+		ip = listener->spoof_ip ? listener->spoof_ip : "127.0.0.1";
+	else
+		ip = getpeerip(client, fd, &port);
+
+	if (!ip)
+	{
+		/* On Linux 2.4 and FreeBSD the socket may just have been disconnected
+		 * so it's not a serious error and can happen quite frequently -- Syzop
+		 */
+		if (ERRNO != P_ENOTCONN)
+		{
+			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++;
+			client->local->fd = -2;
+			if (!list_empty(&client->client_node))
+				list_del(&client->client_node);
+			if (!list_empty(&client->lclient_node))
+				list_del(&client->lclient_node);
+			free_client(client);
+			fd_close(fd);
+			--OpenFiles;
+			return NULL;
+	}
+
+	/* Fill in sockhost & ip ASAP */
+	set_sockhost(client, ip);
+	safe_strdup(client->ip, ip);
+	client->local->port = port;
+	client->local->fd = fd;
+
+	/* Tag loopback connections */
+	if (is_loopback_ip(client->ip))
+	{
+		ircstats.is_loc++;
+		SetLocalhost(client);
+	}
+
+	add_client_to_list(client);
+	irccounts.unknown++;
+	client->status = CLIENT_STATUS_UNKNOWN;
+	list_add(&client->lclient_node, &unknown_list);
+
+	for (h = Hooks[HOOKTYPE_ACCEPT]; h; h = h->next)
+	{
+		int value = (*(h->func.intfunc))(client);
+		if (value == HOOK_DENY)
+		{
+			irccounts.unknown--;
+			goto refuse_client;
+		}
+		if (value != HOOK_CONTINUE)
+			break;
+	}
+
+	if ((listener->options & LISTENER_TLS) && ctx_server)
+	{
+		SSL_CTX *ctx = listener->ssl_ctx ? listener->ssl_ctx : ctx_server;
+
+		if (ctx)
+		{
+			SetTLSAcceptHandshake(client);
+			if ((client->local->ssl = SSL_new(ctx)) == NULL)
+			{
+				irccounts.unknown--;
+				goto refuse_client;
+			}
+			SetTLS(client);
+			SSL_set_fd(client->local->ssl, fd);
+			SSL_set_nonblocking(client->local->ssl);
+			SSL_set_ex_data(client->local->ssl, tls_client_index, client);
+			if (!unreal_tls_accept(client, fd))
+			{
+				SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
+				SSL_smart_shutdown(client->local->ssl);
+				SSL_free(client->local->ssl);
+				irccounts.unknown--;
+				goto refuse_client;
+			}
+		}
+	} else
+	{
+		listener->start_handshake(client);
+	}
+	return client;
+}
+
+/** 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 TLS connections this is called after the TLS handshake is completed.
+ */
+void start_of_normal_client_handshake(Client *client)
+{
+	struct hostent *he;
+
+	client->status = CLIENT_STATUS_UNKNOWN; /* reset, to be sure (TLS handshake has ended) */
+
+	RunHook(HOOKTYPE_HANDSHAKE, client);
+
+	if (!DONT_RESOLVE && !IsUnixSocket(client))
+	{
+		if (should_show_connect_info(client))
+			sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_DNS);
+		he = unrealdns_doclient(client);
+
+		if (client->local->hostp)
+			goto doauth; /* Race condition detected, DNS has been done, continue with auth */
+
+		if (!he)
+		{
+			/* Resolving in progress */
+			SetDNSLookup(client);
+		} else {
+			/* Host was in our cache */
+			client->local->hostp = he;
+			if (should_show_connect_info(client))
+				sendto_one(client, NULL, ":%s %s", me.name, REPORT_FIN_DNSC);
+		}
+	}
+
+doauth:
+	consider_ident_lookup(client);
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+/** Called when DNS lookup has been completed and we can proceed with the client handshake.
+ * @param client	The client
+ * @param he		The resolved or unresolved host
+ */
+void proceed_normal_client_handshake(Client *client, struct hostent *he)
+{
+	ClearDNSLookup(client);
+	client->local->hostp = he;
+	if (should_show_connect_info(client))
+	{
+		sendto_one(client, NULL, ":%s %s",
+		           me.name,
+		           client->local->hostp ? REPORT_FIN_DNS : REPORT_FAIL_DNS);
+	}
+}
+
+/** Read a packet from a client.
+ * @param fd		File descriptor
+ * @param revents	Read events (ignored)
+ * @param data		Associated data (the client)
+ */
+void read_packet(int fd, int revents, void *data)
+{
+	Client *client = data;
+	int length = 0;
+	time_t now = TStime();
+	Hook *h;
+	int processdata;
+
+	/* Don't read from dead sockets */
+	if (IsDeadSocket(client))
+	{
+		fd_setselect(fd, FD_SELECT_READ, NULL, client);
+		return;
+	}
+
+	SET_ERRNO(0);
+
+	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 (TLS) writes by read_packet(), see below under
+	 * SSL_ERROR_WANT_WRITE.
+	 */
+	fd_setselect(fd, FD_SELECT_WRITE, send_queued_cb, client);
+
+	while (1)
+	{
+		if (IsTLS(client) && client->local->ssl != NULL)
+		{
+			length = SSL_read(client->local->ssl, readbuf, sizeof(readbuf));
+
+			if (length < 0)
+			{
+				int err = SSL_get_error(client->local->ssl, length);
+
+				switch (err)
+				{
+				case SSL_ERROR_WANT_WRITE:
+					fd_setselect(fd, FD_SELECT_READ, NULL, client);
+					fd_setselect(fd, FD_SELECT_WRITE, read_packet, client);
+					length = -1;
+					SET_ERRNO(P_EWOULDBLOCK);
+					break;
+				case SSL_ERROR_WANT_READ:
+					fd_setselect(fd, FD_SELECT_READ, read_packet, client);
+					length = -1;
+					SET_ERRNO(P_EWOULDBLOCK);
+					break;
+				case SSL_ERROR_SYSCALL:
+					break;
+				case SSL_ERROR_SSL:
+					if (ERRNO == P_EAGAIN)
+						break;
+				default:
+					/*length = 0;
+					SET_ERRNO(0);
+					^^ why this? we should error. -- todo: is errno correct?
+					*/
+					break;
+				}
+			}
+		}
+		else
+			length = recv(client->local->fd, readbuf, sizeof(readbuf), 0);
+
+		if (length <= 0)
+		{
+			if (length < 0 && ((ERRNO == P_EWOULDBLOCK) || (ERRNO == P_EAGAIN) || (ERRNO == P_EINTR)))
+				return;
+
+			if (IsServer(client) || client->server) /* server or outgoing connection */
+				lost_server_link(client, NULL);
+
+			exit_client(client, NULL, ERRNO ? "Read error" : "Connection closed");
+			return;
+		}
+
+		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);
+
+		ClearPingWarning(client);
+
+		processdata = 1;
+		for (h = Hooks[HOOKTYPE_RAWPACKET_IN]; h; h = h->next)
+		{
+			processdata = (*(h->func.intfunc))(client, readbuf, &length);
+			if (processdata == 0)
+				break; /* if hook tells to ignore the data, then break now */
+			if (processdata < 0)
+				return; /* if hook tells client is dead, return now */
+		}
+
+		if (processdata && !process_packet(client, readbuf, length, 0))
+			return;
+
+		/* bail on short read! */
+		if (length < sizeof(readbuf))
+			return;
+	}
+}
+
+/** Process input from clients that may have been deliberately delayed due to fake lag */
+void process_clients(void)
+{
+	Client *client;
+        
+	/* Problem:
+	 * When processing a client, that current client may exit due to eg QUIT.
+	 * Similarly, current->next may be killed due to /KILL.
+	 * When a client is killed, in the past we were not allowed to touch it anymore
+	 * so that was a bit problematic. Now we can touch current->next, but it may
+	 * have been removed from the lclient_list or unknown_list.
+	 * In other words, current->next->next may be NULL even though there are more
+	 * clients on the list.
+	 * This is why the whole thing is wrapped in an additional do { } while() loop
+	 * to make sure we re-run the list if we ended prematurely.
+	 * We could use some kind of 'tagging' to mark already processed clients.
+	 * However, parse_client_queued() already takes care not to read (fake) lagged
+	 * clients, and we don't actually read/recv anything in the meantime, so clients
+	 * in the beginning of the list won't benefit, they won't get higher prio.
+	 * Another alternative is not to run the loop again, but that WOULD be
+	 * unfair to clients later in the list which wouldn't be processed then
+	 * under a heavy (kill) load scenario.
+	 * I think the chosen solution is best, though it remains silly. -- Syzop
+	 */
+
+	do {
+		list_for_each_entry(client, &lclient_list, lclient_node)
+		{
+			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
+			{
+				parse_client_queued(client);
+				if (IsDead(client))
+					break;
+			}
+		}
+	} while(&client->lclient_node != &lclient_list);
+
+	do {
+		list_for_each_entry(client, &unknown_list, lclient_node)
+		{
+			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
+			{
+				parse_client_queued(client);
+				if (IsDead(client) || (client->status > CLIENT_STATUS_UNKNOWN))
+					break;
+			}
+		}
+	} while(&client->lclient_node != &unknown_list);
+
+	do {
+		list_for_each_entry(client, &control_list, lclient_node)
+		{
+			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
+			{
+				parse_client_queued(client);
+				if (IsDead(client))
+					break;
+			}
+		}
+	} while(&client->lclient_node != &control_list);
+
+
+}
+
+/** Check if 'ip' is a valid IP address, and if so what type.
+ * @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(const char *ip)
+{
+	char scratch[64];
+
+	if (BadPtr(ip))
+		return 0;
+
+	if (inet_pton(AF_INET, ip, scratch) == 1)
+		return 4; /* IPv4 */
+
+	if (inet_pton(AF_INET6, ip, scratch) == 1)
+		return 6; /* IPv6 */
+
+	return 0; /* not an IP address */
+}
+
+/** 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..
+ */
+int ipv6_capable(void)
+{
+	int s = socket(AF_INET6, SOCK_STREAM, 0);
+	if (s < 0)
+		return 0; /* NO ipv6 */
+
+	CLOSE_SOCK(s);
+	return 1; /* YES */
+}
+
+/** Return 1 if UNIX sockets of type SOCK_STREAM are supported, and 0 otherwise */
+int unix_sockets_capable(void)
+{
+	int fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Testing UNIX socket");
+	if (fd < 0)
+		return 0;
+	fd_close(fd);
+	return 1;
+}
+
+/** Attempt to deliver data to a client.
+ * This function is only called from send_queued() and will deal
+ * with sending to the TLS or plaintext connection.
+ * @param cptr The client
+ * @param str  The string to send
+ * @param len  The length of the string
+ * @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
+ *                  there is data ready to be READ.
+ * @retval <0  Some fatal error occurred, (but not EWOULDBLOCK).
+ *             This return is a request to close the socket and
+ *             clean up the link.
+ * @retval >=0 No real error occurred, returns the number of
+ *             bytes actually transferred. EWOULDBLOCK and other
+ *             possibly similar conditions should be mapped to
+ *             zero return. Upper level routine will have to
+ *             decide what to do with those unwritten bytes...
+ */
+int deliver_it(Client *client, char *str, int len, int *want_read)
+{
+	int  retval;
+
+	*want_read = 0;
+
+	if (IsDeadSocket(client) ||
+	    (!IsServer(client) && !IsUser(client) && !IsHandshake(client) &&
+	     !IsTLSHandshake(client) && !IsUnknown(client) &&
+	     !IsControl(client) && !IsRPC(client)))
+	{
+		return -1;
+	}
+
+	if (IsTLS(client) && client->local->ssl != NULL)
+	{
+		retval = SSL_write(client->local->ssl, str, len);
+
+		if (retval < 0)
+		{
+			switch (SSL_get_error(client->local->ssl, retval))
+			{
+			case SSL_ERROR_WANT_READ:
+				SET_ERRNO(P_EWOULDBLOCK);
+				*want_read = 1;
+				return 0;
+			case SSL_ERROR_WANT_WRITE:
+				SET_ERRNO(P_EWOULDBLOCK);
+				break;
+			case SSL_ERROR_SYSCALL:
+				break;
+			case SSL_ERROR_SSL:
+				if (ERRNO == P_EAGAIN)
+					break;
+				/* FALLTHROUGH */
+			default:
+				return -1; /* hm.. why was this 0?? we have an error! */
+			}
+		}
+	}
+	else
+		retval = send(client->local->fd, str, len, 0);
+	/*
+	   ** Convert WOULDBLOCK to a return of "0 bytes moved". This
+	   ** should occur only if socket was non-blocking. Note, that
+	   ** all is Ok, if the 'write' just returns '0' instead of an
+	   ** error and errno=EWOULDBLOCK.
+	   **
+	   ** ...now, would this work on VMS too? --msa
+	 */
+# ifndef _WIN32
+	if (retval < 0 && (errno == EWOULDBLOCK || errno == EAGAIN ||
+	    errno == ENOBUFS))
+# else
+		if (retval < 0 && (WSAGetLastError() == WSAEWOULDBLOCK ||
+		    WSAGetLastError() == WSAENOBUFS))
+# endif
+			retval = 0;
+
+	if (retval > 0)
+	{
+		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, const char *ip, int port, SocketType socket_type)
+{
+	int n;
+
+	if (socket_type == SOCKET_TYPE_IPV6)
+	{
+		struct sockaddr_in6 server;
+		memset(&server, 0, sizeof(server));
+		server.sin6_family = AF_INET6;
+		inet_pton(AF_INET6, ip, &server.sin6_addr);
+		server.sin6_port = htons(port);
+		n = connect(fd, (struct sockaddr *)&server, sizeof(server));
+	}
+	else if (socket_type == SOCKET_TYPE_IPV4)
+	{
+		struct sockaddr_in server;
+		memset(&server, 0, sizeof(server));
+		server.sin_family = AF_INET;
+		inet_pton(AF_INET, ip, &server.sin_addr);
+		server.sin_port = htons(port);
+		n = connect(fd, (struct sockaddr *)&server, sizeof(server));
+	} else
+	{
+		struct sockaddr_un server;
+		memset(&server, 0, sizeof(server));
+		server.sun_family = AF_UNIX;
+		strlcpy(server.sun_path, ip, sizeof(server.sun_path));
+		n = connect(fd, (struct sockaddr *)&server, sizeof(server));
+	}
+
+#ifndef _WIN32
+	if (n < 0 && (errno != EINPROGRESS))
+#else
+	if (n < 0 && (WSAGetLastError() != WSAEINPROGRESS) && (WSAGetLastError() != WSAEWOULDBLOCK))
+#endif
+	{
+		return 0; /* FATAL ERROR */
+	}
+
+	return 1; /* SUCCESS (probably still in progress) */
+}
+
+/** Bind to an IP/port (port may be 0 for auto).
+ * @returns 0 on failure, other on success.
+ */
+int unreal_bind(int fd, const char *ip, int port, SocketType socket_type)
+{
+	if (socket_type == SOCKET_TYPE_IPV4)
+	{
+		struct sockaddr_in server;
+		memset(&server, 0, sizeof(server));
+		server.sin_family = AF_INET;
+		server.sin_port = htons(port);
+		if (inet_pton(AF_INET, ip, &server.sin_addr.s_addr) != 1)
+			return 0;
+		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
+	}
+	else if (socket_type == SOCKET_TYPE_IPV6)
+	{
+		struct sockaddr_in6 server;
+		memset(&server, 0, sizeof(server));
+		server.sin6_family = AF_INET6;
+		server.sin6_port = htons(port);
+		if (inet_pton(AF_INET6, ip, &server.sin6_addr.s6_addr) != 1)
+			return 0;
+		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
+	} else
+	{
+		struct sockaddr_un server;
+		mode_t saved_umask, new_umask;
+		int ret;
+
+		if (port == 0)
+			new_umask = 077;
+		else
+			new_umask = port ^ 0777;
+
+		unlink(ip); /* (ignore errors) */
+
+		memset(&server, 0, sizeof(server));
+		server.sun_family = AF_UNIX;
+		strlcpy(server.sun_path, ip, sizeof(server.sun_path));
+		saved_umask = umask(new_umask);
+		ret = !bind(fd, (struct sockaddr *)&server, sizeof(server));
+		umask(saved_umask);
+
+		return ret;
+	}
+}
+
+#ifdef _WIN32
+void init_winsock(void)
+{
+	WSADATA WSAData;
+	if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
+	{
+		MessageBox(NULL, "Unable to initialize WinSock", "UnrealIRCD Initalization Error", MB_OK);
+		fprintf(stderr, "Unable to initialize WinSock\n");
+		exit(1);
+	}
+}
+#endif
diff --git a/ircd/src/support.c b/ircd/src/support.c
@@ -0,0 +1,1534 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/support.c
+ *   Copyright (C) 1990, 1991 Armin Gruner
+ *
+ *   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 Functions that don't always exist on every OS are provided here.
+ * That was the original idea, anyway. Right now it also contains some
+ * functions that should probably be in src/misc.c instead.
+ * In any case, most functions here don't have any special meaning
+ * specific for IRC, they could just as well be used in non-IRC code.
+ */
+
+/* support.c 2.21 4/13/94 1990, 1991 Armin Gruner; 1992, 1993 Darren Reed */
+
+#include "unrealircd.h"
+
+extern void outofmemory();
+
+#define is_enabled match
+
+/** Convert integer to string */
+const char *my_itoa(int i)
+{
+	static char buf[128];
+	ircsnprintf(buf, sizeof(buf), "%d", i);
+	return buf;
+}
+
+/** Walk through a string of tokens, using a set of separators.
+ * @param save	Pointer used for saving between calls
+ * @param str	String to parse (will be altered!)
+ * @param fs	Separator character(s)
+ * @returns substring (token)
+ * @note This function works similar to (but not identical?) to strtok_r().
+ * @section Ex1 Example
+ * @code
+ * for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
+ *      unreal_log(ULOG_INFO, "test", "TEST", "Got: $name", log_data_string(name));
+ * @endcode
+ */
+char *strtoken(char **save, char *str, char *fs)
+{
+	char *pos, *tmp;
+
+	if (str)
+		pos = str;	/* new string scan */
+	else
+		pos = *save; /* keep last position across calls */
+
+	while (pos && *pos && strchr(fs, *pos) != NULL)
+		pos++;		/* skip leading separators */
+
+	if (!pos || !*pos)
+		return (pos = *save = NULL);	/* string contains only sep's */
+
+	tmp = pos;		/* now, keep position of the token */
+
+	while (*pos && strchr(fs, *pos) == NULL)
+		pos++;		/* skip content of the token */
+
+	if (*pos)
+		*pos++ = '\0';	/* remove first sep after the token */
+	else
+		pos = NULL;	/* end of string */
+
+	*save = pos;
+	return (tmp);
+}
+
+/** Walk through a string of tokens, using a set of separators.
+ * This is the special version that won't skip/merge tokens,
+ * eg "a,,c" would return "a", then "" (empty), then "c".
+ * This in contrast to strtoken() which would return "a" and then "c".
+ * This strtoken_noskip() will also not skip tokens at the
+ * beginning, eg ",,c" would return "" (empty), "" (empty), "c".
+ *
+ * @param save	Pointer used for saving between calls
+ * @param str	String to parse (will be altered!)
+ * @param fs	Separator character(s)
+ * @returns substring (token)
+ * @note This function works similar to (but not identical?) to strtok_r().
+ */
+char *strtoken_noskip(char **save, char *str, char *fs)
+{
+	char *pos, *tmp;
+
+	if (str)
+	{
+		pos = str;	/* new string scan */
+	} else {
+		if (*save == NULL)
+		{
+			/* We reached the end of the string */
+			return NULL;
+		}
+		pos = *save; /* keep last position across calls */
+	}
+
+	tmp = pos; /* start position, used for returning later */
+
+	/* Hunt for next separator (fs in pos) */
+	while (*pos && !strchr(fs, *pos))
+		pos++;
+
+	if (!*pos)
+	{
+		/* Next call is end of string */
+		*save = NULL;
+		*pos++ = '\0';
+	} else {
+		*pos++ = '\0';
+		*save = pos;
+	}
+
+	return tmp;
+}
+
+/** Convert binary address to an IP string - like inet_ntop but will always return the uncompressed IPv6 form.
+ * @param af	Address family (AF_INET, AF_INET6)
+ * @param in	Address (binary)
+ * @param out	Buffer to use for storing the returned IP string
+ * @param size	Size of the 'out' buffer
+ * @returns IP address as a string (IPv4 or IPv6, in case of the latter:
+ *          always the uncompressed form without ::)
+ */
+const char *inetntop(int af, const void *in, char *out, size_t size)
+{
+	char tmp[MYDUMMY_SIZE];
+
+	inet_ntop(af, in, tmp, size);
+	if (!strstr(tmp, "::"))
+	{
+		/* IPv4 or IPv6 that is already uncompressed */
+		strlcpy(out, tmp, size);
+	} else
+	{
+		char cnt = 0, *cp = tmp, *op = out;
+
+		/* It's an IPv6 compressed address that we need to expand */
+		while (*cp)
+		{
+			if (*cp == ':')
+				cnt += 1;
+			if (*cp++ == '.')
+			{
+				cnt += 1;
+				break;
+			}
+		}
+		cp = tmp;
+		while (*cp)
+		{
+			*op++ = *cp++;
+			if (*(cp - 1) == ':' && *cp == ':')
+			{
+				if ((cp - 1) == tmp)
+				{
+					op--;
+					*op++ = '0';
+					*op++ = ':';
+				}
+
+				*op++ = '0';
+				while (cnt++ < 7)
+				{
+					*op++ = ':';
+					*op++ = '0';
+				}
+			}
+		}
+		if (*(op - 1) == ':')
+			*op++ = '0';
+		*op = '\0';
+	}
+	return out;
+}
+
+/** Cut string off at the first occurance of CR or LF */
+void stripcrlf(char *c)
+{
+	for (; *c; c++)
+	{
+		if ((*c == '\n') || (*c == '\r'))
+		{
+			*c = '\0';
+			return;
+		}
+	}
+}
+
+#ifndef HAVE_STRNLEN
+size_t strnlen(const char *s, size_t maxlen)
+{
+	const char *end = memchr (s, 0, maxlen);
+	return end ? (size_t)(end - s) : maxlen;
+}
+#endif
+
+#ifndef HAVE_STRLCPY
+/** BSD'ish strlcpy().
+ * The strlcpy() function copies up to size-1 characters from the
+ * NUL-terminated string src to dst, NUL-terminating the result.
+ * Return: total length of the string tried to create.
+ */
+size_t strlcpy(char *dst, const char *src, size_t size)
+{
+	size_t len = strlen(src);
+	size_t ret = len;
+
+	if (size <= 0)
+		return 0;
+	if (len >= size)
+		len = size - 1;
+	memcpy(dst, src, len);
+	dst[len] = 0;
+
+	return ret;
+}
+#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 = strnlen(src, n);
+	size_t ret = len;
+
+	if (size <= 0)
+		return 0;
+	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
+ * dst. It will append at most size - strlen(dst) - 1 bytes, NUL-terminating
+ * the result.
+ */
+size_t strlcat(char *dst, const char *src, size_t size)
+{
+	size_t len1 = strlen(dst);
+	size_t len2 = strlen(src);
+	size_t ret = len1 + len2;
+
+	if (size <= len1)
+		return size;
+	if (len1 + len2 >= size)
+		len2 = size - (len1 + 1);
+
+	if (len2 > 0) {
+		memcpy(dst + len1, src, len2);
+		dst[len1 + len2] = 0;
+	}
+	
+	return ret;
+}
+#endif
+
+#ifndef HAVE_STRLNCAT
+/** BSD'ish strlncat() - similar to strlcat but never cat more then n characters.
+ */
+size_t strlncat(char *dst, const char *src, size_t size, size_t n)
+{
+	size_t len1 = strlen(dst);
+	size_t len2 = strnlen(src, n);
+	size_t ret = len1 + len2;
+
+	if (size <= len1)
+		return size;
+		
+	if (len1 + len2 >= size)
+		len2 = size - (len1 + 1);
+
+	if (len2 > 0) {
+		memcpy(dst + len1, src, len2);
+		dst[len1 + len2] = 0;
+	}
+
+	return ret;
+}
+#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.
+ * If you wonder why not use strndup() instead?
+ * I feel that mixing code with strlcpy() and strndup() would be
+ * rather confusing since strlcpy() assumes buffer size INCLUDING
+ * the nul byte and strndup() assumes WITHOUT the nul byte and
+ * will write one character extra. Hence this strldup(). -- Syzop
+ */
+char *strldup(const char *src, size_t max)
+{
+	char *ptr;
+	int n;
+
+	if ((max == 0) || !src)
+		return NULL;
+
+	n = strlen(src);
+	if (n > max-1)
+		n = max-1;
+
+	ptr = safe_alloc(n+1);
+	memcpy(ptr, src, n);
+	ptr[n] = '\0';
+
+	return ptr;
+}
+
+static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char Pad64 = '=';
+
+/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt)
+   The following encoding technique is taken from RFC 1521 by Borenstein
+   and Freed.  It is reproduced here in a slightly edited form for
+   convenience.
+
+   A 65-character subset of US-ASCII is used, enabling 6 bits to be
+   represented per printable character. (The extra 65th character, "=",
+   is used to signify a special processing function.)
+
+   The encoding process represents 24-bit groups of input bits as output
+   strings of 4 encoded characters. Proceeding from left to right, a
+   24-bit input group is formed by concatenating 3 8-bit input groups.
+   These 24 bits are then treated as 4 concatenated 6-bit groups, each
+   of which is translated into a single digit in the base64 alphabet.
+
+   Each 6-bit group is used as an index into an array of 64 printable
+   characters. The character referenced by the index is placed in the
+   output string.
+
+                         Table 1: The Base64 Alphabet
+
+      Value Encoding  Value Encoding  Value Encoding  Value Encoding
+          0 A            17 R            34 i            51 z
+          1 B            18 S            35 j            52 0
+          2 C            19 T            36 k            53 1
+          3 D            20 U            37 l            54 2
+          4 E            21 V            38 m            55 3
+          5 F            22 W            39 n            56 4
+          6 G            23 X            40 o            57 5
+          7 H            24 Y            41 p            58 6
+          8 I            25 Z            42 q            59 7
+          9 J            26 a            43 r            60 8
+         10 K            27 b            44 s            61 9
+         11 L            28 c            45 t            62 +
+         12 M            29 d            46 u            63 /
+         13 N            30 e            47 v
+         14 O            31 f            48 w         (pad) =
+         15 P            32 g            49 x
+         16 Q            33 h            50 y
+
+   Special processing is performed if fewer than 24 bits are available
+   at the end of the data being encoded.  A full encoding quantum is
+   always completed at the end of a quantity.  When fewer than 24 input
+   bits are available in an input group, zero bits are added (on the
+   right) to form an integral number of 6-bit groups.  Padding at the
+   end of the data is performed using the '=' character.
+
+   Since all base64 input is an integral number of octets, only the
+         -------------------------------------------------                       
+   following cases can arise:
+   
+       (1) the final quantum of encoding input is an integral
+           multiple of 24 bits; here, the final unit of encoded
+	   output will be an integral multiple of 4 characters
+	   with no "=" padding,
+       (2) the final quantum of encoding input is exactly 8 bits;
+           here, the final unit of encoded output will be two
+	   characters followed by two "=" padding characters, or
+       (3) the final quantum of encoding input is exactly 16 bits;
+           here, the final unit of encoded output will be three
+	   characters followed by one "=" padding character.
+   */
+
+/** Base64 encode data.
+ * @param src		The data to encode (input)
+ * @param srclength	The length of the data to encode (input length)
+ * @param target	The output buffer to use (output)
+ * @param targetsize	The length of the output buffer to use (maximum output length)
+ * @returns length of the targetsize, or -1 in case of error.
+ */
+int b64_encode(unsigned char const *src, size_t srclength, char *target, size_t targsize)
+{
+	size_t datalength = 0;
+	u_char input[3];
+	u_char output[4];
+	size_t i;
+
+	while (2 < srclength) {
+		input[0] = *src++;
+		input[1] = *src++;
+		input[2] = *src++;
+		srclength -= 3;
+
+		output[0] = input[0] >> 2;
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+		output[3] = input[2] & 0x3f;
+
+		if (datalength + 4 > targsize)
+			return (-1);
+		target[datalength++] = Base64[output[0]];
+		target[datalength++] = Base64[output[1]];
+		target[datalength++] = Base64[output[2]];
+		target[datalength++] = Base64[output[3]];
+	}
+    
+	/* Now we worry about padding. */
+	if (0 != srclength) {
+		/* Get what's left. */
+		input[0] = input[1] = input[2] = '\0';
+		for (i = 0; i < srclength; i++)
+			input[i] = *src++;
+	
+		output[0] = input[0] >> 2;
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+
+		if (datalength + 4 > targsize)
+			return (-1);
+		target[datalength++] = Base64[output[0]];
+		target[datalength++] = Base64[output[1]];
+		if (srclength == 1)
+			target[datalength++] = Pad64;
+		else
+			target[datalength++] = Base64[output[2]];
+		target[datalength++] = Pad64;
+	}
+	if (datalength >= targsize)
+		return (-1);
+	target[datalength] = '\0';	/* Returned value doesn't count \0. */
+	return (datalength);
+}
+
+/** Base64 decode a string.
+ * @param src		The data to decode (input)
+ * @param srclength	The length of the data to decode (input length)
+ * @param target	The output buffer to use (output)
+ * @param targetsize	The length of the output buffer to use (maximum output length)
+ * @returns length of the targetsize, or -1 in case of error.
+ * @note Skips whitespace, and hmm.. I think we don't require padding? Not sure.
+ */
+int b64_decode(char const *src, unsigned char *target, size_t targsize)
+{
+	int tarindex, state, ch;
+	char *pos;
+
+	state = 0;
+	tarindex = 0;
+
+	while ((ch = *src++) != '\0') {
+		if (isspace(ch))	/* Skip whitespace anywhere. */
+			continue;
+
+		if (ch == Pad64)
+			break;
+
+		pos = strchr(Base64, ch);
+		if (pos == 0) 		/* A non-base64 character. */
+			return (-1);
+
+		switch (state) {
+		case 0:
+			if (target) {
+				if ((size_t)tarindex >= targsize)
+					return (-1);
+				target[tarindex] = (pos - Base64) << 2;
+			}
+			state = 1;
+			break;
+		case 1:
+			if (target) {
+				if ((size_t)tarindex + 1 >= targsize)
+					return (-1);
+				target[tarindex]   |=  (pos - Base64) >> 4;
+				target[tarindex+1]  = ((pos - Base64) & 0x0f)
+							<< 4 ;
+			}
+			tarindex++;
+			state = 2;
+			break;
+		case 2:
+			if (target) {
+				if ((size_t)tarindex + 1 >= targsize)
+					return (-1);
+				target[tarindex]   |=  (pos - Base64) >> 2;
+				target[tarindex+1]  = ((pos - Base64) & 0x03)
+							<< 6;
+			}
+			tarindex++;
+			state = 3;
+			break;
+		case 3:
+			if (target) {
+				if ((size_t)tarindex >= targsize)
+					return (-1);
+				target[tarindex] |= (pos - Base64);
+			}
+			tarindex++;
+			state = 0;
+			break;
+		default:
+			abort();
+		}
+	}
+
+	/*
+	 * We are done decoding Base-64 chars.  Let's see if we ended
+	 * on a byte boundary, and/or with erroneous trailing characters.
+	 */
+
+	if (ch == Pad64) {		/* We got a pad char. */
+		ch = *src++;		/* Skip it, get next. */
+		switch (state) {
+		case 0:		/* Invalid = in first position */
+		case 1:		/* Invalid = in second position */
+			return (-1);
+
+		case 2:		/* Valid, means one byte of info */
+			/* Skip any number of spaces. */
+			for (; ch != '\0'; ch = *src++)
+				if (!isspace(ch))
+					break;
+			/* Make sure there is another trailing = sign. */
+			if (ch != Pad64)
+				return (-1);
+			ch = *src++;		/* Skip the = */
+			/* Fall through to "single trailing =" case. */
+			/* FALLTHROUGH */
+
+		case 3:		/* Valid, means two bytes of info */
+			/*
+			 * We know this char is an =.  Is there anything but
+			 * whitespace after it?
+			 */
+			for (; ch != '\0'; ch = *src++)
+				if (!isspace(ch))
+					return (-1);
+
+			/*
+			 * Now make sure for cases 2 and 3 that the "extra"
+			 * bits that slopped past the last full byte were
+			 * zeros.  If we don't check them, they become a
+			 * subliminal channel.
+			 */
+			if (target && target[tarindex] != 0)
+				return (-1);
+		}
+	} else {
+		/*
+		 * We ended by seeing the end of the string.  Make sure we
+		 * have no partial bytes lying around.
+		 */
+		if (state != 0)
+			return (-1);
+	}
+
+	return (tarindex);
+}
+
+/* Natural sort case comparison routines. The following copyright header applies:
+  strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
+  Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+ The code was modified / UnrealIRCderized by Bram Matthys ("Syzop").
+ We always have unsigned char and this makes it possible to get rid
+ of various stuff.. also re-indent this monster.
+*/
+
+static int compare_right(char const *a, char const *b)
+{
+	int bias = 0;
+
+	/* The longest run of digits wins.  That aside, the greatest
+	 * value wins, but we can't know that it will until we've scanned
+	 * both numbers to know that they have the same magnitude, so we
+	 * remember it in BIAS.
+	 */
+	for (;; a++, b++)
+	{
+		if (!isdigit(*a) && !isdigit(*b))
+			return bias;
+		if (!isdigit(*a))
+			return -1;
+		if (!isdigit(*b))
+			return +1;
+		if (*a < *b)
+		{
+			if (!bias)
+				bias = -1;
+		} else
+		if (*a > *b)
+		{
+			if (!bias)
+				bias = +1;
+		} else
+		if (!*a && !*b)
+			return bias;
+	}
+
+	return 0;
+}
+
+
+static int compare_left(char const *a, char const *b)
+{
+	/* Compare two left-aligned numbers: the first to have a
+	 * different value wins.
+	 */
+	for (;; a++, b++)
+	{
+		if (!isdigit(*a) && !isdigit(*b))
+			return 0;
+		if (!isdigit(*a))
+			return -1;
+		if (!isdigit(*b))
+			return +1;
+		if (*a < *b)
+			return -1;
+		if (*a > *b)
+			return +1;
+	}
+
+	return 0;
+}
+
+static int strnatcmp0(char const *a, char const *b, int fold_case)
+{
+	int ai, bi;
+	char ca, cb;
+	int fractional, result;
+
+	ai = bi = 0;
+	while (1)
+	{
+		ca = a[ai]; cb = b[bi];
+
+		/* skip over leading spaces or zeros */
+		while (isspace(ca))
+			ca = a[++ai];
+
+		while (isspace(cb))
+			cb = b[++bi];
+
+		/* process run of digits */
+		if (isdigit(ca)  &&  isdigit(cb))
+		{
+			fractional = (ca == '0' || cb == '0');
+			if (fractional)
+			{
+				if ((result = compare_left(a+ai, b+bi)) != 0)
+					return result;
+			} else {
+				if ((result = compare_right(a+ai, b+bi)) != 0)
+					return result;
+			}
+		}
+
+		if (!ca && !cb)
+		{
+			/* The strings compare the same.  Perhaps the caller
+			 * will want to call strcmp to break the tie.
+			 */
+			return 0;
+		}
+
+		if (fold_case)
+		{
+			ca = toupper(ca);
+			cb = toupper(cb);
+		}
+
+		if (ca < cb)
+			return -1;
+
+		if (ca > cb)
+			return +1;
+
+		ai++;
+		bi++;
+	}
+}
+
+/** Like strcmp() but with "natural sort", so that for example
+ * the string "1.4.10" is seen as higher than "1.4.9"
+ * This is the case sensitive version.
+ */
+int strnatcmp(char const *a, char const *b)
+{
+	return strnatcmp0(a, b, 0);
+}
+
+/** Like strcmp() but with "natural sort", so that for example
+ * the string "1.4.10" is seen as higher than "1.4.9"
+ * This is the case insensitive version.
+ */
+int strnatcasecmp(char const *a, char const *b)
+{
+	return strnatcmp0(a, b, 1);
+}
+
+/* End of natural sort case comparison functions */
+
+/* Memory allocation routines */
+
+/** Allocate memory - should always be used instead of malloc/calloc.
+ * @param size How many bytes to allocate
+ * @returns A pointer to the newly allocated memory.
+ * @note If out of memory then the IRCd will exit.
+ */
+void *safe_alloc(size_t size)
+{
+	void *p;
+	if (size == 0)
+		return NULL;
+	p = calloc(1, size);
+	if (!p)
+		outofmemory(size);
+	return p;
+}
+
+/** Safely duplicate a string */
+char *our_strdup(const char *str)
+{
+	char *ret = strdup(str);
+	if (!ret)
+		outofmemory(strlen(str));
+	return ret;
+}
+
+/** Safely duplicate a string with a maximum size */
+char *our_strldup(const char *str, size_t max)
+{
+	char *ret = strldup(str, max);
+	if (!ret)
+		outofmemory(MAX(strlen(str), max));
+	return ret;
+}
+
+/** Called when out of memory */
+void outofmemory(size_t bytes)
+{
+	static int log_attempt = 1;
+
+	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;
+	}
+	exit(7);
+}
+
+/** Allocate sensitive memory - this should only be used for HIGHLY sensitive data, since
+ * it wastes 8192+ bytes even if only asked to allocate for example 32 bytes (this is by design).
+ * @param size How many bytes to allocate
+ * @returns A pointer to the newly allocated memory.
+ * @note If out of memory then the IRCd will exit.
+ */
+void *safe_alloc_sensitive(size_t size)
+{
+	void *p;
+	if (size == 0)
+		return NULL;
+	p = sodium_malloc(((size/32)*32)+32);
+	if (!p)
+		outofmemory(size);
+	memset(p, 0, size);
+	return p;
+}
+
+/** Safely duplicate a string */
+char *our_strdup_sensitive(const char *str)
+{
+	char *ret = safe_alloc_sensitive(strlen(str)+1);
+	if (!ret)
+		outofmemory(strlen(str));
+	strcpy(ret, str); /* safe, see above */
+	return ret;
+}
+
+/** Returns a unique filename in the specified directory
+ * using the specified suffix. The returned value will
+ * be of the form <dir>/<random-hex>.<suffix>
+ */
+char *unreal_mktemp(const char *dir, const char *suffix)
+{
+	FILE *fd;
+	unsigned int i;
+	static char tempbuf[PATH_MAX+1];
+
+	for (i = 500; i > 0; i--)
+	{
+		snprintf(tempbuf, PATH_MAX, "%s/%X.%s", dir, getrandom32(), suffix);
+		fd = fopen(tempbuf, "r");
+		if (!fd)
+			return tempbuf;
+		fclose(fd);
+	}
+	config_error("Unable to create temporary file in directory '%s': %s",
+		dir, strerror(errno)); /* eg: permission denied :p */
+	return NULL; 
+}
+
+/** Returns the path portion of the given path/file
+ * in the specified location (must be at least PATH_MAX bytes).
+ */
+char *unreal_getpathname(const char *filepath, char *path)
+{
+	const char *end = filepath+strlen(filepath);
+
+	while (*end != '\\' && *end != '/' && end > filepath)
+		end--;
+	if (end == filepath)
+		path = NULL;
+	else
+	{
+		int size = end-filepath;
+		if (size >= PATH_MAX)
+			path = NULL;
+		else
+		{
+			memcpy(path, filepath, size);
+			path[size] = 0;
+		}
+	}
+	return path;
+}
+
+/** Returns the filename portion of the given path.
+ * The original string is not modified
+ */
+const char *unreal_getfilename(const char *path)
+{
+	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;
+}
+
+/** Wrapper for mkdir() so you don't need ifdefs everywhere for Windows.
+ * @returns 0 on failure!! (like mkdir)
+ */
+int unreal_mkdir(const char *pathname, mode_t mode)
+{
+#ifdef _WIN32
+	return mkdir(pathname);
+#else
+	return mkdir(pathname, mode);
+#endif
+}
+
+/** Create the entire directory structure.
+ * @param dname	The directory name, eg /home/irc/unrealircd/logs/2022/08/05
+ * @param mode	The mode to create with, eg 0777. Ignored on Windows.
+ * @returns 1 on success, 0 on failure.
+ */
+int unreal_create_directory_structure(const char *dname, mode_t mode)
+{
+	if (unreal_mkdir(dname, mode) == 0)
+	{
+		/* Ok, that failed as well, we have some work to do:
+		 * for every path element run mkdir().
+		 */
+		int lastresult;
+		char buf[512], *p;
+		strlcpy(buf, dname, sizeof(buf)); /* work on a copy */
+		for (p=strchr(buf+1, '/'); p; p=strchr(p+1, '/'))
+		{
+			*p = '\0';
+			unreal_mkdir(buf,mode);
+			*p = '/';
+		}
+		/* Finally, try the complete path */
+		if (unreal_mkdir(dname, mode))
+			return 0; /* failed */
+		/* fallthrough.... */
+	}
+	return 1; /* success */
+}
+
+/** Create entire directory structure for a path with a filename.
+ * @param fname	The full path name, eg /home/irc/unrealircd/logs/2022/08/05/ircd.log
+ * @param mode	The mode to create with, eg 0777. Ignored on Windows.
+ * @notes This is used as an easier way to call unreal_create_directory_structure()
+ *        if you have a filename instead of the directory part.
+ * @returns 1 on success, 0 on failure.
+ */
+int unreal_create_directory_structure_for_file(const char *fname, mode_t mode)
+{
+	char buf[PATH_MAX+1];
+	const char *path = unreal_getpathname(fname, buf);
+	if (!path)
+		return 0;
+	return unreal_create_directory_structure(path, mode);
+}
+
+/** Returns the special module tmp name for a given path.
+ * The original string is not modified.
+ */
+const char *unreal_getmodfilename(const char *path)
+{
+	static char ret[512];
+	char buf[512];
+	char *p;
+	char *name = NULL;
+	char *directory = NULL;
+	
+	if (BadPtr(path))
+		return path;
+	
+	strlcpy(buf, path, sizeof(buf));
+	
+	/* Backtrack... */
+	for (p = buf + strlen(buf); p >= buf; p--)
+	{
+		if ((*p == '/') || (*p == '\\'))
+		{
+			name = p+1;
+			*p = '\0';
+			directory = buf; /* fallback */
+			for (; p >= buf; p--)
+			{
+				if ((*p == '/') || (*p == '\\'))
+				{
+					directory = p + 1;
+					break;
+				}
+			}
+			break;
+		}
+	}
+	
+	if (!name)
+		name = buf;
+	
+	if (!directory || !strcmp(directory, "modules"))
+		snprintf(ret, sizeof(ret), "%s", name);
+	else
+		snprintf(ret, sizeof(ret), "%s.%s", directory, name);
+	
+	return ret;
+}
+
+/* Returns a consistent filename for the cache/ directory.
+ * Returned value will be like: cache/<hash of url>
+ */
+const char *unreal_mkcache(const char *url)
+{
+	static char tempbuf[PATH_MAX+1];
+	char tmp2[128];
+	
+	snprintf(tempbuf, PATH_MAX, "%s/%s", CACHEDIR, sha256hash(tmp2, url, strlen(url)));
+	return tempbuf;
+}
+
+/** Returns 1 if a cached version of the url exists, otherwise 0. */
+int has_cached_version(const char *url)
+{
+	return file_exists(unreal_mkcache(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);
+}
+
+/** Copys the contents of the src file to the dest file.
+ * The dest file will have permissions r-x------
+ */
+int unreal_copyfile(const char *src, const char *dest)
+{
+	char buf[2048];
+	time_t mtime;
+	int srcfd, destfd, len;
+
+	mtime = unreal_getfilemodtime(src);
+
+#ifndef _WIN32
+	srcfd = open(src, O_RDONLY);
+#else
+	srcfd = open(src, _O_RDONLY|_O_BINARY);
+#endif
+
+	if (srcfd < 0)
+	{
+		config_error("Unable to open file '%s': %s", src, strerror(errno));
+		return 0;
+	}
+
+#ifndef _WIN32
+#if defined(DEFAULT_PERMISSIONS) && (DEFAULT_PERMISSIONS != 0)
+	destfd  = open(dest, O_WRONLY|O_CREAT, DEFAULT_PERMISSIONS);
+#else
+	destfd  = open(dest, O_WRONLY|O_CREAT, S_IRUSR | S_IXUSR);
+#endif /* DEFAULT_PERMISSIONS */
+#else
+	destfd = open(dest, _O_BINARY|_O_WRONLY|_O_CREAT, _S_IWRITE);
+#endif /* _WIN32 */
+	if (destfd < 0)
+	{
+		config_error("Unable to create file '%s': %s", dest, strerror(errno));
+		close(srcfd);
+		return 0;
+	}
+
+	while ((len = read(srcfd, buf, 1023)) > 0)
+		if (write(destfd, buf, len) != len)
+		{
+			config_error("Write error to file '%s': %s [not enough free hd space / quota? need several mb's!]",
+				dest, strerror(ERRNO));
+			cancel_copy(srcfd,destfd,dest);
+			return 0;
+		}
+
+	if (len < 0) /* very unusual.. perhaps an I/O error */
+	{
+		config_error("Read error from file '%s': %s", src, strerror(errno));
+		cancel_copy(srcfd,destfd,dest);
+		return 0;
+	}
+
+	close(srcfd);
+	close(destfd);
+	unreal_setfilemodtime(dest, mtime);
+	return 1;
+}
+
+/** Same as unreal_copyfile, but with an option to try hardlinking first */
+int unreal_copyfileex(const char *src, const char *dest, int tryhardlink)
+{
+	unlink(dest);
+#ifndef _WIN32
+	/* Try a hardlink first... */
+	if (tryhardlink && !link(src, dest))
+		return 1; /* success */
+#endif
+	return unreal_copyfile(src, dest);
+}
+
+/** Set the modification time on a file */
+void unreal_setfilemodtime(const char *filename, time_t mtime)
+{
+#ifndef _WIN32
+	struct utimbuf utb;
+	utb.actime = utb.modtime = mtime;
+	utime(filename, &utb);
+#else
+	FILETIME mTime;
+	LONGLONG llValue;
+	HANDLE hFile = CreateFile(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+				  FILE_ATTRIBUTE_NORMAL, NULL);
+	if (hFile == INVALID_HANDLE_VALUE)
+		return;
+	llValue = Int32x32To64(mtime, 10000000) + 116444736000000000;
+	mTime.dwLowDateTime = (long)llValue;
+	mTime.dwHighDateTime = llValue >> 32;
+	
+	SetFileTime(hFile, &mTime, &mTime, &mTime);
+	CloseHandle(hFile);
+#endif
+}
+
+/** Get the modification time ("last modified") of a file */
+time_t unreal_getfilemodtime(const char *filename)
+{
+#ifndef _WIN32
+	struct stat sb;
+	if (stat(filename, &sb))
+		return 0;
+	return sb.st_mtime;
+#else
+	/* See how much more fun WinAPI programming is??? */
+	FILETIME cTime;
+	SYSTEMTIME sTime, lTime;
+	ULARGE_INTEGER fullTime;
+	time_t result;
+	HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+				  FILE_ATTRIBUTE_NORMAL, NULL);
+	if (hFile == INVALID_HANDLE_VALUE)
+		return 0;
+	if (!GetFileTime(hFile, NULL, NULL, &cTime))
+		return 0;
+
+	CloseHandle(hFile);
+
+	FileTimeToSystemTime(&cTime, &sTime);
+	SystemTimeToTzSpecificLocalTime(NULL, &sTime, &lTime);
+	SystemTimeToFileTime(&sTime, &cTime);
+
+	fullTime.LowPart = cTime.dwLowDateTime;
+	fullTime.HighPart = cTime.dwHighDateTime;
+	fullTime.QuadPart -= 116444736000000000;
+	fullTime.QuadPart /= 10000000;
+	
+	return fullTime.LowPart;	
+#endif
+}
+
+#ifndef	AF_INET6
+#define	AF_INET6	AF_MAX+1	/* just to let this compile */
+#endif
+
+/** Encode an IP string (eg: "1.2.3.4") to a BASE64 encoded value for S2S traffic */
+const char *encode_ip(const char *ip)
+{
+	static char retbuf[25]; /* returned string */
+	char addrbuf[16];
+
+	if (!ip)
+		return "*";
+
+	if (strchr(ip, ':'))
+	{
+		/* IPv6 (likely) */
+		inet_pton(AF_INET6, ip, addrbuf);
+		/* hack for IPv4-in-IPv6 (::ffff:1.2.3.4) */
+		if (addrbuf[0] == 0 && addrbuf[1] == 0 && addrbuf[2] == 0 && addrbuf[3] == 0
+		    && addrbuf[4] == 0 && addrbuf[5] == 0 && addrbuf[6] == 0
+			&& addrbuf[7] == 0 && addrbuf[8] == 0 && addrbuf[9] == 0
+			&& addrbuf[10] == 0xff && addrbuf[11] == 0xff)
+		{
+			b64_encode(&addrbuf[12], sizeof(struct in_addr), retbuf, sizeof(retbuf));
+		} else {
+			b64_encode(addrbuf, 16, retbuf, sizeof(retbuf));
+		}
+	}
+	else
+	{
+		/* IPv4 */
+		inet_pton(AF_INET, ip, addrbuf);
+		b64_encode((char *)&addrbuf, sizeof(struct in_addr), retbuf, sizeof(retbuf));
+	}
+	return retbuf;
+}
+
+/** Decode a BASE64 encoded string to an IP address string. Used for S2S traffic. */
+const char *decode_ip(const char *buf)
+{
+	int n;
+	char targ[25];
+	static char result[64];
+
+	n = b64_decode(buf, targ, sizeof(targ));
+	if (n == 4) /* should be IPv4 */
+		return inetntop(AF_INET, targ, result, sizeof(result));
+	else if (n == 16) /* should be IPv6 */
+		return inetntop(AF_INET6, targ, result, sizeof(result));
+	else /* Error! */
+		return NULL;
+}
+
+/* IPv6 stuff */
+
+#ifndef IN6ADDRSZ
+#define	IN6ADDRSZ	16
+#endif
+
+#ifndef INT16SZ
+#define	INT16SZ		 2
+#endif
+
+#ifndef INADDRSZ
+#define	INADDRSZ	 4
+#endif
+
+#ifdef _WIN32
+/* Microsoft makes things nice and fun for us! */
+struct u_WSA_errors {
+	int error_code;
+	char *error_string;
+};
+
+/* Must be sorted ascending by error code */
+struct u_WSA_errors WSAErrors[] = {
+ { WSAEINTR,              "Interrupted system call" },
+ { WSAEBADF,              "Bad file number" },
+ { WSAEACCES,             "Permission denied" },
+ { WSAEFAULT,             "Bad address" },
+ { WSAEINVAL,             "Invalid argument" },
+ { WSAEMFILE,             "Too many open sockets" },
+ { WSAEWOULDBLOCK,        "Operation would block" },
+ { WSAEINPROGRESS,        "Operation now in progress" },
+ { WSAEALREADY,           "Operation already in progress" },
+ { WSAENOTSOCK,           "Socket operation on non-socket" },
+ { WSAEDESTADDRREQ,       "Destination address required" },
+ { WSAEMSGSIZE,           "Message too long" },
+ { WSAEPROTOTYPE,         "Protocol wrong type for socket" },
+ { WSAENOPROTOOPT,        "Bad protocol option" },
+ { WSAEPROTONOSUPPORT,    "Protocol not supported" },
+ { WSAESOCKTNOSUPPORT,    "Socket type not supported" },
+ { WSAEOPNOTSUPP,         "Operation not supported on socket" },
+ { WSAEPFNOSUPPORT,       "Protocol family not supported" },
+ { WSAEAFNOSUPPORT,       "Address family not supported" },
+ { WSAEADDRINUSE,         "Address already in use" },
+ { WSAEADDRNOTAVAIL,      "Can't assign requested address" },
+ { WSAENETDOWN,           "Network is down" },
+ { WSAENETUNREACH,        "Network is unreachable" },
+ { WSAENETRESET,          "Net connection reset" },
+ { WSAECONNABORTED,       "Software caused connection abort" },
+ { WSAECONNRESET,         "Connection reset by peer" },
+ { WSAENOBUFS,            "No buffer space available" },
+ { WSAEISCONN,            "Socket is already connected" },
+ { WSAENOTCONN,           "Socket is not connected" },
+ { WSAESHUTDOWN,          "Can't send after socket shutdown" },
+ { WSAETOOMANYREFS,       "Too many references, can't splice" },
+ { WSAETIMEDOUT,          "Connection timed out" },
+ { WSAECONNREFUSED,       "Connection refused" },
+ { WSAELOOP,              "Too many levels of symbolic links" },
+ { WSAENAMETOOLONG,       "File name too long" },
+ { WSAEHOSTDOWN,          "Host is down" },
+ { WSAEHOSTUNREACH,       "No route to host" },
+ { WSAENOTEMPTY,          "Directory not empty" },
+ { WSAEPROCLIM,           "Too many processes" },
+ { WSAEUSERS,             "Too many users" },
+ { WSAEDQUOT,             "Disc quota exceeded" },
+ { WSAESTALE,             "Stale NFS file handle" },
+ { WSAEREMOTE,            "Too many levels of remote in path" },
+ { WSASYSNOTREADY,        "Network subsystem is unavailable" },
+ { WSAVERNOTSUPPORTED,    "Winsock version not supported" },
+ { WSANOTINITIALISED,     "Winsock not yet initialized" },
+ { WSAHOST_NOT_FOUND,     "Host not found" },
+ { WSATRY_AGAIN,          "Non-authoritative host not found" },
+ { WSANO_RECOVERY,        "Non-recoverable errors" },
+ { WSANO_DATA,            "Valid name, no data record of requested type" },
+ { WSAEDISCON,            "Graceful disconnect in progress" },
+ { WSASYSCALLFAILURE,     "System call failure" },
+ { 0,NULL}
+};
+
+/** Get socket error string */
+const char *sock_strerror(int error)
+{
+	static char unkerr[64];
+	int start = 0;
+	int stop = sizeof(WSAErrors)/sizeof(WSAErrors[0])-1;
+	int mid;
+	
+	if (!error)
+		return "No error";
+
+	if (error < WSABASEERR) /* Just a regular error code */
+		return strerror(error);
+
+	/* Microsoft decided not to use sequential numbers for the error codes,
+	 * so we can't just use the array index for the code. But, at least
+	 * use a binary search to make it as fast as possible. 
+	 */
+	while (start <= stop)
+	{
+		mid = (start+stop)/2;
+		if (WSAErrors[mid].error_code > error)
+			stop = mid-1;
+		
+		else if (WSAErrors[mid].error_code < error)
+			start = mid+1;
+		else
+			return WSAErrors[mid].error_string;	
+	}
+	snprintf(unkerr, sizeof(unkerr), "Unknown Error: %d", error);
+	return unkerr;
+}
+#endif
+
+/** 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 buildvarstring(const char *inbuf, char *outbuf, size_t len, const char *name[], const char *value[])
+{
+	const char *i, *p;
+	char *o;
+	int left = len - 1;
+	int cnt, found;
+
+#ifdef DEBUGMODE
+	if (len <= 0)
+		abort();
+#endif
+
+	for (i = inbuf, o = outbuf; *i; i++)
+	{
+		if (*i == '$')
+		{
+			i++;
+
+			/* $$ = literal $ */
+			if (*i == '$')
+				goto literal;
+
+			if (!isalnum(*i))
+			{
+				/* What do we do with things like '$/' ? -- treat literal */
+				i--;
+				goto literal;
+			}
+			
+			/* find termination */
+			for (p=i; isalnum(*p); p++);
+			
+			/* find variable name in list */
+			found = 0;
+			for (cnt = 0; name[cnt]; cnt++)
+				if (!strncasecmp(name[cnt], i, strlen(name[cnt])))
+				{
+					/* Found */
+					found = 1;
+
+					if (!BadPtr(value[cnt]))
+					{
+						strlcpy(o, value[cnt], left);
+						left -= strlen(value[cnt]); /* may become <0 */
+						if (left <= 0)
+							return; /* return - don't write \0 to 'o'. ensured by strlcpy already */
+						o += strlen(value[cnt]); /* value entirely written */
+					}
+
+					break; /* done */
+				}
+			
+			if (!found)
+			{
+				/* variable name does not exist -- treat literal */
+				i--;
+				goto literal;
+			}
+
+			/* value written. we're done. */
+			i = p - 1;
+			continue;
+		}
+literal:
+		if (!left)
+			break;
+		*o++ = *i;
+		left--;
+		if (!left)
+			break;
+	}
+	*o = '\0';
+}
+
+/** Return the PCRE2 library version in use */
+const char *pcre2_version(void)
+{
+	static char buf[256];
+
+	strlcpy(buf, "PCRE2 ", sizeof(buf));
+	pcre2_config(PCRE2_CONFIG_VERSION, buf+6);
+	return buf;
+}
+
+
+#ifdef _WIN32
+/** POSIX gettimeofday function for Windows (ignoring timezones) */
+int gettimeofday(struct timeval *tp, void *tzp)
+{
+	// This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
+	// until 00:00:00 January 1, 1970
+	static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);
+
+	SYSTEMTIME system_time;
+	FILETIME file_time;
+	uint64_t time;
+
+	GetSystemTime( &system_time );
+	SystemTimeToFileTime( &system_time, &file_time );
+	time =  ((uint64_t)file_time.dwLowDateTime )      ;
+	time += ((uint64_t)file_time.dwHighDateTime) << 32;
+
+	tp->tv_sec  = (long) ((time - EPOCH) / 10000000L);
+	tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
+	return 0;
+}
+#endif
+
+/** Get the numer of characters per line that fit on the terminal (the width) */
+int get_terminal_width(void)
+{
+#if defined(_WIN32) || !defined(TIOCGWINSZ)
+	return 80;
+#else
+	struct winsize w;
+	ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
+	return w.ws_col;
+#endif
+}
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+/** Like strftime() but easier. */
+char *unreal_strftime(const char *str)
+{
+	time_t t;
+	struct tm *tmp;
+	static char buf[512];
+
+	t = time(NULL);
+	tmp = localtime(&t);
+	if (!tmp || !strftime(buf, sizeof(buf), str, tmp))
+	{
+		/* If anything fails bigtime, then return the format string */
+		strlcpy(buf, str, sizeof(buf));
+		return buf;
+	}
+	return buf;
+}
+
+#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 */
+	size--; /* for \0 */
+
+	for (; *src && size; src++)
+	{
+		*dst++ = tolower(*src);
+		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/ircd/src/tls.c b/ircd/src/tls.c
@@ -0,0 +1,1452 @@
+/************************************************************************
+ *   Unreal Internet Relay Chat Daemon, src/tls.c
+ *      (C) 2000 hq.alert.sk (base)
+ *      (C) 2000 Carsten V. Munk <stskeeps@tspre.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.
+ */
+
+/** @file
+ * @brief TLS functions
+ */
+
+#include "unrealircd.h"
+#include "openssl_hostname_validation.h"
+
+#ifdef _WIN32
+#define IDC_PASS                        1166
+extern HINSTANCE hInst;
+extern HWND hwIRCDWnd;
+#endif
+
+#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_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 TLS structures */
+SSL_CTX *ctx_server;
+SSL_CTX *ctx_client;
+
+char *TLSKeyPasswd;
+
+typedef struct {
+	int *size;
+	char **buffer;
+} StreamIO;
+
+MODVAR int tls_client_index = 0;
+
+#ifdef _WIN32
+/** 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) {
+		case WM_INITDIALOG:
+			stream = (StreamIO*)lParam;
+			return TRUE;
+		case WM_COMMAND:
+			if (LOWORD(wParam) == IDCANCEL) {
+				*stream->buffer = NULL;
+				EndDialog(hDlg, IDCANCEL);
+			}
+			else if (LOWORD(wParam) == IDOK) {
+				GetDlgItemText(hDlg, IDC_PASS, *stream->buffer, *stream->size);
+				EndDialog(hDlg, IDOK);
+			}
+			return FALSE;
+		case WM_CLOSE:
+			*stream->buffer = NULL;
+			EndDialog(hDlg, IDCANCEL);
+		default:
+			return FALSE;
+	}
+}
+#endif				
+
+/** Return error string for OpenSSL error.
+ * @param err		OpenSSL error number to lookup
+ * @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.
+ */
+const char *ssl_error_str(int err, int my_errno)
+{
+	static char ssl_errbuf[256];
+	char *ssl_errstr = NULL;
+
+	switch(err)
+	{
+		case SSL_ERROR_NONE:
+			ssl_errstr = "OpenSSL: No error";
+			break;
+		case SSL_ERROR_SSL:
+			ssl_errstr = "Internal OpenSSL error or protocol error";
+			break;
+		case SSL_ERROR_WANT_READ:
+			ssl_errstr = "OpenSSL functions requested a read()";
+			break;
+		case SSL_ERROR_WANT_WRITE:
+			ssl_errstr = "OpenSSL functions requested a write()";
+			break;
+		case SSL_ERROR_WANT_X509_LOOKUP:
+			ssl_errstr = "OpenSSL requested a X509 lookup which didn't arrive";
+			break;
+		case SSL_ERROR_SYSCALL:
+			snprintf(ssl_errbuf, sizeof(ssl_errbuf), "%s", STRERROR(my_errno));
+			ssl_errstr = ssl_errbuf;
+			break;
+		case SSL_ERROR_ZERO_RETURN:
+			ssl_errstr = "Underlying socket operation returned zero";
+			break;
+		case SSL_ERROR_WANT_CONNECT:
+			ssl_errstr = "OpenSSL functions wanted a connect()";
+			break;
+		default:
+			ssl_errstr = "Unknown OpenSSL error (huh?)";
+	}
+	return ssl_errstr;
+}
+
+/** 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;
+	static char beforebuf[1024];
+#ifdef _WIN32
+	StreamIO stream;
+	char passbuf[512];	
+	int passsize = 512;
+#endif
+	if (before)
+	{
+		strlcpy(buf, beforebuf, size);
+		return strlen(buf);
+	}
+#ifndef _WIN32
+	pass = getpass("Password for TLS private key: ");
+#else
+	pass = passbuf;
+	stream.buffer = &pass;
+	stream.size = &passsize;
+	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;
+		TLSKeyPasswd = beforebuf;
+		return (strlen(buf));
+	}
+	return 0;
+}
+
+/** Verify certificate callback. */
+static int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
+{
+	/* We accept the connection. Certificate verifiction takes
+	 * place elsewhere, such as in _verify_link().
+	 */
+	return 1;
+}
+
+/** Get Client pointer by SSL pointer */
+Client *get_client_by_ssl(SSL *ssl)
+{
+	return SSL_get_ex_data(ssl, tls_client_index);
+}
+
+/** Set requested server name as indicated by SNI */
+static void set_client_sni_name(SSL *ssl, char *name)
+{
+	Client *client = get_client_by_ssl(ssl);
+	if (client)
+		safe_strdup(client->local->sni_servername, name);
+}
+
+/** Hostname callback, used for SNI */
+static int ssl_hostname_callback(SSL *ssl, int *unk, void *arg)
+{
+	char *name = (char *)SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+	ConfigItem_sni *sni;
+
+	if (name && (sni = find_sni(name)))
+	{
+		SSL_set_SSL_CTX(ssl, sni->ssl_ctx);
+		set_client_sni_name(ssl, name);
+	}
+
+	return SSL_TLSEXT_ERR_OK;
+}
+
+/** Disable TLS protocols as set by config */
+void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
+{
+	/* OpenSSL has three mechanisms for protocol version control... */
+
+#ifdef HAS_SSL_CTX_SET_SECURITY_LEVEL
+	/* The first one is setting a "security level" as introduced
+	 * by OpenSSL 1.1.0. Some Linux distro's like Ubuntu 20.04
+	 * seemingly compile with -DOPENSSL_TLS_SECURITY_LEVEL=2.
+	 * This means the application (UnrealIRCd) is unable to allow
+	 * TLSv1.0/1.1 even if the application is configured to do so.
+	 * So here we set the level to 1, but -again- ONLY if we are
+	 * configured to allow TLSv1.0 or v1.1, of course.
+	 */
+	if ((tlsoptions->protocols & TLS_PROTOCOL_TLSV1) ||
+	    (tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
+	{
+		SSL_CTX_set_security_level(ctx, 0);
+	}
+#endif
+
+	/* 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 TLS version.
+	 *
+	 * And the new way, which only allows setting a
+	 * minimum and maximum protocol version, using:
+	 * SSL_CTX_set_min_proto_version(... <version>)
+	 * SSL_CTX_set_max_proto_version(....<version>)
+	 *
+	 * We prefer the old way, but because OpenSSL 1.0.1 and
+	 * OS's like Debian use system-wide options we are also
+	 * forced to use the new way... or at least to set a
+	 * minimum protocol version to begin with.
+	 */
+#ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION
+	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1) &&
+	    !(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
+	{
+		SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
+	} else
+	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1))
+	{
+		SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION);
+	} else
+	{
+		SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
+	}
+#endif
+	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); /* always disable SSLv2 */
+	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); /* always disable SSLv3 */
+
+#ifdef SSL_OP_NO_TLSv1
+	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1))
+		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
+#endif
+
+#ifdef SSL_OP_NO_TLSv1_1
+	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
+		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
+#endif
+
+#ifdef SSL_OP_NO_TLSv1_2
+	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_2))
+		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
+#endif
+
+#ifdef SSL_OP_NO_TLSv1_3
+	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_3))
+		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
+#endif
+}
+
+/** 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 TLS context (SSL_CTX) or NULL in case of error.
+ */
+SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
+{
+	SSL_CTX *ctx;
+	char *errstr = NULL;
+
+	if (server)
+		ctx = SSL_CTX_new(SSLv23_server_method());
+	else
+		ctx = SSL_CTX_new(SSLv23_client_method());
+
+	if (!ctx)
+	{
+		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, 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 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).
+		 */
+		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE | (tlsoptions->options & TLSFLAG_FAILIFNOCERT ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0), ssl_verify_callback);
+	}
+	SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+#ifndef SSL_OP_NO_TICKET
+ #error "Your system has an outdated OpenSSL version. Please upgrade OpenSSL."
+#endif
+	SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
+
+	if (SSL_CTX_use_certificate_chain_file(ctx, tlsoptions->certificate_file) <= 0)
+	{
+		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)
+	{
+		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))
+	{
+		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)
+	{
+		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)
+	{
+		unreal_log(ULOG_ERROR, "config", "TLS_INVALID_CIPHERSUITES_LIST", NULL,
+		           "Failed to set TLS ciphersuites list '$tls_ciphersuites_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))
+	{
+		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))
+	{
+		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;
+	}
+
+	if (server)
+		SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+	if (tlsoptions->trusted_ca_file)
+	{
+		if (!SSL_CTX_load_verify_locations(ctx, tlsoptions->trusted_ca_file, NULL))
+		{
+			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;
+		}
+	}
+
+	if (server)
+	{
+#if defined(SSL_CTX_set_ecdh_auto)
+		/* OpenSSL 1.0.x requires us to explicitly turn this on */
+		SSL_CTX_set_ecdh_auto(ctx, 1);
+#elif OPENSSL_VERSION_NUMBER < 0x10100000L
+		/* Even older versions require require setting a fixed curve.
+		 * NOTE: Don't be confused by the <1.1.x check.
+		 * Yes, it must be there. Do not remove it!
+		 */
+		SSL_CTX_set_tmp_ecdh(ctx, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+#else
+		/* If we end up here we don't have SSL_CTX_set_ecdh_auto
+		 * and we are on OpenSSL 1.1.0 or later. We don't need to
+		 * do anything then, since auto ecdh is the default.
+		 */
+#endif
+		/* Let's see if we need to (and can) set specific curves */
+		if (tlsoptions->ecdh_curves)
+		{
+#ifdef HAS_SSL_CTX_SET1_CURVES_LIST
+			if (!SSL_CTX_set1_curves_list(ctx, tlsoptions->ecdh_curves))
+			{
+				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:
+			 */
+			unreal_log(ULOG_ERROR, "config", "BUG_ECDH_CURVES", NULL,
+			           "ecdh-curves specified but not supported by library -- BAD!");
+			goto fail;
+#endif
+		}
+		/* We really want the ECDHE/ECDHE to be generated per-session.
+		 * Added in 2015 for safety. Seems OpenSSL was smart enough
+		 * to make this the default in 2016 after a security advisory.
+		 */
+		SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE|SSL_OP_SINGLE_DH_USE);
+	}
+
+	if (server)
+	{
+		SSL_CTX_set_tlsext_servername_callback(ctx, ssl_hostname_callback);
+	}
+
+	return ctx;
+fail:
+	SSL_CTX_free(ctx);
+	return NULL;
+}
+
+#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: */
+	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_tls(void)
+{
+	ctx_server = init_ctx(iConf.tls_options, 1);
+	if (!ctx_server)
+		return 0;
+	ctx_client = init_ctx(iConf.tls_options, 0);
+	if (!ctx_client)
+		return 0;
+	return 1;
+}
+
+/** Reinitialize TLS server and client contexts - after REHASH -tls
+ */
+int reinit_tls(void)
+{
+	SSL_CTX *tmp;
+	ConfigItem_listen *listen;
+	ConfigItem_sni *sni;
+	ConfigItem_link *link;
+
+	tmp = init_ctx(iConf.tls_options, 1);
+	if (!tmp)
+	{
+		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+		           "TLS Reload failed. See previous errors.");
+		return 0;
+	}
+	if (ctx_server)
+		SSL_CTX_free(ctx_server);
+	ctx_server = tmp; /* activate */
+	
+	tmp = init_ctx(iConf.tls_options, 0);
+	if (!tmp)
+	{
+		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+		           "TLS Reload failed at client context. See previous errors.");
+		return 0;
+	}
+	if (ctx_client)
+		SSL_CTX_free(ctx_client);
+	ctx_client = tmp; /* activate */
+
+	/* listen::tls-options.... */
+	for (listen = conf_listen; listen; listen = listen->next)
+	{
+		if (listen->tls_options)
+		{
+			tmp = init_ctx(listen->tls_options, 1);
+			if (!tmp)
+			{
+				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+					   "TLS Reload failed at listen::tls-options. See previous errors.");
+				return 0;
+			}
+			if (listen->ssl_ctx)
+				SSL_CTX_free(listen->ssl_ctx);
+			listen->ssl_ctx = tmp; /* activate */
+		}
+	}
+
+	/* sni::tls-options.... */
+	for (sni = conf_sni; sni; sni = sni->next)
+	{
+		if (sni->tls_options)
+		{
+			tmp = init_ctx(sni->tls_options, 1);
+			if (!tmp)
+			{
+				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
+					   "TLS Reload failed at sni::tls-options. See previous errors.");
+				return 0;
+			}
+			if (sni->ssl_ctx)
+				SSL_CTX_free(sni->ssl_ctx);
+			sni->ssl_ctx = tmp; /* activate */
+		}
+	}
+
+	/* link::outgoing::tls-options.... */
+	for (link = conf_link; link; link = link->next)
+	{
+		if (link->tls_options)
+		{
+			tmp = init_ctx(link->tls_options, 0);
+			if (!tmp)
+			{
+				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 0;
+			}
+			if (link->ssl_ctx)
+				SSL_CTX_free(link->ssl_ctx);
+			link->ssl_ctx = tmp; /* activate */
+		}
+	}
+
+	return 1;
+}
+
+/** Set SSL connection as nonblocking */
+void SSL_set_nonblocking(SSL *s)
+{
+	BIO_set_nbio(SSL_get_rbio(s),1);
+	BIO_set_nbio(SSL_get_wbio(s),1);
+}
+
+/** 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(client->local->ssl), sizeof(buf));
+	strlcat(buf, "-", sizeof(buf));
+	strlcat(buf, SSL_get_cipher(client->local->ssl), sizeof(buf));
+
+	return buf;
+}
+
+/** Get the applicable ::tls-options block for this local client,
+ * which may be defined in the link block, listen block, or set block.
+ */
+TLSOptions *get_tls_options_for_client(Client *client)
+{
+	if (!client->local)
+		return NULL;
+	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 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->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)
+	{
+		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)
+	{
+		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;
+	}
+
+	SSL_set_fd(client->local->ssl, client->local->fd);
+	SSL_set_connect_state(client->local->ssl);
+	SSL_set_nonblocking(client->local->ssl);
+
+	if (tlsoptions->renegotiate_bytes > 0)
+	{
+		BIO_set_ssl_renegotiate_bytes(SSL_get_rbio(client->local->ssl), tlsoptions->renegotiate_bytes);
+		BIO_set_ssl_renegotiate_bytes(SSL_get_wbio(client->local->ssl), tlsoptions->renegotiate_bytes);
+	}
+
+	if (tlsoptions->renegotiate_timeout > 0)
+	{
+		BIO_set_ssl_renegotiate_timeout(SSL_get_rbio(client->local->ssl), tlsoptions->renegotiate_timeout);
+		BIO_set_ssl_renegotiate_timeout(SSL_get_wbio(client->local->ssl), tlsoptions->renegotiate_timeout);
+	}
+
+	if (client->server && client->server->conf)
+	{
+		/* Client: set hostname for SNI */
+		SSL_set_tlsext_host_name(client->local->ssl, client->server->conf->servername);
+	}
+
+	SetTLS(client);
+
+	switch (unreal_tls_connect(client, fd))
+	{
+		case -1:
+			SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
+			SSL_smart_shutdown(client->local->ssl);
+			SSL_free(client->local->ssl);
+			client->local->ssl = NULL;
+			ClearTLS(client);
+			SetDeadSocket(client);
+			fd_close(fd);
+			fd_unnotify(fd);
+			client->local->fd = -1;
+			--OpenFiles;
+			return;
+		case 0: 
+			SetTLSConnectHandshake(client);
+			return;
+		case 1:
+			return;
+		default:
+			return;
+	}
+
+}
+
+/** 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;
+	unreal_tls_accept(client, fd);
+}
+
+/** Accept an TLS connection - that is: do the TLS handshake */
+int unreal_tls_accept(Client *client, int fd)
+{
+	int ssl_err;
+
+#ifdef MSG_PEEK
+	if (!IsNextCall(client))
+	{
+		char buf[1024];
+		int n;
+		
+		n = recv(fd, buf, sizeof(buf), MSG_PEEK);
+		if ((n >= 8) && !strncmp(buf, "STARTTLS", 8))
+		{
+			char buf[512];
+			snprintf(buf, sizeof(buf),
+				"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_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-TLS command received on TLS-only port. Check your connection settings.\r\n");
+			(void)send(fd, buf, strlen(buf), 0);
+			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-TLS command received on TLS-only port. Check your connection settings.\r\n");
+			(void)send(fd, buf, strlen(buf), 0);
+			return fatal_tls_error(SSL_ERROR_SSL, FUNC_TLS_ACCEPT, ERRNO, client);
+		}
+		if (n > 0)
+			SetNextCall(client);
+	}
+#endif
+	if ((ssl_err = SSL_accept(client->local->ssl)) <= 0)
+	{
+		switch (ssl_err = SSL_get_error(client->local->ssl, ssl_err))
+		{
+			case SSL_ERROR_SYSCALL:
+				if (ERRNO == P_EINTR || ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN)
+				{
+					return 1;
+				}
+				return fatal_tls_error(ssl_err, FUNC_TLS_ACCEPT, ERRNO, client);
+			case SSL_ERROR_WANT_READ:
+				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, unreal_tls_accept_retry, client);
+				return 1;
+			default:
+				return fatal_tls_error(ssl_err, FUNC_TLS_ACCEPT, ERRNO, client);
+		}
+		/* NOTREACHED */
+		return -1;
+	}
+
+	client->local->listener->start_handshake(client);
+
+	return 1;
+}
+
+/** Called by the I/O engine to (re)try to connect to a remote host */
+static void unreal_tls_connect_retry(int fd, int revents, void *data)
+{
+	Client *client = data;
+	unreal_tls_connect(client, fd);
+}
+
+/** Connect to a remote host - that is: connect and do the TLS handshake */
+int unreal_tls_connect(Client *client, int fd)
+{
+	int ssl_err;
+
+	if ((ssl_err = SSL_connect(client->local->ssl)) <= 0)
+	{
+		ssl_err = SSL_get_error(client->local->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(fd, FD_SELECT_READ|FD_SELECT_WRITE, unreal_tls_connect_retry, client);
+					return 0;
+				}
+				return fatal_tls_error(ssl_err, FUNC_TLS_CONNECT, ERRNO, client);
+			case SSL_ERROR_WANT_READ:
+				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, unreal_tls_connect_retry, client);
+				return 0;
+			default:
+				return fatal_tls_error(ssl_err, FUNC_TLS_CONNECT, ERRNO, client);
+		}
+		/* NOTREACHED */
+		return -1;
+	}
+
+	fd_setselect(fd, FD_SELECT_READ | FD_SELECT_WRITE, NULL, client);
+	completed_connection(fd, FD_SELECT_READ | FD_SELECT_WRITE, client);
+
+	return 1;
+}
+
+/** Shutdown a TLS connection (gracefully) */
+int SSL_smart_shutdown(SSL *ssl)
+{
+	char i;
+	int rc = 0;
+
+	for(i = 0; i < 4; i++)
+	{
+		if ((rc = SSL_shutdown(ssl)))
+			break;
+	}
+	return rc;
+}
+
+/**
+ * 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_tls_error(int ssl_error, int where, int my_errno, Client *client)
+{
+	/* don`t alter ERRNO */
+	int errtmp = ERRNO;
+	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))
+		return -1;
+
+	switch(where)
+	{
+		case FUNC_TLS_READ:
+			ssl_func = "SSL_read()";
+			break;
+		case FUNC_TLS_WRITE:
+			ssl_func = "SSL_write()";
+			break;
+		case FUNC_TLS_ACCEPT:
+			ssl_func = "SSL_accept()";
+			break;
+		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);
+	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);
+
+	SetDeadSocket(client);
+	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 == FUNC_TLS_CONNECT)
+	{
+		char extra[256];
+		*extra = '\0';
+		if (ssl_error == SSL_ERROR_SSL)
+		{
+			snprintf(extra, sizeof(extra),
+			         ". Please verify that listen::options::ssl is enabled on port %d in %s's configuration file.",
+			         (client->server && client->server->conf) ? client->server->conf->outgoing.port : -1,
+			         client->name);
+		}
+		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->server && client->server->conf))
+	{
+		/* Either a trusted fully established server (incoming) or an outgoing server link (established or not) */
+		snprintf(buf, sizeof(buf), "%s: %s%s", ssl_func, ssl_errstr, additional_info);
+		lost_server_link(client, buf);
+	}
+
+	if (errtmp)
+	{
+		SET_ERRNO(errtmp);
+		safe_strdup(client->local->error_str, strerror(errtmp));
+	} else {
+		SET_ERRNO(P_EIO);
+		safe_strdup(client->local->error_str, ssl_errstr);
+	}
+
+	/* deregister I/O notification since we don't care anymore. the actual closing of socket will happen later. */
+	if (client->local->fd >= 0)
+		fd_unnotify(client->local->fd);
+
+	return -1;
+}
+
+/** Do a TLS handshake after a STARTTLS, as a client */
+int client_starttls(Client *client)
+{
+	if ((client->local->ssl = SSL_new(ctx_client)) == NULL)
+		goto fail_starttls;
+
+	SetTLS(client);
+
+	SSL_set_fd(client->local->ssl, client->local->fd);
+	SSL_set_nonblocking(client->local->ssl);
+
+	if (client->server && client->server->conf)
+	{
+		/* Client: set hostname for SNI */
+		SSL_set_tlsext_host_name(client->local->ssl, client->server->conf->servername);
+	}
+
+	if (unreal_tls_connect(client, client->local->fd) < 0)
+	{
+		SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
+		SSL_smart_shutdown(client->local->ssl);
+		SSL_free(client->local->ssl);
+		goto fail_starttls;
+	}
+
+	/* HANDSHAKE IN PROGRESS */
+	return 0;
+fail_starttls:
+	/* Failure */
+	sendnumeric(client, ERR_STARTTLS, "STARTTLS failed");
+	client->local->ssl = NULL;
+	ClearTLS(client);
+	SetUnknown(client);
+	return 0; /* hm. we allow to continue anyway. not sure if we want that. */
+}
+
+/** Find the appropriate TLSOptions structure for a client.
+ * 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)
+{
+	ConfigItem_sni *sni;
+	TLSOptions *sslopt = iConf.tls_options; /* default */
+	
+	if (!MyConnect(client) || !IsSecure(client))
+		return NULL;
+
+	/* Different sts-policy depending on SNI: */
+	if (client->local->sni_servername)
+	{
+		sni = find_sni(client->local->sni_servername);
+		if (sni)
+		{
+			sslopt = sni->tls_options;
+		}
+		/* It is perfectly possible that 'name' is not found and 'sni' is NULL,
+		 * if a client used a hostname which we do not know about (eg: 'dummy').
+		 */
+	}
+
+	return sslopt;
+}
+
+/** Verify certificate and make sure the certificate is valid for 'hostname'.
+ * @param ssl: The SSL structure of the client or server
+ * @param hostname: The hostname we should expect the certificate to be valid for
+ * @param errstr: Error will be stored in here (optional)
+ * @returns Returns 1 on success and 0 on error.
+ */
+int verify_certificate(SSL *ssl, const char *hostname, char **errstr)
+{
+	static char buf[512];
+	X509 *cert;
+	int n;
+
+	*buf = '\0';
+
+	if (errstr)
+		*errstr = NULL; /* default */
+
+	if (!ssl)
+	{
+		strlcpy(buf, "Not using TLS", sizeof(buf));
+		if (errstr)
+			*errstr = buf;
+		return 0; /* Cannot verify a non-TLS connection */
+	}
+
+	if (SSL_get_verify_result(ssl) != X509_V_OK)
+	{
+		// FIXME: there are actually about 25+ different possible errors,
+		// this is only the most common one:
+		strlcpy(buf, "Certificate is not issued by a trusted Certificate Authority", sizeof(buf));
+		if (errstr)
+			*errstr = buf;
+		return 0; /* Certificate verify failed */
+	}
+
+	/* Now verify if the name of the certificate matches hostname */
+	cert = SSL_get_peer_certificate(ssl);
+
+	if (!cert)
+	{
+		strlcpy(buf, "No certificate provided", sizeof(buf));
+		if (errstr)
+			*errstr = buf;
+		return 0;
+	}
+
+#ifdef HAS_X509_check_host
+	n = X509_check_host(cert, hostname, strlen(hostname), 0, NULL);
+	X509_free(cert);
+	if (n == 1)
+		return 1; /* Hostname matched. All tests passed. */
+#else
+	/* Fallback code for OpenSSL <1.0.2.
+	 * Wait... 1.0.1 is out of support since January 2017,
+	 * so why do we even support that in 2023 ?
+	 * An well, TODO: ditch this old TLS support in next major UnrealIRCd
+	 * along with all the other old OpenSSL checks in this tls.c :D
+	 * XXX: Actually our HAS_X509_check_host includes openssl/x509v3.h
+	 * which does not exist in 1.0.2 yet either (it is openssl/x509.h there).
+	 * And 1.0.2 is out of support since January 1st, 2020... just saying.
+	 */
+	n = validate_hostname(hostname, cert);
+	X509_free(cert);
+	if (n == MatchFound)
+		return 1; /* Hostname matched. All tests passed. */
+#endif
+
+	/* Certificate is verified but is issued for a different hostname */
+	snprintf(buf, sizeof(buf), "Certificate '%s' is not valid for hostname '%s'",
+		certificate_name(ssl), hostname);
+	if (errstr)
+		*errstr = buf;
+	return 0;
+}
+
+/** Grab the certificate name */
+const char *certificate_name(SSL *ssl)
+{
+	static char buf[384];
+	X509 *cert;
+	X509_NAME *n;
+
+	if (!ssl)
+		return NULL;
+
+	cert = SSL_get_peer_certificate(ssl);
+	if (!cert)
+		return NULL;
+
+	n = X509_get_subject_name(cert);
+	if (n)
+	{
+		buf[0] = '\0';
+		X509_NAME_oneline(n, buf, sizeof(buf));
+		X509_free(cert);
+		return buf;
+	} else {
+		X509_free(cert);
+		return NULL;
+	}
+}
+
+/** Check if any weak ciphers are in use */
+int cipher_check(SSL_CTX *ctx, char **errstr)
+{
+	SSL *ssl;
+	static char errbuf[256];
+	int i;
+	const char *cipher;
+
+	*errbuf = '\0'; // safety
+
+	if (errstr)
+		*errstr = errbuf;
+
+	/* there isn't an SSL_CTX_get_cipher_list() unfortunately. */
+	ssl = SSL_new(ctx);
+	if (!ssl)
+	{
+		snprintf(errbuf, sizeof(errbuf), "Could not create TLS structure");
+		return 0;
+	}
+
+	/* Very weak */
+	i = 0;
+	while ((cipher = SSL_get_cipher_list(ssl, i++)))
+	{
+		if (strstr(cipher, "DES-"))
+		{
+			snprintf(errbuf, sizeof(errbuf), "DES is enabled but is a weak cipher");
+			SSL_free(ssl);
+			return 0;
+		}
+		else if (strstr(cipher, "3DES-"))
+		{
+			snprintf(errbuf, sizeof(errbuf), "3DES is enabled but is a weak cipher");
+			SSL_free(ssl);
+			return 0;
+		}
+		else if (strstr(cipher, "RC4-"))
+		{
+			snprintf(errbuf, sizeof(errbuf), "RC4 is enabled but is a weak cipher");
+			SSL_free(ssl);
+			return 0;
+		}
+		else if (strstr(cipher, "NULL-"))
+		{
+			snprintf(errbuf, sizeof(errbuf), "NULL cipher provides no encryption");
+			SSL_free(ssl);
+			return 0;
+		}
+	}
+
+	SSL_free(ssl);
+	return 1;
+}
+
+/** 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;
+	RSA *rsa_key;
+	int key_length;
+	static char errbuf[256];
+
+	*errbuf = '\0'; // safety
+
+	if (errstr)
+		*errstr = errbuf;
+
+	/* there isn't an SSL_CTX_get_cipher_list() unfortunately. */
+	ssl = SSL_new(ctx);
+	if (!ssl)
+	{
+		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 TLS certificate");
+		SSL_free(ssl);
+		return 0;
+	}
+
+	public_key = X509_get_pubkey(cert);
+	if (!public_key)
+	{
+		/* Now this is unexpected.. */
+		config_warn("certificate_quality_check(): could not check public key !? BUG?");
+		SSL_free(ssl);
+		return 1;
+	}
+	rsa_key = EVP_PKEY_get1_RSA(public_key);
+	if (!rsa_key)
+	{
+		/* Not an RSA key, then we are done. */
+		EVP_PKEY_free(public_key);
+		SSL_free(ssl);
+		return 1;
+	}
+	key_length = RSA_size(rsa_key) * 8;
+
+	EVP_PKEY_free(public_key);
+	RSA_free(rsa_key);
+	SSL_free(ssl);
+
+	if (key_length < 2048)
+	{
+		snprintf(errbuf, sizeof(errbuf), "Your TLS certificate key is only %d bits, which is insecure", key_length);
+		return 0;
+	}
+
+#endif
+	return 1;
+}
+
+const char *spki_fingerprint_ex(X509 *x509_cert);
+
+/** Return the SPKI Fingerprint for a client.
+ *
+ * This is basically the same output as
+ * openssl x509 -noout -in certificate.pem -pubkey | openssl asn1parse -noout -inform pem -out public.key
+ * openssl dgst -sha256 -binary public.key | openssl enc -base64
+ * ( from https://tools.ietf.org/html/draft-ietf-websec-key-pinning-21#appendix-A )
+ */
+const char *spki_fingerprint(Client *cptr)
+{
+	X509 *x509_cert = NULL;
+	const char *ret;
+
+	if (!MyConnect(cptr) || !cptr->local->ssl)
+		return NULL;
+
+	x509_cert = SSL_get_peer_certificate(cptr->local->ssl);
+	if (!x509_cert)
+		return NULL;
+	ret = spki_fingerprint_ex(x509_cert);
+	X509_free(x509_cert);
+	return ret;
+}
+
+const char *spki_fingerprint_ex(X509 *x509_cert)
+{
+	unsigned char *der_cert = NULL, *p;
+	int der_cert_len, n;
+	static char retbuf[256];
+	unsigned char checksum[SHA256_DIGEST_LENGTH];
+
+	memset(retbuf, 0, sizeof(retbuf));
+
+	der_cert_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509_cert), NULL);
+	if ((der_cert_len > 0) && (der_cert_len < 16384))
+	{
+		der_cert = p = safe_alloc(der_cert_len);
+		n = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509_cert), &p);
+
+		if ((n > 0) && ((p - der_cert) == der_cert_len))
+		{
+			/* 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.
+			 */
+			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));
+			safe_free(der_cert);
+			return retbuf; /* SUCCESS */
+		}
+		safe_free(der_cert);
+	}
+	return NULL;
+}
+
+/** Returns 1 if the client is using an outdated protocol or cipher, 0 otherwise */
+int outdated_tls_client(Client *client)
+{
+	TLSOptions *tlsoptions = get_tls_options_for_client(client);
+	char buf[1024], *name, *p;
+	const char *client_protocol = SSL_get_version(client->local->ssl);
+	const char *client_ciphersuite = SSL_get_cipher(client->local->ssl);
+
+	if (!tlsoptions)
+		return 0; /* odd.. */
+
+	strlcpy(buf, tlsoptions->outdated_protocols, sizeof(buf));
+	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
+	{
+		if (match_simple(name, client_protocol))
+			 return 1; /* outdated protocol */
+	}
+
+	strlcpy(buf, tlsoptions->outdated_ciphers, sizeof(buf));
+	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
+	{
+		if (match_simple(name, client_ciphersuite))
+			return 1; /* outdated cipher */
+	}
+
+	return 0; /* OK, not outdated */
+}
+
+/** Returns the expanded string used for set::outdated-tls-policy::user-message etc. */
+const char *outdated_tls_client_build_string(const char *pattern, Client *client)
+{
+	static char buf[512];
+	const char *name[3], *value[3];
+	const char *str;
+
+	str = SSL_get_version(client->local->ssl);
+	name[0] = "protocol";
+	value[0] = str ? str : "???";
+
+	str = SSL_get_cipher(client->local->ssl);
+	name[1] = "cipher";
+	value[1] = str ? str : "???";
+
+	name[2] = value[2] = NULL;
+
+	buildvarstring(pattern, buf, sizeof(buf), name, value);
+	return buf;
+}
+
+int check_certificate_expiry_ctx(SSL_CTX *ctx, char **errstr)
+{
+#if !defined(HAS_ASN1_TIME_diff) || !defined(HAS_X509_get0_notAfter)
+	return 0;
+#else
+	static char errbuf[512];
+	SSL *ssl;
+	X509 *cert;
+	const ASN1_TIME *cert_expiry_time;
+	int days_expiry = 0, seconds_expiry = 0;
+	long duration;
+
+	*errstr = NULL;
+
+	ssl = SSL_new(ctx);
+	if (!ssl)
+		return 0;
+
+	cert = SSL_get_certificate(ssl);
+	if (!cert)
+	{
+		SSL_free(ssl);
+		return 0;
+	}
+
+	/* get certificate time */
+	cert_expiry_time = X509_get0_notAfter(cert);
+
+	/* calculate difference */
+	ASN1_TIME_diff(&days_expiry, &seconds_expiry, cert_expiry_time, NULL);
+	duration = (days_expiry * 86400) + seconds_expiry;
+
+	/* certificate expiry? */
+	if ((days_expiry > 0) || (seconds_expiry > 0))
+	{
+		snprintf(errbuf, sizeof(errbuf), "certificate expired %s ago", pretty_time_val(duration));
+		SSL_free(ssl);
+		*errstr = errbuf;
+		return 1;
+	} else
+	/* or near-expiry? */
+	if (((days_expiry < 0) || (seconds_expiry < 0)) && (days_expiry > -7))
+	{
+		snprintf(errbuf, sizeof(errbuf), "certificate will expire in %s", pretty_time_val(0 - duration));
+		SSL_free(ssl);
+		*errstr = errbuf;
+		return 1;
+	}
+
+	/* All good */
+	SSL_free(ssl);
+	return 0;
+#endif
+}
+
+void check_certificate_expiry_tlsoptions_and_warn(TLSOptions *tlsoptions)
+{
+	SSL_CTX *ctx;
+	int ret;
+	char *errstr = NULL;
+
+	ctx = init_ctx(tlsoptions, 1);
+	if (!ctx)
+		return;
+
+	if (check_certificate_expiry_ctx(ctx, &errstr))
+	{
+		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);
+}
+
+EVENT(tls_check_expiry)
+{
+	ConfigItem_listen *listen;
+	ConfigItem_sni *sni;
+	ConfigItem_link *link;
+
+	/* set block */
+	check_certificate_expiry_tlsoptions_and_warn(iConf.tls_options);
+
+	for (listen = conf_listen; listen; listen = listen->next)
+		if (listen->tls_options)
+			check_certificate_expiry_tlsoptions_and_warn(listen->tls_options);
+
+	/* sni::tls-options.... */
+	for (sni = conf_sni; sni; sni = sni->next)
+		if (sni->tls_options)
+			check_certificate_expiry_tlsoptions_and_warn(sni->tls_options);
+
+	/* link::outgoing::tls-options.... */
+	for (link = conf_link; link; link = link->next)
+		if (link->tls_options)
+			check_certificate_expiry_tlsoptions_and_warn(link->tls_options);
+}
diff --git a/ircd/src/unrealdb.c b/ircd/src/unrealdb.c
@@ -0,0 +1,1174 @@
+/************************************************************************
+ * src/unrealdb.c
+ * Functions for dealing easily with (encrypted) database files.
+ * (C) Copyright 2021 Bram Matthys (Syzop)
+ *
+ * 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"
+
+/** @file
+ * @brief UnrealIRCd database API - see @ref UnrealDBFunctions
+ */
+
+/**
+ * Read and write to database files - encrypted and unencrypted.
+ * This provides functions for dealing with (encrypted) database files.
+ * - File format: https://www.unrealircd.org/docs/Dev:UnrealDB
+ * - KDF: Argon2: https://en.wikipedia.org/wiki/Argon2
+ * - Cipher: XChaCha20 from libsodium: https://libsodium.gitbook.io/doc/advanced/stream_ciphers/xchacha20
+ * @defgroup UnrealDBFunctions Database functions
+ */
+
+/* Benchmarking results:
+ * On standard hardware as of 2021 speeds of 150-200 megabytes per second
+ * are achieved realisticly for both reading and writing encrypted
+ * database files. Of course, YMMV, depending on record sizes, CPU,
+ * and I/O speeds of the underlying hardware.
+ */
+
+/* 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 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).
+ */
+#define UNREALDB_WRITE_V1
+
+/* If a key is specified, it must be this size */
+#define UNREALDB_KEY_LEN	crypto_secretstream_xchacha20poly1305_KEYBYTES
+
+/** Default 'time cost' for Argon2id */
+#define UNREALDB_ARGON2_DEFAULT_TIME_COST             4
+/** Default 'memory cost' for Argon2id. Note that 15 means 1<<15=32M */
+#define UNREALDB_ARGON2_DEFAULT_MEMORY_COST           15
+/** Default 'parallelism cost' for Argon2id. */
+#define UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST      2
+
+#ifdef _WIN32
+/* Ignore this warning on Windows as it is a false positive */
+#pragma warning(disable : 6029)
+#endif
+
+/* 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;
+
+/** Set error condition on unrealdb 'c' (internal function).
+ * @param c		The unrealdb file handle
+ * @param pattern	The format string
+ * @param ...		Any parameters to the format string
+ * @note this will also set c->failed=1 to prevent any further reading/writing.
+ */
+static void unrealdb_set_error(UnrealDB *c, UnrealDBError errcode, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	char buf[512];
+	va_start(vl, pattern);
+	vsnprintf(buf, sizeof(buf), pattern, vl);
+	va_end(vl);
+	if (c)
+	{
+		c->error_code = errcode;
+		safe_strdup(c->error_string, buf);
+	}
+	unrealdb_last_error_code = errcode;
+	safe_strdup(unrealdb_last_error_string, buf);
+}
+
+/** Free a UnrealDB struct (internal function). */
+static void unrealdb_free(UnrealDB *c)
+{
+	unrealdb_free_config(c->config);
+	safe_free(c->error_string);
+	safe_free_sensitive(c);
+}
+
+static int unrealdb_kdf(UnrealDB *c, Secret *secr)
+{
+	if (c->config->kdf != UNREALDB_KDF_ARGON2ID)
+	{
+		unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Unknown KDF 0x%x", (int)c->config->kdf);
+		return 0;
+	}
+	/* Need to run argon2 to generate key */
+	if (argon2id_hash_raw(c->config->t_cost,
+			      1 << c->config->m_cost,
+			      c->config->p_cost,
+			      secr->password, strlen(secr->password),
+			      c->config->salt, c->config->saltlen,
+			      c->config->key, c->config->keylen) != ARGON2_OK)
+	{
+		/* out of memory or some other very unusual error */
+		unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Could not generate argon2 hash - out of memory or something weird?");
+		return 0;
+	}
+	return 1;
+}
+
+/**
+ * @addtogroup UnrealDBFunctions
+ * @{
+ */
+
+/** Get the error string for last failed unrealdb operation.
+ * @returns The error string
+ * @note Use the return value only for displaying of errors
+ *       to the end-user.
+ *       For programmatically checking of error conditions
+ *       use unrealdb_get_error_code() instead.
+ */
+const char *unrealdb_get_error_string(void)
+{
+	return unrealdb_last_error_string;
+}
+
+/** Get the error code for last failed unrealdb operation
+ * @returns An UNREAL_DB_ERROR_*
+ */
+UnrealDBError unrealdb_get_error_code(void)
+{
+	return unrealdb_last_error_code;
+}
+
+/** Open an unrealdb file.
+ * @param filename	The filename to open
+ * @param mode		Either UNREALDB_MODE_READ or UNREALDB_MODE_WRITE
+ * @param secret_block	The name of the secret xx { } block (so NOT the actual password!!)
+ * @returns A pointer to a UnrealDB structure that can be used in subsequent calls for db read/writes,
+ *          and finally unrealdb_close(). Or NULL in case of failure.
+ * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
+ *       unrealdb_get_error_string() to see the actual error.
+ */
+UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block)
+{
+	UnrealDB *c = safe_alloc_sensitive(sizeof(UnrealDB));
+	char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
+	char buf[32]; /* don't change this */
+	Secret *secr=NULL;
+	SecretCache *dbcache;
+	int cached = 0;
+	char *err;
+
+	errno = 0;
+
+	if ((mode != UNREALDB_MODE_READ) && (mode != UNREALDB_MODE_WRITE))
+	{
+		unrealdb_set_error(c, UNREALDB_ERROR_API, "unrealdb_open request for neither read nor write");
+		goto unrealdb_open_fail;
+	}
+
+	/* Do this check early, before we try to create any file */
+	if (secret_block != NULL)
+	{
+		secr = find_secret(secret_block);
+		if (!secr)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Secret block '%s' not found or invalid", secret_block);
+			goto unrealdb_open_fail;
+		}
+
+		if (!valid_secret_password(secr->password, &err))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Password in secret block '%s' does not meet complexity requirements", secr->name);
+			goto unrealdb_open_fail;
+		}
+	}
+
+	c->mode = mode;
+	c->fd = fopen(filename, (c->mode == UNREALDB_MODE_WRITE) ? "wb" : "rb");
+	if (!c->fd)
+	{
+		if (errno == ENOENT)
+			unrealdb_set_error(c, UNREALDB_ERROR_FILENOTFOUND, "File not found: %s", strerror(errno));
+		else
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Could not open file: %s", strerror(errno));
+		goto unrealdb_open_fail;
+	}
+
+	if (secret_block == NULL)
+	{
+		if (mode == UNREALDB_MODE_READ)
+		{
+			/* READ: read header, if any, lots of fallback options here... */
+			if (fgets(buf, sizeof(buf), c->fd))
+			{
+				if (!strncmp(buf, "UnrealIRCd-DB-Crypted", 21))
+				{
+					unrealdb_set_error(c, UNREALDB_ERROR_CRYPTED, "file is encrypted but no password provided");
+					goto unrealdb_open_fail;
+				} else
+				if (!strcmp(buf, "UnrealIRCd-DB-v1"))
+				{
+					/* Skip over the 32 byte header, directly to the creationtime */
+					if (fseek(c->fd, 32L, SEEK_SET) < 0)
+					{
+						unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "file header too short");
+						goto unrealdb_open_fail;
+					}
+					if (!unrealdb_read_int64(c, &c->creationtime))
+					{
+						unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (A4)");
+						goto unrealdb_open_fail;
+					}
+					/* SUCCESS = fallthrough */
+				} else
+				if (!strncmp(buf, "UnrealIRCd-DB", 13)) /* any other version than v1 = not supported by us */
+				{
+					/* We don't support this format, so refuse clearly */
+					unrealdb_set_error(c, UNREALDB_ERROR_HEADER,
+							   "Unsupported version of database. Is this database perhaps created on "
+							   "a new version of UnrealIRCd and are you trying to use it on an older "
+							   "UnrealIRCd version? (Downgrading is not supported!)");
+					goto unrealdb_open_fail;
+				} else
+				{
+					/* Old db format, no header, seek back to beginning */
+					fseek(c->fd, 0L, SEEK_SET);
+					/* SUCCESS = fallthrough */
+				}
+			}
+		} else {
+#ifdef UNREALDB_WRITE_V1
+			/* WRITE */
+			memset(buf, 0, sizeof(buf));
+			snprintf(buf, sizeof(buf), "UnrealIRCd-DB-v1");
+			if ((fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf)) ||
+			    !unrealdb_write_int64(c, TStime()))
+			{
+				unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (A1)");
+				goto unrealdb_open_fail;
+			}
+#endif
+		}
+		safe_free(unrealdb_last_error_string);
+		unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
+		return c;
+	}
+
+	c->crypted = 1;
+
+	if (c->mode == UNREALDB_MODE_WRITE)
+	{
+		/* Write the:
+		 * - generic header ("UnrealIRCd-DB" + some zeroes)
+		 * - the salt
+		 * - the crypto header
+		 */
+		memset(buf, 0, sizeof(buf));
+		snprintf(buf, sizeof(buf), "UnrealIRCd-DB-Crypted-v1");
+		if (fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (1)");
+			goto unrealdb_open_fail; /* Unable to write header nr 1 */
+		}
+
+		if (secr->cache && secr->cache->config)
+		{
+			/* Use first found cached config for this secret */
+			c->config = unrealdb_copy_config(secr->cache->config);
+			cached = 1;
+		} else {
+			/* Create a new config */
+			c->config = safe_alloc(sizeof(UnrealDBConfig));
+			c->config->kdf = UNREALDB_KDF_ARGON2ID;
+			c->config->t_cost = UNREALDB_ARGON2_DEFAULT_TIME_COST;
+			c->config->m_cost = UNREALDB_ARGON2_DEFAULT_MEMORY_COST;
+			c->config->p_cost = UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST;
+			c->config->saltlen = UNREALDB_SALT_LEN;
+			c->config->salt = safe_alloc(c->config->saltlen);
+			randombytes_buf(c->config->salt, c->config->saltlen);
+			c->config->cipher = UNREALDB_CIPHER_XCHACHA20;
+			c->config->keylen = UNREALDB_KEY_LEN;
+			c->config->key = safe_alloc_sensitive(c->config->keylen);
+		}
+
+		if (c->config->kdf == 0)
+			abort();
+
+		/* Write KDF and cipher parameters */
+		if ((fwrite(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
+		    (fwrite(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
+		    (fwrite(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
+		    (fwrite(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
+		    (fwrite(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)) ||
+		    (fwrite(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen) ||
+		    (fwrite(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
+		    (fwrite(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (2)");
+			goto unrealdb_open_fail;
+		}
+		
+		if (cached)
+		{
+#ifdef DEBUGMODE
+			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
+			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))
+			{
+				/* Error already set by called function */
+				goto unrealdb_open_fail;
+			}
+		}
+
+		crypto_secretstream_xchacha20poly1305_init_push(&c->st, header, c->config->key);
+		if (fwrite(header, 1, sizeof(header), c->fd) != sizeof(header))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (3)");
+			goto unrealdb_open_fail; /* Unable to write crypto header */
+		}
+		if (!unrealdb_write_str(c, "UnrealIRCd-DB-Crypted-Now") ||
+		    !unrealdb_write_int64(c, TStime()))
+		{
+			/* error is already set by unrealdb_write_str() */
+			goto unrealdb_open_fail; /* Unable to write crypto header */
+		}
+		if (!cached)
+			unrealdb_add_to_secret_cache(secr, c->config);
+	} else
+	{
+		char *validate = NULL;
+		
+		/* Read file header */
+		if (fread(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file (file too small)");
+			goto unrealdb_open_fail; /* Header too short */
+		}
+		if (strncmp(buf, "UnrealIRCd-DB-Crypted-v1", 24))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file");
+			goto unrealdb_open_fail; /* Invalid header */
+		}
+		c->config = safe_alloc(sizeof(UnrealDBConfig));
+		if ((fread(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
+		    (fread(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
+		    (fread(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
+		    (fread(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
+		    (fread(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid");
+			goto unrealdb_open_fail;
+		}
+		if (c->config->kdf != UNREALDB_KDF_ARGON2ID) 
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown KDF 0x%x", (int)c->config->kdf);
+			goto unrealdb_open_fail;
+		}
+		if (c->config->saltlen > 1024)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (saltlen=%d)", (int)c->config->saltlen);
+			goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
+		}
+		c->config->salt = safe_alloc(c->config->saltlen);
+		if (fread(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (2)");
+			goto unrealdb_open_fail; /* Header too short (read II) */
+		}
+		if ((fread(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
+		    (fread(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid (3)");
+			goto unrealdb_open_fail;
+		}
+		if (c->config->cipher != UNREALDB_CIPHER_XCHACHA20)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown cipher 0x%x", (int)c->config->cipher);
+			goto unrealdb_open_fail;
+		}
+		if (c->config->keylen > 1024)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (keylen=%d)", (int)c->config->keylen);
+			goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
+		}
+		c->config->key = safe_alloc_sensitive(c->config->keylen);
+
+		dbcache = find_secret_cache(secr, c->config);
+		if (dbcache)
+		{
+			/* Use cached key, no need to run expensive argon2.. */
+			memcpy(c->config->key, dbcache->config->key, c->config->keylen);
+#ifdef DEBUGMODE
+			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
+			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))
+			{
+				/* Error already set by called function */
+				goto unrealdb_open_fail;
+			}
+		}
+		/* key is now set */
+		if (fread(header, 1, sizeof(header), c->fd) != sizeof(header))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (3)");
+			goto unrealdb_open_fail; /* Header too short */
+		}
+		if (crypto_secretstream_xchacha20poly1305_init_pull(&c->st, header, c->config->key) != 0)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Crypto error - invalid password or corrupt file");
+			goto unrealdb_open_fail; /* Unusual */
+		}
+		/* Now to validate the key we read a simple string */
+		if (!unrealdb_read_str(c, &validate))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
+			goto unrealdb_open_fail; /* Incorrect key, probably */
+		}
+		if (strcmp(validate, "UnrealIRCd-DB-Crypted-Now"))
+		{
+			safe_free(validate);
+			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
+			goto unrealdb_open_fail; /* Incorrect key, probably */
+		}
+		safe_free(validate);
+		if (!unrealdb_read_int64(c, &c->creationtime))
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (4)");
+			goto unrealdb_open_fail;
+		}
+		unrealdb_add_to_secret_cache(secr, c->config);
+	}
+	sodium_stackzero(1024);
+	safe_free(unrealdb_last_error_string);
+	unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
+	return c;
+
+unrealdb_open_fail:
+	if (c->fd)
+		fclose(c->fd);
+	unrealdb_free(c);
+	sodium_stackzero(1024);
+	return NULL;
+}
+
+/** Close an unrealdb file.
+ * @param c	The struct pointing to an unrealdb file
+ * @returns 1 if the final close was graceful and 0 if not (eg: out of disk space on final flush).
+ *          In all cases the file handle is closed and 'c' is freed.
+ * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
+ *       unrealdb_get_error_string() to see the actual error.
+ */
+int unrealdb_close(UnrealDB *c)
+{
+	/* If this is file was opened for writing then flush the remaining data with a TAG_FINAL
+	 * (or push a block of 0 bytes with TAG_FINAL)
+	 */
+	if (c->crypted && (c->mode == UNREALDB_MODE_WRITE))
+	{
+		char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
+		unsigned long long out_len = sizeof(buf_out);
+
+		crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, c->buflen, NULL, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL);
+		if (out_len > 0)
+		{
+			if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
+			{
+				/* Final write failed, error condition */
+				unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+				fclose(c->fd);
+				unrealdb_free(c);
+				return 0;
+			}
+		}
+	}
+
+	if (fclose(c->fd) != 0)
+	{
+		/* Final close failed, error condition */
+		unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+		unrealdb_free(c);
+		return 0;
+	}
+
+	unrealdb_free(c);
+	return 1;
+}
+
+/** Test if there is something fatally wrong with the configuration of the DB file,
+ * in which case we suggest to reject the /rehash or boot request.
+ * This tests for "wrong password" and for "trying to open an encrypted file without providing a password"
+ * which are clear configuration errors on the admin part.
+ * It does NOT test for any other conditions such as missing file, corrupted file, etc.
+ * since that usually needs different handling anyway, as they are I/O issues and don't
+ * always have a clear solution (if any is needed at all).
+ * @param filename	The filename to open
+ * @param secret_block	The name of the secret xx { } block (so NOT the actual password!!)
+ * @returns 1 if the password was wrong, 0 for any other error or succes.
+ */
+char *unrealdb_test_db(const char *filename, char *secret_block)
+{
+	static char buf[512];
+	UnrealDB *db = unrealdb_open(filename, UNREALDB_MODE_READ, secret_block);
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_PASSWORD)
+		{
+			snprintf(buf, sizeof(buf), "Incorrect password specified in secret block '%s' for file %s",
+				secret_block, filename);
+			return buf;
+		}
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_CRYPTED)
+		{
+			snprintf(buf, sizeof(buf), "File '%s' is encrypted but no secret block provided for it",
+				filename);
+			return buf;
+		}
+		return NULL;
+	} else
+	{
+		unrealdb_close(db);
+	}
+	return NULL;
+}
+
+/** @} */
+
+/** Write to an unrealdb file.
+ * This code uses extra buffering to avoid writing small records
+ * and wasting for example a 32 bytes encryption block for a 8 byte write request.
+ * @param c		Database file open for writing
+ * @param wbuf		The data to be written (plaintext)
+ * @param len		The length of the data to be written
+ * @note This is the internal function, api users must use one of the
+ *       following functions instead:
+ *       unrealdb_write_int64(), unrealdb_write_int32(), unrealdb_write_int16(),
+ *       unrealdb_write_char(), unrealdb_write_str().
+ */
+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;
+	const char *buf = wbuf;
+
+	if (c->error_code)
+		return 0;
+
+	if (c->mode != UNREALDB_MODE_WRITE)
+	{
+		unrealdb_set_error(c, UNREALDB_ERROR_API, "Write operation requested on a file opened for reading");
+		return 0;
+	}
+
+	if (!c->crypted)
+	{
+		if (fwrite(buf, 1, len, c->fd) != len)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+			return 0;
+		}
+		return 1;
+	}
+
+	do {
+		if (c->buflen + len < UNREALDB_CRYPT_FILE_CHUNK_SIZE)
+		{
+			/* New data fits in new buffer. Then we are done with writing.
+			 * This can happen both for the first block (never write)
+			 * or the remainder (tail after X writes which is less than
+			 * UNREALDB_CRYPT_FILE_CHUNK_SIZE, a common case)
+			 */
+			memcpy(c->buf + c->buflen, buf, len);
+			c->buflen += len;
+			break; /* Done! */
+		} else
+		{
+			/* Fill up c->buf with UNREALDB_CRYPT_FILE_CHUNK_SIZE
+			 * Note that 'av_bytes' can be 0 here if c->buflen
+			 * happens to be exactly UNREALDB_CRYPT_FILE_CHUNK_SIZE,
+			 * that's okay.
+			 */
+			int av_bytes = UNREALDB_CRYPT_FILE_CHUNK_SIZE - c->buflen;
+			if (av_bytes > 0)
+				memcpy(c->buf + c->buflen, buf, av_bytes);
+			buf += av_bytes;
+			len -= av_bytes;
+		}
+		if (crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, UNREALDB_CRYPT_FILE_CHUNK_SIZE, NULL, 0, 0) != 0)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Failed to encrypt a block");
+			return 0;
+		}
+		if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+			return 0;
+		}
+		/* Buffer is now flushed for sure */
+		c->buflen = 0;
+	} while(len > 0);
+
+	return 1;
+}
+
+/**
+ * @addtogroup UnrealDBFunctions
+ * @{
+ */
+
+/** Write a string to a database file.
+ * @param c	UnrealDB file struct
+ * @param x	String to be written
+ * @note  This function can write a string up to 65534
+ *        characters, which should be plenty for usage
+ *        in UnrealIRCd.
+ *        Note that 'x' can safely be NULL.
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_str(UnrealDB *c, const char *x)
+{
+	uint16_t len;
+
+	/* First, make sure the string is not too large (would be very unusual, though) */
+	if (x)
+	{
+		int stringlen = strlen(x);
+		if (stringlen >= 0xffff)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_API,
+					   "unrealdb_write_str(): string has length %d, while maximum allowed is 65534",
+					   stringlen);
+			return 0;
+		}
+		len = stringlen;
+	} else {
+		len = 0xffff;
+	}
+
+	/* Write length to db as 16 bit integer */
+	if (!unrealdb_write_int16(c, len))
+		return 0;
+
+	/* Then, write the actual string (if any), without NUL terminator. */
+	if ((len > 0) && (len < 0xffff))
+	{
+		if (!unrealdb_write(c, x, len))
+			return 0;
+	}
+
+	return 1;
+}
+
+/** Write a 64 bit integer to a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_int64(UnrealDB *c, uint64_t t)
+{
+#ifdef NATIVE_BIG_ENDIAN
+	t = bswap_64(t);
+#endif
+	return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** Write a 32 bit integer to a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_int32(UnrealDB *c, uint32_t t)
+{
+#ifdef NATIVE_BIG_ENDIAN
+	t = bswap_32(t);
+#endif
+	return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** Write a 16 bit integer to a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_int16(UnrealDB *c, uint16_t t)
+{
+#ifdef NATIVE_BIG_ENDIAN
+	t = bswap_16(t);
+#endif
+	return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** Write a single 8 bit character to a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_char(UnrealDB *c, char t)
+{
+	return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** @} */
+
+/** Read from an UnrealDB file.
+ * This code deals with buffering, block reading, etc. so the caller doesn't
+ * have to worry about that.
+ * @param c		Database file open for reading
+ * @param rbuf		The data to be read (will be plaintext)
+ * @param len		The length of the data to be read
+ * @note This is the internal function, api users must use one of the
+ *       following functions instead:
+ *       unrealdb_read_int64(), unrealdb_read_int32(), unrealdb_read_int16(),
+ *       unrealdb_read_char(), unrealdb_read_str().
+ */
+static int unrealdb_read(UnrealDB *c, void *rbuf, int len)
+{
+	char buf_in[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
+	unsigned long long out_len;
+	unsigned char tag;
+	size_t rlen;
+	char *buf = rbuf;
+
+	if (c->error_code)
+		return 0;
+
+	if (c->mode != UNREALDB_MODE_READ)
+	{
+		unrealdb_set_error(c, UNREALDB_ERROR_API, "Read operation requested on a file opened for writing");
+		return 0;
+	}
+
+	if (!c->crypted)
+	{
+		rlen = fread(buf, 1, len, c->fd);
+		if (rlen < len)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file (want:%d, got:%d bytes)",
+				len, (int)rlen);
+			return 0;
+		}
+		return 1;
+	}
+
+	/* First, fill 'buf' up with what we have */
+	if (c->buflen)
+	{
+		int av_bytes = MIN(c->buflen, len);
+		memcpy(buf, c->buf, av_bytes);
+		if (c->buflen - av_bytes > 0)
+			memmove(c->buf, c->buf + av_bytes, c->buflen - av_bytes);
+		c->buflen -= av_bytes;
+		len -= av_bytes;
+		if (len == 0)
+			return 1; /* Request completed entirely */
+		buf += av_bytes;
+	}
+
+	if (c->buflen != 0)
+		abort();
+
+	/* If we get here then we need to read some data */
+	do {
+		rlen = fread(buf_in, 1, sizeof(buf_in), c->fd);
+		if (rlen == 0)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file??");
+			return 0;
+		}
+		if (crypto_secretstream_xchacha20poly1305_pull(&c->st, c->buf, &out_len, &tag, buf_in, rlen, NULL, 0) != 0)
+		{
+			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Failed to decrypt a block - either corrupt or wrong key");
+			return 0;
+		}
+
+		/* This should be impossible as this is guaranteed not to happen by libsodium */
+		if (out_len > UNREALDB_CRYPT_FILE_CHUNK_SIZE)
+			abort();
+
+		if (len > out_len)
+		{
+			/* We eat a big block, but want more in next iteration of the loop */
+			memcpy(buf, c->buf, out_len);
+			buf += out_len;
+			len -= out_len;
+		} else {
+			/* This is the only (or last) block we need, we are satisfied */
+			memcpy(buf, c->buf, len);
+			c->buflen = out_len - len;
+			if (c->buflen > 0)
+				memmove(c->buf, c->buf+len, c->buflen);
+			return 1; /* Done */
+		}
+	} while(!feof(c->fd));
+
+	unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file?");
+	return 0;
+}
+
+/**
+ * @addtogroup UnrealDBFunctions
+ * @{
+ */
+
+/** Read a 64 bit integer from a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_int64(UnrealDB *c, uint64_t *t)
+{
+	if (!unrealdb_read(c, t, sizeof(uint64_t)))
+		return 0;
+#ifdef NATIVE_BIG_ENDIAN
+	*t = bswap_64(*t);
+#endif
+	return 1;
+}
+
+/** Read a 32 bit integer from a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_int32(UnrealDB *c, uint32_t *t)
+{
+	if (!unrealdb_read(c, t, sizeof(uint32_t)))
+		return 0;
+#ifdef NATIVE_BIG_ENDIAN
+	*t = bswap_32(*t);
+#endif
+	return 1;
+}
+
+/** Read a 16 bit integer from a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_int16(UnrealDB *c, uint16_t *t)
+{
+	if (!unrealdb_read(c, t, sizeof(uint16_t)))
+		return 0;
+#ifdef NATIVE_BIG_ENDIAN
+	*t = bswap_16(*t);
+#endif
+	return 1;
+}
+
+/** Read a string from a database file.
+ * @param c    UnrealDB file struct
+ * @param x    Pointer to string pointer
+ * @note  This function will allocate memory for the data
+ *        and set the string pointer to this value.
+ *        If a NULL pointer was written via write_str()
+ *        then read_str() may also return a NULL pointer.
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_str(UnrealDB *c, char **x)
+{
+	uint16_t len;
+	size_t size;
+
+	*x = NULL;
+
+	if (!unrealdb_read_int16(c, &len))
+		return 0;
+
+	if (len == 0xffff)
+	{
+		/* Magic value meaning NULL */
+		*x = NULL;
+		return 1;
+	}
+
+	if (len == 0)
+	{
+		/* 0 means empty string */
+		safe_strdup(*x, "");
+		return 1;
+	}
+
+	if (len > 10000)
+		return 0;
+
+	size = len;
+	*x = safe_alloc(size + 1);
+	if (!unrealdb_read(c, *x, size))
+	{
+		safe_free(*x);
+		return 0;
+	}
+	(*x)[len] = 0;
+	return 1;
+}
+
+/** Read a single 8 bit character from a database file.
+ * @param c	UnrealDB file struct
+ * @param t	The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_char(UnrealDB *c, char *t)
+{
+	if (!unrealdb_read(c, t, sizeof(char)))
+		return 0;
+	return 1;
+}
+
+/** @} */
+
+#if 0
+void fatal_error(FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	va_start(vl, pattern);
+	vfprintf(stderr, pattern, vl);
+	va_end(vl);
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Exiting with failure\n");
+	exit(-1);
+}
+
+void unrealdb_test_simple(void)
+{
+	UnrealDB *c;
+	char *key = "test";
+	int i;
+	char *str;
+
+
+	fprintf(stderr, "*** WRITE TEST ***\n");
+	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
+	if (!c)
+		fatal_error("Could not open test db for writing: %s", strerror(errno));
+
+	if (!unrealdb_write_str(c, "Hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
+		fatal_error("Error on write 1");
+	if (!unrealdb_write_str(c, "This is a test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
+		fatal_error("Error on write 2");
+	if (!unrealdb_close(c))
+		fatal_error("Error on close");
+	c = NULL;
+	fprintf(stderr, "Done with writing.\n\n");
+
+	fprintf(stderr, "*** READ TEST ***\n");
+	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
+	if (!c)
+		fatal_error("Could not open test db for reading: %s", strerror(errno));
+	if (!unrealdb_read_str(c, &str))
+		fatal_error("Error on read 1: %s", c->error_string);
+	fprintf(stderr, "Got: '%s'\n", str);
+	safe_free(str);
+	if (!unrealdb_read_str(c, &str))
+		fatal_error("Error on read 2: %s", c->error_string);
+	fprintf(stderr, "Got: '%s'\n", str);
+	safe_free(str);
+	if (!unrealdb_close(c))
+		fatal_error("Error on close");
+	fprintf(stderr, "All good.\n");
+}
+
+#define UNREALDB_SPEED_TEST_BYTES 100000000
+void unrealdb_test_speed(char *key)
+{
+	UnrealDB *c;
+	int i, len;
+	char *str;
+	char buf[1024];
+	int written = 0, read = 0;
+	struct timeval tv_start, tv_end;
+
+	fprintf(stderr, "*** WRITE TEST ***\n");
+	gettimeofday(&tv_start, NULL);
+	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
+	if (!c)
+		fatal_error("Could not open test db for writing: %s", strerror(errno));
+	do {
+		
+		len = getrandom32() % 500;
+		//gen_random_alnum(buf, len);
+		for (i=0; i < len; i++)
+			buf[i] = 'a';
+		buf[i] = '\0';
+		if (!unrealdb_write_str(c, buf))
+			fatal_error("Error on writing a string of %d size", len);
+		written += len + 2; /* +2 for length */
+	} while(written < UNREALDB_SPEED_TEST_BYTES);
+	if (!unrealdb_close(c))
+		fatal_error("Error on close");
+	c = NULL;
+	gettimeofday(&tv_end, NULL);
+	fprintf(stderr, "Done with writing: %lld usecs\n\n",
+		(long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
+
+	fprintf(stderr, "*** READ TEST ***\n");
+	gettimeofday(&tv_start, NULL);
+	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
+	if (!c)
+		fatal_error("Could not open test db for reading: %s", strerror(errno));
+	do {
+		if (!unrealdb_read_str(c, &str))
+			fatal_error("Error on read at position %d/%d: %s", read, written, c->error_string);
+		read += strlen(str) + 2; /* same calculation as earlier */
+		safe_free(str);
+	} while(read < written);
+	if (!unrealdb_close(c))
+		fatal_error("Error on close");
+	gettimeofday(&tv_end, NULL);
+	fprintf(stderr, "Done with reading: %lld usecs\n\n",
+		(long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
+
+	fprintf(stderr, "All good.\n");
+}
+
+void unrealdb_test(void)
+{
+	//unrealdb_test_simple();
+	fprintf(stderr, "**** TESTING ENCRYPTED ****\n");
+	unrealdb_test_speed("test");
+	fprintf(stderr, "**** TESTING UNENCRYPTED ****\n");
+	unrealdb_test_speed(NULL);
+}
+#endif
+
+/** TODO: document and implement
+ */
+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 */
+}
+
+UnrealDBConfig *unrealdb_copy_config(UnrealDBConfig *src)
+{
+	UnrealDBConfig *dst = safe_alloc(sizeof(UnrealDBConfig));
+
+	dst->kdf = src->kdf;
+	dst->t_cost = src->t_cost;
+	dst->m_cost = src->m_cost;
+	dst->p_cost = src->p_cost;
+	dst->saltlen = src->saltlen;
+	dst->salt = safe_alloc(dst->saltlen);
+	memcpy(dst->salt, src->salt, dst->saltlen);
+
+	dst->cipher = src->cipher;
+	dst->keylen = src->keylen;
+	if (dst->keylen)
+	{
+		dst->key = safe_alloc_sensitive(dst->keylen);
+		memcpy(dst->key, src->key, dst->keylen);
+	}
+
+	return dst;
+}
+
+UnrealDBConfig *unrealdb_get_config(UnrealDB *db)
+{
+	return unrealdb_copy_config(db->config);
+}
+
+void unrealdb_free_config(UnrealDBConfig *c)
+{
+	if (!c)
+		return;
+	safe_free(c->salt);
+	safe_free_sensitive(c->key);
+	safe_free(c);
+}
+
+static int unrealdb_config_identical(UnrealDBConfig *one, UnrealDBConfig *two)
+{
+	/* NOTE: do not compare 'key' here or all cache lookups will fail */
+	if ((one->kdf == two->kdf) &&
+	    (one->t_cost == two->t_cost) &&
+	    (one->m_cost == two->m_cost) &&
+	    (one->p_cost == two->p_cost) &&
+	    (one->saltlen == two->saltlen) &&
+	    (memcmp(one->salt, two->salt, one->saltlen) == 0) &&
+	    (one->cipher == two->cipher) &&
+	    (one->keylen == two->keylen))
+	{
+		return 1;
+	}
+	return 0;
+}
+
+static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg)
+{
+	SecretCache *c;
+
+	for (c = secr->cache; c; c = c->next)
+	{
+		if (unrealdb_config_identical(c->config, cfg))
+		{
+			c->cache_hit = TStime();
+			return c;
+		}
+	}
+	return NULL;
+}
+
+static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg)
+{
+	SecretCache *c = find_secret_cache(secr, cfg);
+
+	if (c)
+		return; /* Entry already exists in cache */
+
+	/* New entry, add! */
+	c = safe_alloc(sizeof(SecretCache));
+	c->config = unrealdb_copy_config(cfg);
+	c->cache_hit = TStime();
+	AddListItem(c, secr->cache);
+}
+
+#ifdef DEBUGMODE
+#define UNREALDB_EXPIRE_SECRET_CACHE_AFTER	1200
+#else
+#define UNREALDB_EXPIRE_SECRET_CACHE_AFTER	86400
+#endif
+
+/** Expire cached secret entries (previous Argon2 runs) */
+EVENT(unrealdb_expire_secret_cache)
+{
+	Secret *s;
+	SecretCache *c, *c_next;
+	for (s = secrets; s; s = s->next)
+	{
+		for (c = s->cache; c; c = c_next)
+		{
+			c_next = c->next;
+			if (c->cache_hit < TStime() - UNREALDB_EXPIRE_SECRET_CACHE_AFTER)
+			{
+				DelListItem(c, s->cache);
+				free_secret_cache(c);
+			}
+		}
+	}
+}
diff --git a/ircd/src/unrealircdctl.c b/ircd/src/unrealircdctl.c
@@ -0,0 +1,268 @@
+/************************************************************************
+ *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/unrealircdctl
+ *   (c) 2022- Bram Matthys and The UnrealIRCd team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers. 
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief UnrealIRCd Control
+ */
+#include "unrealircd.h"
+
+#ifdef _WIN32
+ #define UNREALCMD "unrealircdctl"
+#else
+ #define UNREALCMD "./unrealircd"
+#endif
+
+
+extern int procio_client(const char *command, int auto_color_logs);
+
+void unrealircdctl_usage(const char *program_name)
+{
+	printf("Usage: %s <option>\n"
+	       "Where <option> is one of:\n"
+	       "rehash         - Rehash the server (reread configuration files)\n"
+	       "reloadtls      - Reload the SSL/TLS certificates\n"
+	       "status         - Show current status of server\n"
+	       "module-status  - Show currently loaded modules\n"
+	       "mkpasswd       - Hash a password\n"
+	       "gencloak       - Display 3 random cloak keys\n"
+	       "spkifp         - Display SPKI Fingerprint\n"
+	       "\n", program_name);
+	exit(-1);
+}
+
+void unrealircdctl_rehash(void)
+{
+	if (procio_client("REHASH", 1) == 0)
+	{
+		printf("Rehashed succesfully.\n");
+		exit(0);
+	}
+	printf("Rehash failed.\n");
+	exit(1);
+}
+
+void unrealircdctl_reloadtls(void)
+{
+	if (procio_client("REHASH -tls", 1) == 0)
+	{
+		printf("Reloading of TLS certificates successful.\n");
+		exit(0);
+	}
+	printf("Reloading TLS certificates failed.\n");
+	exit(1);
+}
+
+void unrealircdctl_status(void)
+{
+	if (procio_client("STATUS", 2) == 0)
+	{
+		printf("UnrealIRCd is up and running.\n");
+		exit(0);
+	}
+	printf("UnrealIRCd status report failed.\n");
+	exit(1);
+}
+
+void unrealircdctl_module_status(void)
+{
+	if (procio_client("MODULES", 2) == 0)
+		exit(0);
+	printf("Could not retrieve complete module list.\n");
+	exit(1);
+}
+
+void unrealircdctl_mkpasswd(int argc, char *argv[])
+{
+	AuthenticationType type;
+	const char *result;
+	char *p = argv[2];
+
+	type = Auth_FindType(NULL, p);
+	if (type == -1)
+	{
+		type = AUTHTYPE_ARGON2;
+	} else {
+		p = argv[3];
+	}
+	if (BadPtr(p))
+	{
+#ifndef _WIN32
+		p = getpass("Enter password to hash: ");
+#else
+		printf("ERROR: You should specify a password to hash");
+		exit(1);
+#endif
+	}
+	if ((type == AUTHTYPE_UNIXCRYPT) && (strlen(p) > 8))
+	{
+		/* Hmmm.. is this warning really still true (and always) ?? */
+		printf("WARNING: Password truncated to 8 characters due to 'crypt' algorithm. "
+		       "You are suggested to use the 'argon2' algorithm instead.");
+		p[8] = '\0';
+	}
+	if (!(result = Auth_Hash(type, p))) {
+		printf("Failed to generate password. Deprecated method? Try 'argon2' instead.\n");
+		exit(0);
+	}
+	printf("Encrypted password is: %s\n", result);
+	exit(0);
+}
+
+void unrealircdctl_gencloak(int argc, char *argv[])
+{
+	#define GENERATE_CLOAKKEY_LEN 80 /* Length of cloak keys to generate. */
+	char keyBuf[GENERATE_CLOAKKEY_LEN + 1];
+	int keyNum;
+	int charIndex;
+
+	short has_upper;
+	short has_lower;
+	short has_num;
+
+	printf("Here are 3 random cloak keys that you can copy-paste to your configuration file:\n\n");
+
+	printf("set {\n\tcloak-keys {\n");
+	for (keyNum = 0; keyNum < 3; ++keyNum)
+	{
+		has_upper = 0;
+		has_lower = 0;
+		has_num = 0;
+
+		for (charIndex = 0; charIndex < sizeof(keyBuf)-1; ++charIndex)
+		{
+			switch (getrandom8() % 3)
+			{
+				case 0: /* Uppercase. */
+					keyBuf[charIndex] = (char)('A' + (getrandom8() % ('Z' - 'A')));
+					has_upper = 1;
+					break;
+				case 1: /* Lowercase. */
+					keyBuf[charIndex] = (char)('a' + (getrandom8() % ('z' - 'a')));
+					has_lower = 1;
+					break;
+				case 2: /* Digit. */
+					keyBuf[charIndex] = (char)('0' + (getrandom8() % ('9' - '0')));
+					has_num = 1;
+					break;
+			}
+		}
+		keyBuf[sizeof(keyBuf)-1] = '\0';
+
+		if (has_upper && has_lower && has_num)
+			printf("\t\t\"%s\";\n", keyBuf);
+		else
+			/* Try again. For this reason, keyNum must be signed. */
+			keyNum--;
+	}
+	printf("\t}\n}\n\n");
+	exit(0);
+}
+
+void unrealircdctl_spkifp(int argc, char *argv[])
+{
+	char *file = argv[2];
+	SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
+	SSL *ssl;
+	X509 *cert;
+	const char *spkifp;
+
+	if (!ctx)
+	{
+		printf("Internal failure while initializing SSL/TLS library context\n");
+		exit(1);
+	}
+
+	if (!file)
+	{
+		printf("NOTE: This script uses the default certificate location (any set::tls settings\n"
+		       "are ignored). If this is not what you want then specify a certificate\n"
+		       "explicitly like this: %s spkifp conf/tls/example.pem\n\n", UNREALCMD);
+		safe_strdup(file, "tls/server.cert.pem");
+		convert_to_absolute_path(&file, CONFDIR);
+	}
+
+	if (!file_exists(file))
+	{
+		printf("Could not open certificate: %s\n"
+		       "You can specify a certificate like this: %s spkifp conf/tls/example.pem\n",
+		       UNREALCMD, file);
+		exit(1);
+	}
+
+	if (SSL_CTX_use_certificate_chain_file(ctx, file) <= 0)
+	{
+		printf("Could not read certificate '%s'\n", file);
+		exit(1);
+	}
+
+	ssl = SSL_new(ctx);
+	if (!ssl)
+	{
+		printf("Something went wrong when generating the SPKI fingerprint.\n");
+		exit(1);
+	}
+
+	cert = SSL_get_certificate(ssl);
+	spkifp = spki_fingerprint_ex(cert);
+	printf("The SPKI fingerprint for certificate '%s' is:\n"
+	       "%s\n"
+	       "\n"
+	       "You normally add this password on the other side of the link as:\n"
+	       "password \"%s\" { spkifp; };\n"
+	       "\n",
+	       file, spkifp, spkifp);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+#ifdef _WIN32
+	chdir(".."); /* go up one level from "bin" */
+	init_winsock();
+#else
+	alarm(20); /* 20 second timeout */
+#endif
+	dbuf_init();
+	init_random();
+	early_init_tls();
+
+	if (argc == 1)
+		unrealircdctl_usage(argv[0]);
+
+	if (!strcmp(argv[1], "rehash"))
+		unrealircdctl_rehash();
+	else if (!strcmp(argv[1], "reloadtls"))
+		unrealircdctl_reloadtls();
+	else if (!strcmp(argv[1], "status"))
+		unrealircdctl_status();
+	else if (!strcmp(argv[1], "module-status"))
+		unrealircdctl_module_status();
+	else if (!strcmp(argv[1], "mkpasswd"))
+		unrealircdctl_mkpasswd(argc, argv);
+	else if (!strcmp(argv[1], "gencloak"))
+		unrealircdctl_gencloak(argc, argv);
+	else if (!strcmp(argv[1], "spkifp") || !strcmp(argv[1], "spki"))
+		unrealircdctl_spkifp(argc, argv);
+	else
+		unrealircdctl_usage(argv[0]);
+	exit(0);
+}
diff --git a/ircd/src/url_curl.c b/ircd/src/url_curl.c
@@ -0,0 +1,340 @@
+/*
+ *   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
+{
+	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[CURL_ERROR_SIZE];
+	time_t cachetime;
+};
+
+CURLM *multihandle = NULL;
+
+Download *downloads = NULL;
+
+void url_free_handle(Download *handle)
+{
+	DelListItem(handle, downloads);
+	if (handle->file_fd)
+		fclose(handle->file_fd);
+	safe_free(handle->url);
+	safe_free(handle);
+}
+
+void url_cancel_handle_by_callback_data(void *ptr)
+{
+	Download *d, *d_next;
+
+	for (d = downloads; d; d = d_next)
+	{
+		d_next = d->next;
+		if (d->callback_data == ptr)
+		{
+			d->callback = NULL;
+			d->callback_data = NULL;
+		}
+	}
+}
+
+/*
+ * Sets up all of the SSL options necessary to support HTTPS/FTPS
+ * transfers.
+ */
+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 (handle->callback == NULL)
+			{
+				/* Request is already canceled, we don't care about the result, just clean up */
+				remove(handle->filename);
+			} else
+			if (msg->data.result == CURLE_OK)
+			{
+				if (code == 304 || (last_mod != -1 && last_mod <= handle->cachetime))
+				{
+					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;
+	}
+	AddListItem(handle, downloads);
+
+	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/ircd/src/url_unreal.c b/ircd/src/url_unreal.c
@@ -0,0 +1,1086 @@
+/*
+ *   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 */
+
+/* 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 url_cancel_handle_by_callback_data(void *ptr)
+{
+	Download *d, *d_next;
+
+	for (d = downloads; d; d = d_next)
+	{
+		d_next = d->next;
+		if (d->callback_data == ptr)
+		{
+			d->callback = NULL;
+			d->callback_data = NULL;
+		}
+	}
+}
+
+void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	va_start(vl, pattern);
+	vsnprintf(handle->errorbuf, sizeof(handle->errorbuf), pattern, vl);
+	va_end(vl);
+	if (handle->callback)
+		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->callback)
+		; /* No special action, request was cancelled */
+	else if (!handle->got_response)
+		handle->callback(url, NULL, "HTTPS response not received", 0, handle->callback_data);
+	else
+	{
+		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;
+	if (handle->callback)
+		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--;
+
+	if (handle->callback)
+	{
+		/* If still an outstanding request (not cancelled), follow the redirect.. */
+		download_file_async(handle->redirect_new_location, handle->cachetime, handle->callback, handle->callback_data,
+				    handle->url, handle->redirects_remaining);
+	}
+	/* Don't call the hook, just free this, the new redirect from above will call the hook later */
+	url_free_handle(handle);
+}
+
+/** 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/ircd/src/user.c b/ircd/src/user.c
@@ -0,0 +1,1007 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/user.c
+ *   Copyright (C) 1990 Jarkko Oikarinen and
+ *                      University of Oulu, Computing Center
+ *
+ *   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 User-related functions
+ */
+
+/* s_user.c 2.74 2/8/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
+
+#include "unrealircd.h"
+
+MODVAR int dontspread = 0;
+
+/** Inhibit labeled/response reply. This means it will result in an empty ACK
+ *  because we cannot handle the command via labeled-reponse. Rare, but
+ *  possible in for example /TRACE which multiple servers handle and which
+ *  has no clear end.
+ */
+MODVAR int labeled_response_inhibit = 0;
+
+/** Force a labeled/response reply (of course, only if a label is present etc.).
+ * This is used in case the "a remote server is handling the request" was
+ * incorrect and there were 0 responses. This is the case for PRIVMSG.
+ * It will force an empty ACK.
+ * No, this cannot be merged with the other one. Also, the other one
+ * (labeled_response_inhibit) has priority over this one (labeled_response_force).
+ */
+MODVAR int labeled_response_force = 0;
+
+/** Inhibit labeled/response END. Only used in /LIST.
+ */
+MODVAR int labeled_response_inhibit_end = 0;
+
+/** Set to 1 if an UTF8 incompatible nick character set is in use */
+MODVAR int non_utf8_nick_chars_in_use = 0;
+
+/** Set a new vhost on the user
+ * @param client	The client (user)
+ * @param host		The new vhost
+ */
+void iNAH_host(Client *client, const char *host)
+{
+	if (!client->user)
+		return;
+
+	userhost_save_current(client);
+
+	safe_strdup(client->user->virthost, host);
+	if (MyConnect(client))
+		sendto_server(NULL, 0, 0, NULL, ":%s SETHOST :%s", client->id, client->user->virthost);
+	client->umodes |= UMODE_SETHOST|UMODE_HIDE;
+
+	userhost_changed(client);
+}
+
+/** Convert a user mode string to a bitmask - only used by config.
+ * @param umode		The user mode string
+ * @returns the user mode value (long)
+ */
+long set_usermode(const char *umode)
+{
+	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 (um = usermodes; um; um = um->next)
+				{
+					if (um->letter == *m)
+					{
+						if (what == MODE_ADD)
+							newumode |= um->mode;
+						else
+							newumode &= ~um->mode;
+					}
+				}
+		}
+	}
+
+	return newumode;
+}
+
+/** Convert a target pointer to an 8 bit hash, used for target limiting. */
+unsigned char hash_target(void *target)
+{
+	uintptr_t v = (uintptr_t)target;
+	/* ircu does >> 16 and 8 but since our sizeof(Client) is
+	 * towards 512 (and hence the alignment), that bit is useless.
+	 * So we do >> 17 and 9.
+	 */
+	return (unsigned char)((v >> 17) ^ (v >> 9));
+}
+
+/** target_limit_exceeded
+ * @param client   The client.
+ * @param target The target client
+ * @param name   The name of the target client (used in the error message)
+ * @retval Returns 1 if too many targets were addressed (do not send!), 0 if ok to send.
+ */
+int target_limit_exceeded(Client *client, void *target, const char *name)
+{
+	u_char hash = hash_target(target);
+	int i;
+	int max_concurrent_conversations_users, max_concurrent_conversations_new_user_every;
+	FloodSettings *settings;
+
+	if (ValidatePermissionsForPath("immune:max-concurrent-conversations",client,NULL,NULL,NULL))
+		return 0;
+
+	if (client->local->targets[0] == hash)
+		return 0;
+
+	settings = get_floodsettings_for_user(client, FLD_CONVERSATIONS);
+	max_concurrent_conversations_users = settings->limit[FLD_CONVERSATIONS];
+	max_concurrent_conversations_new_user_every = settings->period[FLD_CONVERSATIONS];
+
+	if (max_concurrent_conversations_users <= 0)
+		return 0; /* unlimited */
+
+	/* Shouldn't be needed, but better check here than access out-of-bounds memory */
+	if (max_concurrent_conversations_users > MAXCCUSERS)
+		max_concurrent_conversations_users = MAXCCUSERS;
+
+	for (i = 1; i < max_concurrent_conversations_users; i++)
+	{
+		if (client->local->targets[i] == hash)
+		{
+			/* Move this target hash to the first position */
+			memmove(&client->local->targets[1], &client->local->targets[0], i);
+			client->local->targets[0] = hash;
+			return 0;
+		}
+	}
+
+	if (TStime() < client->local->nexttarget)
+	{
+		/* Target limit reached */
+		client->local->nexttarget += 2; /* punish them some more */
+		add_fake_lag(client, 2000); /* lag them up as well */
+
+		flood_limit_exceeded_log(client, "max-concurrent-conversations");
+		sendnumeric(client, ERR_TARGETTOOFAST, name, (long long)(client->local->nexttarget - TStime()));
+
+		return 1;
+	}
+
+	/* If not set yet or in the very past, then adjust it.
+	 * This is so client->local->nexttarget=0 will become client->local->nexttarget=currenttime-...
+	 */
+	if (TStime() > client->local->nexttarget +
+	    (max_concurrent_conversations_users * max_concurrent_conversations_new_user_every))
+	{
+		client->local->nexttarget = TStime() - ((max_concurrent_conversations_users-1) * max_concurrent_conversations_new_user_every);
+	}
+
+	client->local->nexttarget += max_concurrent_conversations_new_user_every;
+
+	/* Add the new target (first move the rest, then add us at position 0 */
+	memmove(&client->local->targets[1], &client->local->targets[0], max_concurrent_conversations_users - 1);
+	client->local->targets[0] = hash;
+
+	return 0;
+}
+
+/** De-duplicate a string of "x,x,y,z" to "x,y,z"
+ * @param buffer	Input string
+ * @returns The new de-duplicated buffer (temporary storage, only valid until next canonize call)
+ */
+char *canonize(const char *buffer)
+{
+	static char cbuf[2048];
+	char tbuf[2048];
+	char *s, *t, *cp = cbuf;
+	int  l = 0;
+	char *p = NULL, *p2;
+
+	*cp = '\0';
+
+	if (!buffer)
+		return NULL;
+
+	strlcpy(tbuf, buffer, sizeof(tbuf));
+	for (s = strtoken(&p, tbuf, ","); s; s = strtoken(&p, NULL, ","))
+	{
+		if (l)
+		{
+			for (p2 = NULL, t = strtoken(&p2, cbuf, ","); t;
+			    t = strtoken(&p2, NULL, ","))
+				if (!mycmp(s, t))
+					break;
+				else if (p2)
+					p2[-1] = ',';
+		}
+		else
+			t = NULL;
+		if (!t)
+		{
+			if (l)
+				*(cp - 1) = ',';
+			else
+				l = 1;
+			strcpy(cp, s);
+			if (p)
+				cp += (p - s);
+		}
+		else if (p2)
+			p2[-1] = ',';
+	}
+	return cbuf;
+}
+
+/** Get user modes as a string.
+ * @param client	The client
+ * @returns string of user modes (temporary storage)
+ */
+const char *get_usermode_string(Client *client)
+{
+	static char buf[128];
+	Umode *um;
+
+	strlcpy(buf, "+", sizeof(buf));
+	for (um = usermodes; um; um = um->next)
+		if (client->umodes & um->mode)
+			strlcat_letter(buf, um->letter, sizeof(buf));
+
+	return buf;
+}
+
+/** Get user modes as a string - buffer is specified.
+ * @param client	The client
+ * @param buf		The buffer to write to
+ * @param buflen	The size of the buffer
+ * @returns string of user modes (buf)
+ */
+const char *get_usermode_string_r(Client *client, char *buf, size_t buflen)
+{
+	Umode *um;
+
+	strlcpy(buf, "+", buflen);
+	for (um = usermodes; um; um = um->next)
+		if (client->umodes & um->mode)
+			strlcat_letter(buf, um->letter, buflen);
+
+	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)
+ */
+const char *get_usermode_string_raw(long umodes)
+{
+	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));
+
+	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
+ * @param buf		The buffer to write to
+ * @param buflen	The size of the buffer
+ * @returns string of user modes (buf)
+ */
+const char *get_usermode_string_raw_r(long umodes, char *buf, size_t buflen)
+{
+	Umode *um;
+
+	strlcpy(buf, "+", buflen);
+	for (um = usermodes; um; um = um->next)
+		if (umodes & um->mode)
+			strlcat_letter(buf, um->letter, buflen);
+
+	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, const char *snomask)
+{
+	int what = MODE_ADD; /* keep this an int. -- Syzop */
+	const char *p;
+	int i;
+
+	if (snomask == NULL)
+	{
+		remove_all_snomasks(client);
+		return;
+	}
+	
+	for (p = snomask; p && *p; p++)
+	{
+		switch (*p)
+		{
+			case '+':
+				what = MODE_ADD;
+				break;
+			case '-':
+				what = MODE_DEL;
+				break;
+			default:
+				if (what == MODE_ADD)
+				{
+					if (!isalpha(*p) || !is_valid_snomask(*p))
+						continue;
+					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.
+ * @author Originally by avalon.
+ */
+void build_umode_string(Client *client, long old, long sendmask, char *umode_buf)
+{
+	Umode *um;
+	long flag;
+	char *m;
+	int what = MODE_NULL;
+
+	/*
+	 * build a string in umode_buf to represent the change in the user's
+	 * mode between the new (client->flag) and 'old'.
+	 */
+	m = umode_buf;
+	*m = '\0';
+	for (um = usermodes; um; um = um->next)
+	{
+		flag = um->mode;
+		if (MyUser(client) && !(flag & sendmask))
+			continue;
+		if ((flag & old) && !(client->umodes & flag))
+		{
+			if (what == MODE_DEL)
+				*m++ = um->letter;
+			else
+			{
+				what = MODE_DEL;
+				*m++ = '-';
+				*m++ = um->letter;
+			}
+		}
+		else if (!(flag & old) && (client->umodes & flag))
+		{
+			if (what == MODE_ADD)
+				*m++ = um->letter;
+			else
+			{
+				what = MODE_ADD;
+				*m++ = '+';
+				*m++ = um->letter;
+			}
+		}
+	}
+	*m = '\0';
+}
+
+/** Send usermode change to other servers.
+ * @param client	The client
+ * @param show_to_user	Set to 1 to show the MODE change to the user
+ * @param old		The old user modes set on the client
+ */
+void send_umode_out(Client *client, int show_to_user, long old)
+{
+	Client *acptr;
+	char buf[512];
+
+	build_umode_string(client, old, SEND_UMODES, buf);
+
+	list_for_each_entry(acptr, &server_list, special_node)
+	{
+		if ((acptr != client) && (acptr != client->direction) && *buf)
+		{
+			sendto_one(acptr, NULL, ":%s UMODE2 %s",
+			           client->name, buf);
+		}
+	}
+
+	if (MyUser(client) && show_to_user)
+	{
+		build_umode_string(client, old, ALL_UMODES, buf);
+		if (*buf)
+			sendto_one(client, NULL, ":%s MODE %s :%s", client->name, client->name, buf);
+	}
+}
+
+static MaxTarget *maxtargets = NULL; /**< For set::max-targets-per-command configuration */
+
+static void maxtarget_add_sorted(MaxTarget *n)
+{
+	MaxTarget *e;
+
+	if (!maxtargets)
+	{
+		maxtargets = n;
+		return;
+	}
+
+	for (e = maxtargets; e; e = e->next)
+	{
+		if (strcmp(n->cmd, e->cmd) < 0)
+		{
+			/* Insert us before */
+			if (e->prev)
+				e->prev->next = n;
+			else
+				maxtargets = n; /* new head */
+			n->prev = e->prev;
+
+			n->next = e;
+			e->prev = n;
+			return;
+		}
+		if (!e->next)
+		{
+			/* Append us at end */
+			e->next = n;
+			n->prev = e;
+			return;
+		}
+	}
+}
+
+/** Find a maxtarget structure for a cmd (internal) */
+MaxTarget *findmaxtarget(const char *cmd)
+{
+	MaxTarget *m;
+
+	for (m = maxtargets; m; m = m->next)
+		if (!strcasecmp(m->cmd, cmd))
+			return m;
+	return NULL;
+}
+
+/** Set a maximum targets per command restriction */
+void setmaxtargets(const char *cmd, int limit)
+{
+	MaxTarget *m = findmaxtarget(cmd);
+	if (!m)
+	{
+		char cmdupper[64];
+		strlcpy(cmdupper, cmd, sizeof(cmdupper));
+		strtoupper(cmdupper);
+		m = safe_alloc(sizeof(MaxTarget));
+		safe_strdup(m->cmd, cmdupper);
+		maxtarget_add_sorted(m);
+	}
+	m->limit = limit;
+}
+
+/** Free all set::max-targets-per-command configuration (internal) */
+void freemaxtargets(void)
+{
+	MaxTarget *m, *m_next;
+
+	for (m = maxtargets; m; m = m_next)
+	{
+		m_next = m->next;
+		safe_free(m->cmd);
+		safe_free(m);
+	}
+	maxtargets = NULL;
+}
+
+/** Return the maximum number of targets permitted for a command */
+int max_targets_for_command(const char *cmd)
+{
+	MaxTarget *m = findmaxtarget(cmd);
+	if (m)
+		return m->limit;
+	return 1; /* default to 1 */
+}
+
+void set_isupport_targmax(void)
+{
+	char buf[512], tbuf[64];
+	MaxTarget *m;
+
+	*buf = '\0';
+	for (m = maxtargets; m; m = m->next)
+	{
+		if (m->limit == MAXTARGETS_MAX)
+			snprintf(tbuf, sizeof(tbuf), "%s:", m->cmd);
+		else
+			snprintf(tbuf, sizeof(tbuf), "%s:%d", m->cmd, m->limit);
+
+		if (*buf)
+			strlcat(buf, ",", sizeof(buf));
+		strlcat(buf, tbuf, sizeof(buf));
+	}
+	ISupportSet(NULL, "TARGMAX", buf);
+}
+
+/** Called between config test and config run */
+void set_targmax_defaults(void)
+{
+	/* Free existing... */
+	freemaxtargets();
+
+	/* Set the defaults */
+	setmaxtargets("PRIVMSG", 4);
+	setmaxtargets("NOTICE", 1);
+	setmaxtargets("TAGMSG", 1);
+	setmaxtargets("NAMES", 1); // >1 is not supported
+	setmaxtargets("WHOIS", 1);
+	setmaxtargets("WHOWAS", 1); // >1 is not supported
+	setmaxtargets("KICK", 4);
+	setmaxtargets("LIST", MAXTARGETS_MAX);
+	setmaxtargets("JOIN", MAXTARGETS_MAX);
+	setmaxtargets("PART", MAXTARGETS_MAX);
+	setmaxtargets("SAJOIN", MAXTARGETS_MAX);
+	setmaxtargets("SAPART", MAXTARGETS_MAX);
+	setmaxtargets("KILL", MAXTARGETS_MAX);
+	setmaxtargets("DCCALLOW", MAXTARGETS_MAX);
+	/* The following 3 are space-separated (and actually the previous
+	 * mentioned DCCALLOW is both space-and-comma separated).
+	 * It seems most IRCd's don't list space-separated targets list
+	 * in TARGMAX... On the other hand, why not? It says nowhere in
+	 * the TARGMAX specification that it's only for comma-separated
+	 * commands. So let's be nice and consistent and inform the
+	 * clients about the limits for such commands as well:
+	 */
+	setmaxtargets("USERHOST", MAXTARGETS_MAX); // not configurable
+	setmaxtargets("USERIP", MAXTARGETS_MAX); // not configurable
+	setmaxtargets("ISON", MAXTARGETS_MAX); // not configurable
+	setmaxtargets("WATCH", MAXTARGETS_MAX); // not configurable
+}
+
+/** Is the user handshake finished and can register_user() be called?
+ * This checks things like: do we have a NICK, USER, nospoof,
+ * and any other things modules may add:
+ * eg: the cap module checks if client capability negotiation
+ * is in progress
+ */
+int is_handshake_finished(Client *client)
+{
+	Hook *h;
+	int n;
+
+	for (h = Hooks[HOOKTYPE_IS_HANDSHAKE_FINISHED]; h; h = h->next)
+	{
+		n = (*(h->func.intfunc))(client);
+		if (n == 0)
+			return 0; /* We can stop already */
+	}
+
+	/* I figured these can be here, in the core: */
+	if (client->user && *client->user->username && client->name[0] && IsNotSpoof(client))
+		return 1;
+
+	return 0;
+}
+
+/** Should we show connection info to the user?
+ * This depends on the set::show-connect-info setting but also
+ * on various other properties, such as serversonly ports,
+ * websocket, etc.
+ * If someone needs it, then we can also call a hook here. Just tell us.
+ */
+int should_show_connect_info(Client *client)
+{
+	if (SHOWCONNECTINFO &&
+	    !client->server &&
+	    !IsServersOnlyListener(client->local->listener) &&
+	    !client->local->listener->websocket_options)
+	{
+		return 1;
+	}
+	return 0;
+}
+
+/* (helper function for uid_get) */
+static char uid_int_to_char(int v)
+{
+	if (v < 10)
+		return '0'+v;
+	else
+		return 'A'+v-10;
+}
+
+/** Acquire a new unique UID */
+const char *uid_get(void)
+{
+	Client *acptr;
+	static char uid[IDLEN];
+	static int uidcounter = 0;
+
+	uidcounter++;
+	if (uidcounter == 36*36)
+		uidcounter = 0;
+
+	do
+	{
+		snprintf(uid, sizeof(uid), "%s%c%c%c%c%c%c",
+			me.id,
+			uid_int_to_char(getrandom8() % 36),
+			uid_int_to_char(getrandom8() % 36),
+			uid_int_to_char(getrandom8() % 36),
+			uid_int_to_char(getrandom8() % 36),
+			uid_int_to_char(uidcounter / 36),
+			uid_int_to_char(uidcounter % 36));
+		acptr = find_client(uid, NULL);
+	} while (acptr);
+
+	return uid;
+}
+
+/** Get cloaked host for user */
+const char *getcloak(Client *client)
+{
+	if (!*client->user->cloakedhost)
+	{
+		/* need to calculate (first-time) */
+		make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
+	}
+
+	return client->user->cloakedhost;
+}
+
+/** Calculate the cloaked host for a client.
+ * @param client	The client
+ * @param curr		The real host or real IP
+ * @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, const char *curr, char *buf, size_t buflen)
+{
+	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++)
+		*q =  tolower(*p);
+	*q = '\0';
+
+	/* Call the cloaking layer */
+	if (RCallbacks[CALLBACKTYPE_CLOAK_EX] != NULL)
+		mask = RCallbacks[CALLBACKTYPE_CLOAK_EX]->func.stringfunc(client, host);
+	else if (RCallbacks[CALLBACKTYPE_CLOAK] != NULL)
+		mask = RCallbacks[CALLBACKTYPE_CLOAK]->func.stringfunc(host);
+	else
+		mask = curr;
+
+	strlcpy(buf, mask, buflen);
+}
+
+/** Called after a user is logged in (or out) of a services account */
+void user_account_login(MessageTag *recv_mtags, Client *client)
+{
+	if (MyConnect(client))
+	{
+		find_shun(client);
+		if (find_tkline_match(client, 0) && IsDead(client))
+			return;
+	}
+	RunHook(HOOKTYPE_ACCOUNT_LOGIN, client, recv_mtags);
+}
+
+/** Should we hide the idle time of 'target' to user 'client'?
+ * This depends on the set::hide-idle-time policy.
+ */
+int hide_idle_time(Client *client, Client *target)
+{
+	/* First of all, IRCOps bypass the restriction */
+	if (IsOper(client))
+		return 0;
+
+	/* Other than that, it depends on the settings: */
+	switch (iConf.hide_idle_time)
+	{
+		case HIDE_IDLE_TIME_NEVER:
+			return 0;
+		case HIDE_IDLE_TIME_ALWAYS:
+			return 1;
+		case HIDE_IDLE_TIME_USERMODE:
+		case HIDE_IDLE_TIME_OPER_USERMODE:
+			if (target->umodes & UMODE_HIDLE)
+				return 1;
+			return 0;
+		default:
+			return 0;
+	}
+}
+
+/** Get creation time of a client.
+ * @param client	The client to check (user, server, anything)
+ * @returns the time when the client first connected to IRC, or 0 for unknown.
+ */
+time_t get_creationtime(Client *client)
+{
+	const char *str;
+
+	/* Shortcut for local clients */
+	if (client->local)
+		return client->local->creationtime;
+
+	/* Otherwise, hopefully available through this... */
+	str = moddata_client_get(client, "creationtime");
+	if (!BadPtr(str) && (*str != '0'))
+		return atoll(str);
+	return 0;
+}
+
+/** Get how long a client is connected to IRC.
+ * @param client	The client to check
+ * @returns how long the client is connected to IRC (number of seconds)
+ */
+long get_connected_time(Client *client)
+{
+	const char *str;
+	long connect_time = 0;
+
+	/* Shortcut for local clients */
+	if (client->local)
+		return TStime() - client->local->creationtime;
+
+	/* Otherwise, hopefully available through this... */
+	str = moddata_client_get(client, "creationtime");
+	if (!BadPtr(str) && (*str != '0'))
+		return TStime() - atoll(str);
+	return 0;
+}
+
+/** Return extended information about user for the "Client connecting" line.
+ * @returns A string such as "[secure] [reputation: 5]", never returns NULL.
+ */
+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... */
+	RunHook(HOOKTYPE_CONNECT_EXTINFO, client, &list);
+
+	/* And some built-in: */
+
+	/* "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": 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->account);
+
+	/* security groups */
+	secgroups = get_security_groups(client);
+	if (secgroups)
+		add_nvplist(&list, 100, "security-groups", secgroups);
+	
+	/* tkl shunned */
+	if (IsShunned(client))
+		add_nvplist(&list, 110, "shunned", NULL);
+
+	*retbuf = '\0';
+	for (e = list; e; e = e->next)
+	{
+		if (e->value)
+			snprintf(tmp, sizeof(tmp), "[%s: %s] ", e->name, e->value);
+		else
+			snprintf(tmp, sizeof(tmp), "[%s] ", e->name);
+		strlcat(retbuf, tmp, sizeof(retbuf));
+	}
+	/* Cut off last space (unless empty string) */
+	if (*retbuf)
+		retbuf[strlen(retbuf)-1] = '\0';
+
+	/* Free the list, as it was only used to build retbuf */
+	free_nvplist(list);
+
+	return retbuf;
+}
+
+/** Log a message that flood protection kicked in for the client.
+ * This sends to the +f snomask at the moment.
+ * @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, const char *floodname)
+{
+	char buf[1024];
+
+	// NOTE: If you ever change this format, there are a few more
+	// direct unreal_log() calls with "FLOOD_BLOCKED" in the file
+	// src/modules/targetfloodprot.c, so update those as well.
+	unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
+	           "Flood blocked ($flood_type) from $client.details [$client.ip]",
+	           log_data_string("flood_type", floodname));
+}
+
+/** Is the flood limit exceeded for an option? eg for away-flood.
+ * @param client	The client to check flood for (local user)
+ * @param opt		The flood option (eg FLD_AWAY)
+ * @note This increments the flood counter as well.
+ * @returns 1 if exceeded, 0 if not.
+ */
+int flood_limit_exceeded(Client *client, FloodOption opt)
+{
+	FloodSettings *f;
+
+	if (!MyUser(client))
+		return 0;
+
+	f = get_floodsettings_for_user(client, opt);
+	if (f->limit[opt] <= 0)
+		return 0; /* No limit set or unlimited */
+
+	/* Ok, let's do the flood check */
+	if ((client->local->flood[opt].t + f->period[opt]) <= timeofday)
+	{
+		/* Time exceeded, reset */
+		client->local->flood[opt].count = 0;
+		client->local->flood[opt].t = timeofday;
+	}
+	if (client->local->flood[opt].count <= f->limit[opt])
+		client->local->flood[opt].count++;
+	if (client->local->flood[opt].count > f->limit[opt])
+	{
+		flood_limit_exceeded_log(client, floodoption_names[opt]);
+		return 1; /* Flood limit hit! */
+	}
+
+	return 0;
+}
+
+/** Get the appropriate anti-flood settings block for this user.
+ * @param client	The client, should be locally connected.
+ * @param opt		The flood option we are interested in
+ * @returns The FloodSettings for this user, never returns NULL.
+ */
+FloodSettings *get_floodsettings_for_user(Client *client, FloodOption opt)
+{
+	SecurityGroup *s;
+	FloodSettings *f;
+
+	/* Go through all security groups by order of priority
+	 * (eg: first "known-users", then "unknown-users").
+	 * For each of these:
+	 * - Check if a set::anti-flood::xxxx block exists for this group
+	 * - Check if the limit is non-zero (eg there is any limit set)
+	 * If any of these are false then we continue with next block
+	 * that matches.
+	 */
+
+	// XXX: alternatively, instead of this double loop,
+	//      do a post-conf thing and sort iConf.floodsettings
+	//      according to the security-group { } order.
+	for (s = securitygroups; s; s = s->next)
+	{
+		if (user_allowed_by_security_group(client, s) &&
+		    ((f = find_floodsettings_block(s->name))) &&
+		    f->limit[opt])
+		{
+			return f;
+		}
+	}
+
+	/* Return default settings block (which may have a zero limit set) */
+	f = find_floodsettings_block("unknown-users");
+	if (!f)
+		abort(); /* impossible */
+
+	return f;
+}
+
+MODVAR const char *floodoption_names[] = {
+	"nick-flood",
+	"join-flood",
+	"away-flood",
+	"invite-flood",
+	"knock-flood",
+	"max-concurrent-conversations",
+	"lag-penalty",
+	"vhost-flood",
+	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/ircd/src/utf8.c b/ircd/src/utf8.c
@@ -0,0 +1,250 @@
+#include "unrealircd.h"
+
+/**************** UTF8 HELPER FUNCTIONS START HERE *****************/
+
+/* Operations on UTF-8 strings.
+ * This part is taken from "glib" with the following copyright:
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ * Taken from the master snapshot on Oct 23, 2018, glib/gutf8.c.
+ * The library uses LGPL 2.1. From what I understand this allows me to
+ * use this code in a GPLv2-compatible way which fits the rest of
+ * the UnrealIRCd project.
+ *
+ * Code stripped and converted heavily to fit in UnrealIRCd by
+ * Bram Matthys ("Syzop") in 2019. Thanks to i <info@servx.org>
+ * for all the directions and help with regards to UTF8 handling.
+ *
+ * Note that with UnrealIRCd, a char is always unsigned char,
+ * which allows us to cut some corners and make more readable
+ * code without 100 casts.
+ */
+
+#define VALIDATE_BYTE(mask, expect) \
+  do {                              \
+    if ((*p & (mask)) != (expect))  \
+      goto error;                   \
+  } while(0)
+
+/* see IETF RFC 3629 Section 4 */
+
+static const char *fast_validate(const char *str)
+{
+	const char *p;
+
+	for (p = str; *p; p++)
+	{
+		if (*p >= 128)
+		{
+			const char *last;
+
+			last = p;
+			if (*p < 0xe0) /* 110xxxxx */
+			{
+				// ehm.. did you forget a ++p ? ;) or whatever
+				if (*p < 0xc2)
+				{
+					goto error;
+				}
+			}
+			else
+			{
+				if (*p < 0xf0) /* 1110xxxx */
+				{
+					switch (*p++ & 0x0f)
+					{
+						case 0:
+							VALIDATE_BYTE(0xe0, 0xa0); /* 0xa0 ... 0xbf */
+							break;
+						case 0x0d:
+							VALIDATE_BYTE(0xe0, 0x80); /* 0x80 ... 0x9f */
+							break;
+						default:
+							VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
+					}
+				}
+				else if (*p < 0xf5) /* 11110xxx excluding out-of-range */
+				{
+					switch (*p++ & 0x07)
+					{
+						case 0:
+							VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
+							if ((*p & 0x30) == 0)
+								goto error;
+							break;
+						case 4:
+							VALIDATE_BYTE(0xf0, 0x80); /* 0x80 ... 0x8f */
+							break;
+						default:
+							VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
+					}
+					p++;
+					VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
+				}
+				else
+				{
+					goto error;
+				}
+			}
+
+			p++;
+			VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
+
+			continue;
+
+error:
+			return last;
+		}
+	}
+
+	return p;
+}
+
+/** Check if a string is valid UTF8.
+ * @param str   The string to validate
+ * @param end   Pointer to char *, as explained in notes below.
+ * @returns 1 if the string is valid UTF8, 0 if not.
+ * @note  The variable *end will be set to the first invalid UTF8 sequence.
+ *        If no invalid UTF8 sequence is encountered then it points to the NUL byte.
+ */
+int unrl_utf8_validate(const char *str, const char **end)
+{
+	const char *p;
+
+	p = fast_validate(str);
+
+	if (end)
+		*end = p;
+
+	if (*p != '\0')
+		return 0;
+	else
+		return 1;
+}
+
+/** Go backwards in a string until we are at the end of an UTF8 sequence.
+ * Or more accurately: skip sequences that are part of an UTF8 sequence.
+ * @param begin   The string to check
+ * @param p       Where to start backtracking
+ * @returns Byte that is not in the middle of an UTF8 sequence,
+ *          or NULL if we reached the beginning and that isn't valid either.
+ */
+char *unrl_utf8_find_prev_char (const char *begin, const char *p)
+{
+	for (--p; p >= begin; --p)
+	{
+		if ((*p & 0xc0) != 0x80)
+			return (char *)p;
+	}
+	return NULL;
+}
+
+/** Return a valid UTF8 string based on the input.
+ * @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 *outputbuf, size_t outputbuflen, int strictlen)
+{
+	const char *remainder, *invalid;
+	int remaining_bytes, valid_bytes, len;
+	int replaced = 0; /**< UTF8 string needed replacement (was invalid) */
+
+	if (!str || !outputbuflen)
+		return NULL;
+
+	len = strlen(str);
+
+	*outputbuf = '\0';
+	remainder = str;
+	remaining_bytes = len;
+
+	while (remaining_bytes != 0)
+	{
+		if (unrl_utf8_validate(remainder, &invalid))
+		{
+			if (!replaced)
+			{
+				if (strictlen)
+				{
+					/* Caller wants us to go through the 'replaced' branch */
+					strlcpy(outputbuf, str, outputbuflen);
+					replaced = 1;
+				}
+				break;
+			} else {
+				/* We already replaced earlier, now just put the rest at the end. */
+				strlcat(outputbuf, remainder, outputbuflen);
+				break;
+			}
+		}
+		replaced = 1;
+		valid_bytes = invalid - remainder;
+
+		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;
+	}
+
+	if (!replaced)
+		return (char *)str; /* return original string (no changes needed) */
+
+	/* 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(outputbuf) == outputbuflen-1)
+	{
+		char *cut_at = unrl_utf8_find_prev_char(outputbuf, outputbuf+outputbuflen-1);
+		if (cut_at)
+			*cut_at = '\0';
+	}
+
+#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 outputbuf;
+}
+
+/**************** END OF UTF8 HELPER FUNCTIONS *****************/
+
+/** This is just for internal testing */
+void utf8_test(void)
+{
+	char buf[1024];
+	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, workbuf, workbuflen, 1);
+		if (heapbuf == res)
+		{
+			printf("    %s\n", res);
+		} else {
+			printf("[!] %s\n", res);
+		}
+		free(heapbuf);
+	}
+	safe_free(workbuf);
+}
diff --git a/ircd/src/version.c.SH b/ircd/src/version.c.SH
@@ -0,0 +1,282 @@
+# $Id$
+
+echo "Extracting src/version.c..."
+
+#id=`grep '$Id: Changes,v' ../Changes`
+#id=`echo $id |sed 's/.* Changes\,v \(.*\) .* Exp .*/\1/'`
+if [ -d ../.git ]; then
+	SUFFIX="-$(git rev-parse --short HEAD)"
+fi
+id="6.1.0$SUFFIX"
+echo "$id"
+
+if test -r version.c
+then
+   generation=`sed -n 's/^char \*generation = \"\(.*\)\";/\1/p' < version.c`
+   if test ! "$generation" ; then generation=0; fi
+else
+   generation=0
+fi
+
+generation=`expr $generation + 1`
+export LANG=C
+export LC_TIME=C
+export LC_ALL=C
+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 \
+         { print $1 " "  $2 " " $3 " " $7 " at " $4 " " $5 " " $6 }}'`
+
+cat >version.c <<!SUB!THIS!
+/*
+ *   IRC - Internet Relay Chat, ircd/version.c
+ *   Copyright (C) 1990 Chelsea Ashley Dyerman
+ *
+ *   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.
+ * 
+ *   $Id$
+ */
+
+/*
+ * This file is generated by version.c.SH. Any changes made will go away.
+ */
+
+#include "struct.h"
+#include "version.h"
+#include "license.h"
+
+char *generation = "$generation";
+char *creation = "$creation";
+#define IRCDTOTALVERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5 PATCH6 PATCH7 PATCH8 PATCH9
+char *version = IRCDTOTALVERSION;
+char *buildid = "$id";
+/* moved to s_serv.c */
+char *infotext[] = 
+{ 0 };
+
+char *unrealcredits[] =
+{
+	"==================[ " IRCDTOTALVERSION " Credits ]===================",
+	"The people on this list are people who have helped us with the",
+	"development of UnrealIRCd and who have made remarkable",
+	"contributions to the project.",
+	" ",
+	"==========================[ Donations ]==========================",
+	"BlueFlame^, [Real] - ChatFIRST.com, Jameno123 - ByteHosting,",
+	"Internet Services, Interlink Access Corp, Jan Knutar, ThePlayer,",
+	"Headband, noriko, powerstorm.net, RedMaxima, IronHelix, xnet.org,",
+	"Pierce - irc.AAcNet.org, Franky75 - Betas-Online.com, irc.vco.se,",
+	"Beldock - irc.coldfront.net, Kusau - chat.tochat.org, Japsclan,",
+	"WolfLord - UplinkCorp, Isaiah - irc.frogstar.us, Kedrin Milborn,",
+	"Dionisios Koutsikos, Tank - irc.scifi-fans.net, irc.P2Pchat.net,",
+	"Leo Zhadanovsky - irc.leozh.net, Lyote - ZodiaCIrC/DecayOnline,",
+	"Jesse Lanning - Secure Technology Networks, HERZ - insiderZ.DE,",
+	"James Yerge - Kronical Internet Solutions, djw - irc.perldev.org,",
+	"MichaelJE2 - irc.xcelor8.com, AfterShock - irc.liquidvoltage.net,",
+	"recycled-irc - www.recycled-irc.org, Alpha - irc.FireWirez.net,",
+	"FrostByghte - irc.coldfront.net, SdgNem - irc.RealmOfGaming.net,",
+	"MagicalTux - irc.FF.st, irc.P2Pchat.net, Ganja51 - irc.lcirc.net,",
+	"^White_Magic^ - irc.chatuniverse.net, Nick - irc.plasmachat.net,",
+	"Matridom - www.WinDrivers.com, anaconda - irc.lightmoon.org,", 
+	"mnslinky - http://www.secure-computing.net, Justin Furnas,",
+	"Andy Hansis - irc.technerd.net, Crimson - www.n00bstories.com",
+	"Devin Reams, Cleggo - irc.ugcentral.net, Tillo - irc.OSirc.net,",
+	"Matthew Burdine - irc.owns.us, Philip Veale - flame.tiefighter.org,",
+	"Latinus - irc.lokanova.com, Vincent Guesnard - irc.rs2i.net,",
+	"Windfyre IRC Network - irc.windfyre.net, SetNine - www.setnine.com,",
+	"|S| - irc.chatuniverse.net, CommTech Inc. - www.commtechusa.com,",
+	"CatNet IRC - irc.catslair.org, IcE-IRC - irc.ice-irc.de,",
+	"Kevin Devaux, Jackal - www.fyrebird.net, Lax - insiderZ.DE,",
+	"The_devil - chat.snyggast.se, Deltaanime Network - deltaanime.net,",
+	"CatNet IRC network - irc.catslair.org, A-Buz.com - a-buz.com,",
+	"LimonCenter Networks - www.limoncenter.com, Jean-Pierre Fournier,",
+	"AloneInTheDark - irc.sleepwalkers.org, El_barto - irc.ircdit.net,",
+	"Pyrexnetworks - www.pyrexnetworks.com, zunnie - www.MP-Gaming.NET,",
+	"All My Data - www.amdwebhost.com, Wulfie - irc.soundsnwaves.net,",
+	"eCoupons.com - www.ecoupons.com, MauritZ - irc.MindForge.org,",
+	"Voodoohosting - www.voodoohosting.com,",
+	"RealityBytez IRC Network - irc.realitybytez.net,",
+	"LFS - www.linuxfromscratch.org, The_devil - chat.snyggast.se,",
+	"Stephen - dirac.betas-online.com, NationWars - www.nationwars.com,",
+	"SurrealChat.net - www.surrealchat.net, NamesDir - www.namesdir.com,",
+	"Muisje - www.elitez0r.com, Jaya Sri - www.krisna.cc,",
+	"DiabloClone.Org - irc.diabloclone.org, RisingNet - www.RisingNet.net,",
+	"DCloneIRC.net - irc.dcloneirc.net, L.E.-Nation - irc.le-nation.de,",
+	"RomeoIRC - irc.romeoirc.net, Comparison Shopping - www.order.com,",
+	"Atak Trucking - www.AtakTrucking.com, www.Gamehostingprovider.com,",
+	"Xzibition.com - irc.xzibition.com, www.TheBookmarkShop.com,",
+	"Compare Prices - www.PriceV.com, Joe Gronlund - www.joegronlund.com,",
+	"Exoware - www.exoware.net, Bitmaster - irc.synIRC.net,",
+	"Angel IRC - irc.angelirc.net, Pixel - http://indavoid.net,",
+	"Grandview Landscape and Masonry - www.grandviewoutdoor.com,",
+	"Saibot Technologies - http://saibottechnologies.com/,",
+	"IRC-IRC.de Community - irc.irc-irc.de,",
+	"Mike Meisner - www.hottubcoverdepot.com, CubaChat - irc.cubachat.org,",
+	"BluePromoCode - www.bluepromocode.com, DontPayFull - www.dontpayfull.com",
+	"Anikwa - ukblabberbox.co.uk, xShellz - www.xshellz.com, wico,",
+	"Magnolia Road Internet Cooperative, FrostWire - www.frostwire.com,",
+	"Bundeld Inc. - couponlawn.com, Chameleon John - www.chameleonjohn.com,",
+	"Ecigsopedia.com - www.ecigsopedia.com/halo-cigs-coupon,",
+	"Hello Voucher Card - www.hellovouchercard.co.uk, Knoji - knoji.com,",
+	"EveryCloud - www.everycloudtech.com, CouponRaven - couponraven.com,",
+	"Dealspotr - dealspotr.com, GuitarFella.com, Coupofy - Coupofy.com,",
+	"Underfloor Heating Systems - www.underfloorheatingsystems.co.uk,",
+	"Project Free TV - newprojectfreetv.com, companyaccountscheck.com,",
+	"Coyote Traffic - www.coyotetraffic.com, www.emailextractor14lite.com,",
+	"bet365 Bonus Code - williamspromocodes.co.uk/bet365-bonus-code-sports/,",
+	"Valerie Amelia Pond",
+	" ",
+	"==========================[ Supporters ]==========================",
+	"Our support staff: Cronus, Jobe, SpaceDog, Stealth",
+	" ",
+	"Thanks to former supporters and anyone who has helped without",
+	"officially joining the staff.",
+	" ",
+	"===========================[ Coders ]=============================",
+	"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",
+	" ",
+	"For a full list of coders & contributors, see /INFO (/QUOTE INFO)",
+	" ",
+	"==================================================================",
+	"Finally, we would like to thank everyone else who has helped",
+	"UnrealIRCd in any way: reporting bugs, testing releases, helping",
+	"others, recommending UnrealIRCd to friends, or simply just by",
+	"running UnrealIRCd to ensure that we are and will continue to be",
+	"the most used IRC daemon in the world.",
+	"==================================================================",
+	0
+}; 
+
+
+char unreallogo[] = 
+{
+32,95,32,32,32,95,32,32,32,32,32,
+32,32,32,32,32,32,32,32,32,32,32,
+32,32,32,32,32,32,95,32,95,95,95,
+95,95,95,95,95,95,95,95,32,32,95,
+95,95,95,95,32,32,32,32,32,95,32,
+10,124,32,124,32,124,32,124,32,32,32,
+32,32,32,32,32,32,32,32,32,32,32,
+32,32,32,32,32,32,124,32,124,95,32,
+32,32,95,124,32,95,95,95,32,92,47,
+32,32,95,95,32,92,32,32,32,124,32,
+124,10,124,32,124,32,124,32,124,95,32,
+95,95,32,32,95,32,95,95,32,95,95,
+95,32,32,95,95,32,95,124,32,124,32,
+124,32,124,32,124,32,124,95,47,32,47,
+124,32,47,32,32,92,47,32,95,95,124,
+32,124,10,124,32,124,32,124,32,124,32,
+39,95,32,92,124,32,39,95,95,47,32,
+95,32,92,47,32,95,96,32,124,32,124,
+32,124,32,124,32,124,32,32,32,32,47,
+32,124,32,124,32,32,32,32,47,32,95,
+96,32,124,10,124,32,124,95,124,32,124,
+32,124,32,124,32,124,32,124,32,124,32,
+32,95,95,47,32,40,95,124,32,124,32,
+124,95,124,32,124,95,124,32,124,92,32,
+92,32,124,32,92,95,95,47,92,32,40,
+95,124,32,124,10,32,92,95,95,95,47,
+124,95,124,32,124,95,124,95,124,32,32,
+92,95,95,95,124,92,95,95,44,95,124,
+95,124,92,95,95,95,47,92,95,124,32,
+92,95,124,32,92,95,95,95,95,47,92,
+95,95,44,95,124,10,0,85,110,114,
+101,97,108,73,82,67,100,0};
+
+char *dalinfotext[] =
+    {
+        "$package --",
+        "Based on the original code written by Jarkko Oikarinen",
+        "Copyright 1988, 1989, 1990, 1991 University of Oulu, Computing Center",
+        "",
+        "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.",
+	"- Any name/comment should never be changed except by the one who made it -",
+	"",
+	"UnrealIRCd contains code developed by:",
+	"Potvin       Chris Wolkowski          potvin@acestar.org",
+	"RogerY       Roger Y.                 rogery@austnet.org",
+	"GZ                                    gz@starchat.net",
+	"binary                                ",
+	"Dianora      Diane Bruce",
+	"lucas", 
+	"Roar Thronaas <roart@nvg.ntnu.no> added IPv6 support.",
+	"",
+	"",
+	"The following people have helped in making the DALnet ircd",
+        "that is based on irc2.8.21.mu3.2 :",
+        "",
+        "Russell      Russell Miller           russell@dal.net",
+        "Donwulff     Jukka Santala            donwulff@dal.net",
+        "Aetobatus    Michael Sawyer           aetobatus@dal.net",
+        "Dalvenjah    Sven Nielsen             dalvenjah@dal.net",
+        "Skandranon   Michael Graff            explorer@flame.org",
+        "Barubary     -                        barubary@dal.net",
+        "white_dragon Chip Norkus              wd@dal.net",
+        "DuffJ        Dafydd James             duffj@dal.net",
+        "taz          David Kopstain           taz@dal.net",
+        "NikB         Nik Bougalis             nikb@dal.net",
+        "Rakarra      -                        rakarra@dal.net",
+        "DarkRot      Lucas Madar              darkrot@dal.net",
+        "Studded      -                        studded@dal.net",
+        "JoelKatz     David Schwartz           joelkatz@dal.net",
+        "",
+        "This product includes software developed by Colin Plumb.",
+        "",
+        "The following persons have made many changes and enhancements to the",
+        "code and still know how IRC really works if you have questions about it:",
+        "",
+        "Run          Carlo Kid                carlo@runaway.xs4all.nl",
+        "Avalon       Darren Reed              avalon@coombs.anu.edu.au",
+        "msa          Markku Savela            Markku.Savela@vtt.fi",
+        "Wumpus       Greg Lindahl             gl8f@virginia.edu",
+        "WiZ          Jarkko Oikarinen         jto@tolsun.oulu.fi",
+        "Argv         Armin Gruner Armin.Gruner@Informatik.TU-Muenchen.de",
+        "",
+        "Thanks to the following people for help with preparing 2.8",
+        "",
+        "phone        Matthew Green            phone@coombs.anu.edu.au",
+        "Sodapop      Chuck Kane               ckane@ece.uiuc.edu",
+        "Skygod       Matt Lyle                matt@oc.com",
+        "Vesa         Vesa Ruokonen            ruokonen@lut.fi",
+        "Nap          Nicolas PIOCH pioch@poly.polytechnique.fr",
+        "",
+        "Those who helped in prior versions and continue to be helpful:",
+        "",
+        "Stellan Klebom      Dan Goodwin         Mike Bolotski",
+        "Ian Frechette       Markku Jarvinen     Kimmo Suominen",
+        "Jeff Trim           Vijay Subramaniam   Karl Kleinpaste",
+        "Bill Wisner         Tom Davis           Hugo Calendar",
+        "Tom Hopkins         Stephen van den Berg",
+        "Bo Adler            Michael Sandrof     Jon Solomon",
+        "Jan Peterson        Helen Rose          Paul Graham",
+        "",
+        "Thanks also goes to those persons not mentioned here who have added",
+        "their advice, opinions, and code to IRC.",
+        "Thanks also to those who provide the kind sys admins who let me and",
+        "others continue to develop IRC.",
+        "",
+
+        0
+    };
+!SUB!THIS!
diff --git a/ircd/src/whowas.c b/ircd/src/whowas.c
@@ -0,0 +1,208 @@
+/************************************************************************
+*   IRC - Internet Relay Chat, src/whowas.c
+*   Copyright (C) 1990 Markku Savela
+*
+*   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"
+
+// FIXME: move this to cmd_whowas,
+// Consider making add_history an efunc? Or via a hook?
+// Some users may not want to load cmd_whowas at all.
+
+void add_whowas_to_clist(WhoWas **, WhoWas *);
+void del_whowas_from_clist(WhoWas **, WhoWas *);
+void add_whowas_to_list(WhoWas **, WhoWas *);
+void del_whowas_from_list(WhoWas **, WhoWas *);
+
+WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
+
+MODVAR int whowas_next = 0;
+
+void free_whowas_fields(WhoWas *e)
+{
+	safe_free(e->name);
+	safe_free(e->hostname);
+	safe_free(e->virthost);
+	safe_free(e->realname);
+	safe_free(e->username);
+	safe_free(e->account);
+	safe_free(e->ip);
+	e->servername = NULL;
+	e->event = 0;
+	e->logon = 0;
+	e->logoff = 0;
+	e->connected_since = 0;
+
+	/* Remove from lists and reset hashv */
+	if (e->online)
+		del_whowas_from_clist(&(e->online->user->whowas), e);
+	del_whowas_from_list(&WHOWASHASH[e->hashv], e);
+	e->hashv = -1;
+}
+
+void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event)
+{
+	e->hashv = hash_whowas_name(client->name);
+	e->event = event;
+	e->connected_since = get_creationtime(client);
+	e->logon = client->lastnick;
+	e->logoff = TStime();
+	e->umodes = client->umodes;
+	safe_strdup(e->name, client->name);
+	safe_strdup(e->username, client->user->username);
+	safe_strdup(e->hostname, client->user->realhost);
+	safe_strdup(e->ip, client->ip);
+	if (client->user->virthost)
+		safe_strdup(e->virthost, client->user->virthost);
+	else
+		safe_strdup(e->virthost, "");
+	e->servername = client->user->server;
+	safe_strdup(e->realname, client->info);
+	if (strcmp(client->user->account, "0"))
+		safe_strdup(e->account, client->user->account);
+
+	/* Its not string copied, a pointer to the scache hash is copied
+	   -Dianora
+	 */
+	/*  strlcpy(e->servername, client->user->server,HOSTLEN); */
+	e->servername = client->user->server;
+}
+
+void add_history(Client *client, int online, WhoWasEvent event)
+{
+	WhoWas *new;
+
+	new = &WHOWAS[whowas_next];
+
+	if (new->hashv != -1)
+		free_whowas_fields(new);
+
+	create_whowas_entry(client, new, event);
+
+	if (online)
+	{
+		new->online = client;
+		add_whowas_to_clist(&(client->user->whowas), new);
+	} else {
+		new->online = NULL;
+	}
+	add_whowas_to_list(&WHOWASHASH[new->hashv], new);
+	whowas_next++;
+	if (whowas_next == NICKNAMEHISTORYLENGTH)
+		whowas_next = 0;
+}
+
+void off_history(Client *client)
+{
+	WhoWas *temp, *next;
+
+	for (temp = client->user->whowas; temp; temp = next)
+	{
+		next = temp->cnext;
+		temp->online = NULL;
+		del_whowas_from_clist(&(client->user->whowas), temp);
+	}
+}
+
+Client *get_history(const char *nick, time_t timelimit)
+{
+	WhoWas *temp;
+	int  blah;
+
+	timelimit = TStime() - timelimit;
+	blah = hash_whowas_name(nick);
+	temp = WHOWASHASH[blah];
+	for (; temp; temp = temp->next)
+	{
+		if (mycmp(nick, temp->name))
+			continue;
+		if (temp->logoff < timelimit)
+			continue;
+		return temp->online;
+	}
+	return NULL;
+}
+
+void count_whowas_memory(int *wwu, u_long *wwum)
+{
+	WhoWas *tmp;
+	int  i;
+	int  u = 0;
+	u_long um = 0;
+	/* count the number of used whowas structs in 'u' */
+	/* count up the memory used of whowas structs in um */
+
+	for (i = 0, tmp = &WHOWAS[0]; i < NICKNAMEHISTORYLENGTH; i++, tmp++)
+		if (tmp->hashv != -1)
+		{
+			u++;
+			um += sizeof(WhoWas);
+		}
+	*wwu = u;
+	*wwum = um;
+	return;
+}
+
+void initwhowas()
+{
+	int  i;
+
+	for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		memset(&WHOWAS[i], 0, sizeof(WhoWas));
+		WHOWAS[i].hashv = -1;
+	}
+	for (i = 0; i < WHOWAS_HASH_TABLE_SIZE; i++)
+		WHOWASHASH[i] = NULL;
+}
+
+void add_whowas_to_clist(WhoWas ** bucket, WhoWas * whowas)
+{
+	whowas->cprev = NULL;
+	if ((whowas->cnext = *bucket) != NULL)
+		whowas->cnext->cprev = whowas;
+	*bucket = whowas;
+}
+
+void del_whowas_from_clist(WhoWas ** bucket, WhoWas * whowas)
+{
+	if (whowas->cprev)
+		whowas->cprev->cnext = whowas->cnext;
+	else
+		*bucket = whowas->cnext;
+	if (whowas->cnext)
+		whowas->cnext->cprev = whowas->cprev;
+}
+
+void add_whowas_to_list(WhoWas ** bucket, WhoWas * whowas)
+{
+	whowas->prev = NULL;
+	if ((whowas->next = *bucket) != NULL)
+		whowas->next->prev = whowas;
+	*bucket = whowas;
+}
+
+void del_whowas_from_list(WhoWas ** bucket, WhoWas * whowas)
+{
+	if (whowas->prev)
+		whowas->prev->next = whowas->next;
+	else
+		*bucket = whowas->next;
+	if (whowas->next)
+		whowas->next->prev = whowas->prev;
+}
diff --git a/ircd/src/windows/Icon1.ico b/ircd/src/windows/Icon1.ico
Binary files differ.
diff --git a/ircd/src/windows/UnrealIRCd.exe.manifest b/ircd/src/windows/UnrealIRCd.exe.manifest
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+    processorArchitecture="amd64"
+    name="UnrealIRCd.UnrealIRCd.6"
+    version="6.1.0.0"
+    type="win32"
+/>
+<description>Internet Relay Chat Daemon</description>
+<dependency>
+    <dependentAssembly>
+        <assemblyIdentity
+            type="win32"
+            name="Microsoft.Windows.Common-Controls"
+            version="6.0.0.0"
+            processorArchitecture="amd64"
+            publicKeyToken="6595b64144ccf1df"
+            language="*"
+        />
+    </dependentAssembly>
+</dependency>
+</assembly>
diff --git a/ircd/src/windows/bar.bmp b/ircd/src/windows/bar.bmp
Binary files differ.
diff --git a/ircd/src/windows/compilerhelp.c b/ircd/src/windows/compilerhelp.c
@@ -0,0 +1,55 @@
+/*
+ *
+ * A helper program for the compilation process
+ *
+ */
+
+/* x,y,z,w 
+ * | | | `-- private build
+ * | | `---- release build
+ * | `------ minor version
+ * `-------- major version
+ */
+
+#include <stdio.h>
+
+void main(int argc,char *argv[])
+{
+	FILE *openme;
+	char inbuf[512];
+	int i,pb=0,rb=0,mi=0,ma=0;
+
+	if (argc == 1)
+		exit(-1);
+
+	if ((openme = fopen(argv[1],"r+"))==NULL)
+	{
+		printf("error\n");
+		exit(-1);
+	}
+
+	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&pb);		/*Read Buffer*/
+	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&rb);
+	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&mi);
+	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&ma);
+
+	pb++;
+	if (argc > 2)
+	if (atoi(argv[2])==0)  /*Public Build*/
+		rb++;
+
+	printf("new version = %d,%d,%d,%d",ma,mi,rb,pb);
+
+	rewind(openme);
+
+	fprintf(openme,"#define pb %d\n",pb);		/*Write Buffer*/
+	fprintf(openme,"#define rb %d\n",rb);
+	fprintf(openme,"#define mi %d\n",mi);
+	fprintf(openme,"#define ma %d\n",ma);
+
+	fprintf(openme,"#define vFILEVERSION ma,mi,rb,pb\n#define vPRODUCTVERSION ma,mi,0,0\n#define vDISPFILEVERSION \"%d,%d,%d,%d\\0\"\n#define vSUBBUILD \"%d\\0\"\n",ma,mi,rb,pb,pb);
+
+	fclose(openme);
+
+
+}
diff --git a/ircd/src/windows/config.c b/ircd/src/windows/config.c
@@ -0,0 +1,56 @@
+
+#include <stdio.h>
+#include <string.h>
+int main() {
+	FILE *fd = fopen("Changes", "r");
+	FILE *fd2;
+	char buf[1024];
+	int i = 0, space = 0, j = 0;
+	char releaseid[512];
+	int generation = 0;
+
+	*releaseid = '\0';
+
+	i = 0;
+	fd = fopen("src/version.c", "r");
+	if (!fd)
+		generation = 1;
+	else {
+		while (fgets(buf, 1023, fd)) {
+			if (!strstr(buf, "char *generation"))
+				continue;
+			while (!isdigit(buf[i]))
+					i++;
+			j = i;
+			while (isdigit(buf[j])) 
+				j++;
+			buf[j] = 0;
+			generation = (atoi(&buf[i])+1);
+		}
+	}
+	fd = fopen("src/version.c.sh", "r");
+	if (!fd)
+		return 0;
+	fd2 = fopen("src/version.c", "w");
+	if (!fd2)
+		return 0;
+	while (fgets(buf, 1023, fd)) {
+		if (!strncmp("cat >version.c <<!SUB!THIS!",buf,27)) {
+			while (fgets(buf, 1023, fd)) {
+				if (!strncmp("!SUB!THIS!",buf,10))
+					break;
+				if (!strncmp("char *creation = \"$creation\";",buf,29)) 
+					fprintf(fd2,"char *creation = __TIMESTAMP__;\n");
+				else if (!strncmp("char *generation = \"$generation\";",buf,33))
+					fprintf(fd2,"char *generation = \"%d\";\n",generation);
+				else if (!strncmp("char *buildid = \"$id\";",buf,22))
+					fprintf(fd2,"char *buildid = \"%s\";\n",releaseid);
+				else
+					fprintf(fd2,"%s", buf);
+			}
+		}
+	}
+
+
+}
+	
+\ No newline at end of file
diff --git a/ircd/src/windows/def-clean.c b/ircd/src/windows/def-clean.c
@@ -0,0 +1,39 @@
+/* A small utility to clean a def file */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(int argc, char *argv[]) {
+	FILE *fd, *fdout;
+	char buf[1024];
+
+	if (argc < 3)
+		exit(1);
+
+	if (!(fd = fopen(argv[1], "r")))
+		exit(2);
+	
+	if (!(fdout = fopen(argv[2], "w")))
+		exit(3);
+
+	while (fgets(buf, 1023, fd))
+	{
+		if (*buf == '\t') 
+		{
+			char *symbol = strtok(buf, " ");
+
+			if (!strncmp(symbol, "\t_real@", 7))
+				continue;
+			if (!strncmp(symbol, "\t_xmm@", 6))
+				continue;
+
+			fprintf(fdout, "%s\r\n", symbol);	
+		
+		}
+		else
+			fprintf(fdout, "%s", buf);
+
+	}
+	return 0;
+}
diff --git a/ircd/src/windows/editor.c b/ircd/src/windows/editor.c
@@ -0,0 +1,769 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/editor.c
+ *   Copyright (C) 2004 Dominick Meglio (codemastr)
+ *   
+ *   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 <windowsx.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <commdlg.h>
+#include <stdlib.h>
+#include "resource.h"
+#include "win.h"
+
+LRESULT CALLBACK GotoDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK ColorDLG(HWND, UINT, WPARAM, LPARAM);
+
+HWND hFind;
+
+/* Draws the statusbar for the editor
+ * Parameters:
+ *  hInstance  - The instance to create the statusbar in
+ *  hwndParent - The parent of the statusbar
+ *  iId        - The message value used to send messages to the parent
+ * Returns:
+ *  The handle to the statusbar
+ */
+HWND DrawStatusbar(HINSTANCE hInstance, HWND hwndParent, UINT iId)
+{
+	HWND hStatus, hTip;
+	TOOLINFO ti;
+	RECT clrect;
+	hStatus = CreateStatusWindow(WS_CHILD|WS_VISIBLE|SBT_TOOLTIPS, NULL, hwndParent, iId);
+	hTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
+ 		WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP, 0, 0, 0, 0, hwndParent, NULL, hInstance, NULL);
+	GetClientRect(hStatus, &clrect);
+	ti.cbSize = sizeof(TOOLINFO);
+	ti.uFlags = TTF_SUBCLASS;
+	ti.hwnd = hStatus;
+	ti.uId = 1;
+	ti.hinst = hInstance;
+	ti.rect = clrect;
+	ti.lpszText = "Go To";
+	SendMessage(hTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
+	return hStatus;
+}
+
+/* Draws the toolbar for the editor
+ * Parameters:
+ *  hInstance  - The instance to create the toolbar in
+ *  hwndParent - The parent of the toolbar
+ * Returns:
+ *  The handle to the toolbar
+ */
+HWND DrawToolbar(HINSTANCE hInstance, HWND hwndParent) 
+{
+	HWND hTool;
+	TBADDBITMAP tbBit;
+	int newidx;
+	TBBUTTON tbButtons[10] = {
+		{ STD_FILENEW, IDM_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ STD_FILESAVE, IDM_SAVE, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
+		{ STD_CUT, IDM_CUT, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ STD_COPY, IDM_COPY, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ STD_PASTE, IDM_PASTE, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
+		{ STD_UNDO, IDM_UNDO, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ STD_REDOW, IDM_REDO, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0}
+	};
+		
+	TBBUTTON tbAddButtons[7] = {
+		{ 0, IDC_BOLD, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, 0},
+		{ 1, IDC_UNDERLINE, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, 0},
+		{ 2, IDC_COLOR, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ 3, IDC_BGCOLOR, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
+		{ 4, IDC_GOTO, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
+		{ STD_FIND, IDC_FIND, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0}
+	};
+	hTool = CreateToolbarEx(hwndParent, WS_VISIBLE|WS_CHILD|TBSTYLE_FLAT|TBSTYLE_TOOLTIPS, 
+				IDC_TOOLBAR, 0, HINST_COMMCTRL, IDB_STD_SMALL_COLOR,
+				tbButtons, 10, 0, 0, 100, 30, sizeof(TBBUTTON));
+	tbBit.hInst = hInstance;
+	tbBit.nID = IDB_BITMAP1;
+	newidx = SendMessage(hTool, TB_ADDBITMAP, (WPARAM)5, (LPARAM)&tbBit);
+	tbAddButtons[0].iBitmap += newidx;
+	tbAddButtons[1].iBitmap += newidx;
+	tbAddButtons[2].iBitmap += newidx;
+	tbAddButtons[3].iBitmap += newidx;
+	tbAddButtons[5].iBitmap += newidx;
+	SendMessage(hTool, TB_ADDBUTTONS, (WPARAM)7, (LPARAM)&tbAddButtons);
+	return hTool;
+}
+
+/* Dialog procedure for the color selection dialog
+ * Parameters:
+ *  hDlg    - The dialog handle
+ *  message - The message received
+ *  wParam  - The first message parameter
+ *  lParam  - The second message parameter
+ * Returns:
+ *  TRUE if the message was processed, FALSE otherwise
+ */
+LRESULT CALLBACK ColorDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
+	static HBRUSH hBrushWhite, hBrushBlack, hBrushDarkBlue, hBrushDarkGreen, hBrushRed,
+		hBrushDarkRed, hBrushPurple, hBrushOrange, hBrushYellow, hBrushGreen, hBrushVDarkGreen,
+		hBrushLightBlue, hBrushBlue, hBrushPink, hBrushDarkGray, hBrushGray;
+	static UINT ResultMsg = 0;
+
+	switch (message) 
+	{
+		case WM_INITDIALOG:
+			hBrushWhite = CreateSolidBrush(RGB(255,255,255));
+			hBrushBlack = CreateSolidBrush(RGB(0,0,0));
+			hBrushDarkBlue = CreateSolidBrush(RGB(0,0,127));
+			hBrushDarkGreen = CreateSolidBrush(RGB(0,147,0));
+			hBrushRed = CreateSolidBrush(RGB(255,0,0));
+			hBrushDarkRed = CreateSolidBrush(RGB(127,0,0));
+			hBrushPurple = CreateSolidBrush(RGB(156,0,156));
+			hBrushOrange = CreateSolidBrush(RGB(252,127,0));
+			hBrushYellow = CreateSolidBrush(RGB(255,255,0));
+			hBrushGreen = CreateSolidBrush(RGB(0,252,0));
+			hBrushVDarkGreen = CreateSolidBrush(RGB(0,147,147));
+			hBrushLightBlue = CreateSolidBrush(RGB(0,255,255));
+			hBrushBlue = CreateSolidBrush(RGB(0,0,252));
+			hBrushPink = CreateSolidBrush(RGB(255,0,255));
+			hBrushDarkGray = CreateSolidBrush(RGB(127,127,127));
+			hBrushGray = CreateSolidBrush(RGB(210,210,210));
+			ResultMsg = (UINT)lParam;
+			SetFocus(NULL);
+			return TRUE;
+		case WM_DRAWITEM: 
+		{
+			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
+			if (wParam == IDC_WHITE) 
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushWhite);
+			if (wParam == IDC_BLACK)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushBlack);
+			if (wParam == IDC_DARKBLUE) 
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkBlue);
+			if (wParam == IDC_DARKGREEN) 
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkGreen);
+			if (wParam == IDC_RED)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushRed);
+			if (wParam == IDC_DARKRED)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkRed);
+			if (wParam == IDC_PURPLE)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushPurple);
+			if (wParam == IDC_ORANGE)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushOrange);
+			if (wParam == IDC_YELLOW)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushYellow);
+			if (wParam == IDC_GREEN)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushGreen);
+			if (wParam == IDC_VDARKGREEN)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushVDarkGreen);
+			if (wParam == IDC_LIGHTBLUE)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushLightBlue);
+			if (wParam == IDC_BLUE)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushBlue);
+			if (wParam == IDC_PINK)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushPink);
+			if (wParam == IDC_DARKGRAY)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkGray);
+			if (wParam == IDC_GRAY)
+				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushGray);
+			DrawEdge(lpdis->hDC, &lpdis->rcItem, EDGE_SUNKEN, BF_RECT);
+			return TRUE;
+		}
+		case WM_COMMAND: 
+		{
+			COLORREF clrref;
+			if (LOWORD(wParam) == IDC_WHITE) 
+				clrref = RGB(255,255,255);
+			else if (LOWORD(wParam) == IDC_BLACK)
+				clrref = RGB(0,0,0);
+			else if (LOWORD(wParam) == IDC_DARKBLUE)
+				clrref = RGB(0,0,127);
+			else if (LOWORD(wParam) == IDC_DARKGREEN)
+				clrref = RGB(0,147,0);
+			else if (LOWORD(wParam) == IDC_RED)
+				clrref = RGB(255,0,0);
+			else if (LOWORD(wParam) == IDC_DARKRED)
+				clrref = RGB(127,0,0);
+			else if (LOWORD(wParam) == IDC_PURPLE)
+				clrref = RGB(156,0,156);
+			else if (LOWORD(wParam) == IDC_ORANGE)
+				clrref = RGB(252,127,0);
+			else if (LOWORD(wParam) == IDC_YELLOW)
+				clrref = RGB(255,255,0);
+			else if (LOWORD(wParam) == IDC_GREEN)
+				clrref = RGB(0,252,0);
+			else if (LOWORD(wParam) == IDC_VDARKGREEN)
+				clrref = RGB(0,147,147);
+			else if (LOWORD(wParam) == IDC_LIGHTBLUE)
+				clrref = RGB(0,255,255);
+			else if (LOWORD(wParam) == IDC_BLUE)
+				clrref = RGB(0,0,252);
+			else if (LOWORD(wParam) == IDC_PINK)
+				clrref = RGB(255,0,255);
+			else if (LOWORD(wParam) == IDC_DARKGRAY)
+				clrref = RGB(127,127,127);
+			else if (LOWORD(wParam) == IDC_GRAY)
+				clrref = RGB(210,210,210);
+			SendMessage(GetParent(hDlg), ResultMsg, (WPARAM)clrref, (LPARAM)hDlg);
+			break;
+		}
+		case WM_CLOSE:
+			EndDialog(hDlg, TRUE);
+		case WM_DESTROY:
+			DeleteObject(hBrushWhite);
+			DeleteObject(hBrushBlack);
+			DeleteObject(hBrushDarkBlue);
+			DeleteObject(hBrushDarkGreen);
+			DeleteObject(hBrushRed);
+			DeleteObject(hBrushDarkRed);
+			DeleteObject(hBrushPurple);
+			DeleteObject(hBrushOrange);
+			DeleteObject(hBrushYellow);
+			DeleteObject(hBrushGreen);
+			DeleteObject(hBrushVDarkGreen);
+			DeleteObject(hBrushLightBlue);
+			DeleteObject(hBrushBlue);
+			DeleteObject(hBrushPink);
+			DeleteObject(hBrushDarkGray);
+			DeleteObject(hBrushGray);
+			break;
+	}
+
+	return FALSE;
+}
+
+/* Dialog procedure for the goto dialog
+ * Parameters:
+ *  hDlg    - The dialog handle
+ *  message - The message received
+ *  wParam  - The first message parameter
+ *  lParam  - The second message parameter
+ * Returns:
+ *  TRUE if the message was processed, FALSE otherwise
+ */
+LRESULT CALLBACK GotoDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	if (message == WM_COMMAND) 
+	{
+		if (LOWORD(wParam) == IDCANCEL)
+			EndDialog(hDlg, TRUE);
+		else if (LOWORD(wParam) == IDOK) 
+		{
+			HWND hWnd = GetDlgItem(GetParent(hDlg),IDC_TEXT);
+			int line = GetDlgItemInt(hDlg, IDC_GOTO, NULL, FALSE);
+			int pos = SendMessage(hWnd, EM_LINEINDEX, (WPARAM)--line, 0);
+			SendMessage(hWnd, EM_SETSEL, (WPARAM)pos, (LPARAM)pos);
+			SendMessage(hWnd, EM_SCROLLCARET, 0, 0);
+			EndDialog(hDlg, TRUE);
+		}
+	}
+	return FALSE;
+}
+
+LRESULT CALLBACK FromFileDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	HWND hWnd;
+	static FINDREPLACE find;
+	static char findbuf[256];
+	static unsigned char *file;
+	static HWND hTool, hClip, hStatus;
+	static RECT rOld;
+	CHARFORMAT2 chars;
+
+	if (message == WM_FINDMSGSTRING)
+	{
+		FINDREPLACE *fr = (FINDREPLACE *)lParam;
+
+		if (fr->Flags & FR_FINDNEXT)
+		{
+			HWND hRich = GetDlgItem(hDlg, IDC_TEXT);
+			DWORD flags=0;
+			FINDTEXTEX ft;
+			CHARRANGE chrg;
+
+			if (fr->Flags & FR_DOWN)
+				flags |= FR_DOWN;
+			if (fr->Flags & FR_MATCHCASE)
+				flags |= FR_MATCHCASE;
+			if (fr->Flags & FR_WHOLEWORD)
+				flags |= FR_WHOLEWORD;
+			ft.lpstrText = fr->lpstrFindWhat;
+			SendMessage(hRich, EM_EXGETSEL, 0, (LPARAM)&chrg);
+			if (flags & FR_DOWN)
+			{
+				ft.chrg.cpMin = chrg.cpMax;
+				ft.chrg.cpMax = -1;
+			}
+			else
+			{
+				ft.chrg.cpMin = chrg.cpMin;
+				ft.chrg.cpMax = -1;
+			}
+			if (SendMessage(hRich, EM_FINDTEXTEX, flags, (LPARAM)&ft) == -1)
+				MessageBox(NULL, "UnrealIRCd has finished searching the document",
+					"Find", MB_ICONINFORMATION|MB_OK);
+			else
+			{
+				SendMessage(hRich, EM_EXSETSEL, 0, (LPARAM)&(ft.chrgText));
+				SendMessage(hRich, EM_SCROLLCARET, 0, 0);
+				SetFocus(hRich);
+			}
+		}
+		return TRUE;
+	}
+	switch (message) 
+	{
+		case WM_INITDIALOG: 
+		{
+			int fd,len;
+			char *buffer, *string;
+			EDITSTREAM edit;
+			StreamIO *stream = safe_alloc(sizeof(StreamIO));
+			unsigned char szText[256];
+			struct stat sb;
+			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT), hTip;
+			file = (unsigned char *)lParam;
+			if (file)
+				wsprintf(szText, "UnrealIRCd Editor - %s", file);
+			else 
+				strcpy(szText, "UnrealIRCd Editor - New File");
+			SetWindowText(hDlg, szText);
+			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)RESubClassFunc);
+			hTool = DrawToolbar(hInst, hDlg);
+			hStatus = DrawStatusbar(hInst, hDlg, IDC_STATUS);
+			SendMessage(hWnd, EM_SETEVENTMASK, 0, (LPARAM)ENM_SELCHANGE);
+			chars.cbSize = sizeof(CHARFORMAT2);
+			chars.dwMask = CFM_FACE;
+			strcpy(chars.szFaceName,"Fixedsys");
+			SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_ALL, (LPARAM)&chars);
+			if ((fd = open(file, _O_RDONLY|_O_BINARY)) != -1) 
+			{
+				fstat(fd,&sb);
+				/* Only allocate the amount we need */
+				buffer = safe_alloc(sb.st_size+1);
+				len = read(fd, buffer, sb.st_size);
+				buffer[len] = 0;
+				len = CountRTFSize(buffer)+1;
+				string = safe_alloc(len);
+				IRCToRTF(buffer,string);
+				RTFBuf = string;
+				len--;
+				stream->size = &len;
+				stream->buffer = &RTFBuf;
+				edit.dwCookie = (DWORD_PTR)stream;
+				edit.pfnCallback = SplitIt;
+				SendMessage(hWnd, EM_EXLIMITTEXT, 0, (LPARAM)0x7FFFFFFF);
+				SendMessage(hWnd, EM_STREAMIN, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
+				SendMessage(hWnd, EM_SETMODIFY, (WPARAM)FALSE, 0);
+				SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0);
+				close(fd);
+				RTFBuf = NULL;
+				safe_free(buffer);
+				safe_free(string);
+				safe_free(stream);
+				hClip = SetClipboardViewer(hDlg);
+				if (SendMessage(hWnd, EM_CANPASTE, 0, 0)) 
+					SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(TRUE,0));
+				else
+					SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(FALSE,0));
+				SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_UNDO, (LPARAM)MAKELONG(FALSE,0));
+				wsprintf(szText, "Line: 1");
+				SetWindowText(hStatus, szText);
+			}
+			return TRUE;
+		}
+		case WM_WINDOWPOSCHANGING:
+		{
+			GetClientRect(hDlg, &rOld);
+			return FALSE;
+		}
+		case WM_SIZE:
+		{
+			DWORD new_width, new_height;
+			HWND hRich;
+			RECT rOldRich;
+			DWORD old_width, old_height;
+			DWORD old_rich_width, old_rich_height;
+			if (hDlg == hFind)
+				return FALSE;
+			new_width =  LOWORD(lParam);
+			new_height = HIWORD(lParam);
+			hRich  = GetDlgItem(hDlg, IDC_TEXT);
+			SendMessage(hStatus, WM_SIZE, 0, 0);
+			SendMessage(hTool, TB_AUTOSIZE, 0, 0);
+			old_width = rOld.right-rOld.left;
+			old_height = rOld.bottom-rOld.top;
+			new_width = new_width - old_width;
+			new_height = new_height - old_height;
+			GetWindowRect(hRich, &rOldRich);
+			old_rich_width = rOldRich.right-rOldRich.left;
+			old_rich_height = rOldRich.bottom-rOldRich.top;
+			SetWindowPos(hRich, NULL, 0, 0, old_rich_width+new_width, 
+				old_rich_height+new_height,
+				SWP_NOMOVE|SWP_NOREPOSITION|SWP_NOZORDER);
+			memset(&rOld, 0, sizeof(rOld));
+			return TRUE;
+		} 
+
+		case WM_NOTIFY:
+			switch (((NMHDR *)lParam)->code) 
+			{
+				case EN_SELCHANGE: 
+				{
+					HWND hWnd = GetDlgItem(hDlg, IDC_TEXT);
+					DWORD start, end, currline;
+					static DWORD prevline = 0;
+					unsigned char buffer[512];
+					chars.cbSize = sizeof(CHARFORMAT2);
+					SendMessage(hWnd, EM_GETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+					if (chars.dwMask & CFM_BOLD && chars.dwEffects & CFE_BOLD)
+						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_BOLD, (LPARAM)MAKELONG(TRUE,0));
+					else
+						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_BOLD, (LPARAM)MAKELONG(FALSE,0));
+					if (chars.dwMask & CFM_UNDERLINE && chars.dwEffects & CFE_UNDERLINE)
+						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_UNDERLINE, (LPARAM)MAKELONG(TRUE,0));
+					else
+						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_UNDERLINE, (LPARAM)MAKELONG(FALSE,0));
+					SendMessage(hWnd, EM_GETSEL,(WPARAM)&start, (LPARAM)&end);
+					if (start == end) 
+					{
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_COPY, (LPARAM)MAKELONG(FALSE,0));
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_CUT, (LPARAM)MAKELONG(FALSE,0));
+					}
+					else 
+					{
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_COPY, (LPARAM)MAKELONG(TRUE,0));
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_CUT, (LPARAM)MAKELONG(TRUE,0));
+					}
+					if (SendMessage(hWnd, EM_CANUNDO, 0, 0)) 
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_UNDO, (LPARAM)MAKELONG(TRUE,0));
+					else
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_UNDO, (LPARAM)MAKELONG(FALSE,0));
+					if (SendMessage(hWnd, EM_CANREDO, 0, 0)) 
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_REDO, (LPARAM)MAKELONG(TRUE,0));
+					else
+						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_REDO, (LPARAM)MAKELONG(FALSE,0));
+					currline = SendMessage(hWnd, EM_LINEFROMCHAR, (WPARAM)-1, 0);
+					currline++;
+					if (currline != prevline) 
+					{
+						wsprintf(buffer, "Line: %d", currline);
+						SetWindowText(hStatus, buffer);
+						prevline = currline;
+					}
+				return TRUE;
+			}
+			case TTN_GETDISPINFO: 
+			{
+				LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam;
+				lpttt->hinst = NULL;
+				switch (lpttt->hdr.idFrom) 
+				{
+					case IDM_NEW:
+						strcpy(lpttt->szText, "New");
+						break;
+					case IDM_SAVE:
+						strcpy(lpttt->szText, "Save");
+						break;
+					case IDM_CUT:
+						strcpy(lpttt->szText, "Cut");
+						break;
+					case IDM_COPY:
+						strcpy(lpttt->szText, "Copy");
+						break;
+					case IDM_PASTE:
+						strcpy(lpttt->szText, "Paste");
+						break;
+					case IDM_UNDO:
+						strcpy(lpttt->szText, "Undo");
+						break;
+					case IDM_REDO:
+						strcpy(lpttt->szText, "Redo");
+						break;
+					case IDC_BOLD:
+						strcpy(lpttt->szText, "Bold");
+						break;
+					case IDC_UNDERLINE:
+						strcpy(lpttt->szText, "Underline");
+						break;
+					case IDC_COLOR:
+						strcpy(lpttt->szText, "Text Color");
+						break;
+					case IDC_BGCOLOR:
+						strcpy(lpttt->szText, "Background Color");
+						break;
+					case IDC_GOTO:
+						strcpy(lpttt->szText, "Goto");
+						break;
+					case IDC_FIND:
+						strcpy(lpttt->szText, "Find");
+						break;
+				}
+				return TRUE;
+			}
+			case NM_DBLCLK:
+				DialogBox(hInst, "GOTO", hDlg, (DLGPROC)GotoDLG);
+				return (TRUE);
+		}
+				
+				return (TRUE);
+		case WM_COMMAND:
+			if (LOWORD(wParam) == IDC_BOLD) 
+			{
+				hWnd = GetDlgItem(hDlg, IDC_TEXT);
+				if (SendMessage(hTool, TB_ISBUTTONCHECKED, (WPARAM)IDC_BOLD, (LPARAM)0) != 0) 
+				{
+					chars.cbSize = sizeof(CHARFORMAT2);
+					chars.dwMask = CFM_BOLD;
+					chars.dwEffects = CFE_BOLD;
+					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
+					SetFocus(hWnd);
+				}
+				else 
+				{
+					chars.cbSize = sizeof(CHARFORMAT2);
+					chars.dwMask = CFM_BOLD;
+					chars.dwEffects = 0;
+					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
+					SetFocus(hWnd);
+				}
+				return TRUE;
+			}
+			else if (LOWORD(wParam) == IDC_UNDERLINE) 
+			{
+				hWnd = GetDlgItem(hDlg, IDC_TEXT);
+				if (SendMessage(hTool, TB_ISBUTTONCHECKED, (WPARAM)IDC_UNDERLINE, (LPARAM)0) != 0) 
+				{
+					chars.cbSize = sizeof(CHARFORMAT2);
+					chars.dwMask = CFM_UNDERLINETYPE;
+					chars.bUnderlineType = CFU_UNDERLINE;
+					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
+					SetFocus(hWnd);
+				}
+				else 
+				{
+					chars.cbSize = sizeof(CHARFORMAT2);
+					chars.dwMask = CFM_UNDERLINETYPE;
+					chars.bUnderlineType = CFU_UNDERLINENONE;
+					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
+					SetFocus(hWnd);
+				}
+				return TRUE;
+			}
+			if (LOWORD(wParam) == IDC_COLOR) 
+			{
+				DialogBoxParam(hInst, "Color", hDlg, (DLGPROC)ColorDLG, (LPARAM)WM_USER+10);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDC_BGCOLOR)
+			{
+				DialogBoxParam(hInst, "Color", hDlg, (DLGPROC)ColorDLG, (LPARAM)WM_USER+11);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDC_GOTO)
+			{
+				DialogBox(hInst, "GOTO", hDlg, (DLGPROC)GotoDLG);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDC_FIND)
+			{
+				static FINDREPLACE fr;
+				memset(&fr, 0, sizeof(fr));
+				fr.lStructSize = sizeof(FINDREPLACE);
+				fr.hwndOwner = hDlg;
+				fr.lpstrFindWhat = findbuf;
+				fr.wFindWhatLen = 255;
+				hFind = FindText(&fr);
+				return 0;
+			}
+				
+			hWnd = GetDlgItem(hDlg, IDC_TEXT);
+			if (LOWORD(wParam) == IDM_COPY) 
+			{
+				SendMessage(hWnd, WM_COPY, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_SELECTALL) 
+			{
+				SendMessage(hWnd, EM_SETSEL, 0, -1);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_PASTE) 
+			{
+				SendMessage(hWnd, WM_PASTE, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_CUT) 
+			{
+				SendMessage(hWnd, WM_CUT, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_UNDO) 
+			{
+				SendMessage(hWnd, EM_UNDO, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_REDO) 
+			{
+				SendMessage(hWnd, EM_REDO, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_DELETE) 
+			{
+				SendMessage(hWnd, WM_CLEAR, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_SAVE) 
+			{
+				int fd;
+				EDITSTREAM edit;
+				OPENFILENAME lpopen;
+				if (!file) 
+				{
+					unsigned char path[MAX_PATH];
+					path[0] = '\0';
+					memset(&lpopen, 0, sizeof(lpopen));
+					lpopen.lStructSize = sizeof(OPENFILENAME);
+					lpopen.hwndOwner = hDlg;
+					lpopen.lpstrFilter = NULL;
+					lpopen.lpstrCustomFilter = NULL;
+					lpopen.nFilterIndex = 0;
+					lpopen.lpstrFile = path;
+					lpopen.nMaxFile = MAX_PATH;
+					lpopen.lpstrFileTitle = NULL;
+					lpopen.lpstrInitialDir = CONFDIR;
+					lpopen.lpstrTitle = NULL;
+					lpopen.Flags = (OFN_ENABLESIZING|OFN_NONETWORKBUTTON|
+							OFN_OVERWRITEPROMPT);
+					if (GetSaveFileName(&lpopen))
+						file = path;
+					else
+						break;
+				}
+				fd = open(file, _O_TRUNC|_O_CREAT|_O_WRONLY|_O_BINARY,_S_IWRITE);
+				edit.dwCookie = 0;
+				edit.pfnCallback = BufferIt;
+				SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_STREAMOUT, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
+				RTFToIRC(fd, RTFBuf, strlen(RTFBuf));
+				safe_free(RTFBuf);
+				RTFBuf = NULL;
+				SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_SETMODIFY, (WPARAM)FALSE, 0);
+	
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_NEW) 
+			{
+				unsigned char text[1024];
+				BOOL newfile = FALSE;
+				int ans;
+				if (SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_GETMODIFY, 0, 0) != 0) 
+				{
+					sprintf(text, "The text in the %s file has changed.\r\n\r\nDo you want to save the changes?", file ? file : "new");
+					ans = MessageBox(hDlg, text, "UnrealIRCd", MB_YESNOCANCEL|MB_ICONWARNING);
+					if (ans == IDNO)
+						newfile = TRUE;
+					if (ans == IDCANCEL)
+						return TRUE;
+					if (ans == IDYES) 
+					{
+						SendMessage(hDlg, WM_COMMAND, MAKEWPARAM(IDM_SAVE,0), 0);
+						newfile = TRUE;
+					}
+				}
+				else
+					newfile = TRUE;
+				if (newfile == TRUE) 
+				{
+					unsigned char szText[256];
+					file = NULL;
+					strcpy(szText, "UnrealIRCd Editor - New File");
+					SetWindowText(hDlg, szText);
+					SetWindowText(GetDlgItem(hDlg, IDC_TEXT), NULL);
+				}
+				break;
+			}
+			break;
+		case WM_USER+10: 
+		{
+			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT);
+			EndDialog((HWND)lParam, TRUE);
+			chars.cbSize = sizeof(CHARFORMAT2);
+			chars.dwMask = CFM_COLOR;
+			chars.crTextColor = (COLORREF)wParam;
+			SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+			SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
+			SetFocus(hWnd);
+			break;
+		}
+		case WM_USER+11: 
+		{
+			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT);
+			EndDialog((HWND)lParam, TRUE);
+			chars.cbSize = sizeof(CHARFORMAT2);
+			chars.dwMask = CFM_BACKCOLOR;
+			chars.crBackColor = (COLORREF)wParam;
+			SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
+			SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
+			SetFocus(hWnd);
+			break;
+		}
+		case WM_CHANGECBCHAIN:
+			if ((HWND)wParam == hClip)
+				hClip = (HWND)lParam;
+			else
+				SendMessage(hClip, WM_CHANGECBCHAIN, wParam, lParam);
+			break;
+		case WM_DRAWCLIPBOARD:
+			if (SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_CANPASTE, 0, 0)) 
+				SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(TRUE,0));
+			else
+				SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(FALSE,0));
+			SendMessage(hClip, WM_DRAWCLIPBOARD, wParam, lParam);
+			break;
+		case WM_CLOSE: 
+		{
+			unsigned char text[256];
+			int ans;
+			if (SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_GETMODIFY, 0, 0) != 0) 
+			{
+				sprintf(text, "The text in the %s file has changed.\r\n\r\nDo you want to save the changes?", file ? file : "new");
+				ans = MessageBox(hDlg, text, "UnrealIRCd", MB_YESNOCANCEL|MB_ICONWARNING);
+				if (ans == IDNO)
+					EndDialog(hDlg, TRUE);
+				if (ans == IDCANCEL)
+					return TRUE;
+				if (ans == IDYES) 
+				{
+					SendMessage(hDlg, WM_COMMAND, MAKEWPARAM(IDM_SAVE,0), 0);
+					EndDialog(hDlg, TRUE);
+				}
+			}
+			else
+				EndDialog(hDlg, TRUE);
+			break;
+		}
+		case WM_DESTROY:
+			ChangeClipboardChain(hDlg, hClip);
+			break;
+	}
+
+	return FALSE;
+}
diff --git a/ircd/src/windows/gpl.rtf b/ircd/src/windows/gpl.rtf
@@ -0,0 +1,97 @@
+{\rtf1\ansi\ansicpg1250\deff0\deflang1033\deflangfe1060{\fonttbl{\f0\fswiss\fprq2\fcharset0 Verdana;}{\f1\fmodern\fprq1\fcharset0 Lucida Console;}}
+{\colortbl ;\red0\green0\blue0;}
+\viewkind4\uc1\pard\nowidctlpar\sb100\sa100\qc\lang1060\kerning36\b\f0\fs28 GNU General Public License
+\par \kerning0\b0\fs16 Version 2, June 1991
+\par \pard\nowidctlpar\f1\fs14 
+\par Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+\par 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+\par 
+\par Everyone is permitted to copy and distribute verbatim copies
+\par of this license document, but changing it is not allowed.
+\par 
+\par \pard\keepn\nowidctlpar\sb100\sa100\qc\cf1\b\f0\fs20 Preamble\cf0\fs24 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b0\fs16 The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. 
+\par When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 
+\par To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 
+\par For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 
+\par We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 
+\par Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. 
+\par Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 
+\par The precise terms and conditions for copying, distribution and modification follow. 
+\par \pard\keepn\nowidctlpar\sb100\sa100\qc\cf1\b\fs20 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+\par \pard\nowidctlpar\fi142\sb100\sa100\cf0\fs16 0.\b0  This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 
+\par \pard\nowidctlpar\sb100\sa100 Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b 1.\b0  You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 
+\par \pard\nowidctlpar\sb100\sa100 You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b 2.\b0  You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 
+\par \pard\nowidctlpar\li284\sb100\sa100\b a)\b0  You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 
+\par \b b)\b0  You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 
+\par \b c)\b0  If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) 
+\par \pard\nowidctlpar\sb100\sa100 These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 
+\par Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 
+\par In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b 3.\b0  You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: \v <!-- we use this doubled UL to get the sub-sections indented, --><!-- while making the bullets as unobvious as possible. -->\v0 
+\par \pard\nowidctlpar\li284\sb100\sa100\b a)\b0  Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
+\par \b b)\b0  Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
+\par \b c)\b0  Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) 
+\par \pard\nowidctlpar\sb100\sa100 The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 
+\par If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b 4.\b0  You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 
+\par \b 5.\b0  You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 
+\par \b 6.\b0  Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 
+\par \b 7.\b0  If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. 
+\par \pard\nowidctlpar\sb100\sa100 If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 
+\par It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 
+\par This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b 8.\b0  If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 
+\par \b 9.\b0  The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 
+\par \pard\nowidctlpar\sb100\sa100 Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 
+\par \pard\nowidctlpar\fi142\sb100\sa100\b 10.\b0  If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 
+\par \pard\nowidctlpar\sb100\sa100\qc\cf1\fs20 NO WARRANTY
+\par \pard\nowidctlpar\fi142\sb100\sa100\cf0\b\fs16 11.\b0  BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 
+\par \b 12.\b0  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
+\par \pard\keepn\nowidctlpar\sb100\sa100\qc\cf1\b END OF TERMS AND CONDITIONS
+\par \fs20 How to Apply These Terms to Your New Programs
+\par \pard\nowidctlpar\fi142\sb100\sa100\cf0\b0\fs16 If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 
+\par To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 
+\par \pard\nowidctlpar\li284\f1\fs14 
+\par \i one line to give the program's name and an idea of what it does.\i0 
+\par Copyright (C) \i yyyy\i0   \i name of author\i0 
+\par 
+\par This program is free software; you can redistribute it and/or
+\par modify it under the terms of the GNU General Public License
+\par as published by the Free Software Foundation; either version 2
+\par of the License, or (at your option) any later version.
+\par 
+\par This program is distributed in the hope that it will be useful,
+\par but WITHOUT ANY WARRANTY; without even the implied warranty of
+\par MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+\par GNU General Public License for more details.
+\par 
+\par You should have received a copy of the GNU General Public License
+\par along with this program; if not, write to the Free Software
+\par Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+\par 
+\par \pard\nowidctlpar\sb100\sa100\f0\fs16 Also add information on how to contact you by electronic and paper mail. 
+\par If the program is interactive, make it output a short notice like this when it starts in an interactive mode: 
+\par \pard\nowidctlpar\li284\f1\fs14 
+\par Gnomovision version 69, Copyright (C) \i year\i0  \i name of author\i0 
+\par Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
+\par type `show w'.  This is free software, and you are welcome
+\par to redistribute it under certain conditions; type `show c' 
+\par for details.
+\par 
+\par \pard\nowidctlpar\sb100\sa100\f0\fs16 The hypothetical commands \f1\fs14 `show w'\f0\fs16  and \f1\fs14 `show c'\f0\fs16  should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than \f1\fs14 `show w'\f0\fs16  and \f1\fs14 `show c'\f0\fs16 ; they could even be mouse-clicks or menu items--whatever suits your program. 
+\par You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: 
+\par \pard\nowidctlpar\li284\f1\fs14 
+\par Yoyodyne, Inc., hereby disclaims all copyright
+\par interest in the program `Gnomovision'
+\par (which makes passes at compilers) written 
+\par by James Hacker.
+\par 
+\par \i signature of Ty Coon\i0 , 1 April 1989
+\par Ty Coon, President of Vice
+\par 
+\par \pard\nowidctlpar\sb100\sa100\f0\fs16 This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. 
+\par }
+
+\ No newline at end of file
diff --git a/ircd/src/windows/gplplusssl.rtf b/ircd/src/windows/gplplusssl.rtf
@@ -0,0 +1,196 @@
+{\rtf1\ansi\ansicpg1252\uc1 \deff0\deflang1030\deflangfe1030{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f28\fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Verdana;}
+{\f29\fmodern\fcharset0\fprq1{\*\panose 020b0609040504020204}Lucida Console;}{\f30\froman\fcharset238\fprq2 Times New Roman CE;}{\f31\froman\fcharset204\fprq2 Times New Roman Cyr;}{\f33\froman\fcharset161\fprq2 Times New Roman Greek;}
+{\f34\froman\fcharset162\fprq2 Times New Roman Tur;}{\f35\froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f36\froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f37\froman\fcharset186\fprq2 Times New Roman Baltic;}
+{\f254\fswiss\fcharset238\fprq2 Verdana CE;}{\f255\fswiss\fcharset204\fprq2 Verdana Cyr;}{\f257\fswiss\fcharset161\fprq2 Verdana Greek;}{\f258\fswiss\fcharset162\fprq2 Verdana Tur;}{\f261\fswiss\fcharset186\fprq2 Verdana Baltic;}
+{\f262\fmodern\fcharset238\fprq1 Lucida Console CE;}{\f263\fmodern\fcharset204\fprq1 Lucida Console Cyr;}{\f265\fmodern\fcharset161\fprq1 Lucida Console Greek;}{\f266\fmodern\fcharset162\fprq1 Lucida Console Tur;}}{\colortbl;\red0\green0\blue0;
+\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;
+\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1030\langfe1030\cgrid\langnp1030\langfenp1030 \snext0 Normal;}{
+\s1\qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\outlinelevel0\rin0\lin0\itap0 \b\f28\fs16\lang1060\langfe1030\cgrid\langnp1060\langfenp1030 \sbasedon0 \snext0 heading 1;}{\*\cs10 \additive Default Paragraph Font;}{\*\cs15 \additive \ul\cf2 
+\sbasedon10 Hyperlink;}}{\info{\author Carsten V. Munk}{\operator Carsten V. Munk}{\creatim\yr2002\mo8\dy24\hr14\min55}{\revtim\yr2002\mo8\dy24\hr15\min10}{\version3}{\edmins15}{\nofpages5}{\nofwords2726}{\nofchars15541}{\*\company IRCsystems}
+{\nofcharsws19085}{\vern8249}}\widowctrl\ftnbj\aenddoc\hyphhotz425\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\horzdoc\dghspace120\dgvspace120\dghorigin1701\dgvorigin1984\dghshow0\dgvshow3
+\jcompress\viewkind4\viewscale100\nolnhtadjtbl \fet0\sectd \linex0\headery708\footery708\colsx708\sectdefaultcl {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3
+\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}
+{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain 
+\qc \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 \fs24\lang1030\langfe1030\cgrid\langnp1030\langfenp1030 {\b\f28\fs28\lang1060\langfe1030\kerning36\langnp1060 GNU General Public License
+\par }{\f28\fs16\lang1060\langfe1030\langnp1060 Version 2, June 1991
+\par }\pard \ql \li0\ri0\nowidctlpar\faauto\rin0\lin0\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
+\par Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+\par 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+\par 
+\par Everyone is permitted to copy and distribute verbatim copies
+\par of this license document, but changing it is not allowed.
+\par 
+\par }\pard \qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs20\cf1\lang1060\langfe1030\langnp1060 Preamble}{\b\f28\lang1060\langfe1030\langnp1060 
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make s
+ure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the G
+NU Library General Public License instead.) You can apply it to your programs, too. 
+\par When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies
+ of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 
+\par To protect your r
+ights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 
+\par For exam
+ple, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know
+ their rights. 
+\par We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 
+\par Also, for each author's protection and ours, we want to make 
+certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by other
+s will not reflect on the original authors' reputations. 
+\par Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making th
+e program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 
+\par The precise terms and conditions for copying, distribution and modification follow. 
+\par }\pard \qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs20\cf1\lang1060\langfe1030\langnp1060 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 0.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to an
+y such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into a
+nother language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 Activities other than copying, distribution and modification are not covered by this License; they are outside its s
+cope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what t
+he Program does. 
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 1.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty
+; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 
+
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 2.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 
+
+\par }\pard \ql \li284\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin284\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 a)}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 b)}{\f28\fs16\lang1060\langfe1030\langnp1060  You must cause any work that you distribute or pub
+lish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 c)}{\f28\fs16\lang1060\langfe1030\langnp1060  If the modified program normally reads commands interactively when 
+run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warran
+t
+y) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Progr
+am is not required to print an announcement.) 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, t
+hen this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the ter
+ms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 
+\par Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 
+\par In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 3.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: }{\v\f28\fs16\lang1060\langfe1030\langnp1060 
+<!-- we use this doubled UL to get the sub-sections indented, --><!-- while making the bullets as unobvious as possible. -->}{\f28\fs16\lang1060\langfe1030\langnp1060 
+\par }\pard \ql \li284\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin284\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 a)}{\f28\fs16\lang1060\langfe1030\langnp1060  Accompany it with the complete corresponding machine-readabl
+e source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 b)}{\f28\fs16\lang1060\langfe1030\langnp1060  Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no mo
+re than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
+
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 c)}{\f28\fs16\lang1060\langfe1030\langnp1060  Ac
+company it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offe
+r, in accord with Subsection b above.) 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
+The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated i
+nterface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary for
+m) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 
+\par If distribution of executable or object code is made by offering access to copy from a
+ designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 4.}{\f28\fs16\lang1060\langfe1030\langnp1060  You may not copy, modif
+y, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, partie
+s who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 5.}{\f28\fs16\lang1060\langfe1030\langnp1060  You are not required to accept this License, since you have not signed it. However, nothing els
+e grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicat
+e your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 6.}{\f28\fs16\lang1060\langfe1030\langnp1060  Each time you redistribute the Program (or any work based on the Program), the recipient automatically 
+receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible fo
+r enforcing compliance by third parties to this License. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 7.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreeme
+nt or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, 
+t
+hen as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could sa
+tisfy both it and this License would be to refrain entirely from distribution of the Program. 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 
+\par It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting 
+the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of t
+hat system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 
+\par This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 8.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geogra
+phical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 9.}{\f28\fs16\lang1060\langfe1030\langnp1060  The Free Softwar
+e Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 Each version is given a
+ distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by th
+e Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 10.}{\f28\fs16\lang1060\langfe1030\langnp1060  If you wish to incorporate parts of the Program into other free programs whose dist
+ribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by t
+he two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 
+\par }\pard \qc \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs20\cf1\lang1060\langfe1030\langnp1060 NO WARRANTY
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 11.}{\f28\fs16\lang1060\langfe1030\langnp1060 
+ BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EX
+TENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTI
+ES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 
+\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 12.}{\f28\fs16\lang1060\langfe1030\langnp1060  IN NO EV
+ENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONS
+E
+QUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS
+), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
+\par }\pard \qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\cf1\lang1060\langfe1030\langnp1060 END OF TERMS AND CONDITIONS
+\par }{\b\f28\fs20\cf1\lang1060\langfe1030\langnp1060 How to Apply These Terms to Your New Programs
+\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 If you develop a new program, and you want it to be of the greatest possible use to the pub
+lic, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 
+\par To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effecti
+vely convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 
+\par }\pard \ql \li284\ri0\nowidctlpar\faauto\rin0\lin284\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
+\par }{\i\f29\fs14\lang1060\langfe1030\langnp1060 one line to give the program's name and an idea of what it does.}{\f29\fs14\lang1060\langfe1030\langnp1060 
+\par Copyright (C) }{\i\f29\fs14\lang1060\langfe1030\langnp1060 yyyy}{\f29\fs14\lang1060\langfe1030\langnp1060   }{\i\f29\fs14\lang1060\langfe1030\langnp1060 name of author}{\f29\fs14\lang1060\langfe1030\langnp1060 
+\par 
+\par This program is free software; you can redistribute it and/or
+\par modify it under the terms of the GNU General Public License
+\par as published by the Free Software Foundation; either version 2
+\par of the License, or (at your option) any later version.
+\par 
+\par This program is distributed in the hope that it will be useful,
+\par but WITHOUT ANY WARRANTY; without even the implied warranty of
+\par MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+\par GNU General Public License for more details.
+\par 
+\par You should have received a copy of the GNU General Public License
+\par along with this program; if not, write to the Free Software
+\par Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+\par 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 Also add information on how to contact you by electronic and paper mail. 
+\par If the program is interactive, make it output a short notice like this when it starts in an interactive mode: 
+\par }\pard \ql \li284\ri0\nowidctlpar\faauto\rin0\lin284\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
+\par Gnomovision version 69, Copyright (C) }{\i\f29\fs14\lang1060\langfe1030\langnp1060 year}{\f29\fs14\lang1060\langfe1030\langnp1060  }{\i\f29\fs14\lang1060\langfe1030\langnp1060 name of author}{\f29\fs14\lang1060\langfe1030\langnp1060 
+\par Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
+\par type `show w'.  This is free software, and you are welcome
+\par to redistribute it under certain conditions; type `show c' 
+\par for details.
+\par 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 The hypothetical commands }{\f29\fs14\lang1060\langfe1030\langnp1060 `show w'}{\f28\fs16\lang1060\langfe1030\langnp1060  and }{
+\f29\fs14\lang1060\langfe1030\langnp1060 `show c'}{\f28\fs16\lang1060\langfe1030\langnp1060  should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than }{
+\f29\fs14\lang1060\langfe1030\langnp1060 `show w'}{\f28\fs16\lang1060\langfe1030\langnp1060  and }{\f29\fs14\lang1060\langfe1030\langnp1060 `show c'}{\f28\fs16\lang1060\langfe1030\langnp1060 
+; they could even be mouse-clicks or menu items--whatever suits your program. 
+\par You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: 
+\par }\pard \ql \li284\ri0\nowidctlpar\faauto\rin0\lin284\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
+\par Yoyodyne, Inc., hereby disclaims all copyright
+\par interest in the program `Gnomovision'
+\par (which makes passes at compilers) written 
+\par by James Hacker.
+\par 
+\par }{\i\f29\fs14\lang1060\langfe1030\langnp1060 signature of Ty Coon}{\f29\fs14\lang1060\langfe1030\langnp1060 , 1 April 1989
+\par Ty Coon, President of Vice
+\par 
+\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 This Gen
+eral Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do,
+ use the GNU Library General Public License instead of this License. 
+\par 
+\par }\pard\plain \s1\qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\outlinelevel0\rin0\lin0\itap0 \b\f28\fs16\lang1060\langfe1030\cgrid\langnp1060\langfenp1030 {ADDITIONAL INFORMATION WITH REGARDS TO SSL-ENABLED UNREALIRCD
+\par }\pard\plain \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 \fs24\lang1030\langfe1030\cgrid\langnp1030\langfenp1030 {\f28\fs16\lang1060\langfe1030\langnp1060 
+This software package uses strong cryptography, so even if it is created, maintained and distributed from liberal countries in Europe (w
+here it is legal to do this), it falls under certain export/import and/or use restrictions in some other parts of the world. 
+\par PLEASE REMEMBER THAT EXPORT/IMPORT AND/OR USE OF STRONG CRYPTOGRAPHY SOFTWARE, PROVIDING CRYPTOGRAPHY HOOKS OR EVEN JUST COMMUNICA
+TING TECHNICAL DETAILS ABOUT CRYPTOGRAPHY SOFTWARE IS ILLEGAL IN SOME PARTS OF THE WORLD. SO, WHEN YOU IMPORT THIS PACKAGE TO YOUR COUNTRY, RE-DISTRIBUTE IT FROM THERE OR EVEN JUST EMAIL TECHNICAL SUGGESTIONS OR EVEN SOURCE PATCHES TO THE AUTHOR OR OTHER 
+PEOPLE YOU ARE STRONGLY ADVISED TO PAY CLOSE ATTENTION TO ANY EXPORT/IMPORT AND/OR USE LAWS WHICH APPLY TO YOU. THE AUTHORS OF OPENSSL, LIBRESSL OR UNREALIRCD ARE NOT LIABLE FOR ANY VIOLATIONS YOU MAKE HERE. SO BE CAREFUL, IT IS YOUR RESPONSIBILITY.
+\par CREDIT INFORMATION:
+\par This product includes cryptographic software written by Eric A. Young (eay@cryptsoft.com). This product includes software written by Tim J. Hudson (}{\field{\*\fldinst {\f28\fs16\lang1060\langfe1030\langnp1060  HYPERLINK "mailto:tjh@cryptsoft.com" }{
+\f28\fs16\lang1060\langfe1030\langnp1060 {\*\datafield 
+00d0c9ea79f9bace118c8200aa004ba90b02000000170000001200000074006a00680040006300720079007000740073006f00660074002e0063006f006d000000e0c9ea79f9bace118c8200aa004ba90b320000006d00610069006c0074006f003a0074006a00680040006300720079007000740073006f00660074002e00
+63006f006d000000}}}{\fldrslt {\cs15\f28\fs16\ul\cf2\lang1060\langfe1030\langnp1060 tjh@cryptsoft.com}}}{\f28\fs16\lang1060\langfe1030\langnp1060 ). This product uses the LibreSSL (https://www.libressl.org) or OpenSSL library (https://www.openssl.org).
+\par 
+\par }}
+\ No newline at end of file
diff --git a/ircd/src/windows/gui.c b/ircd/src/windows/gui.c
@@ -0,0 +1,1051 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/gui.c
+ *   Copyright (C) 2000-2004 David Flynn (DrBin) & Dominick Meglio (codemastr)
+ *   
+ *   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.
+ */
+
+#define WIN32_VERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5
+
+#include "unrealircd.h"
+#include <windowsx.h>
+#include <commctrl.h>
+#include <commdlg.h>
+#include <richedit.h>
+#include <Strsafe.h>
+#include "resource.h"
+#include "win.h"
+
+#define TOOLBAR_START 82
+#define TOOLBAR_STOP (TOOLBAR_START+20)
+
+__inline void ShowDialog(HWND *handle, HINSTANCE inst, char *template, HWND parent, 
+			 DLGPROC proc)
+{
+	if (!IsWindow(*handle)) 
+	{
+		*handle = CreateDialog(inst, template, parent, (DLGPROC)proc); 
+		ShowWindow(*handle, SW_SHOW);
+	}
+	else
+		SetForegroundWindow(*handle);
+}
+
+/* Comments:
+ * 
+ * DrBin did a great job with the original GUI, but he has been gone a long time.
+ * In his absense, it was decided it would be best to continue windows development.
+ * The new code is based on his so it will be pretty much similar in features, my
+ * main goal is to make it more stable. A lot of what I know about GUI coding 
+ * I learned from DrBin so thanks to him for teaching me :) -- codemastr
+ */
+
+LRESULT CALLBACK MainDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK LicenseDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK InfoDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK CreditsDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK HelpDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK StatusDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK ConfigErrorDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK FromVarDLG(HWND, UINT, WPARAM, LPARAM, unsigned char *, unsigned char **);
+LRESULT CALLBACK FromFileReadDLG(HWND, UINT, WPARAM, LPARAM);
+LRESULT CALLBACK FromFileDLG(HWND, UINT, WPARAM, LPARAM);
+
+HBRUSH MainDlgBackground;
+
+extern void SocketLoop(void *dummy);
+HINSTANCE hInst;
+NOTIFYICONDATA SysTray;
+HTREEITEM AddItemToTree(HWND, LPSTR, int, short);
+void win_map(Client *, HWND, short);
+extern Link *Servers;
+unsigned char *errors = NULL;
+extern VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv);
+void CleanUp(void)
+{
+	Shell_NotifyIcon(NIM_DELETE ,&SysTray);
+}
+HWND hStatusWnd;
+HWND hwIRCDWnd=NULL;
+HWND hwTreeView;
+HWND hWndMod;
+UINT WM_TASKBARCREATED, WM_FINDMSGSTRING;
+FARPROC lpfnOldWndProc;
+HMENU hContext;
+char OSName[OSVER_SIZE];
+
+void TaskBarCreated() 
+{
+	HICON hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(ICO_MAIN), IMAGE_ICON,16, 16, 0);
+	SysTray.cbSize = sizeof(NOTIFYICONDATA);
+	SysTray.hIcon = hIcon;
+	SysTray.hWnd = hwIRCDWnd;
+	SysTray.uCallbackMessage = WM_USER;
+	SysTray.uFlags = NIF_ICON|NIF_TIP|NIF_MESSAGE;
+	SysTray.uID = 0;
+	strcpy(SysTray.szTip, WIN32_VERSION);
+	Shell_NotifyIcon(NIM_ADD ,&SysTray);
+}
+
+LRESULT LinkSubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) 
+{
+	static HCURSOR hCursor;
+	if (!hCursor)
+		hCursor = LoadCursor(hInst, MAKEINTRESOURCE(CUR_HAND));
+	if (Message == WM_MOUSEMOVE || Message == WM_LBUTTONDOWN)
+		SetCursor(hCursor);
+
+	return CallWindowProc((WNDPROC)lpfnOldWndProc, hWnd, Message, wParam, lParam);
+}
+
+
+
+LRESULT RESubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) 
+{
+	POINT p;
+	RECT r;
+	DWORD start, end;
+	unsigned char string[500];
+
+	if (Message == WM_GETDLGCODE)
+		return DLGC_WANTALLKEYS;
+
+	
+	if (Message == WM_CONTEXTMENU) 
+	{
+		p.x = GET_X_LPARAM(lParam);
+		p.y = GET_Y_LPARAM(lParam);
+		if (GET_X_LPARAM(lParam) == -1 && GET_Y_LPARAM(lParam) == -1) 
+		{
+			GetClientRect(hWnd, &r);
+			p.x = (int)((r.left + r.right)/2);
+			p.y = (int)((r.top + r.bottom)/2);
+			ClientToScreen(hWnd,&p);
+		}
+		if (!SendMessage(hWnd, EM_CANUNDO, 0, 0)) 
+			EnableMenuItem(hContext, IDM_UNDO, MF_BYCOMMAND|MF_GRAYED);
+		else
+			EnableMenuItem(hContext, IDM_UNDO, MF_BYCOMMAND|MF_ENABLED);
+		if (!SendMessage(hWnd, EM_CANPASTE, 0, 0)) 
+			EnableMenuItem(hContext, IDM_PASTE, MF_BYCOMMAND|MF_GRAYED);
+		else
+			EnableMenuItem(hContext, IDM_PASTE, MF_BYCOMMAND|MF_ENABLED);
+		if (GetWindowLong(hWnd, GWL_STYLE) & ES_READONLY) 
+		{
+			EnableMenuItem(hContext, IDM_CUT, MF_BYCOMMAND|MF_GRAYED);
+			EnableMenuItem(hContext, IDM_DELETE, MF_BYCOMMAND|MF_GRAYED);
+		}
+		else 
+		{
+			EnableMenuItem(hContext, IDM_CUT, MF_BYCOMMAND|MF_ENABLED);
+			EnableMenuItem(hContext, IDM_DELETE, MF_BYCOMMAND|MF_ENABLED);
+		}
+		SendMessage(hWnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
+		if (start == end) 
+			EnableMenuItem(hContext, IDM_COPY, MF_BYCOMMAND|MF_GRAYED);
+		else
+			EnableMenuItem(hContext, IDM_COPY, MF_BYCOMMAND|MF_ENABLED);
+		TrackPopupMenu(hContext,TPM_LEFTALIGN|TPM_RIGHTBUTTON,p.x,p.y,0,GetParent(hWnd),NULL);
+		return 0;
+	}
+
+	return CallWindowProc((WNDPROC)lpfnOldWndProc, hWnd, Message, wParam, lParam);
+}
+
+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;
+	DoCloseUnreal(hWnd);
+	exit(0);
+}
+
+int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+{
+	MSG msg;
+	unsigned char *s;
+	HWND hWnd;
+	HICON hIcon;
+	SC_HANDLE hService, hSCManager;
+	SERVICE_TABLE_ENTRY DispatchTable[] = 
+	{
+		{ "UnrealIRCd", ServiceMain },
+		{ 0, 0 }
+	};
+	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 6"
+	 */
+	chdir("..");
+
+	GetOSName(OSName);
+
+	/* Check if we are running as a service... */
+	hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+	if ((hService = OpenService(hSCManager, "UnrealIRCd", SC_MANAGER_CONNECT)))
+	{
+		int save_err = 0;
+		StartServiceCtrlDispatcher(DispatchTable);
+		if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
+		{
+			SERVICE_STATUS status;
+			/* Restart handling, it's ugly but it's as
+			 * pretty as it is gonna get :)
+			 */
+			if (__argc == 2 && !strcmp(__argv[1], "restartsvc"))
+			{
+				QueryServiceStatus(hService, &status);
+				if (status.dwCurrentState != SERVICE_STOPPED)
+				{
+					ControlService(hService,
+						SERVICE_CONTROL_STOP, &status);
+					while (status.dwCurrentState == SERVICE_STOP_PENDING)
+					{
+						QueryServiceStatus(hService, &status);
+						if (status.dwCurrentState != SERVICE_STOPPED)
+							Sleep(1000);
+					}
+				}
+			}
+			if (!StartService(hService, 0, NULL))
+				save_err = GetLastError();
+		}
+
+		CloseServiceHandle(hService);
+		CloseServiceHandle(hSCManager);
+		if (save_err != ERROR_SERVICE_DISABLED)
+			exit(0);
+	} else {
+		CloseServiceHandle(hSCManager);
+	}
+	InitCommonControls();
+	WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated");
+	WM_FINDMSGSTRING = RegisterWindowMessage(FINDMSGSTRING);
+	atexit(CleanUp);
+	if (!LoadLibrary("riched20.dll"))
+		LoadLibrary("riched32.dll");
+	InitDebug();
+	init_winsock();
+	hInst = hInstance; 
+
+	MainDlgBackground = CreateSolidBrush(RGB(75, 134, 238)); /* Background of main dialog */
+
+	hWnd = CreateDialog(hInstance, "UnrealIRCd", 0, (DLGPROC)MainDLG); 
+	hwIRCDWnd = hWnd;
+	
+	TaskBarCreated();
+
+	if (InitUnrealIRCd(__argc, __argv) != 1)
+	{
+		MessageBox(NULL, "UnrealIRCd has failed to initialize in InitUnrealIRCd()", "UnrealIRCD Initalization Error" ,MB_OK);
+		return FALSE;
+	}
+	ShowWindow(hWnd, SW_SHOW);
+	_beginthread(SocketLoop, 0, NULL);
+	while (GetMessage(&msg, NULL, 0, 0))
+	{
+		if (!IsWindow(hStatusWnd) || !IsDialogMessage(hStatusWnd, &msg)) 
+		{
+			TranslateMessage(&msg);
+			DispatchMessage(&msg);
+		}
+	}
+	return FALSE;
+
+}
+
+LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+	static HCURSOR hCursor;
+	static HMENU hRehash, hAbout, hConfig, hTray, hLogs;
+
+	unsigned char *argv[3];
+	Client *pClient;
+	unsigned char *msg;
+	POINT p;
+
+	if (message == WM_TASKBARCREATED)
+	{
+		TaskBarCreated();
+		return TRUE;
+	}
+	
+	switch (message)
+	{
+		case WM_INITDIALOG: 
+		{
+			ShowWindow(hDlg, SW_HIDE);
+			hCursor = LoadCursor(hInst, MAKEINTRESOURCE(CUR_HAND));
+			hContext = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_CONTEXT)),0);
+			/* Rehash popup menu */
+			hRehash = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_REHASH)),0);
+			/* About popup menu */
+			hAbout = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_ABOUT)),0);
+			/* Systray popup menu set the items to point to the other menus*/
+			hTray = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_SYSTRAY)),0);
+			ModifyMenu(hTray, IDM_REHASH, MF_BYCOMMAND|MF_POPUP|MF_STRING, HandleToUlong(hRehash), "&Rehash");
+			ModifyMenu(hTray, IDM_ABOUT, MF_BYCOMMAND|MF_POPUP|MF_STRING, HandleToUlong(hAbout), "&About");
+			
+			SetWindowText(hDlg, WIN32_VERSION);
+			SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, 
+				(LPARAM)(HICON)LoadImage(hInst, MAKEINTRESOURCE(ICO_MAIN), IMAGE_ICON,16, 16, 0));
+			SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, 
+				(LPARAM)(HICON)LoadImage(hInst, MAKEINTRESOURCE(ICO_MAIN), IMAGE_ICON,32, 32, 0));
+			return TRUE;
+		}
+		case WM_CTLCOLORDLG:
+			return (LONG)HandleToLong(MainDlgBackground);
+		case WM_SIZE: 
+		{
+			if (wParam & SIZE_MINIMIZED)
+				ShowWindow(hDlg,SW_HIDE);
+			return 0;
+		}
+		case WM_CLOSE: 
+			return DoCloseUnreal(hDlg);
+		case WM_USER: 
+		{
+			switch(LOWORD(lParam)) 
+			{
+				case WM_LBUTTONDBLCLK:
+					ShowWindow(hDlg, SW_SHOW);
+					ShowWindow(hDlg,SW_RESTORE);
+					SetForegroundWindow(hDlg);
+				case WM_RBUTTONDOWN:
+					SetForegroundWindow(hDlg);
+					break;
+				case WM_RBUTTONUP: 
+				{
+					unsigned long i = 60000;
+					MENUITEMINFO mii;
+					GetCursorPos(&p);
+					DestroyMenu(hConfig);
+					hConfig = CreatePopupMenu();
+					DestroyMenu(hLogs);
+					hLogs = CreatePopupMenu();
+					AppendMenu(hConfig, MF_STRING, IDM_CONF, CPATH);
+#if 0
+					if (conf_log) 
+					{
+						ConfigItem_log *logs;
+						AppendMenu(hConfig, MF_POPUP|MF_STRING, HandleToUlong(hLogs), "Logs");
+						for (logs = conf_log; logs; logs = logs->next)
+						{
+							AppendMenu(hLogs, MF_STRING, i++, logs->file);
+						}
+					}
+					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
+#endif
+					if (conf_files)
+					{
+						AppendMenu(hConfig, MF_STRING, IDM_MOTD, conf_files->motd_file);
+						AppendMenu(hConfig, MF_STRING, IDM_SMOTD, conf_files->smotd_file);
+						AppendMenu(hConfig, MF_STRING, IDM_OPERMOTD, conf_files->opermotd_file);
+						AppendMenu(hConfig, MF_STRING, IDM_BOTMOTD, conf_files->botmotd_file);
+						AppendMenu(hConfig, MF_STRING, IDM_RULES, conf_files->rules_file);
+					}
+						
+					if (conf_tld) 
+					{
+						ConfigItem_tld *tlds;
+						AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
+						for (tlds = conf_tld; tlds; tlds = tlds->next)
+						{
+							if (!tlds->flag.motdptr)
+								AppendMenu(hConfig, MF_STRING, i++, tlds->motd_file);
+							if (!tlds->flag.ruleclient)
+								AppendMenu(hConfig, MF_STRING, i++, tlds->rules_file);
+							if (tlds->smotd_file)
+								AppendMenu(hConfig, MF_STRING, i++, tlds->smotd_file);
+						}
+					}
+					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
+					AppendMenu(hConfig, MF_STRING, IDM_NEW, "New File");
+					mii.cbSize = sizeof(MENUITEMINFO);
+					mii.fMask = MIIM_SUBMENU;
+					mii.hSubMenu = hConfig;
+					SetMenuItemInfo(hTray, IDM_CONFIG, MF_BYCOMMAND, &mii);
+					TrackPopupMenu(hTray, TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
+					/* Kludge for a win bug */
+					SendMessage(hDlg, WM_NULL, 0, 0);
+					break;
+				}
+			}
+			return 0;
+		}
+		case WM_DESTROY:
+			return 0;
+		case WM_MOUSEMOVE: 
+		{
+			POINT p;
+			p.x = LOWORD(lParam);
+			p.y = HIWORD(lParam);
+			if ((p.x >= 93) && (p.x <= 150) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+				SetCursor(hCursor);
+			else if ((p.x >= 160) && (p.x <= 208) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+				SetCursor(hCursor);
+			else if ((p.x >= 219) && (p.x <= 267) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+				SetCursor(hCursor);
+			else if ((p.x >= 279) && (p.x <= 325) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+				SetCursor(hCursor);
+			else if ((p.x >= 336) && (p.x <= 411) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+				SetCursor(hCursor);
+			return 0;
+		}
+		case WM_LBUTTONDOWN: 
+		{
+			POINT p;
+			p.x = LOWORD(lParam);
+			p.y = HIWORD(lParam);
+			if ((p.x >= 93) && (p.x <= 150) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP))
+			{
+				ClientToScreen(hDlg,&p);
+				TrackPopupMenu(hRehash,TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
+				return 0;
+			}
+			else if ((p.x >= 160) && (p.x <= 208) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP))
+			{
+				ShowDialog(&hStatusWnd, hInst, "Status", hDlg, StatusDLG);
+				return 0;
+			}
+			else if ((p.x >= 219) && (p.x <= 267) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP))
+			{
+				unsigned long i = 60000;
+				ClientToScreen(hDlg,&p);
+				DestroyMenu(hConfig);
+				hConfig = CreatePopupMenu();
+				DestroyMenu(hLogs);
+				hLogs = CreatePopupMenu();
+
+				AppendMenu(hConfig, MF_STRING, IDM_CONF, CPATH);
+#if 0
+				if (conf_log) 
+				{
+					ConfigItem_log *logs;
+					AppendMenu(hConfig, MF_POPUP|MF_STRING, HandleToUlong(hLogs), "Logs");
+					for (logs = conf_log; logs; logs = logs->next)
+					{
+						AppendMenu(hLogs, MF_STRING, i++, logs->file);
+					}
+				}
+				AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
+#endif
+				if (conf_files)
+				{
+					AppendMenu(hConfig, MF_STRING, IDM_MOTD, conf_files->motd_file);
+					AppendMenu(hConfig, MF_STRING, IDM_SMOTD, conf_files->smotd_file);
+					AppendMenu(hConfig, MF_STRING, IDM_OPERMOTD, conf_files->opermotd_file);
+					AppendMenu(hConfig, MF_STRING, IDM_BOTMOTD, conf_files->botmotd_file);
+					AppendMenu(hConfig, MF_STRING, IDM_RULES, conf_files->rules_file);
+				}
+				
+				if (conf_tld) 
+				{
+					ConfigItem_tld *tlds;
+					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
+					for (tlds = conf_tld; tlds; tlds = tlds->next)
+					{
+						if (!tlds->flag.motdptr)
+							AppendMenu(hConfig, MF_STRING, i++, tlds->motd_file);
+						if (!tlds->flag.ruleclient)
+							AppendMenu(hConfig, MF_STRING, i++, tlds->rules_file);
+						if (tlds->smotd_file)
+							AppendMenu(hConfig, MF_STRING, i++, tlds->smotd_file);
+					}
+				}
+				AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
+				AppendMenu(hConfig, MF_STRING, IDM_NEW, "New File");
+				TrackPopupMenu(hConfig,TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
+
+				return 0;
+			}
+			else if ((p.x >= 279) && (p.x <= 325) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+			{
+				ClientToScreen(hDlg,&p);
+				TrackPopupMenu(hAbout,TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
+				return 0;
+			}
+			else if ((p.x >= 336) && (p.x <= 411) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
+				return AskCloseUnreal(hDlg);
+		}
+		case WM_SYSCOMMAND:
+		{
+			if (wParam == SC_CLOSE)
+			{
+				AskCloseUnreal(hDlg);
+				return 1;
+			}
+			break;
+		}
+		case WM_COMMAND: 
+		{
+			if (LOWORD(wParam) >= 60000 && HIWORD(wParam) == 0 && !lParam) 
+			{
+				unsigned char path[MAX_PATH];
+				if (GetMenuString(hLogs, LOWORD(wParam), path, MAX_PATH, MF_BYCOMMAND))
+					DialogBoxParam(hInst, "FromVar", hDlg, (DLGPROC)FromFileReadDLG, (LPARAM)path);
+				
+				else 
+				{
+					GetMenuString(hConfig,LOWORD(wParam), path, MAX_PATH, MF_BYCOMMAND);
+					if (!url_is_valid(path))
+						DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, (LPARAM)path);
+				}
+				return FALSE;
+			}
+
+			if (!loop.booted)
+			{
+				MessageBox(NULL, "UnrealIRCd not booted due to configuration errors. "
+				                 "Check other window for error details. Then close that window, "
+				                 "fix the errors and start UnrealIRCd again.",
+				                 "UnrealIRCd not started",
+				                 MB_OK);
+				return FALSE;
+			}
+			switch(LOWORD(wParam)) 
+			{
+				case IDM_STATUS:
+					ShowDialog(&hStatusWnd, hInst, "Status", hDlg,StatusDLG);
+					break;
+				case IDM_SHUTDOWN:
+					return AskCloseUnreal(hDlg);
+				case IDM_RHALL:
+					MessageBox(NULL, "Rehashing all files", "Rehashing", MB_OK);
+					dorehash = 1;
+					break;
+				case IDM_LICENSE: 
+					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)LicenseDLG);
+					break;
+				case IDM_INFO:
+					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)InfoDLG);
+					break;
+				case IDM_CREDITS:
+					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)CreditsDLG);
+					break;
+				case IDM_HELP:
+					DialogBox(hInst, "Help", hDlg, (DLGPROC)HelpDLG);
+					break;
+				case IDM_CONF:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, 
+						(LPARAM)CPATH);
+					break;
+				case IDM_MOTD:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, 
+						(LPARAM)conf_files->motd_file);
+					break;
+				case IDM_SMOTD:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, 
+						(LPARAM)conf_files->smotd_file);
+					break;
+				case IDM_OPERMOTD:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG,
+						(LPARAM)conf_files->opermotd_file);
+					break;
+				case IDM_BOTMOTD:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG,
+						(LPARAM)conf_files->botmotd_file);
+					break;
+				case IDM_RULES:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG,
+						(LPARAM)conf_files->rules_file);
+					break;
+				case IDM_NEW:
+					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, (LPARAM)NULL);
+					break;
+			}
+		}
+	}
+	return FALSE;
+}
+
+LRESULT CALLBACK LicenseDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	return FromVarDLG(hDlg, message, wParam, lParam, "UnrealIRCd License", gnulicense);
+}
+
+LRESULT CALLBACK InfoDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	return FromVarDLG(hDlg, message, wParam, lParam, "UnrealIRCd Team", unrealinfo);
+}
+
+LRESULT CALLBACK CreditsDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	return FromVarDLG(hDlg, message, wParam, lParam, "UnrealIRCd Credits", unrealcredits);
+}
+
+LRESULT CALLBACK FromVarDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam,
+                            unsigned char *title, unsigned char **s) 
+{
+	HWND hWnd;
+	switch (message) 
+	{
+		case WM_INITDIALOG: 
+		{
+#if 0
+			unsigned char	String[16384];
+			int size;
+			unsigned char *RTFString;
+			StreamIO *stream = safe_alloc(sizeof(StreamIO));
+			EDITSTREAM edit;
+			SetWindowText(hDlg, title);
+			memset(String, 0, sizeof(String));
+			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(GetDlgItem(hDlg, IDC_TEXT), GWLP_WNDPROC, (LONG_PTR)RESubClassFunc);
+			while (*s) 
+			{
+				strcat(String, *s++);
+				if (*s)
+					strcat(String, "\r\n");
+			}
+			size = CountRTFSize(String)+1;
+			RTFString = safe_alloc(size);
+			IRCToRTF(String,RTFString);
+			RTFBuf = RTFString;
+			size--;
+			stream->size = &size;
+			stream->buffer = &RTFBuf;
+			edit.dwCookie = HandleToUlong(stream);
+			edit.pfnCallback = SplitIt;
+			SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_STREAMIN, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
+			safe_free(RTFString);
+			safe_free(stream);
+			return TRUE;
+#else
+			return FALSE;
+#endif
+		}
+
+		case WM_COMMAND: 
+		{
+			hWnd = GetDlgItem(hDlg, IDC_TEXT);
+			if (LOWORD(wParam) == IDOK)
+				return EndDialog(hDlg, TRUE);
+			if (LOWORD(wParam) == IDM_COPY) 
+			{
+				SendMessage(hWnd, WM_COPY, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_SELECTALL) 
+			{
+				SendMessage(hWnd, EM_SETSEL, 0, -1);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_PASTE) 
+			{
+				SendMessage(hWnd, WM_PASTE, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_CUT) 
+			{
+				SendMessage(hWnd, WM_CUT, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_UNDO) 
+			{
+				SendMessage(hWnd, EM_UNDO, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_DELETE) 
+			{
+				SendMessage(hWnd, WM_CLEAR, 0, 0);
+				return 0;
+			}
+			break;
+		}
+		case WM_CLOSE:
+			EndDialog(hDlg, TRUE);
+			break;
+		case WM_DESTROY:
+			break;
+	}
+	return (FALSE);
+}
+
+LRESULT CALLBACK FromFileReadDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	HWND hWnd;
+	switch (message) 
+	{
+		case WM_INITDIALOG: 
+		{
+			int fd,len;
+			unsigned char *buffer = '\0', *string = '\0';
+			EDITSTREAM edit;
+			StreamIO *stream = safe_alloc(sizeof(StreamIO));
+			unsigned char szText[256];
+			struct stat sb;
+			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT), hTip;
+			StringCbPrintf(szText, sizeof(szText), "UnrealIRCd Viewer - %s", (unsigned char *)lParam);
+			SetWindowText(hDlg, szText);
+			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)RESubClassFunc);
+			if ((fd = open((unsigned char *)lParam, _O_RDONLY|_O_BINARY)) != -1) 
+			{
+				fstat(fd,&sb);
+				/* Only allocate the amount we need */
+				buffer = safe_alloc(sb.st_size+1);
+				buffer[0] = 0;
+				len = read(fd, buffer, sb.st_size); 
+				buffer[len] = 0;
+				len = CountRTFSize(buffer)+1;
+				string = safe_alloc(len);
+				IRCToRTF(buffer,string);
+				RTFBuf = string;
+				len--;
+				stream->size = &len;
+				stream->buffer = &RTFBuf;
+				edit.dwCookie = (DWORD_PTR)stream;
+				edit.pfnCallback = SplitIt;
+				SendMessage(hWnd, EM_EXLIMITTEXT, 0, (LPARAM)0x7FFFFFFF);
+				SendMessage(hWnd, EM_STREAMIN, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
+				close(fd);
+				RTFBuf = NULL;
+				safe_free(buffer);
+				safe_free(string);
+				safe_free(stream);
+			}
+			return TRUE;
+		}
+		case WM_COMMAND: 
+		{
+			hWnd = GetDlgItem(hDlg, IDC_TEXT);
+			if (LOWORD(wParam) == IDOK)
+				return EndDialog(hDlg, TRUE);
+			if (LOWORD(wParam) == IDM_COPY) 
+			{
+				SendMessage(hWnd, WM_COPY, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_SELECTALL) 
+			{
+				SendMessage(hWnd, EM_SETSEL, 0, -1);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_PASTE) 
+			{
+				SendMessage(hWnd, WM_PASTE, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_CUT) 
+			{
+				SendMessage(hWnd, WM_CUT, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_UNDO) 
+			{
+				SendMessage(hWnd, EM_UNDO, 0, 0);
+				return 0;
+			}
+			if (LOWORD(wParam) == IDM_DELETE) 
+			{
+				SendMessage(hWnd, WM_CLEAR, 0, 0);
+				return 0;
+			}
+			break;
+		}
+		case WM_CLOSE:
+			EndDialog(hDlg, TRUE);
+			break;
+		case WM_DESTROY:
+			break;
+	}
+	return FALSE;
+}
+
+LRESULT CALLBACK HelpDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	static HFONT hFont;
+	static HCURSOR hCursor;
+	switch (message) 
+	{
+		case WM_INITDIALOG:
+			hCursor = LoadCursor(hInst, MAKEINTRESOURCE(CUR_HAND));
+			hFont = CreateFont(8,0,0,0,0,0,1,0,ANSI_CHARSET,0,0,PROOF_QUALITY,0,"MS Sans Serif");
+			SendMessage(GetDlgItem(hDlg, IDC_EMAIL), WM_SETFONT, (WPARAM)hFont,TRUE);
+			SendMessage(GetDlgItem(hDlg, IDC_URL), WM_SETFONT, (WPARAM)hFont,TRUE);
+			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(GetDlgItem(hDlg, IDC_EMAIL), GWLP_WNDPROC, (LONG_PTR)LinkSubClassFunc);
+			SetWindowLongPtr(GetDlgItem(hDlg, IDC_URL), GWLP_WNDPROC, (LONG_PTR)LinkSubClassFunc);
+			return TRUE;
+
+		case WM_DRAWITEM: 
+		{
+			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
+			unsigned char text[500];
+			COLORREF oldtext;
+			RECT focus;
+			GetWindowText(lpdis->hwndItem, text, 500);
+			if (wParam == IDC_URL || IDC_EMAIL) 
+			{
+				FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_3DFACE));
+				oldtext = SetTextColor(lpdis->hDC, RGB(0,0,255));
+				DrawText(lpdis->hDC, text, strlen(text), &lpdis->rcItem, DT_CENTER|DT_VCENTER);
+				SetTextColor(lpdis->hDC, oldtext);
+				if (lpdis->itemState & ODS_FOCUS) 
+				{
+					CopyRect(&focus, &lpdis->rcItem);
+					focus.left += 2;
+					focus.right -= 2;
+					focus.top += 1;
+					focus.bottom -= 1;
+					DrawFocusRect(lpdis->hDC, &focus);
+				}
+				return TRUE;
+			}
+		}	
+		case WM_COMMAND:
+			if (LOWORD(wParam) == IDOK)
+				EndDialog(hDlg, TRUE);
+			if (HIWORD(wParam) == BN_DBLCLK) 
+			{
+				if (LOWORD(wParam) == IDC_URL) 
+					ShellExecute(NULL, "open", "https://www.unrealircd.org", NULL, NULL, 
+						SW_MAXIMIZE);
+				else if (LOWORD(wParam) == IDC_EMAIL)
+					ShellExecute(NULL, "open", "mailto:unreal-users@lists.sourceforge.net", NULL, NULL, 
+						SW_MAXIMIZE);
+				EndDialog(hDlg, TRUE);
+				return 0;
+			}
+			break;
+		case WM_CLOSE:
+			EndDialog(hDlg, TRUE);
+			break;
+		case WM_DESTROY:
+			DeleteObject(hFont);
+			break;
+
+	}
+	return FALSE;
+}
+
+
+
+
+
+
+
+
+LRESULT CALLBACK StatusDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	switch (message) 
+	{
+		case WM_INITDIALOG: 
+		{
+			hwTreeView = GetDlgItem(hDlg, IDC_TREE);
+			win_map(&me, hwTreeView, 0);
+			SetDlgItemInt(hDlg, IDC_CLIENTS, irccounts.clients, FALSE);
+			SetDlgItemInt(hDlg, IDC_SERVERS, irccounts.servers, FALSE);
+			SetDlgItemInt(hDlg, IDC_INVISO, irccounts.invisible, FALSE);
+			SetDlgItemInt(hDlg, IDC_UNKNOWN, irccounts.unknown, FALSE);
+			SetDlgItemInt(hDlg, IDC_OPERS, irccounts.operators, FALSE);
+			SetDlgItemInt(hDlg, IDC_CHANNELS, irccounts.channels, FALSE);
+			if (irccounts.clients > irccounts.global_max)
+				irccounts.global_max = irccounts.clients;
+			if (irccounts.me_clients > irccounts.me_max)
+					irccounts.me_max = irccounts.me_clients;
+			SetDlgItemInt(hDlg, IDC_MAXCLIENTS, irccounts.global_max, FALSE);
+			SetDlgItemInt(hDlg, IDC_LCLIENTS, irccounts.me_clients, FALSE);
+			SetDlgItemInt(hDlg, IDC_LSERVERS, irccounts.me_servers, FALSE);
+			SetDlgItemInt(hDlg, IDC_LMAXCLIENTS, irccounts.me_max, FALSE);
+			SetTimer(hDlg, 1, 5000, NULL);
+			return TRUE;
+		}
+		case WM_CLOSE:
+			DestroyWindow(hDlg);
+			return TRUE;
+		case WM_TIMER:
+			TreeView_DeleteAllItems(hwTreeView);
+			win_map(&me, hwTreeView, 1);
+			SetDlgItemInt(hDlg, IDC_CLIENTS, irccounts.clients, FALSE);
+			SetDlgItemInt(hDlg, IDC_SERVERS, irccounts.servers, FALSE);
+			SetDlgItemInt(hDlg, IDC_INVISO, irccounts.invisible, FALSE);
+			SetDlgItemInt(hDlg, IDC_INVISO, irccounts.invisible, FALSE);
+			SetDlgItemInt(hDlg, IDC_UNKNOWN, irccounts.unknown, FALSE);
+			SetDlgItemInt(hDlg, IDC_OPERS, irccounts.operators, FALSE);
+			SetDlgItemInt(hDlg, IDC_CHANNELS, irccounts.channels, FALSE);
+			if (irccounts.clients > irccounts.global_max)
+				irccounts.global_max = irccounts.clients;
+			if (irccounts.me_clients > irccounts.me_max)
+					irccounts.me_max = irccounts.me_clients;
+			SetDlgItemInt(hDlg, IDC_MAXCLIENTS, irccounts.global_max, FALSE);
+			SetDlgItemInt(hDlg, IDC_LCLIENTS, irccounts.me_clients, FALSE);
+			SetDlgItemInt(hDlg, IDC_LSERVERS, irccounts.me_servers, FALSE);
+			SetDlgItemInt(hDlg, IDC_LMAXCLIENTS, irccounts.me_max, FALSE);
+			SetTimer(hDlg, 1, 5000, NULL);
+			return TRUE;
+		case WM_COMMAND:
+			if (LOWORD(wParam) == IDOK) 
+			{
+				DestroyWindow(hDlg);
+				return TRUE;
+			}
+			break;
+
+	}
+	return FALSE;
+}
+
+/* This was made by DrBin but I cleaned it up a bunch to make it work better */
+
+HTREEITEM AddItemToTree(HWND hWnd, LPSTR lpszItem, int nLevel, short remap)
+{
+	TVITEM tvi; 
+	TVINSERTSTRUCT tvins; 
+	static HTREEITEM hPrev = (HTREEITEM)TVI_FIRST; 
+	static HTREEITEM hPrevLev[10] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+	HTREEITEM hti; 
+
+	if (remap) 
+	{
+		hPrev = (HTREEITEM)TVI_FIRST;
+		memset(hPrevLev, 0, sizeof(HTREEITEM)*10);
+	}
+		
+	tvi.mask = TVIF_TEXT|TVIF_PARAM; 
+	tvi.pszText = lpszItem; 
+	tvi.cchTextMax = lstrlen(lpszItem); 
+	tvi.lParam = (LPARAM)nLevel; 
+	tvins.item = tvi; 
+	tvins.hInsertAfter = hPrev; 
+	if (nLevel == 1) 
+		tvins.hParent = TVI_ROOT; 
+	else 
+		tvins.hParent = hPrevLev[nLevel-1];
+	hPrev = (HTREEITEM)SendMessage(hWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT) &tvins); 
+	hPrevLev[nLevel] = hPrev;
+	TreeView_EnsureVisible(hWnd,hPrev);
+	if (nLevel > 1) 
+	{ 
+		hti = TreeView_GetParent(hWnd, hPrev); 
+		tvi.mask = TVIF_IMAGE|TVIF_SELECTEDIMAGE; 
+		tvi.hItem = hti; 
+		TreeView_SetItem(hWnd, &tvi); 
+	} 
+	return hPrev; 
+}
+
+/*
+ * Now used to create list of servers for server list tree view -- David Flynn
+ * Recoded by codemastr to be faster.
+ * I removed the Potvin credit because it no longer uses any original code and I don't
+ * even think Potvin actually made the original code
+ */
+void win_map(Client *server, HWND hwTreeView, short remap)
+{
+/*
+	Client *acptr;
+	Link *lp;
+
+	AddItemToTree(hwTreeView,server->name,server->hopcount+1, remap);
+
+	for (lp = Servers; lp; lp = lp->next)
+        {
+                acptr = lp->value.client;
+                if (acptr->uplink != server)
+                        continue;
+                win_map(acptr, hwTreeView, 0);
+        }
+FIXME
+*/
+}
+
+/* ugly stuff, but hey it works -- codemastr */
+void win_log(FORMAT_STRING(const char *format), ...)
+{
+	va_list ap;
+	unsigned char buf[2048];
+	FILE *fd;
+
+	va_start(ap, format);
+
+	ircvsnprintf(buf, sizeof(buf), format, ap);
+	stripcrlf(buf);
+
+	if (!IsService) 
+	{
+		strcat(buf, "\r\n");
+		if (errors) 
+		{
+			char *tbuf = safe_alloc(strlen(errors) + strlen(buf) + 1);
+			strcpy(tbuf, errors);
+			strcat(tbuf, buf);
+			safe_free(errors);
+			errors = tbuf;
+		}
+		else 
+		{
+			safe_strdup(errors, buf);
+		}
+	}
+	else 
+	{
+		FILE *fd = fopen("logs\\service.log", "a");
+		if (fd)
+		{
+			char timebuf[256];
+			snprintf(timebuf, sizeof(timebuf), "[%s]", myctime(time(NULL)));
+			fprintf(fd, "%s - %s\n", timebuf, buf);
+			fclose(fd);
+		}
+#ifdef _DEBUG
+		else
+		{
+			OutputDebugString(buf);
+		}
+#endif
+	}
+	va_end(ap);
+}
+
+void win_error() 
+{
+	if (errors && !IsService)
+		DialogBox(hInst, "ConfigError", hwIRCDWnd, (DLGPROC)ConfigErrorDLG);
+}
+
+LRESULT CALLBACK ConfigErrorDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
+{
+	switch (message) 
+	{
+		case WM_INITDIALOG:
+			MessageBeep(MB_ICONEXCLAMATION);
+			SetDlgItemText(hDlg, IDC_CONFIGERROR, errors);
+			safe_free(errors);
+			errors = NULL;
+			return (TRUE);
+		case WM_COMMAND:
+			if (LOWORD(wParam) == IDOK)
+				EndDialog(hDlg, TRUE);
+			break;
+		case WM_CLOSE:
+			EndDialog(hDlg, TRUE);
+			break;
+		case WM_DESTROY:
+			break;
+
+		}
+	return (FALSE);
+}
diff --git a/ircd/src/windows/hand.CUR b/ircd/src/windows/hand.CUR
Binary files differ.
diff --git a/ircd/src/windows/makecert.bat b/ircd/src/windows/makecert.bat
@@ -0,0 +1,6 @@
+@title Certificate Generation
+SET OPENSSL_CONF=tls.cnf
+openssl ecparam -out ../conf/tls/server.key.pem -name secp384r1 -genkey
+openssl req -new -config tls.cnf -out ../conf/tls/server.req.pem -key ../conf/tls/server.key.pem -nodes
+openssl req -x509 -config tls.cnf -days 3650 -sha256 -in ../conf/tls/server.req.pem -key ../conf/tls/server.key.pem -out ../conf/tls/server.cert.pem
+
diff --git a/ircd/src/windows/resource.h b/ircd/src/windows/resource.h
@@ -0,0 +1,108 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by Win32GUI.rc
+//
+#define VER_UNREAL                      1
+#define MANIFEST_RESOURCE_ID            2
+#define ICO_MAIN                        129
+#define BMP_LOGO                        130
+#define BMP_BAR                         133
+#define CUR_HAND                        136
+#define MENU_ABOUT                      137
+#define MENU_CONFIG                     141
+#define MENU_REHASH                     144
+#define MENU_SYSTRAY                    145
+#define MENU_CONTEXT                    146
+#define IDB_BITMAP1                     150
+#define IDC_BAR                         1103
+#define IDC_TOOLBAR                     1104
+#define IDC_STATUS                      1105
+#define IDC_TEXT                        1107
+#define IDC_EMAIL                       1108
+#define IDC_URL                         1109
+#define IDC_TREE                        1111
+#define IDC_CHANNELS                    1112
+#define IDC_CLIENTS                     1113
+#define IDC_SERVERS                     1114
+#define IDC_INVISO                      1115
+#define IDC_OPERS                       1116
+#define IDC_UNKNOWN                     1117
+#define IDC_MAXCLIENTS                  1118
+#define IDC_LCLIENTS                    1122
+#define IDC_LSERVERS                    1123
+#define IDC_LMAXCLIENTS                 1124
+#define IDC_UPTIME                      1125
+#define IDC_CONFIGERROR                 1126
+#define IDC_BOLD                        1130
+#define IDC_UNDERLINE                   1131
+#define IDC_FIND                        1132
+#define IDFIND                          1133
+#define IDC_FINDTEXT                    1135
+#define IDC_GOTO                        1135
+#define IDC_MATCHWHOLE                  1137
+#define IDC_MATCHCASE                   1138
+#define IDC_DIRUP                       1139
+#define IDC_DIRDOWN                     1140
+#define IDC_COLOR                       1141
+#define IDC_BGCOLOR			1142
+#define IDC_WHITE                       1163
+#define IDC_BLACK                       1164
+#define IDC_DARKBLUE                    1165
+#define IDC_DARKGREEN                   1166
+#define IDC_PASS                        1166
+#define IDC_RED                         1167
+#define IDC_DARKRED                     1168
+#define IDC_PURPLE                      1169
+#define IDC_ORANGE                      1170
+#define IDC_YELLOW                      1171
+#define IDC_GREEN                       1172
+#define IDC_VDARKGREEN                  1173
+#define IDC_LIGHTBLUE                   1174
+#define IDC_BLUE                        1175
+#define IDC_PINK                        1176
+#define IDC_DARKGRAY                    1177
+#define IDC_GRAY                        1178
+#define IDM_INFO                        40026
+#define IDM_CREDITS                     40027
+#define IDM_DAL                         40028
+#define IDM_LICENSE                     40029
+#define IDM_HELP                        40030
+#define IDM_SAVE                        40031
+#define IDM_REDO                        40032
+#define IDM_SMOTD			40036
+#define IDM_CONF                        40037
+#define IDM_MOTD                        40038
+#define IDM_BOTMOTD                     40039
+#define IDM_OPERMOTD                    40040
+#define IDM_RULES                       40041
+#define IDM_RHALL                       40042
+#define IDM_RHCONF                      40044
+#define IDM_RHMOTD                      40045
+#define IDM_RHOMOTD                     40046
+#define IDM_RHBMOTD                     40047
+#define IDM_RHRULES                     40048
+#define IDM_REHASH                      40049
+#define IDM_STATUS                      40050
+#define IDM_CONFIG                      40051
+#define IDM_ABOUT                       40052
+#define IDM_SHUTDOWN                    40053
+#define IDM_UNDO                        40054
+#define IDM_CUT                         40055
+#define IDM_COPY                        40056
+#define IDM_PASTE                       40057
+#define IDM_DELETE                      40058
+#define IDM_SELECTALL                   40059
+#define IDM_NEW                         40060
+#define IDC_STATIC                      -1
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NO_MFC                     1
+#define _APS_NEXT_RESOURCE_VALUE        152
+#define _APS_NEXT_COMMAND_VALUE         40061
+#define _APS_NEXT_CONTROL_VALUE         1167
+#define _APS_NEXT_SYMED_VALUE           104
+#endif
+#endif
diff --git a/ircd/src/windows/rtf.c b/ircd/src/windows/rtf.c
@@ -0,0 +1,878 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/rtf.c
+ *   Copyright (C) 2004 Dominick Meglio (codemastr)
+ *   
+ *   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 "win.h"
+
+unsigned char *RTFBuf;
+
+#define MIRC_COLORS "{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue127;\\red0\\green147\\blue0;\\red255\\green0\\blue0;\\red127\\green0\\blue0;\\red156\\green0\\blue156;\\red252\\green127\\blue0;\\red255\\green255\\blue0;\\red0\\green252\\blue0;\\red0\\green147\\blue147;\\red0\\green255\\blue255;\\red0\\green0\\blue252;\\red255\\green0\\blue255;\\red127\\green127\\blue127;\\red210\\green210\\blue210;\\red0\\green0\\blue0;}"
+
+/* Splits the file up for the EM_STREAMIN message
+ * Parameters:
+ *  dwCookie - The file information to split
+ *  pbBuff   - The output buffer
+ *  cb       - The size of pbBuff
+ *  pcb      - The total bytes written to bpBuff
+ * Returns:
+ *  Returns 0 to indicate success
+ */
+DWORD CALLBACK SplitIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
+{
+	StreamIO *stream = (StreamIO*)dwCookie;
+	if (*stream->size == 0)
+	{
+		pcb = 0;
+		*stream->buffer = 0;
+	}
+	else if (cb <= *stream->size) 
+	{
+		memcpy(pbBuff, *stream->buffer, cb);
+		*stream->buffer += cb;
+		*stream->size -= cb;
+		*pcb = cb;
+
+	}
+	else 
+	{
+		memcpy(pbBuff, *stream->buffer, *stream->size);
+		*pcb = *stream->size;
+		*stream->size = 0;
+	}
+	return 0;
+}
+
+/* Reassembles the RTF buffer from EM_STREAMOUT
+ * Parameters:
+ *  dwCookie - Unused
+ *  pbBuff   - The input buffer
+ *  cb       - The length of the input buffer
+ *  pcb      - The total bytes read from pbBuff
+ * Returns:
+ *  0 to indicate success
+ * Side Effects:
+ *  RTFBuf contains the assembled RTF buffer
+ */
+DWORD CALLBACK BufferIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
+{
+	unsigned char *buf2;
+	static long size = 0;
+	if (!RTFBuf)
+		size = 0;
+
+	buf2 = safe_alloc(size+cb+1);
+
+	if (RTFBuf)
+		memcpy(buf2,RTFBuf,size);
+
+	memcpy(buf2+size,pbBuff,cb);
+
+	size += cb;
+	safe_free(RTFBuf);
+
+	RTFBuf = buf2;
+
+	pcb = &cb;
+	return 0;
+}
+
+/* Pushes a color onto the stack
+ * Parameters:
+ *  color - The color to add to the stack
+ *  stack - The stack to add the color to
+ */
+void ColorPush(unsigned char *color, IRCColor **stack)
+{
+	IRCColor *t = safe_alloc(sizeof(IRCColor));
+	safe_strdup(t->color, color);
+	t->next = *stack;
+	(*stack) = t;
+}
+
+/* Pops a color off of the stack
+ * Parameters:
+ *  stack - The stack to pop from
+ */
+void ColorPop(IRCColor **stack)
+{
+	IRCColor *p = *stack;
+	if (!(*stack))
+		return;
+	safe_free(p->color);
+	
+	*stack = p->next;
+	safe_free(p);
+}
+
+/* Completely empties the color stack
+ * Parameters:
+ *  stack - The stack to empty
+ */
+void ColorEmpty(IRCColor **stack)
+{
+	IRCColor *t, *next;
+	for (t = *stack; t; t = next)
+	{
+		next = t->next;
+		safe_free(t->color);
+		safe_free(t);
+	}
+}
+
+#define iseol(x) ((x) == '\r' || (x) == '\n')
+
+/* Converts a string in RTF format to IRC codes
+ * Parameters:
+ *  fd     - The file descriptor to write to
+ *  pbBuff - The buffer containing the RTF text
+ *  cb     - The length of the RTF text
+ */
+DWORD CALLBACK RTFToIRC(int fd, unsigned char *pbBuff, long cb) 
+{
+	unsigned char *buffer = safe_alloc(cb*2+2);
+	int colors[17], bold = 0, uline = 0, incolor = 0, inbg = 0;
+	int lastwascf = 0, lastwascf0 = 0;
+	int i = 0;
+
+	IRCColor *TextColors = NULL;
+	IRCColor *BgColors = NULL;
+	
+	memset(buffer, 0, cb);
+
+	for (; *pbBuff; pbBuff++)
+	{
+		if (iseol(*pbBuff) || *pbBuff == '{' || *pbBuff == '}')
+			continue;
+		else if (*pbBuff == '\\')
+		{
+			/* RTF control sequence */
+			pbBuff++;
+			if (*pbBuff == '\\' || *pbBuff == '{' || *pbBuff == '}')
+				buffer[i++] = *pbBuff;
+			else if (*pbBuff == '\'')
+			{
+				/* Extended ASCII character */
+				unsigned char ltr, ultr[3];
+				ultr[0] = *(++pbBuff);
+				ultr[1] = *(++pbBuff);
+				ultr[2] = 0;
+				ltr = strtoul(ultr,NULL,16);
+				buffer[i++] = ltr;
+			}
+			else
+			{
+				int j;
+				char cmd[128];
+				/* Capture the control sequence */
+				for (j = 0; *pbBuff && *pbBuff != '\\' && !isspace(*pbBuff) &&
+					!iseol(*pbBuff); pbBuff++)
+				{
+					cmd[j++] = *pbBuff;
+				}
+				if (*pbBuff != ' ')
+					pbBuff--;
+				cmd[j] = 0;
+				if (!strcmp(cmd, "fonttbl{"))
+				{
+					/* Eat the parameter */
+					while (*pbBuff && *pbBuff != '}')
+						pbBuff++;
+					lastwascf = lastwascf0 = 0;
+				}
+				if (!strcmp(cmd, "colortbl"))
+				{
+					char color[128];
+					int k = 0, m = 1;
+					/* Capture the color table */
+					while (*pbBuff && !isalnum(*pbBuff))
+						pbBuff++;
+					for (; *pbBuff && *pbBuff != '}'; pbBuff++)
+					{
+						if (*pbBuff == ';')
+						{
+							color[k]=0;
+							if (!strcmp(color, "\\red255\\green255\\blue255"))
+								colors[m++] = 0;
+							else if (!strcmp(color, "\\red0\\green0\\blue0"))
+								colors[m++] = 1;
+							else if (!strcmp(color, "\\red0\\green0\\blue127"))
+								colors[m++] = 2;
+							else if (!strcmp(color, "\\red0\\green147\\blue0"))
+								colors[m++] = 3;
+							else if (!strcmp(color, "\\red255\\green0\\blue0"))
+								colors[m++] = 4;
+							else if (!strcmp(color, "\\red127\\green0\\blue0"))
+								colors[m++] = 5;
+							else if (!strcmp(color, "\\red156\\green0\\blue156"))
+								colors[m++] = 6;
+							else if (!strcmp(color, "\\red252\\green127\\blue0"))
+								colors[m++] = 7;
+							else if (!strcmp(color, "\\red255\\green255\\blue0"))
+								colors[m++] = 8;
+							else if (!strcmp(color, "\\red0\\green252\\blue0"))
+								colors[m++] = 9;
+							else if (!strcmp(color, "\\red0\\green147\\blue147"))
+								colors[m++] = 10;
+							else if (!strcmp(color, "\\red0\\green255\\blue255"))
+								colors[m++] = 11;
+							else if (!strcmp(color, "\\red0\\green0\\blue252"))
+								colors[m++] = 12;
+							else if (!strcmp(color, "\\red255\\green0\\blue255"))
+								colors[m++] = 13;
+							else if (!strcmp(color, "\\red127\\green127\\blue127"))
+								colors[m++] = 14;
+							else if (!strcmp(color, "\\red210\\green210\\blue210")) 
+								colors[m++] = 15;
+							k=0;
+						}
+						else
+							color[k++] = *pbBuff;
+					}
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strcmp(cmd, "tab"))
+				{
+					buffer[i++] = '\t';
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strcmp(cmd, "par"))
+				{
+					if (bold || uline || incolor || inbg)
+						buffer[i++] = '\17';
+					buffer[i++] = '\r';
+					buffer[i++] = '\n';
+					if (!*(pbBuff+3) || *(pbBuff+3) != '}')
+					{
+						if (bold)
+							buffer[i++] = '\2';
+						if (uline)
+							buffer[i++] = '\37';
+						if (incolor)
+						{
+							buffer[i++] = '\3';
+							strcat(buffer, TextColors->color);
+							i += strlen(TextColors->color);
+							if (inbg)
+							{
+								buffer[i++] = ',';
+								strcat(buffer, BgColors->color);
+								i += strlen(BgColors->color);
+							}
+						}
+						else if (inbg) 
+						{
+							buffer[i++] = '\3';
+							buffer[i++] = '0';
+							buffer[i++] = '1';
+							buffer[i++] = ',';
+							strcat(buffer, BgColors->color);
+							i += strlen(BgColors->color);
+						}
+					}
+				}
+				else if (!strcmp(cmd, "b"))
+				{
+					bold = 1;
+					buffer[i++] = '\2';
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strcmp(cmd, "b0"))
+				{
+					bold = 0;
+					buffer[i++] = '\2';
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strcmp(cmd, "ul"))
+				{
+					uline = 1;
+					buffer[i++] = '\37';
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strcmp(cmd, "ulnone"))
+				{
+					uline = 0;
+					buffer[i++] = '\37';
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strcmp(cmd, "cf0"))
+				{
+					lastwascf0 = 1;
+					lastwascf = 0;
+				}
+				else if (!strcmp(cmd, "highlight0"))
+				{
+					inbg = 0;
+					ColorPop(&BgColors);
+					buffer[i++] = '\3';
+					if (lastwascf0)
+					{
+						incolor = 0;
+						ColorPop(&TextColors);
+						lastwascf0 = 0;
+					}
+					else if (incolor)
+					{
+						strcat(buffer, TextColors->color);
+						i += strlen(TextColors->color);
+						buffer[i++] = ',';
+						buffer[i++] = '0';
+						buffer[i++] = '0';
+					}
+					lastwascf = lastwascf0 = 0;
+				}
+				else if (!strncmp(cmd, "cf", 2))
+				{
+					unsigned char number[3];
+					int num;
+					incolor = 1;
+					strcpy(number, &cmd[2]);
+					num = atoi(number);
+					buffer[i++] = '\3';
+					if (colors[num] < 10)
+						sprintf(number, "0%d", colors[num]);
+					else
+						sprintf(number, "%d", colors[num]);
+					ColorPush(number, &TextColors);
+					strcat(buffer,number);
+					i += strlen(number);
+					lastwascf = 1;
+					lastwascf0 = 0;
+				}
+				else if (!strncmp(cmd, "highlight", 9))
+				{
+					int num;
+					unsigned char number[3];
+					inbg = 1;
+					num = atoi(&cmd[9]);
+					if (colors[num] < 10)
+						sprintf(number, "0%d", colors[num]);
+					else
+						sprintf(number, "%d", colors[num]);
+					if (incolor && !lastwascf)
+					{
+						buffer[i++] = '\3';
+						strcat(buffer, TextColors->color);
+						i += strlen(TextColors->color);
+					}
+					else if (!incolor)
+					{
+						buffer[i++] = '\3';
+						buffer[i++] = '0';
+						buffer[i++] = '1';
+					}
+					buffer[i++] = ',';
+					strcat(buffer, number);
+					i += strlen(number);
+					ColorPush(number, &BgColors);
+					lastwascf = lastwascf0 = 0;
+				}
+				else
+					lastwascf = lastwascf0 = 0;
+
+				if (lastwascf0 && incolor)
+				{
+					incolor = 0;
+					ColorPop(&TextColors);
+					buffer[i++] = '\3';
+				}
+			}
+		}
+		else
+		{
+			lastwascf = lastwascf0 = 0;
+			buffer[i++] = *pbBuff;
+		}
+				
+	}
+	write(fd, buffer, i);
+	close(fd);
+	ColorEmpty(&TextColors);
+	ColorEmpty(&BgColors);
+	return 0;
+}
+
+/* Determines the size of the buffer needed to convert IRC codes to RTF
+ * Parameters:
+ *  buffer - The input buffer with IRC codes
+ * Returns:
+ *  The lenght of the buffer needed to store the RTF translation
+ */
+int CountRTFSize(unsigned char *buffer) {
+	int size = 0;
+	char bold = 0, uline = 0, incolor = 0, inbg = 0, reverse = 0;
+	char *buf = buffer;
+
+	for (; *buf; buf++) 
+	{
+		if (*buf == '{' || *buf == '}' || *buf == '\\')
+			size++;
+		else if (*buf == '\r')
+		{
+			if (*(buf+1) && *(buf+1) == '\n')
+			{
+				buf++;
+				if (bold)
+					size += 3;
+				if (uline)
+					size += 7;
+				if (incolor && !reverse)
+					size += 4;
+				if (inbg && !reverse)
+					size += 11;
+				if (reverse)
+					size += 15;
+				if (bold || uline || incolor || inbg || reverse)
+					size++;
+				bold = uline = incolor = inbg = reverse = 0;
+				size +=6;
+				continue;
+			}
+		}
+		else if (*buf == '\n')
+		{
+			if (bold)
+				size += 3;
+			if (uline)
+				size += 7;
+			if (incolor && !reverse)
+				size += 4;
+			if (inbg && !reverse)
+				size += 11;
+			if (reverse)
+				size += 15;
+			if (bold || uline || incolor || inbg || reverse)
+				size++;
+			bold = uline = incolor = inbg = reverse = 0;
+			size +=6;
+			continue;	
+		}
+		else if (*buf == '\2')
+		{
+			if (bold)
+				size += 4;
+			else
+				size += 3;
+			bold = !bold;
+			continue;
+		}
+		else if (*buf == '\3' && reverse)
+		{
+			if (*(buf+1) && isdigit(*(buf+1)))
+			{
+				++buf;
+				if (*(buf+1) && isdigit(*(buf+1)))
+					++buf;
+				if (*(buf+1) && *(buf+1) == ',')
+				{
+					if (*(buf+2) && isdigit(*(buf+2)))
+					{
+						buf+=2;
+						if (*(buf+1) && isdigit(*(buf+1)))
+							++buf;
+					}
+				}
+			}
+			continue;
+		}
+		else if (*buf == '\3' && !reverse)
+		{
+			size += 3;
+			if (*(buf+1) && !isdigit(*(buf+1)))
+			{
+				incolor = 0;
+				size++;
+				if (inbg)
+				{
+					inbg = 0;
+					size += 11;
+				}
+			}
+			else if (*(buf+1))
+			{
+				unsigned char color[3];
+				int number;
+				color[0] = *(++buf);
+				color[1] = 0;
+				if (*(buf+1) && isdigit(*(buf+1)))
+					color[1] = *(++buf);
+				color[2] = 0;
+				number = atoi(color);
+				if (number == 99 || number == 1) 
+					size += 2;
+				else if (number == 0) 
+					size++;
+				else  {
+					number %= 16;
+					_itoa(number, color, 10);
+					size += strlen(color);
+				}
+				color[2] = 0;
+				number = atoi(color);
+				if (*(buf+1) && *(buf+1) == ',')
+				{
+					if (*(buf+2) && isdigit(*(buf+2)))
+					{
+						size += 10;
+						buf++;
+						color[0] = *(++buf);
+						color[1] = 0;
+						if (*(buf+1) && isdigit(*(buf+1)))
+							color[1] = *(++buf);
+						color[2] = 0;
+						number = atoi(color);
+						if (number == 1)
+							size += 2;
+						else if (number == 0 || number == 99)
+							size++;
+						else
+						{
+							number %= 16;
+							_itoa(number, color, 10);
+							size += strlen(color);
+						}
+						inbg = 1;
+					}
+				}
+				incolor = 1;
+			}
+			size++;
+			continue;
+		}
+		else if (*buf == '\17')
+		{
+			if (bold)
+				size += 3;
+			if (uline)
+				size += 7;
+			if (incolor && !reverse)
+				size += 4;
+			if (inbg && !reverse)
+				size += 11;
+			if (reverse)
+				size += 15;
+			if (bold || uline || incolor || inbg || reverse)
+				size++;
+			bold = uline = incolor = inbg = reverse = 0;
+			continue;
+		}
+		else if (*buf == '\26')
+		{
+			if (reverse)
+				size += 16;
+			else
+				size += 17;
+			reverse = !reverse;
+			continue;
+		}
+		else if (*buf == '\37')
+		{
+			if (uline)
+				size += 8;
+			else
+				size += 4;
+			uline = !uline;
+			continue;
+		}
+		size++;
+	}			
+	size += strlen("{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl{\\f0\\fmodern\\fprq1\\"
+		"fcharset0 Fixedsys;}}\r\n"
+		MIRC_COLORS
+		"\\viewkind4\\uc1\\pard\\lang1033\\f0\\fs20")+1;
+	return (size);
+}
+
+/* Converts a string containing IRC codes to RTF
+ * Parameters:
+ *  buffer - The input buffer containing IRC codes
+ *  string - The output buffer in RTF
+ */
+void IRCToRTF(unsigned char *buffer, unsigned char *string) 
+{
+	unsigned char *tmp;
+	int i = 0;
+	short bold = 0, uline = 0, incolor = 0, inbg = 0, reverse = 0;
+	sprintf(string, "{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl{\\f0\\fmodern\\fprq1\\"
+		"fcharset0 Fixedsys;}}\r\n"
+		MIRC_COLORS
+		"\\viewkind4\\uc1\\pard\\lang1033\\f0\\fs20");
+	i = strlen(string);
+	for (tmp = buffer; *tmp; tmp++)
+	{
+		if (*tmp == '{')
+		{
+			strcat(string, "\\{");
+			i+=2;
+			continue;
+		}
+		else if (*tmp == '}')
+		{
+			strcat(string, "\\}");
+			i+=2;
+			continue;
+		}
+		else if (*tmp == '\\')
+		{
+			strcat(string, "\\\\");
+			i+=2;
+			continue;
+		}
+		else if (*tmp == '\r')
+		{
+			if (*(tmp+1) && *(tmp+1) == '\n')
+			{
+				tmp++;
+				if (bold)
+				{
+					strcat(string, "\\b0 ");
+					i+=3;
+				}
+				if (uline)
+				{
+					strcat(string, "\\ulnone");
+					i+=7;
+				}
+				if (incolor && !reverse)
+				{
+					strcat(string, "\\cf0");
+					i+=4;
+				}
+				if (inbg && !reverse)
+				{
+					strcat(string, "\\highlight0");
+					i +=11;
+				}
+				if (reverse) {
+					strcat(string, "\\cf0\\highlight0");
+					i += 15;
+				}
+				if (bold || uline || incolor || inbg || reverse)
+					string[i++] = ' ';
+				bold = uline = incolor = inbg = reverse = 0;
+				strcat(string, "\\par\r\n");
+				i +=6;
+			}
+			else
+				string[i++]='\r';
+			continue;
+		}
+		else if (*tmp == '\n')
+		{
+			if (bold)
+			{
+				strcat(string, "\\b0 ");
+				i+=3;
+			}
+			if (uline)
+			{
+				strcat(string, "\\ulnone");
+				i+=7;
+			}
+			if (incolor && !reverse)
+			{
+				strcat(string, "\\cf0");
+				i+=4;
+			}
+			if (inbg && !reverse)
+			{
+				strcat(string, "\\highlight0");
+				i +=11;
+			}
+			if (reverse) {
+				strcat(string, "\\cf0\\highlight0");
+				i += 15;
+			}
+			if (bold || uline || incolor || inbg || reverse)
+				string[i++] = ' ';
+			bold = uline = incolor = inbg = reverse = 0;
+			strcat(string, "\\par\r\n");
+			i +=6;
+			continue;
+		}
+		else if (*tmp == '\2')
+		{
+			if (bold)
+			{
+				strcat(string, "\\b0 ");
+				i+=4;
+			}
+			else
+			{
+				strcat(string, "\\b ");
+				i+=3;
+			}
+			bold = !bold;
+			continue;
+		}
+		else if (*tmp == '\3' && reverse)
+		{
+			if (*(tmp+1) && isdigit(*(tmp+1)))
+			{
+				++tmp;
+				if (*(tmp+1) && isdigit(*(tmp+1)))
+					++tmp;
+				if (*(tmp+1) && *(tmp+1) == ',')
+				{
+					if (*(tmp+2) && isdigit(*(tmp+2)))
+					{
+						tmp+=2;
+						if (*(tmp+1) && isdigit(*(tmp+1)))
+							++tmp;
+					}
+				}
+			}
+			continue;
+		}
+		else if (*tmp == '\3' && !reverse)
+		{
+			strcat(string, "\\cf");
+			i += 3;
+			if (*(tmp+1) && !isdigit(*(tmp+1)))
+			{
+				incolor = 0;
+				string[i++] = '0';
+				if (inbg)
+				{
+					inbg = 0;
+					strcat(string, "\\highlight0");
+					i += 11;
+				}
+			}
+			else if (*(tmp+1))
+			{
+				unsigned char color[3];
+				int number;
+				color[0] = *(++tmp);
+				color[1] = 0;
+				if (*(tmp+1) && isdigit(*(tmp+1)))
+					color[1] = *(++tmp);
+				color[2] = 0;
+				number = atoi(color);
+				if (number == 99 || number == 1)
+				{
+					strcat(string, "16"); 
+					i += 2;
+				}
+				else if (number == 0) 
+				{
+					strcat(string, "1");
+					i++;
+				}
+				else
+				{
+					number %= 16;
+					_itoa(number, color, 10);
+					strcat(string, color);
+					i += strlen(color);
+				}
+				if (*(tmp+1) && *(tmp+1) == ',')
+				{
+					if (*(tmp+2) && isdigit(*(tmp+2)))
+					{
+						strcat(string, "\\highlight");
+						i += 10;
+						tmp++;
+						color[0] = *(++tmp);
+						color[1] = 0;
+						if (*(tmp+1) && isdigit(*(tmp+1)))
+							color[1] = *(++tmp);
+						color[2] = 0;
+						number = atoi(color);
+						if (number == 1)
+						{
+							strcat(string, "16");
+							i += 2;
+						}
+						else if (number == 0 || number == 99)
+							string[i++] = '1';
+						else
+						{
+							number %= 16;
+							_itoa(number, color, 10);
+							strcat(string,color);
+							i += strlen(color);
+						}
+						inbg = 1;
+					}
+				}
+				incolor=1;
+			}
+			string[i++] = ' ';
+			continue;
+		}
+		else if (*tmp == '\17') {
+			if (uline) {
+				strcat(string, "\\ulnone");
+				i += 7;
+			}
+			if (bold) {
+				strcat(string, "\\b0");
+				i += 3;
+			}
+			if (incolor && !reverse) {
+				strcat(string, "\\cf0");
+				i += 4;
+			}
+			if (inbg && !reverse)
+			{
+				strcat(string, "\\highlight0");
+				i += 11;
+			}
+			if (reverse) {
+				strcat(string, "\\cf0\\highlight0");
+				i += 15;
+			}
+			if (uline || bold || incolor || inbg || reverse)
+				string[i++] = ' ';
+			uline = bold = incolor = inbg = reverse = 0;
+			continue;
+		}
+		else if (*tmp == '\26')
+		{
+			if (reverse)
+			{
+				strcat(string, "\\cf0\\highlight0 ");
+				i += 16;
+			}
+			else
+			{
+				strcat(string, "\\cf1\\highlight16 ");
+				i += 17;
+			}
+			reverse = !reverse;
+			continue;
+		}
+
+		else if (*tmp == '\37') {
+			if (uline) {
+				strcat(string, "\\ulnone ");
+				i += 8;
+			}
+			else {
+				strcat(string, "\\ul ");
+				i += 4;
+			}
+			uline = !uline;
+			continue;
+		}
+		string[i++] = *tmp;
+	}
+	strcat(string, "}");
+	return;
+}
diff --git a/ircd/src/windows/service.c b/ircd/src/windows/service.c
@@ -0,0 +1,140 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/service.c
+ *   Copyright (C) 2002-2004 Dominick Meglio (codemastr)
+ *   
+ *   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"
+
+SERVICE_STATUS IRCDStatus; 
+SERVICE_STATUS_HANDLE IRCDStatusHandle;
+
+/* Signal to rehash */
+#define IRCD_SERVICE_CONTROL_REHASH 128
+
+MODVAR BOOL IsService = FALSE;
+
+#define WIN32_VERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5
+
+/* Places the service in the STOPPED state
+ * Parameters:
+ *  code - The error code (or 0)
+ */
+void SetServiceStop(int code)
+{
+	IRCDStatus.dwCurrentState = SERVICE_STOPPED;
+	IRCDStatus.dwCheckPoint = 0;
+	IRCDStatus.dwWaitHint = 0;
+	IRCDStatus.dwWin32ExitCode = code;
+	IRCDStatus.dwServiceSpecificExitCode = code;
+	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
+}	
+
+/* Handles the service messages
+ * Parameters:
+ *  opcode - The message to process
+ */
+VOID WINAPI IRCDCtrlHandler(DWORD opcode) 
+{
+	DWORD status;
+	int i;
+	Client *acptr;
+
+	/* Stopping */
+	if (opcode == SERVICE_CONTROL_STOP) 
+	{
+		IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING;
+		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
+
+/*		for (i = 0; i <= LastSlot; i++) 
+		{
+			if (!(acptr = local[i]))
+				continue;
+			if (IsUser(acptr))
+				sendnotice(acptr, "Server Terminating.");
+			else if (IsServer(acptr))
+				sendto_one(acptr, NULL, ":%s ERROR :Terminated", me.name);
+		} */
+		unload_all_modules();
+/*		for (i = LastSlot; i >= 0; i--)
+			if ((acptr = local[i]) && DBufLength(&acptr->local->sendQ) > 0)
+				(void)send_queued(acptr); */
+		SetServiceStop(0);
+	}
+	/* Rehash */
+	else if (opcode == IRCD_SERVICE_CONTROL_REHASH) 
+	{
+		request_rehash(NULL);
+	}
+
+	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
+} 
+
+/* Entry point function
+ * Parameters:
+ *  dwArgc   - Argument count
+ *  lpszArgv - Arguments
+ */
+VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) 
+{
+	DWORD error = 0;
+	char path[MAX_PATH], *folder;
+
+	IsService = TRUE;
+
+	/* Initialize the service structure */
+	IRCDStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+	IRCDStatus.dwCurrentState = SERVICE_START_PENDING;
+	IRCDStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
+	IRCDStatus.dwWin32ExitCode = NO_ERROR;
+	IRCDStatus.dwServiceSpecificExitCode = 0;
+	IRCDStatus.dwCheckPoint = 0;
+	IRCDStatus.dwWaitHint = 0;
+ 
+	GetModuleFileName(NULL,path,MAX_PATH);
+	folder = strrchr(path, '\\');
+	*folder = 0;
+	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 6"
+	 */
+	chdir("..");
+
+	/* Register the service controller */
+	IRCDStatusHandle = RegisterServiceCtrlHandler("UnrealIRCd", IRCDCtrlHandler); 
+ 
+	GetOSName(OSName);
+
+	InitDebug();
+	init_winsock();
+
+	/* Initialize the IRCd */
+	if ((error = InitUnrealIRCd(dwArgc, lpszArgv)) != 1) 
+	{
+		SetServiceStop(error);
+		return;
+	}
+	
+	/* Go into the running state */
+	IRCDStatus.dwCurrentState = SERVICE_RUNNING;
+	IRCDStatus.dwCheckPoint = 0;
+	IRCDStatus.dwWaitHint = 0;  
+	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
+
+	SocketLoop(0);
+	return;
+}
diff --git a/ircd/src/windows/toolbar.bmp b/ircd/src/windows/toolbar.bmp
Binary files differ.
diff --git a/ircd/src/windows/unreal.bmp b/ircd/src/windows/unreal.bmp
Binary files differ.
diff --git a/ircd/src/windows/unrealinst.iss b/ircd/src/windows/unrealinst.iss
@@ -0,0 +1,204 @@
+; UnrealIRCd Windows Installation Script
+; Requires Inno Setup 4.1.6 or later
+
+; Uncomment the line below to package with libcurl support
+#define USE_CURL
+
+[Setup]
+AppName=UnrealIRCd 6
+AppVerName=UnrealIRCd 6.1.0
+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 6
+DefaultGroupName=UnrealIRCd 6
+AllowNoIcons=yes
+LicenseFile=src\windows\gplplusssl.rtf
+Compression=lzma
+SolidCompression=true
+MinVersion=6.1
+OutputDir=.
+SourceDir=../../
+UninstallDisplayIcon={app}\bin\UnrealIRCd.exe
+UninstallFilesDir={app}\bin\uninstaller
+DisableWelcomePage=no
+ArchitecturesInstallIn64BitMode=x64
+ArchitecturesAllowed=x64
+;These are set only on release:
+;SignedUninstaller=yes
+;SignTool=signtool
+
+; !!! 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
+Name: "installservice"; Description: "Install as a &service (not for beginners)"; GroupDescription: "Service support:"; Flags: unchecked; MinVersion: 0,4.0
+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: "TLS options:";
+Name: "fixperm"; Description: "Make UnrealIRCd folder writable by current user";
+
+[Files]
+; UnrealIRCd binaries
+Source: "UnrealIRCd.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "UnrealIRCd.pdb"; DestDir: "{app}\bin"; Flags: ignoreversion
+Source: "unrealircdctl.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
+Source: "unrealsvc.exe";  DestDir: "{app}\bin"; Flags: ignoreversion signonce
+
+; TLS certificate generation helpers
+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\rpc\*.dll"; DestDir: "{app}\modules\rpc"; 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
+Source: "doc\conf\badwords.conf"; DestDir: "{app}\conf"; Flags: onlyifdoesntexist
+Source: "doc\conf\dccallow.conf"; DestDir: "{app}\conf"; Flags: onlyifdoesntexist
+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
+
+[Dirs]
+Name: "{app}\tmp"
+Name: "{app}\bin"
+Name: "{app}\cache"
+Name: "{app}\logs"
+Name: "{app}\conf"
+Name: "{app}\conf\tls"
+Name: "{app}\data"
+Name: "{app}\modules\third"
+
+[Code]
+var
+	uninstaller: String;
+	ErrorCode: Integer;
+
+//*********************************************************************************
+// This is where all starts.
+//*********************************************************************************
+function InitializeSetup(): Boolean;
+var
+	major: Cardinal;
+begin
+	Result := true;
+
+	if Not RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Major', major) then
+	begin
+		MsgBox('UnrealIRCd requires the Microsoft Visual C++ Redistributable for Visual Studio 2019 to be installed.' #13 +
+		       'After you click OK you will be taken to a download page from Microsoft:' #13 +
+		       '1) Scroll down to the section "Visual Studio 2015, 2017 and 2019"' #13 +
+		       '2) Click on the x64 "vc_redist.x64.exe" to download the 64 bit installer' #13 +
+		       '3) Run the installer.' #13 + #13 +
+		       'If you are already absolutely sure that you have this package installed then you can skip this step.', mbInformation, MB_OK);
+		ShellExec('open', 'https://support.microsoft.com/help/2977003/the-latest-supported-visual-c-downloads', '', '', SW_SHOWNORMAL,ewNoWait,ErrorCode);
+		MsgBox('Your browser was launched. After you have installed the Microsoft Visual C++ Redistributable for Visual Studio 2019 (vc_redist.x64.exe), click OK below to continue the UnrealIRCd installer', mbInformation, MB_OK);
+	end;
+end;
+
+procedure CurStepChanged(CurStep: TSetupStep);
+
+var
+	hWnd: Integer;
+	ResultCode: Integer;
+	ResultXP: boolean;
+	Result2003: boolean;
+	Res: Integer;
+	s: String;
+	d: String;
+begin
+if CurStep = ssPostInstall then
+	begin
+		d := ExpandConstant('{app}');
+		if IsTaskSelected('fixperm') then
+		begin
+			// This fixes the permissions in the UnrealIRCd folder by granting full access to the user
+			// running the install.
+			s := '-on "'+d+'" -ot file -actn ace -ace "n:'+GetUserNameString()+';p:full;m:set"';
+			Exec(d+'\tmp\setacl.exe', s, d, SW_HIDE, ewWaitUntilTerminated, Res);
+		end
+		else
+		begin
+			MsgBox('You have chosen to not have the installer automatically set write access. Please ensure that the user running the IRCd can write to '+d+', otherwise the IRCd will fail to load.',mbConfirmation, MB_OK);
+		end;
+		if IsTaskSelected('installservice') then
+		begin
+			// Similar to above, but this adds full access to NetworkService,
+			// otherwise it cannot copy modules, cannot write to logs, etc etc.
+			s := '-on "'+d+'" -ot file -actn ace -ace "n:NetworkService;p:full;m:set"';
+			Exec(d+'\tmp\setacl.exe', s, d, SW_HIDE, ewWaitUntilTerminated, Res);
+		end;
+	end;
+end;
+
+//*********************************************************************************
+// Checks if TLS cert file exists
+//*********************************************************************************
+
+procedure CurPageChanged(CurPage: Integer);
+begin
+	if (CurPage = wpSelectTasks) then
+	begin
+		if FileExists(ExpandConstant('{app}\conf\tls\server.cert.pem')) then
+		begin
+			WizardForm.TasksList.Checked[9]:=false;
+		end
+		else
+		begin
+			WizardForm.TasksList.Checked[9]:=true;
+		end;
+	end;
+end;
+
+[Icons]
+Name: "{group}\UnrealIRCd"; Filename: "{app}\bin\UnrealIRCd.exe"; WorkingDir: "{app}\bin"
+Name: "{group}\Uninstall UnrealIRCd"; Filename: "{uninstallexe}"; WorkingDir: "{app}\bin"
+Name: "{group}\Make Certificate"; Filename: "{app}\bin\makecert.bat"; WorkingDir: "{app}\bin"
+Name: "{group}\Documentation"; Filename: "https://www.unrealircd.org/docs/"; WorkingDir: "{app}\bin"
+Name: "{userdesktop}\UnrealIRCd"; Filename: "{app}\bin\UnrealIRCd.exe"; WorkingDir: "{app}\bin"; Tasks: desktopicon
+Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\UnrealIRCd"; Filename: "{app}\bin\UnrealIRCd.exe"; WorkingDir: "{app}\bin"; Tasks: quicklaunchicon
+
+[Run]
+Filename: "https://www.unrealircd.org/docs/"; Description: "View documentation"; Parameters: ""; Flags: postinstall skipifsilent shellexec runmaximized
+Filename: "https://www.unrealircd.org/docs/Installing_%28Windows%29"; Description: "View installation instructions"; Parameters: ""; Flags: postinstall skipifsilent shellexec runmaximized
+Filename: "{app}\bin\unrealsvc.exe"; Parameters: "install"; Flags: runminimized nowait; Tasks: installservice
+Filename: "{app}\bin\unrealsvc.exe"; Parameters: "config startup manual"; Flags: runminimized nowait; Tasks: installservice/startdemand
+Filename: "{app}\bin\unrealsvc.exe"; Parameters: "config startup auto"; Flags: runminimized nowait; Tasks: installservice/startboot
+Filename: "{app}\bin\unrealsvc.exe"; Parameters: "config crashrestart 2"; Flags: runminimized nowait; Tasks: installservice/crashrestart
+Filename: "{app}\bin\makecert.bat"; Tasks: makecert; Flags: postinstall;
+
+[UninstallRun]
+Filename: "{app}\bin\unrealsvc.exe"; Parameters: "uninstall"; Flags: runminimized; RunOnceID: "DelService"; Tasks: installservice
diff --git a/ircd/src/windows/unrealircdctl.exe.manifest b/ircd/src/windows/unrealircdctl.exe.manifest
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+    processorArchitecture="amd64"
+    name="UnrealIRCd.UnrealIRCd.6"
+    version="6.0.1.0"
+    type="win32"
+/>
+<description>UnrealIRCd - Control utility</description>
+</assembly>
diff --git a/ircd/src/windows/unrealsvc.c b/ircd/src/windows/unrealsvc.c
@@ -0,0 +1,213 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/unrealsvc.c
+ *   Copyright (C) 2002 Dominick Meglio (codemastr)
+ *   Copyright (C) 2006-2021 Bram Matthys (Syzop)
+ *   
+ *   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"
+
+typedef BOOL (*UCHANGESERVICECONFIG2)(SC_HANDLE, DWORD, LPVOID);
+HMODULE hAdvapi;
+UCHANGESERVICECONFIG2 uChangeServiceConfig2;
+
+#define IRCD_SERVICE_CONTROL_REHASH 128
+void show_usage() {
+	fprintf(stderr, "unrealsvc start|stop|rehash|restart|install|uninstall|config <option> <value>");
+	fprintf(stderr, "\nValid config options:\nstartup auto|manual\n");
+	fprintf(stderr, "crashrestart delay\n");
+}
+
+char *show_error(DWORD code) {
+	static char buf[1024];
+	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, 0, buf, 1024, NULL);
+	return buf;
+}
+
+SC_HANDLE unreal_open_service_manager(void)
+{
+	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+	if (!hSCManager)
+	{
+		printf("Failed to connect to service manager: %s", show_error(GetLastError()));
+		printf("Note that elevated administrator permissions are necessary to execute this command.\n");
+		exit(1);
+	}
+	return hSCManager;
+}
+int main(int argc, char *argv[]) {
+	char *bslash;
+
+	if (argc < 2) {
+		show_usage();
+		return -1;
+	}
+	hAdvapi = LoadLibrary("advapi32.dll");
+	uChangeServiceConfig2 = (UCHANGESERVICECONFIG2)GetProcAddress(hAdvapi, "ChangeServiceConfig2A");
+
+	if (!strcasecmp(argv[1], "install"))
+	{
+		SC_HANDLE hService, hSCManager;
+		char path[MAX_PATH+1];
+		char binpath[MAX_PATH+1];
+		hSCManager = unreal_open_service_manager();
+
+		GetModuleFileName(NULL,path,MAX_PATH);
+		if ((bslash = strrchr(path, '\\')))
+			*bslash = 0;
+		
+		strcpy(binpath,path);
+		strcat(binpath, "\\UnrealIRCd.exe");
+		hService = CreateService(hSCManager, "UnrealIRCd", "UnrealIRCd",
+				 SERVICE_CHANGE_CONFIG, SERVICE_WIN32_OWN_PROCESS,
+				 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binpath,
+				 NULL, NULL, NULL, TEXT("NT AUTHORITY\\NetworkService"), "");
+		if (hService) 
+		{
+			SERVICE_DESCRIPTION info;
+			printf("UnrealIRCd NT Service successfully installed\n");
+			info.lpDescription = "Internet Relay Chat Server. Allows users to chat with eachother via an IRC client.";
+			uChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &info);
+			CloseServiceHandle(hService);
+			printf("\n[!!!] IMPORTANT: By default the network service user cannot write to the \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 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");
+		} else {
+			printf("Failed to install UnrealIRCd NT Service - %s", show_error(GetLastError()));
+		}
+		CloseServiceHandle(hSCManager);
+		return 0;
+	}
+	else if (!strcasecmp(argv[1], "uninstall"))
+	{
+		SC_HANDLE hSCManager = unreal_open_service_manager();
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", DELETE); 
+		if (DeleteService(hService)) 
+			printf("UnrealIRCd NT Service successfully uninstalled\n");
+		else
+			printf("Failed to uninstall UnrealIRCd NT Service - %s\n", show_error(GetLastError()));
+		CloseServiceHandle(hService);
+		CloseServiceHandle(hSCManager);
+		return 0;
+	}
+	else if (!strcasecmp(argv[1], "start"))
+	{
+		SC_HANDLE hSCManager = unreal_open_service_manager();
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_START); 
+		if (StartService(hService, 0, NULL))
+			printf("UnrealIRCd NT Service successfully started");
+		else
+			printf("Failed to start UnrealIRCd NT Service - %s", show_error(GetLastError()));
+		CloseServiceHandle(hService);
+		CloseServiceHandle(hSCManager);
+		return 0;
+	}
+	else if (!strcasecmp(argv[1], "stop"))
+	{
+		SERVICE_STATUS status;
+		SC_HANDLE hSCManager = unreal_open_service_manager();
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP); 
+		ControlService(hService, SERVICE_CONTROL_STOP, &status);
+		printf("UnrealIRCd NT Service successfully stopped");
+		CloseServiceHandle(hService);
+		CloseServiceHandle(hSCManager);
+		return 0;
+	}
+	else if (!strcasecmp(argv[1], "restart"))
+	{
+		SERVICE_STATUS status;
+		SC_HANDLE hSCManager = unreal_open_service_manager();
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP|SERVICE_START); 
+		ControlService(hService, SERVICE_CONTROL_STOP, &status);
+		if (StartService(hService, 0, NULL)) 
+			printf("UnrealIRCd NT Service successfully restarted");
+		CloseServiceHandle(hService);
+		CloseServiceHandle(hSCManager);
+		return 0;
+	}
+	else if (!strcasecmp(argv[1], "rehash"))
+	{
+		SERVICE_STATUS status;
+		SC_HANDLE hSCManager = unreal_open_service_manager();
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_USER_DEFINED_CONTROL); 
+		ControlService(hService, IRCD_SERVICE_CONTROL_REHASH, &status);
+		printf("UnrealIRCd NT Service successfully rehashed");
+	}
+	else if (!strcasecmp(argv[1], "config"))
+	{
+		SERVICE_STATUS status;
+		SC_HANDLE hSCManager = unreal_open_service_manager();
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_CHANGE_CONFIG|SERVICE_START);
+		if (argc < 3) {
+			show_usage();
+			return -1;
+		}
+		if (!strcasecmp(argv[2], "startup")) {
+			if (ChangeServiceConfig(hService, SERVICE_NO_CHANGE,
+					    !strcasecmp(argv[3], "auto") ? SERVICE_AUTO_START
+						: SERVICE_DEMAND_START, SERVICE_NO_CHANGE,
+					    NULL, NULL, NULL, NULL, NULL, NULL, NULL)) 
+				printf("UnrealIRCd NT Service configuration changed");
+			else
+				printf("UnrealIRCd NT Service configuration change failed - %s", show_error(GetLastError()));	
+		}
+		else if (!strcasecmp(argv[2], "crashrestart")) {
+			SERVICE_FAILURE_ACTIONS hFailActions;
+			SC_ACTION hAction;
+			memset(&hFailActions, 0, sizeof(hFailActions));
+			if (argc >= 4) {
+				hFailActions.dwResetPeriod = 30;
+				hFailActions.cActions = 1;
+				hAction.Type = SC_ACTION_RESTART;
+				hAction.Delay = atoi(argv[3])*60000;
+				hFailActions.lpsaActions = &hAction;
+				if (uChangeServiceConfig2(hService, SERVICE_CONFIG_FAILURE_ACTIONS, 	
+						     &hFailActions))
+					printf("UnrealIRCd NT Service configuration changed");
+				else
+					printf("UnrealIRCd NT Service configuration change failed - %s", show_error(GetLastError()));	
+			}
+			else {
+				hFailActions.dwResetPeriod = 0;
+				hFailActions.cActions = 0;
+				hAction.Type = SC_ACTION_NONE;
+				hFailActions.lpsaActions = &hAction;
+				if (uChangeServiceConfig2(hService, SERVICE_CONFIG_FAILURE_ACTIONS,
+						     &hFailActions)) 
+					printf("UnrealIRCd NT Service configuration changed");
+				else
+					printf("UnrealIRCd NT Service configuration change failed - %s", show_error(GetLastError()));	
+
+				
+			}
+		}
+		else {
+			show_usage();
+			return -1;
+		}	
+	}
+	else {
+		show_usage();
+		return -1;
+	}
+}
+
diff --git a/ircd/src/windows/unrealsvc.rc b/ircd/src/windows/unrealsvc.rc
@@ -0,0 +1,99 @@
+//Microsoft Developer Studio generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#define APSTUDIO_HIDDEN_SYMBOLS
+#include "windows.h"
+#undef APSTUDIO_HIDDEN_SYMBOLS
+#include "resource.h"
+#include "winver.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+
+#ifndef _MAC
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VER_UNREAL VERSIONINFO
+ FILEVERSION 1,0,0,0
+ PRODUCTVERSION 1,0,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x10004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "Comments", "\0"
+            VALUE "CompanyName", "none\0"
+            VALUE "FileDescription", "\0"
+            VALUE "FileVersion", "1.0\0"
+            VALUE "InternalName", "UnrealIRCd Service Utility\0"
+            VALUE "LegalCopyright", "\0"
+            VALUE "LegalTrademarks", "\0"
+            VALUE "OriginalFilename", "\0"
+            VALUE "PrivateBuild", "\0"
+            VALUE "ProductName", "UnrealIRCd Service Utility\0"
+            VALUE "ProductVersion", "1.0\0"
+            VALUE "SpecialBuild", "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+#endif    // !_MAC
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+ICO_MAIN                ICON    DISCARDABLE     "icon1.ico"
+
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/ircd/src/windows/win.c b/ircd/src/windows/win.c
@@ -0,0 +1,341 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/win.c
+ *   Copyright (C) 2004 Dominick Meglio (codemastr)
+ *   
+ *   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 <windows.h>
+#include <tchar.h>
+#include <strsafe.h>
+
+#include "win.h"
+
+#pragma comment(lib, "User32.lib")
+
+// Newer product types than what is currently defined in
+//   Visual Studio 2005
+#ifndef PRODUCT_ULTIMATE
+#define PRODUCT_ULTIMATE                        0x00000001
+#endif
+#ifndef PRODUCT_HOME_BASIC
+#define PRODUCT_HOME_BASIC                      0x00000002
+#endif
+#ifndef PRODUCT_HOME_PREMIUM
+#define PRODUCT_HOME_PREMIUM                    0x00000003
+#endif
+#ifndef PRODUCT_ENTERPRISE
+#define PRODUCT_ENTERPRISE                      0x00000004
+#endif
+#ifndef PRODUCT_HOME_BASIC_N
+#define PRODUCT_HOME_BASIC_N                    0x00000005
+#endif
+#ifndef PRODUCT_BUSINESS
+#define PRODUCT_BUSINESS                        0x00000006
+#endif
+#ifndef PRODUCT_STANDARD_SERVER
+#define PRODUCT_STANDARD_SERVER                 0x00000007
+#endif
+#ifndef PRODUCT_DATACENTER_SERVER
+#define PRODUCT_DATACENTER_SERVER               0x00000008
+#endif
+#ifndef PRODUCT_SMALLBUSINESS_SERVER
+#define PRODUCT_SMALLBUSINESS_SERVER            0x00000009
+#endif
+#ifndef PRODUCT_ENTERPRISE_SERVER
+#define PRODUCT_ENTERPRISE_SERVER               0x0000000A
+#endif
+#ifndef PRODUCT_STARTER
+#define PRODUCT_STARTER                         0x0000000B
+#endif
+#ifndef PRODUCT_DATACENTER_SERVER_CORE
+#define PRODUCT_DATACENTER_SERVER_CORE          0x0000000C
+#endif
+#ifndef PRODUCT_STANDARD_SERVER_CORE
+#define PRODUCT_STANDARD_SERVER_CORE            0x0000000D
+#endif
+#ifndef PRODUCT_ENTERPRISE_SERVER_CORE
+#define PRODUCT_ENTERPRISE_SERVER_CORE          0x0000000E
+#endif
+#ifndef PRODUCT_ENTERPRISE_SERVER_IA64
+#define PRODUCT_ENTERPRISE_SERVER_IA64          0x0000000F
+#endif
+#ifndef PRODUCT_BUSINESS_N
+#define PRODUCT_BUSINESS_N                      0x00000010
+#endif
+#ifndef PRODUCT_WEB_SERVER
+#define PRODUCT_WEB_SERVER                      0x00000011
+#endif
+#ifndef PRODUCT_CLUSTER_SERVER
+#define PRODUCT_CLUSTER_SERVER                  0x00000012
+#endif
+#ifndef PRODUCT_HOME_SERVER
+#define PRODUCT_HOME_SERVER                     0x00000013
+#endif
+#ifndef PRODUCT_STORAGE_EXPRESS_SERVER
+#define PRODUCT_STORAGE_EXPRESS_SERVER          0x00000014
+#endif
+#ifndef PRODUCT_STORAGE_STANDARD_SERVER
+#define PRODUCT_STORAGE_STANDARD_SERVER         0x00000015
+#endif
+#ifndef PRODUCT_STORAGE_WORKGROUP_SERVER
+#define PRODUCT_STORAGE_WORKGROUP_SERVER        0x00000016
+#endif
+#ifndef PRODUCT_STORAGE_ENTERPRISE_SERVER
+#define PRODUCT_STORAGE_ENTERPRISE_SERVER       0x00000017
+#endif
+#ifndef PRODUCT_SERVER_FOR_SMALLBUSINESS
+#define PRODUCT_SERVER_FOR_SMALLBUSINESS        0x00000018
+#endif
+#ifndef PRODUCT_SMALLBUSINESS_SERVER_PREMIUM
+#define PRODUCT_SMALLBUSINESS_SERVER_PREMIUM    0x00000019
+#endif
+
+// Newer system metrics values
+#ifndef SM_SERVERR2
+#define SM_SERVERR2 89   
+#endif
+
+#ifndef VER_SUITE_WH_SERVER
+#define VER_SUITE_WH_SERVER                     0x00008000
+#endif
+
+#ifndef VER_SUITE_STORAGE_SERVER
+#define VER_SUITE_STORAGE_SERVER                0x00002000
+#endif
+
+#ifndef VER_SUITE_COMPUTE_SERVER
+#define VER_SUITE_COMPUTE_SERVER                0x00004000
+#endif
+
+typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
+typedef BOOL (WINAPI *PGPI)(DWORD, DWORD, DWORD, DWORD, PDWORD);
+
+
+
+/* Retrieves the OS name as a string
+ * Parameters:
+ * pszOS  - The buffer to write the OS name to (size at least OSVER_SIZE)
+ */
+int GetOSName(char *pszOS)
+{
+   OSVERSIONINFOEX osvi;
+   SYSTEM_INFO si;
+   PGNSI pGNSI;
+   PGPI pGPI;
+   BOOL bOsVersionInfoEx;
+   DWORD dwType;
+
+   ZeroMemory(&si, sizeof(SYSTEM_INFO));
+   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+
+   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+   if ( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
+      return -1;
+
+   // Call GetNativeSystemInfo if supported or GetSystemInfo otherwise.
+
+   pGNSI = (PGNSI) GetProcAddress(
+      GetModuleHandle(TEXT("kernel32.dll")), 
+      "GetNativeSystemInfo");
+   if (NULL != pGNSI)
+      pGNSI(&si);
+   else GetSystemInfo(&si);
+
+   if ( VER_PLATFORM_WIN32_NT==osvi.dwPlatformId && 
+        osvi.dwMajorVersion > 4 )
+   {
+      StringCchCopy(pszOS, OSVER_SIZE, TEXT("Microsoft "));
+
+      // Test for the specific product.
+
+      if ( osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1 )
+      {
+         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 )
+             StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Vista "));
+         else StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Server 2008 " ));
+
+         pGPI = (PGPI) GetProcAddress(
+            GetModuleHandle(TEXT("kernel32.dll")), 
+            "GetProductInfo");
+
+         pGPI( 6, 0, 0, 0, &dwType);
+
+         switch( dwType )
+         {
+            case PRODUCT_ULTIMATE:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Ultimate Edition" ));
+               break;
+            case PRODUCT_HOME_PREMIUM:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Home Premium Edition" ));
+               break;
+            case PRODUCT_HOME_BASIC:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Home Basic Edition" ));
+               break;
+            case PRODUCT_ENTERPRISE:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition" ));
+               break;
+            case PRODUCT_BUSINESS:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Business Edition" ));
+               break;
+            case PRODUCT_STARTER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Starter Edition" ));
+               break;
+            case PRODUCT_CLUSTER_SERVER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Cluster Server Edition" ));
+               break;
+            case PRODUCT_DATACENTER_SERVER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Datacenter Edition" ));
+               break;
+            case PRODUCT_DATACENTER_SERVER_CORE:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Datacenter Edition (core installation)" ));
+               break;
+            case PRODUCT_ENTERPRISE_SERVER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition" ));
+               break;
+            case PRODUCT_ENTERPRISE_SERVER_CORE:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition (core installation)" ));
+               break;
+            case PRODUCT_ENTERPRISE_SERVER_IA64:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition for Itanium-based Systems" ));
+               break;
+            case PRODUCT_SMALLBUSINESS_SERVER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Small Business Server" ));
+               break;
+            case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Small Business Server Premium Edition" ));
+               break;
+            case PRODUCT_STANDARD_SERVER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Standard Edition" ));
+               break;
+            case PRODUCT_STANDARD_SERVER_CORE:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Standard Edition (core installation)" ));
+               break;
+            case PRODUCT_WEB_SERVER:
+               StringCchCat(pszOS, OSVER_SIZE, TEXT("Web Server Edition" ));
+               break;
+         }
+         if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 )
+            StringCchCat(pszOS, OSVER_SIZE, TEXT( ", 64-bit" ));
+         else if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_INTEL )
+            StringCchCat(pszOS, OSVER_SIZE, TEXT(", 32-bit"));
+      }
+
+      if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2 )
+      {
+         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 &&
+                  si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
+         {
+            StringCchCat(pszOS, OSVER_SIZE, TEXT( "Windows XP Professional x64 Edition"));
+         }
+         else StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Server 2003, "));
+
+         // Test for the server type.
+         if ( osvi.wProductType != VER_NT_WORKSTATION )
+         {
+            if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_IA64 )
+            {
+                if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Edition for Itanium-based Systems" ));
+                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 )
+                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter x64 Edition" ));
+                else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
+                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Enterprise x64 Edition" ));
+                else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Standard x64 Edition" ));
+            }
+
+            else
+            {
+                if ( osvi.wSuiteMask & VER_SUITE_COMPUTE_SERVER )
+                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Compute Cluster Edition" ));
+                else if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Edition" ));
+                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" ));
+                else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Standard Edition" ));
+            }
+         }
+      }
+
+      if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )
+      {
+         StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows XP "));
+         if ( osvi.wSuiteMask & VER_SUITE_PERSONAL )
+            StringCchCat(pszOS, OSVER_SIZE, TEXT( "Home Edition" ));
+         else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Professional" ));
+      }
+
+      if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
+      {
+         StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows 2000 "));
+
+         if ( osvi.wProductType == VER_NT_WORKSTATION )
+         {
+            StringCchCat(pszOS, OSVER_SIZE, TEXT( "Professional" ));
+         }
+         else 
+         {
+            if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
+              StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Server" ));
+            else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
+               StringCchCat(pszOS, OSVER_SIZE, TEXT( "Advanced Server" ));
+            else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Server" ));
+         }
+      }
+
+       // Include service pack (if any) and build number.
+
+      if ( _tcslen(osvi.szCSDVersion) > 0 )
+      {
+          StringCchCat(pszOS, OSVER_SIZE, TEXT(" ") );
+          StringCchCat(pszOS, OSVER_SIZE, osvi.szCSDVersion);
+      }
+
+      {
+          TCHAR buf[80];
+
+          StringCchPrintf( buf, 80, TEXT(" (build %d)"), osvi.dwBuildNumber);
+          StringCchCat(pszOS, OSVER_SIZE, buf);
+      }
+
+      return 0; 
+   }
+   else
+   {  
+      return -1;
+   }
+}
diff --git a/ircd/src/windows/win.h b/ircd/src/windows/win.h
@@ -0,0 +1,43 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, win32/win32.h
+ *   Copyright (C) 2004 Dominick Meglio (codemastr)
+ *   
+ *   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 <windows.h>
+
+typedef struct {
+	int *size;
+	unsigned char **buffer;
+} StreamIO;
+
+typedef struct IRCColor {
+	struct IRCColor *next;
+	unsigned char *color;
+} IRCColor;
+
+extern UINT WM_FINDMSGSTRING;
+extern unsigned char *RTFBuf;
+extern HINSTANCE hInst;
+
+extern FARPROC lpfnOldWndProc;
+extern LRESULT RESubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);
+extern DWORD CALLBACK SplitIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
+extern DWORD CALLBACK BufferIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
+extern DWORD CALLBACK RTFToIRC(int fd, unsigned char *pbBuff, long cb);
+
+#define OSVER_SIZE 256
+
diff --git a/ircd/src/windows/windebug.c b/ircd/src/windows/windebug.c
@@ -0,0 +1,370 @@
+/************************************************************************
+ *   IRC - Internet Relay Chat, windows/windebug.c
+ *   Copyright (C) 2002-2004 Dominick Meglio (codemastr)
+ *   
+ *   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 <dbghelp.h>
+
+#ifndef IRCDTOTALVERSION
+#define IRCDTOTALVERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5 PATCH6 PATCH7 PATCH8 PATCH9
+#endif
+
+extern OSVERSIONINFO VerInfo;
+extern char OSName[256];
+extern char backupbuf[8192];
+extern char *buildid;
+extern char *extraflags;
+
+/* crappy, but safe :p */
+typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
+										CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+										CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+										CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
+										);
+
+
+/* Runs a stack trace 
+ * Parameters:
+ *  e - The exception information
+ * Returns:
+ *  The stack trace with function and line number information
+ */
+__inline char *StackTrace(EXCEPTION_POINTERS *e) 
+{
+	static char buffer[5000];
+	char curmodule[256];
+	DWORD symOptions;
+	DWORD64 dwDisp;
+	DWORD dwDisp32;
+	int frame;
+	HANDLE hProcess = GetCurrentProcess();
+	IMAGEHLP_SYMBOL64 *pSym = safe_alloc(sizeof(IMAGEHLP_SYMBOL64)+500);
+	IMAGEHLP_LINE64 pLine;
+	IMAGEHLP_MODULE64 pMod;
+	STACKFRAME64 Stack;
+	CONTEXT context;
+
+	memcpy(&context, e->ContextRecord, sizeof(CONTEXT));
+
+	/* Load the stack information */
+	memset(&Stack, 0, sizeof(Stack));
+	Stack.AddrPC.Offset = e->ContextRecord->Rip;
+	Stack.AddrPC.Mode = AddrModeFlat;
+	Stack.AddrFrame.Offset = e->ContextRecord->Rbp;
+	Stack.AddrFrame.Mode = AddrModeFlat;
+	Stack.AddrStack.Offset = e->ContextRecord->Rsp;
+	Stack.AddrStack.Mode = AddrModeFlat;
+	hProcess = GetCurrentProcess();
+
+	/* Initialize symbol retrieval system */
+	SymInitialize(hProcess, NULL, TRUE);
+	SymSetOptions(SYMOPT_LOAD_LINES|SYMOPT_UNDNAME);
+	pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+	pSym->MaxNameLength = 500;
+
+	/* Retrieve the first module name */
+	memset(&pMod, 0, sizeof(pMod));
+	pMod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
+	SymGetModuleInfo64(hProcess, Stack.AddrPC.Offset, &pMod);
+	strcpy(curmodule, pMod.ModuleName);
+	sprintf(buffer, "\tModule: %s\n", pMod.ModuleName);
+
+	/* Walk through the stack */
+	for (frame = 0; ; frame++) 
+	{
+		char buf[500];
+		if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(),
+			&Stack, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
+			break;
+
+		memset(&pMod, 0, sizeof(pMod));
+		pMod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
+		SymGetModuleInfo64(hProcess, Stack.AddrPC.Offset, &pMod);
+		if (strcmp(curmodule, pMod.ModuleName)) 
+		{
+			strcpy(curmodule, pMod.ModuleName);
+			sprintf(buf, "\tModule: %s\n", pMod.ModuleName);
+			strcat(buffer, buf);
+		}
+
+		memset(&pLine, 0, sizeof(pLine));
+		pLine.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+		SymGetLineFromAddr64(hProcess, Stack.AddrPC.Offset, &dwDisp32, &pLine);
+		SymGetSymFromAddr64(hProcess, Stack.AddrPC.Offset, &dwDisp, pSym);
+		sprintf(buf, "\t\t#%d %s:%d: %s\n", frame, pLine.FileName, pLine.LineNumber, 
+		        pSym->Name);
+		strcat(buffer, buf);
+	}
+	strcat(buffer, "End of Stack trace\n");
+	return buffer;
+
+}
+
+/* Retrieves the values of several registers
+ * Parameters:
+ *  context - The CPU context
+ * Returns:
+ *  The values of the registers as a string.
+ */
+__inline char *GetRegisters(CONTEXT *context) 
+{
+	static char buffer[1024];
+
+	sprintf(buffer,
+		"\tRAX=%p"
+		"\tRBX=%p"
+		"\tRCX=%p"
+		"\tRDX=%p\n"
+		"\tRSI=%p"
+		"\tRDI=%p"
+		"\tRBP=%p"
+		"\tRSP=%p\n"
+		"\tR8=%p"
+		"\tR9=%p"
+		"\tR10=%p"
+		"\tR11=%p\n"
+		"\tR12=%p"
+		"\tR13=%p"
+		"\tR14=%p"
+		"\tR15=%p\n"
+		"\tRIP=%p\n",
+		(void *)context->Rax,
+		(void *)context->Rbx,
+		(void *)context->Rcx,
+		(void *)context->Rdx,
+		(void *)context->Rsi,
+		(void *)context->Rdi,
+		(void *)context->Rbp,
+		(void *)context->Rsp,
+		(void *)context->R8,
+		(void *)context->R9,
+		(void *)context->R10,
+		(void *)context->R11,
+		(void *)context->R12,
+		(void *)context->R13,
+		(void *)context->R14,
+		(void *)context->R15,
+		(void *)context->Rip);
+
+	return buffer;
+}
+
+/* Convert the exception code to a human readable string
+ * Parameters:
+ *  code - The exception code to convert
+ * Returns:
+ *  The exception code represented as a string
+ */
+__inline char *GetException(DWORD code) 
+{
+	switch (code) 
+	{
+		case EXCEPTION_ACCESS_VIOLATION:
+			return "Access Violation";
+		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
+			return "Array Bounds Exceeded";
+		case EXCEPTION_BREAKPOINT:
+			return "Breakpoint";
+		case EXCEPTION_DATATYPE_MISALIGNMENT:
+			return "Datatype Misalignment";
+		case EXCEPTION_FLT_DENORMAL_OPERAND:
+			return "Floating Point Denormal Operand";
+		case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+			return "Floating Point Division By Zero";
+		case EXCEPTION_FLT_INEXACT_RESULT:
+			return "Floating Point Inexact Result";
+		case EXCEPTION_FLT_INVALID_OPERATION:
+			return "Floating Point Invalid Operation";
+		case EXCEPTION_FLT_OVERFLOW:
+			return "Floating Point Overflow";
+		case EXCEPTION_FLT_STACK_CHECK:
+			return "Floating Point Stack Overflow";
+		case EXCEPTION_FLT_UNDERFLOW:
+			return "Floating Point Underflow";
+		case EXCEPTION_ILLEGAL_INSTRUCTION:
+			return "Illegal Instruction";
+		case EXCEPTION_IN_PAGE_ERROR:
+			return "In Page Error";
+		case EXCEPTION_INT_DIVIDE_BY_ZERO:
+			return "Integer Division By Zero";
+		case EXCEPTION_INT_OVERFLOW:
+			return "Integer Overflow";
+		case EXCEPTION_INVALID_DISPOSITION:
+			return "Invalid Disposition";
+		case EXCEPTION_NONCONTINUABLE_EXCEPTION:
+			return "Noncontinuable Exception";
+		case EXCEPTION_PRIV_INSTRUCTION:
+			return "Unallowed Instruction";
+		case EXCEPTION_SINGLE_STEP:
+			return "Single Step";
+		case EXCEPTION_STACK_OVERFLOW:
+			return "Stack Overflow";
+		default:
+			return "Unknown Exception";
+	}
+}
+
+void StartCrashReporter(void)
+{
+	char fname[MAX_PATH], fnamewarg[MAX_PATH+32];
+	PROCESS_INFORMATION pi;
+	STARTUPINFO si;
+	
+	memset(&pi, 0, sizeof(pi));
+	memset(&si, 0, sizeof(si));
+	
+	GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
+	
+	snprintf(fnamewarg, sizeof(fnamewarg), "\"%s\" %s", fname, "-R");
+	CreateProcess(fname, fnamewarg, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+}
+
+void StartUnrealAgain(void)
+{
+	char fname[MAX_PATH], fnamewarg[MAX_PATH+32];
+	PROCESS_INFORMATION pi;
+	STARTUPINFO si;
+	
+	memset(&pi, 0, sizeof(pi));
+	memset(&si, 0, sizeof(si));
+	
+	GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
+	
+	snprintf(fnamewarg, sizeof(fnamewarg), "\"%s\"", fname);
+	CreateProcess(fname, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+}
+
+/* Callback for the exception handler
+ * Parameters:
+ *  e - The exception information
+ * Returns:
+ *  EXCEPTION_EXECUTE_HANDLER to terminate the process
+ * Side Effects:
+ *  unrealircd.PID.core is created
+ *  If not running in service mode, a message box is displayed, 
+ *   else output is written to service.log
+ */
+LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS *e) 
+{
+	MEMORYSTATUSEX memStats;
+	char file[512], text[1024], minidumpf[512];
+	FILE *fd;
+	time_t timet = time(NULL);
+#ifndef NOMINIDUMP
+	HANDLE hDump;
+	HMODULE hDll = NULL;
+#endif
+
+	sprintf(file, "unrealircd.%d.core", getpid());
+	fd = fopen(file, "w");
+	GlobalMemoryStatusEx(&memStats);
+	fprintf(fd, "Generated at %s\nOS: %s\n%s[%s%s%s] (%s) on %s\n"
+		    "-----------------\nMemory Information:\n"
+		    "\tPhysical: (Available:%lluMB/Total:%lluMB)\n"
+		    "\tVirtual: (Available:%lluMB/Total:%lluMB)\n"
+		    "-----------------\nException:\n\t%s\n-----------------\n"
+		    "Backup Buffer:\n\t%s\n-----------------\nRegisters:\n"
+		    "%s-----------------\nStack Trace:\n%s",
+		     asctime(gmtime(&timet)), OSName,
+			 IRCDTOTALVERSION,
+		     serveropts, extraflags ? extraflags : "", tainted ? "3" : "",
+		     buildid, me.name, memStats.ullAvailPhys/1048576, memStats.ullTotalPhys/1048576,
+		     memStats.ullAvailVirtual/1048576, memStats.ullTotalVirtual/1048576,
+		     GetException(e->ExceptionRecord->ExceptionCode), backupbuf,
+		     GetRegisters(e->ContextRecord), StackTrace(e));
+
+	sprintf(text, "UnrealIRCd has encountered a fatal error. Debugging information has been dumped to %s.", file);
+	fclose(fd);
+
+#ifndef NOMINIDUMP
+	hDll = LoadLibrary("DBGHELP.DLL");
+	if (hDll)
+	{
+		MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)GetProcAddress(hDll, "MiniDumpWriteDump");
+		if (pDump)
+		{
+			MINIDUMP_EXCEPTION_INFORMATION ExInfo;
+			sprintf(minidumpf, "unrealircd.%d.mdmp", getpid());
+			hDump = CreateFile(minidumpf, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+			if (hDump != INVALID_HANDLE_VALUE)
+			{
+				ExInfo.ThreadId = GetCurrentThreadId();
+				ExInfo.ExceptionPointers = e;
+				ExInfo.ClientPointers = 0;
+
+				if (pDump(GetCurrentProcess(), GetCurrentProcessId(), hDump, MiniDumpWithIndirectlyReferencedMemory, &ExInfo, NULL, NULL))
+				{
+					sprintf(text, "UnrealIRCd has encountered a fatal error. Debugging information has been dumped to %s and %s.", file, minidumpf);
+				}
+				CloseHandle(hDump);
+			}
+			sprintf(minidumpf, "unrealircd.%d.full.mdmp", getpid());
+			hDump = CreateFile(minidumpf, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+			if (hDump != INVALID_HANDLE_VALUE)
+			{
+				ExInfo.ThreadId = GetCurrentThreadId();
+				ExInfo.ExceptionPointers = e;
+				ExInfo.ClientPointers = 0;
+
+				pDump(GetCurrentProcess(), GetCurrentProcessId(), hDump, MiniDumpWithPrivateReadWriteMemory, &ExInfo, NULL, NULL);
+				CloseHandle(hDump);
+			}
+		}
+	}
+#endif
+	
+	if (!IsService)
+	{
+		MessageBox(NULL, text, "Fatal Error", MB_OK);
+		StartCrashReporter();
+	}
+	else 
+	{
+		FILE *fd = fopen("logs\\service.log", "a");
+
+		if (fd)
+		{
+			fprintf(fd, "UnrealIRCd has encountered a fatal error. Debugging information "
+					"has been dumped to unrealircd.%d.core, please file a bug and upload "
+					"this file to https://bugs.unrealircd.org/.", getpid());
+			fclose(fd);
+		}
+	}
+	CleanUp();
+	return EXCEPTION_EXECUTE_HANDLER;
+}
+
+void GotSigAbort(int signal)
+{
+	/* I just want to call ExceptionFilter() but it requires an argument which we don't have...
+	 * So just crash, which is rather silly but produces at least a crash report.
+	 * Feel free to improve this!
+	 */
+	char *crash = NULL;
+	*crash = 'X';
+}
+
+/* Initializes the exception handler */
+void InitDebug(void) 
+{
+	SetUnhandledExceptionFilter(&ExceptionFilter);
+	_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
+	signal(SIGABRT, GotSigAbort);
+}
+
+
diff --git a/ircd/src/windows/wingui.rc b/ircd/src/windows/wingui.rc
@@ -0,0 +1,410 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#define APSTUDIO_HIDDEN_SYMBOLS
+#include "windows.h"
+#undef APSTUDIO_HIDDEN_SYMBOLS
+#include "resource.h"
+#include "winver.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+UNREALIRCD DIALOGEX 0, 0, 345, 95
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_NOFAILCREATE | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
+FONT 8, "MS Sans Serif", 0, 0, 0x0
+BEGIN
+    CONTROL         130,IDC_STATIC,"Static",SS_BITMAP | SS_NOTIFY | SS_REALSIZEIMAGE,0,0,339,92
+    CONTROL         133,IDC_BAR,"Static",SS_BITMAP | SS_REALSIZEIMAGE,0,51,339,15
+END
+
+FROMVAR DIALOG 0, 0, 418, 183
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "UnrealIRCd License"
+FONT 8, "MS Sans Serif"
+BEGIN
+    DEFPUSHBUTTON   "OK",IDOK,184,162,50,14
+    CONTROL         "",IDC_TEXT,"RichEdit20A",ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP,8,7,402,149
+END
+
+HELP DIALOG 0, 0, 318, 94
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Where to get help"
+FONT 8, "MS Sans Serif"
+BEGIN
+    DEFPUSHBUTTON   "OK",IDOK,134,73,50,14
+    LTEXT           "Where to get help:",IDC_STATIC,14,11,60,8
+    LTEXT           "Point your IRC client to irc.unrealircd.org and join #unreal-support",IDC_STATIC,14,20,234,9
+    LTEXT           "Email us at",IDC_STATIC,14,30,37,8
+    CONTROL         "info@unrealircd.org",IDC_EMAIL,"Button",BS_OWNERDRAW,51,30,110,8
+    LTEXT           "Or visit us on the web at",IDC_STATIC,14,40,78,8
+    CONTROL         "https://www.unrealircd.org",IDC_URL,"Button",BS_OWNERDRAW,93,40,86,8
+    LTEXT           "Thank you for choosing UnrealIRCd :)",IDC_STATIC,98,55,121,9
+END
+
+FROMFILE DIALOG 0, 0, 418, 224
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "UnrealIRCd Configuration File"
+FONT 8, "MS Sans Serif"
+BEGIN
+    CONTROL         "",IDC_TEXT,"RichEdit20A",ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP,0,17,418,194
+END
+
+STATUS DIALOGEX 0, 0, 318, 169
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "UnrealIRCd Status"
+FONT 8, "MS Sans Serif", 0, 0, 0x1
+BEGIN
+    CONTROL         "Tree1",IDC_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_DISABLEDRAGDROP | WS_BORDER | WS_TABSTOP,13,16,100,125
+    GROUPBOX        "Servers",IDC_STATIC,7,7,112,138
+    GROUPBOX        "Global Statistics",IDC_STATIC,122,7,189,78
+    LTEXT           "Clients:",IDC_STATIC,127,18,26,9
+    LTEXT           "",IDC_CLIENTS,170,18,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Servers:",IDC_STATIC,221,18,27,8
+    LTEXT           "",IDC_SERVERS,261,18,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Channels:",IDC_STATIC,127,34,32,8
+    LTEXT           "",IDC_CHANNELS,170,33,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Invisible:",IDC_STATIC,221,34,28,8
+    LTEXT           "",IDC_INVISO,261,33,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Operators:",IDC_STATIC,127,51,34,8
+    LTEXT           "",IDC_OPERS,170,51,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Unknown:",IDC_STATIC,221,51,34,8
+    LTEXT           "",IDC_UNKNOWN,261,51,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Max Clients:",IDC_STATIC,127,69,39,8
+    LTEXT           "",IDC_MAXCLIENTS,170,69,41,9,SS_SUNKEN,WS_EX_RIGHT
+    GROUPBOX        "Local Statistics",IDC_STATIC,122,90,189,55
+    PUSHBUTTON      "OK",IDOK,134,148,50,14
+    LTEXT           "Clients:",IDC_STATIC,127,100,24,8
+    LTEXT           "",IDC_LCLIENTS,170,100,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Servers:",IDC_STATIC,221,100,27,8
+    LTEXT           "",IDC_LSERVERS,261,100,41,9,SS_SUNKEN,WS_EX_RIGHT
+    LTEXT           "Max Clients:",IDC_STATIC,127,118,39,8
+    LTEXT           "",IDC_LMAXCLIENTS,170,118,41,9,SS_SUNKEN,WS_EX_RIGHT
+END
+
+CONFIGERROR DIALOG 0, 0, 331, 178
+STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION
+CAPTION "Configuration Error"
+FONT 8, "MS Sans Serif"
+BEGIN
+    DEFPUSHBUTTON   "OK",IDOK,134,157,50,14
+    EDITTEXT        IDC_CONFIGERROR,7,7,317,142,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | WS_VSCROLL
+END
+
+GOTO DIALOG 0, 0, 124, 52
+STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Goto"
+FONT 8, "MS Sans Serif"
+BEGIN
+    LTEXT           "Line Number:",IDC_STATIC,9,12,43,8
+    EDITTEXT        IDC_GOTO,56,9,48,14,ES_AUTOHSCROLL | ES_NUMBER
+    PUSHBUTTON      "Cancel",IDCANCEL,66,31,50,14
+    DEFPUSHBUTTON   "Go To",IDOK,8,31,50,14
+END
+
+COLOR DIALOG 0, 0, 156, 37
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Color Selection"
+FONT 8, "MS Sans Serif"
+BEGIN
+    CONTROL         "",IDC_WHITE,"Button",BS_OWNERDRAW | WS_TABSTOP,4,3,13,12
+    CONTROL         "",IDC_BLACK,"Button",BS_OWNERDRAW | WS_TABSTOP,23,3,13,12
+    CONTROL         "",IDC_DARKBLUE,"Button",BS_OWNERDRAW | WS_TABSTOP,42,3,13,12
+    CONTROL         "",IDC_DARKGREEN,"Button",BS_OWNERDRAW | WS_TABSTOP,61,3,13,12
+    CONTROL         "",IDC_RED,"Button",BS_OWNERDRAW | WS_TABSTOP,80,3,13,12
+    CONTROL         "",IDC_DARKRED,"Button",BS_OWNERDRAW | WS_TABSTOP,99,3,13,12
+    CONTROL         "",IDC_PURPLE,"Button",BS_OWNERDRAW | WS_TABSTOP,118,3,13,12
+    CONTROL         "",IDC_ORANGE,"Button",BS_OWNERDRAW | WS_TABSTOP,137,3,13,12
+    CONTROL         "",IDC_YELLOW,"Button",BS_OWNERDRAW | WS_TABSTOP,4,21,13,12
+    CONTROL         "",IDC_GREEN,"Button",BS_OWNERDRAW | WS_TABSTOP,23,21,13,12
+    CONTROL         "",IDC_VDARKGREEN,"Button",BS_OWNERDRAW | WS_TABSTOP,42,21,13,12
+    CONTROL         "",IDC_LIGHTBLUE,"Button",BS_OWNERDRAW | WS_TABSTOP,61,21,13,12
+    CONTROL         "",IDC_BLUE,"Button",BS_OWNERDRAW | WS_TABSTOP,80,21,13,12
+    CONTROL         "",IDC_PINK,"Button",BS_OWNERDRAW | WS_TABSTOP,99,21,13,12
+    CONTROL         "",IDC_DARKGRAY,"Button",BS_OWNERDRAW | WS_TABSTOP,118,21,13,12
+    CONTROL         "",IDC_GRAY,"Button",BS_OWNERDRAW | WS_TABSTOP,137,21,13,12
+END
+
+TLSKEY DIALOG 0, 0, 174, 57
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "TLS Private Key Password"
+FONT 8, "MS Sans Serif"
+BEGIN
+    DEFPUSHBUTTON   "OK",IDOK,33,35,50,14
+    PUSHBUTTON      "Cancel",IDCANCEL,89,35,50,14
+    EDITTEXT        IDC_PASS,11,12,152,14,ES_PASSWORD | ES_AUTOHSCROLL
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1                       RT_MANIFEST             "UnrealIRCd.exe.manifest"
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE 
+BEGIN
+    "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
+    "#include ""windows.h""\r\n"
+    "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
+    "#include ""resource.h""\r\n"
+    "#include ""winver.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE 
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+    "UNREALIRCD", DIALOG
+    BEGIN
+        RIGHTMARGIN, 319
+        BOTTOMMARGIN, 94
+    END
+
+    "FROMVAR", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 411
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 176
+    END
+
+    "HELP", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 311
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 87
+    END
+
+    "FROMFILE", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 411
+        TOPMARGIN, 2
+        BOTTOMMARGIN, 189
+    END
+
+    "STATUS", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 311
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 162
+    END
+
+    "CONFIGERROR", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 324
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 171
+    END
+
+    "GOTO", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 117
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 45
+    END
+
+    "COLOR", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 149
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 30
+    END
+
+    "TLSKEY", DIALOG
+    BEGIN
+        LEFTMARGIN, 7
+        RIGHTMARGIN, 167
+        TOPMARGIN, 7
+        BOTTOMMARGIN, 50
+    END
+END
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VER_UNREAL VERSIONINFO
+ FILEVERSION 1,0,0,0
+ PRODUCTVERSION 1,0,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x10004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "CompanyName", "none"
+            VALUE "FileVersion", "3.2"
+            VALUE "InternalName", "UnrealIRCd"
+            VALUE "ProductName", "UnrealIRCd"
+            VALUE "ProductVersion", "1.0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+ICO_MAIN                ICON                    "icon1.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+BMP_LOGO                BITMAP                  "unreal.bmp"
+BMP_BAR                 BITMAP                  "bar.bmp"
+IDB_BITMAP1             BITMAP                  "toolbar.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Cursor
+//
+
+CUR_HAND                CURSOR                  "hand.CUR"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+MENU_ABOUT MENU
+BEGIN
+    POPUP "About"
+    BEGIN
+        MENUITEM "UnrealIRCd &Team",            IDM_INFO
+        MENUITEM "Additional &Credits",         IDM_CREDITS
+        MENUITEM "&License",                    IDM_LICENSE
+        MENUITEM "&Help",                       IDM_HELP
+    END
+END
+
+MENU_CONFIG MENU
+BEGIN
+    POPUP "Config"
+    BEGIN
+        MENUITEM "ircd.&conf",                  IDM_CONF
+        MENUITEM SEPARATOR
+    END
+END
+
+MENU_REHASH MENU
+BEGIN
+    POPUP "Rehash"
+    BEGIN
+        MENUITEM "Rehash &All Files",           IDM_RHALL
+    END
+END
+
+MENU_SYSTRAY MENU
+BEGIN
+    POPUP "Systray"
+    BEGIN
+        MENUITEM "&Rehash",                     IDM_REHASH
+        MENUITEM "&Status",                     IDM_STATUS
+        MENUITEM "&Config",                     IDM_CONFIG
+        MENUITEM "&About",                      IDM_ABOUT
+        MENUITEM "S&hutdown",                   IDM_SHUTDOWN
+    END
+END
+
+MENU_CONTEXT MENU
+BEGIN
+    POPUP "Context"
+    BEGIN
+        MENUITEM "&Undo",                       IDM_UNDO
+        MENUITEM SEPARATOR
+        MENUITEM "Cu&t",                        IDM_CUT
+        MENUITEM "&Copy",                       IDM_COPY
+        MENUITEM "&Paste",                      IDM_PASTE
+        MENUITEM "&Delete",                     IDM_DELETE
+        MENUITEM SEPARATOR
+        MENUITEM "&Select All",                 IDM_SELECTALL
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/ircd/unrealircd.in b/ircd/unrealircd.in
@@ -0,0 +1,340 @@
+#!/bin/sh
+
+PID_FILE="@PIDFILE@"
+PID_BACKUP="@PIDFILE@.bak"
+BINDIR="@BINDIR@"
+UNREALIRCDCTL="$BINDIR/unrealircdctl"
+IRCD="$BINDIR/unrealircd"
+BUILDDIR="@BUILDDIR@"
+CONFDIR="@CONFDIR@"
+TMPDIR="@TMPDIR@"
+SCRIPTDIR="@SCRIPTDIR@"
+MODULESDIR="@MODULESDIR@"
+
+# When built with --with-asan, ASan does not dump core by default because
+# older gcc/clang might dump a 16TB core file. We explicitly enable it here.
+export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1:log_path=$TMPDIR/unrealircd_asan:detect_leaks=0"
+
+if [ ! -f $IRCD ]; then
+	echo "ERROR: Could not find the IRCd binary ($IRCD)"
+	echo "This could mean two things:"
+	echo "1) You forgot to run 'make install' after running 'make'"
+	echo "2) You answered a ./Config question incorrectly"
+	exit
+fi
+if [ ! -d "$TMPDIR" ]; then
+	mkdir "$TMPDIR"
+fi
+
+if [ "$1" = "start" ] ; then
+	if [ -r $PID_FILE ] ; then
+		if kill -CHLD `cat $PID_FILE` 1>/dev/null 2>&1; then
+			if $UNREALIRCDCTL status 1>/dev/null 2>&1; then
+				echo "UnrealIRCd is already running (PID `cat $PID_FILE`)."
+				echo "To restart UnrealIRCd, use: $0 restart"
+				exit 1
+			fi
+		fi
+	fi
+	if [ -r $PID_FILE ] ; then
+		mv -f $PID_FILE $PID_BACKUP
+	fi
+
+	# Check if ~/Unrealxxx/unrealircd.conf exists but the file
+	# ~/unrealircd/conf/unrealircd.conf does not.
+	# If so, then assume a user-build and give the user a nice hint...
+	if [ ! -f $CONFDIR/unrealircd.conf -a -f $BUILDDIR/unrealircd.conf ]; then
+		echo ""
+		echo "There is no unrealircd.conf in $CONFDIR"
+		echo "However I did find an unrealircd.conf in $BUILDDIR"
+		echo "With UnrealIRCd 4 you should no longer run the IRCd from $BUILDDIR."
+		echo "You should 'cd $SCRIPTDIR' and work from there."
+		echo "See https://www.unrealircd.org/docs/UnrealIRCd_files_and_directories"
+		exit 1
+	fi
+	if [ ! -f $CONFDIR/unrealircd.conf ]; then
+		echo ""
+		echo "The configuration file does not exist ($CONFDIR/unrealircd.conf)."
+		echo "Create one using the example configuration file, see the documentation:"
+		echo "https://www.unrealircd.org/docs/Installing_from_source#Creating_a_configuration_file"
+		exit 1
+	fi
+
+	echo "Starting UnrealIRCd"
+
+	$IRCD
+	if [ $? -ne 0 ] ; then
+		if [ -r $PID_BACKUP ] ; then
+			mv -f $PID_BACKUP $PID_FILE
+		fi
+		# Try to be helpful...
+		if ldd $IRCD 2>&1|grep -qF '=> not found'; then
+			echo "========================================================"
+			echo "UnrealIRCd failed to start due to missing libraries."
+			echo "Maybe you need to recompile UnrealIRCd? See"
+			echo "https://www.unrealircd.org/docs/FAQ#shared-library-error"
+			echo "========================================================"
+		else
+			echo "====================================================="
+			echo "UnrealIRCd failed to start. Check above for possible errors."
+			echo "If you don't understand the problem, then have a look at our:"
+			echo "* FAQ (Frequently Asked Questions): https://www.unrealircd.org/docs/FAQ"
+			echo "* Documentation: https://www.unrealircd.org/docs/"
+			echo "====================================================="
+		fi
+		exit 1
+	fi
+	# Now check if we need to create a crash report.
+	$IRCD -R
+elif [ "$1" = "stop" ] ; then
+	echo -n "Stopping UnrealIRCd"
+	if [ ! -r $PID_FILE ] ; then
+		echo
+		echo "ERROR: UnrealIRCd is not running"
+		exit 1
+	fi
+	kill -15 `cat $PID_FILE`
+	if [ "$?" != 0 ]; then
+		echo
+		echo "ERROR: UnrealIRCd is not running"
+		exit 1
+	fi
+	# Wait for UnrealIRCd to terminate, but wait 10 seconds max
+	n="0"
+	while [ "$n" -lt 10 ]
+	do
+		echo -n "."
+		if [ ! -r $PID_FILE ] ; then
+			break
+		fi
+		if ! kill -0 `cat $PID_FILE`; then
+			break
+		fi
+		n=`expr $n + 1`
+		sleep 1
+	done
+	echo
+	# In case it is still running, kill it for good.
+	if [ -r $PID_FILE ] ; then
+		kill -9 `cat $PID_FILE` 1>/dev/null 2>&1
+	fi
+elif [ "$1" = "rehash" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "status" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "module-status" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "reloadtls" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "restart" ] ; then
+	echo "Validating configuration..."
+	TMPF="$TMPDIR/configtest.txt"
+	if ! $0 configtest 1>$TMPF 2>&1; then
+		cat $TMPF
+		rm -f $TMPF
+		echo ""
+		echo "Configuration test failed. Server is NOT restarted."
+		exit 1
+	fi
+	echo "Configuration test OK."
+	$0 stop
+	$0 start
+elif [ "$1" = "croncheck" ] ; then
+	if [ -r $PID_FILE ] ; then
+		kill -CHLD `cat $PID_FILE` 1>/dev/null 2>&1
+		if [ "$?" = 0 ]; then
+			# IRCd is running, bail out silently.
+			exit 0
+		fi
+	fi
+	# PID file not found or found but stale
+	echo "UnrealIRCd is not running. Starting now..."
+	$0 start
+elif [ "$1" = "configtest" ] ; then
+	$IRCD -c
+elif [ "$1" = "module" ] ; then
+	shift
+	$IRCD -m $*
+elif [ "$1" = "mkpasswd" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "version" ] ; then
+	$IRCD -v
+elif [ "$1" = "gencloak" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "backtrace" ] ; then
+	cd $TMPDIR
+
+	# Find the corefile
+	echo "Core files available:"
+	n="0"
+	for i in `echo *core*`
+	do
+		ls -l $i
+		n=`expr $n + 1`
+	done
+
+	if [ "$n" -gt 1 ]; then
+		echo "Type the name of the core file you want to research:"
+		read corefile
+	elif [ "$i" = "*core*" -o "$n" -eq 0 ]; then
+		echo 'No core files found... Nothing to do'
+		echo ''
+		echo 'If you are sure UnrealIRCd crashed, then verify that unreal'
+		echo 'has permission to dump core (type "ulimit -c unlimited" and see'
+		echo 'if you get permission denied errors). Also verify that you did'
+		echo 'not run out of quota.'
+		echo 'If all that is ok, then it might be that UnrealIRCd did not crash but'
+		echo 'got killed by the OS (eg: cpu/mem resource limits), the syadmin,'
+		echo 'or an automated process.'
+		exit 1
+	else
+		corefile="$i"
+	fi
+
+	if [ ! -f "$corefile" ]; then
+		echo "Core file '$corefile' not found"
+	fi
+	if [ ! -s "$corefile" ]; then
+		echo 'Seems the corefile is 0 bytes'
+		echo 'This usually means you need to relax the core file resource limit'
+		echo '(type "ulimit -c unlimited"), or you might have ran out of quota.'
+		exit 1
+	fi
+
+	# This is needed for the script below and is probably also helpful for the
+	# bug report since you usually want to paste this to the development team.
+	export LANG=C
+	export LC_ALL=C
+
+	# The tmp/*.so files are often already deleted. Here we have some
+	# (ugly) scripting to recreate the tmp/*.so links to the modules *.so files...
+	echo 'info sharedlibrary'|gdb $IRCD $corefile 2>/dev/null|\
+	grep No|grep tmp/|awk '{ print $2 }'|\
+	awk -F '.' "{ system(\"[ -f $MODULESDIR/\" \$2 \"/\" \$3 \".so ] && ln -s $MODULESDIR/\" \$2 \"/\" \$3 \".so \" \$0 \" || ln -s $MODULESDIR/\" \$2 \".so \" \$0) }"
+	
+	echo ""
+	echo "=================== START HERE ======================"
+	echo "BACKTRACE:"
+
+cat >$TMPDIR/gdb.commands << __EOF__
+bt
+echo \n
+frame
+echo \n
+x/s backupbuf
+echo \n
+bt 3 full
+quit
+__EOF__
+
+	gdb -batch -x $TMPDIR/gdb.commands $IRCD $corefile
+	rm -f $TMPDIR/gdb.commands
+	echo "GCC: `gcc -v 2>&1|tail -n 1`"
+	echo "UNAME: `uname -a`"
+	echo "UNREAL: `$0 version`"
+	echo "CORE: `ls -al $corefile`"
+	echo "===================  STOP HERE ======================"
+	echo ""
+	echo "Copy the parts between the START HERE and STOP HERE marker"
+	echo "and report it on https://bugs.unrealircd.org/"
+	echo ""
+	echo 'But before you do, note the following:'
+	echo '1. We do not support modifications of any unrealircd code'
+	echo '   (except for config.h changes).'
+	echo '2. If you are using 3rd party modules we might request you'
+	echo '   to run without them and verify you still crash. This is'
+	echo '   to eleminate any loss of time due to bugs made by others'
+	echo '3. Use a reasonably recent UnrealIRCd version. We fix (crash)bugs'
+	echo '   all the time so your bug might as well be fixed already.'
+	echo ""
+	echo "Thanks!"
+elif [ "$1" = "spki" -o "$1" = "spkifp" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
+	if [ ! -d "$BUILDDIR" ]; then
+		echo "UnrealIRCd source not found. Sorry, it is not possible to patch."
+		exit 1
+	fi
+	if [ "$2" = "" ]; then
+		echo "Argument required: ./unrealircd <hot-patch|cold-patch> <name-of-patch>"
+		exit 1
+	fi
+	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 'apt install wget' or a similar command."
+		exit 1
+	fi
+	cd "$BUILDDIR" || exit 1
+
+	# Weird way to get version, but ok.
+	UNREALVER="`./configure --version|head -n1|awk '{ print $3 }'`"
+	wget -O patch "https://www.unrealircd.org/patch?type=$1&patch=$2&version=$UNREALVER" || exit 1
+
+	# A patch file of 0 bytes means the patch is not needed
+	if [ -f patch -a ! -s patch ]; then
+		echo "This UnrealIRCd version does not require that patch"
+	fi
+
+	if patch --dry-run -p1 -R <patch 1>/dev/null 2>&1; then
+		echo "Patch already applied. Nothing to do."
+		exit 1
+	fi
+
+	if ! patch --dry-run -p1 -N <patch 1>/dev/null 2>&1; then
+		echo "Patch failed to apply (no files changed)"
+		exit 1
+	fi
+
+	if ! patch -p1 <patch; then
+		echo "Patch failed to apply"
+		exit 1
+	fi
+
+	echo "Patch applied successfully. Now recompiling..."
+	make || gmake || exit 1
+	make install || gmake install || exit 1
+
+	cd $SCRIPTDIR
+	if [ "$1" = "hot-patch" ]; then
+		echo "Patch applied successfully and installed. Rehashing your IRCd..."
+		if ./unrealircd rehash; then
+			echo "Patch installed and server rehashed correctly. All should be good now!"
+		else
+			echo "Patching the source code and recompiling succeeded,"
+			echo "however rehashing the current UnrealIRCd process FAILED"
+			echo "so it is NOT running the patched code yet."
+			echo "IMPORTANT: Check error output above!"
+			exit 1
+		fi
+	else
+		echo "Patch applied successfully. You must now restart your IRC server."
+	fi
+elif [ "$1" = "upgrade" ] ; then
+	$BINDIR/unrealircd-upgrade-script $*
+	exit
+elif [ "$1" = "genlinkblock" ] ; then
+	$IRCD -L
+else
+	if [ "$1" = "" ]; then
+		echo "This script expects a parameter. Use:"
+	else
+		echo "Unrecognized parameter '$1'. Use:"
+	fi
+	echo "unrealircd configtest    Test the configuration file"
+	echo "unrealircd start         Start the IRC Server"
+	echo "unrealircd stop          Stop (kill) the IRC Server"
+	echo "unrealircd rehash        Reload the configuration file"
+	echo "unrealircd reloadtls     Reload the SSL/TLS certificates"
+	echo "unrealircd restart       Restart the IRC Server (stop+start)"
+	echo "unrealircd status        Show current status of the IRC Server"
+	echo "unrealircd module-status Show all currently loaded modules"
+	echo "unrealircd upgrade       Upgrade UnrealIRCd to the latest version"
+	echo "unrealircd mkpasswd      Hash a password"
+	echo "unrealircd version       Display the UnrealIRCd version"
+	echo "unrealircd module        Install and uninstall 3rd party modules"
+	echo "unrealircd croncheck     For use in crontab: this checks if the server"
+	echo "                         is running. If not, the server is started."
+	echo "unrealircd genlinkblock  Generate link { } block for the other side."
+	echo "unrealircd gencloak      Display 3 random cloak keys"
+	echo "unrealircd spkifp        Display SPKI Fingerprint"
+fi
diff --git a/setup.sh b/setup.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# SuperNETS UnrealIRCd - Developed by acidvegas (https://github.com/supernets/unrealircd)
+# unrealircd/setup.sh
+
+set -xev
+
+# Load environment variables
+if [ -f .env ]; then
+    source .env
+else
+    echo "Error: .env file not found" && exit 1
+fi
+
+# Copy database files from container to assets directory if they exist
+if docker exec ircd sh -c "ls /opt/ircd/data/*.db 2>/dev/null"; then
+    docker cp ircd:/opt/ircd/data/*.db assets/
+fi    
+
+# Check if all of the assets exist
+[ ! -d assets          ] && echo "error: assets directory not found" && exit 1
+[ ! -f assets/tls.crt  ] && echo "error: tls.crt file not found"     && exit 1
+[ ! -f assets/tls.key  ] && echo "error: tls.key file not found"     && exit 1
+[ -z $(ls assets/*.db) ] && echo "warning: no database files found"
+
+# Remove existing container if it exists
+docker rm -f ircd 2>/dev/null || true
+
+# Cleanup docker volumes
+docker system prune -af --volumes
+
+# Build the Docker image
+docker build -t ircd .
+
+# Run the Docker container with proper settings and environment variables
+docker run -d \
+    --name ircd \
+    --restart always \
+    --hostname $HOSTNAME \
+    -p 6660-6669:6660-6669 \ 
+    -p 6697:6697 \
+    -p 7000:7000 \
+    -p 9000:9000 \
+    -p ${LEAF_PORT}:${LEAF_PORT} \
+    ircd
+\ No newline at end of file
diff --git a/src/modules/chanmodes/regonlyspeak.c b/src/modules/chanmodes/regonlyspeak.c
@@ -1,108 +0,0 @@
-/*
- * Only registered users can speak UnrealIRCd Module (Channel Mode +M)
- * (C) Copyright 2014 Travis McArthur (Heero) 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/regonlyspeak",
-	"4.2",
-	"Channel Mode +M",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-Cmode_t EXTCMODE_REGONLYSPEAK;
-static char errMsg[2048];
-
-#define IsRegOnlySpeak(channel)    (channel->mode.mode & EXTCMODE_REGONLYSPEAK)
-
-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()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	CmodeInfo req;
-
-	memset(&req, 0, sizeof(req));
-	req.paracount = 0;
-	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);
-	HookAddConstString(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, regonlyspeak_part_message);
-
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-const char *regonlyspeak_part_message (Client *client, Channel *channel, const char *comment)
-{
-	if (!comment)
-		return NULL;
-
-	if (IsRegOnlySpeak(channel) && !IsLoggedIn(client) && !ValidatePermissionsForPath("channel:override:message:regonlyspeak",client,NULL,NULL,NULL))
-		return NULL;
-
-	return comment;
-}
-
-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) &&
-	    !check_channel_access_membership(lp, "vhoaq"))
-	{
-		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_ALLOW)
-				return HOOK_CONTINUE; /* bypass +M restriction */
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-
-		*errmsg = "You must have a registered nick (+r) to talk on this channel";
-		return HOOK_DENY; /* BLOCK message */
-	}
-
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/chanmodes/secret.c b/src/modules/chanmodes/secret.c
@@ -1,73 +0,0 @@
-/*
- * 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
@@ -1,190 +0,0 @@
-/*
- * Only allow secure users to join UnrealIRCd Module (Channel Mode +z)
- * (C) Copyright 2014 Travis McArthur (Heero) 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/secureonly",
-	"4.2",
-	"Channel Mode +z",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-Cmode_t EXTCMODE_SECUREONLY;
-
-#define IsSecureOnly(channel)    (channel->mode.mode & EXTCMODE_SECUREONLY)
-
-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_pre_local_join(Client *client, Channel *channel, const char *key);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	CmodeInfo req;
-
-	memset(&req, 0, sizeof(req));
-	req.paracount = 0;
-	req.letter = 'z';
-	req.is_ok = extcmode_default_requirechop;
-	CmodeAdd(modinfo->handle, req, &EXTCMODE_SECUREONLY);
-
-	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);
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SAJOIN, 0, secureonly_check_sajoin);
-
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/** Kicks all insecure users on a +z channel
- * Returns 1 if the channel was destroyed as a result of this.
- */
-static int secureonly_kick_insecure_users(Channel *channel)
-{
-	Member *member, *mb2;
-	Client *client;
-	int i = 0;
-	Hook *h;
-	char *comment = "Insecure user not allowed on secure channel (+z)";
-
-	if (!IsSecureOnly(channel))
-		return 0;
-
-	for (member = channel->members; member; member = mb2)
-	{
-		mb2 = member->next;
-		client = member->client;
-		if (MyUser(client) && !IsSecureConnect(client) && !IsULine(client))
-		{
-			char *prefix = NULL;
-			MessageTag *mtags = NULL;
-
-			if (invisible_user_in_channel(client, channel))
-			{
-				/* Send only to chanops */
-				prefix = "ho";
-			}
-
-			new_message(&me, NULL, &mtags);
-
-			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->name, 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->name, client->id, comment);
-
-			free_message_tags(mtags);
-
-			if (remove_user_from_channel(client, channel, 0) == 1)
-				return 1; /* channel was destroyed */
-		}
-	}
-	return 0;
-}
-
-int secureonly_check_join(Client *client, Channel *channel, const char *key, char **errmsg)
-{
-	Link *lp;
-
-	if (IsSecureOnly(channel) && !(client->umodes & UMODE_SECURE))
-	{
-		if (ValidatePermissionsForPath("channel:override:secureonly",client,NULL,channel,NULL))
-		{
-			/* if the channel is +z we still allow an ircop to bypass it
-			 * if they are invited.
-			 */
-			if (is_invited(client, channel))
-				return HOOK_CONTINUE;
-		}
-		*errmsg = STR_ERR_SECUREONLYCHAN;
-		return ERR_SECUREONLYCHAN;
-	}
-	return 0;
-}
-
-int secureonly_check_secure(Channel *channel)
-{
-	if (IsSecureOnly(channel))
-	{
-		return 1;
-	}
-
-	return 0;
-}
-
-int secureonly_channel_sync(Channel *channel, int merge, int removetheirs, int nomode)
-{
-	if (!merge && !removetheirs && !nomode)
-		return secureonly_kick_insecure_users(channel); /* may return 1, meaning channel is destroyed */
-	return 0;
-}
-
-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 TLS",
-			target->name, channel->name);
-		return HOOK_DENY;
-	}
-
-	return HOOK_CONTINUE;
-}
-
-/* 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_pre_local_join(Client *client, Channel *channel, const char *key)
-{
-	if ((channel->users == 0) && (MODES_ON_JOIN & EXTCMODE_SECUREONLY) && !IsSecure(client) && !IsOper(client))
-	{
-		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
@@ -1,131 +0,0 @@
-/*
- * Strip Color UnrealIRCd Module (Channel Mode +S)
- * (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"
-
-CMD_FUNC(stripcolor);
-
-ModuleHeader MOD_HEADER
-  = {
-	"chanmodes/stripcolor",
-	"4.2",
-	"Channel Mode +S",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-Cmode_t EXTCMODE_STRIPCOLOR;
-
-#define IsStripColor(channel)    (channel->mode.mode & EXTCMODE_STRIPCOLOR)
-
-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()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-CmodeInfo req;
-
-	/* Channel mode */
-	memset(&req, 0, sizeof(req));
-	req.paracount = 0;
-	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);
-	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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
-{
-	Hook *h;
-	int i;
-
-	if (MyUser(client) && IsStripColor(channel))
-	{
-		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_COLOR);
-			if (i == HOOK_ALLOW)
-				return HOOK_CONTINUE; /* bypass this restriction */
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-
-		*msg = StripColors(*msg);
-	}
-
-	return HOOK_CONTINUE;
-}
-
-const char *stripcolor_prelocalpart(Client *client, Channel *channel, const char *comment)
-{
-	if (!comment)
-		return NULL;
-
-	if (MyUser(client) && IsStripColor(channel))
-		comment = StripColors(comment);
-
-	return comment;
-}
-
-/** Is any channel where the user is in +S? */
-static int IsAnyChannelStripColor(Client *client)
-{
-	Membership *lp;
-
-	for (lp = client->user->channel; lp; lp = lp->next)
-		if (IsStripColor(lp->channel))
-			return 1;
-	return 0;
-}
-
-
-const char *stripcolor_prelocalquit(Client *client, const char *comment)
-{
-	if (!comment)
-		return NULL;
-
-	if (MyUser(client) && !BadPtr(comment) && IsAnyChannelStripColor(client))
-		comment = StripColors(comment);
-        
-	return comment;
-}
-
diff --git a/src/modules/chanmodes/topiclimit.c b/src/modules/chanmodes/topiclimit.c
@@ -1,82 +0,0 @@
-/*
- * 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
@@ -1,88 +0,0 @@
-/*
- * 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/channel-context.c b/src/modules/channel-context.c
@@ -1,95 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/channel-context.c
- *   (C) 2022 Valware & 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
-  = {
-	"channel-context",
-	"1.0",
-	"Channel Context (IRCv3)",
-	"UnrealIRCd team",
-	"unrealircd-6",
-	};
-
-int chancontext_mtag_is_ok(Client *client, const char *name, const char *value);
-void mtag_add_chancontext(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
-
-MOD_INIT()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.is_ok = chancontext_mtag_is_ok;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	mtag.name = "+draft/channel-context";
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_chancontext);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int chancontext_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (BadPtr(value))
-		return 0;
-
-	/* Validate a bit further, but only for local users.. */
-	if (MyUser(client))
-	{
-		Channel *channel = find_channel(value);
-		if (!channel)
-			return 0;
-		if (!IsMember(client, channel))
-			return 0;
-	}
-
-	return 1;
-}
-
-void mtag_add_chancontext(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m;
-
-	if (IsUser(client))
-	{
-		m = find_mtag(recv_mtags, "+draft/channel-context");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-			AddListItem(m, *mtag_list);
-		}
-	}
-}
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
@@ -1,567 +0,0 @@
-/*
- * Stores channel settings for +P channels in a .db file
- * (C) Copyright 2019 Syzop, Gottem and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER = {
-	"channeldb",
-	"1.0",
-	"Stores and retrieves channel settings for persistent (+P) channels",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Database version */
-#define CHANNELDB_VERSION 100
-/* Save channels to file every <this> seconds */
-#define CHANNELDB_SAVE_EVERY 300
-/* The very first save after boot, apply this delta, this
- * so we don't coincide with other (potentially) expensive
- * I/O events like saving tkldb.
- */
-#define CHANNELDB_SAVE_EVERY_DELTA -15
-
-#define MAGIC_CHANNEL_START	0x11111111
-#define MAGIC_CHANNEL_END	0x22222222
-
-// #undef BENCHMARK
-
-#define WARN_WRITE_ERROR(fname) \
-	do { \
-		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) \
-	do { \
-		if (!(x)) { \
-			WARN_WRITE_ERROR(tmpfname); \
-			unrealdb_close(db); \
-			return 0; \
-		} \
-	} while(0)
-
-#define IsMDErr(x, y, z) \
-	do { \
-		if (!(x)) { \
-			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
-			return MOD_FAILED; \
-		} \
-	} while(0)
-
-/* Structs */
-struct cfgstruct {
-	char *database;
-	char *db_secret;
-};
-
-/* Forward declarations */
-void channeldb_moddata_free(ModData *md);
-void setcfg(struct cfgstruct *cfg);
-void freecfg(struct cfgstruct *cfg);
-int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int channeldb_config_posttest(int *errs);
-int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-EVENT(write_channeldb_evt);
-int write_channeldb(void);
-int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel);
-int read_channeldb(void);
-
-/* Global variables */
-static uint32_t channeldb_version = CHANNELDB_VERSION;
-static struct cfgstruct cfg;
-static struct cfgstruct test;
-
-static long channeldb_next_event = 0;
-
-MOD_TEST()
-{
-	memset(&cfg, 0, sizeof(cfg));
-	memset(&test, 0, sizeof(test));
-	setcfg(&test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, channeldb_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, channeldb_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	/* We must unload early, when all channel modes and such are still in place: */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
-
-	LoadPersistentLong(modinfo, channeldb_next_event);
-
-	setcfg(&cfg);
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_config_run);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (!channeldb_next_event)
-	{
-		/* If this is the first time that our module is loaded, then read the database. */
-		if (!read_channeldb())
-		{
-			char fname[512];
-			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
-			if (rename(cfg.database, fname) == 0)
-				config_warn("[channeldb] Existing database renamed to %s and starting a new one...", fname);
-			else
-				config_warn("[channeldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
-		}
-		channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY + CHANNELDB_SAVE_EVERY_DELTA;
-	}
-	EventAdd(modinfo->handle, "channeldb_write_channeldb", write_channeldb_evt, NULL, 1000, 0);
-	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
-	{
-		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	if (loop.terminating)
-		write_channeldb();
-	freecfg(&test);
-	freecfg(&cfg);
-	SavePersistentLong(modinfo, channeldb_next_event);
-	return MOD_SUCCESS;
-}
-
-void channeldb_moddata_free(ModData *md)
-{
-	if (md->i)
-		md->i = 0;
-}
-
-void setcfg(struct cfgstruct *cfg)
-{
-	// Default: data/channel.db
-	safe_strdup(cfg->database, "channel.db");
-	convert_to_absolute_path(&cfg->database, PERMDATADIR);
-}
-
-void freecfg(struct cfgstruct *cfg)
-{
-	safe_free(cfg->database);
-	safe_free(cfg->db_secret);
-}
-
-int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	// We are only interested in set::channeldb::database
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "channeldb"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->value)
-		{
-			config_error("%s:%i: blank set::channeldb::%s without value", cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		} else
-		if (!strcmp(cep->name, "database"))
-		{
-			convert_to_absolute_path(&cep->value, PERMDATADIR);
-			safe_strdup(test.database, cep->value);
-		} else
-		if (!strcmp(cep->name, "db-secret"))
-		{
-			const char *err;
-			if ((err = unrealdb_test_secret(cep->value)))
-			{
-				config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
-				errors++;
-				continue;
-			}
-			safe_strdup(test.db_secret, cep->value);
-		} else
-		{
-			config_error("%s:%i: unknown directive set::channeldb::%s", cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int channeldb_config_posttest(int *errs)
-{
-	int errors = 0;
-	char *errstr;
-
-	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
-	{
-		config_error("[channeldb] %s", errstr);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	// We are only interested in set::channeldb::database
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "channeldb"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "database"))
-			safe_strdup(cfg.database, cep->value);
-		else if (!strcmp(cep->name, "db-secret"))
-			safe_strdup(cfg.db_secret, cep->value);
-	}
-	return 1;
-}
-
-EVENT(write_channeldb_evt)
-{
-	if (channeldb_next_event > TStime())
-		return;
-	channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY;
-	write_channeldb();
-}
-
-int write_channeldb(void)
-{
-	char tmpfname[512];
-	UnrealDB *db;
-	Channel *channel;
-	int cnt = 0;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	// Write to a tempfile first, then rename it if everything succeeded
-	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
-	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
-	if (!db)
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-	W_SAFE(unrealdb_write_int32(db, channeldb_version));
-
-	/* First, count +P channels and write the count to the database */
-	for (channel = channels; channel; channel=channel->nextch)
-		if (has_channel_mode(channel, 'P'))
-			cnt++;
-	W_SAFE(unrealdb_write_int64(db, cnt));
-
-	for (channel = channels; channel; channel=channel->nextch)
-	{
-		/* We only care about +P (persistent) channels */
-		if (has_channel_mode(channel, 'P'))
-		{
-			if (!write_channel_entry(db, tmpfname, channel))
-				return 0;
-		}
-	}
-
-	// Everything seems to have gone well, attempt to close and rename the tempfile
-	if (!unrealdb_close(db))
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-#ifdef _WIN32
-	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
-	unlink(cfg.database);
-#endif
-	if (rename(tmpfname, cfg.database) < 0)
-	{
-		config_error("[channeldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
-		return 0;
-	}
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	config_status("[channeldb] Benchmark: SAVE DB: %ld microseconds",
-		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
-#endif
-	return 1;
-}
-
-int write_listmode(UnrealDB *db, const char *tmpfname, Ban *lst)
-{
-	Ban *l;
-	int cnt = 0;
-
-	/* First count and write the list count */
-	for (l = lst; l; l = l->next)
-		cnt++;
-	W_SAFE(unrealdb_write_int32(db, cnt));
-
-	for (l = lst; l; l = l->next)
-	{
-		/* The entry, setby, seton */
-		W_SAFE(unrealdb_write_str(db, l->banstr));
-		W_SAFE(unrealdb_write_str(db, l->who));
-		W_SAFE(unrealdb_write_int64(db, l->when));
-	}
-	return 1;
-}
-
-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->name));
-	/* Channel creation time */
-	W_SAFE(unrealdb_write_int64(db, channel->creationtime));
-	/* Topic (topic, setby, seton) */
-	W_SAFE(unrealdb_write_str(db, channel->topic));
-	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, 1);
-	W_SAFE(unrealdb_write_str(db, modebuf));
-	W_SAFE(unrealdb_write_str(db, parabuf));
-	/* Mode lock */
-	W_SAFE(unrealdb_write_str(db, channel->mode_lock));
-	/* List modes (bans, exempts, invex) */
-	if (!write_listmode(db, tmpfname, channel->banlist))
-		return 0;
-	if (!write_listmode(db, tmpfname, channel->exlist))
-		return 0;
-	if (!write_listmode(db, tmpfname, channel->invexlist))
-		return 0;
-	W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_END));
-	return 1;
-}
-
-#define R_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
-			if (e) \
-			{ \
-				safe_free(e->banstr); \
-				safe_free(e->who); \
-				safe_free(e); \
-			} \
-			return 0; \
-		} \
-	} while(0)
-
-int read_listmode(UnrealDB *db, Ban **lst)
-{
-	uint32_t total;
-	uint64_t when;
-	int i;
-	Ban *e = NULL;
-
-	R_SAFE(unrealdb_read_int32(db, &total));
-
-	for (i = 0; i < total; i++)
-	{
-		const char *str;
-		e = safe_alloc(sizeof(Ban));
-		R_SAFE(unrealdb_read_str(db, &e->banstr));
-		R_SAFE(unrealdb_read_str(db, &e->who));
-		R_SAFE(unrealdb_read_int64(db, &when));
-		str = clean_ban_mask(e->banstr, MODE_ADD, &me, 0);
-		if (str == NULL)
-		{
-			/* Skip this item */
-			config_warn("[channeldb] listmode skipped (no longer valid?): %s", e->banstr);
-			safe_free(e->banstr);
-			safe_free(e->who);
-			safe_free(e);
-			continue;
-		}
-		safe_strdup(e->banstr, str);
-
-		if (ban_exists(*lst, e->banstr))
-		{
-			/* Free again - duplicate item */
-			safe_free(e->banstr);
-			safe_free(e->who);
-			safe_free(e);
-		} else {
-			/* Add to list */
-			e->when = when;
-			e->next = *lst;
-			*lst = e;
-		}
-	}
-
-	return 1;
-}
-#undef R_SAFE
-
-#define FreeChannelEntry() \
- 	do { \
-		/* Some of these might be NULL */ \
-		safe_free(chname); \
-		safe_free(topic); \
-		safe_free(topic_nick); \
-		safe_free(modes1); \
-		safe_free(modes2); \
-		safe_free(mode_lock); \
-	} while(0)
-
-#define R_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
-			unrealdb_close(db); \
-			FreeChannelEntry(); \
-			return 0; \
-		} \
-	} while(0)
-
-int read_channeldb(void)
-{
-	UnrealDB *db;
-	uint32_t version;
-	int added = 0;
-	int i;
-	uint64_t count = 0;
-	uint32_t magic;
-	// Variables for the channels
-	// Some of them need to be declared and NULL initialised early due to the macro FreeChannelEntry() being used by R_SAFE() on error
-	char *chname = NULL;
-	uint64_t creationtime = 0;
-	char *topic = NULL;
-	char *topic_nick = NULL;
-	uint64_t topic_time = 0;
-	char *modes1 = NULL;
-	char *modes2 = NULL;
-	char *mode_lock = NULL;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
-	if (!db)
-	{
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
-		{
-			/* Database does not exist. Could be first boot */
-			config_warn("[channeldb] No database present at '%s', will start a new one", cfg.database);
-			return 1;
-		} else
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
-		{
-			/* Re-open as unencrypted */
-			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
-			if (!db)
-			{
-				/* This should actually never happen, unless some weird I/O error */
-				config_warn("[channeldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
-				return 0;
-			}
-		} else
-		{
-			config_warn("[channeldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
-			return 0;
-		}
-	}
-
-	R_SAFE(unrealdb_read_int32(db, &version));
-	if (version > channeldb_version)
-	{
-		config_warn("[channeldb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, channeldb_version, version);
-		unrealdb_close(db);
-		return 0;
-	}
-
-	R_SAFE(unrealdb_read_int64(db, &count));
-
-	for (i=1; i <= count; i++)
-	{
-		// Variables
-		chname = NULL;
-		creationtime = 0;
-		topic = NULL;
-		topic_nick = NULL;
-		topic_time = 0;
-		modes1 = NULL;
-		modes2 = NULL;
-		mode_lock = NULL;
-		
-		Channel *channel;
-		R_SAFE(unrealdb_read_int32(db, &magic));
-		if (magic != MAGIC_CHANNEL_START)
-		{
-			config_error("[channeldb] Corrupt database (%s) - channel magic start is 0x%x. Further reading aborted.", cfg.database, magic);
-			break;
-		}
-		R_SAFE(unrealdb_read_str(db, &chname));
-		R_SAFE(unrealdb_read_int64(db, &creationtime));
-		R_SAFE(unrealdb_read_str(db, &topic));
-		R_SAFE(unrealdb_read_str(db, &topic_nick));
-		R_SAFE(unrealdb_read_int64(db, &topic_time));
-		R_SAFE(unrealdb_read_str(db, &modes1));
-		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 = 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;
-		safe_strdup(channel->mode_lock, mode_lock);
-		set_channel_mode(channel, NULL, modes1, modes2);
-		R_SAFE(read_listmode(db, &channel->banlist));
-		R_SAFE(read_listmode(db, &channel->exlist));
-		R_SAFE(read_listmode(db, &channel->invexlist));
-		R_SAFE(unrealdb_read_int32(db, &magic));
-		FreeChannelEntry();
-		added++;
-		if (magic != MAGIC_CHANNEL_END)
-		{
-			config_error("[channeldb] Corrupt database (%s) - channel magic end is 0x%x. Further reading aborted.", cfg.database, magic);
-			break;
-		}
-	}
-
-	unrealdb_close(db);
-
-	if (added)
-		config_status("[channeldb] Added %d persistent channels (+P)", added);
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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
diff --git a/src/modules/charsys.c b/src/modules/charsys.c
@@ -1,1295 +0,0 @@
-/*
- * Unreal Internet Relay Chat Daemon, src/charsys.c
- * (C) Copyright 2005-2017 Bram Matthys and The UnrealIRCd Team.
- *
- * Character system: This subsystem deals with finding out wheter a
- * character should be allowed or not in nicks (nicks only for now).
- *
- * 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"
-
-#ifndef ARRAY_SIZEOF
- #define ARRAY_SIZEOF(x) (sizeof((x))/sizeof((x)[0]))
-#endif
-
-ModuleHeader MOD_HEADER
-= {
-	"charsys",	/* Name of module */
-	"5.0", /* Version */
-	"Character System (set::allowed-nickchars)", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* NOTE: it is guaranteed that char is unsigned by compiling options
- *       (-funsigned-char @ gcc, /J @ MSVC)
- * NOTE2: Original credit for supplying the correct chinese
- *        coderanges goes to: RexHsu, Mr.WebBar and Xuefer
- */
-
-/** Our multibyte structure */
-typedef struct MBList MBList;
-struct MBList
-{
-	MBList *next;
-	char s1, e1, s2, e2;
-};
-MBList *mblist = NULL, *mblist_tail = NULL;
-
-/* Use this to prevent mixing of certain combinations
- * (such as GBK & high-ascii, etc)
- */
-static int langav = 0;
-char langsinuse[4096];
-
-/* bitmasks: */
-#define LANGAV_ASCII			0x000001 /* 8 bit ascii */
-#define LANGAV_LATIN1			0x000002 /* latin1 (western europe) */
-#define LANGAV_LATIN2			0x000004 /* latin2 (eastern europe, eg: polish) */
-#define LANGAV_ISO8859_7		0x000008 /* greek */
-#define LANGAV_ISO8859_8I		0x000010 /* hebrew */
-#define LANGAV_ISO8859_9		0x000020 /* turkish */
-#define LANGAV_W1250			0x000040 /* windows-1250 (eg: polish-w1250) */
-#define LANGAV_W1251			0x000080 /* windows-1251 (eg: russian) */
-#define LANGAV_LATIN2W1250		0x000100 /* Compatible with both latin2 AND windows-1250 (eg: hungarian) */
-#define LANGAV_ISO8859_6		0x000200 /* arabic */
-#define LANGAV_GBK			0x001000 /* (Chinese) GBK encoding */
-#define LANGAV_UTF8			0x002000 /* any UTF8 encoding */
-#define LANGAV_LATIN_UTF8		0x004000 /* UTF8: latin script */
-#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
-{
-	char *directive;
-	char *code;
-	int setflags;
-};
-
-/* MUST be alphabetized (first column) */
-static LangList langlist[] = {
-	{ "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 },
-	{ "catalan-utf8", "cat-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "chinese",      "chi-j,chi-s,chi-t", LANGAV_GBK },
-	{ "chinese-ja",   "chi-j", LANGAV_GBK },
-	{ "chinese-simp", "chi-s", LANGAV_GBK },
-	{ "chinese-trad", "chi-t", LANGAV_GBK },
-	{ "cyrillic-utf8", "blr-utf8,rus-utf8,ukr-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
-	{ "czech",        "cze-m", LANGAV_ASCII|LANGAV_W1250 },
-	{ "czech-utf8",   "cze-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "danish",       "dan", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "danish-utf8",  "dan-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "dutch",        "dut", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "dutch-utf8",   "dut-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "estonian-utf8","est-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "french",       "fre", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "french-utf8",  "fre-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "gbk",          "chi-s,chi-t,chi-j", LANGAV_GBK },
-	{ "german",       "ger", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "german-utf8",  "ger-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "greek",        "gre", LANGAV_ASCII|LANGAV_ISO8859_7 },
-	{ "greek-utf8",   "gre-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_GREEK_UTF8 },
-	{ "hebrew",       "heb", LANGAV_ASCII|LANGAV_ISO8859_8I },
-	{ "hebrew-utf8",  "heb-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_HEBREW_UTF8 },
-	{ "hungarian",    "hun", LANGAV_ASCII|LANGAV_LATIN2W1250 },
-	{ "hungarian-utf8","hun-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "icelandic",    "ice", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "icelandic-utf8","ice-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "italian",      "ita", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "italian-utf8", "ita-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "latin-utf8",   "cat-utf8,cze-utf8,dan-utf8,dut-utf8,fre-utf8,ger-utf8,hun-utf8,ice-utf8,ita-utf8,pol-utf8,rum-utf8,slo-utf8,spa-utf8,swe-utf8,tur-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "latin1",       "cat,dut,fre,ger,ita,spa,swe", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "latin2",       "hun,pol,rum", LANGAV_ASCII|LANGAV_LATIN2 },
-	{ "latvian-utf8", "lav-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "lithuanian-utf8","lit-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "polish",       "pol", LANGAV_ASCII|LANGAV_LATIN2 },
-	{ "polish-utf8",  "pol-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "polish-w1250", "pol-m", LANGAV_ASCII|LANGAV_W1250 },
-	{ "romanian",     "rum", LANGAV_ASCII|LANGAV_LATIN2W1250 },
-	{ "romanian-utf8","rum-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "russian-utf8", "rus-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
-	{ "russian-w1251","rus", LANGAV_ASCII|LANGAV_W1251 },
-	{ "slovak",       "slo-m", LANGAV_ASCII|LANGAV_W1250 },
-	{ "slovak-utf8",  "slo-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "spanish",      "spa", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "spanish-utf8", "spa-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "swedish",      "swe", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "swedish-utf8", "swe-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "swiss-german", "swg", LANGAV_ASCII|LANGAV_LATIN1 },
-	{ "swiss-german-utf8", "swg-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "turkish",      "tur", LANGAV_ASCII|LANGAV_ISO8859_9 },
-	{ "turkish-utf8", "tur-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_LATIN_UTF8 },
-	{ "ukrainian-utf8", "ukr-utf8", LANGAV_ASCII|LANGAV_UTF8|LANGAV_CYRILLIC_UTF8 },
-	{ "ukrainian-w1251", "ukr", LANGAV_ASCII|LANGAV_W1251 },
-	{ "windows-1250", "cze-m,pol-m,rum,slo-m,hun",  LANGAV_ASCII|LANGAV_W1250 },
-	{ "windows-1251", "rus,ukr,blr", LANGAV_ASCII|LANGAV_W1251 },
-	{ NULL, NULL, 0 }
-};
-
-/* For temporary use during config_run */
-typedef struct ILangList ILangList;
-struct ILangList
-{
-	ILangList *prev, *next;
-	char *name;
-};
-ILangList *ilanglist = NULL;
-
-/* These characters are ALWAYS disallowed... from remote, in
- * multibyte, etc.. even though this might mean a certain
- * (legit) character cannot be used (eg: in chinese GBK).
- * - ! (nick!user seperator)
- * - prefix chars: +, %, @, &, ~
- * - channel chars: #
- * - scary chars: $, :, ', ", ?, *, ',', '.'
- * NOTE: the caller should also check for ascii <= 32.
- * [CHANGING THIS WILL CAUSE SECURITY/SYNCH PROBLEMS AND WILL
- *  VIOLATE YOUR ""RIGHT"" ON SUPPORT IMMEDIATELY]
- */
-const char *illegalnickchars = "!+%@&~#$:'\"?*,.";
-
-/* Forward declarations */
-int _do_nick_name(char *nick);
-int _do_remote_nick_name(char *nick);
-static int do_nick_name_multibyte(char *nick);
-static int do_nick_name_standard(char *nick);
-void charsys_reset(void);
-void charsys_reset_pretest(void);
-void charsys_finish(void);
-void charsys_addmultibyterange(char s1, char e1, char s2, char e2);
-void charsys_addallowed(char *s);
-int charsys_test_language(char *name);
-void charsys_add_language(char *name);
-static void charsys_doadd_language(char *name);
-int charsys_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int charsys_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-int charsys_config_posttest(int *errs);
-char *_charsys_get_current_languages(void);
-
-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);
-	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);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, charsys_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, charsys_config_run);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	charsys_finish();
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int charsys_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::allowed-nickchars... */
-	if (!ce || !ce->name || strcmp(ce->name, "allowed-nickchars"))
-		return 0;
-
-	if (ce->value)
-	{
-		config_error("%s:%i: set::allowed-nickchars: please use 'allowed-nickchars { name; };' "
-					 "and not 'allowed-nickchars name;'",
-					 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->items; cep; cep=cep->next)
-	{
-		if (!charsys_test_language(cep->name))
-		{
-			config_error("%s:%i: set::allowed-nickchars: Unknown (sub)language '%s'",
-				ce->file->filename, ce->line_number, cep->name);
-			errors++;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int charsys_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	if (type != CONFIG_SET)
-		return 0;
-
-	/* We are only interrested in set::allowed-nickchars... */
-	if (!ce || !ce->name || strcmp(ce->name, "allowed-nickchars"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-		charsys_add_language(cep->name);
-
-	return 1;
-}
-
-/** Check if the specified charsets during the TESTING phase can be
- * premitted without getting into problems.
- * RETURNS: -1 in case of failure, 1 if ok
- */
-int charsys_config_posttest(int *errs)
-{
-	int errors = 0;
-	int x=0;
-
-	if ((langav & LANGAV_ASCII) && (langav & LANGAV_GBK))
-	{
-		config_error("ERROR: set::allowed-nickchars specifies incorrect combination "
-		             "of languages: high-ascii languages (such as german, french, etc) "
-		             "cannot be mixed with chinese/..");
-		return -1;
-	}
-	if (langav & LANGAV_LATIN_UTF8)
-		x++;
-	if (langav & LANGAV_GREEK_UTF8)
-		x++;
-	if (langav & LANGAV_CYRILLIC_UTF8)
-		x++;
-	if (langav & LANGAV_HEBREW_UTF8)
-		x++;
-	if (langav & LANGAV_LATIN1)
-		x++;
-	if (langav & LANGAV_LATIN2)
-		x++;
-	if (langav & LANGAV_ISO8859_6)
-		x++;
-	if (langav & LANGAV_ISO8859_7)
-		x++;
-	if (langav & LANGAV_ISO8859_9)
-		x++;
-	if (langav & LANGAV_W1250)
-		x++;
-	if (langav & LANGAV_W1251)
-		x++;
-	if ((langav & LANGAV_LATIN2W1250) && !(langav & LANGAV_LATIN2) && !(langav & LANGAV_W1250))
-	    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");
-			errors++;
-		}
-		if (langav & LANGAV_GREEK_UTF8)
-		{
-			config_error("ERROR: set::allowed-nickchars: you cannot combine 'greek-utf8' with any other character set");
-			errors++;
-		}
-		if (langav & LANGAV_CYRILLIC_UTF8)
-		{
-			config_error("ERROR: set::allowed-nickchars: you cannot combine 'cyrillic-utf8' with any other character set");
-			errors++;
-		}
-		if (langav & LANGAV_HEBREW_UTF8)
-		{
-			config_error("ERROR: set::allowed-nickchars: you cannot combine 'hebrew-utf8' with any other character set");
-			errors++;
-		}
-		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;
-	return errors ? -1 : 1;
-}
-
-/** Called on boot and just before config run */
-void charsys_reset(void)
-{
-	int i;
-	MBList *m, *m_next;
-
-	/* First, reset everything */
-	for (i=0; i < 256; i++)
-		char_atribs[i] &= ~ALLOWN;
-	for (m=mblist; m; m=m_next)
-	{
-		m_next = m->next;
-		safe_free(m);
-	}
-	mblist=mblist_tail=NULL;
-	/* Then add the default which will always be allowed */
-	charsys_addallowed("0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyzy{|}");
-	langav = 0;
-	langsinuse[0] = '\0';
-#ifdef DEBUGMODE
-	if (ilanglist)
-		abort();
-#endif
-}
-
-void charsys_reset_pretest(void)
-{
-	langav = 0;
-	non_utf8_nick_chars_in_use = 0;
-}
-
-static inline void ilang_swap(ILangList *one, ILangList *two)
-{
-	char *tmp = one->name;
-	one->name = two->name;
-	two->name = tmp;
-}
-
-static void ilang_sort(void)
-{
-	ILangList *outer, *inner;
-
-	/* Selection sort -- perhaps optimize to qsort/whatever if
-     * possible? ;)
-     */
-	for (outer=ilanglist; outer; outer=outer->next)
-	{
-		for (inner=outer->next; inner; inner=inner->next)
-		{
-			if (strcmp(outer->name, inner->name) > 0)
-				ilang_swap(outer, inner);
-		}
-	}
-}
-
-void charsys_finish(void)
-{
-	ILangList *e, *e_next;
-
-	/* Sort alphabetically */
-	ilang_sort();
-
-	/* [note: this can be optimized] */
-	langsinuse[0] = '\0';
-	for (e=ilanglist; e; e=e->next)
-	{
-		strlcat(langsinuse, e->name, sizeof(langsinuse));
-		if (e->next)
-			strlcat(langsinuse, ",", sizeof(langsinuse));
-	}
-
-	/* Free everything */
-	for (e=ilanglist; e; e=e_next)
-	{
-		e_next=e->next;
-		safe_free(e->name);
-		safe_free(e);
-	}
-	ilanglist = NULL;
-#ifdef DEBUGMODE
-	if (strlen(langsinuse) > 490)
-		abort();
-#endif
-	charsys_check_for_changes();
-}
-
-/** Add a character range to the multibyte list.
- * Eg: charsys_addmultibyterange(0xaa, 0xbb, 0x00, 0xff) for 0xaa00-0xbbff.
- * @param s1 Start of highest byte
- * @param e1 End of highest byte
- * @param s2 Start of lowest byte
- * @param e2 End of lowest byte
- */
-void charsys_addmultibyterange(char s1, char e1, char s2, char e2)
-{
-MBList *m = safe_alloc(sizeof(MBList));
-
-	m->s1 = s1;
-	m->e1 = e1;
-	m->s2 = s2;
-	m->e2 = e2;
-
-	if (mblist_tail)
-		mblist_tail->next = m;
-	else
-		mblist = m;
-	mblist_tail = m;
-}
-
-/** Adds all characters in the specified string to the allowed list. */
-void charsys_addallowed(char *s)
-{
-	for (; *s; s++)
-	{
-		if ((*s <= 32) || strchr(illegalnickchars, *s))
-		{
-			config_error("INTERNAL ERROR: charsys_addallowed() called for illegal characters: %s", s);
-#ifdef DEBUGMODE
-			abort();
-#endif
-		}
-		char_atribs[(unsigned char)*s] |= ALLOWN;
-	}
-}
-
-void charsys_addallowed_range(unsigned char from, unsigned char to)
-{
-	unsigned char i;
-
-	for (i = from; i != to; i++)
-		char_atribs[i] |= ALLOWN;
-}
-
-int _do_nick_name(char *nick)
-{
-	if (mblist)
-		return do_nick_name_multibyte(nick);
-	else
-		return do_nick_name_standard(nick);
-}
-
-static int do_nick_name_standard(char *nick)
-{
-	int len;
-	char *ch;
-
-	if ((*nick == '-') || isdigit(*nick))
-		return 0;
-
-	for (ch=nick,len=0; *ch && len <= NICKLEN; ch++, len++)
-		if (!isvalid(*ch))
-			return 0; /* reject the full nick */
-	*ch = '\0';
-	return len;
-}
-
-static int isvalidmbyte(unsigned char c1, unsigned char c2)
-{
-	MBList *m;
-
-	for (m=mblist; m; m=m->next)
-	{
-		if ((c1 >= m->s1) && (c1 <= m->e1) &&
-		    (c2 >= m->s2) && (c2 <= m->e2))
-		    return 1;
-	}
-	return 0;
-}
-
-/* hmmm.. there must be some problems with multibyte &
- * other high ascii characters I think (such as german etc).
- * Not sure if this can be solved? I don't think so... -- Syzop.
- */
-static int do_nick_name_multibyte(char *nick)
-{
-	int len;
-	char *ch;
-	int firstmbchar = 0;
-
-	if ((*nick == '-') || isdigit(*nick))
-		return 0;
-
-	for (ch=nick,len=0; *ch && len <= NICKLEN; ch++, len++)
-	{
-		/* Some characters are ALWAYS illegal, so they have to be disallowed here */
-		if ((*ch <= 32) || strchr(illegalnickchars, *ch))
-			return 0;
-		if (firstmbchar)
-		{
-			if (!isvalidmbyte(ch[-1], *ch))
-				return 0;
-			firstmbchar = 0;
-		} else if ((*ch) & 0x80)
-			firstmbchar = 1;
-		else if (!isvalid(*ch))
-			return 0;
-	}
-	if (firstmbchar)
-	{
-		ch--;
-		len--;
-	}
-	*ch = '\0';
-	return len;
-}
-
-/** Does some very basic checking on remote nickname.
- * It's only purpose is not to cause the whole network
- * to fall down in pieces, that's all. Display problems
- * are not really handled here. They are assumed to have been
- * checked by PROTOCTL NICKCHARS= -- Syzop.
- */
-int _do_remote_nick_name(char *nick)
-{
-	char *c;
-
-	/* Don't allow nicks to start with a digit, ever. */
-	if ((*nick == '-') || isdigit(*nick))
-		return 0;
-
-	/* Now the other, more relaxed checks.. */
-	for (c=nick; *c; c++)
-		if ((*c <= 32) || strchr(illegalnickchars, *c))
-			return 0;
-
-	return (c - nick);
-}
-
-static LangList *charsys_find_language(char *name)
-{
-	int start = 0;
-	int stop = ARRAY_SIZEOF(langlist)-1;
-	int mid;
-
-	while (start <= stop)
-	{
-		mid = (start+stop)/2;
-		if (!langlist[mid].directive || smycmp(name, langlist[mid].directive) < 0)
-			stop = mid-1;
-		else if (strcmp(name, langlist[mid].directive) == 0)
-			return &langlist[mid];
-		else
-			start = mid+1;
-	}
-	return NULL;
-}
-
-static LangList *charsys_find_language_code(char *code)
-{
-	int i;
-	for (i = 0; langlist[i].code; i++)
-		if (!strcasecmp(langlist[i].code, code))
-			return &langlist[i];
-	return NULL;
-}
-
-/** Check if language is available. */
-int charsys_test_language(char *name)
-{
-	LangList *l = charsys_find_language(name);
-
-	if (l)
-	{
-		langav |= l->setflags;
-		if (!(l->setflags & LANGAV_UTF8))
-			non_utf8_nick_chars_in_use = 1;
-		return 1;
-	}
-	if (!strcmp(name, "euro-west"))
-	{
-		config_error("set::allowed-nickchars: ERROR: 'euro-west' got renamed to 'latin1'");
-		return 0;
-	}
-	return 0;
-}
-
-static void charsys_doadd_language(char *name)
-{
-LangList *l;
-ILangList *li;
-int found;
-char tmp[512], *lang, *p;
-
-	l = charsys_find_language(name);
-	if (!l)
-	{
-#ifdef DEBUGMODE
-		abort();
-#endif
-		return;
-	}
-
-	strlcpy(tmp, l->code, sizeof(tmp));
-	for (lang = strtoken(&p, tmp, ","); lang; lang = strtoken(&p, NULL, ","))
-	{
-		/* Check if present... */
-		found=0;
-		for (li=ilanglist; li; li=li->next)
-			if (!strcmp(li->name, lang))
-			{
-				found = 1;
-				break;
-			}
-		if (!found)
-		{
-			/* Add... */
-			li = safe_alloc(sizeof(ILangList));
-			safe_strdup(li->name, lang);
-			AddListItem(li, ilanglist);
-		}
-	}
-}
-
-void charsys_add_language(char *name)
-{
-	char latin1=0, latin2=0, w1250=0, w1251=0, chinese=0;
-	char latin_utf8=0, cyrillic_utf8=0;
-
-	/** Note: there could well be some characters missing in the lists below.
-	 *        While I've seen other altnernatives that just allow pretty much
-	 *        every accent that exists even for dutch (where we rarely use
-	 *        accents except for like 3 types), I rather prefer to use a bit more
-	 *        reasonable aproach ;). That said, anyone is welcome to make
-	 *        suggestions about characters that should be added (or removed)
-	 *        of course. -- Syzop
-	 */
-
-	/* Add our language to our list */
-	charsys_doadd_language(name);
-
-	/* GROUPS */
-	if (!strcmp(name, "latin-utf8"))
-		latin_utf8 = 1;
-	else if (!strcmp(name, "cyrillic-utf8"))
-		cyrillic_utf8 = 1;
-	else if (!strcmp(name, "latin1"))
-		latin1 = 1;
-	else if (!strcmp(name, "latin2"))
-		latin2 = 1;
-	else if (!strcmp(name, "windows-1250"))
-		w1250 = 1;
-	else if (!strcmp(name, "windows-1251"))
-		w1251 = 1;
-	else if (!strcmp(name, "chinese") || !strcmp(name, "gbk"))
-		chinese = 1;
-
-	/* INDIVIDUAL CHARSETS */
-
-	/* [LATIN1] and [LATIN-UTF8] */
-	if (latin1 || !strcmp(name, "german"))
-	{
-		/* a", A", o", O", u", U" and es-zett */
-		charsys_addallowed("äÄöÖüÜß");
-	}
-	if (latin_utf8 || !strcmp(name, "german-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9f, 0x9f);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-	}
-	if (latin1 || !strcmp(name, "swiss-german"))
-	{
-		/* a", A", o", O", u", U"  */
-		charsys_addallowed("äÄöÖüÜ");
-	}
-	if (latin_utf8 || !strcmp(name, "swiss-german-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-	}
-	if (latin1 || !strcmp(name, "dutch"))
-	{
-		/* Ok, even though I'm Dutch myself, I've trouble getting
-		 * a proper list of this ;). I think I got them all now, but
-		 * I did not include "borrow-words" like words we use in Dutch
-		 * that are literal French. So if you really want to use them all,
-		 * I suggest you to use just latin1 :P.
-		 */
-		/* e', e", o", i", u", e`. */
-		charsys_addallowed("éëöïüè");
-	}
-	if (latin_utf8 || !strcmp(name, "dutch-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa8, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xab, 0xab);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xaf, 0xaf);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-	}
-	if (latin1 || !strcmp(name, "danish"))
-	{
-		/* supplied by klaus:
-		 * <ae>, <AE>, ao, Ao, o/, O/ */
-		charsys_addallowed("æÆåÅøØ");
-	}
-	if (latin_utf8 || !strcmp(name, "danish-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x85, 0x86);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x98, 0x98);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa5, 0xa6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb8, 0xb8);
-	}
-	if (latin1 || !strcmp(name, "french"))
-	{
-		/* A`, A^, a`, a^, weird-C, weird-c, E`, E', E^, E", e`, e', e^, e",
-		 * I^, I", i^, i", O^, o^, U`, U^, U", u`, u", u`, y" [not in that order, sry]
-		 * Hmm.. there might be more, but I'm not sure how common they are
-		 * and I don't think they are always displayed correctly (?).
-		 */
-		charsys_addallowed("ÀÂàâÇçÈÉÊËèéêëÎÏîïÔôÙÛÜùûüÿ");
-	}
-	if (latin_utf8 || !strcmp(name, "french-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x80, 0x80);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x82, 0x82);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x87, 0x8b);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8e, 0x8f);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x94, 0x94);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x99, 0x99);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9b, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa0, 0xa0);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa2, 0xa2);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa7, 0xab);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xae, 0xaf);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb4, 0xb4);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb9, 0xb9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbb, 0xbc);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbf, 0xbf);
-	}
-	if (latin1 || !strcmp(name, "spanish"))
-	{
-		/* a', A', e', E', i', I', o', O', u', U', u", U", n~, N~ */
-		charsys_addallowed("áÁéÉíÍóÓúÚüÜñÑ");
-	}
-	if (latin_utf8 || !strcmp(name, "spanish-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x91, 0x91);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb1, 0xb1);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-	}
-	if (latin1 || !strcmp(name, "italian"))
-	{
-		/* A`, E`, E', I`, I', O`, O', U`, U', a`, e`, e', i`, i', o`, o', u`, u' */
-		charsys_addallowed("ÀÈÉÌÍÒÓÙÚàèéìíòóùú");
-	}
-	if (latin_utf8 || !strcmp(name, "italian-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x80, 0x80);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x88, 0x89);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8c, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x92, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x99, 0x9a);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa0, 0xa0);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa8, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xac, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb2, 0xb3);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb9, 0xba);
-	}
-	if (latin1 || !strcmp(name, "catalan"))
-	{
-		/* supplied by Trocotronic */
-		/* a`, A`, e`, weird-c, weird-C, E`, e', E', i', I', o`, O`, o', O', u', U', i", I", u", U", weird-dot */
-		charsys_addallowed("àÀçÇèÈéÉíÍòÒóÓúÚïÏüÜ");
-	}
-	if (latin_utf8 || !strcmp(name, "catalan-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x80, 0x80);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x87, 0x89);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8f, 0x8f);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x92, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa0, 0xa0);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa7, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xaf, 0xaf);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb2, 0xb3);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
-	}
-	if (latin1 || !strcmp(name, "swedish"))
-	{
-		/* supplied by Tank */
-		/* ao, Ao, a", A", o", O" */
-		charsys_addallowed("åÅäÄöÖ");
-	}
-	if (latin_utf8 || !strcmp(name, "swedish-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x85);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa5);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-	}
-	if (latin1 || !strcmp(name, "icelandic"))
-	{
-		/* supplied by Saevar */
-		charsys_addallowed("ÆæÖöÁáÍíÐðÚúÓóÝýÞþ");
-	}
-	if (latin_utf8 || !strcmp(name, "icelandic-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x86, 0x86);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x90, 0x90);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9d, 0x9e);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa6, 0xa6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb0, 0xb0);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbe);
-	}
-
-	/* [LATIN2] and rest of [LATIN-UTF8] */
-	/* actually hungarian is a special case, include it in both w1250 and latin2 ;p */
-	if (latin2 || w1250 || !strcmp(name, "hungarian"))
-	{
-		/* supplied by AngryWolf */
-		/* a', e', i', o', o", o~, u', u", u~, A', E', I', O', O", O~, U', U", U~ */
-		charsys_addallowed("áéíóöõúüûÁÉÍÓÖÕÚÜÛ");
-	}
-	if (latin_utf8 || !strcmp(name, "hungarian-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x90, 0x91);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xb0, 0xb1);
-	}
-	/* same is true for romanian: latin2 & w1250 compatible */
-	if (latin2 || w1250 || !strcmp(name, "romanian"))
-	{
-		/* With some help from crazytoon */
-		/* 'S,' 's,' 'A^' 'A<' 'I^' 'T,' 'a^' 'a<' 'i^' 't,' */
-		charsys_addallowed("ªºÂÃÎÞâãîþ");
-	}
-	if (latin_utf8 || !strcmp(name, "romanian-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x82, 0x82);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8e, 0x8e);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa2, 0xa2);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xae, 0xae);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x82, 0x83);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x9e, 0x9f);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa2, 0xa3);
-	}
-
-	if (latin2 || !strcmp(name, "polish"))
-	{
-		/* supplied by k4be */
-		charsys_addallowed("±æê³ñó¶¿¼¡ÆÊ£ÑÓ¦¯¬");
-	}
-	if (latin_utf8 || !strcmp(name, "polish-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x84, 0x87);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x98, 0x99);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x81, 0x84);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x9a, 0x9b);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xb9, 0xbc);
-	}
-	/* [windows 1250] */
-	if (w1250 || !strcmp(name, "polish-w1250"))
-	{
-		/* supplied by k4be */
-		charsys_addallowed("¹æê³ñ󜿟¥ÆÊ£ÑÓŒ¯");
-	}
-	if (w1250 || !strcmp(name, "czech-w1250"))
-	{
-		/* Syzop [probably incomplete] */
-		charsys_addallowed("ŠŽšžÁÈÉÌÍÏÒÓØÙÚÝáèéìíïòóøùúý");
-	}
-	if (latin_utf8 || !strcmp(name, "czech-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x93, 0x93);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9a, 0x9a);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9d, 0x9d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb3);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbd);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8f);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x9a, 0x9b);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x87, 0x88);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x98, 0x99);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa4, 0xa5);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xae, 0xaf);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
-	}
-	if (w1250 || !strcmp(name, "slovak-w1250"))
-	{
-		/* Syzop [probably incomplete] */
-		charsys_addallowed("ŠŽšž¼¾ÀÁÄÅÈÉÍÏàáäåèéíïòóôúý");
-	}
-	if (latin_utf8 || !strcmp(name, "slovak-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x81, 0x81);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x89, 0x89);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x8d, 0x8d);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa1, 0xa1);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa9, 0xa9);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xad, 0xad);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb3, 0xb4);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xba, 0xba);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbd, 0xbd);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8f);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xb9, 0xba);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xbd, 0xbe);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x88, 0x88);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x94, 0x95);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa4, 0xa5);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
-	}
-
-	/* [windows 1251] */
-	if (w1251 || !strcmp(name, "russian-w1251"))
-	{
-		/* supplied by Roman Parkin:
-		 * 128-159 and 223-254
-		 */
-		charsys_addallowed("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ¨¸");
-	}
-	if (cyrillic_utf8 || !strcmp(name, "russian-utf8"))
-	{
-		charsys_addmultibyterange(0xd0, 0xd0, 0x81, 0x81);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x90, 0xbf);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x8f);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x91, 0x91);
-	}
-
-	if (w1251 || !strcmp(name, "belarussian-w1251"))
-	{
-		/* supplied by Bock (Samets Anton) & ss:
-		 * 128-159, 161, 162, 178, 179 and 223-254
-		 * Corrected 01.11.2006 to more "correct" behavior by Bock
-		 */
-		charsys_addallowed("ÀÁÂÃÄŨÆÇ²ÉÊËÌÍÎÏÐÑÒÓ¡ÔÕÖרÛÜÝÞßàáâãä叿ç³éêëìíîïðñòó¢ôõö÷øûüýþÿ");
-	}
-	if (cyrillic_utf8 || !strcmp(name, "belarussian-utf8"))
-	{
-		charsys_addmultibyterange(0xd0, 0xd0, 0x81, 0x81);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x86, 0x86);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x8e, 0x8e);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x90, 0x97);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x99, 0xa8);
-		charsys_addmultibyterange(0xd0, 0xd0, 0xab, 0xb7);
-		charsys_addmultibyterange(0xd0, 0xd0, 0xb9, 0xbf);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x88);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x8b, 0x8f);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x91, 0x91);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x96, 0x96);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x9e, 0x9e);
-	}
-
-	if (w1251 || !strcmp(name, "ukrainian-w1251"))
-	{
-		/* supplied by Anton Samets & ss:
-		 * 128-159, 170, 175, 178, 179, 186, 191 and 223-254
-		 * Corrected 01.11.2006 to more "correct" behavior by core
-		 */
-		charsys_addallowed("ÀÁÂÃ¥ÄŪÆÇȲ¯ÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÜÞßàáâã´äåºæç賿éêëìíîïðñòóôõö÷øùüþÿ");
-	}
-	if (cyrillic_utf8 || !strcmp(name, "ukrainian-utf8"))
-	{
-		charsys_addmultibyterange(0xd0, 0xd0, 0x84, 0x84);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x86, 0x87);
-		charsys_addmultibyterange(0xd0, 0xd0, 0x90, 0xa9);
-		charsys_addmultibyterange(0xd0, 0xd0, 0xac, 0xac);
-		charsys_addmultibyterange(0xd0, 0xd0, 0xae, 0xbf);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x89);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x8c, 0x8c);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x8e, 0x8f);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x94, 0x94);
-		charsys_addmultibyterange(0xd1, 0xd1, 0x96, 0x97);
-		charsys_addmultibyterange(0xd2, 0xd2, 0x90, 0x91);
-	}
-
-	/* [GREEK] */
-	if (!strcmp(name, "greek"))
-	{
-		/* supplied by GSF */
-		/* ranges from rfc1947 / iso 8859-7 */
-		charsys_addallowed("¶¸¹º¼¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóô");
-	}
-	if (!strcmp(name, "greek-utf8"))
-	{
-		charsys_addmultibyterange(0xce, 0xce, 0x86, 0x86);
-		charsys_addmultibyterange(0xce, 0xce, 0x88, 0x8a);
-		charsys_addmultibyterange(0xce, 0xce, 0x8c, 0x8c);
-		charsys_addmultibyterange(0xce, 0xce, 0x8e, 0xa1);
-		charsys_addmultibyterange(0xce, 0xce, 0xa3, 0xbf);
-		charsys_addmultibyterange(0xcf, 0xcf, 0x80, 0x84);
-	}
-
-	/* [TURKISH] */
-	if (!strcmp(name, "turkish"))
-	{
-		/* Supplied by Ayberk Yancatoral */
-		charsys_addallowed("öÖçÇþÞüÜðÐý");
-	}
-	if (!strcmp(name, "turkish-utf8"))
-	{
-		charsys_addmultibyterange(0xc3, 0xc3, 0x87, 0x87);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x96, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa7, 0xa7);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb6, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x9e, 0x9f);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xb1, 0xb1);
-		charsys_addmultibyterange(0xc5, 0xc5, 0x9e, 0x9f);
-	}
-
-	/* [HEBREW] */
-	if (!strcmp(name, "hebrew"))
-	{
-		/* Supplied by PHANTOm. */
-		/* 0xE0 - 0xFE */
-		charsys_addallowed("àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ");
-	}
-	if (!strcmp(name, "hebrew-utf8"))
-	{
-		/* Supplied by Lion-O */
-		charsys_addmultibyterange(0xd7, 0xd7, 0x90, 0xaa);
-	}
-
-	/* [CHINESE] */
-	if (chinese || !strcmp(name, "chinese-ja"))
-	{
-		charsys_addmultibyterange(0xa4, 0xa4, 0xa1, 0xf3); /* JIS_PIN */
-		charsys_addmultibyterange(0xa5, 0xa5, 0xa1, 0xf6); /* JIS_PIN */
-	}
-	if (chinese || !strcmp(name, "chinese-simp"))
-	{
-		charsys_addmultibyterange(0xb0, 0xd6, 0xa1, 0xfe); /* GBK/2 BC with GB2312 */
-		charsys_addmultibyterange(0xd7, 0xd7, 0xa1, 0xf9); /* GBK/2 BC with GB2312 */
-		charsys_addmultibyterange(0xd8, 0xf7, 0xa1, 0xfe); /* GBK/2 BC with GB2312 */
-	}
-	if (chinese || !strcmp(name, "chinese-trad"))
-	{
-		charsys_addmultibyterange(0x81, 0xa0, 0x40, 0x7e); /* GBK/3 - lower half */
-		charsys_addmultibyterange(0x81, 0xa0, 0x80, 0xfe); /* GBK/3 - upper half */
-		charsys_addmultibyterange(0xaa, 0xfe, 0x40, 0x7e); /* GBK/4 - lower half */
-		charsys_addmultibyterange(0xaa, 0xfe, 0x80, 0xa0); /* GBK/4 - upper half */
-	}
-
-	/* [LATVIAN] */
-	if (latin_utf8 || !strcmp(name, "latvian-utf8"))
-	{
-		/* A a, C c, E e, G g, I i, K k, Š š, U u, Ž ž */
-		charsys_addmultibyterange(0xc4, 0xc4, 0x80, 0x81);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x92, 0x93);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8d);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x92, 0x93);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xa2, 0xa3);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xaa, 0xab);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xb6, 0xb7);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xaa, 0xab);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xbd, 0xbe);
-	}
-
-	/* [ESTONIAN] */
-	if (latin_utf8 || !strcmp(name, "estonian-utf8"))
-	{
-		/* õ, ä, ö, ü,  Õ, Ä, Ö, Ü */
-		charsys_addmultibyterange(0xc3, 0xc3, 0xb5, 0xb6);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xa4, 0xa4);
-		charsys_addmultibyterange(0xc3, 0xc3, 0xbc, 0xbc);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x95, 0x96);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x84, 0x84);
-		charsys_addmultibyterange(0xc3, 0xc3, 0x9c, 0x9c);
-	}
-
-	/* [LITHUANIAN] */
-	if (latin_utf8 || !strcmp(name, "lithuanian-utf8"))
-	{
-		/* a, c, e, e, i, š, u, u, ž, A, C, E, E, I, Š, U, U, Ž */
-		charsys_addmultibyterange(0xc4, 0xc4, 0x84, 0x85);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x8c, 0x8d);
-		charsys_addmultibyterange(0xc4, 0xc4, 0x96, 0x99);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xae, 0xaf);
-		charsys_addmultibyterange(0xc4, 0xc4, 0xae, 0xaf);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xa0, 0xa1);
-		charsys_addmultibyterange(0xc5, 0xc5, 0xb2, 0xb3);
-		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 */
-char *charsys_displaychars(void)
-{
-#if 0
-	MBList *m;
-	unsigned char hibyte, lobyte;
-#endif
-	static char buf[512];
-	int n = 0;
-	int i, j;
-
-	// 		char_atribs[(unsigned char)*s] |= ALLOWN;
-	for (i = 0; i <= 255; i++)
-	{
-		if (char_atribs[i] & ALLOWN)
-			buf[n++] = i;
-		/* (no bounds checking: first 255 characters always fit a 512 byte buffer) */
-	}
-
-#if 0
-	for (m=mblist; m; m=m->next)
-	{
-		for (hibyte = m->s1; hibyte <= m->e1; hibyte++)
-		{
-			for (lobyte = m->s2; lobyte <= m->e2; lobyte++)
-			{
-				if (n >= sizeof(buf) - 3)
-					break; // break, or an attempt anyway
-				buf[n++] = hibyte;
-				buf[n++] = lobyte;
-			}
-		}
-	}
-#endif
-	/* above didn't work due to multiple overlapping ranges permitted.
-	 * try this instead (lazy).. this is only used in DEBUGMODE
-	 * via a command line option anyway:
-	 */
-	for (i=0; i <= 255; i++)
-	{
-		for (j=0; j <= 255; j++)
-		{
-			if (isvalidmbyte(i, j))
-			{
-				if (n >= sizeof(buf) - 3)
-					break; // break, or an attempt anyway
-				buf[n++] = i;
-				buf[n++] = j;
-			}
-		}
-	}
-
-	buf[n] = '\0'; /* there's always room for a NUL */
-
-	return buf;
-}
-
-char *charsys_group(int v)
-{
-	if (v & LANGAV_LATIN_UTF8)
-		return "Latin script";
-	if (v & LANGAV_CYRILLIC_UTF8)
-		return "Cyrillic script";
-	if (v & LANGAV_GREEK_UTF8)
-		return "Greek script";
-	if (v & LANGAV_HEBREW_UTF8)
-		return "Hebrew script";
-	if (v & LANGAV_ARABIC_UTF8)
-		return "Arabic script";
-
-	return "Other";
-}
-
-void charsys_dump_table(char *filter)
-{
-	int i = 0;
-
-	for (i = 0; langlist[i].directive; i++)
-	{
-		char *charset = langlist[i].directive;
-
-		if (!match_simple(filter, charset))
-			continue; /* skip */
-
-		charsys_reset();
-		charsys_add_language(charset);
-		charsys_finish();
-		printf("%s;%s;%s\n", charset, charsys_group(langlist[i].setflags), charsys_displaychars());
-	}
-}
-
-/** Get current languages (the 'langsinuse' variable) */
-char *_charsys_get_current_languages(void)
-{
-	return langsinuse;
-}
diff --git a/src/modules/chathistory.c b/src/modules/chathistory.c
@@ -1,403 +0,0 @@
-/* src/modules/chathistory.c - IRCv3 CHATHISTORY command.
- * (C) Copyright 2021 Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- *
- * This implements the "CHATHISTORY" command, the CAP and 005 token.
- * https://ircv3.net/specs/extensions/chathistory
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"chathistory",
-	"1.0",
-	"IRCv3 CHATHISTORY command",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Structs */
-typedef struct ChatHistoryTarget ChatHistoryTarget;
-struct ChatHistoryTarget {
-	ChatHistoryTarget *prev, *next;
-	char *datetime;
-	char *object;
-};
-
-/* Forward declarations */
-CMD_FUNC(cmd_chathistory);
-
-/* Global variables */
-long CAP_CHATHISTORY = 0L;
-
-#define CHATHISTORY_LIMIT 50
-
-MOD_INIT()
-{
-	ClientCapabilityInfo c;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	CommandAdd(modinfo->handle, "CHATHISTORY", cmd_chathistory, MAXPARA, CMD_USER);
-
-	memset(&c, 0, sizeof(c));
-	c.name = "draft/chathistory";
-	ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHATHISTORY);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	ISupportSetFmt(modinfo->handle, "CHATHISTORY", "%d", CHATHISTORY_LIMIT);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int chathistory_token(const char *str, char *token, char **store)
-{
-	char request[BUFSIZE];
-	char *p;
-
-	strlcpy(request, str, sizeof(request));
-
-	p = strchr(request, '=');
-	if (!p)
-		return 0;
-	*p = '\0'; // frag
-	if (!strcmp(request, token))
-	{
-		*p = '='; // restore
-		*store = strdup(p + 1); // can be \0
-		return 1;
-	}
-	*p = '='; // restore
-	return 0;
-}
-
-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 *m;
-	time_t ts;
-	char *datetime;
-	ChatHistoryTarget *e;
-
-	if (!r->log || !((m = find_mtag(r->log->mtags, "time"))) || !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))
-	{
-		mtags = safe_alloc(sizeof(MessageTag));
-		mtags->name = strdup("batch");
-		mtags->value = strdup(batchid);
-	}
-
-	sendto_one(client, mtags, ":%s CHATHISTORY TARGETS %s %s",
-		me.name, r->object, r->datetime);
-
-	if (mtags)
-		free_message_tags(mtags);
-}
-
-void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
-{
-	Membership *mp;
-	HistoryResult *r;
-	char batch[BATCHLEN+1];
-	int sent = 0;
-	ChatHistoryTarget *targets = NULL, *targets_next;
-
-	/* 1. Grab all information we need */
-
-	filter->cmd = HFC_BEFORE;
-	if (strcmp(filter->timestamp_a, filter->timestamp_b) < 0)
-	{
-		/* Swap if needed */
-		char *swap = filter->timestamp_a;
-		filter->timestamp_a = filter->timestamp_b;
-		filter->timestamp_b = swap;
-	}
-	filter->limit = 1;
-
-	for (mp = client->user->channel; mp; mp = mp->next)
-	{
-		Channel *channel = mp->channel;
-		r = history_request(channel->name, filter);
-		if (r)
-		{
-			add_chathistory_target(&targets, r);
-			free_history_result(r);
-		}
-	}
-
-	/* 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 */
-	if (*batch)
-		sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
-}
-
-void send_empty_batch(Client *client, const char *target)
-{
-	char batch[BATCHLEN+1];
-
-	if (HasCapability(client, "batch"))
-	{
-		generate_batch_id(batch);
-		sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, target);
-		sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
-	}
-}
-
-CMD_FUNC(cmd_chathistory)
-{
-	HistoryFilter *filter = NULL;
-	HistoryResult *r = NULL;
-	Channel *channel;
-
-	memset(&filter, 0, sizeof(filter));
-
-	/* This command is only for local users */
-	if (!MyUser(client))
-		return;
-
-	if ((parc < 5) || BadPtr(parv[4]))
-	{
-		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS :Insufficient parameters", me.name);
-		return;
-	}
-
-	if (!HasCapability(client, "server-time"))
-	{
-		sendnotice(client, "Your IRC client does not support the 'server-time' capability");
-		sendnotice(client, "https://ircv3.net/specs/extensions/server-time");
-		sendnotice(client, "History request refused.");
-		return;
-	}
-
-	if (!strcasecmp(parv[1], "TARGETS"))
-	{
-		Membership *mp;
-		int limit;
-
-		filter = safe_alloc(sizeof(HistoryFilter));
-		/* Below this point, instead of 'return', use 'goto end' */
-
-		if (!chathistory_token(parv[2], "timestamp", &filter->timestamp_a))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx",
-				me.name, parv[1], parv[3]);
-			goto end;
-		}
-		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_b))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx",
-				me.name, parv[1], parv[4]);
-			goto end;
-		}
-		limit = atoi(parv[4]);
-		chathistory_targets(client, filter, limit);
-		goto end;
-	}
-
-	/* We don't support retrieving chathistory for PM's. Send empty response/batch, similar to channels without +H. */
-	if (parv[2][0] != '#')
-	{
-		send_empty_batch(client, parv[2]);
-		return;
-	}
-
-	channel = find_channel(parv[2]);
-	if (!channel)
-	{
-		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, 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'))
-	{
-		send_empty_batch(client, channel->name);
-		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 (!strcasecmp(parv[1], "BEFORE"))
-	{
-		filter->cmd = HFC_BEFORE;
-		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
-		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
-				me.name, parv[1], parv[3]);
-			goto end;
-		}
-		filter->limit = atoi(parv[4]);
-	} else
-	if (!strcasecmp(parv[1], "AFTER"))
-	{
-		filter->cmd = HFC_AFTER;
-		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
-		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
-				me.name, parv[1], parv[3]);
-			goto end;
-		}
-		filter->limit = atoi(parv[4]);
-	} else
-	if (!strcasecmp(parv[1], "LATEST"))
-	{
-		filter->cmd = HFC_LATEST;
-		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
-		    !chathistory_token(parv[3], "msgid", &filter->msgid_a) &&
-		    strcmp(parv[3], "*"))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx or *",
-				me.name, parv[1], parv[3]);
-			goto end;
-		}
-		filter->limit = atoi(parv[4]);
-	} else
-	if (!strcasecmp(parv[1], "AROUND"))
-	{
-		filter->cmd = HFC_AROUND;
-		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
-		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
-				me.name, parv[1], parv[3]);
-			goto end;
-		}
-		filter->limit = atoi(parv[4]);
-	} else
-	if (!strcasecmp(parv[1], "BETWEEN"))
-	{
-		filter->cmd = HFC_BETWEEN;
-		if (BadPtr(parv[5]))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s :Insufficient parameters", parv[1], me.name);
-			goto end;
-		}
-		if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
-		    !chathistory_token(parv[3], "msgid", &filter->msgid_a))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
-				me.name, parv[1], parv[3]);
-			goto end;
-		}
-		if (!chathistory_token(parv[4], "timestamp", &filter->timestamp_b) &&
-		    !chathistory_token(parv[4], "msgid", &filter->msgid_b))
-		{
-			sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
-				me.name, parv[1], parv[4]);
-			goto end;
-		}
-		filter->limit = atoi(parv[5]);
-	} else {
-		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s :Invalid subcommand", me.name, parv[1]);
-		goto end;
-	}
-
-	if (filter->limit <= 0)
-	{
-		sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %d :Specified limit is =<0",
-			me.name, parv[1], filter->limit);
-		goto end;
-	}
-
-	if (filter->limit > CHATHISTORY_LIMIT)
-		filter->limit = CHATHISTORY_LIMIT;
-
-	if ((r = history_request(channel->name, filter)))
-		history_send_result(client, r);
-
-end:
-	if (filter)
-		free_history_filter(filter);
-	if (r)
-		free_history_result(r);
-}
diff --git a/src/modules/chghost.c b/src/modules/chghost.c
@@ -1,372 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/chghost.c
- *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
- *
- *   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_CHGHOST 	"CHGHOST"
-
-CMD_FUNC(cmd_chghost);
-void _userhost_save_current(Client *client);
-void _userhost_changed(Client *client);
-
-long CAP_CHGHOST = 0L;
-
-ModuleHeader MOD_HEADER
-  = {
-	"chghost",	/* Name of module */
-	"5.0", /* Version */
-	"/chghost", /* Short description of module */
-	"UnrealIRCd Team",
-	"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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-	
-}
-
-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_CHANGE, 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);
-
-		if (MyUser(client))
-			sendnumeric(client, RPL_HOSTHIDDEN, GetHost(client));
-
-		/* 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>
- * parv[1] - target user
- * parv[2] - hostname
- *
-*/
-
-CMD_FUNC(cmd_chghost)
-{
-	Client *target;
-
-	if (MyUser(client) && !ValidatePermissionsForPath("client:set:host",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 3) || BadPtr(parv[2]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGHOST");
-		return;
-	}
-
-	if (strlen(parv[2]) > (HOSTLEN))
-	{
-		sendnotice(client, "*** ChgName Error: Requested hostname too long -- rejected.");
-		return;
-	}
-
-	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;
-	}
-
-	if (parv[2][0] == ':')
-	{
-		sendnotice(client, "*** A hostname cannot start with ':'");
-		return;
-	}
-
-	target = find_client(parv[1], NULL);
-	if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
-	{
-		/* CHGHOST for a UID that is not online.
-		 * Let's assume it may not YET be online and forward the message to
-		 * the remote server and stop processing ourselves.
-		 * That server will then handle pre-registered processing of the
-		 * CHGHOST and later communicate the host when the user actually
-		 * comes online in the UID message.
-		 */
-		sendto_one(target, recv_mtags, ":%s CHGHOST %s %s", client->id, parv[1], parv[2]);
-		return;
-	}
-
-	if (!target || !target->user)
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		return;
-	}
-
-	if (!strcmp(GetHost(target), parv[2]))
-	{
-		sendnotice(client, "*** /ChgHost Error: requested host is same as current host.");
-		return;
-	}
-
-	userhost_save_current(target);
-
-	switch (UHOST_ALLOWED)
-	{
-		case UHALLOW_NEVER:
-			if (MyUser(client))
-			{
-				sendnumeric(client, ERR_DISABLED, "CHGHOST",
-					"This command is disabled on this server");
-				return;
-			}
-			break;
-		case UHALLOW_ALWAYS:
-			break;
-		case UHALLOW_NOCHANS:
-			if (IsUser(target) && MyUser(client) && target->user->joined)
-			{
-				sendnotice(client, "*** /ChgHost can not be used while %s is on a channel", target->name);
-				return;
-			}
-			break;
-		case UHALLOW_REJOIN:
-			/* rejoin sent later when the host has been changed */
-			break;
-	}
-
-	if (!IsULine(client))
-	{
-		const char *issuer = command_issued_by_rpc(recv_mtags);
-		if (issuer)
-		{
-			unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
-				   "CHGHOST: $issuer changed the virtual hostname of $target.details to be $new_hostname",
-				   log_data_string("issuer", issuer),
-				   log_data_string("change_type", "hostname"),
-				   log_data_client("target", target),
-				   log_data_string("new_hostname", parv[2]));
-		} else {
-			unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
-				   "CHGHOST: $client changed the virtual hostname of $target.details to be $new_hostname",
-				   log_data_string("change_type", "hostname"),
-				   log_data_client("target", target),
-				   log_data_string("new_hostname", parv[2]));
-		}
-	}
-
-	target->umodes |= UMODE_HIDE;
-	target->umodes |= UMODE_SETHOST;
-
-	/* Send to other servers too, unless the client is still in the registration phase (SASL) */
-	if (IsUser(target))
-		sendto_server(client, 0, 0, recv_mtags, ":%s CHGHOST %s %s", client->id, target->id, parv[2]);
-
-	safe_strdup(target->user->virthost, parv[2]);
-	userhost_changed(target);
-}
diff --git a/src/modules/chgident.c b/src/modules/chgident.c
@@ -1,164 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/chgident.c
- *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
- *
- *   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_CHGIDENT 	"CHGIDENT"
-
-CMD_FUNC(cmd_chgident);
-
-ModuleHeader MOD_HEADER
-  = {
-	"chgident",	/* Name of module */
-	"5.0", /* Version */
-	"/chgident", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_CHGIDENT, cmd_chgident, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* 
- * cmd_chgident - 12/07/1999 (two months after I made SETIDENT) - Stskeeps
- * :prefix CHGIDENT <nick> <new identname>
- * parv[1] - nickname
- * parv[2] - identname
- *
-*/
-
-CMD_FUNC(cmd_chgident)
-{
-	Client *target;
-	const char *s;
-	int legalident = 1;
-
-	if (!ValidatePermissionsForPath("client:set:ident",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-
-	if ((parc < 3) || !*parv[2])
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGIDENT");
-		return;
-	}
-
-	if (strlen(parv[2]) > (USERLEN))
-	{
-		sendnotice(client, "*** ChgIdent Error: Requested ident too long -- rejected.");
-		return;
-	}
-
-	if (!valid_username(parv[2]))
-	{
-		sendnotice(client, "*** /ChgIdent Error: A ident may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them");
-		return;
-	}
-
-	target = find_client(parv[1], NULL);
-	if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
-	{
-		/* CHGIDENT for a UID that is not online.
-		 * Let's assume it may not YET be online and forward the message to
-		 * the remote server and stop processing ourselves.
-		 * That server will then handle pre-registered processing of the
-		 * CHGIDENT and later communicate the host when the user actually
-		 * comes online in the UID message.
-		 */
-		sendto_one(target, recv_mtags, ":%s CHGIDENT %s %s", client->id, parv[1], parv[2]);
-		return;
-	}
-
-	if (!target || !target->user)
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		return;
-	}
-	userhost_save_current(target);
-
-	switch (UHOST_ALLOWED)
-	{
-		case UHALLOW_NEVER:
-			if (MyUser(client))
-			{
-				sendnumeric(client, ERR_DISABLED, "CHGIDENT",
-					"This command is disabled on this server");
-				return;
-			}
-			break;
-		case UHALLOW_ALWAYS:
-			break;
-		case UHALLOW_NOCHANS:
-			if (IsUser(target) && MyUser(client) && target->user->joined)
-			{
-				sendnotice(client, "*** /ChgIdent can not be used while %s is on a channel", target->name);
-				return;
-			}
-			break;
-		case UHALLOW_REJOIN:
-			/* join sent later when the ident has been changed */
-			break;
-	}
-	if (!IsULine(client))
-	{
-		const char *issuer = command_issued_by_rpc(recv_mtags);
-		if (issuer)
-		{
-			unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
-				   "CHGIDENT: $issuer changed the username of $target.details to be $new_username",
-				   log_data_string("issuer", issuer),
-				   log_data_string("change_type", "username"),
-				   log_data_client("target", target),
-				   log_data_string("new_username", parv[2]));
-		} else {
-			unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
-				   "CHGIDENT: $client changed the username of $target.details to be $new_username",
-				   log_data_string("change_type", "username"),
-				   log_data_client("target", target),
-				   log_data_string("new_username", parv[2]));
-		}
-	}
-
-	/* Send to other servers too, unless the client is still in the registration phase (SASL) */
-	if (IsUser(target))
-		sendto_server(client, 0, 0, recv_mtags, ":%s CHGIDENT %s %s", client->id, target->id, parv[2]);
-
-	ircsnprintf(target->user->username, sizeof(target->user->username), "%s", parv[2]);
-	userhost_changed(target);
-}
diff --git a/src/modules/chgname.c b/src/modules/chgname.c
@@ -1,134 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/chgname.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_CHGNAME     "CHGNAME"
-#define MSG_SVSNAME     "SVSNAME"
-
-CMD_FUNC(cmd_chgname);
-
-ModuleHeader MOD_HEADER
-  = {
-	"chgname",	/* Name of module */
-	"5.0", /* Version */
-	"command /chgname", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_CHGNAME, cmd_chgname, 2, CMD_USER|CMD_SERVER);
-	CommandAdd(modinfo->handle, MSG_SVSNAME, cmd_chgname, 2, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/*
- * cmd_chgname - Tue May 23 13:06:35 BST 200 (almost a year after I made CHGIDENT) - Stskeeps
- * :prefix CHGNAME <nick> <new realname>
- * parv[1] - nickname
- * parv[2] - realname
- *
-*/
-CMD_FUNC(cmd_chgname)
-{
-	Client *target;
-	ConfigItem_ban *bconf;
-
-	if (!ValidatePermissionsForPath("client:set:name",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 3) || !*parv[2])
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGNAME");
-		return;
-	}
-
-	if (strlen(parv[2]) > (REALLEN))
-	{
-		sendnotice(client, "*** ChgName Error: Requested realname too long -- rejected.");
-		return;
-	}
-
-	if (!(target = find_user(parv[1], NULL)))
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		return;
-	}
-
-	/* Let's log this first */
-	if (!IsULine(client))
-	{
-		const char *issuer = command_issued_by_rpc(recv_mtags);
-		if (issuer)
-		{
-			unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
-				   "CHGNAME: $issuer changed the realname of $target.details to be $new_realname",
-				   log_data_string("issuer", issuer),
-				   log_data_string("change_type", "realname"),
-				   log_data_client("target", target),
-				   log_data_string("new_realname", parv[2]));
-		} else {
-			unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
-				   "CHGNAME: $client changed the realname of $target.details to be $new_realname",
-				   log_data_string("change_type", "realname"),
-				   log_data_client("target", target),
-				   log_data_string("new_realname", parv[2]));
-		}
-	}
-
-	/* set the realname to make ban checking work */
-	ircsnprintf(target->info, sizeof(target->info), "%s", parv[2]);
-
-	if (MyUser(target))
-	{
-		/* only check for realname bans if the person who's name is being changed is NOT an oper */
-		if (!ValidatePermissionsForPath("immune:server-ban:ban-realname",target,NULL,NULL,NULL) &&
-		    ((bconf = find_ban(NULL, target->info, CONF_BAN_REALNAME))))
-		{
-			banned_client(target, "realname", bconf->reason?bconf->reason:"", 0, 0);
-			return;
-		}
-	}
-
-	sendto_server(client, 0, 0, recv_mtags, ":%s CHGNAME %s :%s",
-	    client->id, target->name, parv[2]);
-}
diff --git a/src/modules/clienttagdeny.c b/src/modules/clienttagdeny.c
@@ -1,77 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/echo-message.c
- *   (C) 2020 k4be for The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-char *ct_isupport_param(void);
-int tags_rehash_complete(void);
-
-Module *module;
-
-ModuleHeader MOD_HEADER = {
-	"clienttagdeny",
-	"5.0",
-	"Informs clients about supported client tags",
-	"k4be",
-	"unrealircd-6",
-};
-
-MOD_INIT(){
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD(){
-	module = modinfo->handle;
-	ISupportAdd(module, "CLIENTTAGDENY", ct_isupport_param());
-	HookAdd(module, HOOKTYPE_REHASH_COMPLETE, 0, tags_rehash_complete);
-
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD(){
-	return MOD_SUCCESS;
-}
-
-#define BUFLEN 500
-
-char *ct_isupport_param(void){
-	static char buf[BUFLEN];
-	MessageTagHandler *m;
-	
-	strlcpy(buf, "*", sizeof(buf));
-
-	for (m = mtaghandlers; m; m = m->next) {
-		if (!m->unloaded && m->name[0] == '+'){
-			strlcat(buf, ",-", sizeof(buf));
-			strlcat(buf, m->name+1, sizeof(buf));
-		}
-	}
-	return buf;
-}
-
-int tags_rehash_complete(void){
-	ISupportSet(module, "CLIENTTAGDENY", ct_isupport_param());
-	return HOOK_CONTINUE;
-}
-
diff --git a/src/modules/cloak_md5.c b/src/modules/cloak_md5.c
@@ -1,422 +0,0 @@
-/*
- *   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
@@ -1,87 +0,0 @@
-/*
- *   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
@@ -1,411 +0,0 @@
-/*
- *   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
@@ -1,82 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_close);
-
-#define MSG_CLOSE 	"CLOSE"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"close",
-	"5.0",
-	"command /close", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_CLOSE, cmd_close, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_close - added by Darren Reed Jul 13 1992.
-*/
-CMD_FUNC(cmd_close)
-{
-	Client *target, *next;
-	int  closed = 0;
-
-	if (!ValidatePermissionsForPath("server:close",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	list_for_each_entry_safe(target, next, &unknown_list, lclient_node)
-	{
-		sendnumeric(client, RPL_CLOSING,
-		    get_client_name(target, TRUE), target->status);
-		exit_client(target, NULL, "Oper Closing");
-		closed++;
-	}
-
-	sendnumeric(client, RPL_CLOSEEND, 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-flood.c b/src/modules/connect-flood.c
@@ -1,89 +0,0 @@
-/*
- * Connection throttling (set::anti-flood::connect-flood)
- * (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"connect-flood",
-	"6.0.0",
-	"set::anti-flood::connect-flood",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declaration */
-int connect_flood_accept(Client *client);
-int connect_flood_ip_change(Client *client, const char *oldip);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -3000, connect_flood_accept);
-	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -3000, connect_flood_ip_change);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int connect_flood_throttle(Client *client, int exitflags)
-{
-	int val;
-	char zlinebuf[512];
-
-	if (!(val = throttle_can_connect(client)))
-	{
-		if (exitflags & NO_EXIT_CLIENT)
-		{
-			ircsnprintf(zlinebuf, sizeof(zlinebuf),
-				"ERROR :Closing Link: [%s] (Throttled: Reconnecting too fast) - "
-				"Email %s for more information.\r\n",
-				client->ip, KLINE_ADDRESS);
-			(void)send(client->local->fd, zlinebuf, strlen(zlinebuf), 0);
-			return HOOK_DENY;
-		} else {
-			ircsnprintf(zlinebuf, sizeof(zlinebuf),
-				    "Throttled: Reconnecting too fast - "
-				    "Email %s for more information.",
-				    KLINE_ADDRESS);
-			/* WAS: exit_client(client, NULL, zlinebuf);
-			 * Can't use exit_client() here because HOOKTYPE_IP_CHANGE call
-			 * may be too deep. Eg: read_packet -> webserver_packet_in ->
-			 * webserver_handle_request_header -> webserver_handle_request ->
-			 * RunHook().... and then returning without touching anything
-			 * after an exit_client() would not be feasible.
-			 */
-			dead_socket(client, zlinebuf);
-			return HOOK_DENY;
-		}
-	}
-	else if (val == 1)
-		add_throttling_bucket(client);
-
-	return 0;
-}
-
-int connect_flood_accept(Client *client)
-{
-	if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
-		return 0;
-	return connect_flood_throttle(client, NO_EXIT_CLIENT);
-}
-
-int connect_flood_ip_change(Client *client, const char *oldip)
-{
-	return connect_flood_throttle(client, 0);
-}
diff --git a/src/modules/connect.c b/src/modules/connect.c
@@ -1,123 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_connect);
-
-#define MSG_CONNECT 	"CONNECT"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"connect",
-	"5.0",
-	"command /connect", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_CONNECT, cmd_connect, MAXPARA, CMD_USER|CMD_SERVER); /* hmm.. server.. really? */
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/***********************************************************************
- * cmd_connect() - Added by Jto 11 Feb 1989
- ***********************************************************************//*
-   ** cmd_connect
-   **  parv[1] = servername
- */
-CMD_FUNC(cmd_connect)
-{
-	int  retval;
-	ConfigItem_link	*aconf;
-	Client *server;
-	const char *str;
-
-	if (!IsServer(client) && MyConnect(client) && !ValidatePermissionsForPath("route:global",client,NULL,NULL,NULL) && parc > 3)
-	{			/* Only allow LocOps to make */
-		/* local CONNECTS --SRB      */
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	if (!IsServer(client) && MyUser(client) && !ValidatePermissionsForPath("route:local",client,NULL,NULL,NULL) && parc <= 3)
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	if (hunt_server(client, recv_mtags, "CONNECT", 3, parc, parv) != HUNTED_ISME)
-		return;
-
-	if (parc < 2 || *parv[1] == '\0')
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "CONNECT");
-		return;
-	}
-
-	if ((server = find_server_quick(parv[1])))
-	{
-		sendnotice(client, "*** Connect: Server %s already exists from %s.",
-		    parv[1], server->direction->name);
-		return;
-	}
-
-	aconf = find_link(parv[1]);
-	if (!aconf)
-	{
-		sendnotice(client,
-		    "*** Connect: Server %s is not configured for linking",
-		    parv[1]);
-		return;
-	}
-
-	if (!aconf->outgoing.hostname && !aconf->outgoing.file)
-	{
-		sendnotice(client,
-		    "*** Connect: Server %s is not configured to be an outgoing link (has a link block, but no link::outgoing::hostname or link::outgoing::file)",
-		    parv[1]);
-		return;
-	}
-
-	if ((str = check_deny_link(aconf, 0)))
-	{
-		sendnotice(client, "*** Connect: Disallowed by connection rule: %s", str);
-		return;
-	}
-
-	unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
-		   "CONNECT: Link to $link_block requested by $client",
-		   log_data_link_block(aconf));
-
-	connect_server(aconf, client, NULL);
-}
diff --git a/src/modules/connthrottle.c b/src/modules/connthrottle.c
@@ -1,600 +0,0 @@
-/*
- * connthrottle - Connection throttler
- * (C) Copyright 2004-2020 Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- * See https://www.unrealircd.org/docs/Connthrottle
- */
-
-#include "unrealircd.h"
-
-#define CONNTHROTTLE_VERSION "1.3"
-
-#ifndef CALLBACKTYPE_REPUTATION_STARTTIME
- #define CALLBACKTYPE_REPUTATION_STARTTIME 5
-#endif
-
-ModuleHeader MOD_HEADER
-  = {
-	"connthrottle",
-	CONNTHROTTLE_VERSION,
-	"Connection throttler - by Syzop",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-typedef struct {
-	int count;
-	int period;
-} ThrottleSetting;
-
-struct cfgstruct {
-	/* set::connthrottle::known-users: */
-	ThrottleSetting local;
-	ThrottleSetting global;
-	/* set::connthrottle::except: */
-	SecurityGroup *except;
-	/* set::connthrottle::disabled-when: */
-	long reputation_gathering;
-	int start_delay;
-	/* set::connthrottle (generic): */
-	char *reason;
-};
-static struct cfgstruct cfg;
-
-typedef struct {
-	int count;
-	long t;
-} ThrottleCounter;
-
-typedef struct UCounter UCounter;
-struct UCounter {
-	ThrottleCounter local;		/**< Local counter */
-	ThrottleCounter global;		/**< Global counter */
-	int rejected_clients;		/**< Number of rejected clients this minute */
-	int allowed_except;		/**< Number of allowed clients - on except list */
-	int allowed_unknown_users;	/**< Number of allowed clients - not on except list */
-	char disabled;			/**< Module disabled by oper? */
-	int throttling_this_minute;	/**< Did we do any throttling this minute? */
-	int throttling_previous_minute;	/**< Did we do any throttling previous minute? */
-	int throttling_banner_displayed;/**< Big we-are-now-throttling banner displayed? */
-	time_t next_event;		/**< When is next event? (for "last 60 seconds" stats) */
-};
-UCounter *ucounter = NULL;
-
-#define MSG_THROTTLE "THROTTLE"
-
-/* Forward declarations */
-int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int ct_config_posttest(int *errs);
-int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-int ct_pre_lconnect(Client *client);
-int ct_lconnect(Client *);
-int ct_rconnect(Client *);
-CMD_FUNC(ct_throttle);
-EVENT(connthrottle_evt);
-void ucounter_free(ModData *m);
-
-MOD_TEST()
-{
-	memset(&cfg, 0, sizeof(cfg));
-	
-	/* Defaults: */
-	cfg.local.count = 20; cfg.local.period = 60;
-	cfg.global.count = 30; cfg.global.period = 60;
-	cfg.start_delay = 180;		/* 3 minutes */
-	safe_strdup(cfg.reason, "Throttled: Too many users trying to connect, please wait a while and try again");
-	cfg.except = safe_alloc(sizeof(SecurityGroup));
-	cfg.except->reputation_score = 24;
-	cfg.except->identified = 1;
-	cfg.except->webirc = 0;
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, ct_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, ct_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	LoadPersistentPointer(modinfo, ucounter, ucounter_free);
-	if (!ucounter)
-		ucounter = safe_alloc(sizeof(UCounter));
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, ct_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, ct_pre_lconnect);
-	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, ct_lconnect);
-	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, ct_rconnect);
-	CommandAdd(modinfo->handle, MSG_THROTTLE, ct_throttle, MAXPARA, CMD_USER|CMD_SERVER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	EventAdd(modinfo->handle, "connthrottle_evt", connthrottle_evt, NULL, 1000, 0);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	SavePersistentPointer(modinfo, ucounter);
-	safe_free(cfg.reason);
-	free_security_group(cfg.except);
-	return MOD_SUCCESS;
-}
-
-/** This function checks if the reputation module is loaded.
- * If not, then the module will error, since we depend on it.
- */
-int ct_config_posttest(int *errs)
-{
-	int errors = 0;
-
-	/* Note: we use Callbacks[] here, but this is only for checking. Don't
-	 * let this confuse you. At any other place you must use RCallbacks[].
-	 */
-	if (Callbacks[CALLBACKTYPE_REPUTATION_STARTTIME] == NULL)
-	{
-		config_error("The 'connthrottle' module requires the 'reputation' "
-		             "module to be loaded as well.");
-		config_error("Add the following to your configuration file: "
-		             "loadmodule \"reputation\";");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/** Test the set::connthrottle configuration */
-int ct_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::connthrottle.. */
-	if (!ce || !ce->name || strcmp(ce->name, "connthrottle"))
-		return 0;
-	
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "except"))
-		{
-			test_match_block(cf, cep, &errors);
-		} else
-		if (!strcmp(cep->name, "known-users"))
-		{
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-			{
-				CheckNull(cepp);
-				if (!strcmp(cepp->name, "minimum-reputation-score"))
-				{
-					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->file->filename, cepp->line_number);
-						errors++;
-						continue;
-					}
-				} else
-				if (!strcmp(cepp->name, "sasl-bypass"))
-				{
-				} else
-				if (!strcmp(cepp->name, "webirc-bypass"))
-				{
-				} else
-				{
-					config_error_unknown(cepp->file->filename, cepp->line_number,
-					                     "set::connthrottle::known-users", cepp->name);
-					errors++;
-				}
-			}
-		} else
-		if (!strcmp(cep->name, "new-users"))
-		{
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-			{
-				CheckNull(cepp);
-				if (!strcmp(cepp->name, "local-throttle"))
-				{
-					int 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->file->filename, cepp->line_number);
-						errors++;
-						continue;
-					}
-				} else
-				if (!strcmp(cepp->name, "global-throttle"))
-				{
-					int 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->file->filename, cepp->line_number);
-						errors++;
-						continue;
-					}
-				} else
-				{
-					config_error_unknown(cepp->file->filename, cepp->line_number,
-					                     "set::connthrottle::new-users", cepp->name);
-					errors++;
-				}
-			}
-		} else
-		if (!strcmp(cep->name, "disabled-when"))
-		{
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-			{
-				CheckNull(cepp);
-				if (!strcmp(cepp->name, "start-delay"))
-				{
-					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->file->filename, cepp->line_number);
-						errors++;
-						continue;
-					}
-				} else
-				if (!strcmp(cepp->name, "reputation-gathering"))
-				{
-				} else
-				{
-					config_error_unknown(cepp->file->filename, cepp->line_number,
-					                     "set::connthrottle::disabled-when", cepp->name);
-					errors++;
-				}
-			}
-		} else
-		if (!strcmp(cep->name, "reason"))
-		{
-			CheckNull(cep);
-		} else
-		{
-			config_error("%s:%i: unknown directive set::connthrottle::%s",
-				cep->file->filename, cep->line_number, cep->name);
-			errors++;
-			continue;
-		}
-	}
-	
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/* Configure ourselves based on the set::connthrottle settings */
-int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep, *cepp;
-
-	if (type != CONFIG_SET)
-		return 0;
-	
-	/* We are only interrested in set::connthrottle.. */
-	if (!ce || !ce->name || strcmp(ce->name, "connthrottle"))
-		return 0;
-	
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "except"))
-		{
-			conf_match_block(cf, cep, &cfg.except);
-		} else
-		if (!strcmp(cep->name, "known-users"))
-		{
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-			{
-				if (!strcmp(cepp->name, "minimum-reputation-score"))
-					cfg.except->reputation_score = atoi(cepp->value);
-				else if (!strcmp(cepp->name, "sasl-bypass"))
-					cfg.except->identified = config_checkval(cepp->value, CFG_YESNO);
-				else if (!strcmp(cepp->name, "webirc-bypass"))
-					cfg.except->webirc = config_checkval(cepp->value, CFG_YESNO);
-			}
-		} else
-		if (!strcmp(cep->name, "new-users"))
-		{
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-			{
-				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->name, "disabled-when"))
-		{
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-			{
-				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->name, "reason"))
-		{
-			safe_free(cfg.reason);
-			cfg.reason = safe_alloc(strlen(cep->value)+16);
-			sprintf(cfg.reason, "Throttled: %s", cep->value);
-		}
-	}
-	return 1;
-}
-
-/** Returns 1 if the 'reputation' module is still gathering
- * data, such as in the first week of when it is loaded.
- * This behavior is configured via set::disabled-when::reputation-gathering
- */
-int still_reputation_gathering(void)
-{
-	int v;
-
-	if (RCallbacks[CALLBACKTYPE_REPUTATION_STARTTIME] == NULL)
-		return 1; /* Reputation module not loaded, disable us */
-
-	v = RCallbacks[CALLBACKTYPE_REPUTATION_STARTTIME]->func.intfunc();
-
-	if (TStime() - v < cfg.reputation_gathering)
-		return 1; /* Still gathering reputation data (eg: first week) */
-
-	return 0;
-}
-
-EVENT(connthrottle_evt)
-{
-	char buf[512];
-
-	if (ucounter->next_event > TStime())
-		return;
-	ucounter->next_event = TStime() + 60;
-
-	if (ucounter->rejected_clients)
-	{
-		unreal_log(ULOG_INFO, "connthrottle", "CONNTHROTLE_REPORT", NULL,
-		           "ConnThrottle] Stats for this server past 60 secs: "
-		           "Connections rejected: $num_rejected. "
-		           "Accepted: $num_accepted_except except user(s) and "
-		           "$num_accepted_unknown_users new user(s).",
-		           log_data_integer("num_rejected", ucounter->rejected_clients),
-		           log_data_integer("num_accepted_except", ucounter->allowed_except),
-		           log_data_integer("num_accepted_unknown_users", ucounter->allowed_unknown_users));
-	}
-
-	/* Reset stats for next message */
-	ucounter->rejected_clients = 0;
-	ucounter->allowed_except = 0;
-	ucounter->allowed_unknown_users = 0;
-
-	ucounter->throttling_previous_minute = ucounter->throttling_this_minute;
-	ucounter->throttling_this_minute = 0; /* reset */
-	ucounter->throttling_banner_displayed = 0; /* reset */
-}
-
-#define THROT_LOCAL 1
-#define THROT_GLOBAL 2
-int ct_pre_lconnect(Client *client)
-{
-	int throttle=0;
-	int score;
-
-	if (me.local->creationtime + cfg.start_delay > TStime())
-		return HOOK_CONTINUE; /* no throttle: start delay */
-
-	if (ucounter->disabled)
-		return HOOK_CONTINUE; /* protection disabled: allow user */
-
-	if (still_reputation_gathering())
-		return HOOK_CONTINUE; /* still gathering reputation data */
-
-	if (user_allowed_by_security_group(client, cfg.except))
-		return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */
-
-	/* If we reach this then the user is NEW */
-
-	/* +1 global client would reach global limit? */
-	if ((TStime() - ucounter->global.t < cfg.global.period) && (ucounter->global.count+1 > cfg.global.count))
-		throttle |= THROT_GLOBAL;
-
-	/* +1 local client would reach local limit? */
-	if ((TStime() - ucounter->local.t < cfg.local.period) && (ucounter->local.count+1 > cfg.local.count))
-		throttle |= THROT_LOCAL;
-
-	if (throttle)
-	{
-		ucounter->throttling_this_minute = 1;
-		ucounter->rejected_clients++;
-		/* We send the LARGE banner if throttling was activated */
-		if (!ucounter->throttling_previous_minute && !ucounter->throttling_banner_displayed)
-		{
-			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);
-		return HOOK_DENY;
-	}
-
-	return HOOK_CONTINUE;
-}
-
-/** Increase the connect counter(s), nothing else. */
-void bump_connect_counter(int local_connect)
-{
-	if (local_connect)
-	{
-		/* Bump local connect counter */
-		if (TStime() - ucounter->local.t >= cfg.local.period)
-		{
-			ucounter->local.t = TStime();
-			ucounter->local.count = 1;
-		} else {
-			ucounter->local.count++;
-		}
-	}
-
-	/* Bump global connect counter */
-	if (TStime() - ucounter->global.t >= cfg.global.period)
-	{
-		ucounter->global.t = TStime();
-		ucounter->global.count = 1;
-	} else {
-		ucounter->global.count++;
-	}
-}
-
-int ct_lconnect(Client *client)
-{
-	int score;
-
-	if (me.local->creationtime + cfg.start_delay > TStime())
-		return 0; /* no throttle: start delay */
-
-	if (ucounter->disabled)
-		return 0; /* protection disabled: allow user */
-
-	if (still_reputation_gathering())
-		return 0; /* still gathering reputation data */
-
-	if (user_allowed_by_security_group(client, cfg.except))
-	{
-		ucounter->allowed_except++;
-		return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */
-	}
-
-	/* Allowed NEW user */
-	ucounter->allowed_unknown_users++;
-
-	bump_connect_counter(1);
-
-	return 0;
-}
-
-int ct_rconnect(Client *client)
-{
-	int score;
-
-	if (client->uplink && !IsSynched(client->uplink))
-		return 0; /* Netmerge: skip */
-
-	if (IsULine(client))
-		return 0; /* U:lined, such as services: skip */
-
-#if UNREAL_VERSION_TIME >= 201915
-	/* On UnrealIRCd 4.2.3+ we can see the boot time (start time)
-	 * of the remote server. This way we can apply the
-	 * set::disabled-when::start-delay restriction on remote
-	 * servers as well.
-	 */
-	if (client->uplink && client->uplink->server && client->uplink->server->boottime &&
-	    (TStime() - client->uplink->server->boottime < cfg.start_delay))
-	{
-		return 0;
-	}
-#endif
-
-	if (user_allowed_by_security_group(client, cfg.except))
-		return 0; /* user is on except list (known user or otherwise) */
-
-	bump_connect_counter(0);
-
-	return 0;
-}
-
-static void ct_throttle_usage(Client *client)
-{
-	sendnotice(client, "Usage: /THROTTLE [ON|OFF|STATUS|RESET]");
-	sendnotice(client, " ON:     Enabled protection");
-	sendnotice(client, " OFF:    Disables protection");
-	sendnotice(client, " STATUS: Status report");
-	sendnotice(client, " RESET:  Resets all counters(&more)");
-	sendnotice(client, "NOTE: All commands only affect this server. Remote servers are not affected.");
-}
-
-CMD_FUNC(ct_throttle)
-{
-	if (!IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		ct_throttle_usage(client);
-		return;
-	}
-
-	if (!strcasecmp(parv[1], "STATS") || !strcasecmp(parv[1], "STATUS"))
-	{
-		sendnotice(client, "STATUS:");
-		if (ucounter->disabled)
-		{
-			sendnotice(client, "Module DISABLED on oper request. To re-enable, type: /THROTTLE ON");
-		} else {
-			if (still_reputation_gathering())
-			{
-				sendnotice(client, "Module DISABLED because the 'reputation' module has not gathered enough data yet (set::connthrottle::disabled-when::reputation-gathering).");
-			} else
-			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->creationtime + cfg.start_delay) - TStime()));
-			} else
-			{
-				sendnotice(client, "Module ENABLED");
-			}
-		}
-	} else 
-	if (!strcasecmp(parv[1], "OFF"))
-	{
-		if (ucounter->disabled == 1)
-		{
-			sendnotice(client, "Already OFF");
-			return;
-		}
-		ucounter->disabled = 1;
-		unreal_log(ULOG_WARNING, "connthrottle", "CONNTHROTLE_MODULE_DISABLED", client,
-			   "[ConnThrottle] $client.details DISABLED the connthrottle module.");
-	} else
-	if (!strcasecmp(parv[1], "ON"))
-	{
-		if (ucounter->disabled == 0)
-		{
-			sendnotice(client, "Already ON");
-			return;
-		}
-		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));
-		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]);
-		ct_throttle_usage(client);
-	}
-}
-
-void ucounter_free(ModData *m)
-{
-	safe_free(ucounter);
-}
diff --git a/src/modules/creationtime.c b/src/modules/creationtime.c
@@ -1,100 +0,0 @@
-/*
- * Store creationtime in ModData
- * (C) Copyright 2022-.. Syzop and The UnrealIRCd Team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"creationtime",
-	"6.1",
-	"Store and retrieve creation time of clients",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-void creationtime_free(ModData *m);
-const char *creationtime_serialize(ModData *m);
-void creationtime_unserialize(const char *str, ModData *m);
-int creationtime_handshake(Client *client);
-int creationtime_welcome_user(Client *client, int numeric);
-int creationtime_whois(Client *client, Client *target);
-
-ModDataInfo *creationtime_md; /* Module Data structure which we acquire */
-
-#define SetCreationTime(x,y)	do { moddata_client(x, creationtime_md).ll = y; } while(0)
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "creationtime";
-	mreq.free = creationtime_free;
-	mreq.serialize = creationtime_serialize;
-	mreq.unserialize = creationtime_unserialize;
-	mreq.self_write = 1;
-	mreq.sync = MODDATA_SYNC_EARLY;
-	mreq.type = MODDATATYPE_CLIENT;
-	creationtime_md = ModDataAdd(modinfo->handle, mreq);
-	if (!creationtime_md)
-		abort();
-
-	/* This event sets creationtime very early: on handshake in and out */
-	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, creationtime_handshake);
-	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, creationtime_handshake);
-
-	/* And this event (re)sets it because that also happens in
-	 * welcome_user() in nick.c regarding #2174
-	 */
-	HookAdd(modinfo->handle, HOOKTYPE_WELCOME, 0, creationtime_welcome_user);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int creationtime_handshake(Client *client)
-{
-	SetCreationTime(client, client->local->creationtime);
-	return 0;
-}
-
-int creationtime_welcome_user(Client *client, int numeric)
-{
-	if (numeric == 0)
-		SetCreationTime(client, client->local->creationtime);
-	return 0;
-}
-
-void creationtime_free(ModData *m)
-{
-	m->ll = 0;
-}
-
-const char *creationtime_serialize(ModData *m)
-{
-	static char buf[64];
-
-	snprintf(buf, sizeof(buf), "%lld", (long long)m->ll);
-	return buf;
-}
-
-void creationtime_unserialize(const char *str, ModData *m)
-{
-	m->ll = atoll(str);
-}
diff --git a/src/modules/cycle.c b/src/modules/cycle.c
@@ -1,83 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/cycle.c
- *   (C) 2000-2001 Carsten V. Munk 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"
-
-CMD_FUNC(cmd_cycle);
-
-/* Place includes here */
-#define MSG_CYCLE       "CYCLE"
-
-ModuleHeader MOD_HEADER
-  = {
-	"cycle",	/* Name of module */
-	"5.0", /* Version */
-	"command /cycle", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_CYCLE, cmd_cycle, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-	
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-/*
- * cmd_cycle() - Stskeeps
- * parv[1] = channels
-*/
-
-CMD_FUNC(cmd_cycle)
-{
-	char channels[BUFSIZE];
-	const char *parx[3];
-	int n;
-	
-	if (parc < 2)
-		return;
-
-	/* First PART the user... */
-	parv[2] = "cycling";
-	strlcpy(channels, parv[1], sizeof(channels));
-	do_cmd(client, recv_mtags, "PART", 3, parv);
-	if (IsDead(client))
-		return;
-
-	/* Then JOIN the user again... */
-	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
@@ -1,257 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/m_dccallow
- *   (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_dccallow);
-
-#define MSG_DCCALLOW 	"DCCALLOW"
-
-ModuleHeader MOD_HEADER
-  = {
-	"dccallow",
-	"5.0",
-	"command /dccallow", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_DCCALLOW, cmd_dccallow, 1, CMD_USER);
-	ISupportAdd(modinfo->handle, "USERIP", NULL);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* cmd_dccallow:
- * HISTORY:
- * Taken from bahamut 1.8.1
- */
-CMD_FUNC(cmd_dccallow)
-{
-	char request[BUFSIZE];
-	Link *lp;
-	char *p, *s;
-	Client *friend;
-	int didlist = 0, didhelp = 0, didanything = 0;
-	char **ptr;
-	int ntargets = 0;
-	int maxtargets = max_targets_for_command("WHOIS");
-	static char *dcc_help[] =
-	{
-		"/DCCALLOW [<+|->nick[,<+|->nick, ...]] [list] [help]",
-		"You may allow DCCs of files which are otherwise blocked by the IRC server",
-		"by specifying a DCC allow for the user you want to recieve files from.",
-		"For instance, to allow the user Bob to send you file.exe, you would type:",
-		"/DCCALLOW +bob",
-		"and Bob would then be able to send you files. Bob will have to resend the file",
-		"if the server gave him an error message before you added him to your allow list.",
-		"/DCCALLOW -bob",
-		"Will do the exact opposite, removing him from your dcc allow list.",
-		"/dccallow list",
-		"Will list the users currently on your dcc allow list.",
-		NULL
-	};
-
-	if (!MyUser(client))
-		return;
-	
-	if (parc < 2)
-	{
-		sendnotice(client, "No command specified for DCCALLOW. "
-			"Type '/DCCALLOW HELP' for more information.");
-		return;
-	}
-
-	strlcpy(request, parv[1], sizeof(request));
-	for (p = NULL, s = strtoken(&p, request, ", "); s; s = strtoken(&p, NULL, ", "))
-	{
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, s, maxtargets, "DCCALLOW");
-			break;
-		}
-		if (*s == '+')
-		{
-			didanything = 1;
-			if (!*++s)
-				continue;
-			
-			friend = find_user(s, NULL);
-			
-			if (friend == client)
-				continue;
-			
-			if (!friend)
-			{
-				sendnumeric(client, ERR_NOSUCHNICK, s);
-				continue;
-			}
-			add_dccallow(client, friend);
-		} else
-		if (*s == '-')
-		{
-			didanything = 1;
-			if (!*++s)
-				continue;
-			
-			friend = find_user(s, NULL);
-			if (friend == client)
-				continue;
-			if (!friend)
-			{
-				sendnumeric(client, ERR_NOSUCHNICK, s);
-				continue;
-			}
-			del_dccallow(client, friend);
-		} else
-		if (!didlist && !strncasecmp(s, "list", 4))
-		{
-			didanything = didlist = 1;
-			sendnumeric(client, RPL_DCCINFO, "The following users are on your dcc allow list:");
-			for(lp = client->user->dccallow; lp; lp = lp->next)
-			{
-				if (lp->flags == DCC_LINK_REMOTE)
-					continue;
-				sendnumericfmt(client, RPL_DCCLIST, ":%s (%s@%s)", lp->value.client->name,
-					lp->value.client->user->username,
-					GetHost(lp->value.client));
-			}
-			sendnumeric(client, RPL_ENDOFDCCLIST, s);
-		} else
-		if (!didhelp && !strncasecmp(s, "help", 4))
-		{
-			didanything = didhelp = 1;
-			for(ptr = dcc_help; *ptr; ptr++)
-				sendnumeric(client, RPL_DCCINFO, *ptr);
-			sendnumeric(client, RPL_ENDOFDCCLIST, s);
-		}
-	}
-	if (!didanything)
-	{
-		sendnotice(client, "Invalid syntax for DCCALLOW. Type '/DCCALLOW HELP' for more information.");
-		return;
-	}
-}
-
-/* The dccallow functions below are all taken from bahamut (1.8.1).
- * Well, with some small modifications of course. -- Syzop
- */
-
-/** Adds 'optr' to the DCCALLOW list of 'client' */
-int add_dccallow(Client *client, Client *optr)
-{
-	Link *lp;
-	int cnt = 0;
-
-	for (lp = client->user->dccallow; lp; lp = lp->next)
-	{
-		if (lp->flags != DCC_LINK_ME)
-			continue;
-		cnt++;
-		if (lp->value.client == optr)
-			return 0;
-	}
-
-	if (cnt >= MAXDCCALLOW)
-	{
-		sendnumeric(client, ERR_TOOMANYDCC,
-			optr->name, MAXDCCALLOW);
-		return 0;
-	}
-
-	lp = make_link();
-	lp->value.client = optr;
-	lp->flags = DCC_LINK_ME;
-	lp->next = client->user->dccallow;
-	client->user->dccallow = lp;
-
-	lp = make_link();
-	lp->value.client = client;
-	lp->flags = DCC_LINK_REMOTE;
-	lp->next = optr->user->dccallow;
-	optr->user->dccallow = lp;
-
-	sendnumeric(client, RPL_DCCSTATUS, optr->name, "added to");
-	return 0;
-}
-
-/** Removes 'optr' from the DCCALLOW list of 'client' */
-int del_dccallow(Client *client, Client *optr)
-{
-	Link **lpp, *lp;
-	int found = 0;
-
-	for (lpp = &(client->user->dccallow); *lpp; lpp=&((*lpp)->next))
-	{
-		if ((*lpp)->flags != DCC_LINK_ME)
-			continue;
-		if ((*lpp)->value.client == optr)
-		{
-			lp = *lpp;
-			*lpp = lp->next;
-			free_link(lp);
-			found++;
-			break;
-		}
-	}
-	if (!found)
-	{
-		sendnumericfmt(client, RPL_DCCINFO, ":%s is not in your DCC allow list", optr->name);
-		return 0;
-	}
-
-	for (found = 0, lpp = &(optr->user->dccallow); *lpp; lpp=&((*lpp)->next))
-	{
-		if ((*lpp)->flags != DCC_LINK_REMOTE)
-			continue;
-		if ((*lpp)->value.client == client)
-		{
-			lp = *lpp;
-			*lpp = lp->next;
-			free_link(lp);
-			found++;
-			break;
-		}
-	}
-	if (!found)
-	{
-		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");
-
-	return 0;
-}
diff --git a/src/modules/dccdeny.c b/src/modules/dccdeny.c
@@ -1,882 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/dccdeny.c
- *   (C) 2004-2019 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
-  = {
-	"dccdeny",
-	"6.0.2",
-	"command /dccdeny", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Variables */
-ConfigItem_deny_dcc     *conf_deny_dcc = NULL;
-ConfigItem_allow_dcc    *conf_allow_dcc = NULL;
-
-/* Forward declarions */
-int dccdeny_configtest_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-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, 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, 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, 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 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()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, dccdeny_configtest_deny_dcc);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, dccdeny_configtest_allow_dcc);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	LoadPersistentPointer(modinfo, conf_deny_dcc, dccdeny_unload_free_all_conf_deny_dcc);
-	LoadPersistentPointer(modinfo, conf_allow_dcc, dccdeny_unload_free_all_conf_allow_dcc);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, dccdeny_configrun_deny_dcc);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, dccdeny_configrun_allow_dcc);
-	CommandAdd(modinfo->handle, "DCCDENY", cmd_dccdeny, 2, CMD_USER);
-	CommandAdd(modinfo->handle, "UNDCCDENY", cmd_undccdeny, MAXPARA, CMD_USER);
-	CommandAdd(modinfo->handle, "SVSFLINE", cmd_svsfline, MAXPARA, CMD_SERVER);
-	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, dccdeny_stats);
-	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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	free_dcc_config_blocks();
-	SavePersistentPointer(modinfo, conf_deny_dcc);
-	SavePersistentPointer(modinfo, conf_allow_dcc);
-
-	return MOD_SUCCESS;
-}
-
-int dccdeny_configtest_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep;
-	int errors = 0;
-	char has_filename = 0, has_reason = 0, has_soft = 0;
-
-	/* We are only interested in deny dcc { } */
-	if ((type != CONFIG_DENY) || strcmp(ce->value, "dcc"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (config_is_blankorempty(cep, "deny dcc"))
-		{
-			errors++;
-			continue;
-		}
-		if (!strcmp(cep->name, "filename"))
-		{
-			if (has_filename)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "deny dcc::filename");
-				continue;
-			}
-			has_filename = 1;
-		}
-		else if (!strcmp(cep->name, "reason"))
-		{
-			if (has_reason)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "deny dcc::reason");
-				continue;
-			}
-			has_reason = 1;
-		}
-		else if (!strcmp(cep->name, "soft"))
-		{
-			if (has_soft)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "deny dcc::soft");
-				continue;
-			}
-			has_soft = 1;
-		}
-		else
-		{
-			config_error_unknown(cep->file->filename,
-				cep->line_number, "deny dcc", cep->name);
-			errors++;
-		}
-	}
-	if (!has_filename)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"deny dcc::filename");
-		errors++;
-	}
-	if (!has_reason)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"deny dcc::reason");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int dccdeny_configtest_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep;
-	int errors = 0, has_filename = 0, has_soft = 0;
-
-	/* We are only interested in allow dcc { } */
-	if ((type != CONFIG_ALLOW) || strcmp(ce->value, "dcc"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (config_is_blankorempty(cep, "allow dcc"))
-		{
-			errors++;
-			continue;
-		}
-		if (!strcmp(cep->name, "filename"))
-		{
-			if (has_filename)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "allow dcc::filename");
-				continue;
-			}
-			has_filename = 1;
-		}
-		else if (!strcmp(cep->name, "soft"))
-		{
-			if (has_soft)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "allow dcc::soft");
-				continue;
-			}
-			has_soft = 1;
-		}
-		else
-		{
-			config_error_unknown(cep->file->filename, cep->line_number,
-				"allow dcc", cep->name);
-			errors++;
-		}
-	}
-	if (!has_filename)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"allow dcc::filename");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int dccdeny_configrun_deny_dcc(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigItem_deny_dcc 	*deny = NULL;
-	ConfigEntry 	    	*cep;
-
-	/* We are only interested in deny dcc { } */
-	if ((type != CONFIG_DENY) || strcmp(ce->value, "dcc"))
-		return 0;
-
-	deny = safe_alloc(sizeof(ConfigItem_deny_dcc));
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "filename"))
-		{
-			safe_strdup(deny->filename, cep->value);
-		}
-		else if (!strcmp(cep->name, "reason"))
-		{
-			safe_strdup(deny->reason, cep->value);
-		}
-		else if (!strcmp(cep->name, "soft"))
-		{
-			int x = config_checkval(cep->value,CFG_YESNO);
-			if (x == 1)
-				deny->flag.type = DCCDENY_SOFT;
-		}
-	}
-	if (!deny->reason)
-	{
-		if (deny->flag.type == DCCDENY_HARD)
-			safe_strdup(deny->reason, "Possible infected virus file");
-		else
-			safe_strdup(deny->reason, "Possible executable content");
-	}
-	AddListItem(deny, conf_deny_dcc);
-	return 0;
-}
-
-int dccdeny_configrun_allow_dcc(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigItem_allow_dcc *allow = NULL;
-	ConfigEntry *cep;
-
-	/* We are only interested in allow dcc { } */
-	if ((type != CONFIG_ALLOW) || strcmp(ce->value, "dcc"))
-		return 0;
-
-	allow = safe_alloc(sizeof(ConfigItem_allow_dcc));
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "filename"))
-			safe_strdup(allow->filename, cep->value);
-		else if (!strcmp(cep->name, "soft"))
-		{
-			int x = config_checkval(cep->value,CFG_YESNO);
-			if (x)
-				allow->flag.type = DCCDENY_SOFT;
-		}
-	}
-	AddListItem(allow, conf_allow_dcc);
-	return 1;
-}
-
-/** Free allow dcc { } and deny dcc { } blocks, called on REHASH */
-void free_dcc_config_blocks(void)
-{
-	ConfigItem_deny_dcc *d, *d_next;
-	ConfigItem_allow_dcc *a, *a_next;
-
-	for (d = conf_deny_dcc; d; d = d_next)
-	{
-		d_next = d->next;
-		if (d->flag.type2 == CONF_BAN_TYPE_CONF)
-		{
-			safe_free(d->filename);
-			safe_free(d->reason);
-			DelListItem(d, conf_deny_dcc);
-			safe_free(d);
-		}
-	}
-	for (a = conf_allow_dcc; a; a = a_next)
-	{
-		a_next = a->next;
-		if (a->flag.type2 == CONF_BAN_TYPE_CONF)
-		{
-			safe_free(a->filename);
-			DelListItem(a, conf_allow_dcc);
-			safe_free(a);
-		}
-	}
-}
-
-/** Free all deny dcc { } blocks - only called on permanent module unload (rare) */
-void dccdeny_unload_free_all_conf_deny_dcc(ModData *m)
-{
-	ConfigItem_deny_dcc *d, *d_next;
-
-	for (d = conf_deny_dcc; d; d = d_next)
-	{
-		d_next = d->next;
-		safe_free(d->filename);
-		safe_free(d->reason);
-		DelListItem(d, conf_deny_dcc);
-		safe_free(d);
-	}
-	conf_deny_dcc = NULL;
-}
-
-/** Free all allow dcc { } blocks - only called on permanent module unload (rare) */
-void dccdeny_unload_free_all_conf_allow_dcc(ModData *m)
-{
-	ConfigItem_allow_dcc *a, *a_next;
-
-	for (a = conf_allow_dcc; a; a = a_next)
-	{
-		a_next = a->next;
-		safe_free(a->filename);
-		DelListItem(a, conf_allow_dcc);
-		safe_free(a);
-	}
-	conf_allow_dcc = NULL;
-}
-
-
-/* Add a temporary dccdeny line
- *
- * parv[1] - file
- * parv[2] - reason
- */
-CMD_FUNC(cmd_dccdeny)
-{
-	if (!MyUser(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:dccdeny",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 2) || BadPtr(parv[2]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "DCCDENY");
-		return;
-	}
-
-	if (!find_deny_dcc(parv[1]))
-	{
-		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
-	{
-		sendnotice(client, "*** %s already has a dccdeny", parv[1]);
-	}
-}
-
-/* Remove a temporary dccdeny line
- * parv[1] - file/mask
- */
-CMD_FUNC(cmd_undccdeny)
-{
-	ConfigItem_deny_dcc *d;
-
-	if (!MyUser(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:dccdeny",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "UNDCCDENY");
-		return;
-	}
-
-	if ((d = find_deny_dcc(parv[1])) && d->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
-	{
-		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
-	{
-		sendnotice(client, "*** Unable to find a temp dccdeny matching %s", parv[1]);
-	}
-}
-
-CMD_FUNC(cmd_svsfline)
-{
-	if (parc < 2)
-		return;
-
-	switch (*parv[1])
-	{
-		/* Allow non-U-Lines to send ONLY SVSFLINE +, but don't send it out
-		 * unless it is from a U-Line -- codemastr
-		 */
-		case '+':
-		{
-			if (parc < 4)
-				return;
-
-			if (!find_deny_dcc(parv[2]))
-				DCCdeny_add(parv[2], parv[3], DCCDENY_HARD, CONF_BAN_TYPE_AKILL);
-
-			if (IsULine(client))
-			{
-				sendto_server(client, 0, 0, NULL, ":%s SVSFLINE + %s :%s",
-				    client->id, parv[2], parv[3]);
-			}
-
-			break;
-		}
-
-		case '-':
-		{
-			ConfigItem_deny_dcc *deny;
-
-			if (!IsULine(client))
-				return;
-
-			if (parc < 3)
-				return;
-
-			if (!(deny = find_deny_dcc(parv[2])))
-				break;
-
-			DCCdeny_del(deny);
-
-			sendto_server(client, 0, 0, NULL, ":%s SVSFLINE %s", client->id, parv[2]);
-
-			break;
-		}
-
-		case '*':
-		{
-			if (!IsULine(client))
-				return;
-
-			dcc_wipe_services();
-
-			sendto_server(client, 0, 0, NULL, ":%s SVSFLINE *", client->id);
-
-			break;
-		}
-	}
-}
-
-/** Sync the DCC entries on server connect */
-int dccdeny_server_sync(Client *client)
-{
-	ConfigItem_deny_dcc *p;
-	for (p = conf_deny_dcc; p; p = p->next)
-	{
-		if (p->flag.type2 == CONF_BAN_TYPE_AKILL)
-			sendto_one(client, NULL, ":%s SVSFLINE + %s :%s", me.id,
-			    p->filename, p->reason);
-	}
-	return 0;
-}
-
-/** Check if a DCC should be blocked (user-to-user) */
-int dccdeny_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
-{
-	if (**text == '\001')
-	{
-		const char *filename = get_dcc_filename(*text);
-		if (filename)
-		{
-			if (MyUser(client) && !can_dcc(client, target->name, target, filename, errmsg))
-				return HOOK_DENY;
-			if (MyUser(target) && !can_dcc_soft(client, target, filename, errmsg))
-				return HOOK_DENY;
-		}
-	}
-
-	return HOOK_CONTINUE;
-}
-
-/** Check if a DCC should be blocked (user-to-channel, unusual) */
-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'))
-	{
-		const char *err = NULL;
-		const char *filename = get_dcc_filename(*msg);
-		if (filename && !can_dcc(client, channel->name, NULL, filename, &err))
-		{
-			strlcpy(errbuf, err, sizeof(errbuf));
-			*errmsg = errbuf;
-			return HOOK_DENY;
-		}
-	}
-	return HOOK_CONTINUE;
-}
-
-/* e->flag.type2:
- * CONF_BAN_TYPE_AKILL		banned by SVSFLINE ('global')
- * CONF_BAN_TYPE_CONF		banned by conf
- * CONF_BAN_TYPE_TEMPORARY	banned by /DCCDENY ('temporary')
- * e->flag.type:
- * DCCDENY_HARD				Fully denied
- * DCCDENY_SOFT				Denied, but allowed to override via /DCCALLOW
- */
-
-/** Make a viewable dcc filename.
- * This is to protect a bit against tricks like 'flood-it-off-the-buffer'
- * and color 1,1 etc...
- */
-static const char *dcc_displayfile(const char *f)
-{
-	static char buf[512];
-	const char *i;
-	char *o = buf;
-	size_t n = strlen(f);
-
-	if (n < 300)
-	{
-		for (i = f; *i; i++)
-			if (*i < 32)
-				*o++ = '?';
-			else
-				*o++ = *i;
-		*o = '\0';
-		return buf;
-	}
-
-	/* Else, we show it as: [first 256 chars]+"[..TRUNCATED..]"+[last 20 chars] */
-	for (i = f; i < f+256; i++)
-		if (*i < 32)
-			*o++ = '?';
-		else
-			*o++ = *i;
-	strcpy(o, "[..TRUNCATED..]");
-	o += sizeof("[..TRUNCATED..]");
-	for (i = f+n-20; *i; i++)
-		if (*i < 32)
-			*o++ = '?';
-		else
-			*o++ = *i;
-	*o = '\0';
-	return buf;
-}
-
-static const char *get_dcc_filename(const char *text)
-{
-	static char filename[BUFSIZE+1];
-	char *end;
-	int size_string;
-
-	if (*text != '\001')
-		return 0;
-
-	if (!strncasecmp(text+1, "DCC SEND ", 9))
-		text = text + 10;
-	else if (!strncasecmp(text+1, "DCC RESUME ", 11))
-		text = text + 12;
-	else
-		return 0;
-
-	for (; *text == ' '; text++); /* skip leading spaces */
-
-	if (*text == '"' && *(text+1))
-		end = strchr(text+1, '"');
-	else
-		end = strchr(text, ' ');
-
-	if (!end || (end < text))
-		return 0;
-
-	size_string = (int)(end - text);
-
-	if (!size_string || (size_string > (BUFSIZE - 1)))
-		return 0;
-
-	strlcpy(filename, text, size_string+1);
-	return filename;
-}
-
-/** Checks if a DCC SEND is allowed.
- * @param client      Sending client
- * @param target      Target name (user or channel)
- * @param targetcli   Target client (NULL in case of channel!)
- * @param text        The entire message
- * @returns 1 if DCC SEND allowed, 0 if rejected
- */
-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];
-	int size_string, ret;
-
-	/* User (IRCOp) may bypass send restrictions */
-	if (ValidatePermissionsForPath("immune:dcc",client,targetcli,NULL,NULL))
-		return 1;
-
-	/* User (IRCOp) likes to receive bad dcc's */
-	if (targetcli && ValidatePermissionsForPath("self:getbaddcc",targetcli,NULL,NULL,NULL))
-		return 1;
-
-	/* Check if user is already blocked (from the past) */
-	if (IsDCCBlock(client))
-	{
-		*errmsg = "*** You are blocked from sending files as you have tried to "
-		          "send a forbidden file - reconnect to regain ability to send";
-		return 0;
-	}
-
-	if (match_spamfilter(client, filename, SPAMF_DCC, "PRIVMSG", target, 0, NULL))
-	{
-		/* Dirty hack, yeah spamfilter already sent the error message :( */
-		*errmsg = "";
-		return 0;
-	}
-
-	if ((fl = dcc_isforbidden(client, filename)))
-	{
-		const char *displayfile = dcc_displayfile(filename);
-
-		RunHook(HOOKTYPE_DCC_DENIED, client, target, filename, displayfile, fl);
-
-		ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
-		*errmsg = errbuf;
-		SetDCCBlock(client);
-		return 0;
-	}
-
-	/* Channel dcc (???) and discouraged? just block */
-	if (!targetcli && ((fl = dcc_isdiscouraged(client, filename))))
-	{
-		ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
-		*errmsg = errbuf;
-		return 0;
-	}
-
-	/* If we get here, the file is allowed */
-	return 1;
-}
-
-/** Checks if a DCC is allowed by DCCALLOW rules (only SOFT bans are checked).
- * PARAMETERS:
- * from:		the sender client (possibly remote)
- * to:			the target client (always local)
- * text:		the whole msg
- * RETURNS:
- * 1:			allowed
- * 0:			block
- */
-static int can_dcc_soft(Client *from, Client *to, const char *filename, const char **errmsg)
-{
-	ConfigItem_deny_dcc *fl;
-	const char *displayfile;
-	static char errbuf[256];
-
-	/* User (IRCOp) may bypass send restrictions */
-	if (ValidatePermissionsForPath("immune:dcc",from,to,NULL,NULL))
-		return 1;
-
-	/* User (IRCOp) likes to receive bad dcc's */
-	if (ValidatePermissionsForPath("self:getbaddcc",to,NULL,NULL,NULL))
-		return 1;
-
-	/* On the 'soft' blocklist ? */
-	if (!(fl = dcc_isdiscouraged(from, filename)))
-		return 1; /* No, so is OK */
-
-	/* If on DCCALLOW list then the user is OK with it */
-	if (on_dccallow_list(to, from))
-		return 1;
-
-	/* Soft-blocked */
-	displayfile = dcc_displayfile(filename);
-
-	ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason);
-	*errmsg = errbuf;
-
-	/* Inform target ('to') about the /DCCALLOW functionality */
-	sendnotice(to, "%s (%s@%s) tried to DCC SEND you a file named '%s', the request has been blocked.",
-		from->name, from->user->username, GetHost(from), displayfile);
-	if (!IsDCCNotice(to))
-	{
-		SetDCCNotice(to);
-		sendnotice(to, "Files like these might contain malicious content (viruses, trojans). "
-			"Therefore, you must explicitly allow anyone that tries to send you such files.");
-		sendnotice(to, "If you trust %s, and want him/her to send you this file, you may obtain "
-			"more information on using the dccallow system by typing '/DCCALLOW HELP'", from->name);
-	}
-	return 0;
-}
-
-/** Checks if the dcc is blacklisted. */
-static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, const char *filename)
-{
-	ConfigItem_deny_dcc *d;
-	ConfigItem_allow_dcc *a;
-
-	if (!conf_deny_dcc || !filename)
-		return NULL;
-
-	for (d = conf_deny_dcc; d; d = d->next)
-	{
-		if ((d->flag.type == DCCDENY_HARD) && match_simple(d->filename, filename))
-		{
-			for (a = conf_allow_dcc; a; a = a->next)
-			{
-				if ((a->flag.type == DCCDENY_HARD) && match_simple(a->filename, filename))
-					return NULL;
-			}
-			return d;
-		}
-	}
-
-	return NULL;
-}
-
-/** checks if the dcc is discouraged ('soft bans'). */
-static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, const char *filename)
-{
-	ConfigItem_deny_dcc *d;
-	ConfigItem_allow_dcc *a;
-
-	if (!conf_deny_dcc || !filename)
-		return NULL;
-
-	for (d = conf_deny_dcc; d; d = d->next)
-	{
-		if ((d->flag.type == DCCDENY_SOFT) && match_simple(d->filename, filename))
-		{
-			for (a = conf_allow_dcc; a; a = a->next)
-			{
-				if ((a->flag.type == DCCDENY_SOFT) && match_simple(a->filename, filename))
-					return NULL;
-			}
-			return d;
-		}
-	}
-
-	return NULL;
-}
-
-static void DCCdeny_add(const char *filename, const char *reason, int type, int type2)
-{
-	ConfigItem_deny_dcc *deny = NULL;
-
-	deny = safe_alloc(sizeof(ConfigItem_deny_dcc));
-	safe_strdup(deny->filename, filename);
-	safe_strdup(deny->reason, reason);
-	deny->flag.type = type;
-	deny->flag.type2 = type2;
-	AddListItem(deny, conf_deny_dcc);
-}
-
-static void DCCdeny_del(ConfigItem_deny_dcc *deny)
-{
-	DelListItem(deny, conf_deny_dcc);
-	safe_free(deny->filename);
-	safe_free(deny->reason);
-	safe_free(deny);
-}
-
-ConfigItem_deny_dcc *find_deny_dcc(const char *name)
-{
-	ConfigItem_deny_dcc	*e;
-
-	if (!name)
-		return NULL;
-
-	for (e = conf_deny_dcc; e; e = e->next)
-	{
-		if (match_simple(name, e->filename))
-			return e;
-	}
-	return NULL;
-}
-
-static void dcc_wipe_services(void)
-{
-	ConfigItem_deny_dcc *dconf, *next;
-
-	for (dconf = conf_deny_dcc; dconf; dconf = next)
-	{
-		next = dconf->next;
-		if (dconf->flag.type2 == CONF_BAN_TYPE_AKILL)
-		{
-			DelListItem(dconf, conf_deny_dcc);
-			safe_free(dconf->filename);
-			safe_free(dconf->reason);
-			safe_free(dconf);
-		}
-	}
-
-}
-
-int dccdeny_stats(Client *client, const char *para)
-{
-	ConfigItem_deny_dcc *denytmp;
-	ConfigItem_allow_dcc *allowtmp;
-	char *filemask, *reason;
-	char a = 0;
-
-	/* '/STATS F' or '/STATS denydcc' is for us... */
-	if (strcmp(para, "F") && strcasecmp(para, "denydcc"))
-		return 0;
-
-	for (denytmp = conf_deny_dcc; denytmp; denytmp = denytmp->next)
-	{
-		filemask = BadPtr(denytmp->filename) ? "<NULL>" : denytmp->filename;
-		reason = BadPtr(denytmp->reason) ? "<NULL>" : denytmp->reason;
-		if (denytmp->flag.type2 == CONF_BAN_TYPE_CONF)
-			a = 'c';
-		if (denytmp->flag.type2 == CONF_BAN_TYPE_AKILL)
-			a = 's';
-		if (denytmp->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
-			a = 'o';
-		/* <d> <s|h> <howadded> <filemask> <reason> */
-		sendtxtnumeric(client, "d %c %c %s %s", (denytmp->flag.type == DCCDENY_SOFT) ? 's' : 'h',
-			a, filemask, reason);
-	}
-	for (allowtmp = conf_allow_dcc; allowtmp; allowtmp = allowtmp->next)
-	{
-		filemask = BadPtr(allowtmp->filename) ? "<NULL>" : allowtmp->filename;
-		if (allowtmp->flag.type2 == CONF_BAN_TYPE_CONF)
-			a = 'c';
-		if (allowtmp->flag.type2 == CONF_BAN_TYPE_AKILL)
-			a = 's';
-		if (allowtmp->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
-			a = 'o';
-		/* <a> <s|h> <howadded> <filemask> */
-		sendtxtnumeric(client, "a %c %c %s", (allowtmp->flag.type == DCCDENY_SOFT) ? 's' : 'h',
-			a, filemask);
-	}
-	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
@@ -1,107 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/echo-message.c
- *   (C) 2019 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"echo-message",
-	"5.0",
-	"echo-message CAP",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Variables */
-long CAP_ECHO_MESSAGE = 0L;
-
-/* Forward declarations */
-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()
-{
-	ClientCapabilityInfo cap;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "echo-message";
-	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_ECHO_MESSAGE);
-
-	HookAdd(modinfo->handle, HOOKTYPE_CHANMSG, 0, em_chanmsg);
-	HookAdd(modinfo->handle, HOOKTYPE_USERMSG, 0, em_usermsg);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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))
-	{
-		if (sendtype != SEND_TYPE_TAGMSG)
-		{
-			sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
-				client->name,
-				sendtype_to_cmd(sendtype),
-				target,
-				text);
-		} else {
-			sendto_prefix_one(client, client, mtags, ":%s %s %s",
-				client->name,
-				sendtype_to_cmd(sendtype),
-				target);
-		}
-	}
-	return 0;
-}
-
-int em_usermsg(Client *client, Client *to, MessageTag *mtags, const char *text, SendType sendtype)
-{
-	if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
-	{
-		if (sendtype != SEND_TYPE_TAGMSG)
-		{
-			sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
-				client->name,
-				sendtype_to_cmd(sendtype),
-				to->name,
-				text);
-		} else {
-			sendto_prefix_one(client, client, mtags, ":%s %s %s",
-				client->name,
-				sendtype_to_cmd(sendtype),
-				to->name);
-		}
-	}
-	return 0;
-}
diff --git a/src/modules/eos.c b/src/modules/eos.c
@@ -1,71 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_eos);
-
-#define MSG_EOS 	"EOS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"eos",
-	"5.0",
-	"command /eos", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_EOS, cmd_eos, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * EOS (End Of Sync) command.
- * Type: Broadcast
- * Purpose: Broadcasted over a network if a server is synced (after the users, channels,
- *          etc are introduced). Makes us able to know if a server is linked.
- * History: Added in beta18 (in cvs since 2003-08-11) by Syzop
- */
-CMD_FUNC(cmd_eos)
-{
-	if (!IsServer(client))
-		return;
-	client->server->flags.synced = 1;
-	/* pass it on ^_- */
-	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
@@ -1,56 +0,0 @@
-#************************************************************************
-#*   IRC - Internet Relay Chat, src/modules/chanmodes/Makefile
-#*   Copyright (C) Carsten V. Munk 2001 & The UnrealIRCd Team
-#*
-#*   This program is free software; you can redistribute it and/or modify
-#*   it under the terms of the GNU General Public License as published by
-#*   the Free Software Foundation; either version 1, or (at your option)
-#*   any later version.
-#*
-#*   This program is distributed in the hope that it will be useful,
-#*   but WITHOUT ANY WARRANTY; without even the implied warranty of
-#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#*   GNU General Public License for more details.
-#*
-#*   You should have received a copy of the GNU General Public License
-#*   along with this program; if not, write to the Free Software
-#*   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#*/
-
-CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
-
-INCLUDES = ../../include/channel.h \
-	../../include/common.h ../../include/config.h ../../include/dbuf.h \
-	../../include/dynconf.h ../../include/fdlist.h ../../include/h.h \
-	../../include/ircsprintf.h \
-	../../include/license.h \
-	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
-	../../include/numeric.h ../../include/dns.h \
-	../../include/resource.h ../../include/setup.h \
-	../../include/struct.h ../../include/sys.h \
-	../../include/types.h \
-	../../include/version.h ../../include/whowas.h
-
-R_MODULES= \
-	join.so quiet.so nickchange.so inchannel.so realname.so \
-	account.so operclass.so certfp.so textban.so msgbypass.so \
-	timedban.so partmsg.so securitygroup.so \
-	country.so flood.so
-
-MODULES=$(R_MODULES)
-MODULEFLAGS=@MODULEFLAGS@
-RM=@RM@
-
-.SUFFIXES:
-.SUFFIXES: .c .h .so
-
-all: build
-
-build: $(MODULES)
-
-clean:
-	$(RM) -f *.o *.so *~ core
-
-%.so: %.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o $@ $<
diff --git a/src/modules/extbans/account.c b/src/modules/extbans/account.c
@@ -1,119 +0,0 @@
-/*
- * Extended ban to ban/exempt by services account (~b ~a:accountname)
- * (C) Copyright 2011-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/account",
-	"4.2",
-	"ExtBan ~a - Ban/exempt by services account name",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-const char *extban_account_conv_param(BanContext *b, Extban *extban);
-int extban_account_is_banned(BanContext *b);
-
-Extban *register_account_extban(ModuleInfo *modinfo)
-{
-	ExtbanInfo req;
-
-	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;
-	return ExtbanAdd(modinfo->handle, req);
-}
-
-/** Called upon module test */
-MOD_TEST()
-{
-	if (!register_account_extban(modinfo))
-	{
-		config_error("could not register extended ban type");
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-/** Called upon module init */
-MOD_INIT()
-{
-	if (!register_account_extban(modinfo))
-	{
-		config_error("could not register extended ban type");
-		return MOD_FAILED;
-	}
-
-	ISupportAdd(modinfo->handle, "ACCOUNTEXTBAN", "account,a");
-
-	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;
-}
-
-/** Account bans */
-const char *extban_account_conv_param(BanContext *b, Extban *extban)
-{
-	char *mask, *acc;
-	static char retbuf[NICKLEN + 4];
-
-	strlcpy(retbuf, b->banstr, sizeof(retbuf)); /* truncate */
-
-	acc = retbuf;
-	if (!*acc)
-		return NULL; /* don't allow "~a:" */
-
-	return retbuf;
-}
-
-int extban_account_is_banned(BanContext *b)
-{
-	/* ~a:0 is special and matches all unauthenticated users */
-	if (!strcmp(b->banstr, "0"))
-		return IsLoggedIn(b->client) ? 0 : 1;
-
-	/* ~a:* matches all authenticated users
-	 * (Yes this special code is needed because account
-	 *  is 0 or * for unauthenticated users)
-	 */
-	if (!strcmp(b->banstr, "*"))
-		return IsLoggedIn(b->client) ? 1 : 0;
-
-	if (b->client->user && 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
@@ -1,142 +0,0 @@
-/*
- * Extended ban to ban/exempt by certificate fingerprint (+b ~S:certfp)
- * (C) Copyright 2015 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/certfp",
-	"4.2",
-	"ExtBan ~S - Ban/exempt by SHA256 TLS certificate fingerprint",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-int extban_certfp_is_ok(BanContext *b);
-const char *extban_certfp_conv_param(BanContext *b, Extban *extban);
-int extban_certfp_is_banned(BanContext *b);
-
-Extban *register_certfp_extban(ModuleInfo *modinfo)
-{
-	ExtbanInfo req;
-
-	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;
-	return ExtbanAdd(modinfo->handle, req);
-}
-
-/* Called upon module test */
-MOD_TEST()
-{
-	if (!register_certfp_extban(modinfo))
-	{
-		config_error("could not register extended ban type");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-/* Called upon module init */
-MOD_INIT()
-{
-	if (!register_certfp_extban(modinfo))
-	{
-		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;
-}
-
-#define CERT_FP_LEN 64
-
-int extban_certfp_usage(Client *client)
-{
-	sendnotice(client, "ERROR: ExtBan ~S expects an SHA256 fingerprint in hexadecimal format (no colons). "
-					 "For example: +e ~S:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)");
-	return EX_DENY;
-}
-
-int extban_certfp_is_ok(BanContext *b)
-{
-	if (b->is_ok_check == EXCHK_PARAM)
-	{
-		const char *p;
-
-		if (strlen(b->banstr) != CERT_FP_LEN)
-			return extban_certfp_usage(b->client);
-
-		for (p = b->banstr; *p; p++)
-			if (!isxdigit(*p))
-				return extban_certfp_usage(b->client);
-
-		return EX_ALLOW;
-	}
-	return EX_ALLOW;
-}
-
-/* Obtain targeted certfp from the ban string */
-const char *extban_certfp_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 = tolower(*p);
-	}
-
-	return retbuf;
-}
-
-int extban_certfp_is_banned(BanContext *b)
-{
-	const char *fp = moddata_client_get(b->client, "certfp");
-
-	if (!fp)
-		return 0; /* not using TLS */
-
-	if (!strcmp(b->banstr, fp))
-		return 1;
-
-	return 0;
-}
diff --git a/src/modules/extbans/country.c b/src/modules/extbans/country.c
@@ -1,141 +0,0 @@
-/*
- * 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);
-
-Extban *register_country_extban(ModuleInfo *modinfo)
-{
-	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;
-	return ExtbanAdd(modinfo->handle, req);
-}
-
-/* Called upon module test */
-MOD_TEST()
-{
-	if (!register_country_extban(modinfo))
-	{
-		config_error("could not register extended ban type");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-/* Called upon module init */
-MOD_INIT()
-{
-	if (!register_country_extban(modinfo))
-	{
-		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/flood.c b/src/modules/extbans/flood.c
@@ -1,187 +0,0 @@
-/*
- * Extended ban to exempt from +f/+F checking.
- * Eg: +e ~flood:*:~account:TrustedBot
- * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/flood",
-	"1.0",
-	"Extban ~flood - exempt from +f/+F checks",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/** Maximum length of the ~flood ban exemption */
-#define MAX_FLOODBAN_LENGTH 128
-
-/* Forward declarations */
-int extban_flood_is_banned(BanContext *b);
-int flood_extban_is_ok(BanContext *b);
-const char *flood_extban_conv_param(BanContext *b, Extban *extban);
-
-/** Called upon module init */
-MOD_INIT()
-{
-	ExtbanInfo req;
-
-	memset(&req, 0, sizeof(req));
-	req.letter = 'F';
-	req.name = "flood";
-	req.is_ok = flood_extban_is_ok;
-	req.conv_param = flood_extban_conv_param;
-	req.options = EXTBOPT_ACTMODIFIER;
-	if (!ExtbanAdd(modinfo->handle, req))
-	{
-		config_error("could not register extended ban type 'flood'");
-		return MOD_FAILED;
-	}
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	return MOD_SUCCESS;
-}
-
-/** Called upon module load */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Called upon unload */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Check if letters in 'str' are valid flood-types.
- * TODO: ideally this would call a function in chanmode +F module!!
- */
-static int flood_type_ok(char *str)
-{
-	char *p;
-
-	/* The * (asterisk) simply means ALL. */
-	if (!strcmp(str, "*"))
-		return 1;
-
-	for (p = str; *p; p++)
-		if (!strchr("cjkmntr", *p))
-			return 0;
-
-	return 1;
-}
-
-const char *flood_extban_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[MAX_FLOODBAN_LENGTH+1];
-	char para[MAX_FLOODBAN_LENGTH+1];
-	char tmpmask[MAX_FLOODBAN_LENGTH+1];
-	char *type; /**< Type(s), such as 'j' */
-	char *matchby; /**< Matching method, such as 'n!u@h' */
-	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
-
-	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
-
-	/* ~flood:type:n!u@h   for direct matching
-	 * ~flood:type:~x:.... when calling another bantype
-	 */
-
-	type = para;
-	matchby = strchr(para, ':');
-	if (!matchby || !matchby[1])
-		return NULL;
-	*matchby++ = '\0';
-
-	/* don't verify type(s), already done in is_ok for local clients */
-	//if (!flood_type_ok(type))
-	//	return NULL;
-	/* ... but limit them to a reasonable value :D */
-	if (strlen(type) > 16)
-		return NULL;
-
-	b->banstr = matchby;
-	newmask = extban_conv_param_nuh_or_extban(b, extban);
-	if (BadPtr(newmask))
-		return NULL;
-
-	snprintf(retbuf, sizeof(retbuf), "%s:%s", type, newmask);
-	return retbuf;
-}
-
-int flood_extban_syntax(Client *client, int checkt, char *reason)
-{
-	if (MyUser(client) && (checkt == EXBCHK_PARAM))
-	{
-		sendnotice(client, "Error when setting ban exception: %s", reason);
-		sendnotice(client, " Syntax: +e ~flood:floodtype(s):mask");
-		sendnotice(client, "Example: +e ~flood:*:~account:TrustedUser");
-		sendnotice(client, "Valid flood types are: c, j, k, m, n, t, r, and * for all");
-		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~account, ~certfp, etc.");
-	}
-	return 0; /* FAIL: ban rejected */
-}
-
-int flood_extban_is_ok(BanContext *b)
-{
-	static char para[MAX_FLOODBAN_LENGTH+1];
-	char *type; /**< Type(s), such as 'j' */
-	char *matchby; /**< Matching method, such as 'n!u@h' */
-	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
-
-	/* Always permit deletion */
-	if (b->what == MODE_DEL)
-		return 1;
-
-	if (b->ban_type != EXBTYPE_EXCEPT)
-	{
-		if (b->is_ok_check == EXBCHK_PARAM)
-			sendnotice(b->client, "Ban type ~flood only works with exceptions (+e) and not with bans or invex (+b/+I)");
-		return 0; /* reject */
-	}
-
-	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
-
-	/* ~flood:type:n!u@h   for direct matching
-	 * ~flood:type:~x:.... when calling another bantype
-	 */
-
-	type = para;
-	matchby = strchr(para, ':');
-	if (!matchby || !matchby[1])
-		return flood_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
-	*matchby++ = '\0';
-
-	if (!flood_type_ok(type))
-		return flood_extban_syntax(b->client, b->is_ok_check, "Unknown flood type");
-	if (strlen(type) > 16)
-		return flood_extban_syntax(b->client, b->is_ok_check, "Too many flood types specified");
-
-	b->banstr = matchby;
-	if (extban_is_ok_nuh_extban(b) == 0)
-	{
-		/* This could be anything ranging from:
-		 * invalid n!u@h syntax, unknown (sub)extbantype,
-		 * disabled extban type in conf, too much recursion, etc.
-		 */
-		return flood_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
-	}
-
-	return 1; /* OK */
-}
diff --git a/src/modules/extbans/inchannel.c b/src/modules/extbans/inchannel.c
@@ -1,168 +0,0 @@
-/*
- * Extended ban: "in channel?" (+b ~c:#chan)
- * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/inchannel",
-	"4.2",
-	"ExtBan ~c - banned when in specified channel",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-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;
-	
-	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))
-	{
-		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;
-}
-
-const char *extban_inchannel_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[CHANNELLEN+6];
-	char *chan, *p, symbol='\0';
-
-	strlcpy(retbuf, b->banstr, sizeof(retbuf));
-	chan = retbuf;
-
-	if ((*chan == '+') || (*chan == '%') || (*chan == '%') ||
-	    (*chan == '@') || (*chan == '&') || (*chan == '~'))
-	    chan++;
-
-	if ((*chan != '#') && (*chan != '*') && (*chan != '?'))
-		return NULL;
-
-	if (!valid_channelname(chan))
-		return NULL;
-
-	p = strchr(chan, ':'); /* ~r:#chan:*.blah.net is not allowed (for now) */
-	if (p)
-		*p = '\0';
-
-	/* on a sidenote '#' is allowed because it's a valid channel (atm) */
-	return retbuf;
-}
-
-/* The only purpose of this function is a temporary workaround to prevent a desync.. pfff */
-int extban_inchannel_is_ok(BanContext *b)
-{
-	const char *p = b->banstr;
-
-	if ((b->is_ok_check == EXBCHK_PARAM) && MyUser(b->client) && (b->what == MODE_ADD) && (strlen(b->banstr) > 3))
-	{
-		if ((*p == '+') || (*p == '%') || (*p == '%') ||
-		    (*p == '@') || (*p == '&') || (*p == '~'))
-		    p++;
-
-		if (*p != '#')
-		{
-			sendnotice(b->client, "Please use a # in the channelname (eg: ~c:#*blah*)");
-			return 0;
-		}
-	}
-	return 1;
-}
-
-static int extban_inchannel_compareflags(char symbol, const char *member_modes)
-{
-	const char *required_modes = NULL;
-
-	if (symbol == '+')
-		required_modes = "vhoaq";
-	else if (symbol == '%')
-		required_modes = "hoaq";
-	else if (symbol == '@')
-		required_modes = "oaq";
-	else if (symbol == '&')
-		required_modes = "aq";
-	else if (symbol == '~')
-		required_modes = "q";
-	else
-		return 0; /* unknown prefix character */
-
-	if (check_channel_access_string(member_modes, required_modes))
-		return 1;
-
-	return 0;
-}
-
-int extban_inchannel_is_banned(BanContext *b)
-{
-	Membership *lp;
-	const char *p = b->banstr;
-	char symbol = '\0';
-
-	if (*p != '#')
-	{
-		symbol = *p;
-		p++;
-	}
-
-	for (lp = b->client->user->channel; lp; lp = lp->next)
-	{
-		if (match_esc(p, lp->channel->name))
-		{
-			/* Channel matched, check symbol if needed (+/%/@/etc) */
-			if (symbol)
-			{
-				if (extban_inchannel_compareflags(symbol, lp->member_modes))
-					return 1;
-			} else
-				return 1;
-		}
-	}
-
-	return 0;
-}
-
diff --git a/src/modules/extbans/join.c b/src/modules/extbans/join.c
@@ -1,73 +0,0 @@
-/*
- * Extended ban that affects JOIN only (+b ~j)
- * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/join",
-	"4.2",
-	"Extban ~j - prevent join only",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-int extban_modej_is_banned(BanContext *b);
-
-/** Called upon module init */
-MOD_INIT()
-{
-	ExtbanInfo req;
-	
-	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))
-	{
-		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;
-}
-
-/** This ban that affects JOINs only */
-int extban_modej_is_banned(BanContext *b)
-{
-	return ban_check_mask(b);
-}
diff --git a/src/modules/extbans/msgbypass.c b/src/modules/extbans/msgbypass.c
@@ -1,224 +0,0 @@
-/*
- * Extended ban that allows user to bypass message restrictions
- * (C) Copyright 2017-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/msgbypass",
-	"4.2",
-	"ExtBan ~m - bypass +m/+n/+c/+S/+T (msgbypass)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-int msgbypass_can_bypass(Client *client, Channel *channel, BypassChannelMessageRestrictionType bypass_type);
-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;
-	
-	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.options = EXTBOPT_ACTMODIFIER;
-	if (!ExtbanAdd(modinfo->handle, req))
-	{
-		config_error("could not register extended ban type ~m");
-		return MOD_FAILED;
-	}
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	
-	return MOD_SUCCESS;
-}
-
-/** Called upon module load */
-MOD_LOAD()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION, 0, msgbypass_can_bypass);
-	return MOD_SUCCESS;
-}
-
-/** Called upon unload */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** 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)))
-		{
-			matchby = strchr(type, ':');
-			if (!matchby)
-				continue;
-			matchby++;
-			
-			b->banstr = matchby;
-			if (ban_check_mask(b))
-			{
-				safe_free(b);
-				return HOOK_ALLOW; /* Yes, user may bypass */
-			}
-		}
-	}
-
-	safe_free(b);
-	return HOOK_CONTINUE; /* No, may NOT bypass. */
-}
-
-/** Does this bypass type exist? (eg: 'external') */
-int msgbypass_extban_type_ok(char *type)
-{
-	if (!strcmp(type, "external") ||
-	    !strcmp(type, "moderated") ||
-	    !strcmp(type, "censor") ||
-	    !strcmp(type, "color") ||
-	    !strcmp(type, "notice"))
-	{
-		return 1; /* Yes, OK type */
-	}
-	return 0; /* NOMATCH */
-}
-
-#define MAX_LENGTH 128
-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' */
-	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
-
-	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
-	 */
-
-	type = para;
-	matchby = strchr(para, ':');
-	if (!matchby || !matchby[1])
-		return NULL;
-	*matchby++ = '\0';
-
-	if (!msgbypass_extban_type_ok(type))
-		return NULL;
-
-	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);
-	snprintf(retbuf, sizeof(retbuf), "%s:%s", type, newmask);
-	return retbuf;
-}
-
-int msgbypass_extban_syntax(Client *client, int checkt, char *reason)
-{
-	if (MyUser(client) && (checkt == EXBCHK_PARAM))
-	{
-		sendnotice(client, "Error when setting ban exception: %s", reason);
-		sendnotice(client, " Syntax: +e ~m:type:mask");
-		sendnotice(client, "Example: +e ~m:moderated:~a:TrustedUser");
-		sendnotice(client, "Valid types are: external, moderated, color, notice");
-		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
-	}
-	return 0; /* FAIL: ban rejected */
-}
-
-int msgbypass_extban_is_ok(BanContext *b)
-{
-	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 (b->what == MODE_DEL)
-		return 1;
-	
-	if (b->ban_type != EXBTYPE_EXCEPT)
-	{
-		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, 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
-	 */
-
-	type = para;
-	matchby = strchr(para, ':');
-	if (!matchby || !matchby[1])
-		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
-	*matchby++ = '\0';
-
-	if (!msgbypass_extban_type_ok(type))
-		return msgbypass_extban_syntax(b->client, b->is_ok_check, "Unknown type");
-
-	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(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
@@ -1,76 +0,0 @@
-/*
- * Extended ban that affects nick-changes only (+b ~n)
- * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/nickchange",
-	"4.2",
-	"ExtBan ~n - prevent nick-changes only",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-int extban_nickchange_is_banned(BanContext *b);
-
-/** Called upon module init */
-MOD_INIT()
-{
-	ExtbanInfo req;
-	
-	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))
-	{
-		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;
-}
-
-/** This ban that affects nick-changes only */
-int extban_nickchange_is_banned(BanContext *b)
-{
-	if (check_channel_access(b->client, b->channel, "v"))
-		return 0;
-
-	return ban_check_mask(b);
-}
diff --git a/src/modules/extbans/operclass.c b/src/modules/extbans/operclass.c
@@ -1,101 +0,0 @@
-/*
- * Extended ban type: ban (or rather: exempt or invex) an operclass.
- * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/operclass",
-	"4.2",
-	"ExtBan ~O - Ban/exempt operclass",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-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;
-	
-	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))
-	{
-		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;
-}
-
-
-#define OPERCLASSLEN 64
-
-const char *extban_operclass_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[OPERCLASSLEN + 4];
-	char *p;
-
-	strlcpy(retbuf, b->banstr, sizeof(retbuf));
-
-	/* allow alpha, numeric, -, _, * and ? wildcards */
-	for (p = retbuf; *p; p++)
-		if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_?*", *p))
-			*p = '\0';
-
-	if (retbuf[3] == '\0')
-		return NULL; /* just "~O:" is invalid */
-
-	return retbuf;
-}
-
-int extban_operclass_is_banned(BanContext *b)
-{
-	if (MyUser(b->client) && IsOper(b->client))
-	{
-		const char *operclass = get_operclass(b->client);
-		if (operclass && match_simple(b->banstr, operclass))
-			return 1;
-	}
-
-	return 0;
-}
diff --git a/src/modules/extbans/partmsg.c b/src/modules/extbans/partmsg.c
@@ -1,74 +0,0 @@
-/*
- * Hide Part/Quit message extended ban (+b ~p:nick!user@host)
- * (C) Copyright i <info@servx.org> and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/partmsg",
-	"4.2",
-	"ExtBan ~p - Ban/exempt Part/Quit message",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-int extban_partmsg_is_banned(BanContext *b);
-
-MOD_INIT()
-{
-	ExtbanInfo req;
-
-	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");
-		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_partmsg_is_banned(BanContext *b)
-{
-	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
@@ -1,73 +0,0 @@
-/*
- * Extended ban that affects messages/notices only (+b ~q)
- * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/quiet",
-	"4.2",
-	"ExtBan ~q - prevent messages only (quiet)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-int extban_quiet_is_banned(BanContext *b);
-
-/** Called upon module init */
-MOD_INIT()
-{
-	ExtbanInfo req;
-	
-	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))
-	{
-		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;
-}
-
-/** This ban that affects messages/notices only */
-int extban_quiet_is_banned(BanContext *b)
-{
-	return ban_check_mask(b);
-}
diff --git a/src/modules/extbans/realname.c b/src/modules/extbans/realname.c
@@ -1,114 +0,0 @@
-/*
- * Extended ban to ban based on real name / gecos field (+b ~r)
- * (C) Copyright 2003-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/realname",
-	"4.2",
-	"ExtBan ~r - Ban based on realname/gecos field",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-const char *extban_realname_conv_param(BanContext *b, Extban *extban);
-int extban_realname_is_banned(BanContext *b);
-
-Extban *register_realname_extban(ModuleInfo *modinfo)
-{
-	ExtbanInfo req;
-
-	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_INVEX|EXTBOPT_TKL;
-	return ExtbanAdd(modinfo->handle, req);
-}
-
-/** Called upon module test */
-MOD_TEST()
-{
-	if (!register_realname_extban(modinfo))
-	{
-		config_error("could not register extended ban type");
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-/** Called upon module init */
-MOD_INIT()
-{
-	if (!register_realname_extban(modinfo))
-	{
-		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;
-}
-
-/** Realname bans - conv_param */
-const char *extban_realname_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[REALLEN + 8];
-	char *mask;
-
-	strlcpy(retbuf, b->banstr, sizeof(retbuf));
-
-	mask = retbuf;
-
-	if (!*mask)
-		return NULL; /* don't allow "~r:" */
-
-	if (strlen(mask) > REALLEN)
-		mask[REALLEN] = '\0';
-
-	/* Prevent otherwise confusing extban relationship */
-	if (*mask == '~')
-		*mask = '?';
-
-	return retbuf;
-}
-
-int extban_realname_is_banned(BanContext *b)
-{
-	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
@@ -1,152 +0,0 @@
-/*
- * Extended ban to ban based on security groups such as "unknown-users"
- * (C) Copyright 2020 Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 1, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"extbans/securitygroup",
-	"4.2",
-	"ExtBan ~G - Ban based on security-group",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban);
-int extban_securitygroup_is_ok(BanContext *b);
-int extban_securitygroup_is_banned(BanContext *b);
-
-Extban *register_securitygroup_extban(ModuleInfo *modinfo)
-{
-	ExtbanInfo req;
-
-	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;
-	return ExtbanAdd(modinfo->handle, req);
-}
-
-/** Called upon module test */
-MOD_TEST()
-{
-	if (!register_securitygroup_extban(modinfo))
-	{
-		config_error("could not register extended ban type ~G");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-/** Called upon module init */
-MOD_INIT()
-{
-	if (!register_securitygroup_extban(modinfo))
-	{
-		config_error("could not register extended ban type ~G");
-		return MOD_FAILED;
-	}
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	
-	return MOD_SUCCESS;
-}
-
-/** Called upon module load */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Called upon unload */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Helper function for extban_securitygroup_is_ok() and extban_securitygroup_conv_param()
- * to do ban validation.
- */
-int extban_securitygroup_generic(char *mask, int strict)
-{
-	/* ! at the start means negative match */
-	if (*mask == '!')
-		mask++;
-
-	/* Check if the rest of the security group name is valid */
-	if (strict)
-	{
-		if (!security_group_exists(mask))
-			return 0; /* security group does not exist */
-	} else {
-		if (!security_group_valid_name(mask))
-			return 0; /* invalid characters or too long */
-	}
-
-	if (!*mask)
-		return 0; /* don't allow "~G:" nor "~G:!" */
-
-	return 1;
-}
-
-int extban_securitygroup_is_ok(BanContext *b)
-{
-	if (MyUser(b->client) && (b->what == MODE_ADD) && (b->is_ok_check == EXBCHK_PARAM))
-	{
-		char banbuf[SECURITYGROUPLEN+8];
-		strlcpy(banbuf, b->banstr, sizeof(banbuf));
-		if (!extban_securitygroup_generic(banbuf, 1))
-		{
-			SecurityGroup *s;
-			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(b->client, "%s", s->name);
-			sendnotice(b->client, "unknown-users");
-			sendnotice(b->client, "End of security group list.");
-			return 0;
-		}
-	}
-	return 1;
-}
-
-/** Security group extban - conv_param */
-const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[SECURITYGROUPLEN + 8];
-
-	strlcpy(retbuf, b->banstr, sizeof(retbuf));
-	if (!extban_securitygroup_generic(retbuf, 0))
-		return NULL;
-
-	return retbuf;
-}
-
-/** Is the user banned by ~G:something ? */
-int extban_securitygroup_is_banned(BanContext *b)
-{
-	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
@@ -1,533 +0,0 @@
-/*
- * Text ban. (C) Copyright 2004-2016 Bram Matthys.
- * 
- * 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 2
- * of the License, 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
- */
-
-#include "unrealircd.h"
-
-/** Max number of text bans per channel.
- * This is basically the most important setting. It directly affects
- * how much CPU you want to spend on text processing.
- * For comparison: with 10 textbans of max length (150), and messages said in
- * the channel with max length (~500 bytes), on an A1800+ (1.53GHz) machine
- * this consumes 30 usec per-channel message PEAK/MAX (usec = 1/1000000 of a
- * second), and in normal (non-supersize messages) count on 10-15 usec.
- * Basically this means this allows for like >25000 messages per second at
- * 100% CPU usage in a worth case scenario. Which seems by far sufficient to me.
- * Also note that (naturally) only local clients are processed, only people
- * that do not have halfops or higher, and only channels that have any
- * textbans set.
- * UPDATE: The speed impact for 15 bans per channel is 42 usec PEAK.
- * HINT: If you are hitting the "normal banlimit" before you actually hit this
- *       one, then you might want to tweak the #define MAXBANS and #define
- *       MAXBANLENGTH in include/struct.h. Doubling MAXBANLENGTH is usually
- *       a good idea, and then you can enlarge MAXBANS too a bit if you want to.
- */
-#define MAX_EXTBANT_PER_CHAN     15 /* Max number of ~T bans in a channel. */
-
-/** Max length of a ban.
- * NOTE: This is mainly for 'cosmetic' purposes. Lowering it does not
- *       decrease CPU usage for text processing.
- */
-#define MAX_LENGTH               150 /* Max length of a ban */
-
-/** Allow user@host in the textban? This changes the syntax! */
-#undef UHOSTFEATURE
-
-/** Enable 'censor' support. What this type will do is replace the
- * matched word with "<censored>" (or another word, see later)
- * Like:
- * <Idiot> hey check out my fucking new car
- * will become:
- * <Idiot> hey check out my <censored> new car
- *
- * SPEED: See README
- */
-#define CENSORFEATURE
-
-/** Which censor replace word to use when CENSORFEATURE is enabled. */
-#define CENSORWORD "<censored>"
-
-ModuleHeader MOD_HEADER
-  = {
-	"extbans/textban",
-	"2.2",
-	"ExtBan ~T (textban) by Syzop",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-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()
-{
-	ExtbanInfo req;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&req, 0, sizeof(ExtbanInfo));
-	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_ok = extban_modeT_is_ok;
-
-	if (!ExtbanAdd(modinfo->handle, req))
-	{
-		config_error("textban module: adding extban ~T failed! module NOT loaded");
-		return MOD_FAILED;
-	}
-
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, textban_can_send_to_channel);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-#if defined(CENSORFEATURE) || defined(STRIPFEATURE)
-static char *my_strcasestr(char *haystack, char *needle)
-{
-	int i;
-	int nlength = strlen (needle);
-	int hlength = strlen (haystack);
-
-	if (nlength > hlength)
-		return NULL;
-	if (hlength <= 0)
-		return NULL;
-	if (nlength <= 0)
-		return haystack;
-	for (i = 0; i <= (hlength - nlength); i++)
-	{
-		if (strncasecmp (haystack + i, needle, nlength) == 0)
-			return haystack + i;
-	}
-	return NULL; /* not found */
-}
-
-#define TEXTBAN_WORD_LEFT	0x1
-#define TEXTBAN_WORD_RIGHT	0x2
-
-/* textban_replace:
- * a fast replace routine written by Syzop used for replacing.
- * searches in line for huntw and replaces it with replacew,
- * buf is used for the result and max is sizeof(buf).
- * (Internal assumptions: size of 'buf' is 512 characters or more)
- */
-int textban_replace(int type, char *badword, char *line, char *buf)
-{
-	char *replacew;
-	char *pold = line, *pnew = buf; /* Pointers to old string and new string */
-	char *poldx = line;
-	int replacen;
-	int searchn = -1;
-	char *startw, *endw;
-	char *c_eol = buf + 510 - 1; /* Cached end of (new) line */
-	int cleaned = 0;
-
-	replacew = CENSORWORD;
-	replacen = sizeof(CENSORWORD)-1;
-
-	while (1)
-	{
-		pold = my_strcasestr(pold, badword);
-		if (!pold)
-			break;
-		if (searchn == -1)
-			searchn = strlen(badword);
-		/* Hunt for start of word */
- 		if (pold > line)
- 		{
-			for (startw = pold; (!iswseperator(*startw) && (startw != line)); startw--);
-			if (iswseperator(*startw))
-				startw++; /* Don't point at the space/seperator but at the word! */
-		} else {
-			startw = pold;
-		}
-
-		if (!(type & TEXTBAN_WORD_LEFT) && (pold != startw))
-		{
-			/* not matched */
-			pold++;
-			continue;
-		}
-
-		/* Hunt for end of word
-		 * Fix for bug #4909: word will be at least 'searchn' long so we can skip
-		 * 'searchn' bytes and avoid stopping half-way the badword.
-		 */
-		for (endw = pold+searchn; ((*endw != '\0') && (!iswseperator(*endw))); endw++);
-
-		if (!(type & TEXTBAN_WORD_RIGHT) && (pold+searchn != endw))
-		{
-			/* not matched */
-			pold++;
-			continue;
-		}
-
-		cleaned = 1; /* still too soon? Syzop/20050227 */
-
-		/* Do we have any not-copied-yet data? */
-		if (poldx != startw)
-		{
-			int tmp_n = startw - poldx;
-			if (pnew + tmp_n >= c_eol)
-			{
-				/* Partial copy and return... */
-				memcpy(pnew, poldx, c_eol - pnew);
-				*c_eol = '\0';
-				return 1;
-			}
-
-			memcpy(pnew, poldx, tmp_n);
-			pnew += tmp_n;
-		}
-		/* Now update the word in buf (pnew is now something like startw-in-new-buffer */
-
-		if (replacen)
-		{
-			if ((pnew + replacen) >= c_eol)
-			{
-				/* Partial copy and return... */
-				memcpy(pnew, replacew, c_eol - pnew);
-				*c_eol = '\0';
-				return 1;
-			}
-			memcpy(pnew, replacew, replacen);
-			pnew += replacen;
-		}
-		poldx = pold = endw;
-	}
-	/* Copy the last part */
-	if (*poldx)
-	{
-		strncpy(pnew, poldx, c_eol - pnew);
-		*(c_eol) = '\0';
-	} else {
-		*pnew = '\0';
-	}
-	return cleaned;
-}
-#endif
-
-unsigned int counttextbans(Channel *channel)
-{
-	Ban *ban;
-	unsigned int cnt = 0;
-
-	for (ban = channel->banlist; ban; ban=ban->next)
-		if ((ban->banstr[0] == '~') && (ban->banstr[1] == 'T') && (ban->banstr[2] == ':'))
-			cnt++;
-	for (ban = channel->exlist; ban; ban=ban->next)
-		if ((ban->banstr[0] == '~') && (ban->banstr[1] == 'T') && (ban->banstr[2] == ':'))
-			cnt++;
-	return cnt;
-}
-
-
-int extban_modeT_is_ok(BanContext *b)
-{
-	int n;
-
-	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 ((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(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;
-}
-
-char *conv_pattern_asterisks(const char *pattern)
-{
-	static char buf[512];
-	char missing_prefix = 0, missing_suffix = 0;
-	if (*pattern != '*')
-		missing_prefix = 1;
-	if (*pattern && (pattern[strlen(pattern)-1] != '*'))
-		missing_suffix = 1;
-	snprintf(buf, sizeof(buf), "%s%s%s",
-		missing_prefix ? "*" : "",
-		pattern,
-		missing_suffix ? "*" : "");
-	return buf;
-}
-
-/** Ban callbacks */
-const char *extban_modeT_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[MAX_LENGTH+1];
-	char para[MAX_LENGTH+1], *action, *text, *p;
-#ifdef UHOSTFEATURE
-	char *uhost;
-	int ap = 0;
-#endif
-
-	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
-	 */
-
-#ifdef UHOSTFEATURE
-	action = strchr(para, ':');
-	if (!action)
-		return NULL;
-	*action++ = '\0';
-	if (!*action)
-		return NULL;
-	text = strchr(action, ':');
-	if (!text || !text[1])
-		return NULL;
-	*text++ = '\0';
-	uhost = para;
-
-	for (p = uhost; *p; p++)
-	{
-		if (*p == '@')
-			ap++;
-		else if ((*p <= ' ') || (*p > 128))
-			return NULL; /* cannot be in a username/host */
-	}
-	if (ap != 1)
-		return NULL; /* no @ */
-#else
-	text = strchr(para, ':');
-	if (!text)
-		return NULL;
-	*text++ = '\0';
-	/* para=action, text=text */
-	if (!*text)
-		return NULL; /* empty text */
-	action = para;
-#endif
-
-	/* ~T:<action>:<text> */
-	if (!strcasecmp(action, "block"))
-	{
-		action = "block"; /* ok */
-		text = conv_pattern_asterisks(text);
-	}
-#ifdef CENSORFEATURE
-	else if (!strcasecmp(action, "censor"))
-	{
-		char *p;
-		action = "censor";
-		for (p = text; *p; p++)
-			if ((*p == '*') && !(p == text) && !(p[1] == '\0'))
-				return NULL; /* can only be *word, word* or *word* or word */
-		if (!strcmp(p, "*") || !strcmp(p, "**"))
-			return NULL; /* cannot match everything ;p */
-	}
-#endif
-	else
-		return NULL; /* unknown action */
-
-	/* check the string.. */
-	for (p=text; *p; p++)
-	{
-		if ((*p == '\003') || (*p == '\002') || 
-		    (*p == '\037') || (*p == '\026') ||
-		    (*p == ' '))
-		{
-			return NULL; /* codes not permitted, would be confusing since they are stripped */
-		}
-	}
-
-	/* Rebuild the string.. can be cut off if too long. */
-#ifdef UHOSTFEATURE
-	snprintf(retbuf, sizeof(retbuf), "%s:%s:%s", uhost, action, text);
-#else
-	snprintf(retbuf, sizeof(retbuf), "%s:%s", action, text);
-#endif
-	return retbuf;
-}
-
-/** Check for text bans (censor and block) */
-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 (check_channel_access(client, channel, "hoaq"))
-		return HOOK_CONTINUE;
-
-	/* IRCOps with these privileges bypass textbans too */
-	if (op_can_override("channel:override:message:ban", client, channel, NULL))
-		return HOOK_CONTINUE;
-
-	/* Now we have to manually walk the banlist and check if things match */
-	for (ban = channel->banlist; ban; ban=ban->next)
-	{
-		char *banstr = ban->banstr;
-
-		/* Pretend time does not exist... */
-		if (!strncmp(banstr, "~t:", 3))
-		{
-			banstr = strchr(banstr+3, ':');
-			if (!banstr)
-				continue;
-			banstr++;
-		}
-		else if (!strncmp(banstr, "~time:", 6))
-		{
-			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;
-		}
-	}
-
-	return HOOK_CONTINUE;
-}
-
-
-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;
-	const char *p;
-#ifdef UHOSTFEATURE
-	char buf[512], uhost[USERLEN + HOSTLEN + 16];
-#endif
-	char tmp[1024], *word;
-	int type;
-
-	/* We can only filter on non-NULL text of course */
-	if ((msg == NULL) || (*msg == NULL))
-		return 0;
-
-	filtered[0] = '\0'; /* NOT needed, but... :P */
-
-#ifdef UHOSTFEATURE
-	ircsprintf(uhost, "%s@%s", client->user->username, GetHost(client));
-#endif
-	strlcpy(filtered, StripControlCodes(*msg), sizeof(filtered));
-
-	p = strchr(ban, ':');
-	if (!p)
-		return 0; /* "impossible" */
-	p++;
-#ifdef UHOSTFEATURE
-	/* First.. deal with userhost... */
-	strcpy(buf, p);
-	p = strchr(buf, ':');
-	if (!p)
-		return 0; /* invalid format */
-	*p++ = '\0';
-
-	if (match_simple(buf, uhost))
-#else
-	if (1)
-#endif
-	{
-		if (!strncasecmp(p, "block:", 6))
-		{
-			if (match_simple(p+6, filtered))
-			{
-				if (errmsg)
-					*errmsg = "Message blocked due to a text ban";
-				return 1; /* BLOCK */
-			}
-		}
-#ifdef CENSORFEATURE
-		else if (!strncasecmp(p, "censor:", 7))
-		{
-			parse_word(p+7, &word, &type);
-			if (textban_replace(type, word, filtered, tmp))
-			{
-				strlcpy(filtered, tmp, sizeof(filtered));
-				cleaned = 1;
-			}
-		}
-#endif
-	}
-
-	if (cleaned)
-	{
-		/* check for null string */
-		char *p;
-		for (p = filtered; *p; p++)
-		{
-			if (*p != ' ')
-			{
-				strlcpy(retbuf, filtered, sizeof(retbuf));
-				*msg = retbuf;
-				return 0; /* allow through, but filtered */
-			}
-		}
-		return 1; /* nothing but spaces found.. */
-	}
-	return 0; /* nothing blocked */
-}
-
-#ifdef CENSORFEATURE
-void parse_word(const char *s, char **word, int *type)
-{
-	static char buf[512];
-	const char *tmp;
-	int len;
-	int tpe = 0;
-	char *o = buf;
-
-	for (tmp = s; *tmp; tmp++)
-	{
-		if (*tmp != '*')
-			*o++ = *tmp;
-		else
-		{
-			if (s == tmp)
-				tpe |= TEXTBAN_WORD_LEFT;
-			if (*(tmp + 1) == '\0')
-				tpe |= TEXTBAN_WORD_RIGHT;
-		}
-	}
-	*o = '\0';
-
-	*word = buf;
-	*type = tpe;
-}
-#endif
diff --git a/src/modules/extbans/timedban.c b/src/modules/extbans/timedban.c
@@ -1,506 +0,0 @@
-/*
- * timedban - Timed bans that are automatically unset.
- * (C) Copyright 2009-2017 Bram Matthys (Syzop) and the UnrealIRCd team.
- * License: GPLv2 or later
- *
- * This module adds an extended ban ~t:time:mask
- * Where 'time' is the time in minutes after which the ban will be removed.
- * Where 'mask' is any banmask that is normally valid.
- *
- * Note that this extended ban is rather special in the sense that
- * it permits (crazy) triple-extbans to be set, such as:
- * +b ~t:1:~q:~a:Account
- * (=a temporary 1min ban to mute a user with services account Account)
- * +e ~t:1440:~m:moderated:*!*@host
- * (=user with *!*@host may speak through +m for the next 1440m / 24h)
- *
- * The triple-extbans / double-stacking requires special routines that
- * are based on parts of the core and special recursion checks.
- * If you are looking for inspiration of coding your own extended ban
- * then look at another extended ban * module as this module is not a
- * good starting point ;)
- */
-   
-#include "unrealircd.h"
-
-/* Maximum time (in minutes) for a ban */
-#define TIMEDBAN_MAX_TIME	9999
-
-/* Maximum length of a ban */
-#define MAX_LENGTH 128
-
-/* Split timeout event in <this> amount of iterations */
-#define TIMEDBAN_TIMER_ITERATION_SPLIT 4
-
-/* Call timeout event every <this> seconds.
- * NOTE: until all channels are processed it takes
- *       TIMEDBAN_TIMER_ITERATION_SPLIT * TIMEDBAN_TIMER.
- */
-#define TIMEDBAN_TIMER	2
-
-/* We allow a ban to (potentially) expire slightly before the deadline.
- * For example with TIMEDBAN_TIMER_ITERATION_SPLIT=4 and TIMEDBAN_TIMER=2
- * a 1 minute ban would expire at 56-63 seconds, rather than 60-67 seconds.
- * This is usually preferred.
- */
-#define TIMEDBAN_TIMER_DELTA ((TIMEDBAN_TIMER_ITERATION_SPLIT*TIMEDBAN_TIMER)/2)
-
-ModuleHeader MOD_HEADER
-  = {
-	"extbans/timedban",
-	"1.0",
-	"ExtBan ~t: automatically removed timed bans",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-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);
-
-EVENT(timedban_timeout);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ExtbanInfo extban;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&extban, 0, sizeof(ExtbanInfo));
-	extban.letter = 't';
-	extban.name = "time";
-	extban.options |= EXTBOPT_ACTMODIFIER; /* not really, but ours shouldn't be stacked from group 1 */
-	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))
-	{
-		config_error("timedban: unable to register 't' extban type!!");
-		return MOD_FAILED;
-	}
-                
-	EventAdd(modinfo->handle, "timedban_timeout", timedban_timeout, NULL, TIMEDBAN_TIMER*1000, 0);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** 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? ;)
- */
-const char *generic_clean_ban_mask(BanContext *b, Extban *extban)
-{
-	char *cp, *x;
-	static char maskbuf[512];
-	char *mask;
-
-	/* Work on a copy */
-	strlcpy(maskbuf, b->banstr, sizeof(maskbuf));
-	mask = maskbuf;
-
-	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 <= ' ')
-			return NULL;
-
-	/* Extended ban? */
-	if (is_extended_ban(mask))
-	{
-		const char *nextbanstr;
-		Extban *extban = findmod_by_bantype(mask, &nextbanstr);
-		if (!extban)
-			return NULL; /* reject unknown extban */
-		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 */
-		if (strlen(mask) > 80)
-			mask[80] = '\0';
-		return mask;
-	}
-
-	return convert_regular_ban(mask, NULL, 0);
-}
-
-/** Convert ban to an acceptable format (or return NULL to fully reject it) */
-const char *timedban_extban_conv_param(BanContext *b, Extban *extban)
-{
-	static char retbuf[MAX_LENGTH+1];
-	char para[MAX_LENGTH+1];
-	char tmpmask[MAX_LENGTH+1];
-	char *durationstr; /**< Duration, such as '5' */
-	int duration;
-	char *matchby; /**< 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, 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
-	 */
-
-	durationstr = para;
-	matchby = strchr(para, ':');
-	if (!matchby || !matchby[1])
-		return NULL;
-	*matchby++ = '\0';
-	
-	duration = atoi(durationstr);
-
-	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
-		return NULL;
-
-	strlcpy(tmpmask, matchby, sizeof(tmpmask));
-	timedban_extban_conv_param_recursion++;
-	//newmask = extban_conv_param_nuh_or_extban(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), "%d:%s", duration, newmask);
-	return retbuf;
-}
-
-int timedban_extban_syntax(Client *client, int checkt, char *reason)
-{
-	if (MyUser(client) && (checkt == EXBCHK_PARAM))
-	{
-		sendnotice(client, "Error when setting timed ban: %s", reason);
-		sendnotice(client, " Syntax: +b ~t:duration:mask");
-		sendnotice(client, "Example: +b ~t:5:nick!user@host");
-		sendnotice(client, "Duration is the time in minutes after which the ban is removed (1-9999)");
-		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
-	}
-	return 0; /* FAIL: ban rejected */
-}
-
-/** Generic helper for sub-bans, used by our "is this ban ok?" function */
-int generic_ban_is_ok(BanContext *b)
-{
-	if ((b->banstr[0] == '~') && MyUser(b->client))
-	{
-		Extban *extban;
-		const char *nextbanstr;
-
-		/* This portion is copied from clean_ban_mask() */
-		if (is_extended_ban(b->banstr) && MyUser(b->client))
-		{
-			if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL))
-			{
-				if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
-				{
-					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, b->banstr[1]))
-				{
-					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 */
-			extban = findmod_by_bantype(b->banstr, &nextbanstr);
-			if (extban && extban->is_ok)
-			{
-				b->banstr = nextbanstr;
-				if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR))
-				{
-					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)
-				{
-					if (!extban->is_ok(b))
-					{
-						return 0; /* REJECT */
-					}
-				}
-			}
-		}
-	}
-	
-	/* ACCEPT:
-	 * - not an extban; OR
-	 * - extban with NULL is_ok; OR
-	 * - non-existing extban character (handled by conv_param?)
-	 */
-	return 1;
-}
-
-/** Validate ban ("is this ban ok?") */
-int timedban_extban_is_ok(BanContext *b)
-{
-	char para[MAX_LENGTH+1];
-	char tmpmask[MAX_LENGTH+1];
-	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' */
-	static int timedban_extban_is_ok_recursion = 0;
-	int res;
-
-	/* Always permit deletion */
-	if (b->what == MODE_DEL)
-		return 1;
-
-	if (timedban_extban_is_ok_recursion)
-		return 0; /* Recursion detected (~t:1:~t:....) */
-
-	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
-	 */
-
-	durationstr = para;
-	matchby = strchr(para, ':');
-	if (!matchby || !matchby[1])
-		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(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(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)
-	{
-		/* This could be anything ranging from:
-		 * invalid n!u@h syntax, unknown (sub)extbantype,
-		 * disabled extban type in conf, too much recursion, etc.
-		 */
-		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(BanContext *b)
-{
-	b->banstr = strchr(b->banstr, ':'); /* skip time argument */
-	if (!b->banstr)
-		return 0; /* invalid fmt */
-	b->banstr++; /* skip over final semicolon */
-
-	return ban_check_mask(b);
-}
-
-/** Helper to check if the ban has been expired.
- */
-int timedban_has_ban_expired(Ban *ban)
-{
-	char *banstr = ban->banstr;
-	char *p1, *p2;
-	int t;
-	time_t expire_on;
-
-	/* 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 */
-	p2 = strchr(p1+1, ':'); /* skip time argument */
-	if (!p2)
-		return 0; /* invalid fmt */
-	*p2 = '\0'; /* danger.. must restore!! */
-	t = atoi(p1);
-	*p2 = ':'; /* restored.. */
-	
-	expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA;
-	
-	if (expire_on < TStime())
-		return 1;
-	return 0;
-}
-
-static char mbuf[512];
-static char pbuf[512];
-
-/** This removes any expired timedbans */
-EVENT(timedban_timeout)
-{
-	Channel *channel;
-	Ban *ban, *nextban;
-	static int current_iteration = 0;
-
-	if (++current_iteration >= TIMEDBAN_TIMER_ITERATION_SPLIT)
-		current_iteration = 0;
-
-	for (channel = channels; channel; channel = channel->nextch)
-	{
-		/* This is a very quick check, at the cost of it being
-		 * biased since there's always a tendency of more channel
-		 * names to start with one specific letter. But hashing
-		 * is too costly. So we stick with this. It should be
-		 * good enough. Alternative would be some channel->id value.
-		 */
-		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", 2) && timedban_has_ban_expired(ban))
-			{
-				add_send_mode_param(channel, &me, '-',  'b', ban->banstr);
-				del_listmode(&channel->banlist, channel, ban->banstr);
-			}
-		}
-		for (ban = channel->exlist; ban; ban=nextban)
-		{
-			nextban = ban->next;
-			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);
-			}
-		}
-		for (ban = channel->invexlist; ban; ban=nextban)
-		{
-			nextban = ban->next;
-			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);
-			}
-		}
-		if (*pbuf)
-		{
-			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->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;
-		}
-	}
-}
-
-#if MODEBUFLEN > 512
- #error "add_send_mode_param() is not made for MODEBUFLEN > 512"
-#endif
-
-void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param) {
-	static char *modes = NULL, lastwhat;
-	static short count = 0;
-	short send = 0;
-	
-	if (!modes) modes = mbuf;
-	
-	if (!mbuf[0]) {
-		modes = mbuf;
-		*modes++ = what;
-		*modes = 0;
-		lastwhat = what;
-		*pbuf = 0;
-		count = 0;
-	}
-	if (lastwhat != what) {
-		*modes++ = what;
-		*modes = 0;
-		lastwhat = what;
-	}
-	if (strlen(pbuf) + strlen(param) + 11 < MODEBUFLEN) {
-		if (*pbuf) 
-			strcat(pbuf, " ");
-		strcat(pbuf, param);
-		*modes++ = mode;
-		*modes = 0;
-		count++;
-	}
-	else if (*pbuf) 
-		send = 1;
-
-	if (count == MAXMODEPARAMS)
-		send = 1;
-
-	if (send)
-	{
-		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->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;
-		modes = mbuf;
-		*modes++ = what;
-		lastwhat = what;
-		if (count != MAXMODEPARAMS)
-		{
-			strlcpy(pbuf, param, sizeof(pbuf));
-			*modes++ = mode;
-			count = 1;
-		} else {
-			count = 0;
-		}
-		*modes = 0;
-	}
-}
diff --git a/src/modules/extended-monitor.c b/src/modules/extended-monitor.c
@@ -1,153 +0,0 @@
-/*
- *   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_change(Client *client, const char *olduser, const char *oldhost);
-int extended_monitor_realname_change(Client *client, const char *oldinfo);
-int extended_monitor_notification(Client *client, Watch *watch, Link *lp, int event);
-
-ModuleHeader MOD_HEADER
-  = {
-	"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 = "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_CHANGE, 0, extended_monitor_userhost_change);
-	HookAdd(modinfo->handle, HOOKTYPE_REALNAME_CHANGE, 0, extended_monitor_realname_change);
-
-	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_change(Client *client, const char *olduser, const char *oldhost)
-{
-	watch_check(client, WATCH_EVENT_USERHOST, extended_monitor_notification);
-	return 0;
-}
-
-int extended_monitor_realname_change(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
@@ -1,1151 +0,0 @@
-/*
- *   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-tag.c b/src/modules/geoip-tag.c
@@ -1,108 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/geoip-tag.c
- *   (C) 2022 westor, 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.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"geoip-tag",
-	"6.0",
-	"geoip message tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Forward declarations */
-int geoip_mtag_is_ok(Client *client, const char *name, const char *value);
-int geoip_mtag_should_send_to_client(Client *target);
-void mtag_add_geoip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
-
-MOD_INIT()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "unrealircd.org/geoip";
-	mtag.is_ok = geoip_mtag_is_ok;
-	mtag.should_send_to_client = geoip_mtag_should_send_to_client;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_geoip);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending
- * 'geoip-tag' is permitted to do so and uses a permitted
- * syntax.
- * We simply allow geoip-tag ONLY from servers and with any syntax.
- */
-int geoip_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client))
-		return 1;
-
-	return 0;
-}
-
-void mtag_add_geoip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m;
-	
-	GeoIPResult *geoip;
-
-	if (IsUser(client) && ((geoip = geoip_client(client))))
-	{
-		MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/geoip");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-		} else {
-			m = safe_alloc(sizeof(MessageTag));
-			safe_strdup(m->name, "unrealircd.org/geoip");
-			safe_strdup(m->value, geoip->country_code);
-		}
-		AddListItem(m, *mtag_list);
-	}
-}
-
-/** Outgoing filter for this message tag */
-int geoip_mtag_should_send_to_client(Client *target)
-{
-	if (IsServer(target) || IsOper(target))
-		return 1;
-
-	return 0;
-}
diff --git a/src/modules/geoip_base.c b/src/modules/geoip_base.c
@@ -1,351 +0,0 @@
-/*
- * 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 or later
- */
-
-#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_ip_change(Client *client, const char *oldip);
-int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list);
-int geoip_connect_extinfo(Client *client, NameValuePrioList **list);
-int geoip_json_expand_client(Client *client, int detail, json_t *j);
-int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
-EVENT(geoip_base_set_existing_users_evt);
-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_IP_CHANGE, 0, geoip_base_ip_change);
-	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, geoip_base_handshake);
-	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 1, geoip_connect_extinfo); /* (prio: near-first) */
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0,geoip_base_handshake); /* in case the IP changed in registration phase (WEBIRC, HTTP Forwarded) */
-	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois);
-	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, geoip_json_expand_client);
-
-	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;
-}
-
-int geoip_base_ip_change(Client *client, const char *oldip)
-{
-	geoip_base_handshake(client);
-	return 0;
-}
-
-void geoip_base_free(ModData *m)
-{
-	if (m->ptr)
-	{
-		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)
-	{
-		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 (MyUser(client))
-			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_json_expand_client(Client *client, int detail, json_t *j)
-{
-	GeoIPResult *geo = GEOIPDATA(client);
-	json_t *geoip;
-
-	if (!geo)
-		return 0;
-
-	geoip = json_object();
-	json_object_set_new(j, "geoip", geoip);
-	json_object_set_new(geoip, "country_code", json_string_unreal(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
@@ -1,297 +0,0 @@
-/* GEOIP Classic module
- * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#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
@@ -1,838 +0,0 @@
-/*
- *   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
@@ -1,239 +0,0 @@
-/* GEOIP maxmind module
- * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#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
@@ -1,85 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_globops);
-
-#define MSG_GLOBOPS 	"GLOBOPS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"globops",
-	"5.0",
-	"command /globops", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_GLOBOPS, cmd_globops, 1, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Write message to IRCOps.
- * parv[1] = message text
- */
-CMD_FUNC(cmd_globops)
-{
-	const char *message = parc > 1 ? parv[1] : NULL;
-
-	if (BadPtr(message))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "GLOBOPS");
-		return;
-	}
-
-	if (MyUser(client) && !ValidatePermissionsForPath("chat:globops",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (MyUser(client))
-	{
-		/* Easy */
-		sendto_umode_global(UMODE_OPER, "from %s: %s", client->name, message);
-	} else
-	{
-		/* Backward-compatible (3.2.x) */
-		sendto_umode(UMODE_OPER, "from %s: %s", client->name, message);
-		sendto_server(client, 0, 0, NULL, ":%s SENDUMODE o :from %s: %s",
-		    me.id, client->name, message);
-	}
-}
diff --git a/src/modules/help.c b/src/modules/help.c
@@ -1,145 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_help);
-
-#define MSG_HELP 	"HELP"	
-#define MSG_HELPOP	"HELPOP"
-
-ModuleHeader MOD_HEADER
-  = {
-	"help",
-	"5.0",
-	"command /help", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_HELP, cmd_help, 1, CMD_USER);
-	CommandAdd(modinfo->handle, MSG_HELPOP, cmd_help, 1, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-#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(const char *command)
-{
-	ConfigItem_help *help;
-
-	if (!command)
-	{
-		for (help = conf_help; help; help = help->next)
-		{
-			if (help->command == NULL)
-				return help;
-		}
-		return NULL;
-	}
-	for (help = conf_help; help; help = help->next)
-	{
-		if (help->command == NULL)
-			continue;
-		else if (!strcasecmp(command,help->command))
-			return help;
-	}
-	return NULL;
-}
-
-void parse_help(Client *client, const char *help)
-{
-	ConfigItem_help *helpitem;
-	MOTDLine *text;
-	if (BadPtr(help))
-	{
-		helpitem = find_Help(NULL);
-		if (!helpitem)
-			return;
-		SND(" -");
-		HDR("        ***** UnrealIRCd Help System *****");
-		SND(" -");
-		text = helpitem->text;
-		while (text) {
-			SND(text->line);
-			text = text->next;
-		}
-		SND(" -");
-		return;
-		
-	}
-	helpitem = find_Help(help);
-	if (!helpitem) {
-		SND(" -");
-		HDR("        ***** No Help Available *****");
-		SND(" -");
-		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, HELP_CHANNEL);
-		SND(" -");
-		return;
-	}
-	text = helpitem->text;
-	SND(" -");
-	sendto_one(client, NULL, ":%s 290 %s :***** %s *****",
-	    me.name, client->name, helpitem->command);
-	SND(" -");
-	while (text) {
-		SND(text->line);
-		text = text->next;
-	}
-	SND(" -");
-}
-
-/*
-** cmd_help (help/write to +h currently online) -Donwulff
-**	parv[1] = optional message text
-*/
-CMD_FUNC(cmd_help)
-{
-	const char *helptopic;
-
-	if (!MyUser(client))
-		return; /* never remote */
-
-	helptopic = parc > 1 ? parv[1] : NULL;
-	
-	if (helptopic && (*helptopic == '?'))
-		helptopic++;
-
-	parse_help(client, BadPtr(helptopic) ? NULL : helptopic);
-}
diff --git a/src/modules/hideserver.c b/src/modules/hideserver.c
@@ -1,467 +0,0 @@
-/*
- * hideserver.c - Hide certain or all servers from /MAP & /LINKS
- *
- * Note that this module simple hides servers. It does not truly
- * increase security. Use as you wish.
- *
- * (C) Copyright 2003-2004 AngryWolf <angrywolf@flashmail.com>
- * (C) Copyright 2016 Bram Matthys <syzop@vulnscan.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"
-
-CMD_OVERRIDE_FUNC(override_map);
-CMD_OVERRIDE_FUNC(override_links);
-static int cb_test(ConfigFile *, ConfigEntry *, int, int *);
-static int cb_conf(ConfigFile *, ConfigEntry *, int);
-
-ConfigItem_ulines *HiddenServers;
-
-static struct
-{
-	unsigned	disable_map : 1;
-	unsigned	disable_links : 1;
-	char		*map_deny_message;
-	char		*links_deny_message;
-} Settings;
-
-static ModuleInfo	*MyModInfo;
-#define MyMod		MyModInfo->handle
-#define SAVE_MODINFO	MyModInfo = modinfo;
-
-static int lmax = 0;
-static int umax = 0;
-
-static int dcount(int n)
-{
-   int cnt = 0;
-
-   while (n != 0)
-   {
-	   n = n/10;
-	   cnt++;
-   }
-
-   return cnt;
-}
-
-ModuleHeader MOD_HEADER
-  = {
-	"hideserver",
-	"5.0",
-	"Hide servers from /MAP & /LINKS",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-static void InitConf()
-{
-	memset(&Settings, 0, sizeof Settings);
-}
-
-static void FreeConf()
-{
-	ConfigItem_ulines	*h, *next;
-
-	safe_free(Settings.map_deny_message);
-	safe_free(Settings.links_deny_message);
-
-	for (h = HiddenServers; h; h = next)
-	{
-		next = h->next;
-		DelListItem(h, HiddenServers);
-		safe_free(h->servername);
-		safe_free(h);
-	}
-}
-
-MOD_TEST()
-{
-	SAVE_MODINFO
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cb_test);
-
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	SAVE_MODINFO
-	HiddenServers = NULL;
-	InitConf();
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cb_conf);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (!CommandOverrideAdd(MyMod, "MAP", 0, override_map))
-		return MOD_FAILED;
-
-	if (!CommandOverrideAdd(MyMod, "LINKS", 0, override_links))
-		return MOD_FAILED;
-
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	FreeConf();
-
-	return MOD_SUCCESS;
-}
-
-static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep;
-	int errors = 0;
-
-	if (type == CONFIG_MAIN)
-	{
-		if (!strcmp(ce->name, "hideserver"))
-		{
-			for (cep = ce->items; cep; cep = cep->next)
-			{
-				if (!strcmp(cep->name, "hide"))
-				{
-					/* No checking needed */
-				}
-				else if (!cep->value)
-				{
-					config_error("%s:%i: %s::%s without value",
-						cep->file->filename,
-						cep->line_number,
-						ce->name, cep->name);
-					errors++;
-					continue;
-				}
-				else if (!strcmp(cep->name, "disable-map"))
-					;
-				else if (!strcmp(cep->name, "disable-links"))
-					;
-				else if (!strcmp(cep->name, "map-deny-message"))
-					;
-				else if (!strcmp(cep->name, "links-deny-message"))
-					;
-				else
-				{
-					config_error("%s:%i: unknown directive hideserver::%s",
-						cep->file->filename, cep->line_number, cep->name);
-					errors++;
-				}
-			}
-			*errs = errors;
-			return errors ? -1 : 1;
-		}
-	}
-
-	return 0;
-}
-
-static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry		*cep, *cepp;
-	ConfigItem_ulines	*ca;
-
-	if (type == CONFIG_MAIN)
-	{
-		if (!strcmp(ce->name, "hideserver"))
-		{
-			for (cep = ce->items; cep; cep = cep->next)
-			{
-				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->value);
-				}
-				else if (!strcmp(cep->name, "links-deny-message"))
-				{
-					safe_strdup(Settings.links_deny_message, cep->value);
-				}
-				else if (!strcmp(cep->name, "hide"))
-				{
-					for (cepp = cep->items; cepp; cepp = cepp->next)
-					{
-						if (!strcasecmp(cepp->name, me.name))
-							continue;
-
-						ca = safe_alloc(sizeof(ConfigItem_ulines));
-						safe_strdup(ca->servername, cepp->name);
-						AddListItem(ca, HiddenServers);
-					}
-				}
-			}
-
-			return 1;
-		}
-	}
-
-	return 0;
-}
-
-ConfigItem_ulines *FindHiddenServer(char *servername)
-{
-	ConfigItem_ulines *h;
-
-	for (h = HiddenServers; h; h = h->next)
-		if (!strcasecmp(servername, h->servername))
-			break;
-
-	return h;
-}
-
-/*
- * New /MAP format -Potvin
- * dump_map function.
- */
-static void dump_map(Client *client, Client *server, char *mask, int prompt_length, int length)
-{
-	static char prompt[64];
-	char *p = &prompt[prompt_length];
-	int  cnt = 0;
-	Client *acptr;
-
-	*p = '\0';
-
-	if (prompt_length > 60)
-		sendnumeric(client, RPL_MAPMORE, prompt, length, server->name);
-	else
-	{
-		char tbuf[256];
-		char sid[10];
-		int len = length - strlen(server->name) + 1;
-
-		if (len < 0)
-			len = 0;
-		if (len > 255)
-			len = 255;
-
-		tbuf[len--] = '\0';
-		while (len >= 0)
-			tbuf[len--] = '-';
-		if (IsOper(client))
-			snprintf(sid, sizeof(sid), " [%s]", server->id);
-		sendnumeric(client, RPL_MAP, prompt, server->name, tbuf, umax,
-			server->server->users, (double)(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
-			(server->server->users * 100.0 / irccounts.clients),
-			IsOper(client) ? sid : "");
-		cnt = 0;
-	}
-
-	if (prompt_length > 0)
-	{
-		p[-1] = ' ';
-		if (p[-2] == '`')
-			p[-2] = ' ';
-	}
-	if (prompt_length > 60)
-		return;
-
-	strcpy(p, "|-");
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if (acptr->uplink != server ||
- 		    (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)))
-			continue;
-		if (FindHiddenServer(acptr->name))
-			break;
-		SetMap(acptr);
-		cnt++;
-	}
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
-			continue;
-		if (FindHiddenServer(acptr->name))
-			break;
-		if (acptr->uplink != server)
-			continue;
-		if (!IsMap(acptr))
-			continue;
-		if (--cnt == 0)
-			*p = '`';
-		dump_map(client, acptr, mask, prompt_length + 2, length - 2);
-	}
-
-	if (prompt_length > 0)
-		p[-1] = '-';
-}
-
-void dump_flat_map(Client *client, Client *server, int length)
-{
-	char buf[4];
-	char tbuf[256];
-	Client *acptr;
-	int cnt = 0, len = 0, hide_ulines;
-
-	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
-
-	len = length - strlen(server->name) + 3;
-	if (len < 0)
-		len = 0;
-	if (len > 255)
-		len = 255;
-
-	tbuf[len--] = '\0';
-	while (len >= 0)
-		tbuf[len--] = '-';
-
-	sendnumeric(client, RPL_MAP, "", server->name, tbuf, umax, server->server->users,
-		(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
-		(server->server->users * 100.0 / irccounts.clients), "");
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
-			continue;
-		if (FindHiddenServer(acptr->name))
-			break;
-		cnt++;
-	}
-
-	strcpy(buf, "|-");
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
-			continue;
-		if (FindHiddenServer(acptr->name))
-			break;
-		if (--cnt == 0)
-			*buf = '`';
-
-		len = length - strlen(acptr->name) + 1;
-		if (len < 0)
-			len = 0;
-		if (len > 255)
-			len = 255;
-
-		tbuf[len--] = '\0';
-		while (len >= 0)
-			tbuf[len--] = '-';
-
-		sendnumeric(client, RPL_MAP, buf, acptr->name, tbuf, umax, acptr->server->users,
-			(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
-			(acptr->server->users * 100.0 / irccounts.clients), "");
-	}
-}
-
-/*
-** New /MAP format. -Potvin
-** cmd_map (NEW)
-**
-**      parv[1] = server mask
-**/
-CMD_OVERRIDE_FUNC(override_map)
-{
-	Client *acptr;
-	int longest = strlen(me.name);
-	float avg_users = 0.0;
-
-	umax = 0;
-	lmax = 0;
-
-	if (parc < 2)
-		parv[1] = "*";
-	
-	if (IsOper(client))
-	{
-		CALL_NEXT_COMMAND_OVERRIDE();
-		return;
-	}
-
-	if (Settings.disable_map)
-	{
-		if (Settings.map_deny_message)
-			sendnotice(client, "%s", Settings.map_deny_message);
-		else
-			sendnumeric(client, RPL_MAPEND);
-		return;
-	}
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		int perc = 0;
-		if (FindHiddenServer(acptr->name))
-			break;
-		perc = (acptr->server->users * 100 / irccounts.clients);
-		if ((strlen(acptr->name) + acptr->hopcount * 2) > longest)
-			longest = strlen(acptr->name) + acptr->hopcount * 2;
-		if (lmax < perc)
-			lmax = perc;
-		if (umax < dcount(acptr->server->users))
-			umax = dcount(acptr->server->users);
-	}
-
-	if (longest > 60)
-		longest = 60;
-	longest += 2;
-
-	if (FLAT_MAP && !ValidatePermissionsForPath("server:info:map:real-map",client,NULL,NULL,NULL))
-		dump_flat_map(client, &me, longest);
-	else
-		dump_map(client, &me, "*", 0, longest);
-
-	avg_users = irccounts.clients * 1.0 / irccounts.servers;
-	sendnumeric(client, RPL_MAPUSERS, irccounts.servers, (irccounts.servers > 1 ? "s" : ""), irccounts.clients,
-		(irccounts.clients > 1 ? "s" : ""), avg_users);
-	sendnumeric(client, RPL_MAPEND);
-}
-
-CMD_OVERRIDE_FUNC(override_links)
-{
-	Client *acptr;
-	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
-
-	if (IsOper(client))
-	{
-		CALL_NEXT_COMMAND_OVERRIDE();
-		return;
-	}
-
-	if (Settings.disable_links)
-	{
-		if (Settings.links_deny_message)
-			sendnotice(client, "%s", Settings.links_deny_message);
-		else
-			sendnumeric(client, RPL_ENDOFLINKS, "*");
-		return;
-	}
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		/* Some checks */
-		if (HIDE_ULINES && IsULine(acptr) && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
-			continue;
-		if (FindHiddenServer(acptr->name))
-			continue;
-		if (flat)
-			sendnumeric(client, RPL_LINKS, acptr->name, me.name,
-			    1, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
-		else
-			sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink ? acptr->uplink->name : me.name,
-			    acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
-	}
-
-	sendnumeric(client, RPL_ENDOFLINKS, "*");
-}
diff --git a/src/modules/history.c b/src/modules/history.c
@@ -1,136 +0,0 @@
-/* src/modules/history.c - (C) 2020 Bram Matthys (Syzop) & The UnrealIRCd Team
- *
- * Simple HISTORY command to fetch channel history.
- * This is one of the interfaces to the channel history backend.
- * The other, most prominent, being history-on-join at the moment.
- * In the future there will likely be an IRCv3 standard which
- * will allow clients to fetch history automatically, which will
- * be implemented under a different command and not really meant
- * for end users. However, it will take a while until such a spec is
- * finalized, let alone when major clients will finally support it.
- *
- * See doc/Authors and git history 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
-  = {
-	"history",
-	"5.0",
-	"Simple history command for end-users",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-#define HISTORY_LINES_DEFAULT 100
-#define HISTORY_LINES_MAX 100
-
-CMD_FUNC(cmd_history);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	CommandAdd(modinfo->handle, "HISTORY", cmd_history, MAXPARA, CMD_USER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void history_usage(Client *client)
-{
-	sendnotice(client, " Use: /HISTORY #channel [lines-to-display]");
-	sendnotice(client, "  Ex: /HISTORY #lobby");
-	sendnotice(client, "  Ex: /HISTORY #lobby 50");
-	sendnotice(client, "The lines-to-display value must be 1-%d, the default is %d",
-		HISTORY_LINES_MAX, HISTORY_LINES_DEFAULT);
-	sendnotice(client, "Naturally, the line count and time limits in channel mode +H are obeyed");
-}
-
-CMD_FUNC(cmd_history)
-{
-	HistoryFilter filter;
-	HistoryResult *r;
-	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]);
-	if (!channel)
-	{
-		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
-		return;
-	}
-
-	if (!IsMember(client, channel))
-	{
-		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->name);
-		return;
-	}
-
-	if (parv[2])
-	{
-		lines = atoi(parv[2]);
-		if (lines < 1)
-		{
-			history_usage(client);
-			return;
-		}
-		if (lines > HISTORY_LINES_MAX)
-			lines = HISTORY_LINES_MAX;
-	}
-
-	if (!HasCapability(client, "server-time"))
-	{
-		sendnotice(client, "Your IRC client does not support the 'server-time' capability");
-		sendnotice(client, "https://ircv3.net/specs/extensions/server-time");
-		sendnotice(client, "History request refused.");
-		return;
-	}
-
-	memset(&filter, 0, sizeof(filter));
-	filter.cmd = HFC_SIMPLE;
-	filter.last_lines = lines;
-
-	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
@@ -1,1618 +0,0 @@
-/* src/modules/history_backend_mem.c - History Backend: memory
- * (C) Copyright 2019-2021 Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-#include "unrealircd.h"
-
-/* This is the memory type backend. It is optimized for speed.
- * For example, per-channel, it caches the field "number of lines"
- * and "oldest record", so frequent cleaning operations such as
- * "delete any record older than time T" or "keep only N lines"
- * are executed as fast as possible.
- */
-
-ModuleHeader MOD_HEADER
-= {
-	"history_backend_mem",
-	"2.0",
-	"History backend: memory",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Defines */
-#define OBJECTLEN	((NICKLEN > CHANNELLEN) ? NICKLEN : CHANNELLEN)
-#define HISTORY_BACKEND_MEM_HASH_TABLE_SIZE 1019
-
-/* The regular history cleaning (by timer) is spread out
- * a bit, rather than doing ALL channels every T time.
- * HISTORY_SPREAD: how much to spread the "cleaning", eg 1 would be
- *  to clean everything in 1 go, 2 would mean the first event would
- *  clean half of the channels, and the 2nd event would clean the rest.
- *  Obviously more = better to spread the load, but doing a reasonable
- *  amount of work is also benefitial for performance (think: CPU cache).
- * HISTORY_MAX_OFF_SECS: how many seconds may the history be 'off',
- *  that is: how much may we store the history longer than required.
- * The other 2 macros are calculated based on that target.
- *
- * Update April 2021: these values are now also used for saving the
- * history if the persistent option is enabled. Therefore changed the
- * values to spread it even more out: from 16/128 to 60/300 so
- * in case of persistent it will save every 5 minutes.
- */
-#if 0 //was: DEBUGMODE
-#define HISTORY_CLEAN_PER_LOOP HISTORY_BACKEND_MEM_HASH_TABLE_SIZE
-#define HISTORY_TIMER_EVERY 5
-#else
-#define HISTORY_SPREAD	60
-#define HISTORY_MAX_OFF_SECS	300
-#define HISTORY_CLEAN_PER_LOOP	(HISTORY_BACKEND_MEM_HASH_TABLE_SIZE/HISTORY_SPREAD)
-#define HISTORY_TIMER_EVERY	(HISTORY_MAX_OFF_SECS/HISTORY_SPREAD)
-#endif
-
-/* Some magic numbers used in the database format */
-#define HISTORYDB_MAGIC_FILE_START	0xFEFEFEFE
-#define HISTORYDB_MAGIC_FILE_END	0xEFEFEFEF
-#define HISTORYDB_MAGIC_ENTRY_START	0xFFFFFFFF
-#define HISTORYDB_MAGIC_ENTRY_END	0xEEEEEEEE
-
-/* Definitions (structs, etc.) -- all for persistent history */
-struct cfgstruct {
-	int persist;
-	char *directory;
-	char *masterdb; /* Autogenerated for convenience, not a real config item */
-	char *db_secret;
-};
-
-typedef struct HistoryLogObject HistoryLogObject;
-struct HistoryLogObject {
-	HistoryLogObject *prev, *next;
-	HistoryLogLine *head; /**< Start of the log (the earliest entry) */
-	HistoryLogLine *tail; /**< End of the log (the latest entry) */
-	int num_lines; /**< Number of lines of log */
-	time_t oldest_t; /**< Oldest time in log */
-	int max_lines; /**< Maximum number of lines permitted */
-	long max_time; /**< Maximum number of seconds to retain history */
-	int dirty; /**< Dirty flag, used for disk writing */
-	char name[OBJECTLEN+1];
-};
-
-/* Global variables */
-struct cfgstruct cfg;
-struct cfgstruct test;
-static char *siphashkey_history_backend_mem = NULL;
-HistoryLogObject **history_hash_table;
-static long already_loaded = 0;
-static char *hbm_prehash = NULL;
-static char *hbm_posthash = NULL;
-
-/* Forward declarations */
-int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int hbm_config_posttest(int *errs);
-int hbm_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-int hbm_rehash(void);
-int hbm_rehash_complete(void);
-static void setcfg(struct cfgstruct *cfg);
-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(const char *object, MessageTag *mtags, const char *line);
-int hbm_history_cleanup(HistoryLogObject *h);
-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(const char *fname);
-static int hbm_write_masterdb(void);
-static int hbm_write_db(HistoryLogObject *h);
-static void hbm_delete_db(HistoryLogObject *h);
-static void hbm_flush(void);
-void hbm_generic_free(ModData *m);
-void hbm_free_all_history(ModData *m);
-
-MOD_TEST()
-{
-	hbm_init_hashes(modinfo);
-	memset(&cfg, 0, sizeof(cfg));
-	memset(&test, 0, sizeof(test));
-	setcfg(&test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, hbm_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, hbm_config_posttest);
-
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	HistoryBackendInfo hbi;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	/* We must unload early, when all channel modes and such are still in place: */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
-
-	setcfg(&cfg);
-
-	LoadPersistentLong(modinfo, already_loaded);
-	LoadPersistentPointer(modinfo, siphashkey_history_backend_mem, hbm_generic_free);
-	LoadPersistentPointer(modinfo, history_hash_table, hbm_free_all_history);
-	if (history_hash_table == NULL)
-		history_hash_table = safe_alloc(sizeof(HistoryLogObject *) * HISTORY_BACKEND_MEM_HASH_TABLE_SIZE);
-	/* hbm_prehash & hbm_posthash already loaded in MOD_TEST through hbm_init_hashes() */
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, hbm_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_DEL, 0, hbm_modechar_del);
-	HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, hbm_rehash);
-	HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, hbm_rehash_complete);
-
-	if (siphashkey_history_backend_mem == NULL)
-	{
-		siphashkey_history_backend_mem = safe_alloc(SIPHASH_KEY_LENGTH);
-		siphash_generate_key(siphashkey_history_backend_mem);
-	}
-
-	memset(&hbi, 0, sizeof(hbi));
-	hbi.name = "mem";
-	hbi.history_add = hbm_history_add;
-	hbi.history_request = hbm_history_request;
-	hbi.history_destroy = hbm_history_destroy;
-	hbi.history_set_limit = hbm_history_set_limit;
-	if (!HistoryBackendAdd(modinfo->handle, &hbi))
-		return MOD_FAILED;
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	/* Need to save these here already (after conf reading these are set),
-	 * as on next round the module reads it in TEST which happens before
-	 * the saving in MOD_UNLOAD:
-	 */
-	SavePersistentPointer(modinfo, hbm_prehash);
-	SavePersistentPointer(modinfo, hbm_posthash);
-
-	EventAdd(modinfo->handle, "history_mem_init", history_mem_init, NULL, 1, 1);
-	EventAdd(modinfo->handle, "history_mem_clean", history_mem_clean, NULL, HISTORY_TIMER_EVERY*1000, 0);
-	init_history_storage(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Read the .db if 'persist' mode is enabled.
- * Normally this would be in MOD_LOAD, but the load order always
- * must be: channeldb first, this module second, and since we
- * cannot influence the load order we do this silly trick
- * with a one-time 1msec event.
- */
-EVENT(history_mem_init)
-{
-	if (!already_loaded)
-	{
-		/* Initial boot / load of the module... */
-		already_loaded = 1;
-		if (cfg.persist)
-			hbm_read_dbs();
-	}
-}
-
-MOD_UNLOAD()
-{
-	if (loop.terminating)
-		hbm_flush();
-	freecfg(&test);
-	freecfg(&cfg);
-	SavePersistentPointer(modinfo, hbm_prehash);
-	SavePersistentPointer(modinfo, hbm_posthash);
-	SavePersistentPointer(modinfo, history_hash_table);
-	SavePersistentPointer(modinfo, siphashkey_history_backend_mem);
-	SavePersistentLong(modinfo, already_loaded);
-	return MOD_SUCCESS;
-}
-
-/** Set cfg->masterdb based on cfg->directory, for convenience */
-static void hbm_set_masterdb_filename(struct cfgstruct *cfg)
-{
-	char buf[512];
-
-	safe_free(cfg->masterdb);
-	if (cfg->directory)
-	{
-		snprintf(buf, sizeof(buf), "%s/master.db", cfg->directory);
-		safe_strdup(cfg->masterdb, buf);
-	}
-}
-
-/** Default configuration for set::history::channel */
-static void setcfg(struct cfgstruct *cfg)
-{
-	safe_strdup(cfg->directory, "history");
-	convert_to_absolute_path(&cfg->directory, PERMDATADIR);
-	hbm_set_masterdb_filename(cfg);
-}
-
-static void freecfg(struct cfgstruct *cfg)
-{
-	safe_free(cfg->masterdb);
-	safe_free(cfg->directory);
-	safe_free(cfg->db_secret);
-}
-
-static void hbm_init_hashes(ModuleInfo *modinfo)
-{
-	char buf[256];
-
-	LoadPersistentPointer(modinfo, hbm_prehash, hbm_generic_free);
-	LoadPersistentPointer(modinfo, hbm_posthash, hbm_generic_free);
-
-	if (!hbm_prehash)
-	{
-		gen_random_alnum(buf, 128);
-		safe_strdup(hbm_prehash, buf);
-	}
-
-	if (!hbm_posthash)
-	{
-		gen_random_alnum(buf, 128);
-		safe_strdup(hbm_posthash, buf);
-	}
-}
-
-/** Test the set::history::channel configuration */
-int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-
-	if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->name)
-		return 0;
-
-	if (!strcmp(ce->name, "persist"))
-	{
-		if (!ce->value)
-		{
-			config_error("%s:%i: missing parameter",
-				ce->file->filename, ce->line_number);
-			errors++;
-		} else {
-			test.persist = config_checkval(ce->value, CFG_YESNO);
-		}
-	} else
-	if (!strcmp(ce->name, "db-secret"))
-	{
-		const char *err;
-		if ((err = unrealdb_test_secret(ce->value)))
-		{
-			config_error("%s:%i: set::history::channel::db-secret: %s", ce->file->filename, ce->line_number, err);
-			errors++;
-		}
-		safe_strdup(test.db_secret, ce->value);
-	} else
-	if (!strcmp(ce->name, "directory")) // or "path" ?
-	{
-		if (!ce->value)
-		{
-			config_error("%s:%i: missing parameter",
-				ce->file->filename, ce->line_number);
-			errors++;
-		} else
-		{
-			safe_strdup(test.directory, ce->value);
-			hbm_set_masterdb_filename(&test);
-		}
-	} else
-	{
-		return 0; /* unknown option to us, let another module handle it */
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/** Post-configuration test on set::history::channel */
-int hbm_config_posttest(int *errs)
-{
-	int errors = 0;
-
-	if (test.db_secret && !test.persist)
-	{
-		config_error("set::history::channel::db-secret is set but set::history::channel::persist is disabled, this makes no sense. "
-			     "Either use 'persist yes' or comment out / delete 'db-secret'.");
-		errors++;
-	} else
-	if (!test.db_secret && test.persist)
-	{
-		config_error("set::history::channel::db-secret needs to be set.");
-		errors++;
-	} else
-	if (test.db_secret && test.persist)
-	{
-		/* Configuration is good, now check if the password is correct
-		 * (if we can check at all, that is)...
-		 */
-		char *errstr = NULL;
-		if (test.masterdb && ((errstr = unrealdb_test_db(test.masterdb, test.db_secret))))
-		{
-			config_error("[history] %s", errstr);
-			errors++;
-			goto hbm_config_posttest_end;
-		}
-
-		/* Ensure directory exists and is writable */
-#ifdef _WIN32
-		(void)mkdir(test.directory); /* (errors ignored) */
-#else
-		(void)mkdir(test.directory, S_IRUSR|S_IWUSR|S_IXUSR); /* (errors ignored) */
-#endif
-		if (!file_exists(test.directory))
-		{
-			config_error("[history] Directory %s does not exist and could not be created",
-				test.directory);
-			errors++;
-		} else
-		{
-			/* Only do this if directory actually exists, hence in the 'else' block */
-			if (!hbm_read_masterdb())
-				errors++;
-		}
-	}
-
-hbm_config_posttest_end:
-	freecfg(&test);
-	setcfg(&test);
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/** 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->name)
-		return 0;
-
-	if (!strcmp(ce->name, "persist"))
-	{
-		cfg.persist = config_checkval(ce->value, CFG_YESNO);
-	} else
-	if (!strcmp(ce->name, "directory")) // or "path" ?
-	{
-		safe_strdup(cfg.directory, ce->value);
-		convert_to_absolute_path(&cfg.directory, PERMDATADIR);
-		hbm_set_masterdb_filename(&cfg);
-	} else
-	if (!strcmp(ce->name, "db-secret"))
-	{
-		safe_strdup(cfg.db_secret, ce->value);
-	} else
-	{
-		return 0; /* unknown option to us, let another module handle it */
-	}
-
-	return 1; /* handled by us */
-}
-
-int hbm_rehash(void)
-{
-	freecfg(&cfg);
-	setcfg(&cfg);
-	return 0;
-}
-
-int hbm_rehash_complete(void)
-{
-	return 0;
-}
-
-const char *history_storage_capability_parameter(Client *client)
-{
-	static char buf[128];
-
-	if (cfg.persist)
-		strlcpy(buf, "memory,disk=encrypted", sizeof(buf));
-	else
-		strlcpy(buf, "memory", sizeof(buf));
-
-	return buf;
-}
-
-static void init_history_storage(ModuleInfo *modinfo)
-{
-	ClientCapabilityInfo cap;
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "unrealircd.org/history-storage";
-	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
-	cap.parameter = history_storage_capability_parameter;
-	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
-}
-
-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(const char *object)
-{
-	int hashv = hbm_hash(object);
-	HistoryLogObject *h;
-
-	for (h = history_hash_table[hashv]; h; h = h->next)
-	{
-		if (!strcasecmp(object, h->name))
-			return h;
-	}
-	return NULL;
-}
-
-HistoryLogObject *hbm_find_or_add_object(const char *object)
-{
-	int hashv = hbm_hash(object);
-	HistoryLogObject *h;
-
-	for (h = history_hash_table[hashv]; h; h = h->next)
-	{
-		if (!strcasecmp(object, h->name))
-			return h;
-	}
-	/* Create new one */
-	h = safe_alloc(sizeof(HistoryLogObject));
-	strlcpy(h->name, object, sizeof(h->name));
-	AddListItem(h, history_hash_table[hashv]);
-	return h;
-}
-
-void hbm_delete_object_hlo(HistoryLogObject *h)
-{
-	int hashv;
-
-	if (cfg.persist)
-		hbm_delete_db(h);
-
-	hashv = hbm_hash(h->name);
-	DelListItem(h, history_hash_table[hashv]);
-	safe_free(h);
-}
-
-int hbm_modechar_del(Channel *channel, int modechar)
-{
-	HistoryLogObject *h;
-
-	if (!cfg.persist)
-		return 0;
-
-	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);
-
-		h->dirty = 1;
-		/* The reason for marking the entry as 'dirty' is that someone may later
-		 * set the channel +P again. If we would not set the h->dirty=1 then this
-		 * would mean the history log would not get rewritten until someone speaks.
-		 */
-	}
-
-	return 0;
-}
-
-void hbm_duplicate_mtags(HistoryLogLine *l, MessageTag *m)
-{
-	MessageTag *n;
-
-	/* Duplicate all message tags */
-	for (; m; m = m->next)
-	{
-		n = duplicate_mtag(m);
-		AppendListItem(n, l->mtags);
-	}
-	n = find_mtag(l->mtags, "time");
-	if (!n)
-	{
-		/* This is duplicate code from src/modules/server-time.c
-		 * which seems silly.
-		 */
-		struct timeval t;
-		struct tm *tm;
-		time_t sec;
-		char buf[64];
-
-		gettimeofday(&t, NULL);
-		sec = t.tv_sec;
-		tm = gmtime(&sec);
-		snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
-			tm->tm_year + 1900,
-			tm->tm_mon + 1,
-			tm->tm_mday,
-			tm->tm_hour,
-			tm->tm_min,
-			tm->tm_sec,
-			(int)(t.tv_usec / 1000));
-
-		n = safe_alloc(sizeof(MessageTag));
-		safe_strdup(n->name, "time");
-		safe_strdup(n->value, buf);
-		AddListItem(n, l->mtags);
-	}
-	/* Now convert the "time" message tag to something we can use in l->t */
-	l->t = server_time_to_unix_time(n->value);
-}
-
-/** Add a line to a history object */
-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 ^ */
-	hbm_duplicate_mtags(l, mtags);
-	if (h->tail)
-	{
-		/* append to tail */
-		h->tail->next = l;
-		l->prev = h->tail;
-		h->tail = l;
-	} else {
-		/* no tail, no head */
-		h->head = h->tail = l;
-	}
-	h->dirty = 1;
-	h->num_lines++;
-	if ((l->t < h->oldest_t) || (h->oldest_t == 0))
-		h->oldest_t = l->t;
-}
-
-/** Delete a line from a history object */
-void hbm_history_del_line(HistoryLogObject *h, HistoryLogLine *l)
-{
-	if (l->prev)
-		l->prev->next = l->next;
-	if (l->next)
-		l->next->prev = l->prev;
-	if (h->head == l)
-	{
-		/* New head */
-		h->head = l->next;
-	}
-	if (h->tail == l)
-	{
-		/* New tail */
-		h->tail = l->prev; /* could be NULL now */
-	}
-
-	free_message_tags(l->mtags);
-	safe_free(l);
-
-	h->dirty = 1;
-	h->num_lines--;
-
-	/* IMPORTANT: updating h->oldest_t takes place at the caller
-	 * because it is in a better position to optimize the process
-	 */
-}
-
-/** Add history entry */
-int hbm_history_add(const char *object, MessageTag *mtags, const char *line)
-{
-	HistoryLogObject *h = hbm_find_or_add_object(object);
-	if (!h->max_lines)
-	{
-		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
-		h->max_lines = 50;
-		h->max_time = 86400;
-#endif
-	}
-	if (h->num_lines >= h->max_lines)
-	{
-		/* Delete previous line */
-		hbm_history_del_line(h, h->head);
-	}
-	hbm_history_add_line(h, mtags, line);
-	return 0;
-}
-
-HistoryLogLine *duplicate_log_line(HistoryLogLine *l)
-{
-	HistoryLogLine *n = safe_alloc(sizeof(HistoryLogLine) + strlen(l->line));
-	strcpy(n->line, l->line); /* safe, see memory allocation above ^ */
-	hbm_duplicate_mtags(n, l->mtags);
-	return n;
-}
-
-/** Quickly append a new line 'n' to result 'r' */
-static void hbm_result_append_line(HistoryResult *r, HistoryLogLine *n)
-{
-	if (!r->log)
-	{
-		/* First item */
-		r->log = r->log_tail = n;
-	} else
-	{
-		/* Quick append to tail */
-		r->log_tail->next = n;
-		n->prev = r->log_tail;
-		r->log_tail = n; /* we are the new tail */
-	}
-}
-
-/** Quickly prepend a new line 'n' to result 'r' */
-static void hbm_result_prepend_line(HistoryResult *r, HistoryLogLine *n)
-{
-	if (!r->log)
-		r->log_tail = n;
-	AddListItem(n, r->log);
-}
-
-/** Put lines in HistoryResult that are after a certain msgid or
- *  timestamp (excluding said msgid/timestamp).
- * @param r		The history result set that we will use
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns Number of lines written, note that this could be zero,
- *          which is a perfectly valid result.
- */
-static int hbm_return_after(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
-{
-	HistoryLogLine *l, *n;
-	int written = 0;
-	int started = 0;
-	MessageTag *m;
-
-	for (l = h->head; l; l = l->next)
-	{
-		/* Not started yet? Check if this is the starting point... */
-		if (!started)
-		{
-			if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) > 0))
-			{
-				started = 1;
-			} else
-			if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
-			{
-				started = 1;
-				continue;
-			}
-		}
-		if (started)
-		{
-			/* Check if we need to stop */
-			if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) >= 0))
-			{
-				break;
-			} else
-			if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
-			{
-				break;
-			}
-
-			/* Add line to the return buffer */
-			n = duplicate_log_line(l);
-			hbm_result_append_line(r, n);
-			if (++written >= filter->limit)
-				break;
-		}
-	}
-
-	return written;
-}
-
-/** Put lines in HistoryResult that before after a certain msgid or
- *  timestamp (excluding said msgid/timestamp).
- * @param r		The history result set that we will use
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns Number of lines written, note that this could be zero,
- *          which is a perfectly valid result.
- */
-static int hbm_return_before(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
-{
-	HistoryLogLine *l, *n;
-	int written = 0;
-	int started = 0;
-	MessageTag *m;
-
-	for (l = h->tail; l; l = l->prev)
-	{
-		/* Not started yet? Check if this is the starting point... */
-		if (!started)
-		{
-			if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) < 0))
-			{
-				started = 1;
-			} else
-			if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
-			{
-				started = 1;
-				continue;
-			}
-		}
-		if (started)
-		{
-			/* Check if we need to stop */
-			if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) < 0))
-			{
-				break;
-			} else
-			if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
-			{
-				break;
-			}
-
-			/* Add line to the return buffer */
-			n = duplicate_log_line(l);
-			hbm_result_prepend_line(r, n);
-			if (++written >= filter->limit)
-				break;
-		}
-	}
-
-	return written;
-}
-
-/** Put lines in HistoryResult that are 'latest'
- * @param r		The history result set that we will use
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns Number of lines written, note that this could be zero,
- *          which is a perfectly valid result.
- */
-static int hbm_return_latest(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
-{
-	HistoryLogLine *l, *n;
-	int written = 0;
-	MessageTag *m;
-
-	for (l = h->tail; l; l = l->prev)
-	{
-		if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) <= 0))
-			break; /* Stop now */
-		else
-		if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
-			break; /* Stop now */
-
-		n = duplicate_log_line(l);
-		hbm_result_prepend_line(r, n);
-		if (++written >= filter->limit)
-			break;
-	}
-
-	return written;
-}
-
-/** Put lines in HistoryResult based on a 'simple' request, that is: maximum lines or time
- * @param r		The history result set that we will use
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns Number of lines written, note that this could be zero,
- *          which is a perfectly valid result.
- */
-static int hbm_return_simple(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
-{
-	HistoryLogLine *l;
-	int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
-	long redline;
-	int written = 0;
-
-	/* Decide on red line, under this the history is too old.
-	 * Filter can be more strict than history object (but not the other way around):
-	 */
-	if (filter && filter->last_seconds && (filter->last_seconds < h->max_time))
-		redline = TStime() - filter->last_seconds;
-	else
-		redline = TStime() - h->max_time;
-
-	/* Once the filter API expands, the following will change too.
-	 * For now, this is sufficient, since requests are only about lines:
-	 */
-	lines_sendable = 0;
-	for (l = h->head; l; l = l->next)
-		if (l->t >= redline)
-			lines_sendable++;
-	if (filter && (lines_sendable > filter->last_lines))
-		lines_to_skip = lines_sendable - filter->last_lines;
-
-	for (l = h->head; l; l = l->next)
-	{
-		/* Make sure we don't send too old entries:
-		 * We only have to check for time here, as line count is already
-		 * taken into account in hbm_history_add.
-		 */
-		if (l->t >= redline && (++cnt > lines_to_skip))
-		{
-			/* Add to result */
-			HistoryLogLine *n = duplicate_log_line(l);
-			hbm_result_append_line(r, n);
-			written++;
-		}
-	}
-
-	return written;
-}
-
-/** Put lines in HistoryResult that are 'around' a certain point.
- * @param r		The history result set that we will use
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns Number of lines written, note that this could be zero,
- *          which is a perfectly valid result.
- */
-static int hbm_return_around(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
-{
-	int n = 0;
-	int orig_limit = filter->limit;
-
-	/* First request 50% above the search term */
-	if (filter->limit > 1)
-		filter->limit = filter->limit / 2;
-	n = hbm_return_before(r, h, filter);
-	/* Then the remainder (50% or more) below the search term.
-	 *
-	 * Ok, well, unless the original limit was 1 and we already
-	 * sent 1 line, then we may not send anything anymore..
-	 */
-	filter->limit = orig_limit - n;
-	if (filter->limit > 0)
-		n += hbm_return_after(r, h, filter);
-
-	return n;
-}
-
-/** Figure out the direction (forwards or backwards) for CHATHISTORY BETWEEN request
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns 0 for backward searching, 1 for forward searching, -1 for invalid / not found
- */
-static int hbm_return_between_figure_out_direction(HistoryLogObject *h, HistoryFilter *filter)
-{
-	HistoryLogLine *l;
-	int found_a = 0;
-	int found_b = 0;
-	MessageTag *m;
-
-	/* Two timestamps? Then we can easily tell the direction. */
-	if (filter->timestamp_a && filter->timestamp_b)
-		return (strcmp(filter->timestamp_a, filter->timestamp_b) <= 0) ? 1 : 0;
-
-	for (l = h->head; l; l = l->next)
-	{
-		if (!found_a)
-		{
-			if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) >= 0))
-			{
-				found_a = 1;
-			} else
-			if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
-			{
-				found_a = 1;
-			}
-			if (found_a)
-			{
-				if (found_b)
-				{
-					/* B was found before A? Then the result is: backwards */
-					return 0;
-				}
-				if (filter->timestamp_b && (m = find_mtag(l->mtags, "time")) && m->value)
-				{
-					/* We can already resolve the direction now: */
-					char *timestamp_a = m->value;
-					return (strcmp(timestamp_a, filter->timestamp_b) <= 0) ? 1 : 0;
-				}
-			}
-		}
-		if (!found_b)
-		{
-			if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) >= 0))
-			{
-				found_b = 1;
-			} else
-			if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
-			{
-				found_b = 1;
-			}
-			if (found_b)
-			{
-				if (found_a)
-				{
-					/* A was found before B? Then the result is: forwards */
-					return 1;
-				}
-				if (filter->timestamp_a && (m = find_mtag(l->mtags, "time")) && m->value)
-				{
-					/* We can already resolve the direction now: */
-					char *timestamp_b = m->value;
-					return (strcmp(filter->timestamp_a, timestamp_b) <= 0) ? 1 : 0;
-				}
-			}
-		}
-	}
-
-	/* Neither points were found OR
-	 * one of the point is a msgid that could not be found.
-	 */
-	return -1; /* Result: invalid */
-}
-
-/** Put lines in HistoryResult that are 'between' two points.
- * @param r		The history result set that we will use
- * @param h		The history log object
- * @param filter	The filter that applies
- * @returns Number of lines written, note that this could be zero,
- *          which is a perfectly valid result.
- */
-static int hbm_return_between(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
-{
-	int direction;
-
-	direction = hbm_return_between_figure_out_direction(h, filter);
-
-	if (direction == 1)
-		return hbm_return_after(r, h, filter);
-	else if (direction == 0)
-		return hbm_return_before(r, h, filter);
-	/* else direction is -1 which means not found / invalid */
-
-	return 0;
-}
-
-HistoryResult *hbm_history_request(const char *object, HistoryFilter *filter)
-{
-	HistoryResult *r;
-	HistoryLogObject *h = hbm_find_object(object);
-	HistoryLogLine *l;
-	int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
-	long redline;
-
-	if (!h)
-		return NULL; /* nothing found */
-
-	/* Check if we need to remove some history entries due to 'time'.
-	 * No need to worry about 'count' as that is being taken care off
-	 * by hbm_history_add().
-	 */
-	if (h->oldest_t < TStime() - h->max_time)
-		hbm_history_cleanup(h);
-
-	r = safe_alloc(sizeof(HistoryResult));
-	safe_strdup(r->object, object);
-
-	switch(filter->cmd)
-	{
-		case HFC_BEFORE:
-			hbm_return_before(r, h, filter);
-			break;
-		case HFC_AFTER:
-			hbm_return_after(r, h, filter);
-			break;
-		case HFC_LATEST:
-			hbm_return_latest(r, h, filter);
-			break;
-		case HFC_AROUND:
-			hbm_return_around(r, h, filter);
-			break;
-		case HFC_BETWEEN:
-			hbm_return_between(r, h, filter);
-			break;
-		case HFC_SIMPLE:
-			hbm_return_simple(r, h, filter);
-			break;
-		default:
-			// unhandled
-			break;
-	}
-
-	return r;
-}
-
-/** Clean up expired entries */
-int hbm_history_cleanup(HistoryLogObject *h)
-{
-	HistoryLogLine *l, *l_next = NULL;
-	long redline = TStime() - h->max_time;
-
-	/* First enforce 'h->max_time', after that enforce 'h->max_lines' */
-
-	/* Checking for time */
-	if (h->oldest_t < redline)
-	{
-		h->oldest_t = 0; /* recalculate in next loop */
-
-		for (l = h->head; l; l = l_next)
-		{
-			l_next = l->next;
-			if (l->t < redline)
-			{
-				hbm_history_del_line(h, l); /* too old, delete it */
-				continue;
-			}
-			if ((h->oldest_t == 0) || (l->t < h->oldest_t))
-				h->oldest_t = l->t;
-		}
-	}
-
-	if (h->num_lines > h->max_lines)
-	{
-		h->oldest_t = 0; /* recalculate in next loop */
-
-		for (l = h->head; l; l = l_next)
-		{
-			l_next = l->next;
-			if (h->num_lines > h->max_lines)
-			{
-				hbm_history_del_line(h, l);
-				continue;
-			}
-			if ((h->oldest_t == 0) || (l->t < h->oldest_t))
-				h->oldest_t = l->t;
-		}
-	}
-
-	return 1;
-}
-
-int hbm_history_destroy(const char *object)
-{
-	HistoryLogObject *h = hbm_find_object(object);
-	HistoryLogLine *l, *l_next;
-
-	if (!h)
-		return 0;
-
-	for (l = h->head; l; l = l_next)
-	{
-		l_next = l->next;
-		/* We could use hbm_history_del_line() here but
-		 * it does unnecessary work, this is quicker.
-		 * The only danger is that we may forget to free some
-		 * fields that are added later there but not here.
-		 */
-		free_message_tags(l->mtags);
-		safe_free(l);
-	}
-
-	hbm_delete_object_hlo(h);
-	return 1;
-}
-
-/** Set new limit on history object */
-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;
-	h->max_time = max_time;
-	hbm_history_cleanup(h); /* impose new restrictions */
-	return 1;
-}
-
-/** Read the master.db file, this is done at the INIT stage so we can still
- * reject the configuration / boot attempt.
- *
- * IMPORTANT: Because we run at INIT you must use test.xyz values and not cfg.xyz!
- */
-static int hbm_read_masterdb(void)
-{
-	UnrealDB *db;
-	uint32_t mdb_version;
-	char *prehash = NULL;
-	char *posthash = NULL;
-
-	db = unrealdb_open(test.masterdb, UNREALDB_MODE_READ, test.db_secret);
-
-	if (!db)
-	{
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
-		{
-			/* Database does not exist. Could be first boot */
-			config_warn("[history] No database present at '%s', will start a new one", test.masterdb);
-			if (!hbm_write_masterdb())
-				return 0; /* fatal error */
-			return 1;
-		} else
-		{
-			config_warn("[history] Unable to open the database file '%s' for reading: %s", test.masterdb, unrealdb_get_error_string());
-			return 0;
-		}
-	}
-
-	/* Master db has an easy format:
-	 * 64 bits: version number
-	 * string:  pre hash
-	 * string:  post hash
-	 */
-	if (!unrealdb_read_int32(db, &mdb_version) ||
-	    !unrealdb_read_str(db, &prehash) ||
-	    !unrealdb_read_str(db, &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;
-	}
-	unrealdb_close(db);
-
-	if (!prehash || !posthash)
-	{
-		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.. */
-	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;
-}
-
-/** Write the master.db file. Only call this if it does not exist yet! */
-static int hbm_write_masterdb(void)
-{
-	UnrealDB *db;
-	uint32_t mdb_version;
-
-	if (!test.db_secret)
-		abort();
-
-	db = unrealdb_open(test.masterdb, UNREALDB_MODE_WRITE, test.db_secret);
-	if (!db)
-	{
-		config_error("[history] Unable to write to '%s': %s",
-			test.masterdb, unrealdb_get_error_string());
-		return 0;
-	}
-
-	if (!hbm_prehash || !hbm_posthash)
-		abort(); /* impossible */
-
-	mdb_version = 5000;
-	if (!unrealdb_write_int32(db, mdb_version) ||
-	    !unrealdb_write_str(db, hbm_prehash) ||
-	    !unrealdb_write_str(db, hbm_posthash))
-	{
-		config_error("[history] Unable to write to '%s': %s",
-			test.masterdb, unrealdb_get_error_string());
-		return 0;
-	}
-	unrealdb_close(db);
-	return 1;
-}
-
-/** Read all database files (except master.db, which is already loaded) */
-static void hbm_read_dbs(void)
-{
-	char buf[512];
-#ifndef _WIN32
-	struct dirent *dir;
-	DIR *fd = opendir(cfg.directory);
-
-	if (!fd)
-		return;
-
-	while ((dir = readdir(fd)))
-	{
-		char *fname = dir->d_name;
-#else
-	/* Windows */
-	WIN32_FIND_DATA hData;
-	HANDLE hFile;
-	char xbuf[512];
-
-	snprintf(xbuf, sizeof(xbuf), "%s/*.db", cfg.directory);
-
-	hFile = FindFirstFile(xbuf, &hData);
-	if (hFile == INVALID_HANDLE_VALUE)
-		return;
-
-	do
-	{
-		char *fname = hData.cFileName;
-#endif
-
-		/* Common section for both *NIX and Windows */
-
-		snprintf(buf, sizeof(buf), "%s/%s", cfg.directory, fname);
-		if (filename_has_suffix(fname, ".db") && strcmp(fname, "master.db"))
-		{
-			if (!hbm_read_db(buf))
-			{
-				/* On error, we move the file to the 'bad' subdirectory,
-				 * eg data/history/bad/xyz.db
-				 */
-				char buf2[512];
-				snprintf(buf2, sizeof(buf2), "%s/bad", cfg.directory);
-#ifdef _WIN32
-				(void)mkdir(buf2); /* (errors ignored) */
-#else
-				(void)mkdir(buf2, S_IRUSR|S_IWUSR|S_IXUSR); /* (errors ignored) */
-#endif
-				snprintf(buf2, sizeof(buf2), "%s/bad/%s", cfg.directory, fname);
-				unlink(buf2);
-				(void)rename(buf, buf2);
-			}
-		}
-
-		/* End of common section */
-#ifndef _WIN32
-	}
-	closedir(fd);
-#else
-	} while (FindNextFile(hFile, &hData));
-	FindClose(hFile);
-#endif
-}
-
-#define RESET_VALUES_LOOP()	do { \
-					safe_free(mtag_name); \
-					safe_free(mtag_value); \
-					safe_free(line); \
-					free_message_tags(mtags); \
-					mtags = NULL; \
-					magic = 0; \
-					line_ts = 0; \
-				} while(0)
-
-#define R_SAFE_CLEANUP()	do { \
-					unrealdb_close(db); \
-					RESET_VALUES_LOOP(); \
-					safe_free(prehash); \
-					safe_free(posthash); \
-					safe_free(object); \
-				} while(0)
-#define R_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			config_warn("[history] Read error from database file '%s' (possible corruption): %s", fname, unrealdb_get_error_string()); \
-			R_SAFE_CLEANUP(); \
-			return 0; \
-		} \
-	} while(0)
-
-
-/** Read a channel history db file */
-static int hbm_read_db(const char *fname)
-{
-	UnrealDB *db = NULL;
-	// header
-	uint32_t magic = 0;
-	uint32_t version = 0;
-	char *prehash = NULL;
-	char *posthash = NULL;
-	char *object = NULL;
-	uint64_t max_lines = 0;
-	uint64_t max_time = 0;
-	// then, for each entry:
-	// (magic)
-	uint64_t line_ts;
-	char *mtag_name = NULL;
-	char *mtag_value = NULL;
-	MessageTag *mtags = NULL, *m;
-	char *line = NULL;
-	HistoryLogObject *h;
-
-	db = unrealdb_open(fname, UNREALDB_MODE_READ, cfg.db_secret);
-	if (!db)
-	{
-		config_warn("[history] Unable to open the database file '%s' for reading: %s", fname, unrealdb_get_error_string());
-		return 0;
-	}
-
-	R_SAFE(unrealdb_read_int32(db, &magic));
-	if (magic != HISTORYDB_MAGIC_FILE_START)
-	{
-		config_warn("[history] Database '%s' has wrong magic value, possibly corrupt (0x%lx), expected HISTORYDB_MAGIC_FILE_START.",
-			fname, (long)magic);
-		unrealdb_close(db);
-		return 0;
-	}
-
-	/* Now do a version check */
-	R_SAFE(unrealdb_read_int32(db, &version));
-	if (version < 4999)
-	{
-		config_warn("[history] Database '%s' uses an unsupported - possibly old - format (%ld).", fname, (long)version);
-		unrealdb_close(db);
-		return 0;
-	}
-	if (version > 5000)
-	{
-		config_warn("[history] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
-			fname, (unsigned long)version, (unsigned long)5000);
-		unrealdb_close(db);
-		return 0;
-	}
-
-	R_SAFE(unrealdb_read_str(db, &prehash));
-	R_SAFE(unrealdb_read_str(db, &posthash));
-
-	if (!prehash || !posthash || strcmp(prehash, hbm_prehash) || strcmp(posthash, hbm_posthash))
-	{
-		config_warn("[history] Database '%s' does not belong to our 'master.db'. Are you mixing old with new .db files perhaps? This is not supported. File ignored.",
-			fname);
-		R_SAFE_CLEANUP();
-		return 0;
-	}
-
-	R_SAFE(unrealdb_read_str(db, &object));
-	R_SAFE(unrealdb_read_int64(db, &max_lines));
-	R_SAFE(unrealdb_read_int64(db, &max_time));
-	h = hbm_find_object(object);
-	if (!h)
-	{
-		config_warn("Channel %s does not have +H set, deleting history", object);
-		R_SAFE_CLEANUP();
-		unlink(fname);
-		return 1; /* No problem */
-	}
-
-	while(1)
-	{
-		RESET_VALUES_LOOP();
-		R_SAFE(unrealdb_read_int32(db, &magic));
-		if (magic == HISTORYDB_MAGIC_FILE_END)
-			break; /* We're done, end gracefully */
-		if (magic != HISTORYDB_MAGIC_ENTRY_START)
-		{
-			config_warn("[history] Read error from database file '%s': wrong magic value in entry (0x%lx), expected HISTORYDB_MAGIC_ENTRY_START",
-				fname, (long)magic);
-			R_SAFE_CLEANUP();
-			return 0;
-		}
-
-		R_SAFE(unrealdb_read_int64(db, &line_ts));
-		while(1)
-		{
-			R_SAFE(unrealdb_read_str(db, &mtag_name));
-			R_SAFE(unrealdb_read_str(db, &mtag_value));
-			if (!mtag_name && !mtag_value)
-				break; /* We're done reading mtags for this particular line */
-			m = safe_alloc(sizeof(MessageTag));
-			safe_strdup(m->name, mtag_name);
-			safe_strdup(m->value, mtag_value);
-			AppendListItem(m, mtags);
-			safe_free(mtag_name);
-			safe_free(mtag_value);
-		}
-		R_SAFE(unrealdb_read_str(db, &line));
-		R_SAFE(unrealdb_read_int32(db, &magic));
-		if (magic != HISTORYDB_MAGIC_ENTRY_END)
-		{
-			config_warn("[history] Read error from database file '%s': wrong magic value in entry (0x%lx), expected HISTORYDB_MAGIC_ENTRY_END",
-				fname, (long)magic);
-			R_SAFE_CLEANUP();
-			return 0;
-		}
-		hbm_history_add(object, mtags, line);
-	}
-
-	/* Prevent directly rewriting the channel, now that we have just read it.
-	 * This could cause things not to fire in case of corner issues like
-	 * hot-loading but that should be acceptable. The alternative is that
-	 * all log files are written again with identical contents for no reason,
-	 * which is a waste of resources.
-	 */
-	h->dirty = 0;
-
-	R_SAFE_CLEANUP();
-	return 1;
-}
-
-/** Flush all dirty logs to disk on UnrealIRCd stop */
-static void hbm_flush(void)
-{
-	int hashnum;
-	HistoryLogObject *h;
-
-	if (!cfg.persist)
-		return; /* nothing to flush anyway */
-
-	for (hashnum = 0; hashnum < HISTORY_BACKEND_MEM_HASH_TABLE_SIZE; hashnum++)
-	{
-		for (h = history_hash_table[hashnum]; h; h = h->next)
-		{
-			hbm_history_cleanup(h);
-			if (cfg.persist && h->dirty)
-				hbm_write_db(h);
-		}
-	}
-}
-
-/** Free all history.
- * This is only called when the module is unloaded for good, so
- * when UnrealIRCd is terminating or someone comments the module out
- * and/or switches history backends.
- */
-void hbm_free_all_history(ModData *m)
-{
-	int hashnum;
-	HistoryLogObject *h, *h_next;
-
-	for (hashnum = 0; hashnum < HISTORY_BACKEND_MEM_HASH_TABLE_SIZE; hashnum++)
-	{
-		for (h = history_hash_table[hashnum]; h; h = h_next)
-		{
-			h_next = h->next;
-			hbm_history_destroy(h->name);
-		}
-	}
-
-	/* And free the hash table pointer */
-	safe_free(m->ptr);
-}
-
-/** Periodically clean the history.
- * Instead of doing all channels in 1 go, we do a limited number
- * of channels each call, hence the 'static int' and the do { } while
- * rather than a regular for loop.
- * Note that we already impose the line limit in hbm_history_add,
- * so this history_mem_clean is for removals due to max_time limits.
- */
-EVENT(history_mem_clean)
-{
-	static int hashnum = 0;
-	int loopcnt = 0;
-	Channel *channel;
-	HistoryLogObject *h;
-
-	do
-	{
-		for (h = history_hash_table[hashnum]; h; h = h->next)
-		{
-			hbm_history_cleanup(h);
-			if (cfg.persist && h->dirty)
-				hbm_write_db(h);
-		}
-
-		hashnum++;
-
-		if (hashnum >= HISTORY_BACKEND_MEM_HASH_TABLE_SIZE)
-			hashnum = 0;
-	} while(loopcnt++ < HISTORY_CLEAN_PER_LOOP);
-}
-
-const char *hbm_history_filename(HistoryLogObject *h)
-{
-	static char fname[512];
-	char oname[OBJECTLEN+1];
-	char hashdata[512];
-	char hash[128];
-
-	if (!hbm_prehash || !hbm_posthash)
-		abort(); /* impossible */
-
-	strtolower_safe(oname, h->name, sizeof(oname));
-	snprintf(hashdata, sizeof(hashdata), "%s %s %s", hbm_prehash, oname, hbm_posthash);
-	sha256hash(hash, hashdata, strlen(hashdata));
-
-	snprintf(fname, sizeof(fname), "%s/%s.db", cfg.directory, hash);
-	return fname;
-}
-
-#define WARN_WRITE_ERROR(fname) \
-	do { \
-		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) \
-	do { \
-		if (!(x)) { \
-			WARN_WRITE_ERROR(tmpfname); \
-			unrealdb_close(db); \
-			return 0; \
-		} \
-	} while(0)
-
-
-// FIXME: the code below will cause massive floods on disk or I/O errors if hundreds of
-// channel logs fail to write... fun.
-static int hbm_write_db(HistoryLogObject *h)
-{
-	UnrealDB *db;
-	const char *realfname;
-	char tmpfname[512];
-	HistoryLogLine *l;
-	MessageTag *m;
-	Channel *channel;
-
-	if (!cfg.db_secret)
-		abort();
-
-	channel = find_channel(h->name);
-	if (!channel || !has_channel_mode(channel, 'P'))
-		return 1; /* Don't save this channel, pretend success */
-
-	realfname = hbm_history_filename(h);
-	snprintf(tmpfname, sizeof(tmpfname), "%s.tmp", realfname);
-
-	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
-	if (!db)
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-	W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_FILE_START));
-	W_SAFE(unrealdb_write_int32(db, 5000)); /* VERSION */
-	W_SAFE(unrealdb_write_str(db, hbm_prehash));
-	W_SAFE(unrealdb_write_str(db, hbm_posthash));
-	W_SAFE(unrealdb_write_str(db, h->name));
-
-	W_SAFE(unrealdb_write_int64(db, h->max_lines));
-	W_SAFE(unrealdb_write_int64(db, h->max_time));
-
-	for (l = h->head; l; l = l->next)
-	{
-		W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_ENTRY_START));
-		W_SAFE(unrealdb_write_int64(db, l->t));
-		for (m = l->mtags; m; m = m->next)
-		{
-			W_SAFE(unrealdb_write_str(db, m->name));
-			W_SAFE(unrealdb_write_str(db, m->value)); /* can be NULL */
-		}
-		W_SAFE(unrealdb_write_str(db, NULL));
-		W_SAFE(unrealdb_write_str(db, NULL));
-		W_SAFE(unrealdb_write_str(db, l->line));
-		W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_ENTRY_END));
-	}
-	W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_FILE_END));
-
-	if (!unrealdb_close(db))
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-#ifdef _WIN32
-	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
-	unlink(realfname);
-#endif
-	if (rename(tmpfname, realfname) < 0)
-	{
-		config_error("[history] Error renaming '%s' to '%s': %s (HISTORY NOT SAVED)",
-			tmpfname, realfname, strerror(errno));
-		return 0;
-	}
-
-	/* Now that everything was successful, clear the dirty flag */
-	h->dirty = 0;
-	return 1;
-}
-
-static void hbm_delete_db(HistoryLogObject *h)
-{
-	UnrealDB *db;
-	const char *fname;
-	if (!cfg.persist || !hbm_prehash || !hbm_posthash)
-	{
-#ifdef DEBUGMODE
-		abort(); /* we should not be called, so debug this */
-#endif
-		return;
-	}
-	fname = hbm_history_filename(h);
-	unlink(fname);
-}
-
-void hbm_generic_free(ModData *m)
-{
-	safe_free(m->ptr);
-}
diff --git a/src/modules/history_backend_null.c b/src/modules/history_backend_null.c
@@ -1,74 +0,0 @@
-/* src/modules/history_backend_null.c - History Backend: null / none
- * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-#include "unrealircd.h"
-
-/* This is the null backend type. It does not store anything at all.
- * This can be useful on a hub server where you don't need channel
- * history but still need to have a backend loaded to use the
- * channel mode +H.
- */
-
-ModuleHeader MOD_HEADER
-= {
-	"history_backend_null",
-	"2.0",
-	"History backend: null/none",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-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()
-{
-	HistoryBackendInfo hbi;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&hbi, 0, sizeof(hbi));
-	hbi.name = "mem";
-	hbi.history_set_limit = hbn_history_set_limit;
-	hbi.history_add = hbn_history_add;
-	hbi.history_request = hbn_history_request;
-	hbi.history_destroy = hbn_history_destroy;
-	if (!HistoryBackendAdd(modinfo->handle, &hbi))
-		return MOD_FAILED;
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int hbn_history_add(const char *object, MessageTag *mtags, const char *line)
-{
-	return 1;
-}
-
-HistoryResult *hbn_history_request(const char *object, HistoryFilter *filter)
-{
-	return NULL;
-}
-
-int hbn_history_set_limit(const char *object, int max_lines, long max_time)
-{
-	return 1;
-}
-
-int hbn_history_destroy(const char *object)
-{
-	return 1;
-}
diff --git a/src/modules/ident_lookup.c b/src/modules/ident_lookup.c
@@ -1,256 +0,0 @@
-/* src/modules/ident_lookup.c - Ident lookups (RFC1413)
- * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"ident_lookup",
-	"1.0",
-	"Ident lookups (RFC1413)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-static EVENT(check_ident_timeout);
-static int ident_lookup_connect(Client *client);
-static void ident_lookup_send(int fd, int revents, void *data);
-static void ident_lookup_receive(int fd, int revents, void *data);
-static char *ident_lookup_parse(Client *client, char *buf);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1); /* needed? or not? */
-	EventAdd(NULL, "check_ident_timeout", check_ident_timeout, NULL, 1000, 0);
-	HookAdd(modinfo->handle, HOOKTYPE_IDENT_LOOKUP, 0, ident_lookup_connect);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-static void ident_lookup_failed(Client *client)
-{
-	ircstats.is_abad++;
-	if (client->local->authfd != -1)
-	{
-		fd_close(client->local->authfd);
-		--OpenFiles;
-		client->local->authfd = -1;
-	}
-	ClearIdentLookupSent(client);
-	ClearIdentLookup(client);
-	if (should_show_connect_info(client))
-		sendto_one(client, NULL, ":%s %s", me.name, REPORT_FAIL_ID);
-}
-
-static EVENT(check_ident_timeout)
-{
-	Client *client, *next;
-
-	list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
-	{
-		if (IsIdentLookup(client))
-		{
-			if (IsIdentLookupSent(client))
-			{
-				/* set::ident::connect-timeout */
-				if ((TStime() - client->local->creationtime) > IDENT_CONNECT_TIMEOUT)
-					ident_lookup_failed(client);
-			} else
-			{
-				/* set::ident::read-timeout */
-				if ((TStime() - client->local->creationtime) > IDENT_READ_TIMEOUT)
-					ident_lookup_failed(client);
-			}
-		}
-	}
-}
-
-/** Start the ident lookup for this user */
-static int ident_lookup_connect(Client *client)
-{
-	char buf[BUFSIZE];
-
-	snprintf(buf, sizeof buf, "identd: %s", get_client_name(client, TRUE));
-	if ((client->local->authfd = fd_socket(IsIPV6(client) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf)) == -1)
-	{
-		ident_lookup_failed(client);
-		return 0;
-	}
-	if (++OpenFiles >= maxclients+1)
-	{
-		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;
-		return 0;
-	}
-
-	if (should_show_connect_info(client))
-		sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_ID);
-
-	set_sock_opts(client->local->authfd, client, IsIPV6(client));
-
-	/* Bind to the IP the user got in */
-	unreal_bind(client->local->authfd, client->local->listener->ip, 0, IsIPV6(client));
-
-	/* And connect... */
-	if (!unreal_connect(client->local->authfd, client->ip, 113, IsIPV6(client)))
-	{
-		ident_lookup_failed(client);
-		return 0;
-	}
-	SetIdentLookupSent(client);
-	SetIdentLookup(client);
-
-	fd_setselect(client->local->authfd, FD_SELECT_WRITE, ident_lookup_send, client);
-
-	return 0;
-}
-
-/** Send the request to the ident server */
-static void ident_lookup_send(int fd, int revents, void *data)
-{
-	char authbuf[32];
-	Client *client = data;
-
-	ircsnprintf(authbuf, sizeof(authbuf), "%d , %d\r\n",
-		client->local->port,
-		client->local->listener->port);
-
-	if (WRITE_SOCK(client->local->authfd, authbuf, strlen(authbuf)) != strlen(authbuf))
-	{
-		if (ERRNO == P_EAGAIN)
-			return; /* Not connected yet, try again later */
-		ident_lookup_failed(client);
-		return;
-	}
-	ClearIdentLookupSent(client);
-
-	fd_setselect(client->local->authfd, FD_SELECT_READ, ident_lookup_receive, client);
-	fd_setselect(client->local->authfd, FD_SELECT_WRITE, NULL, client);
-
-	return;
-}
-
-/** Receive the ident response */
-static void ident_lookup_receive(int fd, int revents, void *userdata)
-{
-	Client *client = userdata;
-	char *ident = NULL;
-	char buf[512];
-	int len;
-
-	len = READ_SOCK(client->local->authfd, buf, sizeof(buf)-1);
-
-	/* We received a response. We don't bother with fragmentation
-	 * since that is not going to happen for such a short string.
-	 * Other IRCd's think the same and this simplifies things a lot.
-	 */
-
-	/* Before we continue, we can already tear down the connection
-	 * and set the appropriate flags that we are finished.
-	 */
-	fd_close(client->local->authfd);
-	--OpenFiles;
-	client->local->authfd = -1;
-	client->local->identbufcnt = 0;
-	ClearIdentLookup(client);
-
-	if (should_show_connect_info(client))
-		sendto_one(client, NULL, ":%s %s", me.name, REPORT_FIN_ID);
-
-	if (len > 0)
-	{
-		buf[len] = '\0'; /* safe, due to the READ_SOCK() being on sizeof(buf)-1 */
-		ident = ident_lookup_parse(client, buf);
-	}
-	if (ident)
-	{
-		strlcpy(client->ident, ident, USERLEN + 1);
-		SetIdentSuccess(client);
-		ircstats.is_asuc++;
-	} else {
-		ircstats.is_abad++;
-	}
-	return;
-}
-
-static char *ident_lookup_parse(Client *client, char *buf)
-{
-	/* <port> , <port> : USERID : <OSTYPE>: <username>
-	 * Actually the only thing we care about is <username>
-	 */
-	int port1 = 0, port2 = 0;
-	char *ostype = NULL;
-	char *username = NULL;
-	char *p, *p2;
-
-	skip_whitespace(&buf);
-	p = strchr(buf, ',');
-	if (!p)
-		return NULL;
-	*p = '\0';
-	port1 = atoi(buf); /* port1 is set */
-
-	/*  <port> : USERID : <OSTYPE>: <username> */
-	buf = p + 1;
-	p = strchr(buf, ':');
-	if (!p)
-		return NULL;
-	*p = '\0';
-	port2 = atoi(buf); /* port2 is set */
-
-	/*  USERID : <OSTYPE>: <username> */
-	buf = p + 1;
-	skip_whitespace(&buf);
-	if (strncmp(buf, "USERID", 6))
-		return NULL;
-	buf += 6; /* skip over strlen("USERID") */
-	skip_whitespace(&buf);
-	if (*buf != ':')
-		return NULL;
-	buf++;
-	skip_whitespace(&buf);
-
-	/*  <OSTYPE>: <username> */
-	p = strchr(buf, ':');
-	if (!p)
-		return NULL;
-
-	/*  <username> */
-	buf = p+1;
-	skip_whitespace(&buf);
-
-	/* Username */
-	// A) Skip any ~ or ^ at the start
-	for (; *buf; buf++)
-		if (!strchr("~^", *buf) && (*buf > 32))
-			break;
-	// B) Stop at the end, IOTW stop at newline, space, etc.
-	for (p=buf; *p; p++)
-	{
-		if (strchr("\n\r@:", *p) || (*p <= 32))
-		{
-			*p = '\0';
-			break;
-		}
-	}
-	if (*buf == '\0')
-		return NULL;
-	return buf;
-}
diff --git a/src/modules/invite.c b/src/modules/invite.c
@@ -1,542 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/invite.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"
-
-#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);
-
-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
-  = {
-	"invite",
-	"5.0",
-	"command /invite", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, invite_config_test);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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_INVITES(client); inv; inv = inv->next)
-	{
-		sendnumeric(client, RPL_INVITELIST,
-			   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 name
-**  parv[3] - override (S2S only)
-*/
-CMD_FUNC(cmd_invite)
-{
-	Client *target = NULL;
-	Channel *channel = NULL;
-	int override = 0;
-	int i = 0;
-	int params_ok = 0;
-	Hook *h;
-
-	if (parc >= 3 && *parv[1] != '\0')
-	{
-		params_ok = 1;
-		target = find_user(parv[1], NULL);
-		channel = find_channel(parv[2]);
-	}
-	
-	if (!MyConnect(client))
-	/*** remote 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;
-	}
-
-	/*** local invite ***/
-
-	/* the client requested own invite list */
-	if (parc == 1)
-	{
-		send_invite_list(client);
-		return;
-	}
-
-	/* notify user about bad parameters */
-	if (!params_ok)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "INVITE");
-		return;
-	}
-
-	if (!target)
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		return;
-	}
-
-	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);
-		if (i == HOOK_DENY)
-			return;
-		if (i == HOOK_ALLOW)
-			break;
-	}
-
-	if (!IsMember(client, channel) && !IsULine(client))
-	{
-		if (ValidatePermissionsForPath("channel:override:invite:notinchannel",client,NULL,channel,NULL) && client == target)
-		{
-			override = 1;
-		} else {
-			sendnumeric(client, ERR_NOTONCHANNEL, parv[2]);
-			return;
-		}
-	}
-
-	if (IsMember(target, channel))
-	{
-		sendnumeric(client, ERR_USERONCHANNEL, parv[1], parv[2]);
-		return;
-	}
-
-	if (has_channel_mode(channel, 'i'))
-	{
-		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->name);
-				return;
-			}
-		}
-		else if (!IsMember(client, channel) && !IsULine(client))
-		{
-			if (ValidatePermissionsForPath("channel:override:invite:invite-only",client,NULL,channel,NULL) && client == target)
-			{
-				override = 1;
-			} else {
-				sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
-				return;
-			}
-		}
-	}
-
-	if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN &&
-	    !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->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 (!override)
-	{
-		sendnumeric(client, RPL_INVITING, target->name, channel->name);
-		if (target->user->away)
-		{
-			sendnumeric(client, RPL_AWAY, target->name, target->user->away);
-		}
-	}
-	else
-	{
-		/* Send OperOverride messages */
-		char override_what = '\0';
-		if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
-			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'))
-			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;
-#endif
-		else
-			return;
-	}
-
-	/* 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)
-	{
-		for (tmp = CLIENT_INVITES(to); tmp->next; tmp = tmp->next)
-			;
-		del_invite(to, tmp->value.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 (link_list_length(CHANNEL_INVITES(channel)) >= MAXCHANNELSPERUSER)
-	{
-		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
@@ -1,141 +0,0 @@
-/*
- *   cmd_ircops - /IRCOPS command that lists IRC Operators
- *   (C) Copyright 2004-2016 Syzop <syzop@vulnscan.org>
- *   (C) Copyright 2003-2004 AngryWolf <angrywolf@flashmail.com>
- *
- *   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_IRCOPS        "IRCOPS"
-#define IsAway(x)         (x)->user->away
-
-CMD_FUNC(cmd_ircops);
-
-ModuleHeader MOD_HEADER
-  = {
-	"ircops",
-	"3.71",
-	"/IRCOPS command that lists IRC Operators",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	if (CommandExists(MSG_IRCOPS))
-	{
-		config_error("Command " MSG_IRCOPS " already exists");
-		return MOD_FAILED;
-	}
-	CommandAdd(modinfo->handle, MSG_IRCOPS, cmd_ircops, MAXPARA, CMD_USER);
-
-	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
-	{
-		config_error("Error adding command " MSG_IRCOPS ": %s",
-			ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/*
- * cmd_ircops
- *
- *     parv[0]: sender prefix
- *
- *     Originally comes from TR-IRCD, but I changed it in several places.
- *     In addition, I didn't like to display network name. In addition,
- *     instead of realname, servername is shown. See the original
- *     header below.
- */
-
-/************************************************************************
- * IRC - Internet Relay Chat, modules/ircops.c
- *
- *   Copyright (C) 2000-2002 TR-IRCD Development
- *
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Co Center
- *
- * 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 2, 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.
- */
-
-CMD_FUNC(cmd_ircops)
-{
-	Client *acptr;
-	char buf[512];
-	int opers = 0, total = 0, aways = 0;
-
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		/* List only real IRC Operators */
-		if (IsULine(acptr) || !IsUser(acptr) || !IsOper(acptr))
-			continue;
-		/* Don't list +H users */
-		if (!IsOper(client) && IsHideOper(acptr))
-			continue;
-
-		sendto_one(client, NULL, ":%s %d %s :\2%s\2 is %s on %s" "%s",
-			me.name, RPL_TEXT, client->name,
-			acptr->name,
-			"an IRC Operator", /* find_otype(acptr->umodes), */
-			acptr->user->server,
-			(IsAway(acptr) ? " [Away]" : ""));
-
-		if (IsAway(acptr))
-			aways++;
-		else
-			opers++;
-
-	}
-
-	total = opers + aways;
-
-	snprintf(buf, sizeof(buf),
-		"Total: \2%d\2 IRCOP%s online - \2%d\2 Oper%s available and \2%d\2 Away",
-		total, (total) != 1 ? "s" : "",
-		opers, opers != 1 ? "s" : "",
-		aways);
-
-	sendnumericfmt(client, RPL_TEXT, ":%s", buf);
-	sendnumericfmt(client, RPL_TEXT, ":End of /IRCOPS list");
-}
diff --git a/src/modules/ison.c b/src/modules/ison.c
@@ -1,105 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/ison.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_ison);
-
-#define MSG_ISON 	"ISON"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"ison",
-	"5.0",
-	"command /ison", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_ISON, cmd_ison, 1, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * cmd_ison added by Darren Reed 13/8/91 to act as an efficent user indicator
- * with respect to cpu/bandwidth used. Implemented for NOTIFY feature in
- * clients. Designed to reduce number of whois requests. Can process
- * nicknames in batches as long as the maximum buffer length.
- *
- * format:
- * ISON :nicklist
- */
-
-CMD_FUNC(cmd_ison)
-{
-	char buf[BUFSIZE];
-	char request[BUFSIZE];
-	char namebuf[USERLEN + HOSTLEN + 4];
-	Client *acptr;
-	char *s, *user;
-	char *p = NULL;
-
-	if (!MyUser(client))
-		return;
-
-	if (parc < 2)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "ISON");
-		return;
-	}
-
-	ircsnprintf(buf, sizeof(buf), ":%s %d %s :", me.name, RPL_ISON, client->name);
-
-	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_user(s, NULL)))
-		{
-			if (user)
-			{
-				ircsnprintf(namebuf, sizeof(namebuf), "%s@%s", acptr->user->username, GetHost(acptr));
-				if (!match_simple(user, namebuf)) continue;
-				*--user = '!';
-			}
-
-			strlcat(buf, s, sizeof(buf));
-			strlcat(buf, " ", sizeof(buf));
-		}
-	}
-
-	sendto_one(client, NULL, "%s", buf);
-}
diff --git a/src/modules/issued-by-tag.c b/src/modules/issued-by-tag.c
@@ -1,155 +0,0 @@
-/*
- * unrealircd.org/issued-by message tag (server only)
- * Shows who or what actually issued the command.
- * (C) Copyright 2023-.. Syzop and The UnrealIRCd Team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"issued-by-tag",
-	"6.0",
-	"unrealircd.org/issued-by message tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Forward declarations */
-int issued_by_mtag_is_ok(Client *client, const char *name, const char *value);
-int issued_by_mtag_should_send_to_client(Client *target);
-void mtag_inherit_issued_by(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
-void _mtag_add_issued_by(MessageTag **mtags, Client *client, MessageTag *recv_mtags);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_MTAG_GENERATE_ISSUED_BY_IRC, _mtag_add_issued_by);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "unrealircd.org/issued-by";
-	mtag.is_ok = issued_by_mtag_is_ok;
-	mtag.should_send_to_client = issued_by_mtag_should_send_to_client;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_inherit_issued_by);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending 'unrealircd.org/issued-by'
- * is permitted to do so and uses a permitted syntax.
- * We simply allow unrealircd.org/issued-by ONLY from servers and with any syntax.
- */
-int issued_by_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client))
-		return 1;
-
-	return 0;
-}
-
-void mtag_inherit_issued_by(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/issued-by");
-	if (m)
-	{
-		m = duplicate_mtag(m);
-		AddListItem(m, *mtag_list);
-	}
-}
-
-/** Outgoing filter for this message tag */
-int issued_by_mtag_should_send_to_client(Client *target)
-{
-	if (IsServer(target) || IsOper(target))
-		return 1;
-
-	return 0;
-}
-
-/** Add "unrealircd.org/issued-by" tag, if applicable.
- * @param mtags		Pointer to the message tags linked list head
- * @param client	The client issuing the command, or NULL for none.
- * @param recv_mtags	The mtags to inherit from, or NULL for none.
- * @notes If specifying both 'client' and 'recv_mtags' then
- * if inheritance through 'recv_mtags' takes precedence (if it exists).
- *
- * Typical usage is:
- * For locally generated:
- *   mtag_add_issued_by(&mtags, client, NULL);
- * For inheriting from remote requests:
- *   mtag_add_issued_by(&mtags, NULL, recv_mtags);
- * For both, such as if the command is used from RPC:
- *   mtag_add_issued_by(&mtags, client, recv_mtags);
- */
-void _mtag_add_issued_by(MessageTag **mtags, Client *client, MessageTag *recv_mtags)
-{
-	MessageTag *m;
-	char buf[512];
-
-	m = find_mtag(recv_mtags, "unrealircd.org/issued-by");
-	if (m)
-	{
-		m = duplicate_mtag(m);
-		AddListItem(m, *mtags);
-		return;
-	}
-
-	if (client == NULL)
-		return;
-
-	if (IsRPC(client) && client->rpc)
-	{
-		// TODO: test with all of: local rpc through unix socket, local rpc web, RRPC
-		if (client->rpc->issuer)
-		{
-			snprintf(buf, sizeof(buf), "RPC:%s@%s:%s", client->rpc->rpc_user, client->uplink->name, client->rpc->issuer);
-		} else {
-			snprintf(buf, sizeof(buf), "RPC:%s@%s", client->rpc->rpc_user, client->uplink->name);
-		}
-	} else
-	if (IsULine(client))
-	{
-		if (IsUser(client))
-			snprintf(buf, sizeof(buf), "SERVICES:%s@%s", client->name, client->uplink->name);
-		else
-			snprintf(buf, sizeof(buf), "SERVICES:%s", client->name);
-	} else
-	if (IsOper(client))
-	{
-		const char *operlogin = moddata_client_get(client, "operlogin");
-		if (operlogin)
-			snprintf(buf, sizeof(buf), "OPER:%s@%s:%s", client->name, client->uplink->name, operlogin);
-		else
-			snprintf(buf, sizeof(buf), "OPER:%s@%s", client->name, client->uplink->name);
-	} else
-	{
-		return;
-	}
-
-	m = safe_alloc(sizeof(MessageTag));
-	safe_strdup(m->name, "unrealircd.org/issued-by");
-	safe_strdup(m->value, buf);
-	AddListItem(m, *mtags);
-}
diff --git a/src/modules/join.c b/src/modules/join.c
@@ -1,615 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/join.c
- *   (C) 2005 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"
-
-/* Forward declarations */
-CMD_FUNC(cmd_join);
-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;
-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 */
-#define MSG_JOIN 	"JOIN"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"join",
-	"5.0",
-	"command /join", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	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_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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* This function checks if a locally connected user may join the channel.
- * It also provides an number of hooks where modules can plug in to.
- * Note that the order of checking has been carefully thought of
- * (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, const char *key, char **errmsg)
-{
-	Hook *h;
-
-	/* An /INVITE lets you bypass all restrictions */
-	if (is_invited(client, channel))
-	{
-		int j = 0;
-		for (h = Hooks[HOOKTYPE_INVITE_BYPASS]; h; h = h->next)
-		{
-			j = (*(h->func.intfunc))(client,channel);
-			if (j != 0)
-				break;
-		}
-		/* Bypass is OK, unless a HOOKTYPE_INVITE_BYPASS hook returns HOOK_DENY */
-		if (j != HOOK_DENY)
-			return 0;
-	}
-
-	for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next)
-	{
-		int i = (*(h->func.intfunc))(client,channel,key, errmsg);
-		if (i != 0)
-			return i;
-	}
-
-	/* See if we can evade this ban */
-	if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL))
-	{
-		*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->name))
-	{
-		*errmsg = STR_ERR_OPERSPVERIFY;
-		return (ERR_OPERSPVERIFY);
-	}
-#endif
-#endif
-
-	return 0;
-}
-
-/*
-** cmd_join
-**	parv[1] = channel
-**	parv[2] = channel password (key)
-**
-** Due to message tags, remote servers should only send 1 channel
-** per JOIN. Or even better, use SJOIN instead.
-** Otherwise we cannot use unique msgid's and such.
-** UnrealIRCd 4 and probably UnrealIRCd 3.2.something already do
-** this, so this comment is mostly for services coders, I guess.
-*/
-CMD_FUNC(cmd_join)
-{
-	int r;
-
-	if (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))
-		return;
-	do_join(client, parc, parv);
-	bouncedtimes = 0;
-}
-
-/** Send JOIN message for 'client' to all users in 'channel'.
- * Taking into account that not everyone in channel should see the JOIN (mode +D)
- * and taking into account the different types of JOIN (due to CAP extended-join).
- */
-void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags)
-{
-	int chanops_only = invisible_user_in_channel(client, channel);
-	Member *lp;
-	Client *acptr;
-	char joinbuf[512];
-	char exjoinbuf[512];
-
-	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);
-
-	for (lp = channel->members; lp; lp = lp->next)
-	{
-		acptr = lp->client;
-
-		if (!MyConnect(acptr))
-			continue; /* only locally connected clients */
-
-		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);
-	}
-}
-
-/* Routine that actually makes a user join the channel
- * this does no actual checking (banned, etc.) it just adds the user.
- * Note: this is called for local JOIN and remote JOIN, but not for SJOIN.
- */
-void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, const char *member_modes)
-{
-	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) */
-	const char *parv[3];
-
-	/* Same way as in SJOIN */
-	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, 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->name, modes_to_sjoin_prefix(member_modes), client->id);
-
-	if (MyUser(client))
-	{
-		/*
-		   ** Make a (temporal) creationtime, if someone joins
-		   ** during a net.reconnect : between remote join and
-		   ** the mode with TS. --Run
-		 */
-		if (channel->creationtime == 0)
-		{
-			channel->creationtime = TStime();
-			sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld",
-			    me.id, channel->name, (long long)channel->creationtime);
-		}
-
-		if (channel->topic)
-		{
-			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 && MODES_ON_JOIN)
-		{
-			MessageTag *mtags_mode = NULL;
-			Cmode *cm;
-			char modebuf[BUFSIZE], parabuf[BUFSIZE];
-			int should_destroy = 0;
-
-			channel->mode.mode = MODES_ON_JOIN;
-
-			/* Param fun */
-			for (cm=channelmodes; cm; cm = cm->next)
-			{
-				if (!cm->letter || !cm->paracount)
-					continue;
-				if (channel->mode.mode & cm->mode)
-				        cm_putparameter(channel, cm->letter, iConf.modes_on_join.extparams[cm->letter]);
-			}
-
-			*modebuf = *parabuf = 0;
-			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->name, modebuf, parabuf);
-			sendto_server(NULL, 0, 0, mtags_mode, ":%s MODE %s %s %s %lld",
-			    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);
-			RunHook(HOOKTYPE_LOCAL_CHANMODE, &me, channel, mtags_mode, modebuf, parabuf, 0, 0, &should_destroy);
-			free_message_tags(mtags_mode);
-		}
-
-		parv[0] = NULL;
-		parv[1] = channel->name;
-		parv[2] = NULL;
-		do_cmd(client, NULL, "NAMES", 2, 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 {
-		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);
-	free_message_tags(mtags_sjoin);
-}
-
-/** User request to join a channel.
- * This routine is normally called from cmd_join but can also be called from
- * do_join->can_join->link module->do_join if the channel is 'linked' (chmode +L).
- * We therefore use a counter 'bouncedtimes' which is set to 0 in cmd_join,
- * 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, const char *parv[])
-{
-	char request[BUFSIZE];
-	char request_key[BUFSIZE];
-	char jbuf[BUFSIZE], jbuf2[BUFSIZE];
-	const char *orig_parv1;
-	Membership *lp;
-	Channel *channel;
-	char *name, *key = NULL;
-	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)
-
-	if (parc < 2 || *parv[1] == '\0')
-	{
-		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 */
-
-	if (bouncedtimes > MAXBOUNCE)
-	{
-		/* bounced too many times. yeah.. should be in the link module, I know.. then again, who cares.. */
-		sendnotice(client, "*** Couldn't join %s ! - Link setting was too bouncy", parv[1]);
-		RET();
-	}
-
-	*jbuf = '\0';
-	/*
-	   ** Rebuild list of channels joined to be the actual result of the
-	   ** JOIN.  Note that "JOIN 0" is the destructive problem.
-	 */
-	strlcpy(request, parv[1], sizeof(request));
-	for (i = 0, name = strtoken(&p, request, ","); name; i++, name = strtoken(&p, NULL, ","))
-	{
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "JOIN");
-			break;
-		}
-		if (*name == '0' && !atoi(name))
-		{
-			/* 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...
-			 * We still support it in remote joins for compatibility.
-			 */
-			if (MyUser(client) && (i != 0))
-				continue;
-			strlcpy(jbuf, "0", sizeof(jbuf));
-			continue;
-		} else
-		if (MyConnect(client) && !valid_channelname(name))
-		{
-			send_invalid_channelname(client, name);
-			if (IsOper(client) && find_channel(name))
-			{
-				/* Give IRCOps a bit more information */
-				sendnotice(client, "Channel '%s' is unjoinable because it contains illegal characters. "
-				                   "However, it does exist because another server in your "
-				                   "network, which has a more loose restriction, created it. "
-				                   "See https://www.unrealircd.org/docs/Set_block#set::allowed-channelchars",
-				                   name);
-			}
-			continue;
-		}
-		else if (!IsChannelName(name))
-		{
-			if (MyUser(client))
-				sendnumeric(client, ERR_NOSUCHCHANNEL, name);
-			continue;
-		}
-		if (*jbuf)
-			strlcat(jbuf, ",", sizeof jbuf);
-		strlcat(jbuf, name, sizeof(jbuf));
-	}
-
-	/* We are going to overwrite 'jbuf' with the calls to strtoken()
-	 * a few lines further down. Copy it to 'jbuf2' and make that
-	 * the new parv[1].. or at least temporarily.
-	 */
-	strlcpy(jbuf2, jbuf, sizeof(jbuf2));
-	parv[1] = jbuf2;
-
-	p = NULL;
-	if (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, ",");
-	     name;
-	     key = key ? strtoken(&p2, NULL, ",") : NULL, name = strtoken(&p, NULL, ","))
-	{
-		MessageTag *mtags = NULL;
-
-		/*
-		   ** JOIN 0 sends out a part for all channels a user
-		   ** has joined.
-		 */
-		if (*name == '0' && !atoi(name))
-		{
-			/* Rewritten so to generate a PART for each channel to servers,
-			 * so the same msgid is used for each part on all servers. -- Syzop
-			 */
-			while ((lp = client->user->channel))
-			{
-				MessageTag *mtags = NULL;
-				channel = lp->channel;
-
-				new_message(client, NULL, &mtags);
-
-				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-				               ":%s PART %s :%s",
-				               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))
-					RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels");
-
-				remove_user_from_channel(client, channel, 0);
-				free_message_tags(mtags);
-			}
-			continue;
-		}
-
-		if (MyConnect(client))
-		{
-			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)
-				{
-					sendnumeric(client, ERR_TOOMANYCHANNELS, name);
-					RET();
-				}
-/* RESTRICTCHAN */
-			if (conf_deny_channel)
-			{
-				if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL))
-				{
-					ConfigItem_deny_channel *d;
-					if ((d = find_channel_allowed(client, name)))
-					{
-						if (d->warn)
-						{
-							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);
-						if (d->redirect)
-						{
-							sendnotice(client, "*** Redirecting you to %s", d->redirect);
-							parv[0] = NULL;
-							parv[1] = d->redirect;
-							do_join(client, 2, parv);
-						}
-						if (d->class)
-							sendnotice(client, "*** Can not join %s: Your class is not allowed", name);
-						continue;
-					}
-				}
-			}
-			if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL) && (tklban = find_qline(client, name, &ishold)))
-			{
-				sendnumeric(client, ERR_FORBIDDENCHANNEL, name, tklban->ptr.nameban->reason);
-				continue;
-			}
-			/* ugly set::spamfilter::virus-help-channel-deny hack.. */
-			if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN &&
-			    !strcasecmp(name, SPAMFILTER_VIRUSCHAN) &&
-			    !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL) && !spamf_ugly_vchanoverride)
-			{
-				Channel *channel = find_channel(name);
-				
-				if (!channel || !is_invited(client, channel))
-				{
-					sendnotice(client, "*** Cannot join '%s' because it's the virus-help-channel "
-					                   "which is reserved for infected users only", name);
-					continue;
-				}
-			}
-		}
-
-		channel = make_channel(name);
-		if (channel && (lp = find_membership_link(client->user->channel, channel)))
-			continue;
-
-		if (!channel)
-			continue;
-
-		i = HOOK_CONTINUE;
-		if (!MyConnect(client))
-			member_modes = "";
-		else
-		{
-			Hook *h;
-			char *errmsg = NULL;
-			for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) 
-			{
-				i = (*(h->func.intfunc))(client,channel,key);
-				if (i == HOOK_DENY || i == HOOK_ALLOW)
-					break;
-			}
-			/* Denied, get out now! */
-			if (i == HOOK_DENY)
-			{
-				/* Rejected... if we just created a new chan we should destroy it too. -- Syzop */
-				if (!channel->users)
-					sub1_from_channel(channel);
-				continue;
-			}
-			/* If they are allowed, don't check can_join */
-			if (i != HOOK_ALLOW && 
-			   (i = can_join(client, channel, key, &errmsg)))
-			{
-				if (i != -1)
-					send_cannot_join_error(client, i, errmsg, name);
-				continue;
-			}
-		}
-
-		/* Generate a new message without inheritance.
-		 * We can do this because remote joins don't follow this code path,
-		 * or are highly discouraged to anyway.
-		 * Remote servers use SJOIN and never reach this function.
-		 * Locally we do follow this code path with JOIN and then generating
-		 * a new_message() here is exactly what we want:
-		 * Each "JOIN #a,#b,#c" gets processed individually in this loop
-		 * and is sent by join_channel() as a SJOIN for #a, then SJOIN for #b,
-		 * and so on, each with their own unique msgid and such.
-		 */
-		new_message(client, NULL, &mtags);
-		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, const char *member_flags)
-{
-	static char modebuf[512]; /* returned */
-	char flagbuf[8]; /* For holding "vhoaq" */
-	char parabuf[512];
-	int n, i;
-
-	if (BadPtr(member_flags))
-		return "";
-
-	parabuf[0] = '\0';
-	n = strlen(member_flags);
-	if (n)
-	{
-		for (i=0; i < n; i++)
-		{
-			strlcat(parabuf, client->name, sizeof(parabuf));
-			if (i < n - 1)
-				strlcat(parabuf, " ", sizeof(parabuf));
-		}
-		/* And we have our mode line! */
-		snprintf(modebuf, sizeof(modebuf), "+%s %s", member_flags, parabuf);
-		return modebuf;
-	}
-
-	return "";
-}
diff --git a/src/modules/jointhrottle.c b/src/modules/jointhrottle.c
@@ -1,248 +0,0 @@
-/*
- * Jointhrottle (set::anti-flood::join-flood).
- * (C) Copyright 2005-.. Bram Matthys (Syzop) and the UnrealIRCd team
- *
- * This was PREVIOUSLY channel mode +j but has been moved to the
- * set::anti-flood::join-flood block instead since people rarely need
- * to tweak this per-channel and it's nice to have this on by default.
- *
- * 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
-  = {
-	"jointhrottle",
-	"5.0",
-	"Join flood protection (set::anti-flood::join-flood)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-ModuleInfo *ModInfo = NULL;
-
-ModDataInfo *jointhrottle_md; /* Module Data structure which we acquire */
-
-typedef struct JoinFlood JoinFlood;
-
-struct JoinFlood {
-	JoinFlood *prev, *next;
-	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, 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);
-JoinFlood *jointhrottle_addentry(Client *client, Channel *channel);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModInfo = modinfo;
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "jointhrottle";
-	mreq.free = jointhrottle_md_free;
-	mreq.serialize = NULL; /* not supported */
-	mreq.unserialize = NULL; /* not supported */
-	mreq.sync = 0;
-	mreq.type = MODDATATYPE_LOCAL_CLIENT;
-	jointhrottle_md = ModDataAdd(modinfo->handle, mreq);
-	if (!jointhrottle_md)
-		abort();
-
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, jointhrottle_can_join);
-	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, jointhrottle_local_join);
-	
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	EventAdd(ModInfo->handle, "jointhrottle_cleanup_structs", jointhrottle_cleanup_structs, NULL, 60000, 0);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_FAILED;
-}
-
-static int isjthrottled(Client *client, Channel *channel)
-{
-	JoinFlood *e;
-	FloodSettings *settings = get_floodsettings_for_user(client, FLD_JOIN);
-
-	if (!MyUser(client))
-		return 0;
-
-	/* Grab user<->chan entry.. */
-	for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
-		if (!strcasecmp(e->name, channel->name))
-			break;
-	
-	if (!e)
-		return 0; /* Not present, so cannot be throttled */
-
-	/* Ok... now the actual check:
-	 * if ([timer valid] && [one more join would exceed num])
-	 */
-	if (((TStime() - e->firstjoin) < settings->period[FLD_JOIN]) &&
-	    (e->numjoins >= settings->limit[FLD_JOIN]))
-		return 1; /* Throttled */
-
-	return 0;
-}
-
-static void jointhrottle_increase_usercounter(Client *client, Channel *channel)
-{
-	JoinFlood *e;
-
-	if (!MyUser(client))
-		return;
-		
-	/* Grab user<->chan entry.. */
-	for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
-		if (!strcasecmp(e->name, channel->name))
-			break;
-	
-	if (!e)
-	{
-		/* Allocate one */
-		e = jointhrottle_addentry(client, channel);
-		e->firstjoin = TStime();
-		e->numjoins = 1;
-	} else
-	if ((TStime() - e->firstjoin) < iConf.floodsettings->period[FLD_JOIN]) /* still valid? */
-	{
-		e->numjoins++;
-	} else {
-		/* reset :p */
-		e->firstjoin = TStime();
-		e->numjoins = 1;
-	}
-}
-
-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)
-{
-	jointhrottle_increase_usercounter(client, channel);
-	return 0;
-}
-
-/** Adds a JoinFlood entry to user & channel and returns entry.
- * NOTE: Does not check for already-existing-entry
- */
-JoinFlood *jointhrottle_addentry(Client *client, Channel *channel)
-{
-	JoinFlood *e;
-
-#ifdef DEBUGMODE
-	if (!IsUser(client))
-		abort();
-
-	for (e=moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
-		if (!strcasecmp(e->name, channel->name))
-			abort(); /* already exists -- should never happen */
-#endif
-
-	e = safe_alloc(sizeof(JoinFlood));
-	strlcpy(e->name, channel->name, sizeof(e->name));
-
-	/* Insert our new entry as (new) head */
-	if (moddata_local_client(client, jointhrottle_md).ptr)
-	{
-		JoinFlood *current_head = moddata_local_client(client, jointhrottle_md).ptr;
-		current_head->prev = e;
-		e->next = current_head;
-	}
-	moddata_local_client(client, jointhrottle_md).ptr = e;
-
-	return e;
-}
-
-/** Regularly cleans up user/chan structs */
-EVENT(jointhrottle_cleanup_structs)
-{
-	Client *client;
-	JoinFlood *jf, *jf_next;
-	
-	list_for_each_entry(client, &lclient_list, lclient_node)
-	{
-		if (!MyUser(client))
-			continue; /* only (local) persons.. */
-
-		for (jf = moddata_local_client(client, jointhrottle_md).ptr; jf; jf = jf_next)
-		{
-			jf_next = jf->next;
-			
-			if (jf->firstjoin + iConf.floodsettings->period[FLD_JOIN] > TStime())
-				continue; /* still valid entry */
-			if (moddata_local_client(client, jointhrottle_md).ptr == jf)
-			{
-				/* change head */
-				moddata_local_client(client, jointhrottle_md).ptr = jf->next; /* could be set to NULL now */
-				if (jf->next)
-					jf->next->prev = NULL;
-			} else {
-				/* change non-head entries */
-				jf->prev->next = jf->next; /* could be set to NULL now */
-				if (jf->next)
-					jf->next->prev = jf->prev;
-			}
-			safe_free(jf);
-		}
-	}
-}
-
-void jointhrottle_md_free(ModData *m)
-{
-	JoinFlood *j, *j_next;
-
-	if (!m->ptr)
-		return;
-
-	for (j = m->ptr; j; j = j_next)
-	{
-		j_next = j->next;
-		safe_free(j);
-	}	
-
-	m->ptr = NULL;
-}
diff --git a/src/modules/json-log-tag.c b/src/modules/json-log-tag.c
@@ -1,97 +0,0 @@
-/*
- *   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_TEST()
-{
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -1000000001); /* load very early */
-	return MOD_SUCCESS;
-}
-
-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);
-
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, 1000000001); /* unload very late */
-	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
@@ -1,271 +0,0 @@
-/*
- * JumpServer: This module can redirect clients to another server.
- * (C) Copyright 2004-2016 Bram Matthys (Syzop).
- *
- * 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
-  = {
-	"jumpserver",
-	"1.1",
-	"/jumpserver command",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-CMD_FUNC(cmd_jumpserver);
-int jumpserver_preconnect(Client *);
-void jumpserver_free_jss(ModData *m);
-
-/* Jumpserver status struct */
-typedef struct JSS JSS;
-struct JSS
-{
-	char *reason;
-	char *server;
-	int port;
-	char *tls_server;
-	int tls_port;
-};
-
-JSS *jss=NULL; /**< JumpServer Status. NULL=disabled. */
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	LoadPersistentPointer(modinfo, jss, jumpserver_free_jss);
-	CommandAdd(modinfo->handle, "JUMPSERVER", cmd_jumpserver, 3, CMD_USER);
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, jumpserver_preconnect);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	SavePersistentPointer(modinfo, jss);
-	return MOD_SUCCESS;
-}
-
-static void do_jumpserver_exit_client(Client *client)
-{
-	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);
-}
-
-static void redirect_all_clients(void)
-{
-	int count = 0;
-	Client *client, *next;
-
-	list_for_each_entry_safe(client, next, &lclient_list, lclient_node)
-	{
-		if (IsUser(client) && !IsOper(client))
-		{
-			do_jumpserver_exit_client(client);
-			count++;
-		}
-	}
-	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)
-{
-	if (jss)
-	{
-		do_jumpserver_exit_client(client);
-		return HOOK_DENY;
-	}
-	return HOOK_CONTINUE;
-}
-
-void free_jss(void)
-{
-	if (jss)
-	{
-		safe_free(jss->server);
-		safe_free(jss->reason);
-		safe_free(jss->tls_server);
-		safe_free(jss);
-		jss = NULL;
-	}
-}
-
-void jumpserver_free_jss(ModData *m)
-{
-	free_jss();
-}
-
-CMD_FUNC(cmd_jumpserver)
-{
-	char *serv, *tlsserv=NULL, *p;
-	const char *reason;
-	int all=0, port=6667, sslport=6697;
-	char request[BUFSIZE];
-	char logbuf[512];
-
-	if (!IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		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'",
-				jss->server, jss->port, jss->reason);
-		else
-			sendnotice(client, "JumpServer is \002DISABLED\002");
-		return;
-	}
-
-	if ((parc > 1) && (!strcasecmp(parv[1], "OFF") || !strcasecmp(parv[1], "STOP")))
-	{
-		if (!jss)
-		{
-			sendnotice(client, "JUMPSERVER: No redirect active (already OFF)");
-			return;
-		}
-		free_jss();
-		unreal_log(ULOG_INFO, "jumpserver", "JUMPSERVER_DISABLED", client,
-		           "[jumpserver] $client.details turned jumpserver OFF");
-		return;
-	}
-
-	if (parc < 4)
-	{
-		/* Waah, pretty verbose usage info ;) */
-		sendnotice(client, "Use: /JUMPSERVER <server>[: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");
-		sendnotice(client, "And then for example 10 minutes later...");
-		sendnotice(client, "         /JUMPSERVER irc2.test.net ALL This server will be upgraded, please use irc2.test.net for now");
-		sendnotice(client, "Use: '/JUMPSERVER OFF' to turn off any redirects");
-		return;
-	}
-
-	/* Parsing code follows... */
-
-	strlcpy(request, parv[1], sizeof(request));
-	serv = request;
-	
-	p = strchr(serv, '/');
-	if (p)
-	{
-		*p = '\0';
-		tlsserv = p+1;
-	}
-	
-	p = strchr(serv, ':');
-	if (p)
-	{
-		*p++ = '\0';
-		port = atoi(p);
-		if ((port < 1) || (port > 65535))
-		{
-			sendnotice(client, "Invalid serverport specified (%d)", port);
-			return;
-		}
-	}
-	if (tlsserv)
-	{
-		p = strchr(tlsserv, ':');
-		if (p)
-		{
-			*p++ = '\0';
-			sslport = atoi(p);
-			if ((sslport < 1) || (sslport > 65535))
-			{
-				sendnotice(client, "Invalid TLS serverport specified (%d)", sslport);
-				return;
-			}
-		}
-		if (!*tlsserv)
-			tlsserv = NULL;
-	}
-	if (!strcasecmp(parv[2], "new"))
-		all = 0;
-	else if (!strcasecmp(parv[2], "all"))
-		all = 1;
-	else {
-		sendnotice(client, "ERROR: Invalid action '%s', should be 'NEW' or 'ALL' (see /jumpserver help for usage)", parv[2]);
-		return;
-	}
-
-	reason = parv[3];
-
-	/* Free any old stuff (needed!) */
-	if (jss)
-		free_jss();
-
-	jss = safe_alloc(sizeof(JSS));
-
-	/* Set it */
-	safe_strdup(jss->server, serv);
-	jss->port = port;
-	if (tlsserv)
-	{
-		safe_strdup(jss->tls_server, tlsserv);
-		jss->tls_port = sslport;
-	}
-	safe_strdup(jss->reason, reason);
-
-	/* Broadcast/log */
-	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
@@ -1,369 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/kick.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"
-
-ModuleHeader MOD_HEADER
-  = {
-	"kick",
-	"5.0",
-	"command /kick",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-CMD_FUNC(cmd_kick);
-void _kick_user(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_KICK_USER, _kick_user);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "KICK", cmd_kick, 3, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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
- * @param client	The evil user doing the kick, can be &me
- * @param victim	The target user that will be kicked
- * @param comment	The KICK comment (cannot be NULL)
- * @notes The msgid in initial_mtags is actually used as a prefix.
- *        The actual mtag will be "initial_mtags_msgid-suffix_msgid"
- *        All this is done in order for message tags to be
- *        consistent accross servers.
- *        The suffix is necessary to handle multi-target-kicks.
- *        If initial_mtags is NULL then we will autogenerate one.
- */
-void _kick_user(MessageTag *initial_mtags, Channel *channel, Client *client, Client *victim, char *comment)
-{
-	MessageTag *mtags = NULL;
-	int initial_mtags_generated = 0;
-
-	if (!initial_mtags)
-	{
-		/* Yeah, we allow callers to be lazy.. */
-		initial_mtags_generated = 1;
-		new_message(client, NULL, &initial_mtags);
-	}
-
-	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))
-		RunHook(HOOKTYPE_LOCAL_KICK, client, victim, channel, mtags, comment);
-	else
-		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,
-			       "h", 0,
-			       SEND_LOCAL, mtags,
-			       ":%s KICK %s %s :%s",
-			       client->name, channel->name, victim->name, comment);
-
-		if (MyUser(victim))
-		{
-			sendto_prefix_one(victim, client, mtags, ":%s KICK %s %s :%s",
-				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->name, victim->name, comment);
-	}
-
-	sendto_server(client, 0, 0, mtags, ":%s KICK %s %s :%s",
-	    client->id, channel->name, victim->id, comment);
-
-	free_message_tags(mtags);
-	if (initial_mtags_generated)
-	{
-		free_message_tags(initial_mtags);
-		initial_mtags = NULL;
-	}
-
-	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 (single channel)
-**	parv[2] = client to kick (comma separated)
-**	parv[3] = kick comment
-*/
-
-CMD_FUNC(cmd_kick)
-{
-	Client *target;
-	Channel *channel;
-	int  chasing = 0;
-	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')
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "KICK");
-		return;
-	}
-
-	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;
-	}
-
-	/* 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;
-	}
-
-	strlcpy(request, parv[2], sizeof(request));
-	for (user = strtoken(&p2, request, ","); user; user = strtoken(&p2, NULL, ","))
-	{
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, user, maxtargets, "KICK");
-			break;
-		}
-
-		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)
-		{
-			if (MyUser(client))
-				sendnumeric(client, ERR_USERNOTINCHANNEL, user, request_chans);
-			continue;
-		}
-
-		if (IsULine(client) || IsServer(client) || IsMe(client))
-			goto attack;
-
-		/* 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 "target" access flags */
-		target_member_modes = get_channel_access(target, channel);
-
-		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);
-
-			if (n == EX_DENY)
-				ret = n;
-			else if (n == EX_ALWAYS_DENY)
-			{
-				ret = n;
-				break;
-			}
-		}
-
-		if (ret == EX_ALWAYS_DENY)
-		{
-			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) */
-		}
-
-		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 */
-
-				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 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")))
-			{
-				kick_operoverride_msg(client, channel, target, comment);
-				goto attack;
-			}	/* is_chan_op */
-
-		}
-
-		/* 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) */
-			{
-				/* IRCop kicking owner/prot */
-				kick_operoverride_msg(client, channel, target, comment);
-				goto attack;
-			}
-			else if (!IsULine(client) && (target != client) && MyUser(client))
-			{
-				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;
-			}
-		}
-
-		/* 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;
-		}
-
-		/* 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;
-		}
-
-		kick_user(recv_mtags, channel, client, target, comment);
-	}
-}
diff --git a/src/modules/kill.c b/src/modules/kill.c
@@ -1,184 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/kill.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_kill);
-
-ModuleHeader MOD_HEADER
-  = {
-	"kill",
-	"5.0",
-	"command /kill",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "KILL", cmd_kill, 2, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/** The KILL command - this forcefully terminates a users' connection.
- * parv[1] = kill victim(s) - comma separated list
- * parv[2] = reason
- */
-CMD_FUNC(cmd_kill)
-{
-	char targetlist[BUFSIZE];
-	char reason[BUFSIZE];
-	char buf2[BUFSIZE];
-	char *str;
-	char *nick, *save = NULL;
-	Client *target;
-	Hook *h;
-	int ntargets = 0;
-	int maxtargets = max_targets_for_command("KILL");
-
-	if ((parc < 3) || BadPtr(parv[2]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "KILL");
-		return;
-	}
-
-	if (!IsServer(client->direction) && !ValidatePermissionsForPath("kill:global",client,NULL,NULL,NULL) && !ValidatePermissionsForPath("kill:local",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (MyUser(client))
-		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, ","))
-	{
-		MessageTag *mtags = NULL;
-
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, nick, maxtargets, "KILL");
-			break;
-		}
-
-		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.
-		 * We don't do this for remote KILL requests as we have UID for that.
-		 */
-		if (!target && MyUser(client))
-		{
-			target = get_history(nick, KILLCHASETIMELIMIT);
-			if (target)
-				sendnotice(client, "*** KILL changed from %s to %s", nick, target->name);
-		}
-
-		if (!target)
-		{
-			sendnumeric(client, ERR_NOSUCHNICK, nick);
-			continue;
-		}
-
-		if ((!MyConnect(target) && MyUser(client) && !ValidatePermissionsForPath("kill:global",client,target,NULL,NULL))
-		    || (MyConnect(target) && MyUser(client)
-		    && !ValidatePermissionsForPath("kill:local",client,target,NULL,NULL)))
-		{
-			sendnumeric(client, ERR_NOPRIVILEGES);
-			continue;
-		}
-
-		/* Hooks can plug-in here to reject a kill */
-		if (MyUser(client))
-		{
-			int ret = EX_ALLOW;
-			for (h = Hooks[HOOKTYPE_PRE_KILL]; h; h = h->next)
-			{
-				/* note: parameters are: client, victim, reason. reason can be NULL !! */
-				ret = (*(h->func.intfunc))(client, target, reason);
-				if (ret != EX_ALLOW)
-					break;
-			}
-			if ((ret == EX_DENY) || (ret == EX_ALWAYS_DENY))
-				continue; /* reject kill for this particular user */
-		}
-
-		/* From here on, the kill is probably going to be successful. */
-
-		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);
-
-		/* Victim gets a little notification (s)he is about to die */
-		if (MyConnect(target))
-		{
-			sendto_prefix_one(target, client, NULL, ":%s KILL %s :%s",
-			    client->name, target->name, reason);
-		}
-
-		if (MyConnect(target) && MyConnect(client))
-		{
-			/* Local kill. This is handled as if it were a QUIT */
-		}
-		else
-		{
-			/* Kill from one server to another (we may be src, victim or something in-between) */
-
-			/* Broadcast it to other servers */
-			sendto_server(client, 0, 0, mtags, ":%s KILL %s :%s",
-			              client->id, target->id, reason);
-
-			/* Don't send a QUIT for this */
-			SetKilled(target);
-
-			ircsnprintf(buf2, sizeof(buf2), "Killed by %s (%s)", client->name, reason);
-		}
-
-		if (MyUser(client))
-			RunHook(HOOKTYPE_LOCAL_KILL, client, target, reason);
-
-		ircsnprintf(buf2, sizeof(buf2), "Killed by %s (%s)", client->name, reason);
-		exit_client(target, mtags, buf2);
-
-		free_message_tags(mtags);
-
-		if (IsDead(client))
-			return; /* stop processing if we killed ourselves */
-	}
-}
diff --git a/src/modules/knock.c b/src/modules/knock.c
@@ -1,160 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/knock.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_knock);
-
-#define MSG_KNOCK 	"KNOCK"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"knock",
-	"5.0",
-	"command /knock", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_KNOCK, cmd_knock, 2, CMD_USER);
-	ISupportAdd(modinfo->handle, "KNOCK", NULL);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_knock
-**	parv[1] - channel
-**	parv[2] - reason
-**
-** Coded by Stskeeps
-** Additional bugfixes/ideas by codemastr
-** (C) codemastr & Stskeeps
-** 
-** 2019-11-27: Behavior change. We now send the KNOCK
-** across servers and only deliver the channel notice
-** to local channel members. The reason for this is that
-** otherwise we cannot count KNOCKs network-wide which
-** caused knock-floods per-channel to be per-server
-** rather than global, which undesirable.
-** Unfortunately, this means that if you have a mixed
-** U4 and U5 network you will see KNOCK notices twice
-** for every attempt.
-*/
-CMD_FUNC(cmd_knock)
-{
-	Channel *channel;
-	Hook *h;
-	int i = 0;
-	MessageTag *mtags = NULL;
-	const char *reason;
-
-	if (IsServer(client))
-		return;
-
-	if (parc < 2 || *parv[1] == '\0')
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "KNOCK");
-		return;
-	}
-
-	reason = parv[2] ? parv[2] : "no reason specified";
-
-	if (MyConnect(client) && !valid_channelname(parv[1]))
-	{
-		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
-		return;
-	}
-
-	if (!(channel = find_channel(parv[1])))
-	{
-		sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "Channel does not exist!");
-		return;
-	}
-
-	/* IsMember bugfix by codemastr */
-	if (IsMember(client, channel) == 1)
-	{
-		sendnumeric(client, ERR_CANNOTKNOCK, channel->name, "You're already there!");
-		return;
-	}
-
-	if (!has_channel_mode(channel, 'i'))
-	{
-		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->name, "You're banned!");
-		return;
-	}
-
-	for (h = Hooks[HOOKTYPE_PRE_KNOCK]; h; h = h->next)
-	{
-		i = (*(h->func.intfunc))(client, channel, &reason);
-		if (i == HOOK_DENY || i == HOOK_ALLOW)
-			break;
-	}
-
-	if (i == HOOK_DENY)
-		return;
-
-	if (MyUser(client) &&
-	    !ValidatePermissionsForPath("immune:knock-flood",client,NULL,NULL,NULL) &&
-	    flood_limit_exceeded(client, FLD_KNOCK))
-	{
-		sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "You are KNOCK flooding");
-		return;
-	}
-
-	new_message(&me, NULL, &mtags);
-
-	sendto_channel(channel, &me, NULL, "o",
-	               0, SEND_LOCAL, mtags,
-	               ":%s NOTICE @%s :[Knock] by %s!%s@%s (%s)",
-	               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->name, reason);
-
-	if (MyUser(client))
-		sendnotice(client, "Knocked on %s", channel->name);
-
-        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
@@ -1,399 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/labeled-response.c
- *   (C) 2019 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"labeled-response",
-	"5.0",
-	"Labeled response CAP",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Data structures */
-typedef struct LabeledResponseContext LabeledResponseContext;
-struct LabeledResponseContext {
-	Client *client; /**< The client who issued the original command with a label */
-	char label[256]; /**< The label attached to this command */
-	char batch[BATCHLEN+1]; /**< The generated batch id */
-	int responses; /**< Number of lines sent back to client */
-	int sent_remote; /**< Command has been sent to remote server */
-	char firstbuf[MAXLINELENGTH]; /**< First buffered response */
-};
-
-/* Forward declarations */
-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);
-void _labeled_response_set_context(void *ctx);
-void _labeled_response_force_end(void);
-
-/* Our special version of SupportBatch() assumes that remote servers always handle it */
-#define SupportBatch(x)		(MyConnect(x) ? HasCapability((x), "batch") : 1)
-#define SupportLabel(x)		(HasCapabilityFast((x), CAP_LABELED_RESPONSE))
-
-/* Variables */
-static LabeledResponseContext currentcmd;
-static long CAP_LABELED_RESPONSE = 0L;
-
-static char packet[MAXLINELENGTH*2];
-
-int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddPVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SAVE_CONTEXT, _labeled_response_save_context);
-	EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_SET_CONTEXT, _labeled_response_set_context);
-	EfunctionAddVoid(modinfo->handle, EFUNC_LABELED_RESPONSE_FORCE_END, _labeled_response_force_end);
-
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ClientCapabilityInfo cap;
-	ClientCapability *c;
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&currentcmd, 0, sizeof(currentcmd));
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "labeled-response";
-	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_LABELED_RESPONSE);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "label";
-	mtag.is_ok = labeled_response_mtag_is_ok;
-	mtag.clicap_handler = c;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_COMMAND, -1000000000, lr_pre_command);
-	HookAdd(modinfo->handle, HOOKTYPE_POST_COMMAND, 1000000000, lr_post_command);
-	HookAdd(modinfo->handle, HOOKTYPE_CLOSE_CONNECTION, 1000000000, lr_close_connection);
-	HookAdd(modinfo->handle, HOOKTYPE_PACKET, 1000000000, lr_packet);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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;
-
-	if (IsServer(from))
-		return 0;
-
-	for (; mtags; mtags = mtags->next)
-	{
-		if (!strcmp(mtags->name, "label") && mtags->value)
-		{
-			strlcpy(currentcmd.label, mtags->value, sizeof(currentcmd.label));
-			currentcmd.client = from;
-			break;
-		}
-	}
-
-	return 0;
-}
-
-char *gen_start_batch(void)
-{
-	static char buf[512];
-
-	generate_batch_id(currentcmd.batch);
-
-	if (MyConnect(currentcmd.client))
-	{
-		/* Local connection */
-		snprintf(buf, sizeof(buf), "@label=%s :%s BATCH +%s labeled-response",
-			currentcmd.label,
-			me.name,
-			currentcmd.batch);
-	} else {
-		/* Remote connection: requires intra-server BATCH syntax */
-		snprintf(buf, sizeof(buf), "@label=%s :%s BATCH %s +%s labeled-response",
-			currentcmd.label,
-			me.name,
-			currentcmd.client->name,
-			currentcmd.batch);
-	}
-	return buf;
-}
-
-int lr_post_command(Client *from, MessageTag *mtags, const char *buf)
-{
-	/* ** IMPORTANT **
-	 * Take care NOT to return here, use 'goto done' instead
-	 * as some variables need to be cleared.
-	 */
-
-	/* We may have to send a response or end a BATCH here, if all of
-	 * the following is true:
-	 * 1. The client is still online (from is not NULL)
-	 * 2. A "label" was attached
-	 * 3. The client supports BATCH (or is remote)
-	 * 4. The command has not been forwarded to a remote server
-	 *    (in which case they would be handling it, and not us)
-	 * 5. Unless labeled_response_force is set, in which case
-	 *    we are assumed to have handled it anyway (necessary for
-	 *    commands like PRIVMSG, quite rare).
-	 */
-	if (from && currentcmd.client &&
-	    !(currentcmd.sent_remote && !currentcmd.responses && !labeled_response_force))
-	{
-		Client *savedptr;
-
-		if (currentcmd.responses == 0)
-		{
-			MessageTag *m = safe_alloc(sizeof(MessageTag));
-			safe_strdup(m->name, "label");
-			safe_strdup(m->value, currentcmd.label);
-			memset(&currentcmd, 0, sizeof(currentcmd));
-			sendto_one(from, m, ":%s ACK", me.name);
-			free_message_tags(m);
-			goto done;
-		} else
-		if (currentcmd.responses == 1)
-		{
-			/* We have buffered this response earlier,
-			 * now we will send it
-			 */
-			int more_tags = currentcmd.firstbuf[0] == '@';
-			currentcmd.client = NULL; /* prevent lr_packet from interfering */
-			snprintf(packet, sizeof(packet)-3,
-				 "@label=%s%s%s",
-				 currentcmd.label,
-				 more_tags ? ";" : " ",
-				 more_tags ? currentcmd.firstbuf+1 : currentcmd.firstbuf);
-			/* Format the IRC message correctly here, so we can take the
-			 * quick path through sendbufto_one().
-			 */
-			strlcat(packet, "\r\n", sizeof(packet));
-			sendbufto_one(from, packet, strlen(packet));
-			goto done;
-		}
-
-		/* End the batch */
-		if (!labeled_response_inhibit_end)
-		{
-			savedptr = currentcmd.client;
-			currentcmd.client = NULL;
-			if (MyConnect(savedptr))
-				sendto_one(from, NULL, ":%s BATCH -%s", me.name, currentcmd.batch);
-			else
-				sendto_one(from, NULL, ":%s BATCH %s -%s", me.name, savedptr->name, currentcmd.batch);
-		}
-	}
-done:
-	memset(&currentcmd, 0, sizeof(currentcmd));
-	labeled_response_inhibit = labeled_response_inhibit_end = labeled_response_force = 0;
-	return 0;
-}
-
-int lr_close_connection(Client *client)
-{
-	/* Flush all data before closing connection */
-	lr_post_command(client, NULL, NULL);
-	return 0;
-}
-
-/** Helper function for lr_packet() to skip the message tags prefix,
- * and possibly @batch as well.
- */
-char *skip_tags(char *msg)
-{
-	if (*msg != '@')
-		return msg;
-	if (!strncmp(msg, "@batch", 6))
-	{
-		char *p;
-		for (p = msg; *p; p++)
-			if ((*p == ';') || (*p == ' '))
-				return p;
-	}
-	return msg+1; /* just skip the '@' */
-}
-
-int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len)
-{
-	if (currentcmd.client && !labeled_response_inhibit)
-	{
-		/* Labeled response is active */
-		if (currentcmd.client == intended_to)
-		{
-			/* Add the label */
-			if (currentcmd.responses == 0)
-			{
-				int n = *len;
-				if (n > sizeof(currentcmd.firstbuf))
-					n = sizeof(currentcmd.firstbuf);
-				strlcpy(currentcmd.firstbuf, *msg, n);
-				/* Don't send anything -- yet */
-				*msg = NULL;
-				*len = 0;
-			} else
-			if (currentcmd.responses == 1)
-			{
-				/* Start the batch now, normally this would be a sendto_one()
-				 * but doing so is not possible since we are in the sending code :(
-				 * The code below is almost unbearable to see, but the alternative
-				 * is to use an intermediate buffer or pointer jugling, of which
-				 * the former is slower than this implementation and with the latter
-				 * it is easy to make a mistake and create an overflow issue.
-				 * So guess I'll stick with this...
-				 */
-				char *batchstr = gen_start_batch();
-				int more_tags_one = currentcmd.firstbuf[0] == '@';
-				int more_tags_two = **msg == '@';
-
-				if (!strncmp(*msg, "@batch", 6))
-				{
-					/* Special case: current message (*msg) already contains a batch */
-					snprintf(packet, sizeof(packet),
-						 "%s\r\n"
-						 "@batch=%s%s%s\r\n"
-						 "%s",
-						 batchstr,
-						 currentcmd.batch,
-						 more_tags_one ? ";" : " ",
-						 more_tags_one ? currentcmd.firstbuf+1 : currentcmd.firstbuf,
-						 *msg);
-				} else
-				{
-					/* Regular case: current message (*msg) contains no batch yet, add one.. */
-					snprintf(packet, sizeof(packet),
-						 "%s\r\n"
-						 "@batch=%s%s%s\r\n"
-						 "@batch=%s%s%s",
-						 batchstr,
-						 currentcmd.batch,
-						 more_tags_one ? ";" : " ",
-						 more_tags_one ? currentcmd.firstbuf+1 : currentcmd.firstbuf,
-						 currentcmd.batch,
-						 more_tags_two ? ";" : " ",
-						 more_tags_two ? *msg+1 : *msg);
-				}
-				*msg = packet;
-				*len = strlen(*msg);
-			} else {
-				/* >2 responses.... the first 2 have already been sent */
-				if (!strncmp(*msg, "@batch", 6))
-				{
-					/* No buffer change needed, already contains a (now inner) batch */
-				} else {
-					int more_tags = **msg == '@';
-					snprintf(packet, sizeof(packet), "@batch=%s%s%s",
-						currentcmd.batch,
-						more_tags ? ";" : " ",
-						more_tags ? *msg+1 : *msg);
-					*msg = packet;
-					*len = strlen(*msg);
-				}
-			}
-			currentcmd.responses++;
-		}
-		else if (IsServer(to) || !MyUser(to))
-		{
-			currentcmd.sent_remote = 1;
-		}
-	}
-
-	return 0;
-}
-
-/** 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, const char *name, const char *value)
-{
-	if (BadPtr(value))
-		return 0;
-
-	if (IsServer(client))
-		return 1;
-
-	/* Ignore the label if the client does not support both
-	 * (draft/)labeled-response and batch. Yeah, batch too,
-	 * it's too much hassle to support labeled-response without
-	 * batch and the end result is quite broken too.
-	 */
-	if (MyUser(client) && (!SupportLabel(client) || !SupportBatch(client)))
-		return 0;
-
-	/* Do some basic sanity checking for non-servers */
-	if (strlen(value) <= 64)
-		return 1;
-
-	return 0;
-}
-
-/** Save current context for later use in labeled-response.
- * Currently used in /LIST. Is not planned for other places tbh.
- */
-void *_labeled_response_save_context(void)
-{
-	LabeledResponseContext *ctx = safe_alloc(sizeof(LabeledResponseContext));
-	memcpy(ctx, &currentcmd, sizeof(LabeledResponseContext));
-	return (void *)ctx;
-}
-
-/** Set previously saved context 'ctx', or clear the context.
- * @param ctx    The context, or NULL to clear the context.
- * @note The client from the previously saved context should be
- *       the same. Don't save one context when processing
- *       client A and then restore it when processing client B (duh).
- */
-void _labeled_response_set_context(void *ctx)
-{
-	if (ctx == NULL)
-	{
-		/* This means: clear the current context */
-		memset(&currentcmd, 0, sizeof(currentcmd));
-	} else {
-		/* Set the current context to the provided one */
-		memcpy(&currentcmd, ctx, sizeof(LabeledResponseContext));
-	}
-}
-
-/** Force an end of the labeled-response (only used in /LIST atm) */
-void _labeled_response_force_end(void)
-{
-	if (currentcmd.client)
-		lr_post_command(currentcmd.client, NULL, NULL);
-}
diff --git a/src/modules/lag.c b/src/modules/lag.c
@@ -1,85 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/lag.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_lag);
-
-/* Place includes here */
-#define MSG_LAG         "LAG"   /* Lag detect */
-
-ModuleHeader MOD_HEADER
-  = {
-	"lag",	/* Name of module */
-	"5.0", /* Version */
-	"command /lag", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_LAG, cmd_lag, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* cmd_lag (lag measure) - Stskeeps
- * parv[1] = server to query
-*/
-
-CMD_FUNC(cmd_lag)
-{
-	if (!ValidatePermissionsForPath("server:info:lag",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc < 2)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "LAG");
-		return;
-	}
-
-	if (*parv[1] == '\0')
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "LAG");
-		return;
-	}
-
-	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
@@ -1,281 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/link-security.c
- *   (C) 2017 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"link-security",
-	"5.0",
-	"Link Security CAP",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Forward declarations */
-const char *link_security_md_serialize(ModData *m);
-void link_security_md_unserialize(const char *str, ModData *m);
-EVENT(checklinksec);
-const char *link_security_capability_parameter(Client *client);
-CMD_FUNC(cmd_linksecurity);
-
-/* Global variables */
-ModDataInfo *link_security_md;
-int local_link_security = -1;
-int global_link_security = -1;
-int effective_link_security = -1;
-
-/** Module initalization */
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1);
-	
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "link-security";
-	mreq.type = MODDATATYPE_CLIENT;
-	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)
-	{
-		config_error("Unable to ModDataAdd() -- too many 3rd party modules loaded perhaps?");
-		abort();
-	}
-	
-	CommandAdd(modinfo->handle, "LINKSECURITY", cmd_linksecurity, MAXPARA, CMD_USER);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	ClientCapabilityInfo cap;
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "unrealircd.org/link-security";
-	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
-	cap.parameter = link_security_capability_parameter;
-	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
-
-	EventAdd(modinfo->handle, "checklinksec", checklinksec, NULL, 2000, 0);
-	checklinksec(NULL);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Magic value to differentiate between "not set" and "zero".
- * Only used for internal moddata storage, not exposed
- * outside these two functions.
- */
-#define LNKSECMAGIC 100
-
-const char *link_security_md_serialize(ModData *m)
-{
-	static char buf[32];
-	if (m->i == 0)
-		return NULL; /* not set */
-	snprintf(buf, sizeof(buf), "%d", m->i - LNKSECMAGIC);
-	return buf;
-}
-
-void link_security_md_unserialize(const char *str, ModData *m)
-{
-	m->i = atoi(str) + LNKSECMAGIC;
-}
-
-/** Return 1 if the server certificate is verified for
- * server 'client', return 0 if not.
- */
-int certificate_verification_active(Client *client)
-{
-	ConfigItem_link *conf;
-	
-	if (!client->server || !client->server->conf)
-		return 0; /* wtf? */
-	conf = client->server->conf;
-	
-	if (conf->verify_certificate)
-		return 1; /* yes, verify-certificate is 'yes' */
-	
-	if ((conf->auth->type == AUTHTYPE_TLS_CLIENTCERT) ||
-	    (conf->auth->type == AUTHTYPE_TLS_CLIENTCERTFP) ||
-	    (conf->auth->type == AUTHTYPE_SPKIFP))
-	{
-		/* yes, verified by link::password being a
-		 * certificate fingerprint or certificate file.
-		 */
-	    return 1;
-	}
-
-	return 0; /* no, certificate is not verified in any way */
-}
-
-/** Calculate our (local) link-security level.
- * This means stepping through the list of directly linked
- * servers and determining if they are linked via TLS and
- * certificate verification is active.
- * @returns value from 0 to 2.
- */
-int our_link_security(void)
-{
-	Client *client;
-	int level = 2; /* safest */
-	
-	list_for_each_entry(client, &server_list, special_node)
-	{
-		if (IsLocalhost(client))
-			continue; /* server connected via localhost */
-		if (!IsSecure(client))
-			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 */
-	}
-	
-	return level;
-}
-
-char *valtostr(int i)
-{
-	static char buf[32];
-	snprintf(buf, sizeof(buf), "%d", i);
-	return buf;
-}
-
-/** Check link security. This is called every X seconds to see if there
- * is a change, either local or network-wide.
- */
-EVENT(checklinksec)
-{
-	int last_local_link_security = local_link_security;
-	int last_global_link_security = global_link_security;
-	Client *client;
-	int v;
-	int warning_sent = 0;
-	
-	local_link_security = our_link_security();
-	if (local_link_security != last_local_link_security)
-	{
-		/* Our own link-security changed (for better or worse),
-		 * Set and broadcast it immediately to the other servers.
-		 */
-		moddata_client_set(&me, "link-security", valtostr(local_link_security));
-	}
-
-	global_link_security = 2;
-	list_for_each_entry(client, &global_server_list, client_node)
-	{
-		const char *s = moddata_client_get(client, "link-security");
-		if (s)
-		{
-			v = atoi(s);
-			if (v == 0)
-			{
-				global_link_security = 0;
-				break;
-			}
-			if (v == 1)
-				global_link_security = 1;
-		}
-	}
-	
-	if (local_link_security < last_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)
-	{
-		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;
-	}
-	
-	effective_link_security = MIN(local_link_security, global_link_security);
-
-	if (warning_sent)
-	{
-		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));
-	}
-}
-
-const char *link_security_capability_parameter(Client *client)
-{
-	return valtostr(effective_link_security);
-}
-
-/** /LINKSECURITY command */
-CMD_FUNC(cmd_linksecurity)
-{
-	Client *acptr;
-	
-	if (!IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	
-	sendtxtnumeric(client, "== Link Security Report ==");
-	
-	sendtxtnumeric(client, "= By server =");
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		const char *s = moddata_client_get(acptr, "link-security");
-		if (s)
-			sendtxtnumeric(client, "%s: level %d", acptr->name, atoi(s));
-		else
-			sendtxtnumeric(client, "%s: level UNKNOWN", acptr->name);
-	}
-	
-	sendtxtnumeric(client, "-");
-	sendtxtnumeric(client, "= Network =");
-	sendtxtnumeric(client, "This results in an effective (network-wide) link-security of level %d", effective_link_security);
-	sendtxtnumeric(client, "-");
-	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 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");
-	sendtxtnumeric(client, "see https://www.unrealircd.org/docs/Link_security");
-}
diff --git a/src/modules/links.c b/src/modules/links.c
@@ -1,77 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_links);
-
-#define MSG_LINKS 	"LINKS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"links",
-	"5.0",
-	"command /links", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_LINKS, cmd_links, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-CMD_FUNC(cmd_links)
-{
-	Client *acptr;
-	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
-
-	if (!MyUser(client))
-		return;
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		/* Some checks */
-		if (HIDE_ULINES && IsULine(acptr) && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
-			continue;
-		if (flat)
-			sendnumeric(client, RPL_LINKS, acptr->name, me.name,
-			    1, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
-		else
-			sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink ? acptr->uplink->name : me.name,
-			    acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)"));
-	}
-
-	sendnumeric(client, RPL_ENDOFLINKS, "*");
-}
diff --git a/src/modules/list.c b/src/modules/list.c
@@ -1,468 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/list.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_list);
-int send_list(Client *client);
-
-#define MSG_LIST 	"LIST"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"list",
-	"5.0",
-	"command /LIST",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-typedef struct ChannelListOptions ChannelListOptions;
-struct ChannelListOptions {
-	NameList *yeslist;
-	NameList *nolist;
-	unsigned int starthash;
-	short int showall;
-	unsigned short usermin;
-	int  usermax;
-	time_t currenttime;
-	time_t chantimemin;
-	time_t chantimemax;
-	time_t topictimemin;
-	time_t topictimemax;
-	void *lr_context;
-};
-
-/* Global variables */
-ModDataInfo *list_md = NULL;
-char modebuf[BUFSIZE], parabuf[BUFSIZE];
-
-/* Macros */
-#define CHANNELLISTOPTIONS(x)       ((ChannelListOptions *)moddata_local_client(x, list_md).ptr)
-#define ALLOCATE_CHANNELLISTOPTIONS(client)	do { moddata_local_client(client, list_md).ptr = safe_alloc(sizeof(ChannelListOptions)); } while(0)
-#define free_list_options(client)		list_md_free(&moddata_local_client(client, list_md))
-
-#define DoList(x)               (MyUser((x)) && CHANNELLISTOPTIONS((x)))
-#define IsSendable(x)		(DBufLength(&x->local->sendQ) < 2048)
-
-/* Forward declarations */
-EVENT(send_queued_list_data);
-void list_md_free(ModData *md);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "list";
-	mreq.type = MODDATATYPE_LOCAL_CLIENT;
-	mreq.free = list_md_free;
-	list_md = ModDataAdd(modinfo->handle, mreq);
-	if (!list_md)
-	{
-		config_error("could not register list moddata");
-		return MOD_FAILED;
-	}
-
-	CommandAdd(modinfo->handle, MSG_LIST, cmd_list, MAXPARA, CMD_USER);
-	EventAdd(modinfo->handle, "send_queued_list_data", send_queued_list_data, NULL, 1500, 0);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Originally from bahamut, modified a bit for UnrealIRCd by codemastr
- * also Opers can now see +s channels -- codemastr */
-
-/*
- * parv[1] = channel
- */
-CMD_FUNC(cmd_list)
-{
-	Channel *channel;
-	time_t currenttime = TStime();
-	char *name, *p = NULL;
-	ChannelListOptions *lopt = NULL;
-	int usermax, usermin, error = 0, doall = 0;
-	time_t chantimemin, chantimemax;
-	time_t topictimemin, topictimemax;
-	NameList *yeslist = NULL;
-	NameList *nolist = NULL;
-	int ntargets = 0;
-	int maxtargets = max_targets_for_command("LIST");
-	char request[BUFSIZE];
-
-	static char *usage[] = {
-		"   Usage: /LIST <options>",
-		"",
-		"If you don't include any options, the default is to send you the",
-		"entire unfiltered list of channels. Below are the options you can",
-		"use, and what channels LIST will return when you use them.",
-		">number  List channels with more than <number> people.",
-		"<number  List channels with less than <number> people.",
-		"C>number List channels created more than <number> minutes ago.",
-		"C<number List channels created less than <number> minutes ago.",
-		"T>number List channels whose topics are older than <number> minutes",
-		"         (Ie, they have not changed in the last <number> minutes.",
-		"T<number List channels whose topics are not older than <number> minutes.",
-		"*mask*   List channels that match *mask*",
-		"!*mask*  List channels that do not match *mask*",
-		NULL
-	};
-
-	/* Remote /LIST is not supported */
-	if (!MyUser(client))
-		return;
-
-	/* If a /LIST is in progress then a new one will cancel it */
-	if (CHANNELLISTOPTIONS(client))
-	{
-		sendnumeric(client, RPL_LISTEND);
-		free_list_options(client);
-		return;
-	}
-
-	if (parc < 2 || BadPtr(parv[1]))
-	{
-		sendnumeric(client, RPL_LISTSTART);
-		ALLOCATE_CHANNELLISTOPTIONS(client);
-		CHANNELLISTOPTIONS(client)->showall = 1;
-
-		if (send_list(client))
-		{
-			/* Save context since there is more to be sent */
-			CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context();
-			labeled_response_inhibit_end = 1;
-		}
-
-		return;
-	}
-
-	if ((parc == 2) && (parv[1][0] == '?') && (parv[1][1] == '\0'))
-	{
-		char **ptr = usage;
-		for (; *ptr; ptr++)
-			sendnumeric(client, RPL_LISTSYNTAX, *ptr);
-		return;
-	}
-
-	sendnumeric(client, RPL_LISTSTART);
-
-	chantimemax = topictimemax = currenttime + 86400;
-	chantimemin = topictimemin = 0;
-	usermin = 0;		/* Minimum of 0 */
-	usermax = -1;		/* No maximum */
-
-	strlcpy(request, parv[1], sizeof(request));
-	for (name = strtoken(&p, request, ","); name && !error; name = strtoken(&p, NULL, ","))
-	{
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "LIST");
-			break;
-		}
-		switch (*name)
-		{
-			case '<':
-				usermax = atoi(name + 1) - 1;
-				doall = 1;
-				break;
-			case '>':
-				usermin = atoi(name + 1) + 1;
-				doall = 1;
-				break;
-			case 'C':
-			case 'c':	/* Channel time -- creation time? */
-				++name;
-				switch (*name++)
-				{
-					case '<':
-						chantimemin = currenttime - 60 * atoi(name);
-						doall = 1;
-						break;
-					case '>':
-						chantimemax = currenttime - 60 * atoi(name);
-						doall = 1;
-						break;
-					default:
-						sendnumeric(client, ERR_LISTSYNTAX);
-						error = 1;
-				}
-				break;
-			case 'T':
-			case 't':
-				++name;
-				switch (*name++)
-				{
-					case '<':
-						topictimemin = currenttime - 60 * atoi(name);
-						doall = 1;
-						break;
-					case '>':
-						topictimemax = currenttime - 60 * atoi(name);
-						doall = 1;
-						break;
-					default:
-						sendnumeric(client, ERR_LISTSYNTAX);
-						error = 1;
-				}
-				break;
-			default:
-				/* A channel, possibly with wildcards.
-				 * Thought for the future: Consider turning wildcard
-				 * processing on the fly.
-				 * new syntax: !channelmask will tell ircd to ignore
-				 * any channels matching that mask, and then
-				 * channelmask will tell ircd to send us a list of
-				 * channels only masking channelmask. Note: Specifying
-				 * a channel without wildcards will return that
-				 * channel even if any of the !channelmask masks
-				 * matches it.
-				 */
-				if (*name == '!')
-				{
-					/* Negative matching by name */
-					doall = 1;
-					add_name_list(nolist, name + 1);
-				}
-				else if (strchr(name, '*') || strchr(name, '?'))
-				{
-					/* Channel with wildcards */
-					doall = 1;
-					add_name_list(yeslist, name);
-				}
-				else
-				{
-					/* A specific channel name without wildcards */
-					channel = find_channel(name);
-					if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL)))
-					{
-						modebuf[0] = '[';
-						channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
-
-						if (modebuf[2] == '\0')
-							modebuf[0] = '\0';
-						else
-							strlcat(modebuf, "]", sizeof modebuf);
-
-						sendnumeric(client, RPL_LIST, name, channel->users, modebuf,
-							    channel->topic ? channel->topic : "");
-					}
-				}
-		} /* switch */
-	} /* for */
-
-	if (doall)
-	{
-		ALLOCATE_CHANNELLISTOPTIONS(client);
-		CHANNELLISTOPTIONS(client)->usermin = usermin;
-		CHANNELLISTOPTIONS(client)->usermax = usermax;
-		CHANNELLISTOPTIONS(client)->topictimemax = topictimemax;
-		CHANNELLISTOPTIONS(client)->topictimemin = topictimemin;
-		CHANNELLISTOPTIONS(client)->chantimemax = chantimemax;
-		CHANNELLISTOPTIONS(client)->chantimemin = chantimemin;
-		CHANNELLISTOPTIONS(client)->nolist = nolist;
-		CHANNELLISTOPTIONS(client)->yeslist = yeslist;
-
-		if (send_list(client))
-		{
-			/* Save context since there is more to be sent */
-			CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context();
-			labeled_response_inhibit_end = 1;
-		}
-		return;
-	}
-
-	sendnumeric(client, RPL_LISTEND);
-}
-/*
- * The function which sends the actual channel list back to the user.
- * Operates by stepping through the hashtable, sending the entries back if
- * they match the criteria.
- * client = Local client to send the output back to.
- * Taken from bahamut, modified for UnrealIRCd by codemastr.
- */
-int send_list(Client *client)
-{
-	Channel *channel;
-	ChannelListOptions *lopt = CHANNELLISTOPTIONS(client);
-	unsigned int  hashnum;
-	int numsend = (get_sendq(client) / 768) + 1; /* (was previously hard-coded) */
-	/* ^
-	 * numsend = Number (roughly) of lines to send back. Once this number has
-	 * been exceeded, send_list will finish with the current hash bucket,
-	 * and record that number as the number to start next time send_list
-	 * is called for this user. So, this function will almost always send
-	 * back more lines than specified by numsend (though not by much,
-	 * assuming the hashing algorithm works well). Be conservative in your
-	 * choice of numsend. -Rak
-	 */	
-
-	/* Begin of /LIST? then send official channels first. */
-	if ((lopt->starthash == 0) && conf_offchans)
-	{
-		ConfigItem_offchans *x;
-		for (x = conf_offchans; x; x = x->next)
-		{
-			if (find_channel(x->name))
-				continue; /* exists, >0 users.. will be sent later */
-			sendnumeric(client, RPL_LIST, x->name, 0, "",
-			            x->topic ? x->topic : "");
-		}
-	}
-
-	for (hashnum = lopt->starthash; hashnum < CHAN_HASH_TABLE_SIZE; hashnum++)
-	{
-		if (numsend > 0)
-			for (channel = hash_get_chan_bucket(hashnum); channel; channel = channel->hnextch)
-			{
-				if (SecretChannel(channel)
-				    && !IsMember(client, channel)
-				    && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
-					continue;
-
-				/* set::hide-list { deny-channel } */
-				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->name))
-					continue;
-
-				/* Much more readable like this -- codemastr */
-				if ((!lopt->showall))
-				{
-					/* User count must be in range */
-					if ((channel->users < lopt->usermin) ||
-					    ((lopt->usermax >= 0) && (channel->users > lopt->usermax)))
-						continue;
-
-					/* Creation time must be in range */
-					if ((channel->creationtime && (channel->creationtime < lopt->chantimemin)) ||
-					    (channel->creationtime > lopt->chantimemax))
-						continue;
-
-					/* Topic time must be in range */
-					if ((channel->topic_time < lopt->topictimemin) ||
-					    (channel->topic_time > lopt->topictimemax))
-						continue;
-
-					/* Must not be on nolist (if it exists) */
-					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->name))
-						continue;
-				}
-				modebuf[0] = '[';
-				channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
-				if (modebuf[2] == '\0')
-					modebuf[0] = '\0';
-				else
-					strlcat(modebuf, "]", sizeof modebuf);
-				if (!ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))
-					sendnumeric(client, RPL_LIST,
-					    ShowChannel(client,
-					    channel) ? channel->name :
-					    "*", channel->users,
-					    ShowChannel(client, channel) ?
-					    modebuf : "",
-					    ShowChannel(client,
-					    channel) ? (channel->topic ?
-					    channel->topic : "") : "");
-				else
-					sendnumeric(client, RPL_LIST, channel->name,
-					    channel->users,
-					    modebuf,
-					    (channel->topic ? channel->topic : ""));
-				numsend--;
-			}
-		else
-			break;
-	}
-
-	/* All done */
-	if (hashnum == CHAN_HASH_TABLE_SIZE)
-	{
-		sendnumeric(client, RPL_LISTEND);
-		free_list_options(client);
-		return 0;
-	}
-
-	/*
-	 * We've exceeded the limit on the number of channels to send back
-	 * at once.
-	 */
-	lopt->starthash = hashnum;
-	return 1;
-}
-
-EVENT(send_queued_list_data)
-{
-	Client *client, *saved;
-	list_for_each_entry_safe(client, saved, &lclient_list, lclient_node)
-	{
-		if (DoList(client) && IsSendable(client))
-		{
-			labeled_response_set_context(CHANNELLISTOPTIONS(client)->lr_context);
-			if (!send_list(client))
-			{
-				/* We are done! */
-				labeled_response_force_end();
-			}
-			labeled_response_set_context(NULL);
-		}
-	}
-}
-
-/** Called on client exit: free the channel list options of this user */
-void list_md_free(ModData *md)
-{
-	ChannelListOptions *lopt = (ChannelListOptions *)md->ptr;
-
-	if (!lopt)
-		return;
-
-	free_entire_name_list(lopt->yeslist);
-	free_entire_name_list(lopt->nolist);
-	safe_free(lopt->lr_context);
-
-	safe_free(md->ptr);
-}
diff --git a/src/modules/locops.c b/src/modules/locops.c
@@ -1,74 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_locops);
-
-#define MSG_LOCOPS 	"LOCOPS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"locops",
-	"5.0",
-	"command /locops", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_LOCOPS, cmd_locops, 1, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_locops (write to opers who are +g currently online *this* server)
-**      parv[1] = message text
-*/
-CMD_FUNC(cmd_locops)
-{
-	const char *message = parc > 1 ? parv[1] : NULL;
-
-	if (BadPtr(message))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "LOCOPS");
-		return;
-	}
-	if (MyUser(client) && !ValidatePermissionsForPath("chat:locops",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	sendto_umode(UMODE_OPER, "from %s: %s", client->name, message);
-}
diff --git a/src/modules/lusers.c b/src/modules/lusers.c
@@ -1,96 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/lusers.c
- *   (C) 2005 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_lusers);
-
-#define MSG_LUSERS 	"LUSERS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"lusers",
-	"5.0",
-	"command /lusers", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_LUSERS, cmd_lusers, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * parv[1] = server to query
- */
-CMD_FUNC(cmd_lusers)
-{
-char flatmap;
-
-	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;
-
-	/* Just to correct results ---Stskeeps */
-	if (irccounts.clients > irccounts.global_max)
-		irccounts.global_max = irccounts.clients;
-	if (irccounts.me_clients > irccounts.me_max)
-		irccounts.me_max = irccounts.me_clients;
-
-	sendnumeric(client, RPL_LUSERCLIENT,
-	    irccounts.clients - irccounts.invisible, irccounts.invisible,
-	    irccounts.servers);
-
-	if (irccounts.operators)
-		sendnumeric(client, RPL_LUSEROP, irccounts.operators);
-	if (irccounts.unknown)
-		sendnumeric(client, RPL_LUSERUNKNOWN, irccounts.unknown);
-	if (irccounts.channels)
-		sendnumeric(client, RPL_LUSERCHANNELS, irccounts.channels);
-	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 > max_connection_count)
-	{
-		max_connection_count = irccounts.me_clients;
-		if (max_connection_count % 10 == 0)	/* only send on even tens */
-		{
-			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
@@ -1,243 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_map);
-
-#define MSG_MAP 	"MAP"	
-
-static int lmax = 0;
-static int umax = 0;
-
-static int dcount(int n)
-{
-   int cnt = 0;
-
-   while (n != 0)
-   {
-	   n = n/10;
-	   cnt++;
-   }
-
-   return cnt;
-}
-
-ModuleHeader MOD_HEADER
-  = {
-	"map",
-	"5.0",
-	"command /map", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_MAP, cmd_map, MAXPARA, CMD_USER);
-	ISupportAdd(modinfo->handle, "MAP", NULL);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * New /MAP format -Potvin
- * dump_map function.
- */
-static void dump_map(Client *client, Client *server, char *mask, int prompt_length, int length)
-{
-	static char prompt[64];
-	char *p = &prompt[prompt_length];
-	int  cnt = 0;
-	Client *acptr;
-
-	*p = '\0';
-
-	if (prompt_length > 60)
-		sendnumeric(client, RPL_MAPMORE, prompt, length, server->name);
-	else
-	{
-		char tbuf[256];
-		char sid[10];
-		int len = length - strlen(server->name) + 1;
-
-		if (len < 0)
-			len = 0;
-		if (len > 255)
-			len = 255;
-
-		tbuf[len--] = '\0';
-		while (len >= 0)
-			tbuf[len--] = '-';
-		if (IsOper(client))
-			snprintf(sid, sizeof(sid), " [%s]", server->id);
-		sendnumeric(client, RPL_MAP, prompt, server->name, tbuf, umax,
-			server->server->users, (double)(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
-			(server->server->users * 100.0 / irccounts.clients),
-			IsOper(client) ? sid : "");
-		cnt = 0;
-	}
-
-	if (prompt_length > 0)
-	{
-		p[-1] = ' ';
-		if (p[-2] == '`')
-			p[-2] = ' ';
-	}
-	if (prompt_length > 60)
-		return;
-
-	strcpy(p, "|-");
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if (acptr->uplink != server ||
- 		    (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)))
-			continue;
-		SetMap(acptr);
-		cnt++;
-	}
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if (IsULine(acptr) && HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL))
-			continue;
-		if (acptr->uplink != server)
-			continue;
-		if (!IsMap(acptr))
-			continue;
-		if (--cnt == 0)
-			*p = '`';
-		dump_map(client, acptr, mask, prompt_length + 2, length - 2);
-	}
-
-	if (prompt_length > 0)
-		p[-1] = '-';
-}
-
-void dump_flat_map(Client *client, Client *server, int length)
-{
-	char buf[4];
-	char tbuf[256];
-	Client *acptr;
-	int cnt = 0, len = 0, hide_ulines;
-
-	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
-
-	len = length - strlen(server->name) + 3;
-	if (len < 0)
-		len = 0;
-	if (len > 255)
-		len = 255;
-
-	tbuf[len--] = '\0';
-	while (len >= 0)
-		tbuf[len--] = '-';
-
-	sendnumeric(client, RPL_MAP, "", server->name, tbuf, umax, server->server->users,
-		(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
-		(server->server->users * 100.0 / irccounts.clients), "");
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
-			continue;
-		cnt++;
-	}
-
-	strcpy(buf, "|-");
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if ((IsULine(acptr) && hide_ulines) || (acptr == server))
-			continue;
-		if (--cnt == 0)
-			*buf = '`';
-
-		len = length - strlen(acptr->name) + 1;
-		if (len < 0)
-			len = 0;
-		if (len > 255)
-			len = 255;
-
-		tbuf[len--] = '\0';
-		while (len >= 0)
-			tbuf[len--] = '-';
-
-		sendnumeric(client, RPL_MAP, buf, acptr->name, tbuf, umax, acptr->server->users,
-			(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
-			(acptr->server->users * 100.0 / irccounts.clients), "");
-	}
-}
-
-/*
-** New /MAP format. -Potvin
-** cmd_map (NEW)
-**
-**      parv[1] = server mask
-**/
-CMD_FUNC(cmd_map)
-{
-	Client *acptr;
-	int  longest = strlen(me.name);
-	float avg_users;
-
-	umax = 0;
-	lmax = 0;
-
-	if (parc < 2)
-		parv[1] = "*";
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		int perc = (acptr->server->users * 100 / irccounts.clients);
-		if ((strlen(acptr->name) + acptr->hopcount * 2) > longest)
-			longest = strlen(acptr->name) + acptr->hopcount * 2;
-		if (lmax < perc)
-			lmax = perc;
-		if (umax < dcount(acptr->server->users))
-			umax = dcount(acptr->server->users);
-	}
-
-	if (longest > 60)
-		longest = 60;
-	longest += 2;
-
-	if (FLAT_MAP && !ValidatePermissionsForPath("server:info:map:real-map",client,NULL,NULL,NULL))
-		dump_flat_map(client, &me, longest);
-	else
-		dump_map(client, &me, "*", 0, longest);
-
-	avg_users = irccounts.clients * 1.0 / irccounts.servers;
-	sendnumeric(client, RPL_MAPUSERS, irccounts.servers, (irccounts.servers > 1 ? "s" : ""), irccounts.clients,
-		(irccounts.clients > 1 ? "s" : ""), avg_users);
-	sendnumeric(client, RPL_MAPEND);
-}
diff --git a/src/modules/max-unknown-connections-per-ip.c b/src/modules/max-unknown-connections-per-ip.c
@@ -1,96 +0,0 @@
-/*
- * Connection throttling (set::max-unknown-connections-per-ip)
- * (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"max-unknown-connections-per-ip",
-	"6.0.0",
-	"set::max-unknown-connections-per-ip",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declaration */
-int max_unknown_connections_accept(Client *client);
-int max_unknown_connections_ip_change(Client *client, const char *oldip);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -2000, max_unknown_connections_accept);
-	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -2000, max_unknown_connections_ip_change);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This checks set::max-unknown-connections-per-ip,
- * which is an important safety feature.
- */
-static int check_too_many_unknown_connections(Client *client)
-{
-	int cnt = 1;
-	Client *c;
-
-	if (!find_tkl_exception(TKL_CONNECT_FLOOD, client))
-	{
-		list_for_each_entry(c, &unknown_list, lclient_node)
-		{
-			if (client->local && client->local->listener &&
-			    (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD))
-			{
-				continue;
-			}
-			if (!strcmp(client->ip,GetIP(c)))
-			{
-				cnt++;
-				if (cnt > iConf.max_unknown_connections_per_ip)
-					return 1;
-			}
-		}
-	}
-
-	return 0;
-}
-
-int max_unknown_connections_accept(Client *client)
-{
-	if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
-		return 0;
-
-	/* Check set::max-unknown-connections-per-ip */
-	if (check_too_many_unknown_connections(client))
-	{
-		send_raw_direct(client, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)", client->ip);
-		return HOOK_DENY;
-	}
-
-	return 0;
-}
-
-int max_unknown_connections_ip_change(Client *client, const char *oldip)
-{
-	/* Check set::max-unknown-connections-per-ip */
-	if (check_too_many_unknown_connections(client))
-	{
-		sendto_one(client, NULL, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)", client->ip);
-		return HOOK_DENY;
-	}
-
-	return 0;
-}
diff --git a/src/modules/md.c b/src/modules/md.c
@@ -1,527 +0,0 @@
-/*
- * Module Data module (command MD)
- * (C) Copyright 2014-.. Bram Matthys and The UnrealIRCd Team
- *
- * This file contains all commands that deal with sending and
- * receiving module data over the network.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"md",
-	"5.0",
-	"command /MD (S2S only)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-CMD_FUNC(cmd_md);
-void _broadcast_md_client(ModDataInfo *mdi, Client *client, ModData *md);
-void _broadcast_md_channel(ModDataInfo *mdi, Channel *channel, ModData *md);
-void _broadcast_md_member(ModDataInfo *mdi, Channel *channel, Member *m, ModData *md);
-void _broadcast_md_membership(ModDataInfo *mdi, Client *client, Membership *m, ModData *md);
-void _broadcast_md_globalvar(ModDataInfo *mdi, ModData *md);
-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);
-void _broadcast_moddata_client(Client *client);
-
-extern MODVAR ModDataInfo *MDInfo;
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CLIENT, _broadcast_md_client);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CHANNEL, _broadcast_md_channel);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_MEMBER, _broadcast_md_member);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_MEMBERSHIP, _broadcast_md_membership);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_GLOBALVAR, _broadcast_md_globalvar);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CLIENT_CMD, _broadcast_md_client_cmd);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MD_CHANNEL_CMD, _broadcast_md_channel_cmd);
-	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);
-	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_MODDATA_CLIENT, _broadcast_moddata_client);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "MD", cmd_md, MAXPARA, CMD_SERVER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-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
- *
- * If <value> is ommitted, the variable is unset & freed.
- *
- * The appropriate module is called to set the data (unserialize) and
- * then the command is broadcasted to all other servers.
- *
- * Technical documentation (if writing services) is available at:
- * https://www.unrealircd.org/docs/Server_protocol:MD_command
- * Module API documentation (if writing an UnrealIRCd module):
- * https://www.unrealircd.org/docs/Dev:Module_Storage
- */
-CMD_FUNC(cmd_md)
-{
-	const char *type, *objname, *varname, *value;
-	ModDataInfo *md;
-
-	if (!IsServer(client) || (parc < 4) || BadPtr(parv[3]))
-		return;
-
-	type = parv[1];
-	objname = parv[2];
-	varname = parv[3];
-	value = parv[4]; /* may be NULL */
-
-	if (!strcmp(type, "client"))
-	{
-		Client *target = find_client(objname, NULL);
-		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
-		{
-			if (md->free)
-				md->free(&moddata_client(target, md));
-			memset(&moddata_client(target, md), 0, sizeof(ModData));
-		}
-		/* Pass on to other servers */
-		broadcast_md_client_cmd(client->direction, client, target, varname, value);
-	} else
-	if (!strcmp(type, "channel"))
-	{
-		Channel *channel = find_channel(objname);
-		md = findmoddata_byname(varname, MODDATATYPE_CHANNEL);
-		if (!md || !md->unserialize || !channel)
-			return;
-		if (value)
-			md->unserialize(value, &moddata_channel(channel, md));
-		else
-		{
-			if (md->free)
-				md->free(&moddata_channel(channel, md));
-			memset(&moddata_channel(channel, md), 0, sizeof(ModData));
-		}
-		/* Pass on to other servers */
-		broadcast_md_channel_cmd(client->direction, client, channel, varname, value);
-	} 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;
-		*p++ = '\0';
-
-		channel = find_channel(objname);
-		if (!channel)
-			return;
-
-		target = find_user(p, NULL);
-		if (!target)
-			return;
-
-		m = find_member_link(channel->members, target);
-		if (!m)
-			return;
-
-		md = findmoddata_byname(varname, MODDATATYPE_MEMBER);
-		if (!md || !md->unserialize)
-			return;
-
-		if (!md_access_check(client, md, target))
-			return;
-
-		if (value)
-			md->unserialize(value, &moddata_member(m, md));
-		else
-		{
-			if (md->free)
-				md->free(&moddata_member(m, md));
-			memset(&moddata_member(m, md), 0, sizeof(ModData));
-		}
-		/* Pass on to other servers */
-		broadcast_md_member_cmd(client->direction, client, channel, target, varname, value);
-	} 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;
-		*p++ = '\0';
-
-		target = find_user(objname, NULL);
-		if (!target)
-			return;
-
-		channel = find_channel(p);
-		if (!channel)
-			return;
-
-		m = find_membership_link(target->user->channel, channel);
-		if (!m)
-			return;
-
-		md = findmoddata_byname(varname, MODDATATYPE_MEMBERSHIP);
-		if (!md || !md->unserialize)
-			return;
-
-		if (!md_access_check(client, md, target))
-			return;
-
-		if (value)
-			md->unserialize(value, &moddata_membership(m, md));
-		else
-		{
-			if (md->free)
-				md->free(&moddata_membership(m, md));
-			memset(&moddata_membership(m, md), 0, sizeof(ModData));
-		}
-		/* Pass on to other servers */
-		broadcast_md_membership_cmd(client->direction, client, target, channel, varname, value);
-	} else
-	if (!strcmp(type, "globalvar"))
-	{
-		/* objname is ignored */
-		md = findmoddata_byname(varname, MODDATATYPE_GLOBAL_VARIABLE);
-		if (!md || !md->unserialize)
-			return;
-		if (value)
-			md->unserialize(value, &moddata_global_variable(md));
-		else
-		{
-			if (md->free)
-				md->free(&moddata_global_variable(md));
-			memset(&moddata_global_variable(md), 0, sizeof(ModData));
-		}
-		/* Pass on to other servers */
-		broadcast_md_globalvar_cmd(client->direction, client, varname, value);
-	}
-}
-
-void _broadcast_md_client_cmd(Client *except, Client *sender, Client *client, const char *varname, const char *value)
-{
-	if (value)
-	{
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s :%s",
-			sender->id, "client", client->id, varname, value);
-	}
-	else
-	{
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s",
-			sender->id, "client", client->id, varname);
-	}
-}
-
-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->name, varname, value);
-	else
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s %s",
-			sender->id, "channel", channel->name, varname);
-}
-
-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->name, client->id, varname, value);
-	}
-	else
-	{
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s",
-			sender->id, "member", channel->name, client->id, varname);
-	}
-}
-
-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->name, varname, value);
-	}
-	else
-	{
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s:%s %s",
-			sender->id, "membership", client->id, channel->name, varname);
-	}
-}
-
-void _broadcast_md_globalvar_cmd(Client *except, Client *sender, const char *varname, const char *value)
-{
-	if (value)
-	{
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s :%s",
-			sender->id, "globalvar", varname, value);
-	}
-	else
-	{
-		sendto_server(except, 0, 0, NULL, ":%s MD %s %s",
-			sender->id, "globalvar", varname);
-	}
-}
-
-/** Send module data update to all servers.
- * @param mdi    Module Data Info structure (which you received from ModDataAdd)
- * @param client The affected client
- * @param md     The ModData. May be NULL for unset.
- */
- 
-void _broadcast_md_client(ModDataInfo *mdi, Client *client, ModData *md)
-{
-	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)
-{
-	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)
-{
-	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)
-{
-	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)
-{
-	const char *value = md ? mdi->serialize(md) : NULL;
-
-	broadcast_md_globalvar_cmd(NULL, &me, mdi->name, value);
-}
-
-/** Send all moddata attached to client 'client' to remote server 'srv' (if the module wants this), called by .. */
-void _send_moddata_client(Client *srv, Client *client)
-{
-	ModDataInfo *mdi;
-
-	for (mdi = MDInfo; mdi; mdi = mdi->next)
-	{
-		if ((mdi->type == MODDATATYPE_CLIENT) && mdi->sync && mdi->serialize)
-		{
-			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);
-		}
-	}
-}
-
-/** 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)
-{
-	ModDataInfo *mdi;
-
-	for (mdi = MDInfo; mdi; mdi = mdi->next)
-	{
-		if ((mdi->type == MODDATATYPE_CHANNEL) && mdi->sync && mdi->serialize)
-		{
-			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->name, mdi->name, value);
-		}
-	}
-}
-
-/** Send all moddata attached to member & memberships for 'channel' to remote server 'srv' (if the module wants this), called by SJOIN */
-void _send_moddata_members(Client *srv)
-{
-	ModDataInfo *mdi;
-	Channel *channel;
-	Client *client;
-
-	for (channel = channels; channel; channel = channel->nextch)
-	{
-		Member *m;
-		for (m = channel->members; m; m = m->next)
-		{
-			client = m->client;
-			if (client->direction == srv)
-				continue; /* from srv's direction */
-			for (mdi = MDInfo; mdi; mdi = mdi->next)
-			{
-				if ((mdi->type == MODDATATYPE_MEMBER) && mdi->sync && mdi->serialize)
-				{
-					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->name, client->id, mdi->name, value);
-				}
-			}
-		}
-	}
-
-	list_for_each_entry(client, &client_list, client_node)
-	{
-		Membership *m;
-		if (!IsUser(client) || !client->user)
-			continue;
-
-		if (client->direction == srv)
-			continue; /* from srv's direction */
-
-		for (m = client->user->channel; m; m = m->next)
-		{
-			for (mdi = MDInfo; mdi; mdi = mdi->next)
-			{
-				if ((mdi->type == MODDATATYPE_MEMBERSHIP) && mdi->sync && mdi->serialize)
-				{
-					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->name, mdi->name, value);
-				}
-			}
-		}
-	}
-}
-
-/** Broadcast moddata attached to client 'client' to all servers. */
-void _broadcast_moddata_client(Client *client)
-{
-	Client *acptr;
-
-	list_for_each_entry(acptr, &server_list, special_node)
-	{
-		send_moddata_client(acptr, client);
-	}
-}
diff --git a/src/modules/message-ids.c b/src/modules/message-ids.c
@@ -1,156 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/message-ids.c
- *   (C) 2019 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"message-ids",
-	"5.0",
-	"msgid CAP",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Variables */
-long CAP_ACCOUNT_TAG = 0L;
-
-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()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "msgid";
-	mtag.is_ok = msgid_mtag_is_ok;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_or_inherit_msgid);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending
- * 'msgid' is permitted to do so and uses a permitted
- * syntax.
- * We simply allow msgid ONLY from servers and with any syntax.
- */
-int msgid_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client) && !BadPtr(value))
-		return 1;
-
-	return 0;
-}
-
-/** Generate a msgid.
- * @returns a MessageTag struct that you can use directly.
- * @note
- * Apparently there has been some discussion on what method to use to
- * generate msgid's. I am not going to list them here. Just saying that
- * they have been considered and we chose to go for a string that contains
- * 128+ bits of randomness, which has an extremely low chance of colissions.
- * Or, to quote wikipedia on the birthday attack problem:
- * "For comparison, 10^-18 to 10^-15 is the uncorrectable bit error rate
- *  of a typical hard disk. In theory, hashes or UUIDs being 128 bits,
- *  should stay within that range until about 820 billion outputs"
- * For reference, 10^-15 is 0.000000000000001%
- * The main reasons for this choice are: that it is extremely simple,
- * the chance of making a mistake in an otherwise complex implementation
- * is nullified and we don't risk "leaking" any details.
- */
-MessageTag *mtag_generate_msgid(void)
-{
-	MessageTag *m = safe_alloc(sizeof(MessageTag));
-	safe_strdup(m->name, "msgid");
-	m->value = safe_alloc(MSGIDLEN+1);
-	gen_random_alnum(m->value, MSGIDLEN);
-	return m;
-}
-
-
-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)
-		m = duplicate_mtag(m);
-	else
-		m = mtag_generate_msgid();
-
-	if (signature)
-	{
-		/* Special case:
-		 * Some commands will receive a single msgid from
-		 * a remote server for multiple events.
-		 * Take for example SJOIN which may contain 5 joins,
-		 * 3 bans setting, 2 invites, and setting a few modes.
-		 * This way we can still generate unique msgid's
-		 * for such sub-events. It is a hash of the subevent
-		 * concatenated to the existing msgid.
-		 * The hash is the first half of a SHA256 hash, then
-		 * base64'd, and with the == suffix removed.
-		 */
-		char prefix[MSGIDLEN+1], *p;
-		strlcpy(prefix, m->value, sizeof(prefix));
-		p = strchr(prefix, '-');
-		if (p)
-		{
-			/* It is possible that we have more stacking.
-			 * IOTW: we are already stacked like xxx-yyy
-			 * and it would have become an xxx-yyy-zzz
-			 * sequence. Instead, we strip the yyy-
-			 * so the end result will be xxx-zzz.
-			 *
-			 * One example code path would be when someone joins
-			 * and the issecure module sets -Z.
-			 */
-			*p = '\0';
-		}
-		SHA256_CTX hash;
-		char binaryhash[SHA256_DIGEST_LENGTH];
-		char b64hash[SHA256_DIGEST_LENGTH*2+1];
-		char newbuf[256];
-		memset(&binaryhash, 0, sizeof(binaryhash));
-		memset(&b64hash, 0, sizeof(b64hash));
-		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);
-		safe_strdup(m->value, newbuf);
-	}
-	AddListItem(m, *mtag_list);
-}
diff --git a/src/modules/message-tags.c b/src/modules/message-tags.c
@@ -1,311 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/message-tags.c
- *   (C) 2019 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"message-tags",
-	"5.0",
-	"Message tags CAP", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-long CAP_MESSAGE_TAGS = 0L;
-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);
-
-	EfunctionAddConstString(modinfo->handle, EFUNC_MTAGS_TO_STRING, _mtags_to_string);
-	EfunctionAddVoid(modinfo->handle, EFUNC_PARSE_MESSAGE_TAGS, _parse_message_tags);
-
-	return 0;
-}
-
-MOD_INIT()
-{
-	ClientCapabilityInfo cap;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "message-tags";
-	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_MESSAGE_TAGS);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Unescape a message tag (name or value).
- * @param in  The input string
- * @param out The output string for writing
- * @note  No size checking, so ensure that the output buffer
- *        is at least as long as the input buffer.
- */
-void message_tag_unescape(char *in, char *out)
-{
-	for (; *in; in++)
-	{
-		if (*in == '\\')
-		{
-			in++;
-			if (*in == ':')
-				*out++ = ';';  /* \: to ; */
-			else if (*in == 's')
-				*out++ = ' ';  /* \s to SPACE */
-			else if (*in == 'r')
-				*out++ = '\r'; /* \r to CR */
-			else if (*in == 'n')
-				*out++ = '\n'; /* \n to LF */
-			else if (*in == '\0')
-				break; /* unfinished escaping (\) */
-			else
-				*out++ = *in; /* all rest is as-is */
-			continue;
-		}
-		*out++ = *in;
-	}
-	*out = '\0';
-}
-
-/** Escape a message tag (name or value).
- * @param in  The input string
- * @param out The output string for writing
- * @note  No size checking, so ensure that the output buffer
- *        is at least twice as long as the input buffer + 1.
- */
-void message_tag_escape(char *in, char *out)
-{
-	for (; *in; in++)
-	{
-		if (*in == ';')
-		{
-			*out++ = '\\';
-			*out++ = ':';
-		} else
-		if (*in == ' ')
-		{
-			*out++ = '\\';
-			*out++ = 's';
-		} else
-		if (*in == '\\')
-		{
-			*out++ = '\\';
-			*out++ = '\\';
-		} else
-		if (*in == '\r')
-		{
-			*out++ = '\\';
-			*out++ = 'r';
-		} else
-		if (*in == '\n')
-		{
-			*out++ = '\\';
-			*out++ = 'n';
-		} else
-		{
-			*out++ = *in;
-		}
-	}
-	*out = '\0';
-}
-
-/** Incoming filter for message tags */
-int message_tag_ok(Client *client, char *name, char *value)
-{
-	MessageTagHandler *m;
-
-	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;
-
-	return 0;
-}
-
-void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list)
-{
-	char *remainder;
-	char *element, *p, *x;
-	static char name[8192], value[8192];
-	MessageTag *m;
-
-	remainder = strchr(*str, ' ');
-	if (remainder)
-		*remainder = '\0';
-
-	if (!IsServer(client) && (strlen(*str) > 4094))
-	{
-		sendnumeric(client, ERR_INPUTTOOLONG);
-		remainder = NULL; /* stop parsing */
-	}
-
-	if (!remainder)
-	{
-		/* A message with only message tags (or starting with @ anyway).
-		 * This is useless. So we make it point to the NUL byte,
-		 * aka: empty message.
-		 * This is also used by a line-length-check above to force the
-		 * same error condition ("don't parse this").
-		 */
-		for (; **str; *str += 1);
-		return;
-	}
-
-	/* Now actually parse the tags: */
-	for (element = strtoken(&p, *str+1, ";"); element; element = strtoken(&p, NULL, ";"))
-	{
-		*name = *value = '\0';
-
-		/* Element has style: 'name=value', or it could be just 'name' */
-		x = strchr(element, '=');
-		if (x)
-		{
-			*x++ = '\0';
-			message_tag_unescape(x, value);
-		}
-		message_tag_unescape(element, name);
-
-		/* Let the message tag handler check if this mtag is
-		 * acceptable. If so, we add it to the list.
-		 */
-		if (message_tag_ok(client, name, value))
-		{
-			m = safe_alloc(sizeof(MessageTag));
-			safe_strdup(m->name, name);
-			/* Both NULL and empty become NULL: */
-			if (!*value)
-				m->value = NULL;
-			else /* a real value... */
-				safe_strdup(m->value, value);
-			AddListItem(m, *mtag_list);
-		}
-	}
-
-	*str = remainder + 1;
-}
-
-/** Outgoing filter for tags */
-int client_accepts_tag(const char *token, Client *client)
-{
-	MessageTagHandler *m;
-
-	/* Send all tags to remote links, without checking here.
-	 * Note that mtags_to_string() already prevents sending messages
-	 * with message tags to links without PROTOCTL MTAGS, so we can
-	 * simply always return 1 here, regardless of checking (again).
-	 */
-	if (IsServer(client) || !MyConnect(client))
-		return 1;
-
-	m = MessageTagHandlerFind(token);
-	if (!m)
-		return 0;
-
-	/* Maybe there is an outgoing filter in effect (usually not) */
-	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
-	 * send any message tag, regardless of other CAP's.
-	 */
-	if (HasCapability(client, "message-tags"))
-		return 1;
-
-	/* We continue here if the client did not indicate 'message-tags' support... */
-
-	/* If 'message-tags' is not indicated, then these cannot be sent as they don't
-	 * have a CAP to enable anyway (eg: msgid):
-	 */
-	if (m->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED)
-		return 0;
-
-	/* Otherwise, check if the capability is set:
-	 * eg 'account-tag' for 'account', 'time' for 'server-time' and so on..
-	 */
-	if (m->clicap_handler && (client->local->caps & m->clicap_handler->cap))
-		return 1;
-
-	return 0;
-}
-
-/** Return the message tag string (without @) of the message tag linked list.
- * Taking into account the restrictions that 'client' may have.
- * @returns A string (static buffer) or NULL if no tags at all (!)
- */
-const char *_mtags_to_string(MessageTag *m, Client *client)
-{
-	static char buf[4096], name[8192], value[8192];
-	static char tbuf[4094];
-
-	if (!m)
-		return NULL;
-
-	/* Remote servers need to indicate support via PROTOCTL MTAGS */
-	if (client->direction && IsServer(client->direction) && !SupportMTAGS(client->direction))
-		return NULL;
-
-	*buf = '\0';
-	for (; m; m = m->next)
-	{
-		if (!client_accepts_tag(m->name, client))
-			continue;
-		if (m->value)
-		{
-			message_tag_escape(m->name, name);
-			message_tag_escape(m->value, value);
-			snprintf(tbuf, sizeof(tbuf), "%s=%s;", name, value);
-		} else {
-			message_tag_escape(m->name, name);
-			snprintf(tbuf, sizeof(tbuf), "%s;", name);
-		}
-		strlcat(buf, tbuf, sizeof(buf));
-	}
-
-	if (!*buf)
-		return NULL;
-
-	/* Strip off the final semicolon */
-	buf[strlen(buf)-1] = '\0';
-
-	return buf;
-}
diff --git a/src/modules/message.c b/src/modules/message.c
@@ -1,710 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/message.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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"
-
-/* Forward declarations */
-const char *_StripColors(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, 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 */
-
-ModuleHeader MOD_HEADER
-  = {
-	"message",	/* Name of module */
-	"6.0.2", /* Version */
-	"private message and notice", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCOLORS, _StripColors);
-	EfunctionAdd(modinfo->handle, EFUNC_CAN_SEND_TO_CHANNEL, _can_send_to_channel);
-	return MOD_SUCCESS;
-}
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "PRIVMSG", cmd_private, 2, CMD_USER|CMD_SERVER|CMD_RESETIDLE|CMD_VIRUS);
-	CommandAdd(modinfo->handle, "NOTICE", cmd_notice, 2, CMD_USER|CMD_SERVER);
-	CommandAdd(modinfo->handle, "TAGMSG", cmd_tagmsg, 1, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	CAP_MESSAGE_TAGS = ClientCapabilityBit("message-tags");
-
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-#define CANPRIVMSG_CONTINUE		100
-#define CANPRIVMSG_SEND			101
-/** Check if PRIVMSG's are permitted from a person to another person.
- * client:	source client
- * target:	target client
- * sendtype:	One of SEND_TYPE_*
- * text:	Pointer to a pointer to a text [in, out]
- * cmd:		Pointer to a pointer which contains the command to use [in, out]
- */
-int can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype)
-{
-	int ret;
-	Hook *h;
-	int n;
-	static char errbuf[256];
-
-	*errmsg = NULL;
-
-	if (IsVirus(client))
-	{
-		ircsnprintf(errbuf, sizeof(errbuf), "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
-		*errmsg = errbuf;
-		return 0;
-	}
-
-	if (MyUser(client) && target_limit_exceeded(client, target, target->name))
-	{
-		/* target_limit_exceeded() is an exception, in the sense that
-		 * it will send a different numeric. So we don't set errmsg.
-		 */
-		return 0;
-	}
-
-	if (is_silenced(client, target))
-	{
-		RunHook(HOOKTYPE_SILENCED, client, target, sendtype);
-		/* Silently discarded, no error message */
-		return 0;
-	}
-
-	// Possible FIXME: make match_spamfilter also use errmsg, or via a wrapper? or use same numeric?
-	if (MyUser(client))
-	{
-		int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG);
-		const char *cmd = sendtype_to_cmd(sendtype);
-
-		if (match_spamfilter(client, *msgtext, spamtype, cmd, target->name, 0, NULL))
-			return 0;
-	}
-
-	n = HOOK_CONTINUE;
-	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_USER]; h; h = h->next)
-	{
-		n = (*(h->func.intfunc))(client, target, msgtext, errmsg, sendtype);
-		if (n == HOOK_DENY)
-		{
-			if (!*errmsg)
-			{
-				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;
-		}
-		if (!*msgtext || !**msgtext)
-		{
-			if (sendtype != SEND_TYPE_TAGMSG)
-				return 0;
-			else
-				*msgtext = "";
-		}
-	}
-
-	return 1;
-}
-
-/** Check if user is allowed to send to a prefix (eg: @#channel).
- * @param client	The client (sender)
- * @param channel	The target channel
- * @param mode		The member mode to send to (eg: 'o')
- */
-int can_send_to_member_mode(Client *client, Channel *channel, char mode)
-{
-	Membership *lp;
-
-	if (op_can_override("channel:override:message:prefix",client,channel,NULL))
-		return 1;
-
-	lp = find_membership_link(client->user->channel, channel);
-
-	/* Check if user is allowed to send. RULES:
-	 * Need at least voice (+) in order to send to +,% or @
-	 * Need at least ops (@) in order to send to & or ~
-	 */
-	if (!lp || !check_channel_access_membership(lp, "vhoaq"))
-	{
-		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
-		return 0;
-	}
-
-#if 0
-	if (!(prefix & PREFIX_OP) && ((prefix & PREFIX_OWNER) || (prefix & PREFIX_ADMIN)) &&
-	    !check_channel_access_membership(lp, "oaq"))
-	{
-		sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->name);
-		return 0;
-	}
-#endif
-
-	return 1;
-}
-
-int has_client_mtags(MessageTag *mtags)
-{
-	MessageTag *m;
-
-	for (m = mtags; m; m = m->next)
-		if (*m->name == '+')
-			return 1;
-	return 0;
-}
-
-/* General message handler to users and channels. Used by PRIVMSG, NOTICE, etc.
- */
-void cmd_message(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], SendType sendtype)
-{
-	Client *target;
-	Channel *channel;
-	char targets[BUFSIZE];
-	char *targetstr, *p, *p2, *pc;
-	const char *text, *errmsg;
-	int ret;
-	int ntargets = 0;
-	const char *cmd = sendtype_to_cmd(sendtype);
-	int maxtargets = max_targets_for_command(cmd);
-	Hook *h;
-	MessageTag *mtags;
-	int sendflags;
-
-	/* Force a labeled-response, even if we don't send anything
-	 * and the request was sent to other servers (which won't
-	 * reply either :D).
-	 */
-	labeled_response_force = 1;
-
-	if (parc < 2 || *parv[1] == '\0')
-	{
-		sendnumeric(client, ERR_NORECIPIENT, cmd);
-		return;
-	}
-
-	if ((sendtype != SEND_TYPE_TAGMSG) && (parc < 3 || *parv[2] == '\0'))
-	{
-		sendnumeric(client, ERR_NOTEXTTOSEND);
-		return;
-	}
-
-	if (MyConnect(client))
-		parv[1] = (char *)canonize(parv[1]);
-
-	strlcpy(targets, parv[1], sizeof(targets));
-	for (p = NULL, targetstr = strtoken(&p, targets, ","); targetstr; targetstr = strtoken(&p, NULL, ","))
-	{
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, targetstr, maxtargets, cmd);
-			break;
-		}
-
-		/* The nicks "ircd" and "irc" are special (and reserved) */
-		if (!strcasecmp(targetstr, "ircd") && MyUser(client))
-			return;
-
-		if (!strcasecmp(targetstr, "irc") && MyUser(client))
-		{
-			/* When ban version { } is enabled the IRCd sends a CTCP VERSION request
-			 * from the "IRC" nick. So we need to handle CTCP VERSION replies to "IRC".
-			 */
-			if (!strncmp(parv[2], "\1VERSION ", 9))
-				ban_version(client, parv[2] + 9);
-			else if (!strncmp(parv[2], "\1SCRIPT ", 8))
-				ban_version(client, parv[2] + 8);
-			return;
-		}
-
-		p2 = strchr(targetstr, '#');
-
-		/* Message to channel */
-		if (p2 && (channel = find_channel(p2)))
-		{
-			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)
-			{
-				/* Replace target so the privmsg always goes to the "official" channel name */
-				strlcpy(pfixchan, channel->name, sizeof(pfixchan));
-				targetstr = pfixchan;
-			}
-
-			if (IsVirus(client) && strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN))
-			{
-				sendnotice(client, "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
-				continue;
-			}
-
-			text = parv[2];
-			errmsg = NULL;
-			if (MyUser(client) && !IsULine(client))
-			{
-				if (!can_send_to_channel(client, channel, &text, &errmsg, sendtype))
-				{
-					/* Send the error message, but only if:
-					 * 1) The user has not been killed
-					 * 2) It is not a NOTICE
-					 */
-					if (IsDead(client))
-						return;
-					if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE) && !BadPtr(errmsg))
-						sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->name, errmsg, p2);
-					continue; /* skip delivery to this target */
-				}
-			}
-			mtags = NULL;
-			sendflags = SEND_ALL;
-
-			if (!strchr(CHANCMDPFX,parv[2][0]))
-				sendflags |= SKIP_DEAF;
-
-			if ((*parv[2] == '\001') && strncmp(&parv[2][1], "ACTION ", 7))
-				sendflags |= SKIP_CTCP;
-
-			if (MyUser(client))
-			{
-				int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG);
-
-				if (match_spamfilter(client, text, spamtype, cmd, channel->name, 0, NULL))
-					return;
-			}
-
-			new_message(client, recv_mtags, &mtags);
-
-			RunHook(HOOKTYPE_PRE_CHANMSG, client, channel, mtags, text, sendtype);
-
-			if (!text)
-			{
-				free_message_tags(mtags);
-				continue;
-			}
-
-			if (sendtype != SEND_TYPE_TAGMSG)
-			{
-				/* PRIVMSG or NOTICE */
-				sendto_channel(channel, client, client->direction,
-					       member_modes, 0, sendflags, mtags,
-					       ":%s %s %s :%s",
-					       client->name, cmd, targetstr, text);
-			} else {
-				/* TAGMSG:
-				 * Only send if the message includes any user message tags
-				 * and if the 'message-tags' module is loaded.
-				 * Do not allow empty and useless TAGMSG.
-				 */
-				if (!CAP_MESSAGE_TAGS || !has_client_mtags(mtags))
-				{
-					free_message_tags(mtags);
-					continue;
-				}
-				sendto_channel(channel, client, client->direction,
-					       member_modes, CAP_MESSAGE_TAGS, sendflags, mtags,
-					       ":%s TAGMSG %s",
-					       client->name, targetstr);
-			}
-
-			RunHook(HOOKTYPE_CHANMSG, client, channel, sendflags, member_modes, targetstr, mtags, text, sendtype);
-
-			free_message_tags(mtags);
-
-			continue;
-		}
-		else if (p2)
-		{
-			sendnumeric(client, ERR_NOSUCHNICK, p2);
-			continue;
-		}
-
-
-		/* Message to $servermask */
-		if (*targetstr == '$')
-		{
-			MessageTag *mtags = NULL;
-
-			if (!ValidatePermissionsForPath("chat:notice:global", client, NULL, NULL, NULL))
-			{
-				/* Apparently no other IRCd does this, but I think it's confusing not to
-				 * send an error message, especially with our new privilege system.
-				 * Error message could be more descriptive perhaps.
-				 */
-				sendnumeric(client, ERR_NOPRIVILEGES);
-				continue;
-			}
-			new_message(client, recv_mtags, &mtags);
-			sendto_match_butone(IsServer(client->direction) ? client->direction : NULL,
-			    client, targetstr + 1,
-			    (*targetstr == '#') ? MATCH_HOST :
-			    MATCH_SERVER,
-			    mtags,
-			    ":%s %s %s :%s", client->name, cmd, targetstr, parv[2]);
-			free_message_tags(mtags);
-			continue;
-		}
-
-		/* nickname addressed? */
-		target = hash_find_nickatserver(targetstr, NULL);
-		if (target)
-		{
-			const char *errmsg = NULL;
-			text = parv[2];
-			if (!can_send_to_user(client, target, &text, &errmsg, sendtype))
-			{
-				/* Message is discarded */
-				if (IsDead(client))
-					return;
-				if ((sendtype != SEND_TYPE_NOTICE) && !BadPtr(errmsg))
-					sendnumeric(client, ERR_CANTSENDTOUSER, target->name, errmsg);
-			} else
-			{
-				/* We may send the message */
-				MessageTag *mtags = NULL;
-
-				/* Inform sender that recipient is away, if this is so */
-				if ((sendtype == SEND_TYPE_PRIVMSG) && MyConnect(client) && target->user && target->user->away)
-					sendnumeric(client, RPL_AWAY, target->name, target->user->away);
-
-				new_message(client, recv_mtags, &mtags);
-				if ((sendtype == SEND_TYPE_TAGMSG) && !has_client_mtags(mtags))
-				{
-					free_message_tags(mtags);
-					continue;
-				}
-				labeled_response_inhibit = 1;
-				if (MyUser(target))
-				{
-					/* Deliver to end-user */
-					if (sendtype == SEND_TYPE_TAGMSG)
-					{
-						if (HasCapability(target, "message-tags"))
-						{
-							sendto_prefix_one(target, client, mtags, ":%s %s %s",
-									  client->name, cmd, target->name);
-						}
-					} else {
-						sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
-								  client->name, cmd, target->name, text);
-					}
-				} else {
-					/* Send to another server */
-					if (sendtype == SEND_TYPE_TAGMSG)
-					{
-						sendto_prefix_one(target, client, mtags, ":%s %s %s",
-								  client->id, cmd, target->id);
-					} else {
-						sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
-								  client->id, cmd, target->id, text);
-					}
-				}
-				labeled_response_inhibit = 0;
-				RunHook(HOOKTYPE_USERMSG, client, target, mtags, text, sendtype);
-				free_message_tags(mtags);
-				continue;
-			}
-			continue; /* Message has been delivered or rejected, continue with next target */
-		}
-
-		/* If nick@server -and- the @server portion was set::services-server then send a special message */
-		if (!target && SERVICES_NAME)
-		{
-			char *server = strchr(targetstr, '@');
-			if (server && strncasecmp(server + 1, SERVICES_NAME, strlen(SERVICES_NAME)) == 0)
-			{
-				sendnumeric(client, ERR_SERVICESDOWN, targetstr);
-				continue;
-			}
-		}
-
-		/* nothing, nada, not anything found */
-		sendnumeric(client, ERR_NOSUCHNICK, targetstr);
-		continue;
-	}
-}
-
-/*
-** cmd_private
-**	parv[1] = receiver list
-**	parv[2] = message text
-*/
-CMD_FUNC(cmd_private)
-{
-	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_PRIVMSG);
-}
-
-/*
-** cmd_notice
-**	parv[1] = receiver list
-**	parv[2] = notice text
-*/
-CMD_FUNC(cmd_notice)
-{
-	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_NOTICE);
-}
-
-/*
-** cmd_tagmsg
-**	parv[1] = receiver list
-*/
-CMD_FUNC(cmd_tagmsg)
-{
-	/* compatibility hack */
-	parv[2] = "";
-	parv[3] = NULL;
-	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_TAGMSG);
-}
-
-/* Taken from xchat by Peter Zelezny
- * changed very slightly by codemastr
- * RGB color stripping support added -- codemastr
- *
- * NOTE: if you change/update/enhance StripColors() then consider changing
- *       the StripControlCodes() function as well (in misc.c) !!
- */
-const char *_StripColors(const char *text)
-{
-	int i = 0, len = strlen(text), save_len=0;
-	char nc = 0, col = 0, rgb = 0;
-	const char *save_text=NULL;
-	static char new_str[4096];
-
-	while (len > 0) 
-	{
-		if ((col && isdigit(*text) && nc < 2) ||
-		    ((col == 1) && (*text == ',') && isdigit(text[1]) && (nc > 0) && (nc < 3)))
-		{
-			nc++;
-			if (*text == ',')
-			{
-				nc = 0;
-				col++;
-			}
-		}
-		/* Syntax for RGB is ^DHHHHHH where H is a hex digit.
-		 * If < 6 hex digits are specified, the code is displayed
-		 * as text
-		 */
-		else if ((rgb && isxdigit(*text) && nc < 6) || (rgb && *text == ',' && nc < 7))
-		{
-			nc++;
-			if (*text == ',')
-				nc = 0;
-		}
-		else 
-		{
-			if (col)
-				col = 0;
-			if (rgb)
-			{
-				if (nc != 6)
-				{
-					text = save_text+1;
-					len = save_len-1;
-					rgb = 0;
-					continue;
-				}
-				rgb = 0;
-			}
-			if (*text == '\003') 
-			{
-				col = 1;
-				nc = 0;
-			}
-			else if (*text == '\004')
-			{
-				save_text = text;
-				save_len = len;
-				rgb = 1;
-				nc = 0;
-			}
-			else if (*text != '\026') /* (strip reverse too) */
-			{
-				new_str[i] = *text;
-				i++;
-			}
-		}
-		text++;
-		len--;
-	}
-	new_str[i] = 0;
-	if (new_str[0] == '\0')
-		return NULL;
-	return new_str;
-}
-
-/** Check ban version { } blocks, returns 1  if banned and  0 if not. */
-int ban_version(Client *client, const char *text)
-{
-	int len;
-	ConfigItem_ban *ban;
-	char ctcp_reply[BUFSIZE];
-
-	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 ((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 */
-
-		if (find_tkl_exception(TKL_BAN_VERSION, client))
-			return 0; /* we are exempt */
-
-		place_host_ban(client, ban->action, ban->reason, BAN_VERSION_TKL_TIME);
-		return 1;
-	}
-
-	return 0;
-}
-
-/** Can user send a message to this channel?
- * @param client    The client
- * @param channel   The channel
- * @param msgtext   The message to send (MAY be changed, even if user is allowed to send)
- * @param errmsg    The error message (will be filled in)
- * @param sendtype  One of SEND_TYPE_*
- * @returns Returns 1 if the user is allowed to send, otherwise 0.
- * (note that this behavior was reversed in UnrealIRCd versions <5.x.
- */
-int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype)
-{
-	Membership *lp;
-	int  member, i = 0;
-	Hook *h;
-
-	if (!MyUser(client))
-		return 1;
-
-	*errmsg = NULL;
-
-	member = IsMember(client, channel);
-
-	lp = find_membership_link(client->user->channel, channel);
-
-	/* Modules can plug in as well */
-	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_CHANNEL]; h; h = h->next)
-	{
-		i = (*(h->func.intfunc))(client, channel, lp, msgtext, errmsg, sendtype);
-		if (i != HOOK_CONTINUE)
-		{
-			if (!*errmsg)
-			{
-				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;
-		}
-		if (!*msgtext || !**msgtext)
-		{
-			if (sendtype != SEND_TYPE_TAGMSG)
-				return 0;
-			else
-				*msgtext = "";
-		}
-	}
-
-	if (i != HOOK_CONTINUE)
-	{
-		if (!*errmsg)
-			*errmsg = "You are banned";
-		/* Don't send message if the user was previously a member
-		 * and isn't anymore, so if the user is KICK'ed, eg by floodprot.
-		 */
-		if (member && !IsDead(client) && !find_membership_link(client->user->channel, channel))
-			*errmsg = NULL;
-		return 0;
-	}
-
-	/* Now we are going to check bans */
-
-	/* ..but first: exempt ircops */
-	if (op_can_override("channel:override:message:ban",client,channel,NULL))
-		return 1;
-
-	/* 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)
-			*errmsg = "You are banned";
-		return 0;
-	}
-
-	return 1;
-}
diff --git a/src/modules/mkpasswd.c b/src/modules/mkpasswd.c
@@ -1,106 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/mkpasswd.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   mkpasswd command
- *
- *   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_mkpasswd);
-
-#define MSG_MKPASSWD 	"MKPASSWD"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"mkpasswd",
-	"5.0",
-	"command /mkpasswd", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_MKPASSWD, cmd_mkpasswd, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_mkpasswd
-**      parv[1] = password to encrypt
-*/
-CMD_FUNC(cmd_mkpasswd)
-{
-	short type;
-	const char *result = NULL;
-
-	if (!MKPASSWD_FOR_EVERYONE && !IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	if (!IsOper(client))
-	{
-		/* 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.
-		 */
-		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]))
-	{
-		sendnotice(client, "*** Syntax: /mkpasswd <authmethod> :parameter");
-		return;
-	}
-	/* Don't want to take any risk ;p. -- Syzop */
-	if (strlen(parv[2]) > 64)
-	{
-		sendnotice(client, "*** Your parameter (text-to-hash) is too long.");
-		return;
-	}
-	if ((type = Auth_FindType(NULL, parv[1])) == -1)
-	{
-		sendnotice(client, "*** %s is not an enabled authentication method", parv[1]);
-		return;
-	}
-
-	if (!(result = Auth_Hash(type, parv[2])))
-	{
-		sendnotice(client, "*** Authentication method %s failed", parv[1]);
-		return;
-	}
-
-	sendnotice(client, "*** Authentication phrase (method=%s, para=%s) is: %s",
-		parv[1], parv[2], result);
-}
diff --git a/src/modules/mode.c b/src/modules/mode.c
@@ -1,1569 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/mode.c
- *   (C) 2005-.. 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
-  = {
-	"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, 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, MessageTag *mtags, const char *modes, const char *parameters);
-CMD_FUNC(_cmd_umode);
-
-/* local: */
-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]);
-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 char *mode_cutoff(const char *s);
-void mode_operoverride_msg(Client *client, Channel *channel, char *modebuf, char *parabuf);
-
-static int samode_in_progress = 0;
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_DO_MODE, _do_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, "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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * cmd_mode -- written by binary (garryb@binary.islesfan.net)
- * Completely rewrote it.  The old mode command was 820 lines of ICKY
- * coding, which is a complete waste, because I wrote it in 570 lines of
- * *decent* coding.  This is also easier to read, change, and fine-tune.  Plus,
- * everything isn't scattered; everything's grouped where it should be.
- *
- * parv[1] - channel
- */
-CMD_FUNC(cmd_mode)
-{
-	long unsigned sendts = 0;
-	Ban *ban;
-	Channel *channel;
-
-	/* Now, try to find the channel in question */
-	if (parc > 1)
-	{
-		if (*parv[1] == '#')
-		{
-			channel = find_channel(parv[1]);
-			if (!channel)
-			{
-				CALL_CMD_FUNC(cmd_umode);
-				return;
-			}
-		} else
-		{
-			CALL_CMD_FUNC(cmd_umode);
-			return;
-		}
-	} else
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "MODE");
-		return;
-	}
-
-	if (MyConnect(client) && !valid_channelname(parv[1]))
-	{
-		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
-		return;
-	}
-
-	if (parc < 3)
-	{
-		char modebuf[BUFSIZE], parabuf[BUFSIZE];
-		*modebuf = *parabuf = '\0';
-
-		modebuf[1] = '\0';
-		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;
-	}
-
-	/* 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) && !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) && !check_channel_access(client, channel, "oaq") &&
-	    check_channel_access(client, channel, "h") && ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
-	{
-		opermode = 2;
-		goto aftercheck;
-	}
-#endif
-
-	/* User does not have permission to use the MODE command */
-	if (MyUser(client) && !IsULine(client) && !check_channel_access(client, channel, "hoaq") &&
-	    !ValidatePermissionsForPath("channel:override:mode",client,NULL,channel,NULL))
-	{
-		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) && (sendts > channel->creationtime))
-	{
-		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')
-		sendts = -1;
-	if (IsServer(client) && sendts != -1)
-		parc--;		/* server supplied a time stamp, remove it now */
-
-aftercheck:
-
-	/* This is to prevent excess +<whatever> modes. -- Syzop */
-	if (MyUser(client) && parv[2])
-	{
-		parv[2] = mode_cutoff(parv[2]);
-	}
-
-	/* Filter out the unprivileged FIRST. *
-	 * Now, we can actually do the mode.  */
-
-	(void)do_mode(channel, client, recv_mtags, parc - 2, parv + 2, sendts, 0);
-	/* After this don't touch 'channel' anymore, as permanent module may have destroyed the channel */
-	opermode = 0; /* Important since sometimes forgotten. -- Syzop */
-}
-
-/** 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
- * @returns The cleaned up string
- */
-static char *mode_cutoff(const char *i)
-{
-	static char newmodebuf[BUFSIZE];
-	char *o;
-	unsigned short modesleft = MAXMODEPARAMS * 2; /* be generous... */
-
-	strlcpy(newmodebuf, i, sizeof(newmodebuf));
-
-	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, 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;
-	int i;
-	char tschange = 0;
-	MultiLineMode *m;
-
-	/* Please keep the next 3 lines next to each other */
-	samode_in_progress = samode;
-	m = set_mode(channel, client, parc, parv, &pcount, pvar);
-	samode_in_progress = 0;
-
-	if (IsServer(client))
-	{
-		if (sendts > 0)
-		{
-			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 > channel->creationtime && channel->creationtime)
-			{
-				/* Their timestamp is wrong */
-				sendts = channel->creationtime;
-				sendto_one(client, NULL, ":%s MODE %s + %lld", me.name,
-				    channel->name, (long long)channel->creationtime);
-			}
-		}
-		if (sendts == -1)
-			sendts = channel->creationtime;
-	}
-
-	if (!m)
-	{
-		/* No modes changed (empty mode change) */
-		if (tschange && !m)
-		{
-			/* 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;
-	}
-
-	/* Now loop through the multiline modes... */
-	for (i = 0; i < m->numlines; i++)
-	{
-		char *modebuf = m->modeline[i];
-		char *parabuf = m->paramline[i];
-		MessageTag *mtags = NULL;
-		int should_destroy = 0;
-
-		if (IsUser(orig_client) && samode && MyUser(orig_client))
-		{
-			if (!sajoinmode)
-			{
-				char buf[512];
-				snprintf(buf, sizeof(buf), "%s%s%s", modebuf, *parabuf ? " " : "", parabuf);
-				unreal_log(ULOG_INFO, "samode", "SAMODE_COMMAND", orig_client,
-					   "Client $client used SAMODE $channel ($mode)",
-					   log_data_channel("channel", channel),
-					   log_data_string("mode", buf));
-			}
-
-			client = &me;
-			sendts = 0;
-		}
-
-		if (m->numlines == 1)
-		{
-			/* Single mode lines are easy: retain original msgid etc */
-			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);
-		}
-
-		/* IMPORTANT: if you return, don't forget to free mtags!! */
-
-		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);
-
-		/* opermode for twimodesystem --sts */
-#ifndef NO_OPEROVERRIDE
-		if ((opermode == 1) && IsUser(client))
-		{
-			mode_operoverride_msg(client, channel, modebuf, parabuf);
-
-			sendts = 0;
-		}
-#endif
-
-		sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-			       ":%s MODE %s %s %s",
-			       client->name, channel->name, modebuf, parabuf);
-
-		if (IsServer(client) || IsMe(client))
-		{
-			sendto_server(client, 0, 0, mtags,
-				      ":%s MODE %s %s %s %lld",
-				      client->id, channel->name,
-				      modebuf, parabuf,
-				      (sendts != -1) ? (long long)sendts : 0LL);
-		} else
-		{
-			sendto_server(client, 0, 0, mtags,
-				      ":%s MODE %s %s %s",
-				      client->id, channel->name,
-				      modebuf, parabuf);
-		}
-
-		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);
-
-		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.
- */
-MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, int pcount, char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
-{
-	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;
-
-	/* 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;
-
-	/* 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 (!cm->letter || cm->paracount)
-			continue;
-		/* have it now and didn't have it before? */
-		if ((channel->mode.mode & cm->mode) &&
-		    !(oldem & cm->mode))
-		{
-			if (what != MODE_ADD)
-			{
-				strlcat_letter(m->modeline[curr], '+', BUFSIZE);
-				what = MODE_ADD;
-			}
-			strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
-		}
-	}
-
-	/* Which paramless modes got unset? Eg -r */
-	for (cm=channelmodes; cm; cm = cm->next)
-	{
-		if (!cm->letter || cm->unset_with_param)
-			continue;
-		/* don't have it now and did have it before */
-		if (!(channel->mode.mode & cm->mode) && (oldem & cm->mode))
-		{
-			if (what != MODE_DEL)
-			{
-				strlcat_letter(m->modeline[curr], '-', BUFSIZE);
-				what = MODE_DEL;
-			}
-			strlcat_letter(m->modeline[curr], cm->letter, BUFSIZE);
-		}
-	}
-
-	/* 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 ((strlen(m->modeline[curr]) + strlen(m->paramline[curr]) + strlen(&pvar[cnt][2])) > 507)
-		{
-			if (curr == MAXMULTILINEMODES)
-			{
-				/* Should be impossible.. */
-				unreal_log(ULOG_ERROR, "mode", "MODE_MULTILINE_EXCEEDED", client,
-				           "A mode string caused an avalanche effect of more than $max_multiline_modes modes "
-				           "in channel $channel. Caused by client $client. Expect a desync.",
-				           log_data_integer("max_multiline_modes", MAXMULTILINEMODES),
-				           log_data_channel("channel", channel));
-				break;
-			}
-			curr++;
-			m->modeline[curr] = safe_alloc(BUFSIZE);
-			m->paramline[curr] = safe_alloc(BUFSIZE);
-			m->numlines = curr+1;
-			what = 0;
-		}
-		if ((*(pvar[cnt]) == '+') && what != MODE_ADD)
-		{
-			strlcat_letter(m->modeline[curr], '+', BUFSIZE);
-			what = MODE_ADD;
-		}
-		if ((*(pvar[cnt]) == '-') && what != MODE_DEL)
-		{
-			strlcat_letter(m->modeline[curr], '-', BUFSIZE);
-			what = MODE_DEL;
-		}
-		strlcat_letter(m->modeline[curr], *(pvar[cnt] + 1), BUFSIZE);
-		strlcat(m->paramline[curr], &pvar[cnt][2], BUFSIZE);
-		strlcat_letter(m->paramline[curr], ' ', BUFSIZE);
-	}
-
-	for (i = 0; i <= curr; i++)
-	{
-		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';
-	}
-
-	/* Now check for completely empty mode: */
-	if ((curr == 0) && empty_mode(m->modeline[0]))
-	{
-		/* And convert it to a NULL result */
-		safe_free_multilinemode(m);
-		return NULL;
-	}
-
-	return m;
-}
-
-const char *mode_ban_handler(Client *client, Channel *channel, const char *param, int what, int extbtype, Ban **banlist)
-{
-	const char *tmpstr;
-	BanContext *b;
-
-	tmpstr = clean_ban_mask(param, what, client, 0);
-	if (BadPtr(tmpstr))
-	{
-		/* Invalid ban. See if we can send an error about that (only for extbans) */
-		if (MyUser(client) && is_extended_ban(param))
-		{
-			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)
-		{
-			if ((extbtype == EXBTYPE_INVEX) && !(extban->options & EXTBOPT_INVEX))
-				return NULL; /* this extended ban type does not support INVEX */
-			if (extban->is_ok)
-			{
-				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 (ValidatePermissionsForPath("channel:override:mode:extban",client,NULL,channel,NULL))
-					{
-						/* TODO: send operoverride notice */
-					} else {
-						b->banstr = nextbanstr;
-						b->is_ok_check = EXBCHK_ACCESS_ERR;
-						extban->is_ok(b);
-						safe_free(b);
-						return NULL;
-					}
-				}
-				b->banstr = nextbanstr;
-				b->is_ok_check = EXBCHK_PARAM;
-				if (!extban->is_ok(b))
-				{
-					safe_free(b);
-					return NULL;
-				}
-				safe_free(b);
-			}
-		}
-	}
-
-	if ( (what == MODE_ADD && add_listmode(banlist, client, channel, tmpstr)) ||
-	     (what == MODE_DEL && del_listmode(banlist, channel, tmpstr)))
-	{
-		return NULL;	/* already exists */
-	}
-
-	return tmpstr;
-}
-
-/** 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
-
-	ircsnprintf(pvar[*pcount], MODEBUFLEN + 3,
-	            "%c%c%s",
-	            (what == MODE_ADD) ? '+' : '-',
-	            modeletter,
-	            str);
-	(*pcount)++;
-}
-
-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;
-
-	/* Check if there is a parameter present */
-	if (!param || *pcount >= MAXMODEPARAMS)
-		return 0;
-
-	switch (modetype)
-	{
-		case MODE_BAN:
-			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:
-			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:
-			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 1;
-}
-
-/** Check access and if granted, set the extended chanmode to the requested value in memory.
-  * @returns amount of params eaten (0 or 1)
-  */
-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->letter;
-	int x;
-	const char *morphed;
-
-	if ((what == MODE_DEL) && handler->unset_with_param)
-		paracnt = 1; /* there's always an exception! */
-
-	/* Expected a param and it isn't there? */
-	if (paracnt && (!param || (*pcount >= MAXMODEPARAMS)))
-		return 0;
-
-	/* Prevent remote users from setting local channel modes */
-	if (handler->local && !MyUser(client))
-		return paracnt;
-
-	if (MyUser(client))
-	{
-		x = handler->is_ok(client, channel, mode, param, EXCHK_ACCESS, what);
-		if ((x == EX_ALWAYS_DENY) ||
-		    ((x == EX_DENY) && !op_can_override("channel:override:mode:del",client,channel,handler) && !samode_in_progress))
-		{
-			handler->is_ok(client, channel, mode, param, EXCHK_ACCESS_ERR, what);
-			return paracnt; /* Denied & error msg sent */
-		}
-		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 */
-		if (!IsULine(client) && IsUser(client) && op_can_override("channel:override:mode:del",client,channel,handler) &&
-		    (handler->is_ok(client, channel, mode, param, EXCHK_ACCESS, what) != EX_ALLOW))
-		{
-			opermode = 1; /* override in progress... */
-		}
-	}
-
-	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->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.
-			 * we do eat the parameter. -- Syzop
-			 */
-			return paracnt;
-		}
-	}
-
-	/* w00t... a parameter mode */
-	if (handler->paracount)
-	{
-		if (what == MODE_DEL)
-		{
-			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.
-				 */
-				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.
-				 */
-			}
-		} else {
-			/* add: is the parameter ok? */
-			if (handler->is_ok(client, channel, mode, param, EXCHK_PARAM, what) == FALSE)
-				return paracnt; /* rejected by is_ok */
-
-			morphed = handler->conv_param(param, client, channel);
-			if (!morphed)
-				return paracnt; /* rejected by conv_param */
-
-			/* is it already set at the same value? if so, ignore it. */
-			if (channel->mode.mode & handler->mode)
-			{
-				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... */
-			}
-			do_mode_char_write(pvar, pcount, what, handler->letter, handler->conv_param(param, client, channel));
-			param = morphed; /* set param to converted parameter. */
-		}
-	}
-
-	if (what == MODE_ADD)
-	{	/* + */
-		channel->mode.mode |= handler->mode;
-		if (handler->paracount)
-			cm_putparameter(channel, handler->letter, param);
-		RunHook(HOOKTYPE_MODECHAR_ADD, channel, (int)mode);
-	} else
-	{	/* - */
-		channel->mode.mode &= ~(handler->mode);
-		RunHook(HOOKTYPE_MODECHAR_DEL, channel, (int)mode);
-		if (handler->paracount)
-			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_client("target", target),
-			   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 */
-
-	/* HOOKTYPE_MODE_DEOP code */
-	if (what == MODE_DEL)
-	{
-		int ret = EX_ALLOW;
-		const char *badmode = NULL;
-		Hook *h;
-		const char *my_access;
-		Membership *my_membership;
-
-		/* Set "my_access" to access flags of the requestor */
-		if (IsUser(client) && (my_membership = find_membership_link(client->user->channel, channel)))
-			my_access = my_membership->member_modes; /* client */
-		else
-			my_access = ""; /* server */
-
-		for (h = Hooks[HOOKTYPE_MODE_DEOP]; h; h = h->next)
-		{
-			int n = (*(h->func.intfunc))(client, target, channel, what, modechar, my_access, member->member_modes, &badmode);
-			if (n == EX_DENY)
-			{
-				ret = n;
-			} else
-			if (n == EX_ALWAYS_DENY)
-			{
-				ret = n;
-				break;
-			}
-		}
-
-		if (ret == EX_ALWAYS_DENY)
-		{
-			if (MyUser(client) && badmode)
-				sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
-
-			if (MyUser(client))
-				return; /* stop processing this mode */
-		}
-
-		/* 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,handler))
-			{
-				if (badmode)
-					sendto_one(client, NULL, "%s", badmode); /* send error message, if any */
-				return; /* stop processing this mode */
-			} else {
-				opermode = 1;
-			}
-		}
-	}
-
-	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
- */
-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->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->server)
-			return 0;
-		client = client->direction;
-	}
-
-	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->server->features.chanmodes[1] && strchr(client->server->features.chanmodes[1], mode))
-		return 1; /* 1 parameter for set, 1 parameter for unset */
-
-	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->server->features.chanmodes[3] && strchr(client->server->features.chanmodes[3], mode))
-		return 0; /* no parameter for set, no parameter for unset */
-
-	if (mode == '&')
-		return 0; /* & indicates bounce, it is not an actual mode character */
-
-	if (mode == 'F')
-		return (what == MODE_ADD) ? 1 : 0; /* Future compatibility */
-
-	/* If we end up here it means we have no idea if it is a parameter-eating or paramless
-	 * channel mode. That's actually pretty bad. This shouldn't happen since CHANMODES=
-	 * is sent since 2003 and the (often also required) EAUTH PROTOCTL is in there since 2010.
-	 */
-	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;
-}
-
-/** Quick way to find out parameter count for a channel mode.
- * Only for LOCAL mode requests. For remote modes use
- * paracount_for_chanmode_from_server() instead.
- */
-int paracount_for_chanmode(u_int what, char 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.server->features.chanmodes[1] && strchr(me.server->features.chanmodes[1], mode))
-		return 1; /* 1 parameter for set, 1 parameter for unset */
-
-	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.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;
-}
-
-MultiLineMode *_set_mode(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
-                        char pvar[MAXMODEPARAMS][MODEBUFLEN + 3])
-{
-	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;
-#ifdef DEVELOP
-	char *tmpo = NULL;
-#endif
-	CoreChannelModeTable *tab = &corechannelmodetable[0];
-	CoreChannelModeTable foundat;
-	int found = 0;
-	int sent_mlock_warning = 0;
-	int checkrestr = 0, warnrestr = 1;
-	Cmode_t oldem;
-	paracount = 1;
-	*pcount = 0;
-
-	oldem = channel->mode.mode;
-	if (RESTRICT_CHANNELMODES && !ValidatePermissionsForPath("immune:restrict-channelmodes",client,NULL,channel,NULL)) /* "cache" this */
-		checkrestr = 1;
-
-	for (curchr = parv[0]; *curchr; curchr++)
-	{
-		switch (*curchr)
-		{
-			case '+':
-				what = MODE_ADD;
-				break;
-			case '-':
-				what = MODE_DEL;
-				break;
-			default:
-				if (MyUser(client) && channel->mode_lock && strchr(channel->mode_lock, *curchr) != NULL)
-				{
-					if (!IsOper(client) || find_server(SERVICES_NAME, NULL) ||
-					    !ValidatePermissionsForPath("channel:override:mlock",client,NULL,channel,NULL))
-					{
-						if (!sent_mlock_warning)
-						{
-							sendnumeric(client, ERR_MLOCKRESTRICTED, channel->name, *curchr, channel->mode_lock);
-							sent_mlock_warning++;
-						}
-						continue;
-					}
-				}
-				found = 0;
-				tab = &corechannelmodetable[0];
-				while ((tab->mode != 0x0) && found == 0)
-				{
-					if (tab->flag == *curchr)
-					{
-						found = 1;
-						foundat = *tab;
-					}
-					tab++;
-				}
-				if (found == 1)
-				{
-					modetype = foundat.mode;
-				} else {
-					/* Maybe in extmodes */
-					for (cm=channelmodes; cm; cm = cm->next)
-					{
-						if (cm->letter == *curchr)
-						{
-							found = 2;
-							break;
-						}
-					}
-				}
-				if (found == 0) /* Mode char unknown */
-				{
-					if (!MyUser(client))
-						paracount += paracount_for_chanmode_from_server(client, what, *curchr);
-					else
-						sendnumeric(client, ERR_UNKNOWNMODE, *curchr);
-					break;
-				}
-
-				if (checkrestr && strchr(RESTRICT_CHANNELMODES, *curchr))
-				{
-					if (warnrestr)
-					{
-						sendnotice(client, "Setting/removing of channelmode(s) '%s' has been disabled.",
-							RESTRICT_CHANNELMODES);
-						warnrestr = 0;
-					}
-					paracount += paracount_for_chanmode(what, *curchr);
-					break;
-				}
-
-				if ((paracount < parc) && parv[paracount])
-				{
-					strlcpy(argumentbuf, parv[paracount], sizeof(argumentbuf));
-					argument = argumentbuf;
-				} else {
-					argument = NULL;
-				}
-
-				if (found == 1)
-					paracount += do_mode_char_list_mode(channel, modetype, *curchr, argument, what, client, pcount, pvar);
-				else if (found == 2)
-					paracount += do_extmode_char(channel, cm, argument, what, client, pcount, pvar);
-				break;
-		} /* switch(*curchr) */
-	} /* for loop through mode letters */
-
-	mlm = make_mode_str(client, channel, oldem, *pcount, pvar);
-	return mlm;
-}
-
-/*
- * cmd_umode() added 15/10/91 By Darren Reed.
- * parv[1] - username to change mode for
- * parv[2] - modes to change
- */
-CMD_FUNC(_cmd_umode)
-{
-	Umode *um;
-	const char *m;
-	Client *acptr;
-	int what;
-	long oldumodes = 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)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "MODE");
-		return;
-	}
-
-	if (!(acptr = find_user(parv[1], NULL)))
-	{
-		if (MyConnect(client))
-		{
-			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		}
-		return;
-	}
-	if (acptr != client)
-	{
-		sendnumeric(client, ERR_USERSDONTMATCH);
-		return;
-	}
-
-	if (parc < 3)
-	{
-		sendnumeric(client, RPL_UMODEIS, get_usermode_string(client));
-		if (client->user->snomask)
-			sendnumeric(client, RPL_SNOMASK, client->user->snomask);
-		return;
-	}
-
-	userhost_save_current(client); /* save host, in case we do any +x/-x or similar */
-
-	oldumodes = client->umodes;
-
-	if (RESTRICT_USERMODES && MyUser(client) && !ValidatePermissionsForPath("immune:restrict-usermodes",client,NULL,NULL,NULL))
-		chk_restrict = 1;
-
-	if (client->user->snomask)
-		strlcpy(oldsnomask, client->user->snomask, sizeof(oldsnomask));
-
-	/*
-	 * parse mode change string(s)
-	 */
-	for (m = parv[2]; *m; m++)
-	{
-		if (chk_restrict && strchr(RESTRICT_USERMODES, *m))
-		{
-			if (!umode_restrict_err)
-			{
-				sendnotice(client, "Setting/removing of usermode(s) '%s' has been disabled.",
-					RESTRICT_USERMODES);
-				umode_restrict_err = 1;
-			}
-			continue;
-		}
-		switch (*m)
-		{
-			case '+':
-				what = MODE_ADD;
-				break;
-			case '-':
-				what = MODE_DEL;
-				break;
-				/* we may not get these,
-				 * but they shouldnt be in default
-				 */
-			case ' ':
-			case '\t':
-				break;
-			case 's':
-				if (what == MODE_DEL)
-				{
-					if (parc >= 4 && client->user->snomask)
-					{
-						set_snomask(client, parv[3]);
-						if (client->user->snomask == NULL)
-							goto def;
-						break;
-					} else {
-						set_snomask(client, NULL);
-						goto def;
-					}
-				}
-				if ((what == MODE_ADD) && IsOper(client))
-				{
-					if (parc < 4)
-						set_snomask(client, OPER_SNOMASKS);
-					else
-						set_snomask(client, parv[3]);
-					goto def;
-				}
-				break;
-			case 'o':
-			case 'O':
-				if (IsQuarantined(client->direction))
-				{
-					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;
-				}
-				/* A local user trying to set himself +o/+O is denied here.
-				 * A while later (outside this loop) it is handled as well (and +C, +N, etc too)
-				 * but we need to take care here too because it might cause problems
-				 * that's just asking for bugs! -- Syzop.
-				 */
-				if (MyUser(client) && (what == MODE_ADD)) /* Someone setting himself +o? Deny it. */
-					break;
-				goto def;
-			case 't':
-			case 'x':
-				/* set::anti-flood::vhost-flood */
-				if (MyUser(client))
-				{
-					if ((what == MODE_DEL) && !ValidatePermissionsForPath("immune:vhost-flood",client,NULL,NULL,NULL) &&
-							flood_limit_exceeded(client, FLD_VHOST))
-					{
-						/* Throttle... */
-						if (!modex_err)
-						{
-							sendnotice(client, "*** Setting -%c too fast. Please try again later.", *m);
-							modex_err = 1;
-						}
-						break;
-					}
-				}
-
-				switch (UHOST_ALLOWED)
-				{
-				case UHALLOW_ALWAYS:
-					goto def;
-				case UHALLOW_NEVER:
-					if (MyUser(client))
-					{
-						if (!modex_err)
-						{
-							sendnotice(client, "*** Setting %c%c is disabled",
-								what == MODE_ADD ? '+' : '-', *m);
-							modex_err = 1;
-						}
-						break;
-					}
-					goto def;
-				case UHALLOW_NOCHANS:
-					if (MyUser(client) && client->user->joined)
-					{
-						if (!modex_err)
-						{
-							sendnotice(client, "*** Setting %c%c can not be done while you are on channels",
-								what == MODE_ADD ? '+' : '-', *m);
-							modex_err = 1;
-						}
-						break;
-					}
-					goto def;
-				case UHALLOW_REJOIN:
-					/* Handled later */
-					goto def;
-				}
-				break;
-			default:
-			def:
-				for (um = usermodes; um; um = um->next)
-				{
-					if (um->letter == *m)
-					{
-						if (um->allowed && !um->allowed(client,what))
-							break;
-						if (what == MODE_ADD)
-							client->umodes |= um->mode;
-						else
-							client->umodes &= ~um->mode;
-						break;
-					}
-				}
-				if (!um && MyConnect(client) && !rpterror)
-				{
-					sendnumeric(client, ERR_UMODEUNKNOWNFLAG);
-					rpterror = 1;
-				}
-				break;
-		} /* switch */
-	} /* for */
-
-	/* Don't let non-ircops set ircop-only modes or snomasks */
-	if (!ValidatePermissionsForPath("self:opermodes",client,NULL,NULL,NULL))
-	{
-		if ((oldumodes & UMODE_OPER) && IsOper(client))
-		{
-			/* User is an oper but does not have the self:opermodes capability.
-			 * This only happens for heavily restricted IRCOps.
-			 * Fixes bug https://bugs.unrealircd.org/view.php?id=5130
-			 */
-			int i;
-
-			/* MODES */
-			for (um = usermodes; um; um = um->next)
-			{
-				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.
-					 */
-					if ((client->umodes & um->mode) && !(oldumodes & um->mode))
-						client->umodes &= ~um->mode; /* remove */
-				}
-			}
-
-			/* SNOMASKS: user can delete existing but not add new ones */
-			if (client->user->snomask)
-			{
-				char rerun;
-				do {
-					char *p;
-
-					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: */
-			remove_oper_privileges(client, 0);
-		}
-	}
-
-	if (MyUser(client) && (client->umodes & UMODE_SECURE) && !IsSecure(client))
-		client->umodes &= ~UMODE_SECURE;
-
-	/* -x translates to -xt (if applicable) */
-	if ((oldumodes & UMODE_HIDE) && !IsHidden(client))
-		client->umodes &= ~UMODE_SETHOST;
-
-	/* Vhost unset = unset some other data as well */
-	if ((oldumodes & UMODE_SETHOST) && !IsSetHost(client))
-	{
-		swhois_delete(client, "vhost", "*", &me, NULL);
-	}
-
-	/* +x or -t+x */
-	if ((IsHidden(client) && !(oldumodes & UMODE_HIDE)) ||
-	    ((oldumodes & UMODE_SETHOST) && !IsSetHost(client) && IsHidden(client)))
-	{
-		if (!dontspread)
-			sendto_server(client, PROTO_VHP, 0, NULL, ":%s SETHOST :%s",
-				client->name, client->user->virthost);
-
-		/* Set the vhost */
-		safe_strdup(client->user->virthost, client->user->cloakedhost);
-
-		/* Notify */
-		userhost_changed(client);
-	}
-
-	/* -x */
-	if (!IsHidden(client) && (oldumodes & UMODE_HIDE))
-	{
-		/* (Re)create the cloaked virthost, because it will be used
-		 * for ban-checking... free+recreate here because it could have
-		 * been a vhost for example. -- Syzop
-		 */
-		safe_strdup(client->user->virthost, client->user->cloakedhost);
-
-		/* Notify */
-		userhost_changed(client);
-	}
-	/*
-	 * If I understand what this code is doing correctly...
-	 * If the user WAS an operator and has now set themselves -o/-O
-	 * then remove their access, d'oh!
-	 * In order to allow opers to do stuff like go +o, +h, -o and
-	 * remain +h, I moved this code below those checks. It should be
-	 * O.K. The above code just does normal access flag checks. This
-	 * only changes the operflag access level.  -Cabal95
-	 */
-	if ((oldumodes & UMODE_OPER) && !IsOper(client) && MyConnect(client))
-	{
-		list_del(&client->special_node);
-		if (MyUser(client))
-			RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL);
-		remove_oper_privileges(client, 0);
-	}
-
-	if (!(oldumodes & UMODE_OPER) && IsOper(client))
-		irccounts.operators++;
-
-	/* deal with opercounts and stuff */
-	if ((oldumodes & UMODE_OPER) && !IsOper(client))
-	{
-		irccounts.operators--;
-		VERIFY_OPERCOUNT(client, "umode1");
-	} else /* YES this 'else' must be here, otherwise we can decrease twice. fixes opercount bug. */
-	if (!(oldumodes & UMODE_HIDEOPER) && IsHideOper(client))
-	{
-		irccounts.operators--;
-		VERIFY_OPERCOUNT(client, "umode2");
-	}
-	/* end of dealing with opercounts */
-
-	if ((oldumodes & UMODE_HIDEOPER) && !IsHideOper(client))
-	{
-		irccounts.operators++;
-	}
-	if (!(oldumodes & UMODE_INVISIBLE) && IsInvisible(client))
-		irccounts.invisible++;
-	if ((oldumodes & UMODE_INVISIBLE) && !IsInvisible(client))
-		irccounts.invisible--;
-
-	if (MyConnect(client) && !IsOper(client))
-		remove_oper_privileges(client, 0);
-
-	/*
-	 * compare new flags with old flags and send string which
-	 * will cause servers to update correctly.
-	 */
-	if (oldumodes != client->umodes)
-		RunHook(HOOKTYPE_UMODE_CHANGE, client, oldumodes, client->umodes);
-	if (dontspread == 0)
-		send_umode_out(client, 1, oldumodes);
-
-	if (MyConnect(client) && client->user->snomask && strcmp(oldsnomask, client->user->snomask))
-		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
-}
-
-CMD_FUNC(cmd_mlock)
-{
-	Channel *channel = NULL;
-	time_t t;
-
-	if ((parc < 3) || BadPtr(parv[2]))
-		return;
-
-	t = (time_t) atol(parv[1]);
-
-	/* Now, try to find the channel in question */
-	channel = find_channel(parv[2]);
-	if (!channel)
-		return;
-
-	/* Senders' Channel t is higher, drop it. */
-	if (t > channel->creationtime)
-		return;
-
-	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, MessageTag *mtags, const char *modes, const 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, mtags, 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/module.def b/src/modules/module.def
@@ -1,5 +0,0 @@
-EXPORTS
-	Mod_Header
-	Mod_Init
-	Mod_Load
-	Mod_Unload
diff --git a/src/modules/monitor.c b/src/modules/monitor.c
@@ -1,232 +0,0 @@
-/*
- *   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
@@ -1,123 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/motd.c
- *   (C) 2005 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_motd);
-
-#define MSG_MOTD 	"MOTD"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"motd",
-	"5.0",
-	"command /motd", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_MOTD, cmd_motd, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * Heavily modified from the ircu cmd_motd by codemastr
- * Also svsmotd support added
- */
-CMD_FUNC(cmd_motd)
-{
-	ConfigItem_tld *tld;
-	MOTDFile *themotd;
-	MOTDLine *motdline;
-	int  svsnofile = 0;
-
-	if (IsServer(client))
-		return;
-
-	if (hunt_server(client, recv_mtags, "MOTD", 1, parc, parv) != HUNTED_ISME)
-	{
-		if (MyUser(client))
-			add_fake_lag(client, 15000);
-		return;
-	}
-
-	tld = find_tld(client);
-
-	if (tld && tld->motd.lines)
-		themotd = &tld->motd;
-	else
-		themotd = &motd;
-
-	if (themotd == NULL || themotd->lines == NULL)
-	{
-		sendnumeric(client, ERR_NOMOTD);
-		svsnofile = 1;
-		goto svsmotd;
-	}
-
-	sendnumeric(client, RPL_MOTDSTART, me.name);
-
-	/* tm_year should be zero only if the struct is zero-ed */
-	if (themotd && themotd->lines && themotd->last_modified.tm_year)
-	{
-		sendnumericfmt(client, RPL_MOTD, ":- %.04d-%.02d-%.02d %.02d:%02d",
-			themotd->last_modified.tm_year + 1900,
-			themotd->last_modified.tm_mon + 1,
-			themotd->last_modified.tm_mday,
-			themotd->last_modified.tm_hour,
-			themotd->last_modified.tm_min);
-	}
-
-	motdline = NULL;
-	if (themotd)
-		motdline = themotd->lines;
-	while (motdline)
-	{
-		sendnumeric(client, RPL_MOTD,
-			motdline->line);
-		motdline = motdline->next;
-	}
-	svsmotd:
-
-	motdline = svsmotd.lines;
-	while (motdline)
-	{
-		sendnumeric(client, RPL_MOTD,
-			motdline->line);
-		motdline = motdline->next;
-	}
-	if (svsnofile == 0)
-		sendnumeric(client, RPL_ENDOFMOTD);
-}
diff --git a/src/modules/names.c b/src/modules/names.c
@@ -1,190 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/names.c
- *   (C) 2006 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_names);
-
-long CAP_MULTI_PREFIX = 0L;
-long CAP_USERHOST_IN_NAMES = 0L;
-
-#define MSG_NAMES 	"NAMES"
-
-ModuleHeader MOD_HEADER
-  = {
-	"names",
-	"5.0",
-	"command /names", 
-	"UnrealIRCd Team",
-	"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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/************************************************************************
- * cmd_names() - Added by Jto 27 Apr 1989
- * 12 Feb 2000 - geesh, time for a rewrite -lucas
- ************************************************************************/
-
-/*
-** cmd_names
-**	parv[1] = channel
-*/
-#define TRUNCATED_NAMES 64
-CMD_FUNC(cmd_names)
-{
-	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;
-	Client *acptr;
-	int member;
-	Member *cm;
-	int idx, flag = 1, spos;
-	const char *para = parv[1], *s;
-	char nuhBuffer[NICKLEN+USERLEN+HOSTLEN+3];
-	char buf[BUFSIZE];
-
-	if (parc < 2 || !MyConnect(client))
-	{
-		sendnumeric(client, RPL_ENDOFNAMES, "*");
-		return;
-	}
-
-	for (s = para; *s; s++)
-	{
-		if (*s == ',')
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, s+1, 1, "NAMES");
-			return;
-		}
-	}
-
-	channel = find_channel(para);
-
-	if (!channel || (!ShowChannel(client, channel) && !ValidatePermissionsForPath("channel:see:names:secret",client,NULL,channel,NULL)))
-	{
-		sendnumeric(client, RPL_ENDOFNAMES, para);
-		return;
-	}
-
-	/* 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))
-		buf[0] = '@';
-	else
-		buf[0] = '*';
-
-	idx = 1;
-	buf[idx++] = ' ';
-	for (s = channel->name; *s; s++)
-		buf[idx++] = *s;
-	buf[idx++] = ' ';
-	buf[idx++] = ':';
-
-	/* If we go through the following loop and never add anything,
-	   we need this to be empty, otherwise spurious things from the
-	   LAST /names call get stuck in there.. - lucas */
-	buf[idx] = '\0';
-
-	spos = idx;		/* starting point in buffer for names! */
-
-	for (cm = channel->members; cm; cm = cm->next)
-	{
-		acptr = cm->client;
-		if (IsInvisible(acptr) && !member && !ValidatePermissionsForPath("channel:see:names:invisible",client,acptr,channel,NULL))
-			continue;
-
-		if (!user_can_see_member(client, acptr, channel))
-			continue; /* invisible (eg: due to delayjoin) */
-
-		if (!multiprefix)
-		{
-			/* 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) */
-			strcpy(&buf[idx], modes_to_prefix(cm->member_modes));
-			idx += strlen(&buf[idx]);
-		}
-
-		if (!uhnames) {
-			s = acptr->name;
-		} else {
-			strlcpy(nuhBuffer,
-			        make_nick_user_host(acptr->name, acptr->user->username, GetHost(acptr)),
-				bufLen + 1);
-			s = nuhBuffer;
-		}
-		/* 's' is intialized above to point to either acptr->name (normal),
-		 * or to nuhBuffer (for UHNAMES).
-		 */
-		for (; *s; s++)
-			buf[idx++] = *s;
-		if (cm->next)
-			buf[idx++] = ' ';
-		buf[idx] = '\0';
-		flag = 1;
-		if (mlen + idx + bufLen + MEMBERMODESLEN >= BUFSIZE - 1)
-		{
-			sendnumeric(client, RPL_NAMREPLY, buf);
-			idx = spos;
-			flag = 0;
-		}
-	}
-
-	if (flag)
-		sendnumeric(client, RPL_NAMREPLY, buf);
-
-	sendnumeric(client, RPL_ENDOFNAMES, para);
-}
diff --git a/src/modules/netinfo.c b/src/modules/netinfo.c
@@ -1,141 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_netinfo);
-
-#define MSG_NETINFO 	"NETINFO"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"netinfo",
-	"5.0",
-	"command /netinfo", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_NETINFO, cmd_netinfo, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** NETINFO: Share configuration settings with directly linked server.
- * Originally written by Stskeeps
- *
- * Technical documentation:
- * https://www.unrealircd.org/docs/Server_protocol:NETINFO_command
- *
- * parv[1] = max global count
- * parv[2] = time of end sync
- * parv[3] = unreal protocol using (numeric)
- * parv[4] = cloak key check (> u2302)
- * parv[5] = free(**)
- * parv[6] = free(**)
- * parv[7] = free(**)
- * parv[8] = network name
- */
-CMD_FUNC(cmd_netinfo)
-{
-	long 		lmax;
-	long 		endsync, protocol;
-	char		buf[512];
-
-	if (parc < 9)
-		return;
-
-	/* Only allow from directly connected servers */
-	if (!MyConnect(client))
-		return;
-
-	if (IsNetInfo(client))
-	{
-		unreal_log(ULOG_WARNING, "link", "NETINFO_ALREADY_RECEIVED", client,
-		           "Got NETINFO from server $client, but we already received it earlier!");
-		return;
-	}
-
-	/* is a long therefore not ATOI */
-	lmax = atol(parv[1]);
-	endsync = atol(parv[2]);
-	protocol = atol(parv[3]);
-
-	/* max global count != max_global_count --sts */
-	if (lmax > irccounts.global_max)
-	{
-		irccounts.global_max = lmax;
-		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));
-	}
-
-	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(NETWORK_NAME, parv[8]) == 0))
-	{
-		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))
-	{
-		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_KEY_CHECKSUM, sizeof(buf));
-	if (*parv[4] != '*' && strcmp(buf, parv[4]))
-	{
-		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
@@ -1,1321 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/nick.c
- *   (C) 1999-2005 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
-  = {
-	"nick",
-	"5.0",
-	"command /nick",
-	"UnrealIRCd Team",
-	"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 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);
-void nick_collision(Client *cptr, const char *newnick, const char *newid, Client *new, Client *existing, int type);
-int AllowClient(Client *client);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAdd(modinfo->handle, EFUNC_REGISTER_USER, _register_user);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "NICK", cmd_nick, MAXPARA, CMD_USER|CMD_SERVER|CMD_UNREGISTERED);
-	CommandAdd(modinfo->handle, "UID", cmd_uid, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Hmm.. don't we already have such a function? */
-void set_user_modes_dont_spread(Client *client, const char *umode)
-{
-	const char *args[4];
-
-	args[0] = client->name;
-	args[1] = client->name;
-	args[2] = umode;
-	args[3] = NULL;
-
-	dontspread = 1;
-	do_cmd(client, NULL, "MODE", 3, args);
-	dontspread = 0;
-}
-
-/** 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;
-	MessageTag *mtags = NULL;
-
-	/* '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) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
-	{
-		ircstats.is_kill++;
-		unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client,
-		           "Server link $server tried to change '$client' to bad nick '$nick' -- rejected.",
-		           log_data_string("nick", parv[1]),
-		           log_data_client("server", client->uplink));
-		mtags = NULL;
-		new_message(client, NULL, &mtags);
-		sendto_one(client, mtags, ":%s KILL %s :Illegal nick name", me.id, client->id);
-		SetKilled(client);
-		exit_client(client, mtags, "Illegal nick name");
-		free_message_tags(mtags);
-		mtags = NULL;
-		return;
-	}
-
-	/* Check Q-lines / ban nick */
-	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)) && !ishold)
-	{
-		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)))
-	{
-		/* If existing nick is still in handshake, kill it */
-		if (IsUnknown(acptr) && MyConnect(acptr))
-		{
-			SetKilled(acptr);
-			exit_client(acptr, NULL, "Overridden");
-		} else
-		if (acptr == client)
-		{
-			/* 100% identical? Must be a bug, but ok */
-			if (!strcmp(acptr->name, nick))
-				return;
-			/* Allows change of case in their nick */
-			removemoder = 0; /* don't set the user -r */
-		} else
-		{
-			/*
-			   ** A NICK change has collided (e.g. message type ":old NICK new").
-			 */
-			differ = (mycmp(acptr->user->username, client->user->username) ||
-			          mycmp(acptr->user->realhost, client->user->realhost));
-
-			if (!(parc > 2) || lastnick == acptr->lastnick)
-			{
-				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], 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], client->id, client, acptr, NICKCOL_EXISTING_WON);
-				return; /* their user lost, ignore the NICK */
-			} else
-			{
-				return;		/* just in case */
-			}
-		}
-	}
-
-	mtags = NULL;
-
-	if (!IsULine(client))
-	{
-		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);
-	RunHook(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
-	client->lastnick = lastnick ? lastnick : TStime();
-	add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE);
-	sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
-	    client->id, nick, (long long)client->lastnick);
-	sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
-	if (removemoder)
-		client->umodes &= ~UMODE_REGNICK;
-
-	/* Finally set new nick name. */
-	del_from_client_hash_table(client->name, client);
-	strlcpy(client->name, nick, sizeof(client->name));
-	add_to_client_hash_table(nick, client);
-
-	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];
-	char oldnick[NICKLEN + 1];
-	char descbuf[BUFSIZE];
-	Membership *mp;
-	int newuser = 0;
-	unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
-	Hook *h;
-	int ret;
-
-	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)
-	{
-		snprintf(descbuf, sizeof descbuf, "A minimum length of %d chars is required", iConf.min_nick_length);
-		sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], descbuf);
-		return;
-	}
-
-	/* Enforce maximum nick length */
-	strlcpy(nick, parv[1], iConf.nick_length + 1);
-
-	/* Check if this is a valid nick name */
-	if (!do_nick_name(nick))
-	{
-		sendnumeric(client, ERR_ERRONEUSNICKNAME, parv[1], "Illegal characters");
-		return;
-	}
-
-	/* Check for collisions / in use */
-	if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
-	{
-		sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, "Reserved for internal IRCd purposes");
-		return;
-	}
-
-	if (MyUser(client))
-	{
-		/* Local client changing nick: check spamfilter */
-		spamfilter_build_user_string(spamfilter_user, nick, client);
-		if (match_spamfilter(client, spamfilter_user, SPAMF_USER, "NICK", NULL, 0, NULL))
-			return;
-	}
-
-	/* Check Q-lines / ban nick */
-	if (!IsULine(client) && (tklban = find_qline(client, nick, &ishold)))
-	{
-		if (ishold)
-		{
-			sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, tklban->ptr.nameban->reason);
-			return;
-		}
-		if (!ValidatePermissionsForPath("immune:server-ban:ban-nick",client,NULL,NULL,nick))
-		{
-			add_fake_lag(client, 4000); /* lag them up */
-			sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, 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 */
-	}
-
-	if (!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
-		add_fake_lag(client, 3000);
-
-	if ((acptr = find_client(nick, NULL)))
-	{
-		/* Shouldn't be possible since dot is disallowed: */
-		if (IsServer(acptr))
-		{
-			sendnumeric(client, ERR_NICKNAMEINUSE, nick);
-			return;
-		}
-		if (acptr == client)
-		{
-			/* New nick is exactly the same as the old nick? */
-			if (!strcmp(acptr->name, nick))
-				return;
-			/* Changing cAsE */
-			removemoder = 0;
-		} else
-		/* Collision with a nick of a session that is still in handshake */
-		if (IsUnknown(acptr) && MyConnect(acptr))
-		{
-			/* Kill the other connection that is still in progress */
-			SetKilled(acptr);
-			exit_client(acptr, NULL, "Overridden");
-		} else
-		{
-			sendnumeric(client, ERR_NICKNAMEINUSE, nick);
-			return;	/* NICK message ignored */
-		}
-	}
-
-	/* 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)
-		{
-			/*
-			 * Client setting NICK the first time.
-			 *
-			 * Generate a random string for them to pong with.
-			 */
-			client->local->nospoof = getrandom32();
-
-			sendto_one(client, NULL, "PING :%X", client->local->nospoof);
-		}
-
-		/* Copy password to the passwd field if it's given after NICK */
-		if ((parc > 2) && (strlen(parv[2]) <= PASSWDLEN))
-			safe_strdup(client->local->passwd, parv[2]);
-
-		/* This had to be copied here to avoid problems.. */
-		strlcpy(client->name, nick, sizeof(client->name));
-
-		/* Let's see if we can get online now... */
-		if (is_handshake_finished(client))
-		{
-			/* Send a CTCP VERSION */
-			if (!iConf.ping_cookie && USE_BAN_VERSION && MyConnect(client))
-				sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1", me.name, nick);
-
-			client->lastnick = TStime();
-			if (!register_user(client))
-			{
-				if (IsDead(client))
-					return;
-				/* ..otherwise.. fallthrough so we run the same code
-				 * as in case of !is_handshake_finished()
-				 */
-			} else {
-				/* New user! */
-				strlcpy(nick, client->name, sizeof(nick)); /* don't ask, but I need this. do not remove! -- Syzop */
-			}
-		}
-	} else
-	if (MyUser(client))
-	{
-		MessageTag *mtags = NULL;
-		int ret;
-
-		/* Existing client nick-changing */
-
-		/*
-		   ** If the client belongs to me, then check to see
-		   ** if client is currently on any channels where it
-		   ** is currently banned.  If so, do not allow the nick
-		   ** change to occur.
-		   ** Also set 'lastnick' to current time, if changed.
-		 */
-		for (mp = client->user->channel; mp; mp = mp->next)
-		{
-			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->name);
-				return;
-			}
-			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->name);
-				return;
-			}
-
-			for (h = Hooks[HOOKTYPE_CHAN_PERMIT_NICK_CHANGE]; h; h = h->next)
-			{
-				ret = (*(h->func.intfunc))(client,mp->channel);
-				if (ret != HOOK_CONTINUE)
-					break;
-			}
-
-			if (ret == HOOK_DENY)
-			{
-				sendnumeric(client, ERR_NONICKCHANGE, mp->channel->name);
-				return;
-			}
-		}
-
-		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);
-		RunHook(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
-		add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE);
-		client->lastnick = TStime(); /* needs to be done AFTER add_history() */
-		sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
-		    client->id, nick, (long long)client->lastnick);
-		sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
-		sendto_one(client, mtags, ":%s NICK :%s", client->name, nick);
-		free_message_tags(mtags);
-		if (removemoder)
-			client->umodes &= ~UMODE_REGNICK;
-	} else
-	{
-		/* Someone changing nicks in the pre-registered phase */
-	}
-
-	del_from_client_hash_table(client->name, client);
-
-	strlcpy(client->name, nick, sizeof(client->name));
-	add_to_client_hash_table(nick, client);
-
-	/* update fdlist --nenolod */
-	snprintf(descbuf, sizeof(descbuf), "Client: %s", nick);
-	fd_desc(client->local->fd, descbuf);
-
-	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);
-}
-
-/*
-** cmd_uid
-**	parv[1] = nickname
-**      parv[2] = hopcount
-**      parv[3] = timestamp
-**      parv[4] = username
-**      parv[5] = hostname
-**      parv[6] = UID
-**	parv[7] = account name (SVID)
-**      parv[8] = umodes
-**	parv[9] = virthost, * if none
-**	parv[10] = cloaked host, * if none
-**	parv[11] = ip
-**	parv[12] = info
-**
-** Technical documentation is available at:
-** https://www.unrealircd.org/docs/Server_protocol:UID_command
-*/
-CMD_FUNC(cmd_uid)
-{
-	TKL *tklban;
-	int ishold;
-	Client *acptr, *serv = NULL;
-	Client *acptrs;
-	char nick[NICKLEN + 1];
-	char buf[BUFSIZE];
-	long lastnick = 0;
-	int differ = 1;
-	const char *hostname, *username, *sstamp, *umodes, *virthost, *ip_raw, *realname;
-	const char *ip = NULL;
-
-	if (parc < 13)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "UID");
-		return;
-	}
-
-	/* It's not just the directly attached client which must be a
-	 * server. The source itself needs to be a server.
-	 */
-	if (!IsServer(client))
-	{
-		sendnumeric(client, ERR_NOTFORUSERS, "UID");
-		return;
-	}
-
-	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) || !strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
-	{
-		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++;
-		/* 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;
-	}
-
-	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 :Bad UID", me.id, parv[6]);
-		return;
-	}
-
-	if (!valid_host(hostname, 0))
-	{
-		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 (strcmp(virthost, "*") && !valid_host(virthost, 0))
-	{
-		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)))
-		{
-			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;
-		}
-	}
-
-	/* Kill quarantined opers early... */
-	if (IsQuarantined(client->direction) && strchr(parv[8], 'o'))
-	{
-		ircstats.is_kill++;
-		/* 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)
-	{
-		/* If there's a collision with a user that is still in handshake, on our side,
-		 * then we can just kill our client and continue.
-		 */
-		if (MyConnect(acptr) && IsUnknown(acptr))
-		{
-			SetKilled(acptr);
-			exit_client(acptr, NULL, "Overridden");
-			goto nickkill2done;
-		}
-
-		lastnick = atol(parv[3]);
-		differ = (mycmp(acptr->user->username, parv[4]) || mycmp(acptr->user->realhost, parv[5]));
-
-		if (acptr->lastnick == lastnick)
-		{
-			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EQUAL);
-			return;	/* We killed both users, now stop the process. */
-		}
-
-		if ((differ && (acptr->lastnick > lastnick)) ||
-		    (!differ && (acptr->lastnick < lastnick)) || acptr->direction == client->direction)	/* we missed a QUIT somewhere ? */
-		{
-			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_NEW_WON);
-			/* We got rid of the "wrong" user. Introduce the correct one. */
-			/* ^^ hmm.. why was this absent in nenolod's code, resulting in a 'return 0'? seems wrong. */
-			goto nickkill2done;
-		}
-
-		if ((differ && (acptr->lastnick < lastnick)) || (!differ && (acptr->lastnick > lastnick)))
-		{
-			nick_collision(client, parv[1], parv[6], NULL, acptr, NICKCOL_EXISTING_WON);
-			return;	/* Ignore the NICK */
-		}
-		return; /* just in case */
-	}
-
-nickkill2done:
-	/* Proceed with introducing the new client, change source (replace client) */
-
-	serv = client;
-	client = make_client(serv->direction, serv);
-	strlcpy(client->id, parv[6], IDLEN);
-	add_client_to_list(client);
-	add_to_id_hash_table(client->id, client);
-	client->lastnick = atol(parv[3]);
-	strlcpy(client->name, nick, NICKLEN+1);
-	add_to_client_hash_table(nick, client);
-
-	make_user(client);
-
-	/* Note that cloaked host aka parv[10] is unused */
-
-	client->user->server = find_or_add(client->uplink->name);
-	strlcpy(client->user->realhost, hostname, sizeof(client->user->realhost));
-	if (ip)
-		safe_strdup(client->ip, ip);
-
-	if (*sstamp != '*')
-		strlcpy(client->user->account, sstamp, sizeof(client->user->account));
-
-	strlcpy(client->info, realname, sizeof(client->info));
-	strlcpy(client->user->username, username, USERLEN + 1);
-	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);
-
-	/* 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
-		 * since that can only happen for local users.
-		 */
-	}
-
-	RunHook(HOOKTYPE_REMOTE_CONNECT, client);
-
-	if (!IsULine(serv) && IsSynched(serv))
-	{
-		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 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))
-	{
-		CALL_CMD_FUNC(cmd_nick_local);
-	} 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
-	{
-		CALL_CMD_FUNC(cmd_nick_remote);
-	}
-}
-
-/** Welcome the user on IRC.
- * Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything...
- */
-void welcome_user(Client *client, TKL *viruschan_tkl)
-{
-	int i;
-	ConfigItem_tld *tld;
-	char buf[BUFSIZE];
-
-	/* 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();
-
-	RunHook(HOOKTYPE_WELCOME, client, 0);
-	sendnumeric(client, RPL_WELCOME, NETWORK_NAME, client->name, client->user->username, client->user->realhost);
-
-	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))
-	{
-		sendnumeric(client, RPL_HOSTHIDDEN, client->user->virthost);
-		RunHook(HOOKTYPE_WELCOME, client, 396);
-	}
-
-	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));
-		}
-	}
-
-	{
-		const char *parv[2];
-		parv[0] = NULL;
-		parv[1] = NULL;
-		do_cmd(client, NULL, "LUSERS", 1, parv);
-		if (IsDead(client))
-			return;
-	}
-
-	RunHook(HOOKTYPE_WELCOME, client, 266);
-
-	short_motd(client);
-
-	RunHook(HOOKTYPE_WELCOME, client, 376);
-
-#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
-
-	if (client->umodes & UMODE_INVISIBLE)
-		irccounts.invisible++;
-
-	build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf);
-
-	sendto_serv_butone_nickcmd(client->direction, NULL, client, (*buf == '\0' ? "+" : buf));
-
-	broadcast_moddata_client(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);
-
-	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));
-
-	RunHook(HOOKTYPE_LOCAL_CONNECT, client);
-
-	/* 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();
-
-	RunHook(HOOKTYPE_WELCOME, client, 999);
-
-	/* 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;
-	}
-
-	/* Force the user to join the given chans -- codemastr */
-	tld = find_tld(client);
-
-	if (tld && !BadPtr(tld->channel))
-	{
-		char *chans = strdup(tld->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;
-	}
-}
-
-/** Make a valid client->user->username, or try to anyway.
- * @param client	The client to check
- * @param noident	Whether we should ignore the first ~ or not
- * @returns 1 if the username is acceptable, 0 if not.
- * @note This function will modify client->user->username to make it valid.
- *       Only if there are zero valid characters it will return 0.
- * @note There is also valid_username() in src/misc.c
- */
-int make_valid_username(Client *client, int noident)
-{
-	static char stripuser[USERLEN + 1];
-	char *i;
-	char *o = stripuser;
-	char filtered = 0; /* any changes? */
-
-	*stripuser = '\0';
-
-	for (i = client->user->username + noident; *i; i++)
-	{
-		if (isallowed(*i))
-			*o++ = *i;
-		else
-			filtered = 1;
-	}
-	*o = '\0';
-
-	if (filtered == 0)
-		return 1; /* No change needed, all good */
-
-	if (*stripuser == '\0')
-		return 0; /* Zero valid characters, reject it */
-
-	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 */
-}
-
-/** 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 (!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)
-		{
-			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);
-	}
-
-	/* 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));
-
-	/* Check allow { } blocks... */
-	if (!AllowClient(client))
-	{
-		ircstats.is_ref++;
-		/* For safety, we have an extra kill here */
-		if (!IsDead(client))
-			exit_client(client, NULL, "Rejected");
-		return 0;
-	}
-
-	if (IsUseIdent(client))
-	{
-		if (IsIdentSuccess(client))
-		{
-			/* ident succeeded: overwite client->user->username with the ident reply */
-			strlcpy(client->user->username, client->ident, sizeof(client->user->username));
-		} else
-		if (IDENT_CHECK)
-		{
-			/* 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;
-		}
-	}
-
-	/* Now validate the username. This may alter client->user->username
-	 * or reject it completely.
-	 */
-	if (!make_valid_username(client, noident))
-	{
-		exit_client(client, NULL, "Hostile username. Please use only 0-9 a-z A-Z _ - and . in your username.");
-		return 0;
-	}
-
-	/* 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);
-
-	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;
-		}
-	}
-
-	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)
-			{
-				/* 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 (ret == HOOK_ALLOW)
-			break;
-	}
-
-	SetUser(client);
-
-	make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
-
-	/* client->user->virthost should never be empty */
-	if (!IsSetHost(client) || !client->user->virthost)
-		safe_strdup(client->user->virthost, client->user->cloakedhost);
-
-	snprintf(descbuf, sizeof descbuf, "Client: %s", client->name);
-	fd_desc(client->local->fd, descbuf);
-
-	/* Move user from unknown list to client list */
-	list_move(&client->lclient_node, &lclient_list);
-
-	/* Update counts */
-	irccounts.unknown--;
-	irccounts.clients++;
-	irccounts.me_clients++;
-	if (client->uplink && client->uplink->server)
-		client->uplink->server->users++;
-
-	if (IsSecure(client))
-	{
-		client->umodes |= UMODE_SECURE;
-		RunHook(HOOKTYPE_SECURE_CONNECT, client);
-	}
-
-	safe_free(client->local->passwd);
-
-	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)));
-
-	/* Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything... */
-	welcome_user(client, savetkl);
-
-	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, const char *newnick, const char *newid, Client *new, Client *existing, int type)
-{
-	char comment[512];
-	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;
-	if (type == NICKCOL_EXISTING_WON)
-		snprintf(comment, sizeof(comment), "Nick collision: %s <- %s", new_server, existing_server);
-	else if (type == NICKCOL_NEW_WON)
-		snprintf(comment, sizeof(comment), "Nick collision: %s <- %s", existing_server, new_server);
-	else
-		snprintf(comment, sizeof(comment), "Nick collision: %s <-> %s", existing_server, new_server);
-
-	/* We only care about the direction from this point, not about the originally sending server */
-	cptr = cptr->direction;
-
-	if ((type == NICKCOL_EQUAL) || (type == NICKCOL_EXISTING_WON))
-	{
-		/* Kill 'new':
-		 * - 'new' is known by the cptr-side as 'newnick' already
-		 * - if not nick-changing then the other servers don't know this user
-		 * - if nick-changing, then the the other servers know the user as new->name
-		 */
-
-		/* cptr case first... this side knows the user by newnick/newid */
-		/* SID server can kill 'new' by ID */
-		sendto_one(cptr, NULL, ":%s KILL %s :%s", me.id, newid, comment);
-
-		/* non-cptr case... only necessary if nick-changing. */
-		if (new)
-		{
-			MessageTag *mtags = NULL;
-
-			new_message(new, NULL, &mtags);
-
-			/* non-cptr side knows this user by their old nick name */
-			sendto_server(cptr, 0, 0, mtags, ":%s KILL %s :%s", me.id, new->id, comment);
-
-			/* Exit the client */
-			ircstats.is_kill++;
-			SetKilled(new);
-			exit_client(new, mtags, comment);
-
-			free_message_tags(mtags);
-		}
-	}
-
-	if ((type == NICKCOL_EQUAL) || (type == NICKCOL_NEW_WON))
-	{
-		MessageTag *mtags = NULL;
-
-		new_message(existing, NULL, &mtags);
-
-		/* Now let's kill 'existing' */
-		sendto_server(NULL, 0, 0, mtags, ":%s KILL %s :%s", me.id, existing->id, comment);
-
-		/* Exit the client */
-		ircstats.is_kill++;
-		SetKilled(existing);
-		exit_client(existing, mtags, comment);
-
-		free_message_tags(mtags);
-	}
-}
-
-/** Returns 1 if allow::maxperip is exceeded by 'client' */
-int exceeds_maxperip(Client *client, ConfigItem_allow *aconf)
-{
-	Client *acptr;
-	int local_cnt = 1;
-	int global_cnt = 1;
-
-	if (find_tkl_exception(TKL_MAXPERIP, client))
-		return 0; /* exempt */
-
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (IsUser(acptr) && !strcmp(GetIP(acptr), GetIP(client)))
-		{
-			if (MyUser(acptr))
-			{
-				local_cnt++;
-				if (local_cnt > aconf->maxperip)
-					return 1;
-			}
-			global_cnt++;
-			if (global_cnt > aconf->global_maxperip)
-				return 1;
-		}
-	}
-	return 0;
-}
-
-/** Allow or reject the client based on allow { } blocks and all other restrictions.
- * @param client     Client to check (local)
- * @param username   Username, for some reason...
- * @returns 1 if OK, 0 if client is rejected (likely killed too)
- */
-int AllowClient(Client *client)
-{
-	static char sockhost[HOSTLEN + 1];
-	int i;
-	ConfigItem_allow *aconf;
-	char *hname;
-	static char uhost[HOSTLEN + USERLEN + 3];
-	static char fullname[HOSTLEN + 1];
-
-	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
-	{
-		exit_client(client, NULL, iConf.plaintext_policy_user_message->line);
-		return 0;
-	}
-
-	if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
-	{
-		const char *msg = outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client);
-		exit_client(client, NULL, msg);
-		return 0;
-	}
-
-	for (aconf = conf_allow; aconf; aconf = aconf->next)
-	{
-		if (aconf->flags.tls && !IsSecure(client))
-			continue;
-
-		if (!user_allowed_by_security_group(client, aconf->match))
-			continue;
-
-		/* Check authentication */
-		if (aconf->auth && !Auth_Check(client, aconf->auth, client->local->passwd))
-		{
-			/* Incorrect password/authentication - but was is it required? */
-			if (aconf->flags.reject_on_auth_failure)
-			{
-				exit_client(client, NULL, iConf.reject_message_unauthorized);
-				return 0;
-			} else {
-				continue; /* Continue (this is the default behavior) */
-			}
-		}
-
-		if (!aconf->flags.noident)
-			SetUseIdent(client);
-
-		if (aconf->flags.useip)
-			set_sockhost(client, GetIP(client));
-
-		if (exceeds_maxperip(client, aconf))
-		{
-			/* Already got too many with that ip# */
-			exit_client(client, NULL, iConf.reject_message_too_many_connections);
-			return 0;
-		}
-
-		if (!((aconf->class->clients + 1) > aconf->class->maxclients))
-		{
-			client->local->class = aconf->class;
-			client->local->class->clients++;
-		}
-		else
-		{
-			/* Class is full */
-			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;
-		}
-		return 1;
-	}
-	/* User did not match any allow { } blocks: */
-	exit_client(client, NULL, iConf.reject_message_unauthorized);
-	return 0;
-}
diff --git a/src/modules/nocodes.c b/src/modules/nocodes.c
@@ -1,102 +0,0 @@
-/*
- * "No Codes", a very simple (but often requested) module written by Syzop.
- * This module will strip messages with bold/underline/reverse if chanmode
- * +S is set, and block them if +c is set.
- *
- *   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
-= {
-	"nocodes",	/* Name of module */
-	"1.3", /* Version */
-	"Strip/block color/bold/underline/italic/reverse - by Syzop", /* Short description of module */
-	"UnrealIRCd Team", /* Author */
-	"unrealircd-6",
-};
-
-int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, nocodes_can_send_to_channel);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-static int has_controlcodes(const char *p)
-{
-	for (; *p; p++)
-		if ((*p == '\002') || (*p == '\037') || (*p == '\026') || (*p == '\035')) /* bold, underline, reverse, italic */
-			return 1;
-	return 0;
-}
-
-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;
-	int i;
-
-	if (has_channel_mode(channel, 'S'))
-	{
-		if (!has_controlcodes(*msg))
-			return HOOK_CONTINUE;
-
-		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_COLOR);
-			if (i == HOOK_ALLOW)
-				return HOOK_CONTINUE; /* bypass +S restriction */
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-
-		strlcpy(retbuf, StripControlCodes(*msg), sizeof(retbuf));
-		*msg = retbuf;
-		return HOOK_CONTINUE;
-	} else
-	if (has_channel_mode(channel, 'c'))
-	{
-		if (!has_controlcodes(*msg))
-			return HOOK_CONTINUE;
-
-		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client, channel, BYPASS_CHANMSG_COLOR);
-			if (i == HOOK_ALLOW)
-				return HOOK_CONTINUE; /* bypass +c restriction */
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-
-		*errmsg = "Control codes (color/bold/underline/italic/reverse) are not permitted in this channel";
-		return HOOK_DENY;
-	}
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/oper.c b/src/modules/oper.c
@@ -1,381 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/oper.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_OPER        "OPER"  /* OPER */
-
-ModuleHeader MOD_HEADER
-  = {
-	"oper",	/* Name of module */
-	"5.0", /* Version */
-	"command /oper", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-CMD_FUNC(cmd_oper);
-int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
-int oper_connect(Client *client);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAdd(modinfo->handle, EFUNC_MAKE_OPER, _make_oper);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	CommandAdd(modinfo->handle, MSG_OPER, cmd_oper, MAXPARA, CMD_USER);
-	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, oper_connect);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void set_oper_host(Client *client, const char *host)
-{
-	char uhost[HOSTLEN + USERLEN + 1];
-	char *p;
-
-	if (!valid_vhost(host))
-		return;
-
-	strlcpy(uhost, host, sizeof(uhost));
-
-	if ((p = strchr(uhost, '@')))
-	{
-		*p++ = '\0';
-		strlcpy(client->user->username, uhost, sizeof(client->user->username));
-		sendto_server(NULL, 0, 0, NULL, ":%s SETIDENT %s",
-		              client->id, client->user->username);
-		host = p;
-	}
-	safe_strdup(client->user->virthost, host);
-	if (MyConnect(client))
-		sendto_server(NULL, 0, 0, NULL, ":%s SETHOST :%s", client->id, client->user->virthost);
-	client->umodes |= UMODE_SETHOST|UMODE_HIDE;
-}
-
-int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost)
-{
-	long old_umodes = client->umodes & ALL_UMODES;
-
-	userhost_save_current(client);
-
-	/* Put in the right class (if any) */
-	if (clientclass)
-	{
-		if (client->local->class)
-			client->local->class->clients--;
-		client->local->class = clientclass;
-		client->local->class->clients++;
-	}
-
-	/* set oper user modes */
-	client->umodes |= UMODE_OPER;
-	if (modes)
-		client->umodes |= modes; /* oper::modes */
-	else
-		client->umodes |= OPER_MODES; /* set::modes-on-oper */
-
-	/* oper::vhost */
-	if (vhost)
-	{
-		set_oper_host(client, vhost);
-	} else
-	if (IsHidden(client) && !client->user->virthost)
-	{
-		/* +x has just been set by modes-on-oper and no vhost. cloak the oper! */
-		safe_strdup(client->user->virthost, client->user->cloakedhost);
-	}
-
-	userhost_changed(client);
-
-	unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client,
-		   "$client.details is now an IRC Operator [oper-block: $oper_block] [operclass: $operclass]",
-		   log_data_string("oper_block", operblock_name),
-		   log_data_string("operclass", operclass));
-
-	/* set oper snomasks */
-	if (snomask)
-		set_snomask(client, snomask); /* oper::snomask */
-	else
-		set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */
-
-	send_umode_out(client, 1, old_umodes);
-	if (client->user->snomask)
-		sendnumeric(client, RPL_SNOMASK, client->user->snomask);
-
-	list_add(&client->special_node, &oper_list);
-
-	RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock_name, operclass);
-
-	sendnumeric(client, RPL_YOUREOPER);
-
-	/* Update statistics */
-	if (IsInvisible(client) && !(old_umodes & UMODE_INVISIBLE))
-		irccounts.invisible++;
-	if (IsOper(client) && !IsHideOper(client))
-		irccounts.operators++;
-
-	if (SHOWOPERMOTD == 1)
-	{
-		const char *args[1] = { NULL };
-		do_cmd(client, NULL, "OPERMOTD", 1, args);
-	}
-
-	if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0"))
-	{
-		char *chans = strdup(OPER_AUTO_JOIN_CHANS);
-		const char *args[3] = {
-			client->name,
-			chans,
-			NULL
-		};
-		do_cmd(client, NULL, "JOIN", 3, args);
-		safe_free(chans);
-		/* Theoretically the oper may be killed on join. Would be fun, though */
-		if (IsDead(client))
-			return 0;
-	}
-
-	return 1;
-}
-
-/*
-** cmd_oper
-**	parv[1] = oper name
-**	parv[2] = oper password
-*/
-CMD_FUNC(cmd_oper)
-{
-	ConfigItem_oper *operblock;
-	const char *operblock_name, *password;
-
-	if (!MyUser(client))
-		return;
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "OPER");
-		return;
-	}
-
-	if (SVSNOOP)
-	{
-		sendnotice(client,
-		    "*** This server is in NOOP mode, you cannot /oper");
-		return;
-	}
-
-	if (IsOper(client))
-	{
-		sendnotice(client, "You are already an IRC Operator. If you want to re-oper then de-oper first via /MODE yournick -o");
-		return;
-	}
-
-	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);
-		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;
-	}
-
-	/* set::outdated-tls-policy::oper 'deny' */
-	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));
-		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(operblock_name)))
-	{
-		sendnumeric(client, ERR_NOOPERHOST);
-		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 (!user_allowed_by_security_group(client, operblock->match))
-	{
-		sendnumeric(client, ERR_NOOPERHOST);
-		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;
-	}
-
-	if (operblock->auth && !Auth_Check(client, operblock->auth, password))
-	{
-		sendnumeric(client, ERR_PASSWDMISMATCH);
-		if (FAILOPER_WARN)
-			sendnotice(client,
-			    "*** Your attempt has been logged.");
-		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;
-	}
-
-	/* Authentication of the oper succeeded (like, password, ssl cert),
-	 * but we still have some other restrictions to check below as well,
-	 * like 'require-modes' and 'maxlogins'...
-	 */
-
-	/* Check oper::require_modes */
-	if (operblock->require_modes & ~client->umodes)
-	{
-		sendnumericfmt(client, ERR_NOOPERHOST, ":You are missing user modes required to OPER");
-		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");
-		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;
-	}
-
-	if (operblock->maxlogins && (count_oper_sessions(operblock->name) >= operblock->maxlogins))
-	{
-		sendnumeric(client, ERR_NOOPERHOST);
-		sendnotice(client, "Your maximum number of concurrent oper logins has been reached (%d)",
-			operblock->maxlogins);
-		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;
-	}
-
-	/* /OPER really succeeded now. Start processing it. */
-
-	/* Store which oper block was used to become IRCOp (for maxlogins and whois) */
-	safe_strdup(client->user->operlogin, operblock->name);
-
-	/* oper::swhois */
-	if (operblock->swhois)
-	{
-		SWhois *s;
-		for (s = operblock->swhois; s; s = s->next)
-			swhois_add(client, "oper", -100, s->line, &me, NULL);
-	}
-
-	make_oper(client, operblock->name, operblock->operclass, operblock->class, operblock->modes, operblock->snomask, operblock->vhost);
-
-	/* set::plaintext-policy::oper 'warn' */
-	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_WARN))
-	{
-		sendnotice_multiline(client, iConf.plaintext_policy_oper_message);
-		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));
-		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"));
-	}
-}
-
-int oper_connect(Client *client)
-{
-	ConfigItem_oper *e;
-
-	if (IsOper(client))
-		return 0;
-
-	for (e = conf_oper; e; e = e->next)
-	{
-		if (e->auto_login && user_allowed_by_security_group(client, e->match))
-		{
-			/* Ideally we would check all the criteria that cmd_oper does.
-			 * I'm taking a shortcut for now that is not ideal...
-			 */
-			const char *parx[3];
-			parx[0] = NULL;
-			parx[1] = e->name;
-			parx[2] = NULL;
-			do_cmd(client, NULL, "OPER", 3, parx);
-			return 0;
-		}
-	}
-
-	return 0;
-}
diff --git a/src/modules/operinfo.c b/src/modules/operinfo.c
@@ -1,99 +0,0 @@
-/*
- * Store oper login in ModData, used by WHOIS and for auditting purposes.
- * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
- * License: GPLv2 or later
- */
-
-#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, const char *oper_block, const char *operclass);
-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, const char *oper_block, const char *operclass)
-{
-	if (up)
-	{
-		moddata_client_set(client, "operlogin", oper_block);
-		moddata_client_set(client, "operclass", 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
@@ -1,90 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/opermotd.c
- *   (C) 2005 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_opermotd);
-
-#define MSG_OPERMOTD 	"OPERMOTD"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"opermotd",
-	"5.0",
-	"command /opermotd", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_OPERMOTD, cmd_opermotd, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * Modified from comstud by codemastr
- */
-CMD_FUNC(cmd_opermotd)
-{
-	MOTDLine *motdline;
-	ConfigItem_tld *tld;
-
-	if (!ValidatePermissionsForPath("server:opermotd",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	tld = find_tld(client);
-	if (tld && tld->opermotd.lines)
-		motdline = tld->opermotd.lines;
-	else
-		motdline = opermotd.lines;
-
-	if (!motdline)
-	{
-		sendnumeric(client, ERR_NOOPERMOTD);
-		return;
-	}
-	sendnumeric(client, RPL_MOTDSTART, me.name);
-	sendnumeric(client, RPL_MOTD, "IRC Operator Message of the Day");
-
-	while (motdline)
-	{
-		sendnumeric(client, RPL_MOTD,
-			   motdline->line);
-		motdline = motdline->next;
-	}
-	sendnumericfmt(client, RPL_ENDOFMOTD, ":End of /OPERMOTD command.");
-}
diff --git a/src/modules/part.c b/src/modules/part.c
@@ -1,216 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/part.c
- *   (C) 2005 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_part);
-
-#define MSG_PART 	"PART"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"part",
-	"5.0",
-	"command /part", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_PART, cmd_part, 2, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_part
-**	parv[1] = channel
-**	parv[2] = comment (added by Lefler)
-*/
-CMD_FUNC(cmd_part)
-{
-	char request[BUFSIZE];
-	Channel *channel;
-	Membership *lp;
-	char *p = NULL, *name;
-	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");
-	
-	if (parc < 2 || parv[1][0] == '\0')
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "PART");
-		return;
-	}
-
-	if (MyUser(client))
-	{
-		if (IsShunned(client))
-			commentx = NULL;
-		if (STATIC_PART)
-		{
-			if (!strcasecmp(STATIC_PART, "yes") || !strcmp(STATIC_PART, "1"))
-				commentx = NULL;
-			else if (!strcasecmp(STATIC_PART, "no") || !strcmp(STATIC_PART, "0"))
-				; /* keep original reason */
-			else
-				commentx = STATIC_PART;
-		}
-		if (commentx)
-		{
-			if (match_spamfilter(client, commentx, SPAMF_PART, "PART", parv[1], 0, NULL))
-				commentx = NULL;
-			if (IsDead(client))
-				return;
-		}
-	}
-
-	strlcpy(request, parv[1], sizeof(request));
-	for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
-	{
-		MessageTag *mtags = NULL;
-
-		if (MyUser(client) && (++ntargets > maxtargets))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "PART");
-			break;
-		}
-
-		channel = find_channel(name);
-		if (!channel)
-		{
-			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
-			continue;
-		}
-
-		/* 'commentx' is the general part msg, but it can be changed
-		 * per-channel (eg some chans block badwords, strip colors, etc)
-		 * so we copy it to 'comment' and use that in this for loop :)
-		 */
-		comment = commentx;
-
-		if (!(lp = find_membership_link(client->user->channel, channel)))
-		{
-			/* Normal to get get when our client did a kick
-			   ** for a remote client (who sends back a PART),
-			   ** so check for remote client or not --Run
-			 */
-			if (MyUser(client))
-				sendnumeric(client, ERR_NOTONCHANNEL, name);
-			continue;
-		}
-
-		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;
-		}
-
-		if (MyConnect(client))
-		{
-			Hook *tmphook;
-			for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_PART]; tmphook; tmphook = tmphook->next) {
-				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->name);
-
-		/* Send to other servers... */
-		sendto_server(client, 0, 0, mtags, ":%s PART %s :%s",
-			client->id, channel->name, comment ? comment : "");
-
-		if (invisible_user_in_channel(client, channel))
-		{
-			/* Show PART only to chanops and self */
-			if (!comment)
-			{
-				sendto_channel(channel, client, client,
-					       "ho", 0,
-					       SEND_LOCAL, mtags,
-					       ":%s PART %s",
-					       client->name, channel->name);
-				if (MyUser(client))
-				{
-					sendto_one(client, mtags, ":%s!%s@%s PART %s",
-						client->name, client->user->username, GetHost(client), channel->name);
-				}
-			}
-			else
-			{
-				sendto_channel(channel, client, client,
-					       "ho", 0,
-					       SEND_LOCAL, mtags,
-					       ":%s PART %s %s",
-					       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->name, comment);
-				}
-			}
-		}
-		else
-		{
-			/* Show PART to all users in channel */
-			if (!comment)
-			{
-				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-				               ":%s PART %s",
-				               client->name, channel->name);
-			} else {
-				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-				               ":%s PART %s :%s",
-				               client->name, channel->name, comment);
-			}
-		}
-
-		if (MyUser(client))
-			RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, comment);
-		else
-			RunHook(HOOKTYPE_REMOTE_PART, client, channel, mtags, comment);
-
-		free_message_tags(mtags);
-
-		remove_user_from_channel(client, channel, 0);
-	}
-}
diff --git a/src/modules/pass.c b/src/modules/pass.c
@@ -1,84 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/pass.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_pass);
-
-#define MSG_PASS 	"PASS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"pass",
-	"5.0",
-	"command /pass", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_PASS, cmd_pass, 1, CMD_UNREGISTERED|CMD_USER|CMD_SERVER);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/***************************************************************************
- * cmd_pass() - Added Sat, 4 March 1989
- ***************************************************************************/
-/*
-** cmd_pass
-**	parv[1] = password
-*/
-CMD_FUNC(cmd_pass)
-{
-	const char *password = parc > 1 ? parv[1] : NULL;
-
-	if (!MyConnect(client) || (!IsUnknown(client) && !IsHandshake(client)))
-	{
-		sendnumeric(client, ERR_ALREADYREGISTRED);
-		return;
-	}
-
-	if (BadPtr(password))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "PASS");
-		return;
-	}
-
-	/* Store the password */
-	safe_strldup(client->local->passwd, password, PASSWDLEN+1);
-
-	/* note: the original non-truncated password is supplied as 2nd parameter. */
-	RunHookReturn(HOOKTYPE_LOCAL_PASS, !=0, client, password);
-}
diff --git a/src/modules/pingpong.c b/src/modules/pingpong.c
@@ -1,208 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/pingpong.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_ping);
-CMD_FUNC(cmd_pong);
-CMD_FUNC(cmd_nospoof);
-
-/* Place includes here */
-#define MSG_PING        "PING"  /* PING */
-#define MSG_PONG        "PONG"  /* PONG */
-
-ModuleHeader MOD_HEADER
-  = {
-	"pingpong",	/* Name of module */
-	"5.0", /* Version */
-	"ping, pong and nospoof", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_PING, cmd_ping, MAXPARA, CMD_USER|CMD_SERVER|CMD_SHUN);
-	CommandAdd(modinfo->handle, MSG_PONG, cmd_pong, MAXPARA, CMD_UNREGISTERED|CMD_USER|CMD_SERVER|CMD_SHUN|CMD_VIRUS);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_ping
-**	parv[1] = origin
-**	parv[2] = destination
-*/
-CMD_FUNC(cmd_ping)
-{
-	Client *target;
-	const char *origin, *destination;
-
-	if (parc < 2 || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NOORIGIN);
-		return;
-	}
-
-	origin = parv[1];
-	destination = parv[2];	/* Will get NULL or pointer (parc >= 2!!) */
-
-	if (!MyUser(client))
-		origin = client->name;
-
-	if (!BadPtr(destination) && mycmp(destination, me.name) != 0 && mycmp(destination, me.id) != 0)
-	{
-		if (MyUser(client))
-			origin = client->name; /* Make sure origin is not spoofed */
-		if ((target = find_server_quick(destination)) && (target != &me))
-			sendto_one(target, NULL, ":%s PING %s :%s", client->name, origin, destination);
-		else
-		{
-			sendnumeric(client, ERR_NOSUCHSERVER, destination);
-			return;
-		}
-	}
-	else
-	{
-		MessageTag *mtags = NULL;
-		new_message(&me, recv_mtags, &mtags);
-		sendto_one(client, mtags, ":%s PONG %s :%s", me.name,
-		    (destination) ? destination : me.name, origin);
-		free_message_tags(mtags);
-	}
-}
-
-/*
-** cmd_nospoof - allows clients to respond to no spoofing patch
-**	parv[1] = code
-*/
-CMD_FUNC(cmd_nospoof)
-{
-	unsigned long result;
-
-	if (IsNotSpoof(client))
-		return;
-	if (IsRegistered(client))
-		return;
-	if (!*client->name)
-		return;
-	if (BadPtr(parv[1]))
-	{
-		sendnotice(client, "ERROR: Invalid PING response. Your client must respond back with PONG :<cookie>");
-		return;
-	}
-
-	result = strtoul(parv[1], NULL, 16);
-
-	if (result != client->local->nospoof)
-	{
-		/* Apparently we also accept PONG <irrelevant> <cookie>... */
-		if (BadPtr(parv[2]))
-		{
-			sendnotice(client, "ERROR: Invalid PING response. Your client must respond back with PONG :<cookie>");
-			return;
-		}
-		result = strtoul(parv[2], NULL, 16);
-		if (result != client->local->nospoof)
-		{
-			sendnotice(client, "ERROR: Invalid PING response. Your client must respond back with PONG :<cookie>");
-			return;
-		}
-	}
-
-	client->local->nospoof = 0;
-
-	if (USE_BAN_VERSION && MyConnect(client))
-		sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
-			   me.name, client->name);
-
-	if (is_handshake_finished(client))
-		register_user(client);
-}
-
-/*
-** cmd_pong
-**	parv[1] = origin
-**	parv[2] = destination
-*/
-CMD_FUNC(cmd_pong)
-{
-	Client *target;
-	const char *origin, *destination;
-
-	if (!IsRegistered(client))
-	{
-		CALL_CMD_FUNC(cmd_nospoof);
-		return;
-	}
-
-	if (parc < 2 || *parv[1] == '\0')
-	{
-		sendnumeric(client, ERR_NOORIGIN);
-		return;
-	}
-
-	origin = parv[1];
-	destination = parv[2];
-	ClearPingSent(client);
-	ClearPingWarning(client);
-
-	/* Remote pongs for clients? uhh... */
-	if (MyUser(client) || !IsRegistered(client))
-		return;
-
-	/* PONG from a server - either for us, or needs relaying.. */
-	if (!BadPtr(destination) && mycmp(destination, me.name) != 0)
-	{
-		if ((target = find_client(destination, NULL)) ||
-		    (target = find_server_quick(destination)))
-		{
-			if (IsUser(client) && !IsServer(target))
-			{
-				sendnumeric(client, ERR_NOSUCHSERVER, destination);
-				return;
-			} else
-			{
-				MessageTag *mtags = NULL;
-				new_message(client, recv_mtags, &mtags);
-				sendto_one(target, mtags, ":%s PONG %s %s", client->name, origin, destination);
-				free_message_tags(mtags);
-			}
-		}
-		else
-		{
-			sendnumeric(client, ERR_NOSUCHSERVER, destination);
-			return;
-		}
-	}
-}
diff --git a/src/modules/plaintext-policy.c b/src/modules/plaintext-policy.c
@@ -1,75 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/plaintext-policy.c
- *   (C) 2017 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"plaintext-policy",
-	"5.0",
-	"Plaintext Policy CAP",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	return MOD_SUCCESS;
-}
-
-void init_plaintext_policy(ModuleInfo *modinfo);
-
-MOD_LOAD()
-{
-	/* init_plaintext_policy is delayed to MOD_LOAD due to configuration dependency */
-	init_plaintext_policy(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-const char *plaintext_policy_capability_parameter(Client *client)
-{
-	static char buf[128];
-	
-	snprintf(buf, sizeof(buf), "user=%s,oper=%s,server=%s",
-             policy_valtostr(iConf.plaintext_policy_user),
-             policy_valtostr(iConf.plaintext_policy_oper),
-             policy_valtostr(iConf.plaintext_policy_server));
-	return buf;
-}
-
-void init_plaintext_policy(ModuleInfo *modinfo)
-{
-	ClientCapabilityInfo cap;
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "unrealircd.org/plaintext-policy";
-	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
-	cap.parameter = plaintext_policy_capability_parameter;
-	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
-}
diff --git a/src/modules/protoctl.c b/src/modules/protoctl.c
@@ -1,409 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/protoctl.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_protoctl);
-
-#define MSG_PROTOCTL 	"PROTOCTL"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"protoctl",
-	"5.0",
-	"command /protoctl", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_PROTOCTL, cmd_protoctl, MAXPARA, CMD_UNREGISTERED|CMD_SERVER|CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-#define MAX_SERVER_TIME_OFFSET 60
-
-/* The PROTOCTL command is used for negotiating capabilities with
- * directly connected servers.
- * See https://www.unrealircd.org/docs/Server_protocol:PROTOCTL_command
- * for all technical documentation, especially if you are a server
- * or services coder.
- */
-CMD_FUNC(cmd_protoctl)
-{
-	int  i;
-	int first_protoctl = IsProtoctlReceived(client) ? 0 : 1; /**< First PROTOCTL we receive? Special ;) */
-	char proto[512];
-	char *name, *value, *p;
-
-	if (!MyConnect(client))
-		return; /* Remote PROTOCTL's are not supported */
-
-	SetProtoctlReceived(client);
-
-	for (i = 1; i < parc; i++)
-	{
-		strlcpy(proto, parv[i], sizeof proto);
-		p = strchr(proto, '=');
-		if (p)
-		{
-			name = proto;
-			*p++ = '\0';
-			value = p;
-		} else {
-			name = proto;
-			value = NULL;
-		}
-
-		if (!strcmp(name, "NAMESX"))
-		{
-			SetCapability(client, "multi-prefix");
-		}
-		else if (!strcmp(name, "UHNAMES") && UHNAMES_ENABLED)
-		{
-			SetCapability(client, "userhost-in-names");
-		}
-		else if (IsUser(client))
-		{
-			return;
-		}
-		else if (!strcmp(name, "VL"))
-		{
-			SetVL(client);
-		}
-		else if (!strcmp(name, "VHP"))
-		{
-			SetVHP(client);
-		}
-		else if (!strcmp(name, "CLK"))
-		{
-			SetCLK(client);
-		}
-		else if (!strcmp(name, "SJSBY") && iConf.ban_setter_sync)
-		{
-			SetSJSBY(client);
-		}
-		else if (!strcmp(name, "MTAGS"))
-		{
-			SetMTAGS(client);
-		}
-		else if (!strcmp(name, "NEXTBANS"))
-		{
-			SetNEXTBANS(client);
-		}
-		else if (!strcmp(name, "NICKCHARS") && value)
-		{
-			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
-				continue;
-			/* Ok, server is either authenticated, or is an outgoing connect... */
-			/* Some combinations are fatal because they would lead to mass-kills:
-			 * - use of 'utf8' on our server but not on theirs
-			 */
-			if (strstr(charsys_get_current_languages(), "utf8") && !strstr(value, "utf8"))
-			{
-				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()))
-			{
-				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->server)
-				safe_strdup(client->server->features.nickchars, value);
-
-			/* If this is a runtime change (so post-handshake): */
-			if (IsServer(client))
-				broadcast_sinfo(client, NULL, client);
-		}
-		else if (!strcmp(name, "CHANNELCHARS") && value)
-		{
-			int their_value;
-
-			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
-				continue;
-
-			their_value = allowed_channelchars_strtoval(value);
-			if (their_value != iConf.allowed_channelchars)
-			{
-				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;
-			}
-		}
-		else if (!strcmp(name, "SID") && value)
-		{
-			Client *aclient;
-			char *sid = value;
-
-			if (!IsServer(client) && !IsEAuth(client) && !IsHandshake(client))
-			{
-				exit_client(client, NULL, "Got PROTOCTL SID before EAUTH, that's the wrong order!");
-				return;
-			}
-
-			if (*client->id && (strlen(client->id)==3))
-			{
-				exit_client(client, NULL, "Got PROTOCTL SID twice");
-				return;
-			}
-
-			if (!valid_sid(value))
-			{
-				exit_client(client, NULL, "Invalid SID. The first character must be a digit and the other two characters must be A-Z0-9. Eg: 0AA.");
-				return;
-			}
-
-			if (IsServer(client))
-			{
-				exit_client(client, NULL, "Got PROTOCTL SID after SERVER, that's the wrong order!");
-				return;
-			}
-
-			if ((aclient = hash_find_id(sid, NULL)) != NULL)
-			{
-				unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SID_COLLISION", client,
-					   "Server link $client rejected. Server with SID $sid already exist via uplink $existing_client.server.uplink.",
-					   log_data_string("sid", sid),
-					   log_data_client("existing_client", aclient));
-				exit_client(client, NULL, "SID collision");
-				return;
-			}
-
-			if (*client->id)
-				del_from_id_hash_table(client->id, client); /* delete old UID entry (created on connect) */
-			strlcpy(client->id, sid, IDLEN);
-			add_to_id_hash_table(client->id, client); /* add SID */
-		}
-		else if (!strcmp(name, "EAUTH") && value)
-		{
-			/* Early authorization: EAUTH=servername,protocol,flags,software
-			 * (Only servername is mandatory, rest is optional)
-			 */
-			int ret;
-			char *p;
-			char *servername = NULL, *protocol = NULL, *flags = NULL, *software = NULL;
-			char buf[512];
-			ConfigItem_link *aconf = NULL;
-
-			if (IsEAuth(client))
-			{
-				exit_client(client, NULL, "PROTOCTL EAUTH received twice");
-				return;
-			}
-
-			strlcpy(buf, value, sizeof(buf));
-			p = strchr(buf, ' ');
-			if (p)
-			{
-				*p = '\0';
-				p = NULL;
-			}
-			
-			servername = strtoken_noskip(&p, buf, ",");
-			if (!servername || !valid_server_name(servername))
-			{
-				exit_client(client, NULL, "Bogus server name");
-				return;
-			}
-			
-			
-			protocol = strtoken_noskip(&p, NULL, ",");
-			if (protocol)
-			{
-				flags = strtoken_noskip(&p, NULL, ",");
-				if (flags)
-					software = strtoken_noskip(&p, NULL, ",");
-			}
-			
-			/* 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 (!(aconf = verify_link(client)))
-				return;
-
-			/* note: software, protocol and flags may be NULL */
-			if (!check_deny_version(client, software, protocol ? atoi(protocol) : 0, flags))
-				return;
-
-			SetEAuth(client);
-			make_server(client); /* allocate and set client->server */
-			if (protocol)
-				client->server->features.protocol = atoi(protocol);
-			if (software)
-				safe_strdup(client->server->features.software, software);
-			if (is_services_but_not_ulined(client))
-			{
-				exit_client_fmt(client, NULL, "Services detected but no ulines { } for server name %s", client->name);
-				return;
-			}
-			if (!IsHandshake(client) && aconf) /* Send PASS early... */
-				sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
-		}
-		else if (!strcmp(name, "SERVERS") && value)
-		{
-			Client *aclient, *srv;
-			char *sid = NULL;
-			
-			if (!IsEAuth(client))
-				continue;
-				
-			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: SERVERS=001,002,0AB,004,005
-			 */
-
-			add_pending_net(client, value);
-
-			aclient = find_non_pending_net_duplicates(client);
-			if (aclient)
-			{
-				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;
-			}
-			
-			aclient = find_pending_net_duplicates(client, &srv, &sid);
-			if (aclient)
-			{
-				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;
-			}
-
-			/* Send our PROTOCTL SERVERS= back if this was NOT a response */
-			if (*value != '*')
-				send_protoctl_servers(client, 1);
-		}
-		else if (!strcmp(name, "TS") && value && (IsServer(client) || IsEAuth(client)))
-		{
-			long t = atol(value);
-
-			if (t < 10000)
-				continue; /* ignore */
-
-			if ((TStime() - t) > MAX_SERVER_TIME_OFFSET)
-			{
-				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)
-			{
-				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;
-			}
-		}
-		else if (!strcmp(name, "MLOCK"))
-		{
-			client->local->proto |= PROTO_MLOCK;
-		}
-		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->server)
-		{
-			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->server)
-		{
-			client->server->boottime = atol(value);
-		}
-		else if (!strcmp(name, "EXTSWHOIS"))
-		{
-			client->local->proto |= PROTO_EXTSWHOIS;
-		}
-		/* You can add protocol extensions here.
-		 * Use 'name' and 'value' (the latter may be NULL).
-		 *
-		 * DO NOT error or warn on unknown proto; we just don't
-		 * support it.
-		 */
-	}
-
-	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
-		 * of older servers).
-		 */
-		send_server_message(client);
-	}
-}
diff --git a/src/modules/quit.c b/src/modules/quit.c
@@ -1,177 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/quit.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_quit);
-
-#define MSG_QUIT        "QUIT"  /* QUIT */
-
-ModuleHeader MOD_HEADER
-  = {
-	"quit",	/* Name of module */
-	"5.0", /* Version */
-	"command /quit", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_QUIT, cmd_quit, 1, CMD_UNREGISTERED|CMD_USER|CMD_VIRUS);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_quit
-**	parv[1] = comment
-*/
-CMD_FUNC(cmd_quit)
-{
-	const char *comment = (parc > 1 && parv[1]) ? parv[1] : client->name;
-	char commentbuf[MAXQUITLEN + 1];
-	char commentbuf2[MAXQUITLEN + 1];
-
-	if (parc > 1 && parv[1])
-	{
-		strlncpy(commentbuf, parv[1], sizeof(commentbuf), iConf.quit_length);
-		comment = commentbuf;
-	} else {
-		comment = client->name;
-	}
-
-	if (MyUser(client))
-	{
-		int n;
-		Hook *tmphook;
-
-		if (STATIC_QUIT)
-		{
-			exit_client(client, recv_mtags, STATIC_QUIT);
-			return;
-		}
-
-		if (IsVirus(client))
-		{
-			exit_client(client, recv_mtags, "Client exited");
-			return;
-		}
-
-		if (match_spamfilter(client, comment, SPAMF_QUIT, "QUIT", NULL, 0, NULL))
-		{
-			comment = client->name;
-			if (IsDead(client))
-				return;
-		}
-		
-		if (!ValidatePermissionsForPath("immune:anti-spam-quit-message-time",client,NULL,NULL,NULL) && ANTI_SPAM_QUIT_MSG_TIME)
-		{
-			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;
-			const char *newcomment;
-			Channel *channel;
-
-			for (lp = client->user->channel; lp; lp = lp_next)
-			{
-				channel = lp->channel;
-				newcomment = comment;
-				lp_next = lp->next;
-
-				for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_QUIT_CHAN]; tmphook; tmphook = tmphook->next)
-				{
-					newcomment = (*(tmphook->func.stringfunc))(client, channel, comment);
-					if (!newcomment)
-						break;
-				}
-
-				if (newcomment && is_banned(client, channel, BANCHK_LEAVE_MSG, &newcomment, NULL))
-					newcomment = NULL;
-
-				/* Comment changed? Then PART the user before we do the QUIT. */
-				if (comment != newcomment)
-				{
-					const char *parx[4];
-					char tmp[512];
-					int ret;
-
-
-					parx[0] = 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): */
-					if (IsDead(client))
-						return;
-				}
-			}
-		}
-
-		for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_QUIT]; tmphook; tmphook = tmphook->next)
-		{
-			comment = (*(tmphook->func.stringfunc))(client, comment);
-			if (!comment)
-			{			
-				comment = client->name;
-				break;
-			}
-		}
-
-		if (PREFIX_QUIT)
-			snprintf(commentbuf2, sizeof(commentbuf2), "%s: %s", PREFIX_QUIT, comment);
-		else
-			strlcpy(commentbuf2, comment, sizeof(commentbuf2));
-
-		exit_client(client, recv_mtags, commentbuf2);
-	}
-	else
-	{
-		/* Remote quits and non-person quits always use their original comment.
-		 * Also pass recv_mtags so to keep the msgid and such.
-		 */
-		exit_client(client, recv_mtags, comment);
-	}
-}
diff --git a/src/modules/real-quit-reason.c b/src/modules/real-quit-reason.c
@@ -1,81 +0,0 @@
-/*
- * unrealircd.org/real-quit-reason message tag (server only)
- * This is really server-only, it does not traverse to any clients.
- * (C) Copyright 2023-.. Syzop and The UnrealIRCd Team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"real-quit-reason",
-	"6.0",
-	"unrealircd.org/real-quit-reason message tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Forward declarations */
-int real_quit_reason_mtag_is_ok(Client *client, const char *name, const char *value);
-int real_quit_reason_mtag_should_send_to_client(Client *target);
-void mtag_inherit_real_quit_reason(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
-
-MOD_INIT()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "unrealircd.org/real-quit-reason";
-	mtag.is_ok = real_quit_reason_mtag_is_ok;
-	mtag.should_send_to_client = real_quit_reason_mtag_should_send_to_client;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_inherit_real_quit_reason);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending 'unrealircd.org/real-quit-reason'
- * is permitted to do so and uses a permitted syntax.
- * We simply allow unrealircd.org/real-quit-reason ONLY from servers and with any syntax.
- */
-int real_quit_reason_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client))
-		return 1;
-
-	return 0;
-}
-
-void mtag_inherit_real_quit_reason(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/real-quit-reason");
-	if (m)
-	{
-		m = duplicate_mtag(m);
-		AddListItem(m, *mtag_list);
-	}
-}
-
-/** Outgoing filter for this message tag */
-int real_quit_reason_mtag_should_send_to_client(Client *target)
-{
-	if (IsServer(target))
-		return 1;
-
-	return 0;
-}
diff --git a/src/modules/reply-tag.c b/src/modules/reply-tag.c
@@ -1,116 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/reply-tag.c
- *   (C) 2021 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/* This implements https://ircv3.net/specs/client-tags/reply */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"reply-tag",
-	"5.0",
-	"+reply client tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-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()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-#if 0
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "+reply";
-	mtag.is_ok = replytag_mtag_is_ok;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-#endif
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "+draft/reply";
-	mtag.is_ok = replytag_mtag_is_ok;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_replytag);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending the mtag is permitted to do so.
- */
-int replytag_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	const char *p;
-
-	/* Require a non-empty parameter */
-	if (BadPtr(value))
-		return 0;
-
-	/* All our PRIVMSG/NOTICE msgid's are of this size: */
-	if (strlen(value) != MSGIDLEN)
-		return 0;
-
-	for (p = value; *p; p++)
-		if (!isalnum(*p))
-			return 0; /* non-alphanumeric */
-
-	return 1; /* OK */
-}
-
-void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m;
-
-	if (IsUser(client))
-	{
-#if 0
-		m = find_mtag(recv_mtags, "+reply");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-			AddListItem(m, *mtag_list);
-		}
-#endif
-		m = find_mtag(recv_mtags, "+draft/reply");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-			AddListItem(m, *mtag_list);
-		}
-	}
-}
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
@@ -1,1379 +0,0 @@
-/*
- * reputation - Provides a scoring system for "known users".
- * (C) Copyright 2015-2019 Bram Matthys (Syzop) and the UnrealIRCd team.
- * License: GPLv2 or later
- *
- * How this works is simple:
- * Every 5 minutes the IP address of all the connected users receive
- * a point. Registered users receive 2 points every 5 minutes.
- * The total reputation score is then later used, by other modules, for
- * example to make decisions such as to reject or allow a user if the
- * server is under attack.
- * The reputation scores are saved in a database. By default this file
- * is data/reputation.db (often ~/unrealircd/data/reputation.db).
- *
- * See also https://www.unrealircd.org/docs/Connthrottle
- */
-
-#include "unrealircd.h"
-
-#define REPUTATION_VERSION "1.2"
-
-/* Change to #define to benchmark. Note that this will add random
- * reputation entries so should never be used on production servers!!!
- */
-#undef BENCHMARK
-#undef TEST
-
-/* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
- * 10k random IP's with various expire times:
- * - load db:  23 ms
- * - expiry:    1 ms
- * - save db:   7 ms
- * 100k random IP's with various expire times:
- * - load db: 103 ms
- * - expiry:   10 ms
- * - save db:  32 ms
- * So, even for 100,000 unique IP's, the initial load of the database
- * would delay the UnrealIRCd boot process only for 0.1 second.
- * The writing of the db, which happens every 5 minutes, for such
- * amount of IP's takes 32ms (0.03 second).
- * Of course, exact figures will depend on the storage and cache.
- * That being said, the file for 100k random IP's is slightly under
- * 3MB, so not big, which likely means the timing will be similar
- * for a broad number of (storage) systems.
- */
- 
-#ifndef TEST
- #define BUMP_SCORE_EVERY	300
- #define DELETE_OLD_EVERY	605
- #define SAVE_DB_EVERY		902
-#else
- #define BUMP_SCORE_EVERY 	3
- #define DELETE_OLD_EVERY	3
- #define SAVE_DB_EVERY		3
-#endif
-
-#ifndef CALLBACKTYPE_REPUTATION_STARTTIME
- #define CALLBACKTYPE_REPUTATION_STARTTIME 5
-#endif
-
-ModuleHeader MOD_HEADER
-  = {
-	"reputation",
-	REPUTATION_VERSION,
-	"Known IP's scoring system",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Defines */
-
-#define MAXEXPIRES 10
-
-#define REPUTATION_SCORE_CAP 10000
-
-#define UPDATE_SCORE_MARGIN 1
-
-#define REPUTATION_HASH_TABLE_SIZE 2048
-
-#define Reputation(client)	moddata_client(client, reputation_md).l
-
-#define WARN_WRITE_ERROR(fname) \
-	do { \
-		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) \
-	do { \
-		if (!(x)) { \
-			WARN_WRITE_ERROR(tmpfname); \
-			unrealdb_close(db); \
-			return 0; \
-		} \
-	} while(0)
-
-
-/* Definitions (structs, etc.) */
-
-struct cfgstruct {
-	int expire_score[MAXEXPIRES];
-	long expire_time[MAXEXPIRES];
-	char *database;
-	char *db_secret;
-};
-
-typedef struct ReputationEntry ReputationEntry;
-
-struct ReputationEntry {
-	ReputationEntry *prev, *next;
-	unsigned short score; /**< score for the user */
-	long last_seen; /**< user last seen (unix timestamp) */
-	int marker; /**< internal marker, not written to db */
-	char ip[1]; /*< ip address */
-};
-
-/* Global variables */
-
-static struct cfgstruct cfg; /**< Current configuration */
-static struct cfgstruct test; /**< Testing configuration (not active yet) */
-long reputation_starttime = 0;
-long reputation_writtentime = 0;
-
-static ReputationEntry *ReputationHashTable[REPUTATION_HASH_TABLE_SIZE];
-static char siphashkey_reputation[SIPHASH_KEY_LENGTH];
-
-static ModuleInfo ModInf;
-
-ModDataInfo *reputation_md; /* Module Data structure which we acquire */
-
-/* Forward declarations */
-void reputation_md_free(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, NameValuePrioList **list);
-int reputation_set_on_connect(Client *client);
-int reputation_pre_lconnect(Client *client);
-int reputation_ip_change(Client *client, const char *oldip);
-int reputation_connect_extinfo(Client *client, NameValuePrioList **list);
-int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-int reputation_config_posttest(int *errs);
-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);
-EVENT(reputation_save_db_evt);
-int reputation_load_db(void);
-int reputation_save_db(void);
-int reputation_starttime_callback(void);
-
-MOD_TEST()
-{
-	memcpy(&ModInf, modinfo, modinfo->size);
-	memset(&cfg, 0, sizeof(cfg));
-	memset(&test, 0, sizeof(cfg));
-	reputation_config_setdefaults(&test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reputation_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, reputation_config_posttest);
-	CallbackAdd(modinfo->handle, CALLBACKTYPE_REPUTATION_STARTTIME, reputation_starttime_callback);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
-
-	memset(&ReputationHashTable, 0, sizeof(ReputationHashTable));
-	siphash_generate_key(siphashkey_reputation);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "reputation";
-	mreq.free = reputation_md_free;
-	mreq.serialize = reputation_md_serialize;
-	mreq.unserialize = reputation_md_unserialize;
-	mreq.sync = 0; /* local! */
-	mreq.type = MODDATATYPE_CLIENT;
-	reputation_md = ModDataAdd(modinfo->handle, mreq);
-	if (!reputation_md)
-		abort();
-
-	reputation_config_setdefaults(&cfg);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reputation_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, reputation_whois);
-	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, reputation_set_on_connect);
-	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, reputation_ip_change);
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 2000000000, reputation_pre_lconnect); /* (prio: last) */
-	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, -1000000000, reputation_set_on_connect); /* (prio: near-first) */
-	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 0, reputation_connect_extinfo); /* (prio: near-first) */
-	CommandAdd(ModInf.handle, "REPUTATION", reputation_cmd, MAXPARA, CMD_USER|CMD_SERVER);
-	CommandAdd(ModInf.handle, "REPUTATIONUNPERM", reputationunperm, MAXPARA, CMD_USER|CMD_SERVER);
-	return MOD_SUCCESS;
-}
-
-#ifdef BENCHMARK
-void reputation_benchmark(int entries)
-{
-	char ip[64];
-	int i;
-	ReputationEntry *e;
-
-	srand(1234); // fixed seed
-
-	for (i = 0; i < entries; i++)
-	{
-		ReputationEntry *e = safe_alloc(sizeof(ReputationEntry) + 64);
-		snprintf(e->ip, 63, "%d.%d.%d.%d", rand()%255, rand()%255, rand()%255, rand()%255);
-		e->score = rand()%255 + 1;
-		e->last_seen = TStime();
-		if (find_reputation_entry(e->ip))
-		{
-			safe_free(e);
-			continue;
-		}
-		add_reputation_entry(e);
-	}
-}
-#endif
-MOD_LOAD()
-{
-	reputation_load_db();
-	if (reputation_starttime == 0)
-		reputation_starttime = TStime();
-	EventAdd(ModInf.handle, "delete_old_records", delete_old_records, NULL, DELETE_OLD_EVERY*1000, 0);
-	EventAdd(ModInf.handle, "add_scores", add_scores, NULL, BUMP_SCORE_EVERY*1000, 0);
-	EventAdd(ModInf.handle, "reputation_save_db", reputation_save_db_evt, NULL, SAVE_DB_EVERY*1000, 0);
-#ifdef BENCHMARK
-	reputation_benchmark(10000);
-#endif
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	if (loop.terminating)
-		reputation_save_db();
-	reputation_free_config(&test);
-	reputation_free_config(&cfg);
-	return MOD_SUCCESS;
-}
-
-void reputation_config_setdefaults(struct cfgstruct *cfg)
-{
-	/* data/reputation.db */
-	safe_strdup(cfg->database, "reputation.db");
-	convert_to_absolute_path(&cfg->database, PERMDATADIR);
-
-	/* EXPIRES the following entries if the IP does appear for some time: */
-	/* <=2 points after 1 hour */
-	cfg->expire_score[0] = 2;
-#ifndef TEST
-	cfg->expire_time[0]   = 3600;
-#else
-	cfg->expire_time[0]   = 36;
-#endif
-	/* <=6 points after 7 days */
-	cfg->expire_score[1] = 6;
-	cfg->expire_time[1]   = 86400*7;
-	/* ANY result that has not been seen for 30 days */
-	cfg->expire_score[2] = -1;
-	cfg->expire_time[2]   = 86400*30;
-}
-
-void reputation_free_config(struct cfgstruct *cfg)
-{
-	safe_free(cfg->database);
-	safe_free(cfg->db_secret);
-}
-
-int reputation_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::reputation.. */
-	if (!ce || strcmp(ce->name, "reputation"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->value)
-		{
-			config_error("%s:%i: blank set::reputation::%s without value",
-				cep->file->filename, cep->line_number, cep->name);
-			errors++;
-			continue;
-		} else
-		if (!strcmp(cep->name, "database"))
-		{
-			convert_to_absolute_path(&cep->value, PERMDATADIR);
-			safe_strdup(test.database, cep->value);
-		} else
-		if (!strcmp(cep->name, "db-secret"))
-		{
-			const char *err;
-			if ((err = unrealdb_test_secret(cep->value)))
-			{
-				config_error("%s:%i: set::channeldb::db-secret: %s", cep->file->filename, cep->line_number, err);
-				errors++;
-				continue;
-			}
-			safe_strdup(test.db_secret, cep->value);
-		} else
-		{
-			config_error("%s:%i: unknown directive set::reputation::%s",
-				cep->file->filename, cep->line_number, cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	if (type != CONFIG_SET)
-		return 0;
-
-	/* We are only interrested in set::reputation.. */
-	if (!ce || strcmp(ce->name, "reputation"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "database"))
-		{
-			safe_strdup(cfg.database, cep->value);
-		} else
-		if (!strcmp(cep->name, "db-secret"))
-		{
-			safe_strdup(cfg.db_secret, cep->value);
-		}
-	}
-	return 1;
-}
-
-int reputation_config_posttest(int *errs)
-{
-	int errors = 0;
-	char *errstr;
-
-	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
-	{
-		config_error("[reputation] %s", errstr);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/** Parse database header and set variables appropriately */
-int parse_db_header_old(char *buf)
-{
-	char *header=NULL, *version=NULL, *starttime=NULL, *writtentime=NULL;
-	char *p=NULL;
-
-	if (strncmp(buf, "REPDB", 5))
-		return 0;
-
-	header = strtoken(&p, buf, " ");
-	if (!header)
-		return 0;
-
-	version = strtoken(&p, NULL, " ");
-	if (!version || (atoi(version) != 1))
-		return 0;
-
-	starttime = strtoken(&p, NULL, " ");
-	if (!starttime)
-		return 0;
-
-	writtentime = strtoken(&p, NULL, " ");
-	if (!writtentime)
-		return 0;
-
-	reputation_starttime = atol(starttime);
-	reputation_writtentime = atol(writtentime);
-
-	return 1;
-}
-
-void reputation_load_db_old(void)
-{
-	FILE *fd;
-	char buf[512], *p;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	fd = fopen(cfg.database, "r");
-	if (!fd)
-	{
-		config_warn("WARNING: Could not open/read database '%s': %s", cfg.database, strerror(ERRNO));
-		return;
-	}
-
-	memset(buf, 0, sizeof(buf));
-	if (fgets(buf, 512, fd) == NULL)
-	{
-		config_error("WARNING: Database file corrupt ('%s')", cfg.database);
-		fclose(fd);
-		return;
-	}
-
-	/* Header contains: REPDB <version> <starttime> <writtentime>
-	 * Where:
-	 * REPDB:        Literally the string "REPDB".
-	 * <version>     This is version 1 at the time of this writing.
-	 * <starttime>   The time that recording of reputation started,
-	 *               in other words: when this module was first loaded, ever.
-	 * <writtentime> Time that the database was last written.
-	 */
-	if (!parse_db_header_old(buf))
-	{
-		config_error("WARNING: Cannot load database %s. Error reading header. "
-		             "Database corrupt? Or are you downgrading from a newer "
-		             "UnrealIRCd version perhaps? This is not supported.",
-		             cfg.database);
-		fclose(fd);
-		return;
-	}
-
-	while(fgets(buf, 512, fd) != NULL)
-	{
-		char *ip = NULL, *score = NULL, *last_seen = NULL;
-		ReputationEntry *e;
-
-		stripcrlf(buf);
-		/* Format: <ip> <score> <last seen> */
-		ip = strtoken(&p, buf, " ");
-		if (!ip)
-			continue;
-		score = strtoken(&p, NULL, " ");
-		if (!score)
-			continue;
-		last_seen = strtoken(&p, NULL, " ");
-		if (!last_seen)
-			continue;
-
-		e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
-		strcpy(e->ip, ip); /* safe, see alloc above */
-		e->score = atoi(score);
-		e->last_seen = atol(last_seen);
-
-		add_reputation_entry(e);
-	}
-	fclose(fd);
-
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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
-}
-
-#define R_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			config_warn("[reputation] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
-			unrealdb_close(db); \
-			safe_free(ip); \
-			return 0; \
-		} \
-	} while(0)
-
-int reputation_load_db_new(UnrealDB *db)
-{
-	uint64_t l_db_version = 0;
-	uint64_t l_starttime = 0;
-	uint64_t l_writtentime = 0;
-	uint64_t count = 0;
-	uint64_t i;
-	char *ip = NULL;
-	uint16_t score;
-	uint64_t last_seen;
-	ReputationEntry *e;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	R_SAFE(unrealdb_read_int64(db, &l_db_version)); /* reputation db version */
-	if (l_db_version > 2)
-	{
-		config_error("[reputation] Reputation DB is of a newer version (%ld) than supported by us (%ld). "
-		             "Did you perhaps downgrade your UnrealIRCd?",
-		             (long)l_db_version, (long)2);
-		unrealdb_close(db);
-		return 0;
-	}
-	R_SAFE(unrealdb_read_int64(db, &l_starttime)); /* starttime of data gathering */
-	R_SAFE(unrealdb_read_int64(db, &l_writtentime)); /* current time */
-	R_SAFE(unrealdb_read_int64(db, &count)); /* number of entries */
-
-	reputation_starttime = l_starttime;
-	reputation_writtentime = l_writtentime;
-
-	for (i=0; i < count; i++)
-	{
-		R_SAFE(unrealdb_read_str(db, &ip));
-		R_SAFE(unrealdb_read_int16(db, &score));
-		R_SAFE(unrealdb_read_int64(db, &last_seen));
-
-		e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
-		strcpy(e->ip, ip); /* safe, see alloc above */
-		e->score = score;
-		e->last_seen = last_seen;
-		add_reputation_entry(e);
-		safe_free(ip);
-	}
-	unrealdb_close(db);
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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;
-}
-
-/** Load the reputation DB.
- * Strategy is:
- * 1) Check for the old header "REPDB 1", if so then call reputation_load_db_old().
- * 2) Otherwise, open with unrealdb routine
- * 3) If that fails due to a password provided but the file is unrealdb without password
- *    then fallback to open without a password (so users can easily upgrade to encrypted)
- */
-int reputation_load_db(void)
-{
-	FILE *fd;
-	UnrealDB *db;
-	char buf[512];
-
-	fd = fopen(cfg.database, "r");
-	if (!fd)
-	{
-		/* Database does not exist. Could be first boot */
-		config_warn("[reputation] No database present at '%s', will start a new one", cfg.database);
-		return 1;
-	}
-
-	*buf = '\0';
-	if (fgets(buf, sizeof(buf), fd) == NULL)
-	{
-		fclose(fd);
-		config_warn("[reputation] Database at '%s' is 0 bytes", cfg.database);
-		return 1;
-	}
-	fclose(fd);
-	if (!strncmp(buf, "REPDB 1 ", 8))
-	{
-		reputation_load_db_old();
-		return 1; /* not so good to always pretend succes */
-	}
-
-	/* Otherwise, it is an unrealdb, crypted or not */
-	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
-	if (!db)
-	{
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
-		{
-			/* Database does not exist. Could be first boot */
-			config_warn("[reputation] No database present at '%s', will start a new one", cfg.database);
-			return 1;
-		} else
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
-		{
-			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
-		}
-		if (!db)
-		{
-			config_error("[reputation] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
-			return 0;
-		}
-	}
-	return reputation_load_db_new(db);
-}
-
-int reputation_save_db_old(void)
-{
-	FILE *fd;
-	char tmpfname[512];
-	int i;
-	ReputationEntry *e;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	/* We write to a temporary file. Only to rename it later if everything was ok */
-	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
-
-	fd = fopen(tmpfname, "w");
-	if (!fd)
-	{
-		config_error("ERROR: Could not open/write database '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
-		return 0;
-	}
-
-	if (fprintf(fd, "REPDB 1 %lld %lld\n", (long long)reputation_starttime, (long long)TStime()) < 0)
-		goto write_fail;
-
-	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
-	{
-		for (e = ReputationHashTable[i]; e; e = e->next)
-		{
-			if (fprintf(fd, "%s %d %lld\n", e->ip, (int)e->score, (long long)e->last_seen) < 0)
-			{
-write_fail:
-				config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
-				fclose(fd);
-				return 0;
-			}
-		}
-	}
-
-	if (fclose(fd) < 0)
-	{
-		config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
-		return 0;
-	}
-
-	/* Everything went fine. We rename our temporary file to the existing
-	 * DB file (will overwrite), which is more or less an atomic operation.
-	 */
-#ifdef _WIN32
-	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
-	unlink(cfg.database);
-#endif
-	if (rename(tmpfname, cfg.database) < 0)
-	{
-		config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!",
-			tmpfname, cfg.database, strerror(ERRNO));
-		return 0;
-	}
-
-	reputation_writtentime = TStime();
-
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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;
-}
-
-int reputation_save_db(void)
-{
-	UnrealDB *db;
-	char tmpfname[512];
-	int i;
-	uint64_t count;
-	ReputationEntry *e;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-#ifdef TEST
-	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) */
-	if (cfg.db_secret == NULL)
-		return reputation_save_db_old();
-
-	/* We write to a temporary file. Only to rename it later if everything was ok */
-	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
-
-	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
-	if (!db)
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-	/* Write header */
-	W_SAFE(unrealdb_write_int64(db, 2)); /* reputation db version */
-	W_SAFE(unrealdb_write_int64(db, reputation_starttime)); /* starttime of data gathering */
-	W_SAFE(unrealdb_write_int64(db, TStime())); /* current time */
-
-	/* Count entries */
-	count = 0;
-	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
-		for (e = ReputationHashTable[i]; e; e = e->next)
-			count++;
-	W_SAFE(unrealdb_write_int64(db, count)); /* Number of DB entries */
-
-	/* Now write the actual individual entries: */
-	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
-	{
-		for (e = ReputationHashTable[i]; e; e = e->next)
-		{
-			W_SAFE(unrealdb_write_str(db, e->ip));
-			W_SAFE(unrealdb_write_int16(db, e->score));
-			W_SAFE(unrealdb_write_int64(db, e->last_seen));
-		}
-	}
-
-	if (!unrealdb_close(db))
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-	/* Everything went fine. We rename our temporary file to the existing
-	 * DB file (will overwrite), which is more or less an atomic operation.
-	 */
-#ifdef _WIN32
-	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
-	unlink(cfg.database);
-#endif
-	if (rename(tmpfname, cfg.database) < 0)
-	{
-		config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!",
-			tmpfname, cfg.database, strerror(ERRNO));
-		return 0;
-	}
-
-	reputation_writtentime = TStime();
-
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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(const char *ip)
-{
-	return siphash(ip, siphashkey_reputation) % REPUTATION_HASH_TABLE_SIZE;
-}
-
-void add_reputation_entry(ReputationEntry *e)
-{
-	int hashv = hash_reputation_entry(e->ip);
-
-	AddListItem(e, ReputationHashTable[hashv]);
-}
-
-ReputationEntry *find_reputation_entry(const char *ip)
-{
-	ReputationEntry *e;
-	int hashv = hash_reputation_entry(ip);
-
-	for (e = ReputationHashTable[hashv]; e; e = e->next)
-		if (!strcmp(e->ip, ip))
-			return e;
-
-	return NULL;
-}
-
-int reputation_lookup_score_and_set(Client *client)
-{
-	char *ip = client->ip;
-	ReputationEntry *e;
-
-	Reputation(client) = 0; /* (re-)set to zero (yes, important!) */
-	if (ip)
-	{
-		e = find_reputation_entry(ip);
-		if (e)
-		{
-			Reputation(client) = e->score; /* SET MODDATA */
-		}
-	}
-	return Reputation(client);
-}
-
-/** Called when the user connects.
- * Locally: very early, just after the TCP/IP connection has
- * been established, before any data.
- * Remote user: early in the HOOKTYPE_REMOTE_CONNECT hook.
- */
-int reputation_set_on_connect(Client *client)
-{
-	reputation_lookup_score_and_set(client);
-	return 0;
-}
-
-int reputation_ip_change(Client *client, const char *oldip)
-{
-	reputation_lookup_score_and_set(client);
-	return 0;
-}
-
-int reputation_pre_lconnect(Client *client)
-{
-	/* User will likely be accepted. Inform other servers about the score
-	 * we have for this user. For more information about this type of
-	 * server to server traffic, see the reputation_server_cmd function.
-	 *
-	 * Note that we use reputation_lookup_score_and_set() here
-	 * and not Reputation(client) because we want to RE-LOOKUP
-	 * the score for the IP in the database. We do this because
-	 * between reputation_set_on_connect() and reputation_pre_lconnect()
-	 * the IP of the user may have been changed due to IP-spoofing
-	 * (WEBIRC).
-	 */
-	int score = reputation_lookup_score_and_set(client);
-
-	sendto_server(NULL, 0, 0, NULL, ":%s REPUTATION %s %d", me.id, GetIP(client), score);
-
-	return 0;
-}
-
-EVENT(add_scores)
-{
-	static int marker = 0;
-	char *ip;
-	Client *client;
-	ReputationEntry *e;
-
-	/* This marker is used so we only bump score for an IP entry
-	 * once and not twice (or more) if there are multiple users
-	 * with the same IP address.
-	 */
-	marker += 2;
-
-	/* These macros make the code below easier to read. Also,
-	 * this explains why we just did marker+=2 and not marker++.
-	 */
-	#define MARKER_UNREGISTERED_USER (marker)
-	#define MARKER_REGISTERED_USER (marker+1)
-
-	list_for_each_entry(client, &client_list, client_node)
-	{
-		if (!IsUser(client))
-			continue; /* skip servers, unknowns, etc.. */
-
-		ip = client->ip;
-		if (!ip)
-			continue;
-
-		e = find_reputation_entry(ip);
-		if (!e)
-		{
-			/* Create */
-			e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
-			strcpy(e->ip, ip); /* safe, allocated above */
-			add_reputation_entry(e);
-		}
-
-		/* If this is not a duplicate entry, then bump the score.. */
-		if ((e->marker != MARKER_UNREGISTERED_USER) && (e->marker != MARKER_REGISTERED_USER))
-		{
-			e->marker = MARKER_UNREGISTERED_USER;
-			if (e->score < REPUTATION_SCORE_CAP)
-			{
-				/* Regular users receive a point. */
-				e->score++;
-				/* Registered users receive an additional point */
-				if (IsLoggedIn(client) && (e->score < REPUTATION_SCORE_CAP))
-				{
-					e->score++;
-					e->marker = MARKER_REGISTERED_USER;
-				}
-			}
-		} else
-		if ((e->marker == MARKER_UNREGISTERED_USER) && IsLoggedIn(client) && (e->score < REPUTATION_SCORE_CAP))
-		{
-			/* This is to catch a special case:
-			 * If there are 2 or more users with the same IP
-			 * address and the first user was not registered
-			 * then the IP entry only received a score bump of +1.
-			 * If the 2nd user (with same IP) is a registered
-			 * user then the IP should actually receive a
-			 * score bump of +2 (in total).
-			 */
-			e->score++;
-			e->marker = MARKER_REGISTERED_USER;
-		}
-
-		e->last_seen = TStime();
-		Reputation(client) = e->score; /* update moddata */
-	}
-}
-
-/** Is this entry expired? */
-static inline int is_reputation_expired(ReputationEntry *e)
-{
-	int i;
-	for (i = 0; i < MAXEXPIRES; i++)
-	{
-		if (cfg.expire_time[i] == 0)
-			break; /* end of all entries */
-		if ((e->score <= cfg.expire_score[i]) && (TStime() - e->last_seen > cfg.expire_time[i]))
-			return 1;
-	}
-	return 0;
-}
-
-/** If the reputation changed (due to server syncing) then update the
- * individual users reputation score as well.
- */
-void reputation_changed_update_users(ReputationEntry *e)
-{
-	Client *client;
-
-	list_for_each_entry(client, &client_list, client_node)
-	{
-		if (client->ip && !strcmp(e->ip, client->ip))
-		{
-			/* With some (possibly unneeded) care to only go forward */
-			if (Reputation(client) < e->score)
-				Reputation(client) = e->score;
-		}
-	}
-}
-
-EVENT(delete_old_records)
-{
-	int i;
-	ReputationEntry *e, *e_next;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
-	{
-		for (e = ReputationHashTable[i]; e; e = e_next)
-		{
-			e_next = e->next;
-
-			if (is_reputation_expired(e))
-			{
-#ifdef DEBUGMODE
-				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);
-			}
-		}
-	}
-
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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
-}
-
-EVENT(reputation_save_db_evt)
-{
-	reputation_save_db();
-}
-
-CMD_FUNC(reputationunperm)
-{
-	if (!IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	ModuleSetOptions(ModInf.handle, MOD_OPT_PERM, 0);
-
-	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)
-{
-	add_fmt_nvplist(list, 0, "reputation", "%d", GetReputation(client));
-	return 0;
-}
-
-int count_reputation_records(void)
-{
-	int i;
-	ReputationEntry *e;
-	int total = 0;
-
-	for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
-		for (e = ReputationHashTable[i]; e; e = e->next)
-			total++;
-
-	return total;
-}
-
-void reputation_channel_query(Client *client, Channel *channel)
-{
-	Member *m;
-	char buf[512];
-	char tbuf[256];
-	char **nicks;
-	int *scores;
-	int cnt = 0, i, j;
-	ReputationEntry *e;
-
-	sendtxtnumeric(client, "Users and reputation scores for %s:", channel->name);
-
-	/* Step 1: build a list of nicks and their reputation */
-	nicks = safe_alloc((channel->users+1) * sizeof(char *));
-	scores = safe_alloc((channel->users+1) * sizeof(int));
-	for (m = channel->members; m; m = m->next)
-	{
-		nicks[cnt] = m->client->name;
-		if (m->client->ip)
-		{
-			e = find_reputation_entry(m->client->ip);
-			if (e)
-				scores[cnt] = e->score;
-		}
-		if (++cnt > channel->users)
-		{
-			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
-			break; /* safety net */
-		}
-	}
-
-	/* Step 2: lazy selection sort */
-	for (i = 0; i < cnt && nicks[i]; i++)
-	{
-		for (j = i+1; j < cnt && nicks[j]; j++)
-		{
-			if (scores[i] < scores[j])
-			{
-				char *nick_tmp;
-				int score_tmp;
-				nick_tmp = nicks[i];
-				score_tmp = scores[i];
-				nicks[i] = nicks[j];
-				scores[i] = scores[j];
-				nicks[j] = nick_tmp;
-				scores[j] = score_tmp;
-			}
-		}
-	}
-
-	/* Step 3: send the (ordered) list to the user */
-	*buf = '\0';
-	for (i = 0; i < cnt && nicks[i]; i++)
-	{
-		snprintf(tbuf, sizeof(tbuf), "%s\00314(%d)\003 ", nicks[i], scores[i]);
-		if ((strlen(tbuf)+strlen(buf) > 400) || !nicks[i+1])
-		{
-			sendtxtnumeric(client, "%s%s", buf, tbuf);
-			*buf = '\0';
-		} else {
-			strlcat(buf, tbuf, sizeof(buf));
-		}
-	}
-	sendtxtnumeric(client, "End of list.");
-	safe_free(nicks);
-	safe_free(scores);
-}
-
-void reputation_list_query(Client *client, int maxscore)
-{
-	Client *target;
-	ReputationEntry *e;
-
-	sendtxtnumeric(client, "Users and reputation scores <%d:", maxscore);
-
-	list_for_each_entry(target, &client_list, client_node)
-	{
-		int score = 0;
-
-		if (!IsUser(target) || IsULine(target) || !target->ip)
-			continue;
-
-		e = find_reputation_entry(target->ip);
-		if (e)
-			score = e->score;
-		if (score >= maxscore)
-			continue;
-		sendtxtnumeric(client, "%s!%s@%s [%s] \017(score: %d)",
-			target->name,
-			target->user->username,
-			target->user->realhost,
-			target->ip,
-			score);
-	}
-	sendtxtnumeric(client, "End of list.");
-}
-
-CMD_FUNC(reputation_user_cmd)
-{
-	ReputationEntry *e;
-	const char *ip;
-
-	if (!IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnotice(client, "Reputation module statistics:");
-		sendnotice(client, "Recording for: %lld seconds (since unixtime %lld)",
-			(long long)(TStime() - reputation_starttime),
-			(long long)reputation_starttime);
-		if (reputation_writtentime)
-		{
-			sendnotice(client, "Last successful db write: %lld seconds ago (unixtime %lld)",
-				(long long)(TStime() - reputation_writtentime),
-				(long long)reputation_writtentime);
-		} else {
-			sendnotice(client, "Last successful db write: never");
-		}
-		sendnotice(client, "Current number of records (IP's): %d", count_reputation_records());
-		sendnotice(client, "-");
-		sendnotice(client, "Available commands:");
-		sendnotice(client, "/REPUTATION [nick]     Show reputation info about nick name");
-		sendnotice(client, "/REPUTATION [ip]       Show reputation info about IP address");
-		sendnotice(client, "/REPUTATION [channel]  List users in channel along with their reputation score");
-		sendnotice(client, "/REPUTATION <NN        List users with reputation score below value NN");
-		return;
-	}
-
-	if (strchr(parv[1], '.') || strchr(parv[1], ':'))
-	{
-		ip = parv[1];
-	} else
-	if (parv[1][0] == '#')
-	{
-		Channel *channel = find_channel(parv[1]);
-		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) && !IsMember(client,channel))
-		{
-			sendnumeric(client, ERR_NOTONCHANNEL, channel->name);
-			return;
-		}
-		reputation_channel_query(client, channel);
-		return;
-	} else
-	if (parv[1][0] == '<')
-	{
-		int max = atoi(parv[1] + 1);
-		if (max < 1)
-		{
-			sendnotice(client, "REPUTATION: Invalid search value specified. Use for example '/REPUTATION <5' to search on less-than-five");
-			return;
-		}
-		reputation_list_query(client, max);
-		return;
-	} else {
-		Client *target = find_user(parv[1], NULL);
-		if (!target)
-		{
-			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-			return;
-		}
-		ip = target->ip;
-		if (!ip)
-		{
-			sendnotice(client, "No IP address information available for user '%s'.", parv[1]); /* e.g. services */
-			return;
-		}
-	}
-
-	e = find_reputation_entry(ip);
-	if (!e)
-	{
-		sendnotice(client, "No reputation record found for IP %s", ip);
-		return;
-	}
-
-	sendnotice(client, "****************************************************");
-	sendnotice(client, "Reputation record for IP %s:", ip);
-	sendnotice(client, "    Score: %hd", e->score);
-	sendnotice(client, "Last seen: %lld seconds ago (unixtime: %lld)",
-		(long long)(TStime() - e->last_seen),
-		(long long)e->last_seen);
-	sendnotice(client, "****************************************************");
-}
-
-/** The REPUTATION server command handler.
- * Syntax: :server REPUTATION <ip> <score>
- * Where the <score> may be prefixed by an asterisk (*).
- *
- * The best way to explain this command is to illustrate by example:
- * :servera REPUTATION 1.2.3.4 0
- * Then serverb, which might have a score of 2 for this IP, will:
- * - Send back to the servera direction:  :serverb REPUTATION 1.2.3.4 *2
- *   So the original server (and direction) receive a score update.
- * - Propagate to non-servera direction: :servera REPUTATION 1.2.3.4 2
- *   So use the new higher score (2 rather than 0).
- * Then the next server may do the same. It MUST propagate to non-serverb
- * direction and MAY (again) update the score even higher.
- *
- * If the score is not prefixed by * then the server may do as above and
- * send back to the uplink an "update" of the score. If, however, the
- * score is prefixed by * then the server will NEVER send back to the
- * uplink, it may only propagate. This is to prevent loops.
- *
- * Note that some margin is used when deciding if the server should send
- * back score updates. This is defined by UPDATE_SCORE_MARGIN.
- * If this is for example set to 1 then a point difference of 1 will not
- * yield a score update since such a minor score update is not worth the
- * server to server traffic. Also, due to timing differences a score
- * difference of 1 is quite likely to hapen in normal circumstances.
- */
-CMD_FUNC(reputation_server_cmd)
-{
-	ReputationEntry *e;
-	const char *ip;
-	int score;
-	int allow_reply;
-
-	/* :server REPUTATION <ip> <score> */
-	if ((parc < 3) || BadPtr(parv[2]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "REPUTATION");
-		return;
-	}
-
-	ip = parv[1];
-
-	if (parv[2][0] == '*')
-	{
-		allow_reply = 0;
-		score = atoi(parv[2]+1);
-	} else {
-		allow_reply = 1;
-		score = atoi(parv[2]);
-	}
-
-	if (score > REPUTATION_SCORE_CAP)
-		score = REPUTATION_SCORE_CAP;
-
-	e = find_reputation_entry(ip);
-	if (allow_reply && e && (e->score > score) && (e->score - score > UPDATE_SCORE_MARGIN))
-	{
-		/* We have a higher score, inform the client direction about it.
-		 * This will prefix the score with a * so servers will never reply to it.
-		 */
-		sendto_one(client, NULL, ":%s REPUTATION %s *%d", me.id, parv[1], e->score);
-#ifdef DEBUGMODE
-		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 */
-	}
-
-	/* Update our score if sender has a higher score */
-	if (e && (score > e->score))
-	{
-#ifdef DEBUGMODE
-		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;
-		reputation_changed_update_users(e);
-	}
-
-	/* If we don't have any entry for this IP, add it now. */
-	if (!e && (score > 0))
-	{
-#ifdef DEBUGMODE
-		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 */
-		e->score = score;
-		e->last_seen = TStime();
-		add_reputation_entry(e);
-		reputation_changed_update_users(e);
-	}
-
-	/* Propagate to the non-client direction (score may be updated) */
-	sendto_server(client, 0, 0, NULL,
-	              ":%s REPUTATION %s %s%d",
-	              client->id,
-	              parv[1],
-	              allow_reply ? "" : "*",
-	              score);
-}
-
-CMD_FUNC(reputation_cmd)
-{
-	if (MyUser(client))
-		CALL_CMD_FUNC(reputation_user_cmd);
-	else if (IsServer(client) || IsMe(client))
-		CALL_CMD_FUNC(reputation_server_cmd);
-}
-
-int reputation_whois(Client *client, Client *target, NameValuePrioList **list)
-{
-	int reputation;
-
-	if (whois_get_policy(client, target, "reputation") != WHOIS_CONFIG_DETAILS_FULL)
-		return 0;
-
-	reputation = Reputation(target);
-	if (reputation > 0)
-	{
-		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;
-}
-
-void reputation_md_free(ModData *m)
-{
-	/* we have nothing to free actually, but we must set to zero */
-	m->l = 0;
-}
-
-const char *reputation_md_serialize(ModData *m)
-{
-	static char buf[32];
-	if (m->i == 0)
-		return NULL; /* not set (reputation always starts at 1) */
-	snprintf(buf, sizeof(buf), "%d", m->i);
-	return buf;
-}
-
-void reputation_md_unserialize(const char *str, ModData *m)
-{
-	m->i = atoi(str);
-}
-
-int reputation_starttime_callback(void)
-{
-	/* NB: fix this by 2038 */
-	return (int)reputation_starttime;
-}
diff --git a/src/modules/require-module.c b/src/modules/require-module.c
@@ -1,576 +0,0 @@
-/*
- * Check for modules that are required across the network, as well as modules
- * that *aren't* even allowed (deny/require module { } blocks)
- * (C) Copyright 2019 Gottem 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"
-
-#define MSG_SMOD "SMOD"
-#define SMOD_FLAG_REQUIRED 'R'
-#define SMOD_FLAG_GLOBAL 'G'
-#define SMOD_FLAG_LOCAL 'L'
-
-ModuleHeader MOD_HEADER = {
-	"require-module",
-	"5.0.1",
-	"Require/deny modules across the network",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-typedef struct _denymod DenyMod;
-struct _denymod {
-	DenyMod *prev, *next;
-	char *name;
-	char *reason;
-};
-
-typedef struct _requiremod ReqMod;
-struct _requiremod {
-	ReqMod *prev, *next;
-	char *name;
-	char *minversion;
-};
-
-// Forward declarations
-Module *find_modptr_byname(char *name, unsigned strict);
-DenyMod *find_denymod_byname(char *name);
-ReqMod *find_reqmod_byname(char *name);
-
-int reqmods_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int reqmods_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
-
-int reqmods_configtest_deny(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int reqmods_configrun_deny(ConfigFile *cf, ConfigEntry *ce, int type);
-
-int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int reqmods_configrun_require(ConfigFile *cf, ConfigEntry *ce, int type);
-
-CMD_FUNC(cmd_smod);
-int reqmods_hook_serverconnect(Client *client);
-
-// Globals
-extern MODVAR Module *Modules;
-DenyMod *DenyModList = NULL;
-ReqMod *ReqModList = NULL;
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reqmods_configtest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	MARK_AS_GLOBAL_MODULE(modinfo);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reqmods_configrun);
-	HookAdd(modinfo->handle, HOOKTYPE_SERVER_CONNECT, 0, reqmods_hook_serverconnect);
-	CommandAdd(modinfo->handle, MSG_SMOD, cmd_smod, MAXPARA, CMD_SERVER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
-	{
-		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	DenyMod *dmod, *dnext;
-	ReqMod *rmod, *rnext;
-	for (dmod = DenyModList; dmod; dmod = dnext)
-	{
-		dnext = dmod->next;
-		safe_free(dmod->name);
-		safe_free(dmod->reason);
-		DelListItem(dmod, DenyModList);
-		safe_free(dmod);
-	}
-	for (rmod = ReqModList; rmod; rmod = rnext)
-	{
-		rnext = rmod->next;
-		safe_free(rmod->name);
-		safe_free(rmod->minversion);
-		DelListItem(rmod, ReqModList);
-		safe_free(rmod);
-	}
-	DenyModList = NULL;
-	ReqModList = NULL;
-	return MOD_SUCCESS;
-}
-
-Module *find_modptr_byname(char *name, unsigned strict)
-{
-	Module *mod;
-	for (mod = Modules; mod; mod = mod->next)
-	{
-		// Let's not be too strict with the name
-		if (!strcasecmp(mod->header->name, name))
-		{
-			if (strict && !(mod->flags & MODFLAG_LOADED))
-				mod = NULL;
-			return mod;
-		}
-	}
-	return NULL;
-}
-
-DenyMod *find_denymod_byname(char *name)
-{
-	DenyMod *dmod;
-	for (dmod = DenyModList; dmod; dmod = dmod->next)
-	{
-		if (!strcasecmp(dmod->name, name))
-			return dmod;
-	}
-	return NULL;
-}
-
-ReqMod *find_reqmod_byname(char *name)
-{
-	ReqMod *rmod;
-	for (rmod = ReqModList; rmod; rmod = rmod->next)
-	{
-		if (!strcasecmp(rmod->name, name))
-			return rmod;
-	}
-	return NULL;
-}
-
-int reqmods_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	if (type == CONFIG_DENY)
-		return reqmods_configtest_deny(cf, ce, type, errs);
-
-	if (type == CONFIG_REQUIRE)
-		return reqmods_configtest_require(cf, ce, type, errs);
-
-	return 0;
-}
-
-int reqmods_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	if (type == CONFIG_DENY)
-		return reqmods_configrun_deny(cf, ce, type);
-
-	if (type == CONFIG_REQUIRE)
-		return reqmods_configrun_require(cf, ce, type);
-
-	return 0;
-}
-
-int reqmods_configtest_deny(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-	int has_name, has_reason;
-
-	// We are only interested in deny module { }
-	if (strcmp(ce->value, "module"))
-		return 0;
-
-	has_name = has_reason = 0;
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strlen(cep->name))
-		{
-			config_error("%s:%i: blank directive for deny module { } block", cep->file->filename, cep->line_number);
-			errors++;
-			continue;
-		}
-
-		if (!cep->value || !strlen(cep->value))
-		{
-			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->name, "name"))
-		{
-			if (has_name)
-			{
-				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->value, 0))
-			{
-				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->name, "reason")) // Optional
-		{
-			// Still check for duplicate directives though
-			if (has_reason)
-			{
-				config_error("%s:%i: duplicate %s for deny module { } block", cep->file->filename, cep->line_number, cep->name);
-				errors++;
-				continue;
-			}
-			has_reason = 1;
-			continue;
-		}
-
-		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->file->filename, ce->line_number);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int reqmods_configrun_deny(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-	DenyMod *dmod;
-
-	if (strcmp(ce->value, "module"))
-		return 0;
-
-	dmod = safe_alloc(sizeof(DenyMod));
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "name"))
-		{
-			safe_strdup(dmod->name, cep->value);
-			continue;
-		}
-
-		if (!strcmp(cep->name, "reason"))
-		{
-			safe_strdup(dmod->reason, cep->value);
-			continue;
-		}
-	}
-
-	// Just use a default reason if none was specified (since it's optional)
-	if (!dmod->reason || !strlen(dmod->reason))
-		 safe_strdup(dmod->reason, "no reason");
-	AddListItem(dmod, DenyModList);
-	return 1;
-}
-
-int reqmods_configtest_require(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-	int has_name, has_minversion;
-
-	// We are only interested in require module { }
-	if (strcmp(ce->value, "module"))
-		return 0;
-
-	has_name = has_minversion = 0;
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strlen(cep->name))
-		{
-			config_error("%s:%i: blank directive for require module { } block", cep->file->filename, cep->line_number);
-			errors++;
-			continue;
-		}
-
-		if (!cep->value || !strlen(cep->value))
-		{
-			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->name, "name"))
-		{
-			if (has_name)
-			{
-				config_error("%s:%i: duplicate %s for require module { } block", cep->file->filename, cep->line_number, cep->name);
-				continue;
-			}
-
-			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->value);
-				errors++;
-			}
-
-			// Let's be nice and let configrun handle adding this module to the list
-			has_name = 1;
-			continue;
-		}
-
-		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->file->filename, cep->line_number, cep->name);
-				errors++;
-				continue;
-			}
-			has_minversion = 1;
-			continue;
-		}
-
-		// 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->file->filename, cep->line_number, cep->name);
-		errors++;
-	}
-
-	if (!has_name)
-	{
-		config_error("%s:%i: missing required 'name' directive for require module { } block", ce->file->filename, ce->line_number);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int reqmods_configrun_require(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-	Module *mod;
-	ReqMod *rmod;
-	char *name, *minversion;
-
-	if (strcmp(ce->value, "module"))
-		return 0;
-
-	name = minversion = NULL;
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "name"))
-		{
-			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->value);
-				continue;
-			}
-
-			name = cep->value;
-			continue;
-		}
-
-		if (!strcmp(cep->name, "min-version"))
-		{
-			minversion = cep->value;
-			continue;
-		}
-	}
-
-	// While technically an error, let's not kill the entire server over it
-	if (!name)
-		return 1;
-
-	rmod = safe_alloc(sizeof(ReqMod));
-	safe_strdup(rmod->name, name);
-	if (minversion)
-		safe_strdup(rmod->minversion, minversion);
-	AddListItem(rmod, ReqModList);
-	return 1;
-}
-
-CMD_FUNC(cmd_smod)
-{
-	char modflag, name[64], *version;
-	char buf[BUFSIZE];
-	char *tmp, *p, *modbuf;
-	Module *mod;
-	DenyMod *dmod;
-	int i;
-	int abort;
-
-	// A non-server client shouldn't really be possible here, but still :D
-	if (!MyConnect(client) || !IsServer(client) || BadPtr(parv[1]))
-		return;
-
-	// Module strings are passed as 1 space-delimited parameter
-	strlcpy(buf, parv[1], sizeof(buf));
-	abort = 0;
-	for (modbuf = strtoken(&tmp, buf, " "); modbuf; modbuf = strtoken(&tmp, NULL, " "))
-	{
-		/* The order of checks is:
-		 * 1: deny module { } -- SQUIT always
-		 * 2 (if module not loaded): require module { } -- SQUIT always
-		 * 3 (if module not loaded): warn, but only if MOD_OPT_GLOBAL
-		 * 4 (optional, if module loaded only): require module::min-version
-		 */
-		p = strchr(modbuf, ':');
-		if (!p)
-			continue; /* malformed request */
-		modflag = *modbuf; // Get the module flag (FIXME: parses only first letter atm)
-		modbuf = p+1;
-		strlcpy(name, modbuf, sizeof(name)); // Let's work on a copy of the param
-
-		version = strchr(name, ':');
-		if (!version)
-			continue; /* malformed request */
-		*version++ = '\0';
-
-		// Even if a denied module is only required locally, let's still prevent a server that uses it from linking in
-		if ((dmod = find_denymod_byname(name)))
-		{
-			// Send this particular notice to local opers only
-			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;
-		}
-
-		// Doing a strict check for the module being fully loaded so we can emit an alert in that case too :>
-		mod = find_modptr_byname(name, 1);
-		if (!mod)
-		{
-			/* Since only the server missing the module will report it, we need to broadcast the warning network-wide ;]
-			 * Obviously we won't take any real action if the module seems to be locally required only, except if it's marked as required
-			 */
-			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
-				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')
-			{
-				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;
-		}
-
-		// Further checks are only necessary for explicitly required mods
-		if (modflag != 'R')
-			continue;
-
-		// Module is loaded on both servers and the other end is require { }'ing a specific module version
-		// An explicit version was specified in require module { } but our module version is less than that
-		if (*version != '*' && strnatcasecmp(mod->header->version, version) < 0)
-		{
-			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)
-	{
-		exit_client_fmt(client, NULL, "Link aborted due to missing or banned modules (see previous errors)");
-		return;
-	}
-}
-
-int reqmods_hook_serverconnect(Client *client)
-{
-	/* This function simply dumps a list of modules and their version to the other server,
-	 * which will then run through the received list and check the names/versions
-	 */
-	char modflag;
-	char modbuf[64];
-	char *modversion;
-	/* Try to use a large buffer, but take into account the hostname, command, spaces, etc */
-	char sendbuf[BUFSIZE - HOSTLEN - 16];
-	Module *mod;
-	ReqMod *rmod;
-	size_t len, modlen;
-
-	/* Let's not have leaves directly connected to the hub send their module list to other *leaves* as well =]
-	 * Since the hub will introduce all servers currently linked to it, this hook is actually called for every separate node
-	 */
-	if (!MyConnect(client))
-		return HOOK_CONTINUE;
-
-	sendbuf[0] = '\0';
-	len = 0;
-
-	/* At this stage we don't care if a module isn't global (or not fully loaded), we'll dump all modules so we can properly deny
-	 * certain ones across the network
-	 * Also, the G flag is only used for modules that tag themselves as global, since we're keeping separate lists for require (R flag) and deny
-	 */
-	for (mod = Modules; mod; mod = mod->next)
-	{
-		modflag = SMOD_FLAG_LOCAL;
-		modversion = mod->header->version;
-
-		// require { }'d modules should be loaded on this server anyways, meaning we don't have to use a separate loop for those =]
-		if ((rmod = find_reqmod_byname(mod->header->name)))
-		{
-			// require module::min-version overrides the version found in the module's header
-			modflag = SMOD_FLAG_REQUIRED;
-			modversion = (rmod->minversion ? rmod->minversion : "*");
-		}
-
-		else if ((mod->options & MOD_OPT_GLOBAL))
-			modflag = SMOD_FLAG_GLOBAL;
-
-		ircsnprintf(modbuf, sizeof(modbuf), "%c:%s:%s", modflag, mod->header->name, modversion);
-		modlen = strlen(modbuf);
-		if (len + modlen + 2 > sizeof(sendbuf)) // Account for space and nullbyte, otherwise the last module entry might be cut off
-		{
-			// "Flush" current list =]
-			sendto_one(client, NULL, ":%s %s :%s", me.id, MSG_SMOD, sendbuf);
-			sendbuf[0] = '\0';
-			len = 0;
-		}
-
-		/* Maybe account for the space between modules, can't do this earlier because otherwise the ircsnprintf() would skip past the nullbyte
-		 * of the previous module (which in turn terminates the string prematurely)
-		 */
-		ircsnprintf(sendbuf + len, sizeof(sendbuf) - len, "%s%s", (len > 0 ? " " : ""), modbuf);
-		if (len)
-			len++;
-		len += modlen;
-	}
-
-	// May have something left
-	if (sendbuf[0])
-		sendto_one(client, NULL, ":%s %s :%s", me.id, MSG_SMOD, sendbuf);
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c
@@ -1,409 +0,0 @@
-/*
- * Restrict specific commands unless certain conditions have been met
- * (C) Copyright 2019 Gottem 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 = {
-	"restrict-commands",
-	"1.0.2",
-	"Restrict specific commands unless certain conditions have been met",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-typedef struct RestrictedCommand RestrictedCommand;
-struct RestrictedCommand {
-	RestrictedCommand *prev, *next;
-	char *cmd;
-	char *conftag;
-	SecurityGroup *except;
-};
-
-typedef struct {
-	char *conftag;
-	char *cmd;
-} CmdMap;
-
-// Forward declarations
-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, 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
-static ModuleInfo ModInf;
-RestrictedCommand *RestrictedCommandList = NULL;
-CmdMap conf_cmdmaps[] = {
-	// These are special cases in which we can't override the command, so they are handled through hooks instead
-	{ "channel-message", "PRIVMSG" },
-	{ "channel-notice", "NOTICE" },
-	{ "private-message", "PRIVMSG" },
-	{ "private-notice", "NOTICE" },
-	{ NULL, NULL, }, // REQUIRED for the loop to properly work
-};
-
-MOD_TEST()
-{
-	memcpy(&ModInf, modinfo, modinfo->size);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rcmd_configtest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, rcmd_configrun);
-
-	// Due to the nature of PRIVMSG/NOTICE we're gonna need to hook into PRE_* stuff instead of using command overrides
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, -1000000, rcmd_can_send_to_channel);
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, -1000000, rcmd_can_send_to_user);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
-	{
-		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	RestrictedCommand *rcmd, *next;
-	for (rcmd = RestrictedCommandList; rcmd; rcmd = next)
-	{
-		next = rcmd->next;
-		safe_free(rcmd->conftag);
-		safe_free(rcmd->cmd);
-		free_security_group(rcmd->except);
-		DelListItem(rcmd, RestrictedCommandList);
-		safe_free(rcmd);
-	}
-	RestrictedCommandList = NULL;
-	return MOD_SUCCESS;
-}
-
-const char *find_cmd_byconftag(const char *conftag) {
-	CmdMap *cmap;
-	for (cmap = conf_cmdmaps; cmap->conftag; cmap++)
-	{
-		if (!strcmp(cmap->conftag, conftag))
-			return cmap->cmd;
-	}
-	return NULL;
-}
-
-RestrictedCommand *find_restrictions_bycmd(const char *cmd) {
-	RestrictedCommand *rcmd;
-	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
-	{
-		if (!strcasecmp(rcmd->cmd, cmd))
-			return rcmd;
-	}
-	return NULL;
-}
-
-RestrictedCommand *find_restrictions_byconftag(const char *conftag) {
-	RestrictedCommand *rcmd;
-	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
-	{
-		if (rcmd->conftag && !strcmp(rcmd->conftag, conftag))
-			return rcmd;
-	}
-	return NULL;
-}
-
-int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	int warn_disable = 0;
-	ConfigEntry *cep, *cep2;
-
-	// We are only interested in set::restrict-commands
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "restrict-commands"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		for (cep2 = cep->items; cep2; cep2 = cep2->next)
-		{
-			if (!strcmp(cep2->name, "disable"))
-			{
-				config_warn("%s:%i: set::restrict-commands::%s: the 'disable' option has been removed.",
-				            cep2->file->filename, cep2->line_number, cep->name);
-				if (!warn_disable)
-				{
-					config_warn("Simply remove 'disable yes;' from the configuration file and "
-				                   "it will have the same effect without it (will disable the command).");
-					warn_disable = 1;
-				}
-				continue;
-			}
-
-			if (!strcmp(cep2->name, "except"))
-			{
-				test_match_block(cf, cep2, &errors);
-				continue;
-			}
-
-			if (!cep2->value)
-			{
-				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->name, "connect-delay"))
-			{
-				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->file->filename, cep2->line_number, cep->name);
-					errors++;
-				}
-				continue;
-			}
-
-			if (!strcmp(cep2->name, "exempt-identified"))
-				continue;
-
-			if (!strcmp(cep2->name, "exempt-webirc"))
-				continue;
-
-			if (!strcmp(cep2->name, "exempt-tls"))
-				continue;
-
-			if (!strcmp(cep2->name, "exempt-reputation-score"))
-			{
-				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->file->filename, cep2->line_number, cep->name);
-					errors++;
-				}
-				continue;
-			}
-
-			config_error("%s:%i: unknown directive set::restrict-commands::%s::%s", cep2->file->filename, cep2->line_number, cep->name, cep2->name);
-			errors++;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep, *cep2;
-	const char *cmd, *conftag;
-	RestrictedCommand *rcmd;
-
-	// We are only interested in set::restrict-commands
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "restrict-commands"))
-		return 0;
-
-	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->name)))
-			conftag = cep->name;
-		else
-			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
-		if (!conftag)
-		{
-			// Let's hope nobody tries to unload the module for PRIVMSG/NOTICE :^)
-			if (!CommandExists(cmd))
-			{
-				config_warn("[restrict-commands] Command '%s' does not exist. Did you mistype? Or is the module providing it not loaded?", cmd);
-				continue;
-			}
-			if (find_restrictions_bycmd(cmd))
-			{
-				config_warn("[restrict-commands] Multiple set::restrict-commands items for command '%s'. "
-				            "Only one config block will be effective.",
-				            cmd);
-				continue;
-			}
-			if (!CommandOverrideAdd(ModInf.handle, cmd, 0, rcmd_override))
-			{
-				config_warn("[restrict-commands] Failed to add override for '%s' (NO RESTRICTIONS APPLY)", cmd);
-				continue;
-			}
-		}
-
-		rcmd = safe_alloc(sizeof(RestrictedCommand));
-		safe_strdup(rcmd->cmd, cmd);
-		safe_strdup(rcmd->conftag, conftag);
-		rcmd->except = safe_alloc(sizeof(SecurityGroup));
-
-		for (cep2 = cep->items; cep2; cep2 = cep2->next)
-		{
-			if (!strcmp(cep2->name, "except"))
-			{
-				conf_match_block(cf, cep2, &rcmd->except);
-				continue;
-			}
-
-			if (!cep2->value)
-				continue;
-
-			if (!strcmp(cep2->name, "connect-delay"))
-			{
-				rcmd->except->connect_time = config_checkval(cep2->value, CFG_TIME);
-				continue;
-			}
-
-			if (!strcmp(cep2->name, "exempt-identified"))
-			{
-				rcmd->except->identified = config_checkval(cep2->value, CFG_YESNO);
-				continue;
-			}
-			
-			if (!strcmp(cep2->name, "exempt-webirc"))
-			{
-				rcmd->except->webirc = config_checkval(cep2->value, CFG_YESNO);
-				continue;
-			}
-
-			if (!strcmp(cep2->name, "exempt-tls"))
-			{
-				rcmd->except->tls = config_checkval(cep2->value, CFG_YESNO);
-				continue;
-			}
-
-			if (!strcmp(cep2->name, "exempt-reputation-score"))
-			{
-				rcmd->except->reputation_score = atoi(cep2->value);
-				continue;
-			}
-		}
-		AddListItem(rcmd, RestrictedCommandList);
-	}
-
-	return 1;
-}
-
-int rcmd_canbypass(Client *client, RestrictedCommand *rcmd)
-{
-	if (!client || !rcmd)
-		return 1;
-	if (user_allowed_by_security_group(client, rcmd->except))
-		return 1;
-	return 0;
-}
-
-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;
-
-	return HOOK_CONTINUE;
-}
-
-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))
-		return HOOK_CONTINUE; /* bypass/exempt */
-
-	if (rcmd_block_message(client, *text, sendtype, errmsg, "user", (sendtype == SEND_TYPE_NOTICE ? "private-notice" : "private-message")))
-		return HOOK_DENY;
-
-	return HOOK_CONTINUE;
-}
-
-int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag)
-{
-	RestrictedCommand *rcmd;
-	static char errbuf[256];
-
-	// Let's allow non-local users, opers and U:Lines early =]
-	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
-		return 0;
-
-	rcmd = find_restrictions_byconftag(conftag);
-	if (rcmd && !rcmd_canbypass(client, rcmd))
-	{
-		int notice = (sendtype == SEND_TYPE_NOTICE ? 1 : 0); // temporary hack FIXME !!!
-		if (rcmd->except->connect_time)
-		{
-			ircsnprintf(errbuf, sizeof(errbuf),
-				    "You cannot send %ss to %ss until you've been connected for %ld seconds or more",
-				    (notice ? "notice" : "message"), display, rcmd->except->connect_time);
-		} else {
-			ircsnprintf(errbuf, sizeof(errbuf),
-				    "Sending of %ss to %ss been disabled by the network administrators",
-				    (notice ? "notice" : "message"), display);
-		}
-		*errmsg = errbuf;
-		return 1;
-	}
-
-	// No restrictions apply, process command as normal =]
-	return 0;
-}
-
-CMD_OVERRIDE_FUNC(rcmd_override)
-{
-	RestrictedCommand *rcmd;
-
-	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
-	{
-		CALL_NEXT_COMMAND_OVERRIDE();
-		return;
-	}
-
-	rcmd = find_restrictions_bycmd(ovr->command->cmd);
-	if (rcmd && !rcmd_canbypass(client, rcmd))
-	{
-		if (rcmd->except->connect_time)
-		{
-			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
-			               "%s :You must be connected for at least %ld seconds before you can use this command",
-			               ovr->command->cmd, rcmd->except->connect_time);
-		} else {
-			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
-			               "%s :This command is disabled by the network administrator",
-			               ovr->command->cmd);
-		}
-		return;
-	}
-
-	// No restrictions apply, process command as normal =]
-	CALL_NEXT_COMMAND_OVERRIDE();
-}
diff --git a/src/modules/rmtkl.c b/src/modules/rmtkl.c
@@ -1,295 +0,0 @@
-/*
- * Easily remove *-Lines in bulk
- * (C) Copyright 2019 Gottem 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 = {
-	"rmtkl",
-	"1.4",
-	"Adds /rmtkl command to easily remove *-Lines in bulk",
-	"Gottem and the UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-#define IsParam(x) (parc > (x) && !BadPtr(parv[(x)]))
-#define IsNotParam(x) (parc <= (x) || BadPtr(parv[(x)]))
-
-typedef struct {
-	int type;
-	char flag;
-	char *txt;
-	char *operpriv;
-} TKLType;
-
-static void dump_str(Client *client, const char **buf);
-static TKLType *find_TKLType_by_flag(char flag);
-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[] = {
-	{ TKL_KILL, 'k', "K-Line", "server-ban:kline:remove" },
-	{ TKL_ZAP, 'z',	"Z-Line", "server-ban:zline:local:remove" },
-	{ TKL_KILL | TKL_GLOBAL, 'G', "G-Line", "server-ban:gline:remove" },
-	{ TKL_ZAP | TKL_GLOBAL, 'Z', "Global Z-Line", "server-ban:zline:global:remove" },
-	{ TKL_SHUN | TKL_GLOBAL, 's', "Shun", "server-ban:shun:remove" },
-//	{ TKL_SPAMF | TKL_GLOBAL, 'F', "Global Spamfilter", "server-ban:spamfilter:remove" }, TODO: re-add spamfilter support
-	{ 0, 0, "Unknown *-Line", 0 },
-};
-
-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.",
-	"Syntax:",
-	"    \002/rmtkl\002 \037user@host\037 \037type\037 [\037comment\037] [\037-skipperm\037] [\037-silent\037]",
-	"The \037user@host\037 field is a wildcard mask to match the target of a ban.",
-	"The \037type\037 field may contain any number of the following characters:",
-	"    k, z, G, Z, s, F and *",
-	"    These correspond to (local) K-Line, (local) Z-Line, G-Line, Global Z-Line, (global) Shun and (global) Spamfilter",
-	"    (asterisk includes every type besides F)",
-	"The \037comment\037 field is also a wildcard mask to match the reason text of a ban. If specified, it must always",
-	"come \037before\037 the options starting with \002-\002.",
-	"Examples:",
-	"    - \002/rmtkl * *\002",
-	"        [remove \037all\037 supported TKLs except spamfilters]",
-	"    - \002/rmtkl *@*.mx GZ\002 * -skipperm",
-	"        [remove all Mexican G/Z-Lines while skipping over permanent ones]",
-/*	"    - \002/rmtkl * * *Zombie*\002",
-	"        [remove all non-spamfilter bans having \037Zombie\037 in the reason field]", TODO: re-add spamfilter support  */
-	"*** \002End of help\002 ***",
-	NULL
-};
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	if (CommandExists("RMTKL"))
-	{
-		config_error("Command RMTKL already exists");
-		return MOD_FAILED;
-	}
-	CommandAdd(modinfo->handle, "RMTKL", rmtkl, 5, CMD_USER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
-	{
-		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-static void dump_str(Client *client, const char **buf)
-{
-	if (!MyUser(client))
-		return;
-
-	// Using sendto_one() instead of sendnumericfmt() because the latter strips indentation and stuff ;]
-	for (; *buf != NULL; buf++)
-		sendto_one(client, NULL, ":%s %03d %s :%s", me.name, RPL_TEXT, client->name, *buf);
-
-	// Let user take 8 seconds to read it
-	add_fake_lag(client, 8000);
-}
-
-static TKLType *find_TKLType_by_flag(char flag)
-{
-	TKLType *t;
-	for (t = tkl_types; t->type; t++)
-		if (t->flag == flag)
-			break;
-	return t;
-}
-
-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, const char *uhmask, const char *commentmask, int skipperm, int silent)
-{
-	if (tkl->type != tkltype->type)
-		return 0;
-
-	// Let's not touch Q-Lines
-	if (tkl->type & TKL_NAME)
-		return 0;
-
-	/* Don't touch TKL's that were added through config */
-	if (tkl->flags & TKL_FLAG_CONFIG)
-		return 0;
-
-	if (TKLIsSpamfilter(tkl))
-	{
-#if 0
-//FIXME: re-add spamfilter support
-		// Is a spamfilter added through IRC, we can remove this if the "user" mask matches the reason
-		if (!match_simple(uhmask, tkl->reason))
-			return 0;
-#endif
-	} else
-	if (TKLIsServerBan(tkl))
-	{
-		if (!match_simple(uhmask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
-			return 0;
-
-		if (commentmask && !match_simple(commentmask, tkl->ptr.serverban->reason))
-			return 0;
-	} else
-		return 0;
-
-	if (skipperm && tkl->expire_at == 0)
-		return 0;
-
-	if (!silent)
-		sendnotice_tkl_del(client->name, tkl);
-
-	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
-
-	if (tkl->type & TKL_SHUN)
-		tkl_check_local_remove_shun(tkl);
-	tkl_del_line(tkl);
-	return 1;
-}
-
-CMD_FUNC(rmtkl)
-{
-	TKL *tkl, *next;
-	TKLType *tkltype;
-	const char *types, *uhmask, *commentmask, *p;
-	char tklchar;
-	int tklindex, tklindex2, skipperm, silent;
-	unsigned int count;
-	char broadcast[BUFSIZE];
-
-	if (!IsULine(client) && !IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (IsNotParam(1))
-	{
-		dump_str(client, rmtkl_help);
-		return;
-	}
-
-	if (IsNotParam(2))
-	{
-		sendnotice(client, "Not enough parameters. Type /RMTKL for help.");
-		return;
-	}
-
-	uhmask = parv[1];
-	types = parv[2];
-	commentmask = NULL;
-	skipperm = 0;
-	silent = 0;
-	count = 0;
-	snprintf(broadcast, sizeof(broadcast), ":%s RMTKL %s %s", client->name, types, uhmask);
-
-	// Check for optionals
-	if (IsParam(3))
-	{
-		// Comment mask, if specified, always goes third
-		if (*parv[3] != '-')
-			commentmask = parv[3];
-		else
-			rmtkl_check_options(parv[3], &skipperm, &silent);
-		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[3]);
-	}
-	if (IsParam(4))
-	{
-		rmtkl_check_options(parv[4], &skipperm, &silent);
-		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[4]);
-	}
-	if (IsParam(5))
-	{
-		rmtkl_check_options(parv[5], &skipperm, &silent);
-		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[5]);
-	}
-
-	// Wildcard resolves to everything but 'F', since spamfilters are a bit special
-	if (strchr(types, '*'))
-		types = "kzGZs";
-
-	// Make sure the oper actually has the privileges to remove the *-Lines he wants
-	if (!IsULine(client))
-	{
-		for (p = types; *p; p++)
-		{
-			tkltype = find_TKLType_by_flag(*p);
-			if (!tkltype->type)
-				continue;
-
-			if (!ValidatePermissionsForPath(tkltype->operpriv, client, NULL, NULL, NULL))
-			{
-				sendnumeric(client, ERR_NOPRIVILEGES);
-				return;
-			}
-		}
-	}
-
-	// Broadcast the command to other servers *before* we proceed with removal
-	sendto_server(NULL, 0, 0, NULL, "%s", broadcast);
-
-	// Loop over all supported types
-	for (tkltype = tkl_types; tkltype->type; tkltype++) {
-		if (!strchr(types, tkltype->flag))
-			continue;
-
-		// Loop over all TKL entries, first try the ones in the hash table
-		tklchar = tkl_typetochar(tkltype->type);
-		tklindex = tkl_ip_hash_type(tklchar);
-		if (tklindex >= 0)
-		{
-			for (tklindex2 = 0; tklindex2 < TKLIPHASHLEN2; tklindex2++)
-			{
-				for (tkl = tklines_ip_hash[tklindex][tklindex2]; tkl; tkl = next)
-				{
-					next = tkl->next;
-					count += rmtkl_tryremove(client, tkltype, tkl, uhmask, commentmask, skipperm, silent);
-				}
-			}
-		}
-
-		// Then the regular *-Lines (not an else because certain TKLs might have a hash as well as a plain linked list)
-		tklindex = tkl_hash(tklchar);
-		for (tkl = tklines[tklindex]; tkl; tkl = next)
-		{
-			next = tkl->next;
-			count += rmtkl_tryremove(client, tkltype, tkl, uhmask, commentmask, skipperm, silent);
-		}
-	}
-
-	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/rpc/Makefile.in b/src/modules/rpc/Makefile.in
@@ -1,55 +0,0 @@
-#************************************************************************
-#*   IRC - Internet Relay Chat, src/modules/rpc/Makefile
-#*   Copyright (C) Carsten V. Munk 2001 & The UnrealIRCd Team
-#*
-#*   This program is free software; you can redistribute it and/or modify
-#*   it under the terms of the GNU General Public License as published by
-#*   the Free Software Foundation; either version 1, or (at your option)
-#*   any later version.
-#*
-#*   This program is distributed in the hope that it will be useful,
-#*   but WITHOUT ANY WARRANTY; without even the implied warranty of
-#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#*   GNU General Public License for more details.
-#*
-#*   You should have received a copy of the GNU General Public License
-#*   along with this program; if not, write to the Free Software
-#*   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#*/
-
-CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
-
-INCLUDES = ../../include/channel.h \
-	../../include/common.h ../../include/config.h ../../include/dbuf.h \
-	../../include/dynconf.h ../../include/fdlist.h ../../include/h.h \
-	../../include/ircsprintf.h \
-	../../include/license.h \
-	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
-	../../include/numeric.h ../../include/dns.h \
-	../../include/resource.h ../../include/setup.h \
-	../../include/struct.h ../../include/sys.h \
-	../../include/types.h \
-	../../include/version.h ../../include/whowas.h
-
-R_MODULES= \
-	rpc.so stats.so user.so server.so channel.so server_ban.so \
-	server_ban_exception.so name_ban.so spamfilter.so \
-	log.so whowas.so
-
-MODULES=$(R_MODULES)
-MODULEFLAGS=@MODULEFLAGS@
-RM=@RM@
-
-.SUFFIXES:
-.SUFFIXES: .c .h .so
-
-all: build
-
-build: $(MODULES)
-
-clean:
-	$(RM) -f *.o *.so *~ core
-
-%.so: %.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o $@ $<
diff --git a/src/modules/rpc/channel.c b/src/modules/rpc/channel.c
@@ -1,230 +0,0 @@
-/* channel.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/channel",
-	"1.0.5",
-	"channel.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-void rpc_channel_list(Client *client, json_t *request, json_t *params);
-void rpc_channel_get(Client *client, json_t *request, json_t *params);
-void rpc_channel_set_mode(Client *client, json_t *request, json_t *params);
-void rpc_channel_set_topic(Client *client, json_t *request, json_t *params);
-void rpc_channel_kick(Client *client, json_t *request, json_t *params);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "channel.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_channel_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/channel] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "channel.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_channel_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/channel] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "channel.set_mode";
-	r.call = rpc_channel_set_mode;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/channel] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "channel.set_topic";
-	r.call = rpc_channel_set_topic;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/channel] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "channel.kick";
-	r.call = rpc_channel_kick;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/channel] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void rpc_channel_list(Client *client, json_t *request, json_t *params)
-{
-	json_t *result, *list, *item;
-	Channel *channel;
-	int details;
-
-	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 1);
-	if (details >= 5)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of >=5 is not allowed in this call");
-		return;
-	}
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (channel = channels; channel; channel=channel->nextch)
-	{
-		item = json_object();
-		json_expand_channel(item, NULL, channel, details);
-		json_array_append_new(list, item);
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-void rpc_channel_get(Client *client, json_t *request, json_t *params)
-{
-	json_t *result, *item;
-	const char *channelname;
-	Channel *channel;
-	int details;
-
-	REQUIRE_PARAM_STRING("channel", channelname);
-	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 3);
-
-	if (!(channel = find_channel(channelname)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_channel(result, "channel", channel, details);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-void rpc_channel_set_mode(Client *client, json_t *request, json_t *params)
-{
-	json_t *result, *item;
-	const char *channelname, *modes, *parameters;
-	MessageTag *mtags = NULL;
-	Channel *channel;
-
-	REQUIRE_PARAM_STRING("channel", channelname);
-	REQUIRE_PARAM_STRING("modes", modes);
-	REQUIRE_PARAM_STRING("parameters", parameters);
-
-	if (!(channel = find_channel(channelname)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
-		return;
-	}
-
-	mtag_add_issued_by(&mtags, client, NULL);
-	set_channel_mode(channel, mtags, modes, parameters);
-	safe_free_message_tags(mtags);
-
-	/* Simply return success */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-void rpc_channel_set_topic(Client *client, json_t *request, json_t *params)
-{
-	json_t *result, *item;
-	const char *channelname, *topic, *set_by=NULL, *str;
-	Channel *channel;
-	time_t set_at = 0;
-	MessageTag *mtags = NULL;
-
-	REQUIRE_PARAM_STRING("channel", channelname);
-	REQUIRE_PARAM_STRING("topic", topic);
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	OPTIONAL_PARAM_STRING("set_at", str);
-	if (str)
-		set_at = server_time_to_unix_time(str);
-
-	if (!(channel = find_channel(channelname)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
-		return;
-	}
-
-	mtag_add_issued_by(&mtags, client, NULL);
-	set_channel_topic(&me, channel, mtags, topic, set_by, set_at);
-	safe_free_message_tags(mtags);
-
-	/* Simply return success */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-void rpc_channel_kick(Client *client, json_t *request, json_t *params)
-{
-	json_t *result, *item;
-	const char *channelname, *nick, *reason;
-	MessageTag *mtags = NULL;
-	Channel *channel;
-	Client *acptr;
-	time_t set_at = 0;
-
-	REQUIRE_PARAM_STRING("channel", channelname);
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	if (!(channel = find_channel(channelname)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
-		return;
-	}
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	mtag_add_issued_by(&mtags, client, NULL);
-	kick_user(mtags, channel, &me, acptr, reason);
-	safe_free_message_tags(mtags);
-
-	/* Simply return success
-	 * TODO: actually we can do a find_member() check and such to see if the user is kicked!
-	 * that is, assuming single channel ;)
-	 */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/log.c b/src/modules/rpc/log.c
@@ -1,139 +0,0 @@
-/* log.* RPC calls
- * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/log",
-	"1.0.0",
-	"log.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params);
-void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params);
-int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "log.subscribe";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_log_hook_subscribe;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/log] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	memset(&r, 0, sizeof(r));
-	r.method = "log.unsubscribe";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_log_hook_unsubscribe;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/log] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	HookAdd(modinfo->handle, HOOKTYPE_LOG, 0, rpc_log_hook);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params)
-{
-	json_t *result;
-	json_t *sources;
-	size_t index;
-	json_t *value;
-	const char *str;
-	LogSource *s;
-
-	sources = json_object_get(params, "sources");
-	if (!sources || !json_is_array(sources))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "sources");
-		return;
-	}
-
-	/* Erase the old subscriptions first */
-	free_log_sources(client->rpc->log_sources);
-	client->rpc->log_sources = NULL;
-
-	/* Add subscriptions... */
-	json_array_foreach(sources, index, value)
-	{
-		str = json_get_value(value);
-		if (!str)
-			continue;
-
-		s = add_log_source(str);
-		AddListItem(s, client->rpc->log_sources);
-	}
-
-	result = json_boolean(1);
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-/** log.unsubscribe: unsubscribe from all log messages */
-void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params)
-{
-	json_t *result;
-
-	free_log_sources(client->rpc->log_sources);
-	client->rpc->log_sources = NULL;
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf)
-{
-	Client *client;
-	json_t *request = NULL;
-
-	if (!strcmp(subsystem, "rawtraffic"))
-		return 0;
-
-	list_for_each_entry(client, &unknown_list, lclient_node)
-	{
-		if (IsRPC(client) && client->rpc->log_sources &&
-		    log_sources_match(client->rpc->log_sources, loglevel, subsystem, event_id, 0))
-		{
-			if (request == NULL)
-			{
-				/* Lazy initalization */
-				request = json_object();
-				json_object_set_new(request, "method", json_string_unreal("log.event"));
-			}
-			rpc_response(client, request, json);
-		}
-	}
-
-	if (request)
-		json_decref(request);
-
-	return 0;
-}
diff --git a/src/modules/rpc/name_ban.c b/src/modules/rpc/name_ban.c
@@ -1,241 +0,0 @@
-/* name_ban.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/name_ban",
-	"1.0.1",
-	"name_ban.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_name_ban_list);
-RPC_CALL_FUNC(rpc_name_ban_get);
-RPC_CALL_FUNC(rpc_name_ban_del);
-RPC_CALL_FUNC(rpc_name_ban_add);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "name_ban.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_name_ban_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/name_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "name_ban.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_name_ban_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/name_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "name_ban.del";
-	r.call = rpc_name_ban_del;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/name_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "name_ban.add";
-	r.call = rpc_name_ban_add;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/name_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-RPC_CALL_FUNC(rpc_name_ban_list)
-{
-	json_t *result, *list, *item;
-	int index;
-	TKL *tkl;
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			if (TKLIsNameBan(tkl))
-			{
-				item = json_object();
-				json_expand_tkl(item, NULL, tkl, 1);
-				json_array_append_new(list, item);
-			}
-		}
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-TKL *my_find_tkl_nameban(const char *name)
-{
-	TKL *tkl;
-
-	for (tkl = tklines[tkl_hash('Q')]; tkl; tkl = tkl->next)
-	{
-		if (!TKLIsNameBan(tkl))
-			continue;
-		if (!strcasecmp(name, tkl->ptr.nameban->name))
-			return tkl;
-	}
-	return NULL;
-}
-
-RPC_CALL_FUNC(rpc_name_ban_get)
-{
-	json_t *result, *list, *item;
-	const char *name;
-	TKL *tkl;
-
-	REQUIRE_PARAM_STRING("name", name);
-
-	if (!(tkl = my_find_tkl_nameban(name)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_name_ban_del)
-{
-	json_t *result, *list, *item;
-	const char *name;
-	const char *set_by;
-	TKL *tkl;
-	const char *tkllayer[7];
-
-	REQUIRE_PARAM_STRING("name", name);
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	if (!(tkl = my_find_tkl_nameban(name)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-
-	tkllayer[0] = NULL;
-	tkllayer[1] = "-";
-	tkllayer[2] = "Q";
-	tkllayer[3] = "*";
-	tkllayer[4] = name;
-	tkllayer[5] = set_by;
-	tkllayer[6] = NULL;
-	cmd_tkl(&me, NULL, 6, tkllayer);
-
-	if (!my_find_tkl_nameban(name))
-	{
-		rpc_response(client, request, result);
-	} else {
-		/* Actually this may not be an internal error, it could be an
-		 * incorrect request, such as asking to remove a config-based ban.
-		 */
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
-	}
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_name_ban_add)
-{
-	json_t *result, *list, *item;
-	const char *name;
-	const char *str;
-	const char *reason;
-	const char *set_by;
-	time_t tkl_expire_at;
-	time_t tkl_set_at = TStime();
-	TKL *tkl;
-
-	REQUIRE_PARAM_STRING("name", name);
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	/* Duration / expiry time */
-	if ((str = json_object_get_string(params, "duration_string")))
-	{
-		tkl_expire_at = config_checkval(str, CFG_TIME);
-		if (tkl_expire_at > 0)
-			tkl_expire_at = TStime() + tkl_expire_at;
-	} else
-	if ((str = json_object_get_string(params, "expire_at")))
-	{
-		tkl_expire_at = server_time_to_unix_time(str);
-	} else
-	{
-		/* Never expire */
-		tkl_expire_at = 0;
-	}
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
-		return;
-	}
-
-	if (my_find_tkl_nameban(name))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban already exists");
-		return;
-	}
-
-	tkl = tkl_add_nameban(TKL_NAME|TKL_GLOBAL, name, 0, reason,
-	                      set_by, tkl_expire_at, tkl_set_at,
-	                      0);
-
-	if (!tkl)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
-		return;
-	}
-
-	tkl_added(client, tkl);
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/rpc.c b/src/modules/rpc/rpc.c
@@ -1,1922 +0,0 @@
-/*
- * RPC module - for remote management of UnrealIRCd
- * (C)Copyright 2022 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- */
-   
-#include "unrealircd.h"
-#include "dns.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"rpc/rpc",
-	"1.0.4",
-	"RPC module for remote management",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/** Maximum length of an rpc-user THIS { }.
- * As we use the "RPC:" prefix it is nicklen minus that.
- */
-#define RPCUSERLEN (NICKLEN-4)
-
-/** Timers can be minimum every <this> msec */
-#define RPC_MINIMUM_TIMER_MSEC 250
-
-/* Structs */
-typedef struct RPCUser RPCUser;
-struct RPCUser {
-	RPCUser *prev, *next;
-	SecurityGroup *match;
-	char *name;
-	AuthConfig *auth;
-};
-
-typedef struct RRPC RRPC;
-struct RRPC {
-	RRPC *prev, *next;
-	int request;
-	char source[IDLEN+1];
-	char destination[IDLEN+1];
-	char *requestid;
-	dbuf data;
-};
-
-typedef struct OutstandingRRPC OutstandingRRPC;
-struct OutstandingRRPC {
-	OutstandingRRPC *prev, *next;
-	time_t sent;
-	char source[IDLEN+1];
-	char destination[IDLEN+1];
-	char *requestid;
-};
-
-typedef struct RPCTimer RPCTimer;
-struct RPCTimer {
-	RPCTimer *prev, *next;
-	long every_msec;
-	Client *client;
-	char *timer_id;
-	json_t *request;
-	struct timeval last_run;
-};
-
-/* Forward declarations */
-int rpc_config_test_listen(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int rpc_config_run_ex_listen(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
-int rpc_config_test_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int rpc_config_run_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type);
-int rpc_client_accept(Client *client);
-int rpc_pre_local_handshake_timeout(Client *client, const char **comment);
-void rpc_client_handshake_unix_socket(Client *client);
-void rpc_client_handshake_web(Client *client);
-int rpc_handle_webrequest(Client *client, WebRequest *web);
-int rpc_handle_webrequest_websocket(Client *client, WebRequest *web);
-int rpc_websocket_handshake_send_response(Client *client);
-int rpc_handle_webrequest_data(Client *client, WebRequest *web, const char *buf, int len);
-int rpc_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
-int rpc_packet_in_websocket(Client *client, char *readbuf, int length);
-int rpc_packet_in_unix_socket(Client *client, const char *readbuf, int *length);
-void rpc_call_text(Client *client, const char *buf, int len);
-void rpc_call(Client *client, json_t *request);
-void _rpc_response(Client *client, json_t *request, json_t *result);
-void _rpc_error(Client *client, json_t *request, JsonRpcError error_code, const char *error_message);
-void _rpc_error_fmt(Client *client, json_t *request, JsonRpcError error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5)));
-void _rpc_send_request_to_remote(Client *source, Client *target, json_t *request);
-void _rpc_send_response_to_remote(Client *source, Client *target, json_t *response);
-int _rrpc_supported_simple(Client *target, char **problem_server);
-int _rrpc_supported(Client *target, const char *module, const char *minimum_version, char **problem_server);
-int rpc_handle_auth(Client *client, WebRequest *web);
-int rpc_parse_auth_basic_auth(Client *client, WebRequest *web, char **username, char **password);
-int rpc_parse_auth_uri(Client *client, WebRequest *web, char **username, char **password);
-RPC_CALL_FUNC(rpc_rpc_info);
-RPC_CALL_FUNC(rpc_rpc_set_issuer);
-RPC_CALL_FUNC(rpc_rpc_add_timer);
-RPC_CALL_FUNC(rpc_rpc_del_timer);
-CMD_FUNC(cmd_rrpc);
-EVENT(rpc_remote_timeout);
-EVENT(rpc_do_timers);
-json_t *rrpc_data(RRPC *r);
-void free_rrpc_list(ModData *m);
-void free_outstanding_rrpc_list(ModData *m);
-void free_rpc_timer(RPCTimer *r);
-void free_rpc_timer_list(ModData *m);
-void rpc_call_remote(RRPC *r);
-void rpc_response_remote(RRPC *r);
-int rpc_handle_free_client(Client *client);
-int rpc_handle_server_quit(Client *client, MessageTag *mtags);
-int rpc_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child);
-const char *rrpc_md_serialize(ModData *m);
-void rrpc_md_unserialize(const char *str, ModData *m);
-void rrpc_md_free(ModData *m);
-
-/* Macros */
-#define RPC_PORT(client)  ((client->local && client->local->listener) ? client->local->listener->rpc_options : 0)
-#define WSU(client)     ((WebSocketUser *)moddata_client(client, websocket_md).ptr)
-
-/* Global variables */
-ModDataInfo *websocket_md = NULL; /* (imported) */
-RPCUser *rpcusers = NULL;
-RRPC *rrpc_list = NULL;
-OutstandingRRPC *outstanding_rrpc_list = NULL;
-RPCTimer *rpc_timer_list = NULL;
-ModDataInfo *rrpc_md;
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test_listen);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test_rpc_user);
-	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_RESPONSE, _rpc_response);
-	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR, _rpc_error);
-	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR_FMT, TO_VOIDFUNC(_rpc_error_fmt));
-	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_SEND_REQUEST_TO_REMOTE, _rpc_send_request_to_remote);
-	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_SEND_RESPONSE_TO_REMOTE, _rpc_send_response_to_remote);
-	EfunctionAdd(modinfo->handle, EFUNC_RRPC_SUPPORTED, _rrpc_supported);
-	EfunctionAdd(modinfo->handle, EFUNC_RRPC_SUPPORTED_SIMPLE, _rrpc_supported_simple);
-
-	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
-
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT); /* can be NULL */
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, rpc_config_run_ex_listen);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, rpc_config_run_rpc_user);
-	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, -5000, rpc_client_accept);
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT, 0, rpc_pre_local_handshake_timeout);
-	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, rpc_packet_in_unix_socket);
-	HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, rpc_handle_server_quit);
-	HookAdd(modinfo->handle, HOOKTYPE_FREE_CLIENT, 0, rpc_handle_free_client);
-	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, 0, rpc_json_expand_client_server);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "rpc.info";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_rpc_info;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc.info] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	memset(&r, 0, sizeof(r));
-	r.method = "rpc.set_issuer";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_rpc_set_issuer;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc.set_issuer] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	memset(&r, 0, sizeof(r));
-	r.method = "rpc.add_timer";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_rpc_add_timer;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc.add_timer] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	memset(&r, 0, sizeof(r));
-	r.method = "rpc.del_timer";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_rpc_del_timer;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc.del_timer] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "rrpc";
-	mreq.type = MODDATATYPE_CLIENT;
-	mreq.serialize = rrpc_md_serialize;
-	mreq.unserialize = rrpc_md_unserialize;
-	mreq.free = rrpc_md_free;
-	mreq.sync = 1;
-	mreq.self_write = 1;
-	rrpc_md = ModDataAdd(modinfo->handle, mreq);
-	if (!rrpc_md)
-	{
-		config_error("[rpc/rpc] Unable to ModDataAdd() -- too many 3rd party modules loaded perhaps?");
-		abort();
-	}
-
-	LoadPersistentPointer(modinfo, rrpc_list, free_rrpc_list);
-	LoadPersistentPointer(modinfo, outstanding_rrpc_list, free_outstanding_rrpc_list);
-	LoadPersistentPointer(modinfo, rpc_timer_list, free_rpc_timer_list);
-
-	CommandAdd(modinfo->handle, "RRPC", cmd_rrpc, MAXPARA, CMD_SERVER);
-
-	EventAdd(modinfo->handle, "rpc_remote_timeout", rpc_remote_timeout, NULL, 1000, 0);
-	EventAdd(modinfo->handle, "rpc_do_timers", rpc_do_timers, NULL, RPC_MINIMUM_TIMER_MSEC, 0);
-
-	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
-
-	return MOD_SUCCESS;
-}
-
-#define MYRRPCMODULES		me.moddata[rrpc_md->slot].ptr
-#define RRPCMODULES(client)	((NameValuePrioList *)moddata_client(client, rrpc_md).ptr)
-
-void rpc_do_moddata(void)
-{
-	Module *m;
-
-	free_nvplist(MYRRPCMODULES);
-	MYRRPCMODULES = NULL;
-
-	for (m = Modules; m; m = m->next)
-		if (!strncmp(m->header->name, "rpc/", 4))
-			add_nvplist((NameValuePrioList **)&MYRRPCMODULES, 0, m->header->name + 4, m->header->version);
-}
-
-MOD_LOAD()
-{
-	rpc_do_moddata();
-	return MOD_SUCCESS;
-}
-
-void free_config(void)
-{
-	RPCUser *e, *e_next;
-	for (e = rpcusers; e; e = e_next)
-	{
-		e_next = e->next;
-		safe_free(e->name);
-		free_security_group(e->match);
-		Auth_FreeAuthConfig(e->auth);
-		safe_free(e);
-	}
-	rpcusers = NULL;
-}
-
-MOD_UNLOAD()
-{
-	free_config();
-	SavePersistentPointer(modinfo, rrpc_list);
-	SavePersistentPointer(modinfo, outstanding_rrpc_list);
-	SavePersistentPointer(modinfo, rpc_timer_list);
-	return MOD_SUCCESS;
-}
-
-int rpc_config_test_listen(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	int ext = 0;
-	ConfigEntry *cep;
-
-	if (type != CONFIG_LISTEN_OPTIONS)
-		return 0;
-
-	/* We are only interested in listen::options::rpc.. */
-	if (!ce || !ce->name || strcmp(ce->name, "rpc"))
-		return 0;
-
-	/* No options atm */
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int rpc_config_run_ex_listen(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
-{
-	ConfigEntry *cep, *cepp;
-	ConfigItem_listen *l;
-
-	if (type != CONFIG_LISTEN_OPTIONS)
-		return 0;
-
-	/* We are only interrested in listen::options::rpc.. */
-	if (!ce || !ce->name || strcmp(ce->name, "rpc"))
-		return 0;
-
-	l = (ConfigItem_listen *)ptr;
-	l->options |= LISTENER_NO_CHECK_CONNECT_FLOOD;
-	if (l->socket_type == SOCKET_TYPE_UNIX)
-	{
-		l->start_handshake = rpc_client_handshake_unix_socket;
-	} else {
-		l->options |= LISTENER_TLS;
-		l->start_handshake = rpc_client_handshake_web;
-		l->webserver = safe_alloc(sizeof(WebServer));
-		l->webserver->handle_request = rpc_handle_webrequest;
-		l->webserver->handle_body = rpc_handle_webrequest_data;
-	}
-	l->rpc_options = 1;
-
-	return 1;
-}
-
-/** Valid name for rpc-user THISNAME { } ? */
-static int valid_rpc_user_name(const char *str)
-{
-	const char *p;
-
-	if (strlen(str) > RPCUSERLEN)
-		return 0;
-
-	for (p = str; *p; p++)
-		if (!isalnum(*p) && !strchr("_-", *p))
-			return 0;
-
-	return 1;
-}
-
-int rpc_config_test_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	char has_match = 1, has_password = 1;
-	ConfigEntry *cep;
-
-	/* We are only interested in rpc-user { } */
-	if ((type != CONFIG_MAIN) || !ce || !ce->name || strcmp(ce->name, "rpc-user"))
-		return 0;
-
-	if (!ce->value)
-	{
-		config_error("%s:%d: rpc-user block needs to have a name, eg: rpc-user apiuser { }",
-		             ce->file->filename, ce->line_number);
-		*errs = 1;
-		return -1; /* quick return */
-	}
-
-	if (!valid_rpc_user_name(ce->value))
-	{
-		config_error("%s:%d: rpc-user block has invalid name '%s'. "
-		             "Can be max %d long and may only contain a-z, A-Z, 0-9, - and _.",
-		             ce->file->filename, ce->line_number,
-		             ce->value, RPCUSERLEN);
-		errors++;
-	}
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "match"))
-		{
-			has_match = 1;
-			test_match_block(cf, cep, &errors);
-		} else
-		if (!strcmp(cep->name, "password"))
-		{
-			has_password = 1;
-			if (Auth_CheckError(cep) < 0)
-				errors++;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int rpc_config_run_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-	RPCUser *e;
-
-	/* We are only interested in rpc-user { } */
-	if ((type != CONFIG_MAIN) || !ce || !ce->name || strcmp(ce->name, "rpc-user"))
-		return 0;
-
-	e = safe_alloc(sizeof(RPCUser));
-	safe_strdup(e->name, ce->value);
-	AddListItem(e, rpcusers);
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "match"))
-		{
-			conf_match_block(cf, cep, &e->match);
-		} else
-		if (!strcmp(cep->name, "password"))
-		{
-			e->auth = AuthBlockToAuthConfig(cep);
-		}
-	}
-	return 1;
-}
-
-/** Incoming HTTP request: delegate it to websocket handler or HTTP POST */
-int rpc_handle_webrequest(Client *client, WebRequest *web)
-{
-	if (!rpc_handle_auth(client, web))
-		return 0; /* rejected */
-
-	if (get_nvplist(web->headers, "Sec-WebSocket-Key"))
-		return rpc_handle_webrequest_websocket(client, web);
-
-	if (!strcmp(web->uri, "/api"))
-	{
-		if (web->method != HTTP_METHOD_POST)
-		{
-			webserver_send_response(client, 200, "To use the UnrealIRCd RPC API you need to make a POST request. See https://www.unrealircd.org/docs/RPC\n");
-			return 0;
-		}
-		webserver_send_response(client, 200, NULL); /* continue.. */
-		return 1; /* accept */
-	}
-
-	webserver_send_response(client, 404, "Page not found.\n");
-	return 0;
-}
-
-/** Handle HTTP request - websockets handshake.
- */
-int rpc_handle_webrequest_websocket(Client *client, WebRequest *web)
-{
-	NameValuePrioList *r;
-	const char *value;
-
-	if (!websocket_md)
-	{
-		webserver_send_response(client, 405, "Websockets are disabled on this server (module 'websocket_common' not loaded).\n");
-		return 0;
-	}
-
-	/* Allocate a new WebSocketUser struct for this session */
-	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
-	/* ...and set the default protocol (text or binary) */
-	WSU(client)->type = WEBSOCKET_TYPE_TEXT;
-
-	value = get_nvplist(web->headers, "Sec-WebSocket-Key");
-	if (strchr(value, ':'))
-	{
-		/* This would cause unserialization issues. Should be base64 anyway */
-		webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
-		return 0; // FIXME: 0 here, -1 in the other, what is it ???
-	}
-	safe_strdup(WSU(client)->handshake_key, value);
-
-	rpc_websocket_handshake_send_response(client);
-	return 1; /* ACCEPT */
-}
-
-/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
-int rpc_websocket_handshake_send_response(Client *client)
-{
-	char buf[512], hashbuf[64];
-	char sha1out[20]; /* 160 bits */
-
-	WSU(client)->handshake_completed = 1;
-
-	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
-	sha1hash_binary(sha1out, buf, strlen(buf));
-	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
-
-	snprintf(buf, sizeof(buf),
-	         "HTTP/1.1 101 Switching Protocols\r\n"
-	         "Upgrade: websocket\r\n"
-	         "Connection: Upgrade\r\n"
-	         "Sec-WebSocket-Accept: %s\r\n\r\n",
-	         hashbuf);
-
-	/* Caution: we bypass sendQ flood checking by doing it this way.
-	 * Risk is minimal, though, as we only permit limited text only
-	 * once per session.
-	 */
-	dbuf_put(&client->local->sendQ, buf, strlen(buf));
-	send_queued(client);
-
-	return 0;
-}
-
-int rpc_handle_webrequest_data(Client *client, WebRequest *web, const char *buf, int len)
-{
-	if (WSU(client))
-	{
-		/* Websocket user */
-		return rpc_handle_body_websocket(client, web, buf, len);
-	}
-
-	/* We only handle POST to /api -- reject all the rest */
-	if (strcmp(web->uri, "/api") || (web->method != HTTP_METHOD_POST))
-	{
-		webserver_send_response(client, 404, "Page not found\n");
-		return 0;
-	}
-
-	// NB: content_length
-	// NB: chunked transfers?
-	if (!webserver_handle_body(client, web, buf, len))
-	{
-		webserver_send_response(client, 400, "Error handling POST body data\n");
-		return 0;
-	}
-
-
-	if (web->request_body_complete)
-	{
-		if (!web->request_buffer)
-		{
-			webserver_send_response(client, 500, "Error while processing POST body data\n");
-			return 0;
-		}
-		//config_status("GOT: '%s'", buf);
-		rpc_call_text(client, web->request_buffer, web->request_buffer_size);
-		send_queued(client);
-		webserver_close_client(client);
-	}
-
-	return 0;
-}
-
-int rpc_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
-{
-	return websocket_handle_websocket(client, web, readbuf2, length2, rpc_packet_in_websocket);
-}
-
-int rpc_packet_in_websocket(Client *client, char *readbuf, int length)
-{
-	rpc_call_text(client, readbuf, length);
-	return 0; /* and if dead?? */
-}
-
-int rpc_packet_in_unix_socket(Client *client, const char *readbuf, int *length)
-{
-	char buf[READBUFSIZE];
-
-	if (!RPC_PORT(client) || !(client->local->listener->socket_type == SOCKET_TYPE_UNIX) || (*length <= 0))
-		return 1; /* Not for us */
-
-	dbuf_put(&client->local->recvQ, readbuf, *length);
-
-	while (DBufLength(&client->local->recvQ))
-	{
-		int len = dbuf_getmsg(&client->local->recvQ, buf);
-		if (len <= 0)
-			break;
-		rpc_call_text(client, buf, len);
-		if (IsDead(client))
-			break;
-	}
-
-	return 0;
-}
-
-void rpc_close(Client *client)
-{
-	send_queued(client);
-
-	/* May not be a web request actually, but this works: */
-	webserver_close_client(client);
-}
-
-/** Handle the RPC request: input is a buffer with a certain length.
- * This calls rpc_call()
- */
-void rpc_call_text(Client *client, const char *readbuf, int len)
-{
-	json_t *request = NULL;
-	json_error_t jerr;
-#if JANSSON_VERSION_HEX >= 0x020100
-	const char *buf = readbuf;
-	request = json_loadb(buf, len, JSON_REJECT_DUPLICATES, &jerr);
-#else
-	char buf[2048];
-
-	*buf = '\0';
-	strlncpy(buf, readbuf, sizeof(buf), len);
-
-	request = json_loads(buf, JSON_REJECT_DUPLICATES, &jerr);
-#endif
-	if (!request)
-	{
-		unreal_log(ULOG_INFO, "rpc", "RPC_INVALID_JSON", client,
-		           "Received unparsable JSON request from $client",
-		           log_data_string("json_incoming", buf));
-		rpc_error(client, NULL, JSON_RPC_ERROR_PARSE_ERROR, "Unparsable JSON data");
-		/* This is a fatal error */
-		rpc_close(client);
-		return;
-	}
-	rpc_call(client, request);
-	json_decref(request);
-}
-
-void rpc_sendto(Client *client, const char *buf, int len)
-{
-	if (IsDead(client))
-		return;
-	if (MyConnect(client) && IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
-	{
-		/* Websocket */
-		int utf8bufsize = len*2 + 16;
-		char *utf8buf = safe_alloc(utf8bufsize);
-		char *newbuf = unrl_utf8_make_valid(buf, utf8buf, utf8bufsize, 1);
-		int newlen = strlen(newbuf);
-		int ws_sendbufsize = newlen + 64 + ((newlen / 1024) * 64); // some random magic
-		char *ws_sendbuf = safe_alloc(ws_sendbufsize);
-		websocket_create_packet_ex(WSOP_TEXT, &newbuf, &newlen, ws_sendbuf, ws_sendbufsize);
-		dbuf_put(&client->local->sendQ, newbuf, newlen);
-		safe_free(ws_sendbuf);
-		safe_free(utf8buf);
-	} else {
-		/* Unix domain socket or HTTP */
-		dbuf_put(&client->local->sendQ, buf, len);
-		dbuf_put(&client->local->sendQ, "\n", 1);
-	}
-	mark_data_to_send(client);
-}
-
-void _rpc_error(Client *client, json_t *request, JsonRpcError error_code, const char *error_message)
-{
-	/* Careful, we are in the "error" routine, so everything can be NULL */
-	const char *method = NULL;
-	json_t *id = NULL;
-	char *json_serialized;
-	json_t *error;
-
-	/* Start a new object for the error response */
-	json_t *j = json_object();
-
-	if (request)
-	{
-		method = json_object_get_string(request, "method");
-		id = json_object_get(request, "id");
-	}
-
-	json_object_set_new(j, "jsonrpc", json_string_unreal("2.0"));
-	if (method)
-		json_object_set_new(j, "method", json_string_unreal(method));
-	if (id)
-		json_object_set(j, "id", id);
-
-	error = json_object();
-	json_object_set_new(j, "error", error);
-	json_object_set_new(error, "code", json_integer(error_code));
-	json_object_set_new(error, "message", json_string_unreal(error_message));
-
-	unreal_log(ULOG_INFO, "rpc", "RPC_CALL_ERROR", client,
-	           "[rpc] Client $client: RPC call $method",
-	           log_data_string("method", method ? method : "<invalid>"));
-
-
-	json_serialized = json_dumps(j, 0);
-	if (!json_serialized)
-	{
-		unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_ERROR_SERIALIZE_FAILED", NULL,
-		           "[BUG] rpc_error() failed to serialize response "
-		           "for request from $client ($method)",
-		           log_data_string("method", method));
-		json_decref(j);
-		return;
-	}
-
-	if (MyConnect(client))
-		rpc_sendto(client, json_serialized, strlen(json_serialized));
-	else
-		rpc_send_response_to_remote(&me, client, j);
-
-#ifdef DEBUGMODE
-	unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
-		   "[rpc] Client $client: RPC result error: $response",
-		   log_data_string("response", json_serialized));
-#endif
-	json_decref(j);
-	safe_free(json_serialized);
-}
-
-void _rpc_error_fmt(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...)
-{
-	char buf[512];
-
-	va_list vl;
-	va_start(vl, fmt);
-	vsnprintf(buf, sizeof(buf), fmt, vl);
-	va_end(vl);
-	rpc_error(client, request, error_code, buf);
-}
-
-void _rpc_response(Client *client, json_t *request, json_t *result)
-{
-	const char *method = json_object_get_string(request, "method");
-	json_t *id = json_object_get(request, "id");
-	char *json_serialized;
-	json_t *j = json_object();
-
-	json_object_set_new(j, "jsonrpc", json_string_unreal("2.0"));
-	json_object_set_new(j, "method", json_string_unreal(method));
-	if (id)
-		json_object_set(j, "id", id); /* 'id' is optional */
-	json_object_set(j, "result", result);
-
-	json_serialized = json_dumps(j, 0);
-	if (!json_serialized)
-	{
-		unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_RESPONSE_SERIALIZE_FAILED", NULL,
-		           "[BUG] rpc_response() failed to serialize response "
-		           "for request from $client ($method)",
-		           log_data_string("method", method));
-		json_decref(j);
-		return;
-	}
-
-	if (MyConnect(client))
-		rpc_sendto(client, json_serialized, strlen(json_serialized));
-	else
-		rpc_send_response_to_remote(&me, client, j);
-
-#ifdef DEBUGMODE
-	unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
-		   "[rpc] Client $client: RPC response result: $response",
-		   log_data_string("response", json_serialized));
-#endif
-	json_decref(j);
-	safe_free(json_serialized);
-}
-
-int sanitize_params_actual(Client *client, json_t *request, const char *str)
-{
-	if (!str)
-		return 1;
-
-	if (strlen(str) > 510)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Strings cannot be longer than 510 characters in the request");
-		return 0;
-	}
-
-	if (strchr(str, '\n') || strchr(str, '\r'))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Strings may not contain \n or \r in the request");
-		return 0;
-	}
-
-	return 1;
-}
-
-int sanitize_params(Client *client, json_t *request, json_t *j)
-{
-	/* Check the current object itself */
-	const char *str = json_string_value(j);
-	if (str && !sanitize_params_actual(client, request, str))
-		return 0;
-
-	/* Now walk through the object, if needed */
-
-	if (json_is_array(j))
-	{
-		size_t index;
-		json_t *value;
-		json_array_foreach(j, index, value)
-		{
-			if (!sanitize_params(client, request, value))
-				return 0;
-		}
-	} else
-	if (json_is_object(j))
-	{
-		const char *key;
-		json_t *value;
-		json_object_foreach(j, key, value)
-		{
-			if (!sanitize_params_actual(client, request, key))
-				return 0;
-			if (!sanitize_params(client, request, value))
-				return 0;
-		}
-	}
-
-	return 1;
-}
-
-/** Log the RPC request */
-void rpc_call_log(Client *client, RPCHandler *handler, json_t *request, const char *method, json_t *params)
-{
-	const char *key;
-	json_t *value_object;
-	char params_string[512], tbuf[256];
-
-	*params_string = '\0';
-	json_object_foreach(params, key, value_object)
-	{
-		const char *value = json_get_value(value_object);
-		if (value)
-		{
-			snprintf(tbuf, sizeof(tbuf), "%s='%s', ", key, value);
-			strlcat(params_string, tbuf, sizeof(params_string));
-		}
-	}
-	if (*params_string)
-		params_string[strlen(params_string)-2] = '\0'; /* cut off last comma */
-
-	// TODO: pass log_data_json() or something, pass the entire 'request' ? For JSON logging
-
-	if (client->rpc && client->rpc->issuer)
-	{
-		if (*params_string)
-		{
-			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
-				   "[rpc] RPC call $method by $client ($issuer): $params_string",
-				   log_data_string("issuer", client->rpc->issuer),
-				   log_data_string("method", method),
-				   log_data_string("params_string", params_string));
-		} else {
-			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
-				   "[rpc] RPC call $method by $client ($issuer)",
-				   log_data_string("issuer", client->rpc->issuer),
-				   log_data_string("method", method));
-		}
-	} else {
-		if (*params_string)
-		{
-			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
-				   "[rpc] RPC call $method by $client: $params_string",
-				   log_data_string("method", method),
-				   log_data_string("params_string", params_string));
-		} else {
-			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
-				   "[rpc] RPC call $method by $client",
-				   log_data_string("method", method));
-		}
-	}
-}
-
-/** Parse an RPC request, except that it does not validate 'params'.
- * @param client	The client issuing the request
- * @param mainrequest	The underlying request that we should send errors to (usually same as 'request')
- * @param request	The request that needs parsing
- * @param method	This will be filled in if successfully parsed
- * @param handler	This will be filled in if the handler is found
- * @retval 0		An error occured while parsing or the method was not found.
- * @retval 1		All good. You still need to validate 'params', though.
- */
-int parse_rpc_call(Client *client, json_t *mainrequest, json_t *request, const char **method, RPCHandler **handler)
-{
-	const char *jsonrpc;
-	json_t *id;
-	const char *str;
-
-	*method = NULL;
-	*handler = NULL;
-
-	jsonrpc = json_object_get_string(request, "jsonrpc");
-	if (!jsonrpc || strcasecmp(jsonrpc, "2.0"))
-	{
-		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Only JSON-RPC version 2.0 is supported");
-		return 0;
-	}
-
-	id = json_object_get(request, "id");
-	if (!id)
-	{
-		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'id'");
-		return 0;
-	}
-
-	if ((str = json_string_value(id)))
-	{
-		if (strlen(str) > 32)
-		{
-			rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' cannot be longer than 32 characters in UnrealIRCd JSON-RPC");
-			return 0;
-		}
-		if (strchr(str, '\n') || strchr(str, '\r'))
-		{
-			rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' may not contain \n or \r in UnrealIRCd JSON-RPC");
-			return 0;
-		}
-	} else if (!json_is_integer(id))
-	{
-		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' must be a string or an integer in UnrealIRCd JSON-RPC");
-		return 0;
-	}
-
-	*method = json_object_get_string(request, "method");
-	if (!*method)
-	{
-		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'method' to call");
-		return 0;
-	}
-
-	*handler = RPCHandlerFind(*method);
-	if (!*handler)
-	{
-		rpc_error(client, mainrequest, JSON_RPC_ERROR_METHOD_NOT_FOUND, "Unsupported method");
-		return 0;
-	}
-
-	return 1;
-}
-
-/** Handle the RPC request: request is in JSON */
-void rpc_call(Client *client, json_t *request)
-{
-	const char *method;
-	json_t *params;
-	RPCHandler *handler;
-
-	if (!parse_rpc_call(client, request, request, &method, &handler))
-		return; /* Error already returned to caller */
-
-	params = json_object_get(request, "params");
-	if (params)
-	{
-		if (!(handler->flags & RPC_HANDLER_FLAGS_UNFILTERED) &&
-		    !sanitize_params(client, request, params))
-		{
-			return;
-		}
-	} else
-	{
-		/* Params is optional, so create an empty params object instead
-		 * to make life easier of the RPC handlers (no need to check NULL).
-		 */
-		params = json_object();
-		json_object_set_new(request, "params", params);
-	}
-
-	rpc_call_log(client, handler, request, method, params);
-
-#ifdef DEBUGMODE
-	{
-		char *call = json_dumps(request, 0);
-		if (call)
-		{
-			unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
-				   "[rpc] Client $client: RPC call: $call",
-				   log_data_string("call", call));
-			safe_free(call);
-		}
-	}
-#endif
-	handler->call(client, request, params);
-}
-
-/** Called very early on accept() of the socket, before TLS is ready */
-int rpc_client_accept(Client *client)
-{
-	if (RPC_PORT(client))
-	{
-		SetRPC(client);
-		client->rpc = safe_alloc(sizeof(RPCClient));
-	}
-	return 0;
-}
-
-/** Called upon handshake of unix socket (direct JSON usage, no auth) */
-void rpc_client_handshake_unix_socket(Client *client)
-{
-	if (client->local->listener->socket_type != SOCKET_TYPE_UNIX)
-		abort(); /* impossible */
-
-	strlcpy(client->name, "RPC:local", sizeof(client->name));
-	SetRPC(client);
-	client->rpc = safe_alloc(sizeof(RPCClient));
-	safe_strdup(client->rpc->rpc_user, "<local>");
-
-	/* Allow incoming data to be read from now on.. */
-	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
-}
-
-/** Called upon handshake, after TLS is ready (before any HTTP header parsing) */
-void rpc_client_handshake_web(Client *client)
-{
-	RPCUser *r;
-	char found = 0;
-
-	/* Explicitly mark as RPC, since the TLS layer may
-	 * have set us to SetUnknown() after the TLS handshake.
-	 */
-	SetRPC(client);
-	if (!client->rpc)
-		client->rpc = safe_alloc(sizeof(RPCClient));
-
-	/* Is the client allowed by any rpc-user { } block?
-	 * If not, reject the client immediately, before
-	 * processing any HTTP data.
-	 */
-	for (r = rpcusers; r; r = r->next)
-	{
-		if (user_allowed_by_security_group(client, r->match))
-		{
-			found = 1;
-			break;
-		}
-	}
-	if (!found)
-	{
-		webserver_send_response(client, 401, "Access denied");
-		return;
-	}
-
-	/* Allow incoming data to be read from now on.. */
-	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
-}
-
-#define RPC_WEBSOCKET_PING_TIME 120
-
-int rpc_pre_local_handshake_timeout(Client *client, const char **comment)
-{
-	/* Don't hang up websocket connections */
-	if (IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
-	{
-		long t = TStime() - client->local->last_msg_received;
-		if ((t > RPC_WEBSOCKET_PING_TIME*2) && IsPingSent(client))
-		{
-			*comment = "No websocket PONG received in time.";
-			return HOOK_CONTINUE;
-		} else
-		if ((t > RPC_WEBSOCKET_PING_TIME) && !IsPingSent(client) && !IsDead(client))
-		{
-			char pingbuf[4];
-			const char *pkt = pingbuf;
-			int pktlen = sizeof(pingbuf);
-			pingbuf[0] = 0x11;
-			pingbuf[1] = 0x22;
-			pingbuf[2] = 0x33;
-			pingbuf[3] = 0x44;
-			websocket_create_packet_simple(WSOP_PING, &pkt, &pktlen);
-			dbuf_put(&client->local->sendQ, pkt, pktlen);
-			send_queued(client);
-			SetPingSent(client);
-		}
-		return HOOK_ALLOW; /* prevent closing the connection due to timeout */
-	}
-
-	return HOOK_CONTINUE;
-}
-
-RPCUser *find_rpc_user(const char *username)
-{
-	RPCUser *r;
-	for (r = rpcusers; r; r = r->next)
-		if (!strcmp(r->name, username))
-			return r;
-	return NULL;
-}
-
-/** This function deals with authentication after the HTTP request was received.
- * It is called for both ordinary HTTP(S) requests and Websockets.
- * Note that there has also been some pre-filtering done in rpc_client_handshake()
- * to see if the IP address was allowed to connect at all (::match),
- * but here we actually check the 'correct' rpc-user { } block.
- * @param client	The client to authenticate
- * @param web		The webrequest (containing the headers)
- * @return 1 on success, 0 on failure
- */
-int rpc_handle_auth(Client *client, WebRequest *web)
-{
-	char *username = NULL, *password = NULL;
-	RPCUser *r;
-
-	if (!rpc_parse_auth_basic_auth(client, web, &username, &password) &&
-	    !rpc_parse_auth_uri(client, web, &username, &password))
-	{
-		webserver_send_response(client, 401, "Authentication required");
-		return 0;
-	}
-
-	if (username && password && ((r = find_rpc_user(username))))
-	{
-		if (user_allowed_by_security_group(client, r->match) &&
-		    Auth_Check(client, r->auth, password))
-		{
-			/* Authenticated! */
-			snprintf(client->name, sizeof(client->name), "RPC:%s", r->name);
-			safe_strdup(client->rpc->rpc_user, r->name);
-			return 1;
-		}
-	}
-
-	/* Authentication failed */
-	webserver_send_response(client, 401, "Authentication required");
-	return 0;
-}
-
-int rpc_parse_auth_basic_auth(Client *client, WebRequest *web, char **username, char **password)
-{
-	const char *auth_header = get_nvplist(web->headers, "Authorization");
-	static char buf[512];
-	char *p;
-	int n;
-
-	if (!auth_header)
-		return 0;
-
-	/* We only support basic auth */
-	if (strncasecmp(auth_header, "Basic ", 6))
-		return 0;
-
-	p = strchr(auth_header, ' ');
-	skip_whitespace(&p);
-	n = b64_decode(p, buf, sizeof(buf)-1);
-	if (n <= 1)
-		return 0;
-	buf[n] = '\0';
-
-	p = strchr(buf, ':');
-	if (!p)
-		return 0;
-	*p++ = '\0';
-
-	*username = buf;
-	*password = p;
-	return 1;
-}
-
-// TODO: the ?a=b&c=d stuff should be urldecoded by 'webserver'
-int rpc_parse_auth_uri(Client *client, WebRequest *web, char **username, char **password)
-{
-	static char buf[2048];
-	char *str, *p;
-
-	if (!web->uri)
-		return 0;
-
-	strlcpy(buf, web->uri, sizeof(buf));
-	str = strstr(buf, "username=");
-	if (!str)
-		return 0;
-	str += 9;
-	*username = str;
-	p = strchr(str, '&');
-	if (p)
-	{
-		*p++ = '\0';
-		p = strstr(p, "password=");
-		if (p)
-		{
-			p += 9;
-			*password = p;
-			p = strchr(str, '&');
-			if (p)
-				*p = '\0';
-		}
-	}
-	return 1;
-}
-
-RPC_CALL_FUNC(rpc_rpc_info)
-{
-	json_t *result, *methods, *item;
-	RPCHandler *r;
-
-	result = json_object();
-	methods = json_object();
-	json_object_set_new(result, "methods", methods);
-
-	for (r = rpchandlers; r; r = r->next)
-	{
-		item = json_object();
-		json_object_set_new(item, "name", json_string_unreal(r->method));
-		if (r->owner)
-		{
-			json_object_set_new(item, "module", json_string_unreal(r->owner->header->name));
-			json_object_set_new(item, "version", json_string_unreal(r->owner->header->version));
-		}
-		json_object_set_new(methods, r->method, item);
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_rpc_set_issuer)
-{
-	json_t *result;
-	const char *name;
-	char buf[512];
-
-	REQUIRE_PARAM_STRING("name", name);
-
-	/* Do some validation on the name */
-	strlcpy(buf, name, sizeof(buf));
-	if (!do_remote_nick_name(buf) || strcmp(buf, name))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME,
-		          "The 'name' contains illegal characters or is too long. "
-		          "The same rules as for nick names apply.");
-		return;
-	}
-
-	safe_strdup(client->rpc->issuer, name);
-
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-void free_rrpc(RRPC *r)
-{
-	safe_free(r->requestid);
-	DBufClear(&r->data);
-	DelListItem(r, rrpc_list);
-	safe_free(r);
-}
-
-/* Admin unloading the RPC module for good (not called on rehash) */
-void free_rrpc_list(ModData *m)
-{
-	RRPC *r, *r_next;
-
-	for (r = rrpc_list; r; r = r_next)
-	{
-		r_next = r->next;
-		free_rrpc(r);
-	}
-}
-
-void free_outstanding_rrpc(OutstandingRRPC *r)
-{
-	safe_free(r->requestid);
-	DelListItem(r, outstanding_rrpc_list);
-	safe_free(r);
-}
-
-/* Admin unloading the RPC module for good (not called on rehash) */
-void free_outstanding_rrpc_list(ModData *m)
-{
-	OutstandingRRPC *r, *r_next;
-
-	for (r = outstanding_rrpc_list; r; r = r_next)
-	{
-		r_next = r->next;
-		free_outstanding_rrpc(r);
-	}
-}
-
-/** Remove timer from rpc_timer_list and free it */
-void free_rpc_timer(RPCTimer *r)
-{
-	safe_free(r->timer_id);
-	json_decref(r->request);
-	DelListItem(r, rpc_timer_list);
-	safe_free(r);
-}
-
-/* Admin unloading the RPC module for good (not called on rehash) */
-void free_rpc_timer_list(ModData *m)
-{
-	RPCTimer *r, *r_next;
-
-	for (r = rpc_timer_list; r; r = r_next)
-	{
-		r_next = r->next;
-		free_rpc_timer(r);
-	}
-}
-
-/* Admin unloading the RPC module for good (not called on rehash) */
-void free_rpc_timers_for_user(Client *client)
-{
-	RPCTimer *r, *r_next;
-
-	for (r = rpc_timer_list; r; r = r_next)
-	{
-		r_next = r->next;
-		if (r->client == client)
-			free_rpc_timer(r);
-	}
-}
-
-RPCTimer *find_rpc_timer(Client *client, const char *timer_id)
-{
-	RPCTimer *r;
-
-	for (r = rpc_timer_list; r; r = r->next)
-	{
-		if ((r->client == client) && !strcmp(timer_id, r->timer_id))
-			return r;
-	}
-	return NULL;
-}
-
-/** When a server quits, cancel all the RPC requests to/from those clients */
-int rpc_handle_server_quit(Client *client, MessageTag *mtags)
-{
-	RRPC *r, *r_next;
-	OutstandingRRPC *or, *or_next;
-
-	for (r = rrpc_list; r; r = r_next)
-	{
-		r_next = r->next;
-		if (!strncmp(client->id, r->source, SIDLEN) ||
-		    !strncmp(client->id, r->destination, SIDLEN))
-		{
-			free_rrpc(r);
-		}
-	}
-
-	for (or = outstanding_rrpc_list; or; or = or_next)
-	{
-		or_next = or->next;
-		if (!strcmp(client->id, or->destination))
-		{
-			Client *client = find_client(or->source, NULL);
-			if (client)
-			{
-				json_t *j = json_object();
-				json_object_set_new(j, "id", json_string_unreal(or->requestid));
-				rpc_error(client, NULL, JSON_RPC_ERROR_SERVER_GONE, "Remote server disconnected while processing the request");
-				json_decref(j);
-			}
-			free_outstanding_rrpc(or);
-		}
-	}
-
-	return 0;
-}
-
-EVENT(rpc_remote_timeout)
-{
-	OutstandingRRPC *or, *or_next;
-	time_t deadline = TStime() - 15;
-
-	for (or = outstanding_rrpc_list; or; or = or_next)
-	{
-		or_next = or->next;
-		if (or->sent < deadline)
-		{
-			Client *client = find_client(or->source, NULL);
-			if (client)
-			{
-				json_t *request = json_object();
-				json_object_set_new(request, "id", json_string_unreal(or->requestid));
-				rpc_error(client, request, JSON_RPC_ERROR_TIMEOUT, "Request timed out");
-				json_decref(request);
-			}
-			free_outstanding_rrpc(or);
-		}
-	}
-}
-
-RRPC *find_rrpc(const char *source, const char *destination, const char *requestid)
-{
-	RRPC *r;
-	for (r = rrpc_list; r; r = r->next)
-	{
-		if (!strcmp(r->source, source) &&
-		    !strcmp(r->destination, destination) &&
-		    !strcmp(r->requestid, requestid))
-		{
-			return r;
-		}
-	}
-	return NULL;
-}
-
-OutstandingRRPC *find_outstandingrrpc(const char *source, const char *requestid)
-{
-	OutstandingRRPC *r;
-	for (r = outstanding_rrpc_list; r; r = r->next)
-	{
-		if (!strcmp(r->source, source) &&
-		    !strcmp(r->requestid, requestid))
-		{
-			return r;
-		}
-	}
-	return NULL;
-}
-
-/* Remote RPC call over the network (RRPC)
- * :<server> RRPC <REQ|RES> <source> <destination> <requestid> [S|C|F] :<request data>
- * S = Start
- * C = Continuation
- * F = Finish
- */
-CMD_FUNC(cmd_rrpc)
-{
-	int request;
-	const char *source, *destination, *requestid, *type, *data;
-	RRPC *r;
-	Client *dest;
-	char sid[SIDLEN+1];
-	char binarydata[BUFSIZE+1];
-	int binarydatalen;
-
-	if ((parc < 7) || BadPtr(parv[6]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "KNOCK");
-		return;
-	}
-
-	if (!strcmp(parv[1], "REQ"))
-	{
-		request = 1;
-	} else if (!strcmp(parv[1], "RES"))
-	{
-		request = 0;
-	} else {
-		sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Invalid parameter");
-		return;
-	}
-
-	source = parv[2];
-	destination = parv[3];
-	requestid = parv[4];
-	type = parv[5];
-	data = parv[6];
-
-	/* Search by SID (first 3 characters of destination)
-	 * so we can always deliver, even forn unknown UID destinations
-	 * in case this is a response.
-	 */
-	strlcpy(sid, destination, sizeof(sid));
-	dest = find_server_quick(sid);
-	if (!dest)
-	{
-		sendnumeric(client, ERR_NOSUCHSERVER, sid);
-		return;
-	}
-
-	if (dest != &me)
-	{
-		/* Just pass it along... */
-		sendto_one(dest, recv_mtags, ":%s RRPC %s %s %s %s %s :%s",
-		           client->id, parv[1], parv[2], parv[3], parv[4], parv[5], parv[6]);
-		return;
-	}
-
-	/* It's for us! So handle it ;) */
-
-	if (strchr(type, 'S'))
-	{
-		r = find_rrpc(source, destination, requestid);
-		if (r)
-		{
-			sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Duplicate request found");
-			/* We actually terminate the existing RRPC as well,
-			 * because there's a big risk of the the two different ones
-			 * merging in subsequent RRPC... C ... commands. Bad!
-			 * (and yeah this does not handle the case where you have
-			 *  like 3 or more duplicate request id requests... so be it..)
-			 */
-			free_rrpc(r);
-			return;
-		}
-		/* A new request */
-		r = safe_alloc(sizeof(RRPC));
-		strlcpy(r->source, source, sizeof(r->source));
-		strlcpy(r->destination, destination, sizeof(r->destination));
-		safe_strdup(r->requestid, requestid);
-		r->request = request;
-		dbuf_queue_init(&r->data);
-		AddListItem(r, rrpc_list);
-	} else
-	if (strchr(type, 'C') || strchr(type, 'F'))
-	{
-		r = find_rrpc(source, destination, requestid);
-		if (!r)
-		{
-			sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Request not found");
-			return;
-		}
-	} else
-	{
-		sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Only actions S/C/F are supported");
-		return;
-	}
-
-	/* Append the data */
-	dbuf_put(&r->data, data, strlen(data));
-
-	/* Now check if the request happens to be terminated */
-	if (strchr(type, 'F'))
-	{
-		if (r->request)
-			rpc_call_remote(r);
-		else
-			rpc_response_remote(r);
-		free_rrpc(r);
-		return;
-	}
-}
-
-/** Convert the RRPC data to actual workable JSON output */
-json_t *rrpc_data(RRPC *r)
-{
-	int datalen;
-	char *data;
-	json_t *j;
-	json_error_t jerr;
-
-	datalen = dbuf_get(&r->data, &data);
-	j = json_loads(data, JSON_REJECT_DUPLICATES, &jerr);
-	safe_free(data);
-
-	return j;
-}
-
-/** Received a remote RPC request (from a client on another server) */
-void rpc_call_remote(RRPC *r)
-{
-	json_t *request = NULL;
-	Client *server;
-	Client *client;
-	char sid[SIDLEN+1];
-
-	request = rrpc_data(r);
-	if (!request)
-	{
-		// TODO: handle invalid JSON
-		return;
-	}
-
-	/* Create a (fake) client structure */
-	strlcpy(sid, r->source, sizeof(sid));
-	server = find_server_quick(sid);
-	if (!server)
-	{
-		return;
-	}
-	client = make_client(server->direction, server);
-	strlcpy(client->id, r->source, sizeof(client->id));
-	client->rpc = safe_alloc(sizeof(RPCClient));
-	strlcpy(client->name, "RPC:remote", sizeof(client->name));
-	safe_strdup(client->rpc->rpc_user, "<remote>");
-	// Note: NOT added to hash table or id table etc.
-	list_add(&client->client_node, &rpc_remote_list);
-	rpc_call(client, request);
-	json_decref(request);
-
-	/* And free the temporary client, unless it is async... */
-	if (!IsAsyncRPC(client))
-		free_client(client);
-}
-
-/** Received a remote RPC response (from another server) to our local RPC client */
-void rpc_response_remote(RRPC *r)
-{
-	OutstandingRRPC *or;
-	Client *client = find_client(r->destination, NULL);
-	json_t *json, *j;
-
-	if (!client)
-		return;
-
-	or = find_outstandingrrpc(client->id, r->requestid);
-	if (!or)
-		return; /* Not a known outstanding request, maybe the client left already */
-
-	json = rrpc_data(r);
-	if (!json)
-		return;
-
-	if ((j = json_object_get(json, "result")))
-	{
-		rpc_response(client, json, j);
-	} else if ((j = json_object_get(json, "error")))
-	{
-		json_t *x;
-		int errorcode = 0;
-		const char *error_message = json_object_get_string(j, "message");
-		if ((x = json_object_get(j, "errorcode")))
-			errorcode = json_integer_value(x);
-		if (errorcode == 0)
-			errorcode = JSON_RPC_ERROR_INTERNAL_ERROR;
-		rpc_error(client, json, errorcode, error_message ? error_message : "");
-	}
-
-	json_decref(json);
-
-	free_outstanding_rrpc(or);
-}
-
-const char *rpc_id(json_t *request)
-{
-	static char rid[128];
-	const char *requestid;
-	json_t *j;
-
-	j = json_object_get(request, "id");
-	if (!j)
-		return NULL;
-
-	requestid = json_string_value(j);
-	if (!requestid)
-	{
-		json_int_t v = json_integer_value(j);
-		if (v == 0)
-			return NULL;
-		snprintf(rid, sizeof(rid), "%lld", (long long)v);
-		requestid = rid;
-	}
-
-	return requestid;
-}
-
-/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
-void rpc_send_generic_to_remote(Client *source, Client *target, const char *requesttype, json_t *json)
-{
-	char *json_serialized;
-	json_t *j;
-	const char *type;
-	const char *requestid;
-	char *str;
-	int bytes; /* bytes in this frame */
-	int bytes_remaining; /* bytes remaining overall */
-	int start_frame = 1; /* set to 1 if this is the start frame */
-	char data[451];
-
-	requestid = rpc_id(json);
-	if (!requestid)
-		return;
-
-	json_serialized = json_dumps(json, 0);
-	if (!json_serialized)
-		return;
-
-	/* :<server> RRPC REQ <source> <destination> <requestid> [S|C|F] :<request data>
-	 * S = Start
-	 * C = Continuation
-	 * F = Finish
-	 */
-
-	bytes_remaining = strlen(json_serialized);
-	for (str = json_serialized, bytes = MIN(bytes_remaining, 450);
-	     str && *str && bytes_remaining;
-	     str += bytes, bytes = MIN(bytes_remaining, 450))
-	{
-		bytes_remaining -= bytes;
-		if (start_frame == 1)
-		{
-			start_frame = 0;
-			if (bytes_remaining > 0)
-				type = "S"; /* start (with later continuation frames) */
-			else
-				type = "SF"; /* start and finish */
-		} else
-		if (bytes_remaining > 0)
-		{
-			type = "C"; /* continuation frame (with later a finish frame) */
-		} else {
-			type = "F"; /* finish frame (the last frame) */
-		}
-
-		strlncpy(data, str, sizeof(data), bytes);
-
-		sendto_one(target, NULL, ":%s RRPC %s %s %s %s %s :%s",
-		           me.id,
-		           requesttype,
-		           source->id,
-		           target->id,
-		           requestid,
-		           type,
-		           data);
-	}
-
-	safe_free(json_serialized);
-}
-
-int _rrpc_supported_simple(Client *target, char **problem_server)
-{
-	if (!moddata_client_get(target, "rrpc"))
-	{
-		if (problem_server)
-			*problem_server = target->name;
-		return 0;
-	}
-	if ((target != target->direction) && !rrpc_supported_simple(target->direction, problem_server))
-		return 0;
-	return 1;
-}
-
-int _rrpc_supported(Client *target, const char *module, const char *minimum_version, char **problem_server)
-{
-	if (!moddata_client_get(target, "rrpc"))
-	{
-		if (problem_server)
-			*problem_server = target->name;
-		return 0;
-	}
-	if ((target != target->direction) && !rrpc_supported_simple(target->direction, problem_server))
-		return 0;
-	return 1;
-}
-
-/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
-void _rpc_send_request_to_remote(Client *source, Client *target, json_t *request)
-{
-	OutstandingRRPC *r;
-	const char *requestid = rpc_id(request);
-	char *problem_server = NULL;
-
-	if (!requestid)
-	{
-		/* should never happen, since already covered upstream, but just to be sure... */
-		rpc_error(source, NULL, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' must be a string or an integer in UnrealIRCd JSON-RPC");
-		return;
-	}
-
-	if (find_outstandingrrpc(source->id, requestid))
-	{
-		rpc_error(source, NULL, JSON_RPC_ERROR_INVALID_REQUEST, "A request with that id is already in progress. Use unique id's!");
-		return;
-	}
-
-	/* If we already detect that next server cannot satisfy the request then stop it right now */
-	if (!rrpc_supported_simple(target, &problem_server))
-	{
-		rpc_error_fmt(source, request, JSON_RPC_ERROR_REMOTE_SERVER_NO_RPC, "Server %s does not support remote JSON-RPC", problem_server);
-		return;
-	}
-
-	/* Add the request to the "Outstanding RRPC list" */
-	r = safe_alloc(sizeof(OutstandingRRPC));
-	r->sent = TStime();
-	strlcpy(r->source, source->id, sizeof(r->source));
-	strlcpy(r->destination, target->id, sizeof(r->destination));
-	safe_strdup(r->requestid, requestid);
-	AddListItem(r, outstanding_rrpc_list);
-
-	/* And send it! */
-	rpc_send_generic_to_remote(source, target, "REQ", request);
-}
-
-/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
-void _rpc_send_response_to_remote(Client *source, Client *target, json_t *response)
-{
-	rpc_send_generic_to_remote(source, target, "RES", response);
-}
-
-const char *rrpc_md_serialize(ModData *m)
-{
-	static char buf[512];
-	char tmp[128];
-	NameValuePrioList *nv;
-
-	if (m->ptr == NULL)
-		return NULL;
-
-	*buf = '\0';
-	for (nv = m->ptr; nv; nv = nv->next)
-	{
-		snprintf(tmp, sizeof(tmp), "%s:%s,", nv->name, nv->value);
-		strlcat(buf, tmp, sizeof(buf));
-	}
-	if (*buf)
-		buf[strlen(buf)-1] = '\0'; // strip last comma
-
-	return buf;
-}
-
-void rrpc_md_unserialize(const char *str, ModData *m)
-{
-	char buf[1024], *p, *name, *value;
-
-	/* First free everything if needed */
-	if (m->ptr)
-	{
-		free_nvplist(m->ptr);
-		m->ptr = NULL;
-	}
-
-	if (BadPtr(str))
-		return; /* Done already */
-
-	strlcpy(buf, str, sizeof(buf));
-	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
-	{
-		value = strchr(name, ':');
-		if (!value)
-			continue;
-		*value++ = '\0';
-		add_nvplist((NameValuePrioList **)&m->ptr, 0, name, value);
-	}
-}
-
-void rrpc_md_free(ModData *m)
-{
-	if (m->ptr)
-	{
-		free_nvplist(m->ptr);
-		m->ptr = NULL;
-	}
-}
-
-int rpc_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child)
-{
-	NameValuePrioList *nv = RRPCMODULES(client);
-	json_t *rpc_modules;
-
-	if (!nv || (detail < 2))
-		return 0;
-
-	/* All this belongs under 'features' */
-	child = json_object_get(child, "features");
-	if (!child)
-		return 0;
-
-	rpc_modules = json_array();
-	json_object_set_new(child, "rpc_modules", rpc_modules);
-	for (; nv; nv = nv->next)
-	{
-		json_t *e = json_object();
-		json_object_set_new(e, "name", json_string_unreal(nv->name));
-		json_object_set_new(e, "version", json_string_unreal(nv->value));
-		json_array_append_new(rpc_modules, e);
-	}
-	return 0;
-}
-
-RPC_CALL_FUNC(rpc_rpc_add_timer)
-{
-	json_t *result;
-	json_t *subrequest;
-	long every_msec;
-	const char *timer_id;
-	const char *method;
-	RPCHandler *handler;
-	RPCTimer *timer;
-
-	REQUIRE_PARAM_INTEGER("every_msec", every_msec);
-	REQUIRE_PARAM_STRING("timer_id", timer_id);
-
-	subrequest = json_object_get(params, "request");
-	if (!subrequest)
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "request");
-		return;
-	}
-
-	if (every_msec < RPC_MINIMUM_TIMER_MSEC)
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS,
-		              "Value for every_msec may not be less than %d",
-		              (int)RPC_MINIMUM_TIMER_MSEC);
-		return;
-	}
-
-	/* Do some validation on the name */
-	if (!parse_rpc_call(client, request, subrequest, &method, &handler))
-		return; /* Error already returned to caller */
-
-	/* We don't validate 'params' here, but do so at runtime */
-
-	/* Check if a timer with that same name already exists FOR THIS CLIENT */
-	if (find_rpc_timer(client, timer_id))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Timer already exists with timer_id '%s'", timer_id);
-		return;
-	}
-
-	timer = safe_alloc(sizeof(RPCTimer));
-	timer->every_msec = every_msec;
-	timer->client = client;
-	safe_strdup(timer->timer_id, timer_id);
-	json_incref(subrequest);
-	timer->request = subrequest;
-	AddListItem(timer, rpc_timer_list);
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-EVENT(rpc_do_timers)
-{
-	RPCTimer *e, *e_next;
-
-	for (e = rpc_timer_list; e; e = e_next)
-	{
-		e_next = e->next;
-		if (minimum_msec_since_last_run(&e->last_run, e->every_msec))
-		{
-			rpc_call(e->client, e->request);
-		}
-		// TODO: maybe do counts as well?
-	}
-}
-
-/** Client being freed? If RPC then cancel timers, if any */
-int rpc_handle_free_client(Client *client)
-{
-	if (IsRPC(client))
-		free_rpc_timers_for_user(client);
-	return 0;
-}
-
-RPC_CALL_FUNC(rpc_rpc_del_timer)
-{
-	const char *timer_id;
-	RPCTimer *r;
-	json_t *result;
-
-	REQUIRE_PARAM_STRING("timer_id", timer_id);
-
-	r = find_rpc_timer(client, timer_id);
-	if (!r)
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_NOT_FOUND, "Timer not found with timer_id '%s'", timer_id);
-		return;
-	}
-	free_rpc_timer(r);
-
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/server.c b/src/modules/rpc/server.c
@@ -1,416 +0,0 @@
-/* server.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/server",
-	"1.0.0",
-	"server.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_server_list);
-RPC_CALL_FUNC(rpc_server_get);
-RPC_CALL_FUNC(rpc_server_rehash);
-RPC_CALL_FUNC(rpc_server_connect);
-RPC_CALL_FUNC(rpc_server_disconnect);
-RPC_CALL_FUNC(rpc_server_module_list);
-
-int rpc_server_rehash_log(int failure, json_t *rehash_log);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "server.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "server.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "server.rehash";
-	r.call = rpc_server_rehash;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "server.connect";
-	r.call = rpc_server_connect;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "server.disconnect";
-	r.call = rpc_server_disconnect;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "server.module_list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_module_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	HookAdd(modinfo->handle, HOOKTYPE_REHASH_LOG, 0, rpc_server_rehash_log);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-RPC_CALL_FUNC(rpc_server_list)
-{
-	json_t *result, *list, *item;
-	Client *acptr;
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		if (!IsServer(acptr) && !IsMe(acptr))
-			continue;
-
-		item = json_object();
-		json_expand_client(item, NULL, acptr, 99);
-		json_array_append_new(list, item);
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_get)
-{
-	json_t *result, *list, *item;
-	const char *server;
-	Client *acptr;
-
-	OPTIONAL_PARAM_STRING("server", server);
-	if (server)
-	{
-		if (!(acptr = find_server(server, NULL)))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
-			return;
-		}
-	} else {
-		acptr = &me;
-	}
-
-	result = json_object();
-	json_expand_client(result, "server", acptr, 99);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_rehash)
-{
-	json_t *result, *list, *item;
-	const char *server;
-	Client *acptr;
-
-	OPTIONAL_PARAM_STRING("server", server);
-	if (server)
-	{
-		if (!(acptr = find_server(server, NULL)))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
-			return;
-		}
-	} else {
-		acptr = &me;
-	}
-
-	if (acptr != &me)
-	{
-		/* Forward to remote */
-		if (rrpc_supported_simple(acptr, NULL))
-		{
-			/* Server supports RRPC and will handle the response */
-			rpc_send_request_to_remote(client, acptr, request);
-		} else {
-			/* Server does not support RRPC, so we can only do best effort: */
-			sendto_one(acptr, NULL, ":%s REHASH %s", me.id, acptr->name);
-			result = json_boolean(1);
-			rpc_response(client, request, result);
-			json_decref(result);
-		}
-		return;
-	}
-
-	if (client->rpc->rehash_request)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "A rehash initiated by you is already in progress");
-		return;
-	}
-
-	/* It's for me... */
-
-	SetMonitorRehash(client);
-	SetAsyncRPC(client);
-	client->rpc->rehash_request = json_copy(request); // or json_deep_copy ??
-
-	if (!loop.rehashing)
-	{
-		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
-		request_rehash(client);
-	} /* else.. we simply joined the rehash request so we are notified as well ;) */
-
-	/* Do NOT send the JSON Response here, it is done by rpc_server_rehash_log()
-	 * after the REHASH completed (which may take several seconds).
-	 */
-}
-
-int rpc_server_rehash_log(int failure, json_t *rehash_log)
-{
-	Client *client, *next;
-
-	list_for_each_entry(client, &unknown_list, lclient_node)
-	{
-		if (IsRPC(client) && IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
-		{
-			rpc_response(client, client->rpc->rehash_request, rehash_log);
-			json_decref(client->rpc->rehash_request);
-			client->rpc->rehash_request = NULL;
-		}
-	}
-	list_for_each_entry_safe(client, next, &rpc_remote_list, client_node)
-	{
-		if (IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
-		{
-			rpc_response(client, client->rpc->rehash_request, rehash_log);
-			json_decref(client->rpc->rehash_request);
-			client->rpc->rehash_request = NULL;
-			free_client(client);
-		}
-	}
-	return 0;
-}
-
-RPC_CALL_FUNC(rpc_server_connect)
-{
-	json_t *result, *list, *item;
-	const char *server, *link_name;
-	Client *acptr;
-	ConfigItem_link *link;
-	const char *err;
-
-	OPTIONAL_PARAM_STRING("server", server);
-	if (server)
-	{
-		if (!(acptr = find_server(server, NULL)))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
-			return;
-		}
-	} else {
-		acptr = &me;
-	}
-	REQUIRE_PARAM_STRING("link", link_name);
-
-	if (acptr != &me)
-	{
-		/* Not supported atm */
-		result = json_boolean(0);
-		rpc_response(client, request, result);
-		json_decref(result);
-		return;
-	}
-
-	if (find_server_quick(link_name))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Server is already linked");
-		return;
-	}
-
-	link = find_link(link_name);
-	if (!link)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name does not exist in any link block");
-		return;
-	}
-	if (!link->outgoing.hostname && !link->outgoing.file)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name exists but is not configured as an OUTGOING server.");
-		return;
-	}
-
-	if ((err = check_deny_link(link, 0)))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_DENIED, "Server linking is denied via a deny link { } block: %s", err);
-		return;
-	}
-
-	unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
-		   "CONNECT: Link to $link_block requested by $client",
-		   log_data_link_block(link));
-
-	connect_server(link, client, NULL);
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_disconnect)
-{
-	json_t *result, *list, *item;
-	const char *server, *link_name, *reason;
-	Client *acptr, *target;
-	MessageTag *mtags = NULL;
-
-	OPTIONAL_PARAM_STRING("server", server);
-	if (server)
-	{
-		if (!(acptr = find_server(server, NULL)))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
-			return;
-		}
-	} else {
-		acptr = &me;
-	}
-	REQUIRE_PARAM_STRING("link", link_name);
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	if (acptr != &me)
-	{
-		/* Not supported atm */
-		result = json_boolean(0);
-		rpc_response(client, request, result);
-		json_decref(result);
-		return;
-	}
-
-	target = find_server_quick(link_name);
-	if (!target)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server link not found");
-		return;
-	}
-
-	if (target == &me)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "We cannot disconnect ourselves");
-		return;
-	}
-
-	unreal_log(ULOG_INFO, "link", "SQUIT", client,
-	           "SQUIT: Forced server disconnect of $target by $client ($reason)",
-	           log_data_client("target", target),
-	           log_data_string("reason", reason));
-
-	/* The actual SQUIT: */
-	new_message(client, NULL, &mtags);
-	exit_client_ex(target, NULL, mtags, reason);
-	free_message_tags(mtags);
-
-	/* Return true */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-void json_expand_module(json_t *j, const char *key, Module *m, int detail)
-{
-	char buf[BUFSIZE+1];
-	json_t *child;
-
-	if (key)
-	{
-		child = json_object();
-		json_object_set_new(j, key, child);
-	} else {
-		child = j;
-	}
-
-	json_object_set_new(child, "name", json_string_unreal(m->header->name));
-	json_object_set_new(child, "version", json_string_unreal(m->header->version));
-	json_object_set_new(child, "author", json_string_unreal(m->header->author));
-	json_object_set_new(child, "description", json_string_unreal(m->header->description));
-	json_object_set_new(child, "third_party", json_boolean(m->options & MOD_OPT_OFFICIAL ? 0 : 1));
-	json_object_set_new(child, "permanent", json_boolean(m->options & MOD_OPT_PERM ? 1 : 0));
-	json_object_set_new(child, "permanent_but_reloadable", json_boolean(m->options & MOD_OPT_PERM_RELOADABLE ? 1 : 0));
-}
-
-RPC_CALL_FUNC(rpc_server_module_list)
-{
-	json_t *result, *list, *item;
-	const char *server;
-	Client *acptr;
-	Module *m;
-
-	OPTIONAL_PARAM_STRING("server", server);
-	if (server)
-	{
-		if (!(acptr = find_server(server, NULL)))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
-			return;
-		}
-	} else {
-		acptr = &me;
-	}
-
-	if (acptr != &me)
-	{
-		/* Forward to remote */
-		rpc_send_request_to_remote(client, acptr, request);
-		return;
-	}
-
-	/* Return true */
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (m = Modules; m; m = m->next)
-	{
-		item = json_object();
-		json_expand_module(item, NULL, m, 1);
-		json_array_append_new(list, item);
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/server_ban.c b/src/modules/rpc/server_ban.c
@@ -1,342 +0,0 @@
-/* server_ban.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/server_ban",
-	"1.0.3",
-	"server_ban.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_server_ban_list);
-RPC_CALL_FUNC(rpc_server_ban_get);
-RPC_CALL_FUNC(rpc_server_ban_del);
-RPC_CALL_FUNC(rpc_server_ban_add);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "server_ban.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_ban_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "server_ban.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_ban_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "server_ban.del";
-	r.call = rpc_server_ban_del;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "server_ban.add";
-	r.call = rpc_server_ban_add;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-RPC_CALL_FUNC(rpc_server_ban_list)
-{
-	json_t *result, *list, *item;
-	int index, index2;
-	TKL *tkl;
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				if (TKLIsServerBan(tkl))
-				{
-					item = json_object();
-					json_expand_tkl(item, NULL, tkl, 1);
-					json_array_append_new(list, item);
-				}
-			}
-		}
-	}
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			if (TKLIsServerBan(tkl))
-			{
-				item = json_object();
-				json_expand_tkl(item, NULL, tkl, 1);
-				json_array_append_new(list, item);
-			}
-		}
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-/** Shared code for selecting a server ban, for .add/.del/.get */
-int server_ban_select_criteria(Client *client, json_t *request, json_t *params,
-                               const char **name,
-                               const char **type_name,
-                               char *tkl_type_char,
-                               int *tkl_type_int,
-                               char **usermask,
-                               char **hostmask,
-                               int *soft)
-{
-	const char *error;
-
-	*name = json_object_get_string(params, "name");
-	if (!*name)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
-		return 0;
-	}
-
-	*type_name = json_object_get_string(params, "type");
-	if (!*type_name)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'type'");
-		return 0;
-	}
-
-	*tkl_type_char = tkl_configtypetochar(*type_name);
-	if (!*tkl_type_char)
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s'", *type_name);
-		return 0;
-	}
-	*tkl_type_int = tkl_chartotype(*tkl_type_char);
-	if (!TKLIsServerBanType(*tkl_type_int))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s' (type exists but is not valid for in server_ban.*)", *type_name);
-		return 0;
-	}
-
-	if (!server_ban_parse_mask(client, 0, *tkl_type_char, *name, usermask, hostmask, soft, &error))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
-		return 0;
-	}
-
-	/* Hm, shouldn't this be done by server_ban_parse_mask() ? */
-	if (*soft && (*usermask[0] == '%'))
-		*usermask = *usermask + 1;
-
-	return 1;
-}
-
-RPC_CALL_FUNC(rpc_server_ban_get)
-{
-	json_t *result, *list, *item;
-	const char *name, *type_name;
-	char *usermask, *hostmask;
-	int soft;
-	TKL *tkl;
-	char tkl_type_char;
-	int tkl_type_int;
-
-	if (!server_ban_select_criteria(client, request, params,
-	                                &name, &type_name,
-	                                &tkl_type_char, &tkl_type_int,
-	                                &usermask, &hostmask, &soft))
-	{
-		return;
-	}
-
-	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_ban_del)
-{
-	json_t *result, *list, *item;
-	const char *name, *type_name;
-	const char *set_by;
-	char *usermask, *hostmask;
-	char usermask_mess[256];
-	int soft;
-	TKL *tkl;
-	char tkl_type_char;
-	int tkl_type_int;
-	const char *tkllayer[10];
-	char tkl_type_str[2];
-
-	if (!server_ban_select_criteria(client, request, params,
-	                                &name, &type_name,
-	                                &tkl_type_char, &tkl_type_int,
-	                                &usermask, &hostmask, &soft))
-	{
-		return;
-	}
-
-	tkl_type_str[0] = tkl_type_char;
-	tkl_type_str[1] = '\0';
-
-	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
-		return;
-	}
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-
-	tkllayer[1] = "-";
-	tkllayer[2] = tkl_type_str;
-	if (soft)
-	{
-		/* I don't like this fiddling.
-		 * It will be gone when we move from cmd_tkl() to a real function though.
-		 */
-		snprintf(usermask_mess, sizeof(usermask_mess), "%%%s", usermask);
-		tkllayer[3] = usermask_mess;
-	} else {
-		tkllayer[3] = usermask;
-	}
-	tkllayer[4] = hostmask;
-	tkllayer[5] = set_by;
-	tkllayer[6] = NULL;
-	cmd_tkl(&me, NULL, 6, tkllayer);
-
-	if (!find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
-	{
-		rpc_response(client, request, result);
-	} else {
-		/* Actually this may not be an internal error, it could be an
-		 * incorrect request, such as asking to remove a config-based ban.
-		 */
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
-	}
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_ban_add)
-{
-	json_t *result, *list, *item;
-	const char *name, *type_name;
-	const char *set_by;
-	char *usermask, *hostmask;
-	int soft;
-	TKL *tkl;
-	char tkl_type_char;
-	int tkl_type_int;
-	char tkl_type_str[2];
-	const char *reason;
-	const char *str;
-	time_t tkl_expire_at;
-	time_t tkl_set_at = TStime();
-
-	if (!server_ban_select_criteria(client, request, params,
-	                                &name, &type_name,
-	                                &tkl_type_char, &tkl_type_int,
-	                                &usermask, &hostmask, &soft))
-	{
-		return;
-	}
-
-	tkl_type_str[0] = tkl_type_char;
-	tkl_type_str[1] = '\0';
-
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	/* Duration / expiry time */
-	if ((str = json_object_get_string(params, "duration_string")))
-	{
-		tkl_expire_at = config_checkval(str, CFG_TIME);
-		if (tkl_expire_at > 0)
-			tkl_expire_at = TStime() + tkl_expire_at;
-	} else
-	if ((str = json_object_get_string(params, "expire_at")))
-	{
-		tkl_expire_at = server_time_to_unix_time(str);
-	} else
-	{
-		/* Never expire */
-		tkl_expire_at = 0;
-	}
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
-		return;
-	}
-
-	if (find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban with that mask already exists");
-		return;
-	}
-
-	tkl = tkl_add_serverban(tkl_type_int, usermask, hostmask, reason,
-	                        set_by, tkl_expire_at, tkl_set_at,
-	                        soft, 0);
-
-	if (!tkl)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
-		return;
-	}
-
-	tkl_added(client, tkl);
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/server_ban_exception.c b/src/modules/rpc/server_ban_exception.c
@@ -1,314 +0,0 @@
-/* server_ban_exception.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/server_ban_exception",
-	"1.0.1",
-	"server_ban_exception.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_server_ban_exception_list);
-RPC_CALL_FUNC(rpc_server_ban_exception_get);
-RPC_CALL_FUNC(rpc_server_ban_exception_del);
-RPC_CALL_FUNC(rpc_server_ban_exception_add);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "server_ban_exception.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_ban_exception_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban_exception] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "server_ban_exception.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_server_ban_exception_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban_exception] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "server_ban_exception.del";
-	r.call = rpc_server_ban_exception_del;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban_exception] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "server_ban_exception.add";
-	r.call = rpc_server_ban_exception_add;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/server_ban_exception] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-RPC_CALL_FUNC(rpc_server_ban_exception_list)
-{
-	json_t *result, *list, *item;
-	int index, index2;
-	TKL *tkl;
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				if (TKLIsBanException(tkl))
-				{
-					item = json_object();
-					json_expand_tkl(item, NULL, tkl, 1);
-					json_array_append_new(list, item);
-				}
-			}
-		}
-	}
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			if (TKLIsBanException(tkl))
-			{
-				item = json_object();
-				json_expand_tkl(item, NULL, tkl, 1);
-				json_array_append_new(list, item);
-			}
-		}
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-/** Shared code for selecting a server ban, for .add/.del/.get */
-int server_ban_exception_select_criteria(Client *client, json_t *request, json_t *params, int add,
-                               const char **name,
-                               const char **exception_types,
-                               char **usermask,
-                               char **hostmask,
-                               int *soft)
-{
-	const char *error;
-
-	*name = json_object_get_string(params, "name");
-	if (!*name)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
-		return 0;
-	}
-
-	if (add)
-	{
-		*exception_types = json_object_get_string(params, "exception_types");
-		if (!*exception_types)
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'exception_types'");
-			return 0;
-		}
-	} else {
-		*exception_types = NULL;
-	}
-
-	if (!server_ban_exception_parse_mask(client, add, *exception_types, *name, usermask, hostmask, soft, &error))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
-		return 0;
-	}
-
-	return 1;
-}
-
-RPC_CALL_FUNC(rpc_server_ban_exception_get)
-{
-	json_t *result, *list, *item;
-	const char *name, *exception_types;
-	char *usermask, *hostmask;
-	int soft;
-	TKL *tkl;
-
-	if (!server_ban_exception_select_criteria(client, request, params, 0,
-	                                &name, &exception_types,
-	                                &usermask, &hostmask, &soft))
-	{
-		return;
-	}
-
-	if (!(tkl = find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft)) &&
-	    !(tkl = find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban exception not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_ban_exception_del)
-{
-	json_t *result, *list, *item;
-	const char *name, *exception_types;
-	const char *set_by;
-	const char *error;
-	char *usermask, *hostmask;
-	int soft;
-	TKL *tkl;
-	const char *tkllayer[11];
-
-	if (!server_ban_exception_select_criteria(client, request, params, 0,
-	                                &name, &exception_types,
-	                                &usermask, &hostmask, &soft))
-	{
-		return;
-	}
-
-	if (!(tkl = find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft)) &&
-	    !(tkl = find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban exception not found");
-		return;
-	}
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-
-	tkllayer[1] = "-";
-	tkllayer[2] = "E";
-	tkllayer[3] = usermask;
-	tkllayer[4] = hostmask;
-	tkllayer[5] = set_by;
-	tkllayer[6] = "0";
-	tkllayer[7] = "-";
-	tkllayer[8] = "-";
-	tkllayer[9] = "-";
-	tkllayer[10] = NULL;
-	cmd_tkl(&me, NULL, 6, tkllayer);
-
-	if (!find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft) &&
-	    !find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft))
-	{
-		rpc_response(client, request, result);
-	} else {
-		/* Actually this may not be an internal error, it could be an
-		 * incorrect request, such as asking to remove a config-based ban.
-		 */
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
-	}
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_server_ban_exception_add)
-{
-	json_t *result, *list, *item;
-	const char *name, *exception_types;
-	const char *set_by;
-	const char *error;
-	char *usermask, *hostmask;
-	int soft;
-	TKL *tkl;
-	const char *reason;
-	const char *str;
-	time_t tkl_expire_at;
-	time_t tkl_set_at = TStime();
-
-	if (!server_ban_exception_select_criteria(client, request, params, 1,
-	                                &name, &exception_types,
-	                                &usermask, &hostmask, &soft))
-	{
-		return;
-	}
-
-	// FIXME: where is set_by? is this missing in others as well? :p -> OPTIONAL!
-
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	/* Duration / expiry time */
-	if ((str = json_object_get_string(params, "duration_string")))
-	{
-		tkl_expire_at = config_checkval(str, CFG_TIME);
-		if (tkl_expire_at > 0)
-			tkl_expire_at = TStime() + tkl_expire_at;
-	} else
-	if ((str = json_object_get_string(params, "expire_at")))
-	{
-		tkl_expire_at = server_time_to_unix_time(str);
-	} else
-	{
-		/* Never expire */
-		tkl_expire_at = 0;
-	}
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
-	{
-		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
-		return;
-	}
-
-	if (find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft) ||
-	    find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban exception with that mask already exists");
-		return;
-	}
-
-	tkl = tkl_add_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask,
-	                           NULL, reason,
-	                           set_by, tkl_expire_at, tkl_set_at,
-	                           soft, exception_types, 0);
-
-	if (!tkl)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
-		return;
-	}
-
-	tkl_added(client, tkl);
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/spamfilter.c b/src/modules/rpc/spamfilter.c
@@ -1,326 +0,0 @@
-/* spamfilter.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/spamfilter",
-	"1.0.3",
-	"spamfilter.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_spamfilter_list);
-RPC_CALL_FUNC(rpc_spamfilter_get);
-RPC_CALL_FUNC(rpc_spamfilter_del);
-RPC_CALL_FUNC(rpc_spamfilter_add);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "spamfilter.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_spamfilter_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/spamfilter] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "spamfilter.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_spamfilter_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/spamfilter] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "spamfilter.del";
-	r.call = rpc_spamfilter_del;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/spamfilter] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	r.method = "spamfilter.add";
-	r.call = rpc_spamfilter_add;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/spamfilter] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-RPC_CALL_FUNC(rpc_spamfilter_list)
-{
-	json_t *result, *list, *item;
-	int index;
-	TKL *tkl;
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			if (TKLIsSpamfilter(tkl))
-			{
-				item = json_object();
-				json_expand_tkl(item, NULL, tkl, 1);
-				json_array_append_new(list, item);
-			}
-		}
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-/* Shared code for selecting a spamfilter, for .add/.del/get */
-int spamfilter_select_criteria(Client *client, json_t *request, json_t *params, const char **name, int *match_type,
-                               int *targets, char *targetbuf, size_t targetbuflen, BanAction *action, char *actionbuf)
-{
-	const char *str;
-
-	*name = json_object_get_string(params, "name");
-	if (!*name)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
-		return 0;
-	}
-
-	str = json_object_get_string(params, "match_type");
-	if (!str)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'match_type'");
-		return 0;
-	}
-	*match_type = unreal_match_method_strtoval(str);
-	if (!*match_type)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'match_type'");
-		return 0;
-	}
-
-	str = json_object_get_string(params, "spamfilter_targets");
-	if (!str)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'spamfilter_targets'");
-		return 0;
-	}
-	*targets = spamfilter_gettargets(str, NULL);
-	if (!*targets)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value(s) for parameter 'spamfilter_targets'");
-		return 0;
-	}
-	strlcpy(targetbuf, spamfilter_target_inttostring(*targets), targetbuflen);
-
-	str = json_object_get_string(params, "ban_action");
-	if (!str)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'ban_action'");
-		return 0;
-	}
-	*action = banact_stringtoval(str);
-	if (!*action)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_action'");
-		return 0;
-	}
-	actionbuf[0] = banact_valtochar(*action);
-	actionbuf[1] = '\0';
-	return 1;
-}
-
-RPC_CALL_FUNC(rpc_spamfilter_get)
-{
-	json_t *result;
-	int type = TKL_SPAMF|TKL_GLOBAL;
-	const char *name;
-	TKL *tkl;
-	BanAction action;
-	int match_type = 0;
-	int targets = 0;
-	char targetbuf[64];
-	char actionbuf[2];
-
-	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
-		return; /* Error already communicated to client */
-
-	tkl = find_tkl_spamfilter(type, name, action, targets);
-	if (!tkl)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_spamfilter_add)
-{
-	json_t *result;
-	int type = TKL_SPAMF|TKL_GLOBAL;
-	const char *str;
-	const char *name, *reason;
-	const char *set_by;
-	time_t ban_duration = 0;
-	TKL *tkl;
-	Match *m;
-	BanAction action;
-	int match_type = 0;
-	int targets = 0;
-	char targetbuf[64];
-	char actionbuf[2];
-	char reasonbuf[512];
-	char *err = NULL;
-
-	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
-		return; /* Error already communicated to client */
-
-	/* Reason */
-	reason = json_object_get_string(params, "reason");
-	if (!reason)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'reason'");
-		return;
-	}
-
-	/* Ban duration */
-	if ((str = json_object_get_string(params, "ban_duration")))
-	{
-		ban_duration = config_checkval(str, CFG_TIME);
-		if (ban_duration < 0)
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_duration'");
-			return;
-		}
-	}
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	if (find_tkl_spamfilter(type, name, action, targets))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A spamfilter with that regex+action+target already exists");
-		return;
-	}
-
-	/* Convert reason to use internal storage and wire format */
-	reason = unreal_encodespace(reason);
-	strlcpy(reasonbuf, reason, sizeof(reasonbuf));
-	reason = reasonbuf;
-
-	/* now check the regex / match field (only when adding) */
-	m = unreal_create_match(match_type, name, &err);
-	if (!m)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid regex or match string specified");
-		return;
-	}
-
-	tkl = tkl_add_spamfilter(type, targets, action, m, set_by, 0, TStime(),
-	                         ban_duration, reason, 0);
-
-	if (!tkl)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
-		return;
-	}
-
-	tkl_added(client, tkl);
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_spamfilter_del)
-{
-	json_t *result;
-	int type = TKL_SPAMF|TKL_GLOBAL;
-	const char *name;
-	const char *set_by;
-	TKL *tkl;
-	BanAction action;
-	int match_type = 0;
-	int targets = 0;
-	char targetbuf[64];
-	char actionbuf[2];
-	const char *tkllayer[13];
-
-	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
-		return; /* Error already communicated to client */
-
-	OPTIONAL_PARAM_STRING("set_by", set_by);
-	if (!set_by)
-		set_by = client->name;
-
-	tkl = find_tkl_spamfilter(type, name, action, targets);
-	if (!tkl)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_tkl(result, "tkl", tkl, 1);
-
-	/* Wait.. this is a bit dull? */
-	tkllayer[1] = "-";
-	tkllayer[2] = "F";
-	tkllayer[3] = targetbuf;
-	tkllayer[4] = actionbuf;
-	tkllayer[5] = set_by;
-	tkllayer[6] = "-";
-	tkllayer[7] = "0";
-	tkllayer[8] = "0";
-	tkllayer[9] = "-";
-	tkllayer[10] = unreal_match_method_valtostr(match_type);
-	tkllayer[11] = name;
-	tkllayer[12] = NULL;
-
-	cmd_tkl(&me, NULL, 12, tkllayer);
-
-	tkl = find_tkl_spamfilter(type, name, action, targets);
-	if (!tkl)
-	{
-		rpc_response(client, request, result);
-	} else {
-		/* Spamfilter still exists so failure to remove.
-		 * Actually this may not be an internal error, it could be an
-		 * incorrect request, such as asking to remove a config-based spamfilter.
-		 */
-		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
-		return;
-	}
-	json_decref(result);
-}
diff --git a/src/modules/rpc/stats.c b/src/modules/rpc/stats.c
@@ -1,214 +0,0 @@
-/* stats.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/stats",
-	"1.0.2",
-	"stats.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-void rpc_stats_get(Client *client, json_t *request, json_t *params);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "stats.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_stats_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/stats] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void json_expand_countries(json_t *main, const char *name, NameValuePrioList *geo)
-{
-	json_t *list = json_array();
-	json_t *item;
-
-	json_object_set_new(main, name, list);
-
-	for (; geo; geo = geo->next)
-	{
-		item = json_object();
-		json_object_set_new(item, "country", json_string_unreal(geo->name));
-		json_object_set_new(item, "count", json_integer(0 - geo->priority));
-		json_array_append_new(list, item);
-	}
-}
-
-void rpc_stats_user(json_t *main, int detail)
-{
-	Client *client;
-	int total = 0, ulined = 0, oper = 0;
-	json_t *child;
-	GeoIPResult *geo;
-	NameValuePrioList *countries = NULL;
-
-	child = json_object();
-	json_object_set_new(main, "user", child);
-
-	list_for_each_entry(client, &client_list, client_node)
-	{
-		if (IsUser(client))
-		{
-			total++;
-			if (IsULine(client))
-				ulined++;
-			else if (IsOper(client))
-				oper++;
-			if (detail >= 1)
-			{
-				geo = geoip_client(client);
-				if (geo && geo->country_code)
-				{
-					NameValuePrioList *e = find_nvplist(countries, geo->country_code);
-					if (e)
-					{
-						DelListItem(e, countries);
-						e->priority--;
-						AddListItemPrio(e, countries, e->priority);
-					} else {
-						add_nvplist(&countries, -1, geo->country_code, NULL);
-					}
-				}
-			}
-		}
-	}
-
-	json_object_set_new(child, "total", json_integer(total));
-	json_object_set_new(child, "ulined", json_integer(ulined));
-	json_object_set_new(child, "oper", json_integer(oper));
-	json_object_set_new(child, "record", json_integer(irccounts.global_max));
-	if (detail >= 1)
-		json_expand_countries(child, "countries", countries);
-}
-
-void rpc_stats_channel(json_t *main)
-{
-	json_t *child = json_object();
-	json_object_set_new(main, "channel", child);
-	json_object_set_new(child, "total", json_integer(irccounts.channels));
-}
-
-void rpc_stats_server(json_t *main)
-{
-	Client *client;
-	int total = 0, ulined = 0, oper = 0;
-	json_t *child = json_object();
-	json_object_set_new(main, "server", child);
-
-	total++; /* ourselves */
-	list_for_each_entry(client, &global_server_list, client_node)
-	{
-		if (IsServer(client))
-		{
-			total++;
-			if (IsULine(client))
-				ulined++;
-		}
-	}
-
-	json_object_set_new(child, "total", json_integer(total));
-	json_object_set_new(child, "ulined", json_integer(ulined));
-}
-
-void rpc_stats_server_ban(json_t *main)
-{
-	Client *client;
-	int index, index2;
-	TKL *tkl;
-	int total = 0;
-	int server_ban = 0;
-	int server_ban_exception = 0;
-	int spamfilter = 0;
-	int name_ban = 0;
-	json_t *child = json_object();
-	json_object_set_new(main, "server_ban", child);
-
-	/* First, hashed entries.. */
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				total++;
-				if (TKLIsServerBan(tkl))
-					server_ban++;
-				else if (TKLIsBanException(tkl))
-					server_ban_exception++;
-				else if (TKLIsNameBan(tkl))
-					name_ban++;
-				else if (TKLIsSpamfilter(tkl))
-					spamfilter++;
-			}
-		}
-	}
-
-	/* Now normal entries.. */
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			total++;
-			if (TKLIsServerBan(tkl))
-				server_ban++;
-			else if (TKLIsBanException(tkl))
-				server_ban_exception++;
-			else if (TKLIsNameBan(tkl))
-				name_ban++;
-			else if (TKLIsSpamfilter(tkl))
-				spamfilter++;
-		}
-	}
-
-	json_object_set_new(child, "total", json_integer(total));
-	json_object_set_new(child, "server_ban", json_integer(server_ban));
-	json_object_set_new(child, "spamfilter", json_integer(spamfilter));
-	json_object_set_new(child, "name_ban", json_integer(name_ban));
-	json_object_set_new(child, "server_ban_exception", json_integer(server_ban_exception));
-}
-
-void rpc_stats_get(Client *client, json_t *request, json_t *params)
-{
-	json_t *result, *item;
-	const char *statsname;
-	Channel *stats;
-	int details;
-
-	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 1);
-
-	result = json_object();
-	rpc_stats_server(result);
-	rpc_stats_user(result, details);
-	rpc_stats_channel(result);
-	rpc_stats_server_ban(result);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/user.c b/src/modules/rpc/user.c
@@ -1,697 +0,0 @@
-/* user.* RPC calls
- * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/user",
-	"1.0.7",
-	"user.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_user_list);
-RPC_CALL_FUNC(rpc_user_get);
-RPC_CALL_FUNC(rpc_user_set_nick);
-RPC_CALL_FUNC(rpc_user_set_username);
-RPC_CALL_FUNC(rpc_user_set_realname);
-RPC_CALL_FUNC(rpc_user_set_vhost);
-RPC_CALL_FUNC(rpc_user_set_mode);
-RPC_CALL_FUNC(rpc_user_set_snomask);
-RPC_CALL_FUNC(rpc_user_set_oper);
-RPC_CALL_FUNC(rpc_user_kill);
-RPC_CALL_FUNC(rpc_user_quit);
-RPC_CALL_FUNC(rpc_user_join);
-RPC_CALL_FUNC(rpc_user_part);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "user.list";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_user_list;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_user_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_nick";
-	r.call = rpc_user_set_nick;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_username";
-	r.call = rpc_user_set_username;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_realname";
-	r.call = rpc_user_set_realname;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_vhost";
-	r.call = rpc_user_set_vhost;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_mode";
-	r.call = rpc_user_set_mode;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_snomask";
-	r.call = rpc_user_set_snomask;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.set_oper";
-	r.call = rpc_user_set_oper;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.kill";
-	r.call = rpc_user_kill;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.quit";
-	r.call = rpc_user_quit;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.join";
-	r.call = rpc_user_join;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-	memset(&r, 0, sizeof(r));
-	r.method = "user.part";
-	r.call = rpc_user_part;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/user] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-RPC_CALL_FUNC(rpc_user_list)
-{
-	json_t *result, *list, *item;
-	Client *acptr;
-	int details;
-
-	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2);
-	if (details == 3)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
-		return;
-	}
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (!IsUser(acptr))
-			continue;
-
-		item = json_object();
-		json_expand_client(item, NULL, acptr, details);
-		json_array_append_new(list, item);
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_get)
-{
-	json_t *result, *list, *item;
-	const char *nick;
-	Client *acptr;
-	int details;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-
-	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 4);
-	if (details == 3)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
-		return;
-	}
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	result = json_object();
-	json_expand_client(result, "client", acptr, details);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_nick)
-{
-	json_t *result, *list, *item;
-	MessageTag *mtags = NULL;
-	const char *args[5];
-	const char *nick, *newnick_requested, *str;
-	int force = 0;
-	char newnick[NICKLEN+1];
-	char tsbuf[32];
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("newnick", newnick_requested);
-	strlcpy(newnick, newnick_requested, iConf.nick_length + 1);
-	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	if (!do_nick_name(newnick) || strcmp(newnick, newnick_requested) ||
-	    !strcasecmp(newnick, "IRC") || !strcasecmp(newnick, "IRCd"))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New nickname contains forbidden character(s) or is too long");
-		return;
-	}
-
-	if (!strcmp(nick, newnick))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old nickname and new nickname are identical");
-		return;
-	}
-
-	if (!force)
-	{
-		/* Check other restrictions */
-		Client *check = find_user(newnick, NULL);
-		int ishold = 0;
-
-		/* Check if in use by someone else (do allow case-changing) */
-		if (check && (acptr != check))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "New nickname is already taken by another user");
-			return;
-		}
-
-		// Can't really check for spamfilter here, since it assumes user is local
-
-		// But we can check q-lines...
-		if (find_qline(acptr, newnick, &ishold))
-		{
-			rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New nickname is forbidden by q-line");
-			return;
-		}
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = newnick;
-	snprintf(tsbuf, sizeof(tsbuf), "%lld", (long long)TStime());
-	args[3] = tsbuf;
-	args[4] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "SVSNICK", 4, args);
-	safe_free_message_tags(mtags);
-
-	/* Simply return success */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_username)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *username, *str;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("username", username);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	if (!valid_username(username))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New username contains forbidden character(s) or is too long");
-		return;
-	}
-
-	if (!strcmp(acptr->user->username, username))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new user name are identical");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = username;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "CHGIDENT", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result */
-	if (!strcmp(acptr->user->username, username))
-		result = json_boolean(1);
-	else
-		result = json_boolean(0);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_realname)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *realname, *str;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("realname", realname);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	if (strlen(realname) > REALLEN)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New real name is too long");
-		return;
-	}
-
-	if (!strcmp(acptr->info, realname))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new real name are identical");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = realname;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "CHGNAME", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result */
-	if (!strcmp(acptr->info, realname))
-		result = json_boolean(1);
-	else
-		result = json_boolean(0);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_vhost)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *vhost, *str;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("vhost", vhost);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	if ((strlen(vhost) > HOSTLEN) || !valid_host(vhost, 0))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New vhost contains forbidden character(s) or is too long");
-		return;
-	}
-
-	if (!strcmp(GetHost(acptr), vhost))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new vhost are identical");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = vhost;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "CHGHOST", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result */
-	if (!strcmp(GetHost(acptr), vhost))
-		result = json_boolean(1);
-	else
-		result = json_boolean(0);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_mode)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *modes, *str;
-	int hidden;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("modes", modes);
-	OPTIONAL_PARAM_BOOLEAN("hidden", hidden, 0);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = modes;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, hidden ? "SVSMODE" : "SVS2MODE", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result (always true) */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_snomask)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *snomask, *str;
-	int hidden;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("snomask", snomask);
-	OPTIONAL_PARAM_BOOLEAN("hidden", hidden, 0);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = snomask;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, hidden ? "SVSSNO" : "SVS2SNO", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result (always true) */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_set_oper)
-{
-	json_t *result, *list, *item;
-	const char *args[9];
-	const char *nick, *oper_account, *oper_class;
-	const char *class=NULL, *modes=NULL, *snomask=NULL, *vhost=NULL;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-	char default_modes[64];
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("oper_account", oper_account);
-	REQUIRE_PARAM_STRING("oper_class", oper_class);
-	OPTIONAL_PARAM_STRING("class", class);
-	OPTIONAL_PARAM_STRING("modes", modes);
-	OPTIONAL_PARAM_STRING("snomask", snomask);
-	OPTIONAL_PARAM_STRING("vhost", vhost);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	if (modes == NULL)
-	{
-		strlcpy(default_modes, get_usermode_string_raw(OPER_MODES), sizeof(default_modes));
-		modes = default_modes;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = oper_account;
-	args[3] = oper_class;
-	args[4] = class ? class : "opers";
-	args[5] = modes;
-	args[6] = snomask ? snomask : iConf.oper_snomask;
-	args[7] = vhost ? vhost : "-";
-	args[8] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "SVSO", 8, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result (always true) */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_kill)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *reason;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = reason;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "KILL", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result */
-	if ((acptr = find_user(nick, NULL)) && !IsDead(acptr))
-		result = json_boolean(0);
-	else
-		result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_quit)
-{
-	json_t *result, *list, *item;
-	const char *args[4];
-	const char *nick, *reason;
-	MessageTag *mtags = NULL;
-	Client *acptr;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("reason", reason);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = reason;
-	args[3] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, mtags, "SVSKILL", 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result */
-	if ((acptr = find_user(nick, NULL)) && !IsDead(acptr))
-		result = json_boolean(0);
-	else
-		result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_join)
-{
-	json_t *result, *list, *item;
-	const char *args[5];
-	const char *nick, *channel, *key=NULL;
-	Client *acptr;
-	MessageTag *mtags = NULL;
-	int force = 0;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("channel", channel);
-	OPTIONAL_PARAM_STRING("key", key);
-	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	mtag_add_issued_by(&mtags, client, NULL);
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = channel;
-
-	if (force == 0)
-	{
-		args[3] = key;
-		args[4] = NULL;
-		do_cmd(&me, mtags, "SVSJOIN", key ? 4 : 3, args);
-	} else {
-		args[3] = NULL;
-		do_cmd(&me, mtags, "SAJOIN", 3, args);
-	}
-
-	safe_free_message_tags(mtags);
-
-	/* Return result -- yeah this is always true, not so good.. :D
-	 * It is that way because we (this server) may not actually
-	 * do the SVSJOIN at all, we may be just relaying it to some
-	 * other server.
-	 */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
-
-RPC_CALL_FUNC(rpc_user_part)
-{
-	json_t *result, *list, *item;
-	const char *args[5];
-	const char *nick, *channel, *reason=NULL;
-	Client *acptr;
-	MessageTag *mtags = NULL;
-	int force = 0;
-
-	REQUIRE_PARAM_STRING("nick", nick);
-	REQUIRE_PARAM_STRING("channel", channel);
-	OPTIONAL_PARAM_STRING("reason", reason);
-	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
-
-	if (!(acptr = find_user(nick, NULL)))
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
-		return;
-	}
-
-	args[0] = NULL;
-	args[1] = acptr->name;
-	args[2] = channel;
-	args[3] = reason;
-	args[4] = NULL;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_cmd(&me, NULL, force ? "SAPART" : "SVSPART", reason ? 4 : 3, args);
-	safe_free_message_tags(mtags);
-
-	/* Return result. Always 'true' at the moment.
-	 * Technically we could check if the user is in all of these channels.
-	 * But then again, do we really want to return failure if one specified
-	 * channel does not exist out of X channels to be parted? Not worth it.
-	 */
-	result = json_boolean(1);
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rpc/whowas.c b/src/modules/rpc/whowas.c
@@ -1,148 +0,0 @@
-/* whowas.* RPC calls
- * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"rpc/whowas",
-	"1.0.0",
-	"whowas.* RPC calls",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Externals */
-extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
-
-/* Forward declarations */
-RPC_CALL_FUNC(rpc_whowas_get);
-
-MOD_INIT()
-{
-	RPCHandlerInfo r;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&r, 0, sizeof(r));
-	r.method = "whowas.get";
-	r.loglevel = ULOG_DEBUG;
-	r.call = rpc_whowas_get;
-	if (!RPCHandlerAdd(modinfo->handle, &r))
-	{
-		config_error("[rpc/whowas] Could not register RPC handler");
-		return MOD_FAILED;
-	}
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-const char *whowas_event_to_string(WhoWasEvent event)
-{
-	if (event == WHOWAS_EVENT_QUIT)
-		return "quit";
-	if (event == WHOWAS_EVENT_NICK_CHANGE)
-		return "nick-change";
-	if (event == WHOWAS_EVENT_SERVER_TERMINATING)
-		return "server-terminating";
-	return "unknown";
-}
-
-void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail)
-{
-	json_t *child;
-	json_t *user = NULL;
-	char buf[BUFSIZE+1];
-
-	if (key)
-	{
-		child = json_object();
-		json_object_set_new(j, key, child);
-	} else {
-		child = j;
-	}
-
-	json_object_set_new(child, "name", json_string_unreal(e->name));
-	json_object_set_new(child, "event", json_string_unreal(whowas_event_to_string(e->event)));
-	json_object_set_new(child, "logon_time", json_timestamp(e->logon));
-	json_object_set_new(child, "logoff_time", json_timestamp(e->logoff));
-
-	if (detail == 0)
-		return;
-
-	json_object_set_new(child, "hostname", json_string_unreal(e->hostname));
-	json_object_set_new(child, "ip", json_string_unreal(e->ip));
-
-	snprintf(buf, sizeof(buf), "%s!%s@%s", e->name, e->username, e->hostname);
-	json_object_set_new(child, "details", json_string_unreal(buf));
-
-	if (detail < 2)
-		return;
-
-	if (e->connected_since)
-		json_object_set_new(child, "connected_since", json_timestamp(e->connected_since));
-
-	/* client.user */
-	user = json_object();
-	json_object_set_new(child, "user", user);
-
-	json_object_set_new(user, "username", json_string_unreal(e->username));
-	if (!BadPtr(e->realname))
-		json_object_set_new(user, "realname", json_string_unreal(e->realname));
-	if (!BadPtr(e->virthost))
-		json_object_set_new(user, "vhost", json_string_unreal(e->virthost));
-	json_object_set_new(user, "servername", json_string_unreal(e->servername));
-	if (!BadPtr(e->account))
-		json_object_set_new(user, "account", json_string_unreal(e->account));
-}
-
-RPC_CALL_FUNC(rpc_whowas_get)
-{
-	json_t *result, *list, *item;
-	int details;
-	int i;
-	const char *nick;
-	const char *ip;
-
-	OPTIONAL_PARAM_STRING("nick", nick);
-	OPTIONAL_PARAM_STRING("ip", ip);
-	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2);
-	if (details == 3)
-	{
-		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
-		return;
-	}
-
-	result = json_object();
-	list = json_array();
-	json_object_set_new(result, "list", list);
-
-	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
-	{
-		WhoWas *e = &WHOWAS[i];
-		if (!e->name)
-			continue;
-		if (nick && !match_simple(nick, e->name))
-			continue;
-		if (ip && !match_simple(ip, e->ip))
-			continue;
-		item = json_object();
-		json_expand_whowas(item, NULL, e, details);
-		json_array_append_new(list, item);
-	}
-
-	rpc_response(client, request, result);
-	json_decref(result);
-}
diff --git a/src/modules/rules.c b/src/modules/rules.c
@@ -1,89 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_rules);
-
-#define MSG_RULES 	"RULES"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"rules",
-	"5.0",
-	"command /rules", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_RULES, cmd_rules, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * Heavily modified from the ircu cmd_motd by codemastr
- * Also svsmotd support added
- */
-CMD_FUNC(cmd_rules)
-{
-	ConfigItem_tld *tld;
-	MOTDLine *temp;
-
-	if (hunt_server(client, recv_mtags, "RULES", 1, parc, parv) != HUNTED_ISME)
-		return;
-
-	tld = find_tld(client);
-	if (tld && tld->rules.lines)
-		temp = tld->rules.lines;
-	else
-		temp = rules.lines;
-
-	if (temp == NULL)
-	{
-		sendnumeric(client, ERR_NORULES);
-		return;
-
-	}
-
-	sendnumeric(client, RPL_RULESSTART, me.name);
-
-	while (temp)
-	{
-		sendnumeric(client, RPL_RULES,
-		    temp->line);
-		temp = temp->next;
-	}
-	sendnumeric(client, RPL_ENDOFRULES);
-}
diff --git a/src/modules/sajoin.c b/src/modules/sajoin.c
@@ -1,294 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sajoin.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_sajoin);
-
-#define MSG_SAJOIN 	"SAJOIN"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"sajoin",
-	"5.0",
-	"command /sajoin", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SAJOIN, cmd_sajoin, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-static void log_sajoin(Client *client, MessageTag *mtags, Client *target, const char *channels)
-{
-	const char *issuer = command_issued_by_rpc(mtags);
-	if (issuer)
-	{
-		unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $issuer used SAJOIN to make $target join $channels",
-			   log_data_string("issuer", issuer),
-			   log_data_client("target", target),
-			   log_data_string("channels", channels));
-	} else {
-		unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $client used SAJOIN to make $target join $channels",
-			   log_data_client("target", target),
-			   log_data_string("channels", channels));
-	}
-}
-
-/* cmd_sajoin() - Lamego - Wed Jul 21 20:04:48 1999
-   Copied off PTlink IRCd (C) PTlink coders team.
-   Coded for Sadmin by Stskeeps
-   also Modified by NiQuiL (niquil@programmer.net)
-	parv[1] - nick to make join
-	parv[2] - channel(s) to join
-*/
-CMD_FUNC(cmd_sajoin)
-{
-	Client *target;
-	char request[BUFSIZE];
-	char jbuf[BUFSIZE];
-	int did_anything = 0;
-	int ntargets = 0;
-	int maxtargets = max_targets_for_command("SAJOIN");
-
-	if (parc < 3)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SAJOIN");
-		return;
-	}
-
-	if (!(target = find_user(parv[1], NULL)))
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		return;
-	}
-
-	/* Is this user disallowed from operating on this victim at all? */
-	if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sajoin",client,target,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		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 only log... */
-	if (!MyUser(target))
-	{
-		log_sajoin(client, recv_mtags, 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 parted = 0;
-	
-		*jbuf = 0;
-
-		/* Now works like cmd_join */
-		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)
-			{
-				sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "SAJOIN");
-				break;
-			}
-
-			mode = prefix_to_mode(*name);
-			if (mode)
-			{
-				prefix = *name;
-				name++; /* skip the prefix */
-			}
-
-			if (strlen(name) > CHANNELLEN)
-			{
-				sendnotice(client, "Channel name too long: %s", name);
-				continue;
-			}
-
-			if (*name == '0' && !atoi(name) && !mode)
-			{
-				strlcpy(jbuf, "0", sizeof(jbuf));
-				parted = 1;
-				continue;
-			}
-
-			if (!valid_channelname(name))
-			{
-				send_invalid_channelname(client, name);
-				continue;
-			}
-
-			channel = make_channel(name);
-
-			/* If this _specific_ channel is not permitted, skip it */
-			if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sajoin",client,target,channel,NULL))
-			{
-				sendnumeric(client, ERR_NOPRIVILEGES);
-				continue;
-			}
-
-			if (!parted && channel && (lp = find_membership_link(target->user->channel, channel)))
-			{
-				sendnumeric(client, ERR_USERONCHANNEL, target->name, name);
-				continue;
-			}
-			if (*jbuf)
-				strlcat(jbuf, ",", sizeof(jbuf));
-			if (prefix)
-				strlcat_letter(jbuf, prefix, sizeof(jbuf));
-			strlcat(jbuf, name, sizeof(jbuf));
-		}
-		if (!*jbuf)
-			return;
-
-		strlcpy(request, jbuf, sizeof(request));
-		*jbuf = 0;
-		for (name = strtoken(&p, request, ","); name; name = strtoken(&p, NULL, ","))
-		{
-			MessageTag *mtags = NULL;
-			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) && !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
-				 */
-				did_anything = 1;
-				while ((lp = target->user->channel))
-				{
-					MessageTag *mtags = NULL;
-					channel = lp->channel;
-
-					new_message(target, NULL, &mtags);
-					mtag_add_issued_by(&mtags, client, recv_mtags);
-					sendto_channel(channel, target, NULL, 0, 0, SEND_LOCAL, mtags,
-					               ":%s PART %s :%s",
-					               target->name, channel->name, "Left all channels");
-					sendto_server(NULL, 0, 0, mtags, ":%s PART %s :Left all channels", target->name, channel->name);
-					if (MyConnect(target))
-						RunHook(HOOKTYPE_LOCAL_PART, target, channel, mtags, "Left all channels");
-					free_message_tags(mtags);
-					remove_user_from_channel(target, channel, 0);
-				}
-				strlcpy(jbuf, "0", sizeof(jbuf));
-				continue;
-			}
-			member_modes = (ChannelExists(name)) ? "" : LEVEL_ON_JOIN;
-			channel = make_channel(name);
-			if (channel && (lp = find_membership_link(target->user->channel, channel)))
-				continue;
-
-			i = HOOK_CONTINUE;
-			for (h = Hooks[HOOKTYPE_CAN_SAJOIN]; h; h = h->next)
-			{
-				i = (*(h->func.intfunc))(target,channel,client);
-				if (i != HOOK_CONTINUE)
-					break;
-			}
-
-			if (i == HOOK_DENY)
-				continue; /* process next channel */
-
-			/* Generate a new message without inheritance.
-			 * We can do this because we are the server that
-			 * will send a JOIN for each channel due to this loop.
-			 * Each with their own unique msgid.
-			 */
-			new_message(target, NULL, &mtags);
-			mtag_add_issued_by(&mtags, client, recv_mtags);
-			join_channel(channel, target, mtags, member_modes);
-			if (prefix)
-			{
-				char *modes;
-				const char *mode_args[3];
-
-				opermode = 0;
-				sajoinmode = 1;
-
-				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(modes);
-			}
-			free_message_tags(mtags);
-			did_anything = 1;
-			if (*jbuf)
-				strlcat(jbuf, ",", sizeof jbuf);
-			strlcat(jbuf, name, sizeof jbuf);
-		}
-
-		//if (did_anything)
-		//{
-		//	sendnotice(target, "*** You were forced to join %s", jbuf);
-		//	log_sajoin(client, recv_mtags, target, jbuf);
-		//}
-	}
-}
diff --git a/src/modules/samode.c b/src/modules/samode.c
@@ -1,89 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/samode.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_samode);
-
-#define MSG_SAMODE 	"SAMODE"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"samode",
-	"5.0",
-	"command /samode", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SAMODE, cmd_samode, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * cmd_samode
- * parv[1] = channel
- * parv[2] = modes
- * -t
- */
-CMD_FUNC(cmd_samode)
-{
-	Channel *channel;
-	MessageTag *mtags = NULL;
-
-	if (parc <= 2)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SAMODE");
-		return;
-	}
-
-	channel = find_channel(parv[1]);
-	if (!channel)
-	{
-		sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
-		return;
-	}
-
-	if (!ValidatePermissionsForPath("sacmd:samode", client, NULL, channel, NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	opermode = 0;
-	mtag_add_issued_by(&mtags, client, NULL);
-	do_mode(channel, client, mtags, parc - 2, parv + 2, 0, 1);
-	safe_free_message_tags(mtags);
-}
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -1,210 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sapart.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_sapart);
-
-#define MSG_SAPART 	"SAPART"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"sapart",
-	"5.0",
-	"command /sapart", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SAPART, cmd_sapart, 3, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-static void log_sapart(Client *client, MessageTag *mtags, Client *target, const char *channels, const char *comment)
-{
-	const char *issuer = command_issued_by_rpc(mtags);
-
-	if (issuer)
-	{
-		if (comment)
-		{
-			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $issuer used SAPART to make $target part $channels ($reason)",
-				   log_data_string("issuer", issuer),
-				   log_data_client("target", target),
-				   log_data_string("channels", channels),
-				   log_data_string("reason", comment));
-		}
-		else
-		{
-			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $issuer used SAPART to make $target part $channels",
-				   log_data_string("issuer", issuer),
-				   log_data_client("target", target),
-				   log_data_string("channels", channels));
-		}
-	} else {
-		if (comment)
-		{
-			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels ($reason)",
-				   log_data_client("target", target),
-				   log_data_string("channels", channels),
-				   log_data_string("reason", comment));
-		}
-		else
-		{
-			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels",
-				   log_data_client("target", target),
-				   log_data_string("channels", channels));
-		}
-	}
-}
-
-
-/* cmd_sapart() - Lamego - Wed Jul 21 20:04:48 1999
-   Copied off PTlink IRCd (C) PTlink coders team.
-   Coded for Sadmin by Stskeeps
-   also Modified by NiQuiL (niquil@programmer.net)
-	parv[1] - nick to make part
-	parv[2] - channel(s) to part
-	parv[3] - comment
-*/
-
-CMD_FUNC(cmd_sapart)
-{
-	Client *target;
-	Channel *channel;
-	MessageTag *mtags = NULL;
-	Membership *lp;
-	char *name, *p = NULL;
-	int i;
-	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");
-
-	if ((parc < 3) || BadPtr(parv[2]))
-        {
-                sendnumeric(client, ERR_NEEDMOREPARAMS, "SAPART");
-                return;
-        }
-
-        if (!(target = find_user(parv[1], NULL)))
-        {
-                sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-                return;
-        }
-
-	/* See if we can operate on this vicim/this command */
-	if (!ValidatePermissionsForPath("sacmd:sapart",client,target,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	/* 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))
-	{
-		log_sapart(client, recv_mtags, target, parv[2], comment);
-		return;
-	}
-
-	/* 'target' is our client... */
-
-	*jbuf = 0;
-	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 = find_channel(name)))
-		{
-			sendnumeric(client, ERR_NOSUCHCHANNEL, name);
-			continue;
-		}
-
-		/* Validate oper can do this on chan/victim */
-		if (!IsULine(client) && !ValidatePermissionsForPath("sacmd:sapart",client,target,channel,NULL))
-		{
-			sendnumeric(client, ERR_NOPRIVILEGES);
-			continue;
-		}
-
-		if (!(lp = find_membership_link(target->user->channel, channel)))
-		{
-			sendnumeric(client, ERR_USERNOTINCHANNEL, target->name, name);
-			continue;
-		}
-		if (*jbuf)
-			strlcat(jbuf, ",", sizeof jbuf);
-		strlncat(jbuf, name, sizeof jbuf, sizeof(jbuf) - i - 1);
-		i += strlen(name) + 1;
-	}
-
-	if (!*jbuf)
-		return;
-
-	strlcpy(request, jbuf, sizeof(request));
-
-	log_sapart(client, recv_mtags, target, request, comment);
-
-	//if (comment)
-	//{
-	//	snprintf(commentx, sizeof(commentx), "SAPart: %s", comment);
-	//	sendnotice(target, "*** You were forced to part %s (%s)", request, commentx);
-	//} else {
-	//	sendnotice(target, "*** You were forced to part %s", request);
-	//}
-
-	parv[0] = target->name; // nick
-	parv[1] = request; // chan
-	parv[2] = comment ? commentx : NULL; // comment
-
-	/* Now, do the actual parting: */
-	mtag_add_issued_by(&mtags, client, recv_mtags);
-	do_cmd(target, mtags, "PART", comment ? 3 : 2, parv);
-	safe_free_message_tags(mtags);
-
-	/* NOTE: target may be killed now due to the part reason @ spamfilter */
-}
diff --git a/src/modules/sasl.c b/src/modules/sasl.c
@@ -1,412 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sasl.c
- *   (C) 2012 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
-  = {
-	"sasl",
-	"5.2.1",
-	"SASL", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-void saslmechlist_free(ModData *m);
-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);
-
-/* Macros */
-#define MSG_AUTHENTICATE "AUTHENTICATE"
-#define MSG_SASL "SASL"
-#define AGENT_SID(agent_p)	(agent_p->user != NULL ? agent_p->user->server : agent_p->name)
-
-/* Variables */
-long CAP_SASL = 0L;
-
-/*
- * The following people were involved in making the previous iteration of SASL over
- * IRC which allowed psuedo-identifiers:
- *
- * danieldg, Daniel de Graff <danieldg@inspircd.org>
- * jilles, Jilles Tjoelker <jilles@stack.nl>
- * Jobe, Matthew Beeching <jobe@mdbnet.co.uk>
- * gxti, Michael Tharp <gxti@partiallystapled.com>
- * nenolod, William Pitcock <nenolod@dereferenced.org>
- *
- * Thanks also to all of the client authors which have implemented SASL in their
- * clients.  With the backwards-compatibility layer allowing "lightweight" SASL
- * implementations, we now truly have a universal authentication mechanism for
- * IRC.
- */
-
-int sasl_account_login(Client *client, MessageTag *mtags)
-{
-	if (!MyConnect(client))
-		return 0;
-
-	/* Notify user */
-	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->account, client->user->account);
-	}
-	else
-	{
-		sendnumeric(client, RPL_LOGGEDOUT,
-			BadPtr(client->name) ? "*" : client->name,
-			BadPtr(client->user->username) ? "*" : client->user->username,
-			BadPtr(client->user->realhost) ? "*" : client->user->realhost);
-	}
-	return 0;
-}
-
-
-/*
- * SASL message
- *
- * parv[1]: distribution mask
- * parv[2]: target
- * parv[3]: mode/state
- * parv[4]: data
- * parv[5]: out-of-bound data
- */
-CMD_FUNC(cmd_sasl)
-{
-	if (!SASL_SERVER || MyUser(client) || (parc < 4) || !parv[4])
-		return;
-
-	if (!strcasecmp(parv[1], me.name) || !strncmp(parv[1], me.id, 3))
-	{
-		Client *target;
-
-		target = find_client(parv[2], NULL);
-		if (!target || !MyConnect(target))
-			return;
-
-		if (target->user == NULL)
-			make_user(target);
-
-		/* reject if another SASL agent is answering */
-		if (*target->local->sasl_agent && strcasecmp(client->name, target->local->sasl_agent))
-			return;
-		else
-			strlcpy(target->local->sasl_agent, client->name, sizeof(target->local->sasl_agent));
-
-		if (*parv[3] == 'C')
-		{
-			RunHookReturn(HOOKTYPE_SASL_CONTINUATION, !=0, target, parv[4]);
-			sendto_one(target, NULL, "AUTHENTICATE %s", parv[4]);
-		}
-		else if (*parv[3] == 'D')
-		{
-			*target->local->sasl_agent = '\0';
-			if (*parv[4] == 'F')
-			{
-				target->local->sasl_sent_time = 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++;
-				RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 1);
-				sendnumeric(target, RPL_SASLSUCCESS);
-			}
-		}
-		else if (*parv[3] == 'M')
-			sendnumeric(target, RPL_SASLMECHS, parv[4]);
-
-		return;
-	}
-
-	/* not for us; propagate. */
-	sendto_server(client, 0, 0, NULL, ":%s SASL %s %s %c %s %s",
-	    client->name, parv[1], parv[2], *parv[3], parv[4], parc > 5 ? parv[5] : "");
-}
-
-/*
- * AUTHENTICATE message
- *
- * parv[1]: data
- */
-CMD_FUNC(cmd_authenticate)
-{
-	Client *agent_p = NULL;
-
-	/* Failing to use CAP REQ for sasl is a protocol violation. */
-	if (!SASL_SERVER || !MyConnect(client) || BadPtr(parv[1]) || !HasCapability(client, "sasl"))
-		return;
-
-	if ((parv[1][0] == ':') || strchr(parv[1], ' '))
-	{
-		sendnumeric(client, ERR_CANNOTDOCOMMAND, "AUTHENTICATE", "Invalid parameter");
-		return;
-	}
-
-	if (strlen(parv[1]) > 400)
-	{
-		sendnumeric(client, ERR_SASLTOOLONG);
-		return;
-	}
-
-	if (client->user == NULL)
-		make_user(client);
-
-	if (*client->local->sasl_agent)
-		agent_p = find_client(client->local->sasl_agent, NULL);
-
-	if (agent_p == NULL)
-	{
-		char *addr = BadPtr(client->ip) ? "0" : client->ip;
-		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);
-
-		if (certfp)
-			sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s %s",
-			    me.name, SASL_SERVER, client->id, parv[1], certfp);
-		else
-			sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s",
-			    me.name, SASL_SERVER, client->id, parv[1]);
-	}
-	else
-		sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s C %s",
-		    me.name, AGENT_SID(agent_p), client->id, parv[1]);
-
-	client->local->sasl_out++;
-	client->local->sasl_sent_time = TStime();
-}
-
-static int abort_sasl(Client *client)
-{
-	client->local->sasl_sent_time = 0;
-
-	if (client->local->sasl_out == 0 || client->local->sasl_complete)
-		return 0;
-
-	client->local->sasl_out = client->local->sasl_complete = 0;
-	sendnumeric(client, ERR_SASLABORTED);
-
-	if (*client->local->sasl_agent)
-	{
-		Client *agent_p = find_client(client->local->sasl_agent, NULL);
-
-		if (agent_p != NULL)
-		{
-			sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s D A",
-			    me.name, AGENT_SID(agent_p), client->id);
-			return 0;
-		}
-	}
-
-	sendto_server(NULL, 0, 0, NULL, ":%s SASL * %s D A", me.name, client->id);
-	return 0;
-}
-
-/** Is this capability visible?
- * Note that 'client' may be NULL when queried from CAP DEL / CAP NEW
- */
-int sasl_capability_visible(Client *client)
-{
-	if (!SASL_SERVER || !find_server(SASL_SERVER, NULL))
-		return 0;
-
-	/* Don't advertise 'sasl' capability if we are going to reject the
-	 * user anyway due to set::plaintext-policy. This way the client
-	 * won't attempt SASL authentication and thus it prevents the client
-	 * from sending the password unencrypted (in case of method PLAIN).
-	 */
-	if (client && !IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
-		return 0;
-
-	/* Similarly, don't advertise when we are going to reject the user
-	 * due to set::outdated-tls-policy.
-	 */
-	if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
-		return 0;
-
-	return 1;
-}
-
-int sasl_connect(Client *client)
-{
-	return abort_sasl(client);
-}
-
-int sasl_quit(Client *client, MessageTag *mtags, const char *comment)
-{
-	return abort_sasl(client);
-}
-
-int sasl_server_quit(Client *client, MessageTag *mtags)
-{
-	if (!SASL_SERVER)
-		return 0;
-
-	/* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */
-	if (!strcasecmp(client->name, SASL_SERVER))
-		send_cap_notify(0, "sasl");
-
-	return 0;
-}
-
-void auto_discover_sasl_server(int justlinked)
-{
-	if (!SASL_SERVER && SERVICES_NAME)
-	{
-		Client *client = find_server(SERVICES_NAME, NULL);
-		if (client && moddata_client_get(client, "saslmechlist"))
-		{
-			/* SASL server found */
-			if (justlinked)
-			{
-				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)
-				sasl_server_synced(client);
-		}
-	}
-}
-
-int sasl_server_synced(Client *client)
-{
-	if (!SASL_SERVER)
-	{
-		auto_discover_sasl_server(1);
-		return 0;
-	}
-
-	/* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */
-	if (!strcasecmp(client->name, SASL_SERVER))
-		send_cap_notify(1, "sasl");
-
-	return 0;
-}
-
-MOD_INIT()
-{
-	ClientCapabilityInfo cap;
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	CommandAdd(modinfo->handle, MSG_SASL, cmd_sasl, MAXPARA, CMD_USER|CMD_SERVER);
-	CommandAdd(modinfo->handle, MSG_AUTHENTICATE, cmd_authenticate, MAXPARA, CMD_UNREGISTERED|CMD_USER);
-
-	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, sasl_connect);
-	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, sasl_quit);
-	HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, sasl_server_quit);
-	HookAdd(modinfo->handle, HOOKTYPE_SERVER_SYNCED, 0, sasl_server_synced);
-	HookAdd(modinfo->handle, HOOKTYPE_ACCOUNT_LOGIN, 0, sasl_account_login);
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "sasl";
-	cap.visible = sasl_capability_visible;
-	cap.parameter = sasl_capability_parameter;
-	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SASL);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "saslmechlist";
-	mreq.free = saslmechlist_free;
-	mreq.serialize = saslmechlist_serialize;
-	mreq.unserialize = saslmechlist_unserialize;
-	mreq.sync = MODDATA_SYNC_EARLY;
-	mreq.self_write = 1;
-	mreq.type = MODDATATYPE_CLIENT;
-	ModDataAdd(modinfo->handle, mreq);
-
-	EventAdd(modinfo->handle, "sasl_timeout", sasl_timeout, NULL, 2000, 0);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	auto_discover_sasl_server(0);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void saslmechlist_free(ModData *m)
-{
-	safe_free(m->str);
-}
-
-const char *saslmechlist_serialize(ModData *m)
-{
-	if (!m->str)
-		return NULL;
-	return m->str;
-}
-
-void saslmechlist_unserialize(const char *str, ModData *m)
-{
-	safe_strdup(m->str, str);
-}
-
-const char *sasl_capability_parameter(Client *client)
-{
-	Client *server;
-
-	if (SASL_SERVER)
-	{
-		server = find_server(SASL_SERVER, NULL);
-		if (server)
-			return moddata_client_get(server, "saslmechlist"); /* NOTE: could still return NULL */
-	}
-
-	return NULL;
-}
-
-EVENT(sasl_timeout)
-{
-	Client *client;
-
-	list_for_each_entry(client, &unknown_list, lclient_node)
-	{
-		if (client->local->sasl_sent_time &&
-		    (TStime() - client->local->sasl_sent_time > iConf.sasl_timeout))
-		{
-			sendnotice(client, "SASL request timed out (server or client misbehaving) -- aborting SASL and continuing connection...");
-			abort_sasl(client);
-		}
-	}
-}
diff --git a/src/modules/sdesc.c b/src/modules/sdesc.c
@@ -1,92 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sdesc.c
- *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
- *
- *   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_sdesc);
-
-#define MSG_SDESC 	"SDESC"	/* sdesc */
-
-ModuleHeader MOD_HEADER
-  = {
-	"sdesc",	/* Name of module */
-	"5.0", /* Version */
-	"command /sdesc", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SDESC, cmd_sdesc, 1, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* cmd_sdesc - 15/05/1999 - Stskeeps
- *  :prefix SDESC
- *  parv[1] - description
- *  D: Sets server info if you are Server Admin (ONLINE)
-*/
-
-CMD_FUNC(cmd_sdesc)
-{
-	if (!ValidatePermissionsForPath("server:description",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SDESC");
-		return;
-	}
-
-	if (strlen(parv[1]) > REALLEN)
-	{
-		if (MyConnect(client))
-		{
-			sendnotice(client, "*** /SDESC Error: \"Server info\" may maximum be %i characters of length",
-				REALLEN);
-			return;
-		}
-	}
-
-	strlncpy(client->uplink->info, parv[1], sizeof(client->uplink->info), REALLEN);
-
-	sendto_server(client, 0, 0, NULL, ":%s SDESC :%s", client->name, parv[1]);
-
-	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
@@ -1,101 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/sendsno.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_sendsno);
-
-#define MSG_SENDSNO   "SENDSNO"
-
-ModuleHeader MOD_HEADER
-  = {
-	"sendsno",	/* Name of module */
-	"5.0", /* Version */
-	"command /sendsno", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SENDSNO, cmd_sendsno, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_sendsno - Written by Syzop, bit based on SENDUMODE from Stskeeps
-**      parv[1] = target snomask
-**      parv[2] = message text
-** Servers can use this to:
-**   :server.unreal.net SENDSNO e :Hiiiii
-*/
-CMD_FUNC(cmd_sendsno)
-{
-	MessageTag *mtags = NULL;
-	const char *sno, *msg, *p;
-	Client *acptr;
-
-	if ((parc < 3) || BadPtr(parv[2]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SENDSNO");
-		return;
-	}
-	sno = parv[1];
-	msg = parv[2];
-
-	new_message(client, recv_mtags, &mtags);
-
-	/* Forward to others... */
-	sendto_server(client, 0, 0, mtags, ":%s SENDSNO %s :%s", client->id, parv[1], parv[2]);
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->user->snomask)
-		{
-			char found = 0;
-			for (p = sno; *p; p++)
-			{
-				if (strchr(acptr->user->snomask, *p))
-				{
-					found = 1;
-					break;
-				}
-			}
-			if (found)
-				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
@@ -1,95 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/sendumode.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_sendumode);
-
-/* Place includes here */
-#define MSG_SENDUMODE   "SENDUMODE"
-#define MSG_SMO         "SMO"
-
-ModuleHeader MOD_HEADER
-  = {
-	"sendumode",	/* Name of module */
-	"5.0", /* Version */
-	"command /sendumode", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SENDUMODE, cmd_sendumode, MAXPARA, CMD_SERVER);
-	CommandAdd(modinfo->handle, MSG_SMO, cmd_sendumode, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** SENDUMODE - Send to usermode command (S2S traffic only).
- * parv[1] = target user modes
- * parv[2] = message text
- * For example:
- * :server SENDUMODE o :Serious problem: blablabla
- */
-CMD_FUNC(cmd_sendumode)
-{
-	MessageTag *mtags = NULL;
-	Client *acptr;
-	const char *message;
-	const char *p;
-	int i;
-	long umode_s = 0;
-
-	message = (parc > 3) ? parv[3] : parv[2];
-
-	if (parc < 3)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SENDUMODE");
-		return;
-	}
-
-	new_message(client, recv_mtags, &mtags);
-
-	sendto_server(client, 0, 0, mtags, ":%s SENDUMODE %s :%s", client->id, parv[1], message);
-
-	umode_s = set_usermode(parv[1]);
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		if (acptr->umodes & umode_s)
-			sendto_one(acptr, mtags, ":%s NOTICE %s :%s", client->name, acptr->name, message);
-	}
-
-	free_message_tags(mtags);
-}
diff --git a/src/modules/server-time.c b/src/modules/server-time.c
@@ -1,99 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/server-time.c
- *   (C) 2019 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"server-time",
-	"5.0",
-	"server-time CAP",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Variables */
-long CAP_SERVER_TIME = 0L;
-
-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()
-{
-	ClientCapabilityInfo cap;
-	ClientCapability *c;
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "server-time";
-	c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SERVER_TIME);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "time";
-	mtag.is_ok = server_time_mtag_is_ok;
-	mtag.clicap_handler = c;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_or_inherit_time);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending
- * 'server-time' is permitted to do so and uses a permitted
- * syntax.
- * We simply allow server-time ONLY from servers.
- */
-int server_time_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client) && !BadPtr(value))
-		return 1;
-
-	return 0;
-}
-
-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)
-	{
-		m = duplicate_mtag(m);
-	} else
-	{
-		m = safe_alloc(sizeof(MessageTag));
-		safe_strdup(m->name, "time");
-		safe_strdup(m->value, timestamp_iso8601_now());
-	}
-	AddListItem(m, *mtag_list);
-}
diff --git a/src/modules/server.c b/src/modules/server.c
@@ -1,2259 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/server.c
- *   (C) 2004-present 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"
-
-/* 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;
-};
-
-typedef struct ConfigItem_deny_link ConfigItem_deny_link;
-struct ConfigItem_deny_link {
-	ConfigItem_deny_link *prev, *next;
-	ConfigFlag_except flag;
-	ConfigItem_mask  *mask;
-	CRuleNode *rule; /**< parsed crule */
-	char *prettyrule; /**< human printable version */
-	char *reason; /**< Reason for the deny link */
-};
-
-/* Forward declarations */
-void server_config_setdefaults(cfgstruct *cfg);
-void server_config_free();
-int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-EVENT(server_autoconnect);
-EVENT(server_handshake_timeout);
-void send_channel_modes_sjoin3(Client *to, Channel *channel);
-CMD_FUNC(cmd_server);
-CMD_FUNC(cmd_sid);
-ConfigItem_link *_verify_link(Client *client);
-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 *);
-int _is_services_but_not_ulined(Client *client);
-const char *_check_deny_link(ConfigItem_link *link, int auto_connect);
-int server_stats_denylink_all(Client *client, const char *para);
-int server_stats_denylink_auto(Client *client, const char *para);
-
-/* Global variables */
-static cfgstruct cfg;
-static char *last_autoconnect_server = NULL;
-static ConfigItem_deny_link *conf_deny_link = NULL;
-
-ModuleHeader MOD_HEADER
-  = {
-	"server",
-	"5.0",
-	"command /server", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_PROTOCTL_SERVERS, _send_protoctl_servers);
-	EfunctionAddVoid(modinfo->handle, EFUNC_SEND_SERVER_MESSAGE, _send_server_message);
-	EfunctionAddPVoid(modinfo->handle, EFUNC_VERIFY_LINK, TO_PVOIDFUNC(_verify_link));
-	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);
-	EfunctionAdd(modinfo->handle, EFUNC_IS_SERVICES_BUT_NOT_ULINED, _is_services_but_not_ulined);
-	EfunctionAddConstString(modinfo->handle, EFUNC_CHECK_DENY_LINK, _check_deny_link);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, server_config_test);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	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);
-	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, server_stats_denylink_all);
-	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, server_stats_denylink_auto);
-	CommandAdd(modinfo->handle, "SERVER", cmd_server, MAXPARA, CMD_UNREGISTERED|CMD_SERVER);
-	CommandAdd(modinfo->handle, "SID", cmd_sid, MAXPARA, CMD_SERVER);
-
-	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()
-{
-	server_config_free();
-	SavePersistentPointer(modinfo, last_autoconnect_server);
-	return MOD_SUCCESS;
-}
-
-/** 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;
-}
-
-void server_config_free(void)
-{
-	ConfigItem_deny_link *d, *d_next;
-
-	for (d = conf_deny_link; d; d = d_next)
-	{
-		d_next = d->next;
-		unreal_delete_masks(d->mask);
-		crule_free(&d->rule);
-		safe_free(d->prettyrule);
-		safe_free(d->reason);
-		DelListItem(d, conf_deny_link);
-		safe_free(d);
-	}
-	conf_deny_link = NULL;
-}
-
-int server_config_test_set_server_linking(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		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_set_server_linking(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	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_config_test_deny_link(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-        int errors = 0;
-        ConfigEntry *cep;
-	char has_mask = 0, has_rule = 0, has_type = 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->items)
-		{
-			if (config_is_blankorempty(cep, "deny link"))
-			{
-				errors++;
-				continue;
-			}
-			else if (!strcmp(cep->name, "mask"))
-			{
-				has_mask = 1;
-			} else if (!strcmp(cep->name, "rule"))
-			{
-				int val = 0;
-				if (has_rule)
-				{
-					config_warn_duplicate(cep->file->filename,
-						cep->line_number, "deny link::rule");
-					continue;
-				}
-				has_rule = 1;
-				if ((val = crule_test(cep->value)))
-				{
-					config_error("%s:%i: deny link::rule contains an invalid expression: %s",
-						cep->file->filename,
-						cep->line_number,
-						crule_errstring(val));
-					errors++;
-				}
-			}
-			else if (!strcmp(cep->name, "type"))
-			{
-				if (has_type)
-				{
-					config_warn_duplicate(cep->file->filename,
-						cep->line_number, "deny link::type");
-					continue;
-				}
-				has_type = 1;
-				if (!strcmp(cep->value, "auto"))
-				;
-				else if (!strcmp(cep->value, "all"))
-				;
-				else {
-					config_status("%s:%i: unknown deny link type",
-					cep->file->filename, cep->line_number);
-					errors++;
-				}
-			} else if (!strcmp(cep->name, "reason"))
-			{
-			}
-			else
-			{
-				config_error_unknown(cep->file->filename,
-					cep->line_number, "deny link", cep->name);
-				errors++;
-			}
-		}
-		else
-		{
-			// Sections
-			if (!strcmp(cep->name, "mask"))
-			{
-				if (cep->value || cep->items)
-					has_mask = 1;
-			}
-			else
-			{
-				config_error_unknown(cep->file->filename,
-					cep->line_number, "deny link", cep->name);
-				errors++;
-				continue;
-			}
-		}
-	}
-	if (!has_mask)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"deny link::mask");
-		errors++;
-	}
-	if (!has_rule)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"deny link::rule");
-		errors++;
-	}
-	if (!has_type)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"deny link::type");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int server_config_run_deny_link(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-	ConfigItem_deny_link *deny;
-
-	deny = safe_alloc(sizeof(ConfigItem_deny_link));
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "mask"))
-		{
-			unreal_add_masks(&deny->mask, cep);
-		}
-		else if (!strcmp(cep->name, "rule"))
-		{
-			deny->rule = crule_parse(cep->value);
-			safe_strdup(deny->prettyrule, cep->value);
-		}
-		else if (!strcmp(cep->name, "reason"))
-		{
-			safe_strdup(deny->reason, cep->value);
-		}
-		else if (!strcmp(cep->name, "type")) {
-			if (!strcmp(cep->value, "all"))
-				deny->flag.type = CRULE_ALL;
-			else if (!strcmp(cep->value, "auto"))
-				deny->flag.type = CRULE_AUTO;
-		}
-	}
-
-	/* Set a default reason, if needed */
-	if (!deny->reason)
-		safe_strdup(deny->reason, "Denied");
-
-	AddListItem(deny, conf_deny_link);
-	return 1;
-}
-
-int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	if ((type == CONFIG_SET) && !strcmp(ce->name, "server-linking"))
-		return server_config_test_set_server_linking(cf, ce, type, errs);
-
-	if ((type == CONFIG_DENY) && !strcmp(ce->value, "link"))
-		return server_config_test_deny_link(cf, ce, type, errs);
-
-	return 0; /* not for us */
-}
-
-int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	if ((type == CONFIG_SET) && ce && !strcmp(ce->name, "server-linking"))
-		return server_config_run_set_server_linking(cf, ce, type);
-
-	if ((type == CONFIG_DENY) && !strcmp(ce->value, "link"))
-		return server_config_run_deny_link(cf, ce, type);
-
-	return 0; /* not for us */
-}
-
-int server_needs_linking(ConfigItem_link *aconf)
-{
-	Client *client;
-	ConfigItem_class *class;
-
-	/* We're only interested in autoconnect blocks that also have
-	 * a valid link::outgoing configuration. We also ignore
-	 * temporary link blocks (not that they should exist...).
-	 */
-	if (!(aconf->outgoing.options & CONNECT_AUTO) ||
-	    (!aconf->outgoing.hostname && !aconf->outgoing.file) ||
-	    (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 */
-	if (check_deny_link(aconf, 1))
-		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)
- * @param software	Software version in use (can be NULL)
- * @param protoctol	UnrealIRCd protocol version in use (can be 0)
- * @param flags		Server flags (hardly ever used, can be NULL)
- * @returns 1 if link is denied (client is already killed), 0 if not.
- */
-int _check_deny_version(Client *cptr, char *software, int protocol, char *flags)
-{
-	ConfigItem_deny_version *vlines;
-	
-	for (vlines = conf_deny_version; vlines; vlines = vlines->next)
-	{
-		if (match_simple(vlines->mask, cptr->name))
-			break;
-	}
-	
-	if (vlines)
-	{
-		char *proto = vlines->version;
-		char *vflags = vlines->flags;
-		int result = 0, i;
-		switch (*proto)
-		{
-			case '<':
-				proto++;
-				if (protocol < atoi(proto))
-					result = 1;
-				break;
-			case '>':
-				proto++;
-				if (protocol > atoi(proto))
-					result = 1;
-				break;
-			case '=':
-				proto++;
-				if (protocol == atoi(proto))
-					result = 1;
-				break;
-			case '!':
-				proto++;
-				if (protocol != atoi(proto))
-					result = 1;
-				break;
-			default:
-				if (protocol == atoi(proto))
-					result = 1;
-				break;
-		}
-		if (protocol == 0 || *proto == '*')
-			result = 0;
-
-		if (result)
-		{
-			exit_client(cptr, NULL, "Denied by deny version { } block");
-			return 0;
-		}
-
-		if (flags)
-		{
-			for (i = 0; vflags[i]; i++)
-			{
-				if (vflags[i] == '!')
-				{
-					i++;
-					if (strchr(flags, vflags[i])) {
-						result = 1;
-						break;
-					}
-				}
-				else if (!strchr(flags, vflags[i]))
-				{
-						result = 1;
-						break;
-				}
-			}
-
-			if (*vflags == '*' || !strcmp(flags, "0"))
-				result = 0;
-		}
-
-		if (result)
-		{
-			exit_client(cptr, NULL, "Denied by deny version { } block");
-			return 0;
-		}
-	}
-	
-	return 1;
-}
-
-/** Send our PROTOCTL SERVERS=x,x,x,x stuff.
- * When response is set, it will be PROTOCTL SERVERS=*x,x,x (mind the asterisk).
- */
-void _send_protoctl_servers(Client *client, int response)
-{
-	char buf[512];
-	Client *acptr;
-	int sendit = 1;
-
-	sendto_one(client, NULL, "PROTOCTL EAUTH=%s,%d,%s%s,UnrealIRCd-%s",
-		me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", buildid);
-		
-	ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=%s", response ? "*" : "");
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s,", acptr->id);
-		sendit = 1;
-		if (strlen(buf) > sizeof(buf)-12)
-		{
-			if (buf[strlen(buf)-1] == ',')
-				buf[strlen(buf)-1] = '\0';
-			sendto_one(client, NULL, "%s", buf);
-			/* We use the asterisk here too for continuation lines */
-			ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=*");
-			sendit = 0;
-		}
-	}
-	
-	/* Remove final comma (if any) */
-	if (buf[strlen(buf)-1] == ',')
-		buf[strlen(buf)-1] = '\0';
-
-	if (sendit)
-		sendto_one(client, NULL, "%s", buf);
-}
-
-void _send_server_message(Client *client)
-{
-	if (client->server && client->server->flags.server_sent)
-	{
-#ifdef DEBUGMODE
-		abort();
-#endif
-		return;
-	}
-
-	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->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 client The client which issued the command
- * @returns On successfull authentication, the link block is returned. On failure NULL is returned (client has been killed!).
- */
-ConfigItem_link *_verify_link(Client *client)
-{
-	ConfigItem_link *link, *orig_link;
-	Client *acptr = NULL, *ocptr = NULL;
-	ConfigItem_ban *bconf;
-
-	/* We set the sockhost here so you can have incoming masks based on hostnames.
-	 * Perhaps a bit late to do it here, but does anyone care?
-	 */
-	if (client->local->hostp && client->local->hostp->h_name)
-		set_sockhost(client, client->local->hostp->h_name);
-
-	if (!client->local->passwd)
-	{
-		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 NULL;
-	}
-
-	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->server->conf
-		 */
-
-		/* Actually we still need to double check the servername to avoid confusion. */
-		if (strcasecmp(client->name, client->server->conf->servername))
-		{
-			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 NULL;
-		}
-		link = client->server->conf;
-		goto skip_host_check;
-	} else {
-		/* Hunt the linkblock down ;) */
-		link = find_link(client->name);
-	}
-	
-	if (!link)
-	{
-		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 NULL;
-	}
-	
-	if (!link->incoming.match)
-	{
-		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::match set.",
-		           log_data_link_block(link));
-		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
-		return NULL;
-	}
-
-	orig_link = link;
-	if (!user_allowed_by_security_group(client, link->incoming.match))
-	{
-		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_INCOMING_MASK_MISMATCH", client,
-		           "Link with server $client.details denied: Server is in link block but link::incoming::mask didn't match",
-		           log_data_link_block(orig_link));
-		exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG);
-		return NULL;
-	}
-
-skip_host_check:
-	/* 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
-		 * they mix different authentication systems (plaintext password
-		 * vs an "TLS Auth type" like spkifp/tlsclientcert/tlsclientcertfp).
-		 * The 'if' statement below is a bit complex but it consists of 2 things:
-		 * 1. Check if our side expects a plaintext password but we did not receive one
-		 * 2. Check if our side expects a non-plaintext password but we did receive one
-		 */
-		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, "*")))
-		{
-			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)
-		{
-			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)
-		{
-			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)
-		{
-			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
-		{
-			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));
-		}
-		exit_client(client, NULL, "Link denied (Authentication failed)");
-		return NULL;
-	}
-
-	/* Verify the TLS certificate (if requested) */
-	if (link->verify_certificate)
-	{
-		char *errstr = NULL;
-
-		if (!IsTLS(client))
-		{
-			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 NULL;
-		}
-		if (!verify_certificate(client->local->ssl, link->servername, &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 NULL;
-		}
-	}
-
-	if ((bconf = find_ban(NULL, client->name, CONF_BAN_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 NULL;
-	}
-
-	if (link->class->clients + 1 > link->class->maxclients)
-	{
-		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 NULL;
-	}
-	if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_DENY) && !IsSecure(client))
-	{
-		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 NULL;
-	}
-	if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_DENY) && outdated_tls_client(client))
-	{
-		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 NULL;
-	}
-	/* 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 NULL;
-		} 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");
-		}
-	}
-
-	return link;
-}
-
-/** Server command. Only for locally connected servers!!.
- * parv[1] = server name
- * parv[2] = hop count
- * parv[3] = server description, may include protocol version and other stuff too (VL)
- */
-CMD_FUNC(cmd_server)
-{
-	const char *servername = NULL;	/* Pointer for servername */
-	char *ch = NULL;	/* */
-	char descbuf[BUFSIZE];
-	int  hop = 0;
-	char info[REALLEN + 61];
-	ConfigItem_link *aconf = NULL;
-	char *flags = NULL, *protocol = NULL, *inf = NULL, *num = NULL;
-	int incoming;
-	const char *err;
-
-	if (IsUser(client))
-	{
-		sendnumeric(client, ERR_ALREADYREGISTRED);
-		sendnotice(client, "*** Sorry, but your IRC program doesn't appear to support changing servers.");
-		return;
-	}
-
-	if (parc < 4 || (!*parv[3]))
-	{
-		exit_client(client, NULL,  "Not enough SERVER parameters");
-		return;
-	}
-
-	servername = parv[1];
-
-	/* Remote 'SERVER' command is not possible on a 100% SID network */
-	if (!MyConnect(client))
-	{
-		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;
-	}
-
-	if (client->local->listener && (client->local->listener->options & LISTENER_CLIENTSONLY))
-	{
-		exit_client(client, NULL, "This port is for clients only");
-		return;
-	}
-
-	if (!valid_server_name(servername))
-	{
-		exit_client(client, NULL, "Bogus server name");
-		return;
-	}
-
-	if (!client->local->passwd)
-	{
-		exit_client(client, NULL, "Missing password");
-		return;
-	}
-
-	/* 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 (!(aconf = verify_link(client)))
-		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.
-	 */
-
-
-	if (strlen(client->id) != 3)
-	{
-		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;
-	}
-
-	hop = atol(parv[2]);
-	if (hop != 1)
-	{
-		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;
-	}
-	client->hopcount = hop;
-
-	strlcpy(info, parv[parc - 1], sizeof(info));
-
-	/* Parse "VL" data in description */
-	if (SupportVL(client))
-	{
-		char tmp[REALLEN + 61];
-		inf = protocol = flags = num = NULL;
-		strlcpy(tmp, info, sizeof(tmp)); /* work on a copy */
-
-		/* We are careful here to allow invalid syntax or missing
-		 * stuff, which mean that particular variable will stay NULL.
-		 */
-
-		protocol = strtok(tmp, "-");
-		if (protocol)
-			flags = strtok(NULL, "-");
-		if (flags)
-			num = strtok(NULL, " ");
-		if (num)
-			inf = strtok(NULL, "");
-		if (inf)
-		{
-			strlcpy(client->info, inf[0] ? inf : "server", sizeof(client->info)); /* set real description */
-
-			if (!_check_deny_version(client, NULL, atoi(protocol), flags))
-				return; /* Rejected */
-		} else {
-			strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
-		}
-	} else {
-		strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
-	}
-
-	if ((err = check_deny_link(aconf, 0)))
-	{
-		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DENY_LINK_BLOCK", client,
-			   "Server link $servername rejected by deny link { } block: $reason",
-			   log_data_string("servername", servername),
-			   log_data_string("reason", err));
-		exit_client_fmt(client, NULL, "Disallowed by connection rule: %s", err);
-		return;
-	}
-
-	if (aconf->options & CONNECT_QUARANTINE)
-		SetQuarantined(client);
-
-	ircsnprintf(descbuf, sizeof descbuf, "Server: %s", servername);
-	fd_desc(client->local->fd, descbuf);
-
-	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).
- * parv[1] = server name
- * parv[2] = hop count (always >1)
- * parv[3] = SID
- * parv[4] = server description
- */
-CMD_FUNC(cmd_sid)
-{
-	Client *acptr, *ocptr;
-	ConfigItem_link	*aconf;
-	ConfigItem_ban *bconf;
-	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))
-	{
-		sendnumeric(client, ERR_NOTFORUSERS, "SID");
-		return;
-	}
-
-	if (parc < 4 || BadPtr(parv[3]))
-	{
-		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;
-	}
-
-	/* Check if server already exists... */
-	if ((acptr = find_server(servername, NULL)))
-	{
-		/* Found. Bad. Quit. */
-
-		if (IsMe(acptr))
-		{
-			/* 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;
-		}
-
-		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 = (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)))
-	{
-		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("servername", servername),
-		           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))
-	{
-		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 = atoi(parv[2]);
-	if (hop < 2)
-	{
-		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 (!client->direction->server->conf)
-	{
-		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;
-	}
-
-	aconf = client->direction->server->conf;
-
-	if (!aconf->hub)
-	{
-		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))
-	{
-		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;
-	}
-
-	if (aconf->leaf)
-	{
-		if (!match_simple(aconf->leaf, servername))
-		{
-			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))
-	{
-		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(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);
-	SetServer(acptr);
-	/* 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);
-	irccounts.servers++;
-	find_or_add(acptr->name);
-	add_client_to_list(acptr);
-	add_to_client_hash_table(acptr->name, acptr);
-	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->uplink->id, acptr->name, hop + 1, acptr->id, acptr->info);
-
-	RunHook(HOOKTYPE_POST_SERVER_CONNECT, acptr);
-}
-
-void _introduce_user(Client *to, Client *acptr)
-{
-	char buf[512];
-
-	build_umode_string(acptr, 0, SEND_UMODES, buf);
-
-	sendto_one_nickcmd(to, NULL, acptr, buf);
-	
-	send_moddata_client(to, acptr);
-
-	if (acptr->user->away)
-		sendto_one(to, NULL, ":%s AWAY :%s", acptr->id, acptr->user->away);
-
-	if (acptr->user->swhois)
-	{
-		SWhois *s;
-		for (s = acptr->user->swhois; s; s = s->next)
-		{
-			if (CHECKSERVERPROTO(to, PROTO_EXTSWHOIS))
-			{
-				sendto_one(to, NULL, ":%s SWHOIS %s + %s %d :%s",
-					me.id, acptr->name, s->setby, s->priority, s->line);
-			} else
-			{
-				sendto_one(to, NULL, ":%s SWHOIS %s :%s",
-					me.id, acptr->name, s->line);
-			}
-		}
-	}
-}
-
-#define SafeStr(x)    ((x && *(x)) ? (x) : "*")
-
-/** Broadcast SINFO.
- * @param cptr   The server to send the information about.
- * @param to     The server to send the information TO (NULL for broadcast).
- * @param except The direction NOT to send to.
- * This function takes into account that the server may not
- * provide all of the detailed info. If any information is
- * absent we will send 0 for numbers and * for NULL strings.
- */
-void _broadcast_sinfo(Client *acptr, Client *to, Client *except)
-{
-	char chanmodes[128], buf[512];
-
-	if (acptr->server->features.chanmodes[0])
-	{
-		snprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
-			 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->server->boottime,
-		      acptr->server->features.protocol,
-		      SafeStr(acptr->server->features.usermodes),
-		      chanmodes,
-		      SafeStr(acptr->server->features.nickchars),
-		      SafeStr(acptr->server->features.software));
-
-	if (to)
-	{
-		/* Targetted to one server */
-		sendto_one(to, NULL, ":%s SINFO %s", acptr->id, buf);
-	} else {
-		/* Broadcast (except one side...) */
-		sendto_server(except, 0, 0, NULL, ":%s SINFO %s", acptr->id, buf);
-	}
-}
-
-/** 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)
-{
-	Client *acptr;
-
-	if (incoming)
-	{
-		/* If this is an incomming connection, then we have just received
-		 * their stuff and now send our PASS, PROTOCTL and SERVER messages back.
-		 */
-		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 : "*");
-
-		send_proto(client, aconf);
-		send_server_message(client);
-	}
-
-	/* Broadcast new server to the rest of the network */
-	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(client, NULL, client);
-
-	/* Send moddata of &me (if any, likely minimal) */
-	send_moddata_client(client, &me);
-
-	list_for_each_entry_reverse(acptr, &global_server_list, client_node)
-	{
-		/* acptr->direction == acptr for acptr == client */
-		if (acptr->direction == client)
-			continue;
-
-		if (IsServer(acptr))
-		{
-			sendto_one(client, NULL, ":%s SID %s %d %s :%s",
-			    acptr->uplink->id,
-			    acptr->name, acptr->hopcount + 1,
-			    acptr->id, acptr->info);
-
-			/* Also signal to the just-linked server which
-			 * servers are fully linked.
-			 * Now you might ask yourself "Why don't we just
-			 * assume every server you get during link phase
-			 * is fully linked?", well.. there's a race condition
-			 * if 2 servers link (almost) at the same time,
-			 * then you would think the other one is fully linked
-			 * while in fact he was not.. -- Syzop.
-			 */
-			if (acptr->server->flags.synced)
-				sendto_one(client, NULL, ":%s EOS", acptr->id);
-			/* Send SINFO of our servers to their side */
-			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 == client */
-		if (acptr->direction == client)
-			continue;
-		if (IsUser(acptr))
-			introduce_user(client, acptr);
-	}
-	/*
-	   ** Last, pass all channels plus statuses
-	 */
-	{
-		Channel *channel;
-		for (channel = channels; channel; channel = channel->nextch)
-		{
-			send_channel_modes_sjoin3(client, channel);
-			if (channel->topic_time)
-				sendto_one(client, NULL, "TOPIC %s %s %lld :%s",
-				    channel->name, channel->topic_nick,
-				    (long long)channel->topic_time, channel->topic);
-			send_moddata_channel(client, channel);
-		}
-	}
-	
-	/* Send ModData for all member(ship) structs */
-	send_moddata_members(client);
-	
-	/* pass on TKLs */
-	tkl_sync(client);
-
-	RunHook(HOOKTYPE_SERVER_SYNC, client);
-
-	sendto_one(client, NULL, "NETINFO %i %lld %i %s 0 0 0 :%s",
-	    irccounts.global_max, (long long)TStime(), UnrealProtocol,
-	    CLOAK_KEY_CHECKSUM,
-	    NETWORK_NAME);
-
-	/* Send EOS (End Of Sync) to the just linked server... */
-	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
- * looked weird and just plain inefficient. We now fill up our send-buffer
- * really as much as we can, without causing any overflows of course.
- */
-void send_channel_modes_sjoin3(Client *to, Channel *channel)
-{
-	MessageTag *mtags = NULL;
-	Member *members;
-	Member *lp;
-	Ban *ban;
-	short nomode, nopara;
-	char tbuf[512]; /* work buffer, for temporary data */
-	char buf[1024]; /* send buffer */
-	char *bufptr; /* points somewhere in 'buf' */
-	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->name != '#')
-		return;
-
-	nomode = 0;
-	nopara = 0;
-	members = channel->members;
-
-	/* First we'll send channel, channel modes and members and status */
-
-	*modebuf = *parabuf = '\0';
-	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;
-	if (!(*parabuf))
-		nopara = 1;
-
-	/* Generate a new message (including msgid).
-	 * Due to the way SJOIN works, we will use the same msgid for
-	 * multiple SJOIN messages to servers. Rest assured that clients
-	 * will never see these duplicate msgid's though. They
-	 * will see a 'special' version instead with a suffix.
-	 */
-	new_message(&me, NULL, &mtags);
-
-	if (nomode && nopara)
-	{
-		ircsnprintf(buf, sizeof(buf),
-		    ":%s SJOIN %lld %s :", me.id,
-		    (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->name, modebuf);
-	}
-	if (!nopara && !nomode)
-	{
-		ircsnprintf(buf, sizeof(buf),
-		    ":%s SJOIN %lld %s %s %s :", me.id,
-		    (long long)channel->creationtime, channel->name, modebuf, parabuf);
-	}
-
-	prebuflen = strlen(buf);
-	bufptr = buf + prebuflen;
-
-	/* RULES:
-	 * - Use 'tbuf' as a working buffer, use 'p' to advance in 'tbuf'.
-	 *   Thus, be sure to do a 'p = tbuf' at the top of the loop.
-	 * - When one entry has been build, check if strlen(buf) + strlen(tbuf) > BUFSIZE - 8,
-	 *   if so, do not concat but send the current result (buf) first to the server
-	 *   and reset 'buf' to only the prebuf part (all until the ':').
-	 *   Then, in both cases, concat 'tbuf' to 'buf' and continue
-	 * - Be sure to ALWAYS zero terminate (*p = '\0') when the entry has been build.
-	 * - Be sure to add a space after each entry ;)
-	 *
-	 * For a more illustrated view, take a look at the first for loop, the others
-	 * are pretty much the same.
-	 *
-	 * Follow these rules, and things would be smooth and efficient (network-wise),
-	 * if you ignore them, expect crashes and/or heap corruption, aka: HELL.
-	 * You have been warned.
-	 *
-	 * Side note: of course things would be more efficient if the prebuf thing would
-	 * not be sent every time, but that's another story
-	 *      -- Syzop
-	 */
-
-	for (lp = members; lp; lp = lp->next)
-	{
-		p = mystpcpy(tbuf, modes_to_sjoin_prefix(lp->member_modes)); /* eg @+ */
-		p = mystpcpy(p, lp->client->id); /* nick (well, id) */
-		*p++ = ' ';
-		*p = '\0';
-
-		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
-		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
-		{
-			/* 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';
-		}
-		/* concat our stuff.. */
-		bufptr = mystpcpy(bufptr, tbuf);
-	}
-
-	for (ban = channel->banlist; ban; ban = ban->next)
-	{
-		p = tbuf;
-		if (SupportSJSBY(to))
-			p += add_sjsby(p, ban->who, ban->when);
-		*p++ = '&';
-		p = mystpcpy(p, ban->banstr);
-		*p++ = ' ';
-		*p = '\0';
-		
-		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
-		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
-		{
-			/* 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';
-		}
-		/* concat our stuff.. */
-		bufptr = mystpcpy(bufptr, tbuf);
-	}
-
-	for (ban = channel->exlist; ban; ban = ban->next)
-	{
-		p = tbuf;
-		if (SupportSJSBY(to))
-			p += add_sjsby(p, ban->who, ban->when);
-		*p++ = '"';
-		p = mystpcpy(p, ban->banstr);
-		*p++ = ' ';
-		*p = '\0';
-		
-		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
-		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
-		{
-			/* 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';
-		}
-		/* concat our stuff.. */
-		bufptr = mystpcpy(bufptr, tbuf);
-	}
-
-	for (ban = channel->invexlist; ban; ban = ban->next)
-	{
-		p = tbuf;
-		if (SupportSJSBY(to))
-			p += add_sjsby(p, ban->who, ban->when);
-		*p++ = '\'';
-		p = mystpcpy(p, ban->banstr);
-		*p++ = ' ';
-		*p = '\0';
-		
-		/* this is: if (strlen(tbuf) + strlen(buf) > BUFSIZE - 8) */
-		if ((p - tbuf) + (bufptr - buf) > BUFSIZE - 8)
-		{
-			/* 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';
-		}
-		/* concat our stuff.. */
-		bufptr = mystpcpy(bufptr, tbuf);
-	}
-
-	if (buf[prebuflen] || !sent)
-		sendto_one(to, mtags, "%s", buf);
-
-	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 && !aconf->outgoing.file)
-	{
-		/* 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 or link::outgoing::file 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 && !aconf->outgoing.file)
-	{
-		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 ? aconf->outgoing.hostname : aconf->outgoing.file, 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 ? aconf->outgoing.hostname : "127.0.0.1");
-	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,
-		   aconf->outgoing.file
-		   ? "Trying to activate link with server $client ($link_block.file)..."
-		   : "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 && !aconf->outgoing.file)
-	{
-		unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NOIP", client,
-		           "Connect to $client failed: no IP address or file to connect to",
-		           log_data_link_block(aconf));
-		return 0; /* handled upstream or shouldn't happen */
-	}
-
-	if (aconf->outgoing.file)
-		SetUnixSocket(client);
-	else if (strchr(aconf->connect_ip, ':'))
-		SetIPV6(client);
-	
-	safe_strdup(client->ip, aconf->connect_ip ? aconf->connect_ip : "127.0.0.1");
-	
-	snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(client, TRUE));
-	client->local->fd = fd_socket(IsUnixSocket(client) ? AF_UNIX : (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 ? aconf->outgoing.hostname : "127.0.0.1");
-
-	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,
-			    aconf->outgoing.file ? aconf->outgoing.file : client->ip,
-			    aconf->outgoing.port, client->local->socket_type))
-	{
-			unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client,
-				   aconf->outgoing.file
-				   ? "Connect to $client ($link_block.file) failed: $socket_error"
-				   : "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;
-}
-
-int _is_services_but_not_ulined(Client *client)
-{
-	if (!client->server || !client->server->features.software || !*client->name)
-		return 0; /* cannot detect software version or name not available yet */
-
-	if (our_strcasestr(client->server->features.software, "anope") ||
-	    our_strcasestr(client->server->features.software, "atheme"))
-	{
-		if (!find_uline(client->name))
-		{
-			unreal_log(ULOG_ERROR, "link", "LINK_NO_ULINES", client,
-			           "Server $client is a services server ($software). "
-			           "However, server $me does not have $client in the ulines { } block, "
-			           "which is required for services servers. "
-			           "See https://www.unrealircd.org/docs/Ulines_block",
-			           log_data_client("me", &me),
-			           log_data_string("software", client->server->features.software));
-			return 1; /* Is services AND no ulines { } entry */
-		}
-	}
-	return 0;
-}
-
-/** Check if this link should be denied due to deny link { } configuration
- * @param link		The link block
- * @param auto_connect	Set this to 1 if this is called from auto connect code
- *			(it will then check both CRULE_AUTO + CRULE_ALL)
- *			set it to 0 otherwise (will not check CRULE_AUTO blocks).
- * @returns The deny block if the server should be denied, or NULL if no deny block.
- */
-const char *_check_deny_link(ConfigItem_link *link, int auto_connect)
-{
-	ConfigItem_deny_link *d;
-
-	for (d = conf_deny_link; d; d = d->next)
-	{
-		if ((auto_connect == 0) && (d->flag.type == CRULE_AUTO))
-			continue;
-		if (unreal_mask_match_string(link->servername, d->mask) &&
-		    crule_eval(d->rule))
-		{
-			return d->reason;
-		}
-	}
-	return NULL;
-}
-
-int server_stats_denylink_all(Client *client, const char *para)
-{
-	ConfigItem_deny_link *links;
-	ConfigItem_mask *m;
-
-	if (!para || !(!strcmp(para, "D") || !strcasecmp(para, "denylinkall")))
-		return 0;
-
-	for (links = conf_deny_link; links; links = links->next)
-	{
-		if (links->flag.type == CRULE_ALL)
-		{
-			for (m = links->mask; m; m = m->next)
-				sendnumeric(client, RPL_STATSDLINE, 'D', m->mask, links->prettyrule);
-		}
-	}
-
-	return 1;
-}
-
-int server_stats_denylink_auto(Client *client, const char *para)
-{
-	ConfigItem_deny_link *links;
-	ConfigItem_mask *m;
-
-	if (!para || !(!strcmp(para, "d") || !strcasecmp(para, "denylinkauto")))
-		return 0;
-
-	for (links = conf_deny_link; links; links = links->next)
-	{
-		if (links->flag.type == CRULE_AUTO)
-		{
-			for (m = links->mask; m; m = m->next)
-				sendnumeric(client, RPL_STATSDLINE, 'd', m->mask, links->prettyrule);
-		}
-	}
-
-	return 1;
-}
diff --git a/src/modules/sethost.c b/src/modules/sethost.c
@@ -1,151 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sethost.c
- *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
- *
- *   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_sethost);
-
-/* Place includes here */
-#define MSG_SETHOST 	"SETHOST"	/* sethost */
-
-ModuleHeader MOD_HEADER
-  = {
-	"sethost",	/* Name of module */
-	"5.0", /* Version */
-	"command /sethost", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SETHOST, cmd_sethost, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-   cmd_sethost() added by Stskeeps (30/04/1999)
-               (modified at 15/05/1999) by Stskeeps | Potvin
-   :prefix SETHOST newhost
-   parv[1] - newhost
-*/
-CMD_FUNC(cmd_sethost)
-{
-	const char *vhost;
-
-	if (MyUser(client) && !ValidatePermissionsForPath("self:set:host",client,NULL,NULL,NULL))
-	{
-  		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc < 2)
-		vhost = NULL;
-	else
-		vhost = parv[1];
-
-	if (BadPtr(vhost))
-	{	
-		if (MyConnect(client))
-			sendnotice(client, "*** Syntax: /SetHost <new host>");
-		return;
-	}
-
-	if (strlen(parv[1]) > (HOSTLEN))
-	{
-		if (MyConnect(client))
-			sendnotice(client, "*** /SetHost Error: Hostnames are limited to %i characters.", HOSTLEN);
-		return;
-	}
-
-	if (!valid_host(vhost, 0))
-	{
-		sendnotice(client, "*** /SetHost Error: A hostname may only contain a-z, A-Z, 0-9, '-' & '.'.");
-		return;
-	}
-	if (vhost[0] == ':')
-	{
-		sendnotice(client, "*** A hostname cannot start with ':'");
-		return;
-	}
-
-	if (MyUser(client) && !strcmp(GetHost(client), vhost))
-	{
-		sendnotice(client, "/SetHost Error: requested host is same as current host.");
-		return;
-	}
-
-	userhost_save_current(client);
-
-	switch (UHOST_ALLOWED)
-	{
-		case UHALLOW_NEVER:
-			if (MyUser(client))
-			{
-				sendnotice(client, "*** /SetHost is disabled");
-				return;
-			}
-			break;
-		case UHALLOW_ALWAYS:
-			break;
-		case UHALLOW_NOCHANS:
-			if (MyUser(client) && client->user->joined)
-			{
-				sendnotice(client, "*** /SetHost can not be used while you are on a channel");
-				return;
-			}
-			break;
-		case UHALLOW_REJOIN:
-			/* join sent later when the host has been changed */
-			break;
-	}
-
-	/* hide it */
-	client->umodes |= UMODE_HIDE;
-	client->umodes |= UMODE_SETHOST;
-	/* get it in */
-	safe_strdup(client->user->virthost, vhost);
-	/* spread it out */
-	sendto_server(client, 0, 0, NULL, ":%s SETHOST %s", client->id, parv[1]);
-
-	userhost_changed(client);
-
-	if (MyConnect(client))
-	{
-		sendto_one(client, NULL, ":%s MODE %s :+xt", client->name, client->name);
-		sendnotice(client, 
-		    "Your nick!user@host-mask is now (%s!%s@%s) - To disable it type /mode %s -x",
-		     client->name, client->user->username, vhost,
-		    client->name);
-	}
-}
diff --git a/src/modules/setident.c b/src/modules/setident.c
@@ -1,125 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/setident.c
- *   (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org>
- *
- *   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_SETIDENT 	"SETIDENT"	/* set ident */
-
-CMD_FUNC(cmd_setident);
-
-ModuleHeader MOD_HEADER
-  = {
-	"setident",	/* Name of module */
-	"5.0", /* Version */
-	"/setident", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SETIDENT, cmd_setident, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* cmd_setident - 12/05/1999 - Stskeeps
- * :prefix SETIDENT newident
- * parv[1] - newident
- * D: This will set your username to be <x> (like (/setident Root))
- * (if you are IRCop) **efg*
- * Cloning of cmd_sethost at some points - so same authors ;P
-*/
-CMD_FUNC(cmd_setident)
-{
-	const char *vident, *s;
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		if (MyConnect(client))
-			sendnotice(client, "*** Syntax: /SETIDENT <new ident>");
-		return;
-	}
-
-	vident = parv[1];
-
-	switch (UHOST_ALLOWED)
-	{
-		case UHALLOW_ALWAYS:
-			break;
-		case UHALLOW_NEVER:
-			if (MyUser(client))
-			{
-				sendnotice(client, "*** /SETIDENT is disabled");
-				return;
-			}
-			break;
-		case UHALLOW_NOCHANS:
-			if (MyUser(client) && client->user->joined)
-			{
-				sendnotice(client, "*** /SETIDENT cannot be used while you are on a channel");
-				return;
-			}
-			break;
-		case UHALLOW_REJOIN:
-			/* dealt with later */
-			break;
-	}
-
-	if (strlen(vident) > USERLEN)
-	{
-		if (MyConnect(client))
-			sendnotice(client, "*** /SETIDENT Error: Usernames are limited to %i characters.", USERLEN);
-		return;
-	}
-
-	/* Check if the new ident contains illegal characters */
-	if (!valid_username(vident))
-	{
-		sendnotice(client, "*** /SETIDENT Error: A username may contain a-z, A-Z, 0-9, '-', '~' & '.'.");
-		return;
-	}
-
-	userhost_save_current(client);
-
-	strlcpy(client->user->username, vident, sizeof(client->user->username));
-
-	sendto_server(client, 0, 0, NULL, ":%s SETIDENT %s", client->id, parv[1]);
-
-	userhost_changed(client);
-
-	if (MyConnect(client))
-	{
-		sendnotice(client, "Your nick!user@host-mask is now (%s!%s@%s)",
-		                 client->name, client->user->username, GetHost(client));
-	}
-}
diff --git a/src/modules/setname.c b/src/modules/setname.c
@@ -1,162 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/setname.c
- *   (c) 1999-2001 Dominick Meglio (codemastr) <codemastr@unrealircd.com>
- *
- *   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_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
-  = {
-	"setname",	/* Name of module */
-	"5.0", /* Version */
-	"command /setname", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	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;
-}
-
-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))
-   this is now compatible with IRCv3 SETNAME --k4be
-*/ 
-CMD_FUNC(cmd_setname)
-{
-	int xx;
-	char oldinfo[REALLEN + 1];
-	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64];
-	ConfigItem_ban *bconf;
-	MessageTag *mtags = NULL;
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SETNAME");
-		return;
-	}
-
-	if (strlen(parv[1]) > REALLEN)
-	{
-		if (!MyConnect(client))
-			return;
-		if (HasCapabilityFast(client, CAP_SETNAME))
-		{
-			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 the new name before we check, but don't send to servers unless it is ok */
-		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 */
-			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;
-		}
-
-		/* Check for realname bans here too */
-		if (!ValidatePermissionsForPath("immune:server-ban:ban-realname",client,NULL,NULL,NULL) &&
-		    ((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME))))
-		{
-			banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
-			return;
-		}
-	} else {
-		/* remote user */
-		strlcpy(client->info, parv[1], sizeof(client->info));
-	}
-
-	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))
-	{
-		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_CHANGE, client, oldinfo);
-}
diff --git a/src/modules/silence.c b/src/modules/silence.c
@@ -1,241 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/silence.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_silence);
-
-ModuleHeader MOD_HEADER
-  = {
-	"silence",
-	"5.0",
-	"command /silence", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Structs */
-typedef struct Silence Silence;
-/** A /SILENCE entry */
-struct Silence
-{
-	Silence *prev, *next;
-	char mask[1]; /**< user!nick@host mask of silence entry */
-};
-
-/* Global variables */
-ModDataInfo *silence_md = NULL;
-
-/* Macros */
-#define SILENCELIST(x)       ((Silence *)moddata_local_client(x, silence_md).ptr)
-
-/* Forward declarations */
-int _is_silenced(Client *, Client *);
-int _del_silence(Client *client, const char *mask);
-int _add_silence(Client *client, const char *mask, int senderr);
-void silence_md_free(ModData *md);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAdd(modinfo->handle, EFUNC_ADD_SILENCE, _add_silence);
-	EfunctionAdd(modinfo->handle, EFUNC_DEL_SILENCE, _del_silence);
-	EfunctionAdd(modinfo->handle, EFUNC_IS_SILENCED, _is_silenced);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "silence";
-	mreq.type = MODDATATYPE_LOCAL_CLIENT;
-	mreq.free = silence_md_free;
-	silence_md = ModDataAdd(modinfo->handle, mreq);
-	if (!silence_md)
-	{
-		config_error("could not register silence moddata");
-		return MOD_FAILED;
-	}
-	CommandAdd(modinfo->handle, "SILENCE", cmd_silence, MAXPARA, CMD_USER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** The /SILENCE command - server-side ignore list.
- * Syntax:
- * SILENCE +user  To add a user from the silence list
- * SILENCE -user  To remove a user from the silence list
- * SILENCE        To send the current silence list
- *
- */
-
-CMD_FUNC(cmd_silence)
-{
-	Silence *s;
-	const char *p;
-	char action;
-
-	if (MyUser(client))
-	{
-		if (parc < 2 || BadPtr(parv[1]))
-		{
-			for (s = SILENCELIST(client); s; s = s->next)
-				sendnumeric(client, RPL_SILELIST, s->mask);
-			sendnumeric(client, RPL_ENDOFSILELIST);
-			return;
-		}
-		p = parv[1];
-		action = *p;
-		if (action == '-' || action == '+')
-		{
-			p++;
-		} else
-		if (!strchr(p, '@') && !strchr(p, '.') && !strchr(p, '!') && !strchr(p, '*') && !find_user(p, NULL))
-		{
-			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-			return;
-		} else
-		{
-			action = '+';
-		}
-		p = pretty_mask(p);
-		if ((action == '-' && del_silence(client, p)) ||
-		    (action != '-' && add_silence(client, p, 1)))
-		{
-			sendto_prefix_one(client, client, NULL, ":%s SILENCE %c%s",
-			    client->name, action, p);
-		}
-		return;
-	}
-
-	/* Probably server to server traffic.
-	 * We don't care about this anymore on UnrealIRCd 5 and later.
-	 */
-}
-
-/** Delete item from the silence list.
- * @param client The client.
- * @param mask The mask to delete from the list.
- * @returns 1 if entry was found and deleted, 0 if not found.
- */
-int _del_silence(Client *client, const char *mask)
-{
-	Silence *s;
-
-	for (s = SILENCELIST(client); s; s = s->next)
-	{
-		if (mycmp(mask, s->mask) == 0)
-		{
-			DelListItemUnchecked(s, moddata_local_client(client, silence_md).ptr);
-			safe_free(s);
-			return 1;
-		}
-	}
-	return 0;
-}
-
-/** Add item to the silence list.
- * @param client The client.
- * @param mask The mask to add to the list.
- * @returns 1 if silence entry added,
- *          0 if not added, eg: full or already covered by an existing silence entry.
- */
-int _add_silence(Client *client, const char *mask, int senderr)
-{
-	Silence *s;
-	int cnt = 0;
-
-	if (!MyUser(client))
-		return 0;
-
-	for (s = SILENCELIST(client); s; s = s->next)
-	{
-		if ((strlen(s->mask) > MAXSILELENGTH) || (++cnt >= SILENCE_LIMIT))
-		{
-			if (senderr)
-				sendnumeric(client, ERR_SILELISTFULL, mask);
-			return 0;
-		}
-		else
-		{
-			if (match_simple(s->mask, mask))
-				return 0;
-		}
-	}
-
-	/* Add the new entry */
-	s = safe_alloc(sizeof(Silence)+strlen(mask));
-	strcpy(s->mask, mask); /* safe, allocated above */
-	AddListItemUnchecked(s, moddata_local_client(client, silence_md).ptr);
-	return 1;
-}
-
-/** Check whether sender is silenced by receiver.
- * @param sender    The client that intends to send a message.
- * @param receiver  The client that would receive the message.
- * @returns 1 if sender is silenced by receiver (do NOT send the message),
- *          0 if not silenced (go ahead and send).
- */
-int _is_silenced(Client *sender, Client *receiver)
-{
-	Silence *s;
-	char mask[HOSTLEN + NICKLEN + USERLEN + 5];
-
-	if (!MyUser(receiver) || !receiver->user || !sender->user || !SILENCELIST(receiver))
-		return 0;
-
-	ircsnprintf(mask, sizeof(mask), "%s!%s@%s", sender->name, sender->user->username, GetHost(sender));
-
-	for (s = SILENCELIST(receiver); s; s = s->next)
-	{
-		if (match_simple(s->mask, mask))
-			return 1;
-	}
-
-	return 0;
-}
-
-/** Called on client exit: free the silence list of this user */
-void silence_md_free(ModData *md)
-{
-	Silence *b, *b_next;
-
-	for (b = md->ptr; b; b = b_next)
-	{
-		b_next = b->next;
-		safe_free(b);
-	}
-	md->ptr = NULL;
-}
diff --git a/src/modules/sinfo.c b/src/modules/sinfo.c
@@ -1,171 +0,0 @@
-/*
- * cmd_sinfo - Server information
- * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team.
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"sinfo",
-	"5.0",
-	"Server information",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-CMD_FUNC(cmd_sinfo);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	CommandAdd(modinfo->handle, "SINFO", cmd_sinfo, MAXPARA, CMD_USER|CMD_SERVER);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** SINFO server-to-server command.
- * Technical documentation is available at:
- * https://www.unrealircd.org/docs/Server_protocol:SINFO_command
- * ^ contains important remarks regarding when to send it and when not.
- */
-CMD_FUNC(sinfo_server)
-{
-	char buf[512];
-
-	if (MyConnect(client))
-	{
-		/* It is a protocol violation to send an SINFO for yourself,
-		 * eg if you are server 001, then you cannot send :001 SINFO ....
-		 * Exiting the client may seem harsh, but this way we force users
-		 * to use the correct protocol. If we would not do this then some
-		 * services coders may think they should use only SINFO while in
-		 * fact for directly connected servers they should use things like
-		 * PROTOCTL CHANMODES=... USERMODES=... NICKCHARS=.... etc, and
-		 * failure to do so will lead to potential desyncs or other major
-		 * issues.
-		 */
-		exit_client(client, NULL, "Protocol error: you cannot send SINFO about yourself");
-		return;
-	}
-
-	/* :SID SINFO up_since protocol umodes chanmodes nickchars :software name
-	 *               1        2        3      4        5        6 (last one)
-	 * If we extend it then 'software name' will still be the last one, so
-	 * it may become 7, 8 or 9. New elements are inserted right before it.
-	 */
-
-	if ((parc < 6) || BadPtr(parv[6]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SINFO");
-		return;
-	}
-
-	client->server->boottime = atol(parv[1]);
-	client->server->features.protocol = atoi(parv[2]);
-
-	if (!strcmp(parv[3], "*"))
-		safe_free(client->server->features.usermodes);
-	else
-		safe_strdup(client->server->features.usermodes, parv[3]);
-
-	if (!strcmp(parv[4], "*"))
-	{
-		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->server->features.nickchars);
-	else
-		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->server->features.software);
-	else
-		safe_strdup(client->server->features.software, parv[parc-1]);
-
-	if (is_services_but_not_ulined(client))
-	{
-		char buf[512];
-		snprintf(buf, sizeof(buf), "Services detected but no ulines { } for server name %s", client->name);
-		exit_client_ex(client, &me, NULL, buf);
-		return;
-	}
-
-	/* Broadcast to 'the other side' of the net */
-	concat_params(buf, sizeof(buf), parc, parv);
-	sendto_server(client, 0, 0, NULL, ":%s SINFO %s", client->id, buf);
-}
-
-#define SafeDisplayStr(x)  ((x && *(x)) ? (x) : "-")
-CMD_FUNC(sinfo_user)
-{
-	Client *acptr;
-
-	if (!IsOper(client))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	list_for_each_entry(acptr, &global_server_list, client_node)
-	{
-		sendtxtnumeric(client, "*** Server %s:", acptr->name);
-		sendtxtnumeric(client, "Protocol: %d",
-		               acptr->server->features.protocol);
-		sendtxtnumeric(client, "Software: %s",
-		               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->server->boottime));
-			sendtxtnumeric(client, "Uptime: %s",
-			               pretty_time_val(TStime() - acptr->server->boottime));
-		}
-		sendtxtnumeric(client, "User modes: %s",
-		               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->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->server->features.nickchars));
-	}
-}
-
-CMD_FUNC(cmd_sinfo)
-{
-	if (IsServer(client))
-		CALL_CMD_FUNC(sinfo_server);
-	else if (MyUser(client))
-		CALL_CMD_FUNC(sinfo_user);
-}
diff --git a/src/modules/sjoin.c b/src/modules/sjoin.c
@@ -1,821 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sjoin.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_sjoin);
-
-#define MSG_SJOIN 	"SJOIN"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"sjoin",
-	"5.1",
-	"command /sjoin", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-char modebuf[BUFSIZE], parabuf[BUFSIZE];
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SJOIN, cmd_sjoin, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-typedef struct xParv aParv;
-struct xParv {
-	int  parc;
-	const char *parv[256];
-};
-
-aParv pparv;
-
-aParv *mp2parv(char *xmbuf, char *parmbuf)
-{
-	int  c;
-	char *p, *s;
-
-	pparv.parv[0] = xmbuf;
-	c = 1;
-	
-	for (s = strtoken(&p, parmbuf, " "); s; s = strtoken(&p, NULL, " "))
-	{
-		pparv.parv[c] = s;
-		c++; /* in my dreams */
-	}
-	pparv.parv[c] = NULL;
-	pparv.parc = c;
-	return (&pparv);
-}
-
-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->name, modebuf, parabuf);
-	sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
-	               ":%s MODE %s %s %s", client->name, channel->name, modebuf, parabuf);
-	if (MyConnect(client))
-		RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, -1, &destroy_channel);
-	else
-		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
- *
- *  parv[1] = channel timestamp
- *  parv[2] = channel name
- *
- *  if parc == 3:
- *  parv[3] = nick names + modes - all in one parameter
- *
- *  if parc == 4:
- *  parv[3] = channel modes
- *  parv[4] = nick names + modes - all in one parameter
- *
- *  if parc > 4:
- *  parv[3] = channel modes
- *  parv[4 to parc - 2] = mode parameters
- *  parv[parc - 1] = nick names + modes
- */
-
-/* Note: with regards to message tags we use new_message_special()
- *       here extensively. This because one SJOIN command can (often)
- *       generate multiple events that are sent to clients,
- *       for example 1 SJOIN can cause multiple joins, +beI, etc.
- *       -- Syzop
- */
-
-/* Some ugly macros, but useful */
-#define Addit(mode,param) if ((strlen(parabuf) + strlen(param) + 11 < MODEBUFLEN) && (b <= MAXMODEPARAMS)) { \
-	if (*parabuf) \
-		strcat(parabuf, " ");\
-	strcat(parabuf, param);\
-	modebuf[b++] = mode;\
-	modebuf[b] = 0;\
-}\
-else {\
-	send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf); \
-	strcpy(parabuf,param);\
-	/* modebuf[0] should stay what it was ('+' or '-') */ \
-	modebuf[1] = mode;\
-	modebuf[2] = '\0';\
-	b = 2;\
-}
-#define Addsingle(x) do { modebuf[b] = x; b++; modebuf[b] = '\0'; } while(0)
-#define CheckStatus(x,y) do { if (modeflags & (y)) { Addit((x), acptr->name); } } while(0)
-
-CMD_FUNC(cmd_sjoin)
-{
-	unsigned short nopara;
-	unsigned short nomode; /**< An SJOIN without MODE? */
-	unsigned short removeours; /**< Remove our modes */
-	unsigned short removetheirs; /**< Remove their modes (or actually: do not ADD their modes, the MODE -... line will be sent later by the other side) */
-	unsigned short merge;	/**< same timestamp: merge their & our modes */
-	char pvar[MAXMODEPARAMS][MODEBUFLEN + 3];
-	char cbuf[1024];
-	char scratch_buf[1024]; /**< scratch buffer */
-	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 :") */
-	char *s = NULL;
-	Channel *channel; /**< Channel */
-	aParv *ap;
-	int pcount, i;
-	Hook *h;
-	Cmode *cm;
-	time_t ts, oldts;
-	unsigned short b=0;
-	char *tp, *p, *saved = NULL;
-	
-	if (!IsServer(client) || parc < 4)
-		return;
-
-	if (!IsChannelName(parv[2]))
-		return;
-
-	merge = nopara = nomode = removeours = removetheirs = 0;
-
-	if (parc < 6)
-		nopara = 1;
-
-	if (parc < 5)
-		nomode = 1;
-
-	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;
-		channel->creationtime = ts;
-	}
-	else if (channel->creationtime < ts)
-	{
-		removetheirs = 1;
-	}
-	else if (channel->creationtime == ts)
-	{
-		merge = 1;
-	}
-
-	parabuf[0] = '\0';
-	modebuf[0] = '+';
-	modebuf[1] = '\0';
-
-	/* Grab current modes -> modebuf & parabuf */
-	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;
-
-		modebuf[0] = '-';
-
-		/* remove our modes if any */
-		if (!empty_mode(modebuf))
-		{
-			MessageTag *mtags = NULL;
-			MultiLineMode *mlm;
-			ap = mp2parv(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 */
-		modebuf[0] = '-';
-		modebuf[1] = '\0';
-		parabuf[0] = '\0';
-		b = 1;
-		while(channel->banlist)
-		{
-			Ban *ban = channel->banlist;
-			Addit('b', ban->banstr);
-			channel->banlist = ban->next;
-			safe_free(ban->banstr);
-			safe_free(ban->who);
-			free_ban(ban);
-		}
-		while(channel->exlist)
-		{
-			Ban *ban = channel->exlist;
-			Addit('e', ban->banstr);
-			channel->exlist = ban->next;
-			safe_free(ban->banstr);
-			safe_free(ban->who);
-			free_ban(ban);
-		}
-		while(channel->invexlist)
-		{
-			Ban *ban = channel->invexlist;
-			Addit('I', ban->banstr);
-			channel->invexlist = ban->next;
-			safe_free(ban->banstr);
-			safe_free(ban->who);
-			free_ban(ban);
-		}
-		for (lp = channel->members; lp; lp = lp->next)
-		{
-			Membership *lp2 = find_membership_link(lp->client->user->channel, channel);
-
-			/* Remove all our modes, one by one */
-			for (p = lp->member_modes; *p; p++)
-			{
-				Addit(*p, lp->client->name);
-			}
-			/* And clear all the flags in memory */
-			*lp->member_modes = *lp2->member_modes = '\0';
-		}
-		if (b > 1)
-		{
-			modebuf[b] = '\0';
-			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
-		}
-
-		/* since we're dropping our modes, we want to clear the mlock as well. --nenolod */
-		set_channel_mlock(client, channel, NULL, FALSE);
-	}
-	/* Mode setting done :), now for our beloved clients */
-	parabuf[0] = 0;
-	modebuf[0] = '+';
-	modebuf[1] = '\0';
-	b = 1;
-	strlcpy(cbuf, parv[parc-1], sizeof cbuf);
-
-	sj3_parabuf[0] = '\0';
-	for (i = 2; i <= (parc - 2); i++)
-	{
-		strlcat(sj3_parabuf, parv[i], sizeof sj3_parabuf);
-		if (((i + 1) <= (parc - 2)))
-			strlcat(sj3_parabuf, " ", sizeof sj3_parabuf);
-	}
-
-	/* Now process adding of users & adding of list modes (bans/exempt/invex) */
-
-	snprintf(uid_buf, sizeof uid_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, sj3_parabuf);
-
-	for (s = strtoken(&saved, cbuf, " "); s; s = strtoken(&saved, NULL, " "))
-	{
-		char *setby = client->name; /**< Set by (nick, nick!user@host, or server name) */
-		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 */
-
-		*item_modes = 0;
-		i = 0;
-		tp = s;
-
-		/* UnrealIRCd 4.2.2 and later support "SJSBY" which allows communicating
-		 * setat/setby information for bans, ban exempts and invite exceptions.
-		 */
-		if (SupportSJSBY(client->direction) && (*tp == '<'))
-		{
-			/* Special prefix to communicate timestamp and setter:
-			 * "<" + timestamp + "," + nick[!user@host] + ">" + normal SJOIN stuff
-			 * For example: "<12345,nick>&some!nice@ban"
-			 */
-			char *end = strchr(tp, '>'), *p;
-			if (!end)
-			{
-				/* this obviously should never happen */
-				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';
-
-			p = strchr(tp, ',');
-			if (!p)
-			{
-				/* missing setby parameter */
-				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';
-
-			setat = atol(tp+1);
-			setby = p;
-			sjsby_info = 1;
-
-			tp = end; /* the remainder is used for the actual ban/exempt/invex */
-		}
-
-		/* Process the SJOIN prefixes... */
-		for (p = tp; *p; p++)
-		{
-			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))
-			{
-				p++;
-				break;
-			}
-		}
-
-		/* Now set 'prefix' to the prefixes we encountered.
-		 * This is basically the range tp..p
-		 */
-		strlncpy(prefix, tp, sizeof(prefix), p - tp);
-
-		/* Now copy the "nick" (which can actually be a ban/invex/exempt) */
-		strlcpy(item, p, sizeof(item));
-		if (*item == '\0')
-			continue;
-
-		/* 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;
-
-			/* 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_user(item, NULL)))
-				continue;
-
-			if (acptr->direction != client->direction)
-			{
-				if (IsMember(acptr, channel))
-				{
-					/* Nick collision, don't kick or it desyncs -Griever*/
-					continue;
-				}
-			
-				sendto_one(client, NULL,
-				    ":%s KICK %s %s :Fake direction",
-				    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)
-				*item_modes = '\0';
-
-			if (!IsMember(acptr, channel))
-			{
-				/* User joining the channel, send JOIN to local users.
-				 */
-				MessageTag *mtags = NULL;
-
-				add_user_to_channel(channel, acptr, item_modes);
-				unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", acptr,
-					   "User $client joined $channel",
-					   log_data_channel("channel", channel),
-					   log_data_string("modes", item_modes));
-				RunHook(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags);
-				new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->name);
-				send_join_to_local_users(acptr, channel, mtags);
-				free_message_tags(mtags);
-			}
-
-			/* 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, channel->name);
-				/* Double-check the new buffer is sufficient to concat the data */
-				if (strlen(uid_buf) + strlen(prefix) + strlen(acptr->id) > BUFSIZE - 5)
-				{
-					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;
-				}
-			}
-			sprintf(uid_buf+strlen(uid_buf), "%s%s ", prefix, acptr->id);
-
-			if (strlen(uid_sjsby_buf) + strlen(prefix) + IDLEN > 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, 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)
-				{
-					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;
-				}
-			}
-			sprintf(uid_sjsby_buf+strlen(uid_sjsby_buf), "%s%s ", prefix, acptr->id);
-		}
-		else
-		{
-			/* It's a list mode................ */
-			const char *str;
-			
-			if (removetheirs)
-				continue;
-
-			/* 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 (*item_modes == 'b')
-			{
-				if (add_listmode_ex(&channel->banlist, client, channel, item, setby, setat) != -1)
-				{
-					Addit('b', item);
-				}
-			}
-			if (*item_modes == 'e')
-			{
-				if (add_listmode_ex(&channel->exlist, client, channel, item, setby, setat) != -1)
-				{
-					Addit('e', item);
-				}
-			}
-			if (*item_modes == 'I')
-			{
-				if (add_listmode_ex(&channel->invexlist, client, channel, item, setby, setat) != -1)
-				{
-					Addit('I', item);
-				}
-			}
-
-			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, channel->name);
-				/* Double-check the new buffer is sufficient to concat the data */
-				if (strlen(uid_buf) + strlen(prefix) + strlen(item) > BUFSIZE - 5)
-				{
-					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, item);
-
-			*scratch_buf = '\0';
-			if (sjsby_info)
-				add_sjsby(scratch_buf, setby, setat);
-			strcat(scratch_buf, prefix);
-			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, channel->name);
-				/* Double-check the new buffer is sufficient to concat the data */
-				if (strlen(uid_sjsby_buf) + strlen(scratch_buf) > BUFSIZE - 5)
-				{
-					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;
-				}
-			}
-			strcpy(uid_sjsby_buf+strlen(uid_sjsby_buf), scratch_buf); /* size already checked above */
-		}
-		continue;
-	}
-
-	/* Send out any possible remainder.. */
-	sendto_server(client, 0, PROTO_SJSBY, recv_mtags, "%s", uid_buf);
-	sendto_server(client, PROTO_SJSBY, 0, recv_mtags, "%s", uid_sjsby_buf);
-
-	if (!empty_mode(modebuf))
-	{
-		modebuf[b] = '\0';
-		send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
-	}
-	
-	if (!merge && !removetheirs && !nomode)
-	{
-		MessageTag *mtags = NULL;
-		MultiLineMode *mlm;
-
-		strlcpy(modebuf, parv[3], sizeof modebuf);
-		parabuf[0] = '\0';
-		if (!nopara)
-		{
-			for (b = 4; b <= (parc - 2); b++)
-			{
-				strlcat(parabuf, parv[b], sizeof parabuf);
-				strlcat(parabuf, " ", sizeof parabuf);
-			}
-		}
-		ap = mp2parv(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.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);
-		parabuf[0] = '\0';
-		if (!nopara)
-		{
-			for (b = 4; b <= (parc - 2); b++)
-			{
-				strlcat(parabuf, parv[b], sizeof parabuf);
-				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);
-		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.
-		 */
-		strlcpy(modebuf, "-", sizeof modebuf);
-		parabuf[0] = '\0';
-		b = 1;
-
-		/* 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'))
-		{
-			/* 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 had something that is now gone
-		 * note that: oldmode.* = us, channel->mode.* = merged.
-		 */
-		for (cm=channelmodes; cm; cm = cm->next)
-		{
-			if (cm->letter &&
-			    !cm->local &&
-			    (oldmode.mode & cm->mode) &&
-			    !(channel->mode.mode & cm->mode))
-			{
-				if (cm->paracount)
-				{
-					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(cm->letter);
-				}
-			}
-		}
-
-		if (b > 1)
-		{
-			Addsingle('+');
-		}
-		else
-		{
-			strlcpy(modebuf, "+", sizeof modebuf);
-			b = 1;
-		}
-
-		/* 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 (cm=channelmodes; cm; cm = cm->next)
-		{
-			if ((cm->letter) &&
-			    !(oldmode.mode & cm->mode) &&
-			    (channel->mode.mode & cm->mode))
-			{
-				if (cm->paracount)
-				{
-					const char *parax = cm_getparameter(channel, cm->letter);
-					if (parax)
-					{
-						Addit(cm->letter, parax);
-					}
-				} else {
-					Addsingle(cm->letter);
-				}
-			}
-		}
-
-		/* now, if we had diffent para modes - this loop really could be done better, but */
-
-		/* Now, check for any param differences in extended channel modes..
-		 * note that: oldmode.* = us before, channel->mode.* = merged.
-		 * if we win: copy oldmode to channel mode, if they win: send the mode
-		 */
-		for (cm=channelmodes; cm; cm = cm->next)
-		{
-			if (cm->letter && cm->paracount &&
-			    (oldmode.mode & cm->mode) &&
-			    (channel->mode.mode & cm->mode))
-			{
-				int r;
-				const char *parax;
-				char flag = cm->letter;
-				void *ourm = GETPARASTRUCTEX(oldmode.mode_params, flag);
-				void *theirm = GETPARASTRUCT(channel, flag);
-				
-				r = cm->sjoin_check(channel, ourm, theirm);
-				switch (r)
-				{
-					case EXSJ_WEWON:
-						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);
-						Addit(cm->letter, parax);
-						break;
-
-					case EXSJ_SAME:
-						break;
-
-					case EXSJ_MERGE:
-						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:
-						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;
-				}
-			}
-		}
-
-		Addsingle('\0');
-
-		if (!empty_mode(modebuf))
-			send_local_chan_mode(recv_mtags, client, channel, modebuf, parabuf);
-
-		/* free the oldmode.* crap :( */
-		extcmode_free_paramlist(oldmode.mode_params);
-	}
-
-	for (h = Hooks[HOOKTYPE_CHANNEL_SYNCED]; h; h = h->next)
-	{
-		int i = (*(h->func.intfunc))(channel,merge,removetheirs,nomode);
-		if (i == 1)
-			return; /* channel no longer exists */
-	}
-
-	/* we should be synced by now, */
-	if ((oldts != -1) && (oldts != channel->creationtime))
-	{
-		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
-	 * the channel actually has no users in it at this point,
-	 * then destroy the channel.
-	 */
-	if (!channel->users)
-	{
-		sub1_from_channel(channel);
-		return;
-	}
-}
diff --git a/src/modules/slog.c b/src/modules/slog.c
@@ -1,190 +0,0 @@
-/*
- *   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, j, 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/sqline.c b/src/modules/sqline.c
@@ -1,87 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/sqline.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_sqline);
-
-/* Place includes here */
-#define MSG_SQLINE      "SQLINE"        /* SQLINE */
-
-
-
-ModuleHeader MOD_HEADER
-  = {
-	"sqline",	/* Name of module */
-	"5.0", /* Version */
-	"command /sqline", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SQLINE, cmd_sqline, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* cmd_sqline
- *	parv[1] = nickmask
- *	parv[2] = reason
- */
-CMD_FUNC(cmd_sqline)
-{
-	char mo[32];
-	const char *comment = (parc == 3) ? parv[2] : NULL;
-	const char *tkllayer[9] = {
-		me.name,        /*0  server.name */
-		"+",            /*1  +|- */
-		"Q",            /*2  G   */
-		"*" ,           /*3  user */
-		parv[1],        /*4  host */
-		client->name,     /*5  setby */
-		"0",            /*6  expire_at */
-		NULL,           /*7  set_at */
-		"no reason"     /*8  reason */
-	};
-
-	if (parc < 2)
-		return;
-
-	ircsnprintf(mo, sizeof(mo), "%lld", (long long)TStime());
-	tkllayer[7] = mo;
-	tkllayer[8] = comment ? comment : "no reason";
-	cmd_tkl(&me, NULL, 9, tkllayer);
-}
diff --git a/src/modules/squit.c b/src/modules/squit.c
@@ -1,153 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/squit.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_squit);
-
-#define MSG_SQUIT 	"SQUIT"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"squit",
-	"5.0",
-	"command /squit", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SQUIT, cmd_squit, 2, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_squit
-**	parv[1] = server name
-**	parv[parc-1] = comment
-*/
-CMD_FUNC(cmd_squit)
-{
-	const char *server;
-	Client *target;
-	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?
-
-	if (!ValidatePermissionsForPath("route:local",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SQUIT");
-		return;
-	}
-
-	server = parv[1];
-
-	target = find_server_quick(server);
-	if (target && IsMe(target))
-	{
-		target = client->direction;
-		server = client->direction->local->sockhost;
-	}
-
-	/*
-	   ** SQUIT semantics is tricky, be careful...
-	   **
-	   ** The old (irc2.2PL1 and earlier) code just cleans away the
-	   ** server client from the links (because it is never true
-	   ** "client->direction == target".
-	   **
-	   ** This logic here works the same way until "SQUIT host" hits
-	   ** the server having the target "host" as local link. Then it
-	   ** will do a real cleanup spewing SQUIT's and QUIT's to all
-	   ** directions, also to the link from which the orinal SQUIT
-	   ** came, generating one unnecessary "SQUIT host" back to that
-	   ** link.
-	   **
-	   ** One may think that this could be implemented like
-	   ** "hunt_server" (e.g. just pass on "SQUIT" without doing
-	   ** nothing until the server having the link as local is
-	   ** reached). Unfortunately this wouldn't work in the real life,
-	   ** because either target may be unreachable or may not comply
-	   ** with the request. In either case it would leave target in
-	   ** links--no command to clear it away. So, it's better just
-	   ** clean out while going forward, just to be sure.
-	   **
-	   ** ...of course, even better cleanout would be to QUIT/SQUIT
-	   ** dependant users/servers already on the way out, but
-	   ** currently there is not enough information about remote
-	   ** clients to do this...   --msa
-	 */
-	if (!target)
-	{
-		sendnumeric(client, ERR_NOSUCHSERVER, server);
-		return;
-	}
-	if (MyUser(client) && ((!ValidatePermissionsForPath("route:global",client,NULL,NULL,NULL) && !MyConnect(target)) ||
-	    (!ValidatePermissionsForPath("route:local",client,NULL,NULL,NULL) && MyConnect(target))))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	/*
-	   **  Notify all opers, if my local link is remotely squitted
-	 */
-	if (MyConnect(target) && !MyUser(client))
-	{
-		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))
-	{
-		if (target->user)
-		{
-			sendnotice(client, "ERROR: You're connected to %s, we cannot SQUIT ourselves",
-			           me.name);
-			return;
-		}
-		unreal_log(ULOG_INFO, "link", "SQUIT", client,
-		           "SQUIT: Forced server disconnect of $target by $client ($reason)",
-		           log_data_client("target", target),
-		           log_data_string("reason", comment));
-	}
-
-	exit_client_ex(target, client->direction, recv_mtags, comment);
-}
diff --git a/src/modules/sreply.c b/src/modules/sreply.c
@@ -1,87 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/sreply.c
- *   (C) 2022 Valware and the UnrealIRCd Team
- * 
- *   Allows services to send Standard Replies in response to non-privmsg commands:
- *   https://ircv3.net/specs/extensions/standard-replies
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-CMD_FUNC(cmd_sreply);
-
-ModuleHeader MOD_HEADER
-  = {
-	"sreply",	/* Name of module */
-	"1.0", /* Version */
-	"Server command SREPLY", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "SREPLY", cmd_sreply, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-/**
- * cmd_sreply
- * @param parv[1]		Nick|UID
- * @param parv[2]		"F", "W" or "N" for FAIL, WARN and NOTE.
- * @param parv[3]		The rest of the message
-*/
-CMD_FUNC(cmd_sreply)
-{
-	Client *target;
-
-	if (parc < 4)
-		return;
-
-	target = find_user(parv[1], NULL);
-	if (!target && !(target = find_server_by_uid(parv[1])))
-		return;
-
-	if (!MyUser(target))
-	{
-		/* Target is a remote user/server */
-		sendto_one(target, recv_mtags, ":%s SREPLY %s %s :%s", client->name, parv[1], parv[2], parv[3]);
-		return;
-	}
-
-	/* For a locally connected user... */
-	if (!strcmp(parv[2],"F"))
-		sendto_one(target, recv_mtags, ":%s FAIL %s", client->name, parv[3]);
-	else if (!strcmp(parv[2],"W"))
-		sendto_one(target, recv_mtags, ":%s WARN %s", client->name, parv[3]);
-	else if (!strcmp(parv[2],"N"))
-		sendto_one(target, recv_mtags, ":%s NOTE %s", client->name, parv[3]);
-}
diff --git a/src/modules/staff.c b/src/modules/staff.c
@@ -1,178 +0,0 @@
-/*
- *   cmd_staff: Displays a file(/URL) when the /STAFF command is used.
- *   (C) Copyright 2004-2016 Syzop <syzop@vulnscan.org>
- *   (C) Copyright 2003-2004 AngryWolf <angrywolf@flashmail.com>
- *
- *   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
-  = {
-	"staff",
-	"3.8",
-	"/STAFF command",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-#define MSG_STAFF	"STAFF"
-
-#define DEF_STAFF_FILE   CONFDIR "/network.staff"
-#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 -"
-#define RPL_ENDOFSTAFF   ":%s 702 %s :End of /STAFF command."
-#define RPL_NOSTAFF      ":%s 703 %s :Network Staff File is missing"
-
-/* Forward declarations */
-static void unload_motd_file(MOTDFile *list);
-CMD_FUNC(cmd_staff);
-static int cb_test(ConfigFile *, ConfigEntry *, int, int *);
-static int cb_conf(ConfigFile *, ConfigEntry *, int);
-static int cb_stats(Client *client, const char *flag);
-static void FreeConf();
-
-static MOTDFile staff;
-static char *staff_file = NULL;
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, cb_test);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	memset(&staff, 0, sizeof(staff));
-
-	CommandAdd(modinfo->handle, MSG_STAFF, cmd_staff, MAXPARA, CMD_USER);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, cb_conf);
-	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, cb_stats);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	FreeConf();
-	unload_motd_file(&staff);
-
-	return MOD_SUCCESS;
-}
-
-static void FreeConf()
-{
-	safe_free(staff_file);
-}
-
-static void unload_motd_file(MOTDFile *list)
-{
-	MOTDLine *old, *new;
-
-	if (!list)
-		return;
-
-	new = list->lines;
-
-	if (!new)
-		return;
-
-	while (new)
-	{
-		old = new->next;
-		safe_free(new->line);
-		safe_free(new);
-		new = old;
-	}
-}
-
-static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-
-	if (type == CONFIG_SET)
-	{
-		if (!strcmp(ce->name, "staff-file"))
-		{
-			*errs = errors;
-			return errors ? -1 : 1;
-		}
-	}
-
-	return 0;
-}
-
-static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	if (type == CONFIG_SET)
-	{
-		if (!strcmp(ce->name, "staff-file"))
-		{
-			convert_to_absolute_path(&ce->value, CONFDIR);
-			read_motd(ce->value, &staff);
-			return 1;
-		}
-	}
-
-	return 0;
-}
-
-static int cb_stats(Client *client, const char *flag)
-{
-	if (*flag == 'S')
-	{
-		sendtxtnumeric(client, "staff-file: %s", STAFF_FILE);
-		return 1;
-	}
-
-	return 0;
-}
-
-/** The routine that actual does the /STAFF command */
-CMD_FUNC(cmd_staff)
-{
-	MOTDFile *temp;
-	MOTDLine *aLine;
-
-	if (!IsUser(client))
-		return;
-
-	if (hunt_server(client, recv_mtags, "STAFF", 1, parc, parv) != HUNTED_ISME)
-		return;
-
-	if (!staff.lines)
-	{
-		sendto_one(client, NULL, RPL_NOSTAFF, me.name, client->name);
-		return;
-	}
-
-	sendto_one(client, NULL, RPL_STAFFSTART, me.name, client->name, NETWORK_NAME);
-
-	temp = &staff;
-
-	for (aLine = temp->lines; aLine; aLine = aLine->next)
-		sendto_one(client, NULL, RPL_STAFF, me.name, client->name, aLine->line);
-
-	sendto_one(client, NULL, RPL_ENDOFSTAFF, me.name, client->name);
-}
diff --git a/src/modules/standard-replies.c b/src/modules/standard-replies.c
@@ -1,58 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/standard-replies.c
- *   (C) 2023 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"standard-replies",
-	"6.0",
-	"standard-replies CAP", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Variables */
-long CAP_STANDARD_REPLIES = 0L;
-
-MOD_INIT()
-{
-	ClientCapabilityInfo cap;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "standard-replies";
-	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_STANDARD_REPLIES);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
diff --git a/src/modules/starttls.c b/src/modules/starttls.c
@@ -1,121 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/starttls.c
- *   (C) 2009 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-CMD_FUNC(cmd_starttls);
-
-#define MSG_STARTTLS 	"STARTTLS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"starttls",
-	"5.0",
-	"command /starttls", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-long CLICAP_STARTTLS;
-
-MOD_INIT()
-{
-	ClientCapabilityInfo cap;
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	CommandAdd(modinfo->handle, MSG_STARTTLS, cmd_starttls, MAXPARA, CMD_UNREGISTERED);
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "tls";
-	ClientCapabilityAdd(modinfo->handle, &cap, &CLICAP_STARTTLS);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-CMD_FUNC(cmd_starttls)
-{
-	SSL_CTX *ctx;
-	int tls_options;
-
-	if (!MyConnect(client) || !IsUnknown(client))
-		return;
-
-	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;
-
-	/* This should never happen? */
-	if (!ctx)
-	{
-		/* Pretend STARTTLS is an unknown command, this is the safest approach */
-		sendnumeric(client, ERR_NOTREGISTERED);
-		return;
-	}
-
-	/* Is STARTTLS disabled? (same response as above) */
-	if (tls_options & TLSFLAG_NOSTARTTLS)
-	{
-		sendnumeric(client, ERR_NOTREGISTERED);
-		return;
-	}
-
-	if (IsSecure(client))
-	{
-		sendnumeric(client, ERR_STARTTLS, "STARTTLS failed. Already using TLS.");
-		return;
-	}
-
-	dbuf_delete(&client->local->recvQ, DBufLength(&client->local->recvQ)); /* Clear up any remaining plaintext commands */
-	sendnumeric(client, RPL_STARTTLS);
-	send_queued(client);
-
-	SetStartTLSHandshake(client);
-	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 (!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);
-		goto fail;
-	}
-
-	/* HANDSHAKE IN PROGRESS */
-	return;
-fail:
-	/* Failure */
-	sendnumeric(client, ERR_STARTTLS, "STARTTLS failed");
-	client->local->ssl = NULL;
-	ClearTLS(client);
-	SetUnknown(client);
-}
diff --git a/src/modules/stats.c b/src/modules/stats.c
@@ -1,1057 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/stats.c
- *   (C) 2004-present 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_stats);
-
-#define MSG_STATS 	"STATS"
-
-ModuleHeader MOD_HEADER
-  = {
-	"stats",
-	"5.0",
-	"command /stats",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_STATS, cmd_stats, 3, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-extern MODVAR int  max_connection_count;
-
-int stats_banversion(Client *, const char *);
-int stats_links(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_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
-
-struct statstab {
-	char flag;
-	char *longflag;
-	int (*func)(Client *client, const char *para);
-	int options;
-};
-
-/* Must be listed lexicographically */
-/* Long flags must be lowercase */
-struct statstab StatsTable[] = {
-	{ 'B', "banversion",	stats_banversion,	0		},
-	{ 'C', "link", 		stats_links,		0 		},
-	{ 'G', "gline",		stats_gline,		FLAGS_AS_PARA	},
-	{ 'H', "link",	 	stats_links,		0 		},
-	{ 'I', "allow",		stats_allow,		0 		},
-	{ 'K', "kline",		stats_kline,		0 		},
-	{ 'L', "linkinfoall",	stats_linkinfoall,	SERVER_AS_PARA	},
-	{ 'M', "command",	stats_command,		0 		},
-	{ 'O', "oper",		stats_oper,		0 		},
-	{ 'P', "port",		stats_port,		0 		},
-	{ 'Q', "sqline",	stats_sqline,		FLAGS_AS_PARA 	},
-	{ 'S', "set",		stats_set,		0		},
-	{ 'T', "traffic",	stats_traffic,		0 		},
-	{ 'U', "uline",		stats_uline,		0 		},
-	{ 'V', "vhost", 	stats_vhost,		0 		},
-	{ 'W', "fdtable",       stats_fdtable,          0               },
-	{ 'X', "notlink",	stats_notlink,		0 		},
-	{ 'Y', "class",		stats_class,		0 		},
-	{ 'c', "link", 		stats_links,		0 		},
-	{ 'e', "except",	stats_except,		0 		},
-	{ 'f', "spamfilter",	stats_spamfilter,	FLAGS_AS_PARA	},
-	{ 'g', "gline",		stats_gline,		FLAGS_AS_PARA	},
-	{ 'h', "link", 		stats_links,		0 		},
-	{ 'j', "officialchans", stats_officialchannels, 0 		},
-	{ 'k', "kline",		stats_kline,		0 		},
-	{ 'l', "linkinfo",	stats_linkinfo,		SERVER_AS_PARA 	},
-	{ 'm', "command",	stats_command,		0 		},
-	{ 'n', "banrealname",	stats_banrealname,	0 		},
-	{ 'o', "oper",		stats_oper,		0 		},
-	{ 'q', "bannick",	stats_bannick,		FLAGS_AS_PARA	},
-	{ 'r', "chanrestrict",	stats_chanrestrict,	0 		},
-	{ 's', "shun",		stats_shun,		FLAGS_AS_PARA	},
-	{ 't', "tld",		stats_tld,		0 		},
-	{ 'u', "uptime",	stats_uptime,		0 		},
-	{ 'v', "denyver",	stats_denyver,		0 		},
-	{ 'x', "notlink",	stats_notlink,		0 		},
-	{ 'y', "class",		stats_class,		0 		},
-	{ 0, 	NULL, 		NULL, 			0		}
-};
-
-int stats_compare(const char *s1, const char *s2)
-{
-	/* The long stats flags are always lowercase */
-	while (*s1 == tolower(*s2))
-	{
-		if (*s1 == 0)
-			return 0;
-		s1++;
-		s2++;
-	}
-	return 1;
-}
-
-static inline struct statstab *stats_binary_search(char c) {
-	int start = 0;
-	int stop = sizeof(StatsTable)/sizeof(StatsTable[0])-1;
-	int mid;
-	while (start <= stop) {
-		mid = (start+stop)/2;
-		if (c < StatsTable[mid].flag)
-			stop = mid-1;
-		else if (StatsTable[mid].flag == c)
-			return &StatsTable[mid];
-		else
-			start = mid+1;
-	}
-	return NULL;
-}
-
-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))
-			return &StatsTable[i];
-	return NULL;
-}
-
-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);
-	return buf;
-}
-
-static inline void stats_help(Client *client)
-{
-	sendnumeric(client, RPL_STATSHELP, "/Stats flags:");
-	sendnumeric(client, RPL_STATSHELP, "B - banversion - Send the ban version list");
-	sendnumeric(client, RPL_STATSHELP, "b - badword - Send the badwords list");
-	sendnumeric(client, RPL_STATSHELP, "C - link - Send the link block list");
-	sendnumeric(client, RPL_STATSHELP, "d - denylinkauto - Send the deny link (auto) block list");
-	sendnumeric(client, RPL_STATSHELP, "D - denylinkall - Send the deny link (all) block list");
-	sendnumeric(client, RPL_STATSHELP, "e - except - Send the ban exception list (ELINEs and in config))");
-	sendnumeric(client, RPL_STATSHELP, "f - spamfilter - Send the spamfilter list");
-	sendnumeric(client, RPL_STATSHELP, "F - denydcc - Send the deny dcc and allow dcc block lists");
-	sendnumeric(client, RPL_STATSHELP, "G - gline - Send the gline and gzline list");
-	sendnumeric(client, RPL_STATSHELP, "  Extended flags: [+/-mrs] [mask] [reason] [setby]");
-	sendnumeric(client, RPL_STATSHELP, "   m Return glines matching/not matching the specified mask");
-	sendnumeric(client, RPL_STATSHELP, "   r Return glines with a reason matching/not matching the specified reason");
-	sendnumeric(client, RPL_STATSHELP, "   s Return glines set by/not set by clients matching the specified name");
-	sendnumeric(client, RPL_STATSHELP, "I - allow - Send the allow block list");
-	sendnumeric(client, RPL_STATSHELP, "j - officialchans - Send the offical channels list");
-	sendnumeric(client, RPL_STATSHELP, "K - kline - Send the ban user/ban ip/except ban block list");
-	sendnumeric(client, RPL_STATSHELP, "l - linkinfo - Send link information");
-	sendnumeric(client, RPL_STATSHELP, "L - linkinfoall - Send all link information");
-	sendnumeric(client, RPL_STATSHELP, "M - command - Send list of how many times each command was used");
-	sendnumeric(client, RPL_STATSHELP, "n - banrealname - Send the ban realname block list");
-	sendnumeric(client, RPL_STATSHELP, "O - oper - Send the oper block list");
-	sendnumeric(client, RPL_STATSHELP, "P - port - Send information about ports");
-	sendnumeric(client, RPL_STATSHELP, "q - bannick - Send the ban nick block list");
-	sendnumeric(client, RPL_STATSHELP, "Q - sqline - Send the global qline list");
-	sendnumeric(client, RPL_STATSHELP, "r - chanrestrict - Send the channel deny/allow block list");
-	sendnumeric(client, RPL_STATSHELP, "S - set - Send the set block list");
-	sendnumeric(client, RPL_STATSHELP, "s - shun - Send the shun list");
-	sendnumeric(client, RPL_STATSHELP, "  Extended flags: [+/-mrs] [mask] [reason] [setby]");
-	sendnumeric(client, RPL_STATSHELP, "   m Return shuns matching/not matching the specified mask");
-	sendnumeric(client, RPL_STATSHELP, "   r Return shuns with a reason matching/not matching the specified reason");
-	sendnumeric(client, RPL_STATSHELP, "   s Return shuns set by/not set by clients matching the specified name");
-	sendnumeric(client, RPL_STATSHELP, "t - tld - Send the tld block list");
-	sendnumeric(client, RPL_STATSHELP, "T - traffic - Send traffic information");
-	sendnumeric(client, RPL_STATSHELP, "u - uptime - Send the server uptime and connection count");
-	sendnumeric(client, RPL_STATSHELP, "U - uline - Send the ulines block list");
-	sendnumeric(client, RPL_STATSHELP, "v - denyver - Send the deny version block list");
-	sendnumeric(client, RPL_STATSHELP, "V - vhost - Send the vhost block list");
-	sendnumeric(client, RPL_STATSHELP, "W - fdtable - Send the FD table listing");
-	sendnumeric(client, RPL_STATSHELP, "X - notlink - Send the list of servers that are not current linked");
-	sendnumeric(client, RPL_STATSHELP, "Y - class - Send the class block list");
-}
-
-static inline int allow_user_stats_short(char c)
-{
-	char l;
-	if (!ALLOW_USER_STATS)
-		return 0;
-	if (strchr(ALLOW_USER_STATS, c))
-		return 1;
-	l = tolower(c);
-	/* Hack for the flags that are case insensitive */
-	if (l == 'o' || l == 'y' || l == 'k' || l == 'g' || l == 'x' || l == 'c' ||
-		l =='f' || l == 'i' || l == 'h' || l == 'm')
-	{
-		if (islower(c) && strchr(ALLOW_USER_STATS, toupper(c)))
-			return 1;
-		else if (isupper(c) && strchr(ALLOW_USER_STATS, tolower(c)))
-			return 1;
-	}
-	/* Hack for c/C/H/h */
-	if (l == 'c')
-	{
-		if (strpbrk(ALLOW_USER_STATS, "hH"))
-			return 1;
-	} else if (l == 'h')
-		if (strpbrk(ALLOW_USER_STATS, "cC"))
-			return 1;
-	return 0;
-}
-
-static inline int allow_user_stats_long(const char *s)
-{
-	OperStat *os;
-	for (os = iConf.allow_user_stats_ext; os; os = os->next)
-	{
-		if (!strcasecmp(os->flag, s))
-			return 1;
-	}
-	return 0;
-}
-
-/* This is pretty slow, but it isn't used often so it isn't a big deal */
-static inline char *allow_user_stats_long_to_short()
-{
-	static char buffer[BUFSIZE+1];
-	int i = 0;
-	OperStat *os;
-	for (os = iConf.allow_user_stats_ext; os; os = os->next)
-	{
-		struct statstab *stat = stats_search(os->flag);
-		if (!stat)
-			continue;
-		if (!strchr(ALLOW_USER_STATS, stat->flag))
-			buffer[i++] = stat->flag;
-	}
-	buffer[i] = 0;
-	return buffer;
-}
-
-CMD_FUNC(cmd_stats)
-{
-	struct statstab *stat;
-	char flags[2];
-
-	if (parc == 3 && parv[2][0] != '+' && parv[2][0] != '-')
-	{
-		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, "STATS", 2, parc, parv) != HUNTED_ISME)
-			return;
-	}
-	if (parc < 2 || !*parv[1])
-	{
-		stats_help(client);
-		sendnumeric(client, RPL_ENDOFSTATS, '*');
-		return;
-	}
-
-	/* Decide if we are looking for 1 char or a string */
-	if (parv[1][0] && !parv[1][1])
-	{
-		if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL) && !allow_user_stats_short(parv[1][0]))
-		{
-			sendnumeric(client, ERR_NOPRIVILEGES);
-			return;
-		}
-		/* Old style, we can use a binary search here */
-		stat = stats_binary_search(parv[1][0]);
-	}
-	else
-	{
-		if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL) && !allow_user_stats_long(parv[1]))
-		{
-			sendnumeric(client, ERR_NOPRIVILEGES);
-			return;
-		}
-		/* New style, search the hard way */
-		stat = stats_search(parv[1]);
-	}
-
-	if (!stat)
-	{
-		/* Not found. Perhaps a module provides it? */
-		Hook *h;
-		int found = 0, n;
-		for (h = Hooks[HOOKTYPE_STATS]; h; h = h->next)
-		{
-			n = (*(h->func.intfunc))(client, parv[1]);
-			if (n == 1)
-				found = 1;
-		}
-		if (!found)
-			stats_help(client);
-		sendnumeric(client, RPL_ENDOFSTATS, '*');
-		return;
-	}
-
-	flags[0] = stat->flag;
-	flags[1] = '\0';
-
-	if (stat->options & FLAGS_AS_PARA)
-	{
-		if (parc > 2 && (parv[2][0] == '+' || parv[2][0] == '-'))
-		{
-			if (parc > 3)
-				stat->func(client, stats_combine_parv(parv[2],parv[3]));
-			else
-				stat->func(client, parv[2]);
-		}
-		else if (parc > 3)
-			stat->func(client, parv[3]);
-		else
-			stat->func(client, NULL);
-	}
-	else if (stat->options & SERVER_AS_PARA)
-	{
-		if (parc > 2)
-			stat->func(client, parv[2]);
-		else
-			stat->func(client, NULL);
-	}
-	else
-		stat->func(client, NULL);
-
-	/* Modules can append data:
-	 * ('STATS S' already has special code for this that
-	 *  maintains certain ordering, so not included here)
-	 */
-	if (stat->flag != 'S')
-	{
-		RunHook(HOOKTYPE_STATS, client, flags);
-	}
-
-	sendnumeric(client, RPL_ENDOFSTATS, stat->flag);
-}
-
-int stats_banversion(Client *client, const char *para)
-{
-	ConfigItem_ban *bans;
-	for (bans = conf_ban; bans; bans = bans->next)
-	{
-		if (bans->flag.type != CONF_BAN_VERSION)
-			continue;
-		sendnumeric(client, RPL_STATSBANVER,
-			bans->mask, bans->reason ? bans->reason : "No Reason");
-	}
-	return 0;
-}
-
-int stats_links(Client *client, const char *para)
-{
-	ConfigItem_link *link_p;
-#ifdef DEBUGMODE
-	Client *acptr;
-#endif
-	for (link_p = conf_link; link_p; link_p = link_p->next)
-	{
-		sendnumericfmt(client, RPL_STATSCLINE, "C - * %s %i %s %s%s%s",
-			link_p->servername,
-			link_p->outgoing.port,
-			link_p->class->name,
-			(link_p->outgoing.options & CONNECT_AUTO) ? "a" : "",
-			(link_p->outgoing.options & CONNECT_TLS) ? "S" : "",
-			(link_p->flag.temporary == 1) ? "T" : "");
-#ifdef DEBUGMODE
-		sendnotice(client, "%s (%p) has refcount %d",
-			link_p->servername, link_p, link_p->refcount);
-#endif
-		if (link_p->hub)
-			sendnumericfmt(client, RPL_STATSHLINE, "H %s * %s",
-				link_p->hub, link_p->servername);
-		else if (link_p->leaf)
-			sendnumericfmt(client, RPL_STATSLLINE, "L %s * %s %d",
-				link_p->leaf, link_p->servername, link_p->leaf_depth);
-	}
-#ifdef DEBUGMODE
-	list_for_each_entry(acptr, &client_list, client_node)
-		if (MyConnect(acptr) && acptr->server && !IsMe(acptr))
-		{
-			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->server->conf,
-					acptr->server->conf->refcount,
-					acptr->server->conf->flag.temporary ? "YES" : "NO");
-		}
-#endif
-	return 0;
-}
-
-int stats_gline(Client *client, const char *para)
-{
-	int cnt = 0;
-	tkl_stats(client, TKL_GLOBAL|TKL_KILL, para, &cnt);
-	tkl_stats(client, TKL_GLOBAL|TKL_ZAP, para, &cnt);
-	return 0;
-}
-
-int stats_spamfilter(Client *client, const char *para)
-{
-	int cnt = 0;
-	tkl_stats(client, TKL_SPAMF, para, &cnt);
-	tkl_stats(client, TKL_GLOBAL|TKL_SPAMF, para, &cnt);
-	return 0;
-}
-
-int stats_except(Client *client, const char *para)
-{
-	int cnt = 0;
-	tkl_stats(client, TKL_EXCEPTION, para, &cnt);
-	tkl_stats(client, TKL_EXCEPTION|TKL_GLOBAL, para, &cnt);
-	return 0;
-}
-
-int stats_allow(Client *client, const char *para)
-{
-	ConfigItem_allow *allows;
-	NameValuePrioList *m;
-
-	for (allows = conf_allow; allows; allows = allows->next)
-	{
-		for (m = allows->match->printable_list; m; m = m->next)
-		{
-			sendnumeric(client, RPL_STATSILINE,
-				    namevalue_nospaces(m), "-",
-				    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, const char *para)
-{
-	int i;
-	RealCommand *mptr;
-	for (i = 0; i < 256; i++)
-		for (mptr = CommandHash[i]; mptr; mptr = mptr->next)
-			if (mptr->count)
-			sendnumeric(client, RPL_STATSCOMMANDS, mptr->cmd,
-				mptr->count, mptr->bytes);
-
-	return 0;
-}
-
-int stats_oper(Client *client, const char *para)
-{
-	ConfigItem_oper *o;
-	NameValuePrioList *m;
-
-	for (o = conf_oper; o; o = o->next)
-	{
-		for (m = o->match->printable_list; m; m = m->next)
-		{
-			sendnumeric(client, RPL_STATSOLINE,
-			            'O', namevalue_nospaces(m), o->name,
-			            o->operclass ? o->operclass: "",
-			            o->class->name ? o->class->name : "");
-		}
-	}
-	return 0;
-}
-
-static char *stats_port_helper(ConfigItem_listen *listener)
-{
-	static char buf[256];
-
-	ircsnprintf(buf, sizeof(buf), "%s%s%s",
-	    (listener->options & LISTENER_CLIENTSONLY)? "clientsonly ": "",
-	    (listener->options & LISTENER_SERVERSONLY)? "serversonly ": "",
-	    (listener->options & LISTENER_DEFER_ACCEPT)? "defer-accept ": "");
-
-	/* And one of these.. */
-	if (listener->options & LISTENER_CONTROL)
-		strlcat(buf, "control ", sizeof(buf));
-	else if (listener->socket_type == SOCKET_TYPE_UNIX)
-		;
-	else if (listener->options & LISTENER_TLS)
-		strlcat(buf, "tls ", sizeof(buf));
-	else
-		strlcat(buf, "plaintext ", sizeof(buf));
-	return buf;
-}
-
-int stats_port(Client *client, const char *para)
-{
-	ConfigItem_listen *listener;
-
-	for (listener = conf_listen; listener != NULL; listener = listener->next)
-	{
-		if (!(listener->options & LISTENER_BOUND))
-			continue;
-		if ((listener->options & LISTENER_SERVERSONLY) && !ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
-			continue;
-		if (listener->socket_type == SOCKET_TYPE_UNIX)
-		{
-			sendnotice(client, "*** Listener on %s (UNIX): has %i client(s), options: %s %s",
-				   listener->file,
-				   listener->clients,
-				   stats_port_helper(listener),
-				   listener->flag.temporary ? "[TEMPORARY]" : "");
-		} else {
-			sendnotice(client, "*** Listener on %s:%i (%s): has %i client(s), options: %s %s",
-				   listener->ip,
-				   listener->port,
-				   listener->socket_type == SOCKET_TYPE_IPV6 ? "IPv6" : "IPv4",
-				   listener->clients,
-				   stats_port_helper(listener),
-				   listener->flag.temporary ? "[TEMPORARY]" : "");
-		}
-	}
-	return 0;
-}
-
-int stats_bannick(Client *client, const char *para)
-{
-	int cnt = 0;
-	tkl_stats(client, TKL_NAME, para, &cnt);
-	tkl_stats(client, TKL_GLOBAL|TKL_NAME, para, &cnt);
-	return 0;
-}
-
-int stats_traffic(Client *client, const char *para)
-{
-	Client *acptr;
-	IRCStatistics *sp;
-	IRCStatistics tmp;
-	time_t now = TStime();
-
-	sp = &tmp;
-	memcpy(sp, &ircstats, sizeof(IRCStatistics));
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-	{
-		if (IsServer(acptr))
-		{
-			sp->is_sti += now - acptr->local->creationtime;
-			sp->is_sv++;
-		}
-		else if (IsUser(acptr))
-		{
-			sp->is_cti += now - acptr->local->creationtime;
-			sp->is_cl++;
-		}
-		else if (IsUnknown(acptr))
-			sp->is_ni++;
-	}
-
-	sendnumericfmt(client, RPL_STATSDEBUG, "accepts %u refused %u", sp->is_ac, sp->is_ref);
-	sendnumericfmt(client, RPL_STATSDEBUG, "unknown commands %u prefixes %u", sp->is_unco, sp->is_unpf);
-	sendnumericfmt(client, RPL_STATSDEBUG, "nick collisions %u unknown closes %u", sp->is_kill, sp->is_ni);
-	sendnumericfmt(client, RPL_STATSDEBUG, "wrong direction %u empty %u", sp->is_wrdi, sp->is_empt);
-	sendnumericfmt(client, RPL_STATSDEBUG, "numerics seen %u mode fakes %u", sp->is_num, sp->is_fake);
-	sendnumericfmt(client, RPL_STATSDEBUG, "auth successes %u fails %u", sp->is_asuc, sp->is_abad);
-	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, "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, const char *para)
-{
-	int i;
-
-	for (i = 0; i < MAXCONNECTIONS; i++)
-	{
-		FDEntry *fde = &fd_table[i];
-
-		if (!fde->is_open)
-			continue;
-
-		sendnumericfmt(client, RPL_STATSDEBUG,
-			"fd %3d, desc '%s', read-hdl %p, write-hdl %p, cbdata %p",
-			fde->fd, fde->desc, fde->read_callback, fde->write_callback, fde->data);
-	}
-
-	return 0;
-}
-
-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, const char *para)
-{
-	ConfigItem_vhost *vhosts;
-	NameValuePrioList *m;
-
-	for (vhosts = conf_vhost; vhosts; vhosts = vhosts->next)
-	{
-		for (m = vhosts->match->printable_list; m; m = m->next)
-		{
-			sendtxtnumeric(client, "vhost %s%s%s %s %s",
-			               vhosts->virtuser ? vhosts->virtuser : "",
-			               vhosts->virtuser ? "@" : "",
-			               vhosts->virthost,
-			               vhosts->login,
-			               namevalue_nospaces(m));
-		}
-	}
-	return 0;
-}
-
-int stats_kline(Client *client, const char *para)
-{
-	int cnt = 0;
-	tkl_stats(client, TKL_KILL, NULL, &cnt);
-	tkl_stats(client, TKL_ZAP, NULL, &cnt);
-	return 0;
-}
-
-int stats_banrealname(Client *client, const char *para)
-{
-	ConfigItem_ban *bans;
-	for (bans = conf_ban; bans; bans = bans->next)
-	{
-		if (bans->flag.type == CONF_BAN_REALNAME)
-		{
-			sendnumeric(client, RPL_STATSNLINE, bans->mask, bans->reason
-				? bans->reason : "<no reason>");
-		}
-	}
-	return 0;
-}
-
-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, const char *para)
-{
-	ConfigItem_deny_channel *dchans;
-	ConfigItem_allow_channel *achans;
-	for (dchans = conf_deny_channel; dchans; dchans = dchans->next)
-	{
-		sendtxtnumeric(client, "deny %s %c %s", dchans->channel, dchans->warn ? 'w' : '-', dchans->reason);
-	}
-  	for (achans = conf_allow_channel; achans; achans = achans->next)
-  	{
-		sendtxtnumeric(client, "allow %s", achans->channel);
-	}
-	return 0;
-}
-
-int stats_shun(Client *client, const char *para)
-{
-	int cnt = 0;
-	tkl_stats(client, TKL_GLOBAL|TKL_SHUN, para, &cnt);
-	return 0;
-}
-
-/* should this be moved to a seperate stats flag? */
-int stats_officialchannels(Client *client, const char *para)
-{
-	ConfigItem_offchans *x;
-
-	for (x = conf_offchans; x; x = x->next)
-	{
-		sendtxtnumeric(client, "%s %s", x->name, x->topic ? x->topic : "");
-	}
-	return 0;
-}
-
-#define SafePrint(x)   ((x) ? (x) : "")
-
-/** Helper for stats_set() */
-static void stats_set_anti_flood(Client *client, FloodSettings *f)
-{
-	int i;
-
-	for (i=0; floodoption_names[i]; i++)
-	{
-		if (i == FLD_CONVERSATIONS)
-		{
-			sendtxtnumeric(client, "anti-flood::%s::%s: %d users, new user every %s",
-				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",
-				f->name, floodoption_names[i],
-				(int)f->limit[i], pretty_time_val(f->period[i]));
-		}
-	}
-}
-
-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))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return 0;
-	}
-
-	sendtxtnumeric(client, "*** Configuration Report ***");
-	sendtxtnumeric(client, "network-name: %s", NETWORK_NAME);
-	sendtxtnumeric(client, "default-server: %s", DEFAULT_SERVER);
-	if (SERVICES_NAME)
-	{
-		sendtxtnumeric(client, "services-server: %s", SERVICES_NAME);
-	}
-	if (STATS_SERVER)
-	{
-		sendtxtnumeric(client, "stats-server: %s", STATS_SERVER);
-	}
-	if (SASL_SERVER)
-	{
-		sendtxtnumeric(client, "sasl-server: %s", SASL_SERVER);
-	}
-	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);
-	sendtxtnumeric(client, "modes-on-connect: %s", get_usermode_string_raw(CONN_MODES));
-	sendtxtnumeric(client, "modes-on-oper: %s", get_usermode_string_raw(OPER_MODES));
-	*modebuf = *parabuf = 0;
-	chmode_str(&iConf.modes_on_join, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf));
-	sendtxtnumeric(client, "modes-on-join: %s %s", modebuf, parabuf);
-	if (iConf.min_nick_length)
-		sendtxtnumeric(client, "min-nick-length: %i", iConf.min_nick_length);
-	sendtxtnumeric(client, "nick-length: %i", iConf.nick_length);
-	sendtxtnumeric(client, "snomask-on-oper: %s", OPER_SNOMASK);
-	if (ALLOW_USER_STATS)
-	{
-		char *longflags = allow_user_stats_long_to_short();
-		sendtxtnumeric(client, "allow-user-stats: %s%s", ALLOW_USER_STATS, longflags ? longflags : "");
-	}
-	if (RESTRICT_USERMODES)
-		sendtxtnumeric(client, "restrict-usermodes: %s", RESTRICT_USERMODES);
-	if (RESTRICT_CHANNELMODES)
-		sendtxtnumeric(client, "restrict-channelmodes: %s", RESTRICT_CHANNELMODES);
-	if (RESTRICT_EXTENDEDBANS)
-		sendtxtnumeric(client, "restrict-extendedbans: %s", RESTRICT_EXTENDEDBANS);
-	switch (UHOST_ALLOWED)
-	{
-		case UHALLOW_NEVER:
-			uhallow = "never";
-			break;
-		case UHALLOW_NOCHANS:
-			uhallow = "not-on-channels";
-			break;
-		case UHALLOW_REJOIN:
-			uhallow = "force-rejoin";
-			break;
-		case UHALLOW_ALWAYS:
-		default:
-			uhallow = "always";
-			break;
-	}
-	if (uhallow)
-		sendtxtnumeric(client, "allow-userhost-change: %s", uhallow);
-	sendtxtnumeric(client, "hide-ban-reason: %d", HIDE_BAN_REASON);
-	sendtxtnumeric(client, "anti-spam-quit-message-time: %s", pretty_time_val(ANTI_SPAM_QUIT_MSG_TIME));
-	sendtxtnumeric(client, "channel-command-prefix: %s", CHANCMDPFX ? CHANCMDPFX : "`");
-	sendtxtnumeric(client, "tls::certificate: %s", SafePrint(iConf.tls_options->certificate_file));
-	sendtxtnumeric(client, "tls::key: %s", SafePrint(iConf.tls_options->key_file));
-	sendtxtnumeric(client, "tls::trusted-ca-file: %s", SafePrint(iConf.tls_options->trusted_ca_file));
-	sendtxtnumeric(client, "tls::options: %s", iConf.tls_options->options & TLSFLAG_FAILIFNOCERT ? "FAILIFNOCERT" : "");
-	sendtxtnumeric(client, "options::show-opermotd: %d", SHOWOPERMOTD);
-	sendtxtnumeric(client, "options::hide-ulines: %d", HIDE_ULINES);
-	sendtxtnumeric(client, "options::identd-check: %d", IDENT_CHECK);
-	sendtxtnumeric(client, "options::fail-oper-warn: %d", FAILOPER_WARN);
-	sendtxtnumeric(client, "options::show-connect-info: %d", SHOWCONNECTINFO);
-	sendtxtnumeric(client, "options::no-connect-tls-info: %d", NOCONNECTTLSLINFO);
-	sendtxtnumeric(client, "options::dont-resolve: %d", DONT_RESOLVE);
-	sendtxtnumeric(client, "options::mkpasswd-for-everyone: %d", MKPASSWD_FOR_EVERYONE);
-	sendtxtnumeric(client, "options::allow-insane-bans: %d", ALLOW_INSANE_BANS);
-	sendtxtnumeric(client, "options::allow-part-if-shunned: %d", ALLOW_PART_IF_SHUNNED);
-	sendtxtnumeric(client, "maxchannelsperuser: %i", MAXCHANNELSPERUSER);
-	sendtxtnumeric(client, "ping-warning: %i seconds", PINGWARNING);
-	sendtxtnumeric(client, "auto-join: %s", AUTO_JOIN_CHANS ? AUTO_JOIN_CHANS : "0");
-	sendtxtnumeric(client, "oper-auto-join: %s", OPER_AUTO_JOIN_CHANS ? OPER_AUTO_JOIN_CHANS : "0");
-	sendtxtnumeric(client, "static-quit: %s", STATIC_QUIT ? STATIC_QUIT : "<none>");
-	sendtxtnumeric(client, "static-part: %s", STATIC_PART ? STATIC_PART : "<none>");
-	sendtxtnumeric(client, "who-limit: %d", WHOLIMIT);
-	sendtxtnumeric(client, "silence-limit: %d", SILENCE_LIMIT);
-	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);
-	sendtxtnumeric(client, "anti-flood::connect-flood: %d per %s", THROTTLING_COUNT, pretty_time_val(THROTTLING_PERIOD));
-	sendtxtnumeric(client, "anti-flood::handshake-data-flood::amount: %ld bytes", iConf.handshake_data_flood_amount);
-	sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-action: %s", banact_valtostring(iConf.handshake_data_flood_ban_action));
-	sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-time: %s", pretty_time_val(iConf.handshake_data_flood_ban_time));
-
-	/* set::anti-flood */
-	for (s = securitygroups; s; s = s->next)
-		if ((f = find_floodsettings_block(s->name)))
-			stats_set_anti_flood(client, f);
-	f = find_floodsettings_block("unknown-users");
-	stats_set_anti_flood(client, f);
-
-	//if (AWAY_PERIOD)
-	//	sendtxtnumeric(client, "anti-flood::away-flood: %d per %s", AWAY_COUNT, pretty_time_val(AWAY_PERIOD));
-	//sendtxtnumeric(client, "anti-flood::nick-flood: %d per %s", NICK_COUNT, pretty_time_val(NICK_PERIOD));
-	sendtxtnumeric(client, "handshake-timeout: %s", pretty_time_val(iConf.handshake_timeout));
-	sendtxtnumeric(client, "sasl-timeout: %s", pretty_time_val(iConf.sasl_timeout));
-	sendtxtnumeric(client, "ident::connect-timeout: %s", pretty_time_val(IDENT_CONNECT_TIMEOUT));
-	sendtxtnumeric(client, "ident::read-timeout: %s", pretty_time_val(IDENT_READ_TIMEOUT));
-	sendtxtnumeric(client, "spamfilter::ban-time: %s", pretty_time_val(SPAMFILTER_BAN_TIME));
-	sendtxtnumeric(client, "spamfilter::ban-reason: %s", SPAMFILTER_BAN_REASON);
-	sendtxtnumeric(client, "spamfilter::virus-help-channel: %s", SPAMFILTER_VIRUSCHAN);
-	if (SPAMFILTER_EXCEPT)
-		sendtxtnumeric(client, "spamfilter::except: %s", SPAMFILTER_EXCEPT);
-	sendtxtnumeric(client, "check-target-nick-bans: %s", CHECK_TARGET_NICK_BANS ? "yes" : "no");
-	sendtxtnumeric(client, "plaintext-policy::user: %s", policy_valtostr(iConf.plaintext_policy_user));
-	sendtxtnumeric(client, "plaintext-policy::oper: %s", policy_valtostr(iConf.plaintext_policy_oper));
-	sendtxtnumeric(client, "plaintext-policy::server: %s", policy_valtostr(iConf.plaintext_policy_server));
-	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));
-	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);
-#endif
-	return 1;
-}
-
-int stats_tld(Client *client, const char *para)
-{
-	ConfigItem_tld *tld;
-	NameValuePrioList *m;
-
-	for (tld = conf_tld; tld; tld = tld->next)
-	{
-		for (m = tld->match->printable_list; m; m = m->next)
-		{
-			sendnumeric(client, RPL_STATSTLINE, namevalue_nospaces(m),
-			            tld->motd_file,
-			            tld->rules_file ? tld->rules_file : "none");
-		}
-	}
-
-	return 0;
-}
-
-int stats_uptime(Client *client, const char *para)
-{
-	long long uptime;
-
-	uptime = TStime() - me.local->fake_lag;
-	sendnumeric(client, RPL_STATSUPTIME,
-	    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, const char *para)
-{
-	ConfigItem_deny_version *versions;
-	for (versions = conf_deny_version; versions; versions = versions->next)
-	{
-		sendnumeric(client, RPL_STATSVLINE,
-			versions->version, versions->flags, versions->mask);
-	}
-	return 0;
-}
-
-int stats_notlink(Client *client, const char *para)
-{
-	ConfigItem_link *link_p;
-
-	for (link_p = conf_link; link_p; link_p = link_p->next)
-	{
-		if (!find_server_quick(link_p->servername))
-		{
-			sendnumeric(client, RPL_STATSXLINE, link_p->servername,
-				link_p->outgoing.port);
-		}
-	}
-	return 0;
-}
-
-int stats_class(Client *client, const char *para)
-{
-	ConfigItem_class *classes;
-
-	for (classes = conf_class; classes; classes = classes->next)
-	{
-		sendnumeric(client, RPL_STATSYLINE, classes->name, classes->pingfreq, classes->connfreq,
-			classes->maxclients, classes->sendq, classes->recvq ? classes->recvq : DEFAULT_RECVQ);
-#ifdef DEBUGMODE
-		sendnotice(client, "class '%s' has clients=%d, xrefcount=%d",
-			classes->name, classes->clients, classes->xrefcount);
-#endif
-	}
-	return 0;
-}
-
-int stats_linkinfo(Client *client, const char *para)
-{
-	return stats_linkinfoint(client, para, 0);
-}
-
-int stats_linkinfoall(Client *client, const char *para)
-{
-	return stats_linkinfoint(client, para, 1);
-}
-
-int stats_linkinfoint(Client *client, const char *para, int all)
-{
-	int remote = 0;
-	int wilds = 0;
-	int doall = 0;
-	Client *acptr;
-
-	/*
-	 * send info about connections which match, or all if the
-	 * mask matches me.name.  Only restrictions are on those who
-	 * are invisible not being visible to 'foreigners' who use
-	 * a wild card based search to list it.
-	 */
-	if (para)
-	{
-		if (!mycmp(para, me.name))
-			doall = 2;
-		else if (match_simple(para, me.name))
-			doall = 1;
-		if (strchr(para, '*') || strchr(para, '?'))
-			wilds = 1;
-	}
-	else
-		para = me.name;
-
-	sendnumericfmt(client, RPL_STATSLINKINFO, "Name SendQ SendM SendBytes RcveM RcveBytes Open_since :Idle");
-
-	if (!MyUser(client))
-	{
-		remote = 1;
-		wilds = 0;
-	}
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-	{
-		if (IsInvisible(acptr) && (doall || wilds) &&
-			!IsOper(acptr) && (acptr != client))
-			continue;
-		if (remote && doall && !IsServer(acptr) && !IsMe(acptr))
-			continue;
-		if (remote && !doall && IsServer(acptr))
-			continue;
-		if (!doall && wilds && !match_simple(para, acptr->name))
-			continue;
-		if (!(para && (IsServer(acptr) || IsListening(acptr))) &&
-		    !(doall || wilds) &&
-		    mycmp(para, acptr->name))
-		{
-			continue;
-		}
-
-		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->server->flags.synced ? "SYNCED" : "NOT SYNCED!!");
-	}
-#endif
-	return 0;
-}
diff --git a/src/modules/sts.c b/src/modules/sts.c
@@ -1,111 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/sts.c
- *   (C) 2017 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"sts",
-	"5.0",
-	"Strict Transport Security CAP", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	return MOD_SUCCESS;
-}
-
-void init_sts(ModuleInfo *modinfo);
-
-MOD_LOAD()
-{
-	/* init_sts is delayed to MOD_LOAD due to configuration dependency */
-	init_sts(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Check if this capability should be visible.
- * Note that 'client' may be NULL.
- */
-int sts_capability_visible(Client *client)
-{
-	TLSOptions *ssl;
-
-	/* This is possible if queried from the CAP NEW/DEL code */
-	if (client == NULL)
-		return (iConf.tls_options && iConf.tls_options->sts_port) ? 1 : 0;
-
-	if (!IsSecure(client))
-	{
-		if (iConf.tls_options && iConf.tls_options->sts_port)
-			return 1; /* YES, non-TLS user and set::tls::sts-policy configured */
-		return 0; /* NO, there is no sts-policy */
-	}
-
-	ssl = FindTLSOptionsForUser(client);
-
-	if (ssl && ssl->sts_port)
-		return 1;
-
-	return 0;
-}
-
-const char *sts_capability_parameter(Client *client)
-{
-	TLSOptions *ssl;
-	static char buf[256];
-
-	if (IsSecure(client))
-		ssl = FindTLSOptionsForUser(client);
-	else
-		ssl = iConf.tls_options;
-
-	if (!ssl)
-		return ""; /* This would be odd. */
-
-	snprintf(buf, sizeof(buf), "port=%d,duration=%ld", ssl->sts_port, ssl->sts_duration);
-	if (ssl->sts_preload)
-		strlcat(buf, ",preload", sizeof(buf));
-
-	return buf;
-}
-
-void init_sts(ModuleInfo *modinfo)
-{
-	ClientCapabilityInfo cap;
-
-	memset(&cap, 0, sizeof(cap));
-	cap.name = "sts";
-	cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
-	cap.visible = sts_capability_visible;
-	cap.parameter = sts_capability_parameter;
-	ClientCapabilityAdd(modinfo->handle, &cap, NULL);
-}
diff --git a/src/modules/svsjoin.c b/src/modules/svsjoin.c
@@ -1,97 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/svsjoin.c
- *   (C) 2000-2001 Carsten V. Munk 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"
-
-CMD_FUNC(cmd_svsjoin);
-
-/* Place includes here */
-#define MSG_SVSJOIN       "SVSJOIN"
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsjoin",	/* Name of module */
-	"5.0", /* Version */
-	"command /svsjoin", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSJOIN, cmd_svsjoin, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-/* cmd_svsjoin() - Lamego - Wed Jul 21 20:04:48 1999
-   Copied off PTlink IRCd (C) PTlink coders team.
-	parv[1] - nick to make join
-	parv[2] - channel(s) to join
-	parv[3] - (optional) channel key(s)
-*/
-CMD_FUNC(cmd_svsjoin)
-{
-	Client *target;
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if ((parc < 3) || !(target = find_user(parv[1], NULL)))
-		return;
-
-	if (MyUser(target))
-	{
-		parv[0] = target->name;
-		parv[1] = parv[2];
-		if (parc == 3)
-		{
-			parv[2] = NULL;
-			do_cmd(target, NULL, "JOIN", 2, parv);
-			/* NOTE: 'target' may be killed if we ever implement spamfilter join channel target */
-		} else {
-			parv[2] = parv[3];
-			parv[3] = NULL;
-			do_cmd(target, NULL, "JOIN", 3, parv);
-			/* NOTE: 'target' may be killed if we ever implement spamfilter join channel target */
-		}
-	}
-	else
-	{
-		if (parc == 3)
-			sendto_one(target, NULL, ":%s SVSJOIN %s %s", client->name,
-			    parv[1], parv[2]);
-		else
-			sendto_one(target, NULL, ":%s SVSJOIN %s %s %s", client->name,
-				parv[1], parv[2], parv[3]);
-	}
-}
diff --git a/src/modules/svskill.c b/src/modules/svskill.c
@@ -1,88 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/svskill.c
- *   (C) 2004 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"
-
-CMD_FUNC(cmd_svskill);
-
-#define MSG_SVSKILL	"SVSKILL"
-
-ModuleHeader MOD_HEADER
-  = {
-	"svskill",	/* Name of module */
-	"5.0", /* Version */
-	"command /svskill", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSKILL, cmd_svskill, MAXPARA, CMD_SERVER|CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_svskill
-**	parv[1] = client
-**	parv[2] = kill message
-*/
-CMD_FUNC(cmd_svskill)
-{
-	MessageTag *mtags = NULL;
-	Client *target;
-	const char *comment = "SVS Killed";
-	int n;
-
-	if (parc < 2)
-		return;
-	if (parc > 3)
-		return;
-	if (parc == 3)
-		comment = parv[2];
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if (!(target = find_user(parv[1], NULL)))
-		return;
-
-	/* for new_message() we use target here, makes sense for the exit_client, right? */
-	new_message(target, recv_mtags, &mtags);
-	sendto_server(client, 0, 0, mtags, ":%s SVSKILL %s :%s", client->id, target->id, comment);
-	SetKilled(target);
-	exit_client(target, mtags, comment);
-	free_message_tags(mtags);
-}
diff --git a/src/modules/svslogin.c b/src/modules/svslogin.c
@@ -1,101 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svslogin.c
- *   (C) 2022 The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-#define MSG_SVSLOGIN "SVSLOGIN"
-
-CMD_FUNC(cmd_svslogin);
-
-ModuleHeader MOD_HEADER
-  = {
-	"svslogin",
-	"6.0",
-	"command /SVSLOGIN", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSLOGIN, cmd_svslogin, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * SVSLOGIN message
- *
- * parv[1]: propagation mask
- * parv[2]: target
- * parv[3]: account name (SVID)
- */
-CMD_FUNC(cmd_svslogin)
-{
-	Client *target;
-
-	if (MyUser(client) || (parc < 3) || !parv[3])
-		return;
-
-	/* We actually ignore parv[1] since this is a broadcast message.
-	 * It is a broadcast message because we want ALL servers to know
-	 * that the user is now logged in under account xyz.
-	 */
-
-	target = find_client(parv[2], NULL);
-	if (target)
-	{
-		if (IsServer(target))
-			return;
-
-		if (target->user == NULL)
-			make_user(target);
-
-		strlcpy(target->user->account, parv[3], sizeof(target->user->account));
-		user_account_login(recv_mtags, target);
-		if (MyConnect(target) && IsDead(target))
-			return; /* was killed due to *LINE on ~a probably */
-	} else {
-		/* It is perfectly normal for target to be NULL as this
-		 * happens during registration phase (pre-connect).
-		 * It just means we cannot set any properties for this user,
-		 * which is fine in that case, since it will be synced via
-		 * the UID message instead.
-		 * We still have to broadcast the message, which is why
-		 * we do not return here.
-		 */
-	}
-
-	/* Propagate to the rest of the network */
-	sendto_server(client, 0, 0, NULL, ":%s SVSLOGIN %s %s %s",
-	              client->name, parv[1], parv[2], parv[3]);
-}
-\ No newline at end of file
diff --git a/src/modules/svslusers.c b/src/modules/svslusers.c
@@ -1,79 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svslusers.c
- *   (C) 2002 codemastr [Dominick Meglio] (codemastr@unrealircd.com)
- *
- *   SVSLUSERS command, allows remote setting of local and global max user count
- *
- *   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_svslusers);
-
-#define MSG_SVSLUSERS 	"SVSLUSERS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"svslusers",
-	"5.0",
-	"command /svslusers", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSLUSERS, cmd_svslusers, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_svslusers
-**      parv[1] = server to update
-**      parv[2] = max global users
-**      parv[3] = max local users
-**      If -1 is specified for either number, it is ignored and the current count
-**      is kept.
-*/
-CMD_FUNC(cmd_svslusers)
-{
-        if (!IsSvsCmdOk(client) || parc < 4)
-		return;  
-        if (hunt_server(client, NULL, "SVSLUSERS", 1, parc, parv) == HUNTED_ISME)
-        {
-		int temp;
-		temp = atoi(parv[2]);
-		if (temp >= 0)
-			irccounts.global_max = temp;
-		temp = atoi(parv[3]);
-		if (temp >= 0) 
-			irccounts.me_max = temp;
-        }
-}
diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c
@@ -1,640 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svsmode.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   SVSMODE and SVS2MODE commands
- *
- *   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.
- */
-
-/* FIXME: this one needs a lot more mtag work !! with _special... */
-
-#include "unrealircd.h"
-
-void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param);
-CMD_FUNC(cmd_svsmode);
-CMD_FUNC(cmd_svs2mode);
-
-#define MSG_SVSMODE 	"SVSMODE"	
-#define MSG_SVS2MODE    "SVS2MODE"
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsmode",
-	"5.0",
-	"command /svsmode and svs2mode", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-char modebuf[BUFSIZE], parabuf[BUFSIZE];
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSMODE, cmd_svsmode, MAXPARA, CMD_SERVER|CMD_USER);
-	CommandAdd(modinfo->handle, MSG_SVS2MODE, cmd_svs2mode, MAXPARA, CMD_SERVER|CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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];
-
-	/* BUILD HOSTS */
-
-	*uhost = *vhost = *ihost = *chost = '\0';
-
-	strlcpy(uhost, make_nick_user_host(acptr->name, 
-		acptr->user->username, acptr->user->realhost),
-		sizeof uhost);
-
-	if (GetIP(acptr)) /* only if we actually have an IP */
-		strlcpy(ihost, make_nick_user_host(acptr->name,
-			acptr->user->username, GetIP(acptr)),
-			sizeof ihost);
-
-	/* The next could have been an IsSetHost(), but I'm playing it safe with regards to backward compat. */
-	if (IsHidden(acptr) &&
-	    !(*acptr->user->cloakedhost && !strcasecmp(acptr->user->virthost, acptr->user->cloakedhost))) 
-	{
-		strlcpy(vhost, make_nick_user_host(acptr->name,
-			acptr->user->username, acptr->user->virthost),
-			sizeof vhost);
-	}
-
-	if (*acptr->user->cloakedhost) /* only if we know the cloaked host */
-		strlcpy(chost, make_nick_user_host(acptr->name, 
-			acptr->user->username, acptr->user->cloakedhost),
-			sizeof chost);
-
-	/* SELECT BANLIST */
-
-	switch (chmode)
-	{
-		case 'b':
-			banlist = &channel->banlist;
-			break;
-		case 'e':
-			banlist = &channel->exlist;
-			break;
-		case 'I':
-			banlist = &channel->invexlist;
-			break;
-		default:
-			abort();
-	}
-
-	/* 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;
-		if (match_simple(ban->banstr, uhost) ||
-		    (*vhost && match_simple(ban->banstr, vhost)) ||
-		    (*ihost && match_simple(ban->banstr, ihost)) ||
-		    (*chost && match_simple(ban->banstr, chost)))
-		{
-			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, &nextbanstr)))
-		{
-			if (extban->is_banned_events & b->ban_check_types)
-			{
-				b->banstr = nextbanstr;
-				if (extban->is_banned(b))
-				{
-					add_send_mode_param(channel, acptr, '-', chmode, ban->banstr);
-					del_listmode(banlist, channel, ban->banstr);
-				}
-			}
-		}
-	}
-	safe_free(b);
-}
-
-void clear_bans(Client *client, Channel *channel, char chmode)
-{
-	Extban *extban;
-	Ban *ban, *bnext;
-	Ban **banlist;
-
-	switch (chmode)
-	{
-		case 'b':
-			banlist = &channel->banlist;
-			break;
-		case 'e':
-			banlist = &channel->exlist;
-			break;
-		case 'I':
-			banlist = &channel->invexlist;
-			break;
-		default:
-			abort();
-	}
-	
-	for (ban = *banlist; ban; ban = bnext)
-	{
-		bnext = ban->next;
-		if (chmode != 'I' && (*ban->banstr == '~') && (extban = findmod_by_bantype(ban->banstr, NULL)))
-		{
-			if (!(extban->is_banned_events & BANCHK_JOIN))
-				continue;
-		}
-		add_send_mode_param(channel, client, '-',  chmode, ban->banstr);
-		del_listmode(banlist, channel, ban->banstr);
-	}
-}
-
-/** Special Channel MODE command for services, used by SVSMODE and SVS2MODE.
- * @note
- * This SVSMODE/SVS2MODE for channels is not simply the regular MODE "but for
- * services". No, it does different things.
- *
- *  Syntax: SVSMODE <channel> <mode and params>
- *
- * There are three variants (do NOT mix them!):
- * 1) SVSMODE #chan -[b|e|I]
- *    This will remove all bans/exempts/invex in the channel.
- * 2) SVSMODE #chan -[b|e|I] nickname
- *    This will remove all bans/exempts/invex that affect nickname.
- *    Eg: -b nick may remove bans places on IP, host, cloaked host, vhost,
- *    basically anything that matches this nickname. Note that the
- *    user with the specified nickname needs to be online for this to work.
- * 3) SVSMODE #chan -[v|h|o|a|q]
- *    This will remove the specified mode(s) from all users in the channel.
- *    Eg: -o will result in a MODE -o for every channel operator.
- *
- * OLD syntax had a 'ts' parameter. No services are known to use this.
- */
-void channel_svsmode(Client *client, int parc, const char *parv[]) 
-{
-	Channel *channel;
-	Client *target;
-	const char *m;
-	int what = MODE_ADD;
-	int i = 4; // wtf is this
-	Member *member;
-	int channel_flags;
-
-	*parabuf = *modebuf = '\0';
-
-	if ((parc < 3) || BadPtr(parv[2]))
-		return;
-
-	if (!(channel = find_channel(parv[1])))
-		return;
-
-	for (m = parv[2]; *m; m++)
-	{
-		if (*m == '+') 
-		{
-			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)))
-				{
-					i++;
-					break;
-				}
-				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);
-				}
-			}
-		}
-	}
-
-	/* only send message if modes have changed */
-	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->name,  modebuf, parabuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s%s", client->id, channel->name, modebuf, parabuf, IsServer(client)?" 0":"");
-
-		/* Activate this hook just like cmd_mode.c */
-		RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel);
-
-		free_message_tags(mtags);
-
-		*parabuf = 0;
-	}
-}
-
-/** Special User MODE command for Services.
- * @note
- * 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] - 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, const char *parv[], int show_change)
-{
-	Umode *um;
-	const char *m;
-	Client *target;
-	int  what;
-	long oldumodes = 0;
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	what = MODE_ADD;
-
-	if (parc < 3)
-		return;
-
-	if (parv[1][0] == '#') 
-	{
-		channel_svsmode(client, parc, parv);
-		return;
-	}
-
-	if (!(target = find_user(parv[1], NULL)))
-		return;
-
-	userhost_save_current(target);
-
-	oldumodes = target->umodes;
-
-	/* parse mode change string(s) */
-	for (m = parv[2]; *m; m++)
-		switch (*m)
-		{
-			case '+':
-				what = MODE_ADD;
-				break;
-			case '-':
-				what = MODE_DEL;
-				break;
-
-			/* we may not get these, but they shouldnt be in default */
-			case ' ':
-			case '\n':
-			case '\r':
-			case '\t':
-				break;
-			case 'i':
-				if ((what == MODE_ADD) && !(target->umodes & UMODE_INVISIBLE))
-					irccounts.invisible++;
-				if ((what == MODE_DEL) && (target->umodes & UMODE_INVISIBLE))
-					irccounts.invisible--;
-				goto setmodex;
-			case 'o':
-				if ((what == MODE_ADD) && !(target->umodes & UMODE_OPER))
-				{
-					if (!IsOper(target) && MyUser(target))
-						list_add(&target->special_node, &oper_list);
-
-					irccounts.operators++;
-				}
-				if ((what == MODE_DEL) && (target->umodes & UMODE_OPER))
-				{
-					if (target->umodes & UMODE_HIDEOPER)
-					{
-						/* clear 'H' too, and opercount stays the same.. */
-						target->umodes &= ~UMODE_HIDEOPER;
-					} else {
-						irccounts.operators--;
-					}
-
-					if (MyUser(target) && !list_empty(&target->special_node))
-						list_del(&target->special_node);
-					
-					/* 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, NULL);
-					remove_oper_privileges(target, 0);
-				}
-				goto setmodex;
-			case 'H':
-				if (what == MODE_ADD && !(target->umodes & UMODE_HIDEOPER))
-				{
-					if (!IsOper(target) && !strchr(parv[2], 'o')) /* (ofcoz this strchr() is flawed) */
-					{
-						/* isn't an oper, and would not become one either.. abort! */
-						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--;
-				}
-				if (what == MODE_DEL && (target->umodes & UMODE_HIDEOPER))
-					irccounts.operators++;
-				goto setmodex;
-			case 'd':
-				if (parv[3])
-				{
-					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 */
-				}
-				else
-				{
-					/* setting deaf */
-					goto setmodex;
-				}
-				break;
-			case 'x':
-				if (what == MODE_DEL)
-				{
-					/* -x */
-					if (target->user->virthost)
-					{
-						/* Removing mode +x and virthost set... recalculate host then (but don't activate it!) */
-						safe_strdup(target->user->virthost, target->user->cloakedhost);
-					}
-				} else
-				{
-					/* +x */
-					if (!target->user->virthost)
-					{
-						/* Hmm... +x but no virthost set, that's bad... use cloakedhost.
-						 * Not sure if this could ever happen, but just in case... -- Syzop
-						 */
-						safe_strdup(target->user->virthost, target->user->cloakedhost);
-					}
-					/* Announce the new host to VHP servers if we're setting the virthost to the cloakedhost.
-					 * In other cases, we can assume that the host has been broadcasted already (after all,
-					 * how else could it have been changed...?).
-					 * NOTES: we're doing a strcasecmp here instead of simply checking if it's a "+x but
-					 * not -t"-case. The reason for this is that the 't' might follow ("+xt" instead of "+tx"),
-					 * in which case we would have needlessly announced it. Ok I didn't test it but that's
-					 * the idea behind it :P. -- Syzop
-					 */
-					if (MyUser(target) && !strcasecmp(target->user->virthost, target->user->cloakedhost))
-						sendto_server(NULL, PROTO_VHP, 0, NULL, ":%s SETHOST :%s", target->id,
-							target->user->virthost);
-				}
-				goto setmodex;
-			case 't':
-				/* We support -t nowadays, which means we remove the vhost and set the cloaked host
-				 * (note that +t is a NOOP, that code is in +x)
-				 */
-				if (what == MODE_DEL)
-				{
-					/* First, check if there's a change at all. Perhaps it's a -t on someone
-					 * with no virthost or virthost being cloakedhost?
-					 * Also, check to make sure user->cloakedhost exists at all.
-					 * This so we won't crash in weird cases like non-conformant servers.
-					 */
-					if (target->user->virthost && *target->user->cloakedhost && strcasecmp(target->user->cloakedhost, GetHost(target)))
-					{
-						/* Make the change effective: */
-						safe_strdup(target->user->virthost, target->user->cloakedhost);
-						/* And broadcast the change to VHP servers */
-						if (MyUser(target))
-							sendto_server(NULL, PROTO_VHP, 0, NULL, ":%s SETHOST :%s", target->id,
-								target->user->virthost);
-					}
-					goto setmodex;
-				}
-				break;
-			case 'z':
-				/* Setting and unsetting user mode 'z' remotely is not supported */
-				break;
-			default:
-				setmodex:
-				for (um = usermodes; um; um = um->next)
-				{
-					if (um->letter == *m)
-					{
-						if (what == MODE_ADD)
-							target->umodes |= um->mode;
-						else
-							target->umodes &= ~um->mode;
-						break;
-					}
-				}
-				break;
-		} /*switch*/
-
-	if (parc > 3)
-		sendto_server(client, 0, 0, recv_mtags, ":%s %s %s %s %s",
-		    client->id, show_change ? "SVS2MODE" : "SVSMODE",
-		    parv[1], parv[2], parv[3]);
-	else
-		sendto_server(client, 0, 0, recv_mtags, ":%s %s %s %s",
-		    client->id, show_change ? "SVS2MODE" : "SVSMODE",
-		    parv[1], parv[2]);
-
-	/* Here we trigger the same hooks that cmd_mode does and, likewise,
-	   only if the old flags (oldumodes) are different than the newly-
-	   set ones */
-	if (oldumodes != target->umodes)
-		RunHook(HOOKTYPE_UMODE_CHANGE, target, oldumodes, target->umodes);
-
-	if (show_change)
-	{
-		char buf[BUFSIZE];
-		build_umode_string(target, oldumodes, ALL_UMODES, buf);
-		if (MyUser(target) && *buf)
-		{
-			MessageTag *mtags = NULL;
-			new_message(client, recv_mtags, &mtags);
-			sendto_one(target, mtags, ":%s MODE %s :%s", client->name, target->name, buf);
-			safe_free_message_tags(mtags);
-		}
-	}
-
-	userhost_changed(target); /* we can safely call this, even if nothing changed */
-
-	VERIFY_OPERCOUNT(target, "svsmodeX");
-}
-
-/*
- * cmd_svsmode() added by taz
- * parv[1] - username to change mode for
- * parv[2] - modes to change
- * parv[3] - account name (if mode contains 'd')
- */
-CMD_FUNC(cmd_svsmode)
-{
-	do_svsmode(client, recv_mtags, parc, parv, 0);
-}
-
-/*
- * cmd_svs2mode() added by Potvin
- * parv[1] - username to change mode for
- * parv[2] - modes to change
- * parv[3] - account name (if mode contains 'd')
- */
-CMD_FUNC(cmd_svs2mode)
-{
-	do_svsmode(client, recv_mtags, parc, parv, 1);
-}
-
-void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param)
-{
-	static char *modes = NULL, lastwhat;
-	static short count = 0;
-	short send = 0;
-	
-	if (!modes) modes = modebuf;
-	
-	if (!modebuf[0])
-	{
-		modes = modebuf;
-		*modes++ = what;
-		*modes = 0;
-		lastwhat = what;
-		*parabuf = 0;
-		count = 0;
-	}
-	if (lastwhat != what)
-	{
-		*modes++ = what;
-		*modes = 0;
-		lastwhat = what;
-	}
-	if (strlen(parabuf) + strlen(param) + 11 < MODEBUFLEN)
-	{
-		if (*parabuf) 
-			strcat(parabuf, " ");
-		strcat(parabuf, param);
-		*modes++ = mode;
-		*modes = 0;
-		count++;
-	}
-	else if (*parabuf) 
-		send = 1;
-
-	if (count == MAXMODEPARAMS)
-		send = 1;
-
-	if (send)
-	{
-		MessageTag *mtags = NULL;
-		/* NOTE: cannot use 'recv_mtag' here because MODE could be rewrapped. Not ideal :( */
-		new_message(from, NULL, &mtags);
-		sendto_channel(channel, from, from, 0, 0, SEND_LOCAL, mtags,
-		               ":%s MODE %s %s %s",
-		               from->name, channel->name, modebuf, parabuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s%s", from->id, channel->name, modebuf, parabuf, IsServer(from)?" 0":"");
-		free_message_tags(mtags);
-		send = 0;
-		*parabuf = 0;
-		modes = modebuf;
-		*modes++ = what;
-		lastwhat = what;
-		if (count != MAXMODEPARAMS)
-		{
-			strcpy(parabuf, param);
-			*modes++ = mode;
-			count = 1;
-		}
-		else 
-			count = 0;
-		*modes = 0;
-	}
-}
diff --git a/src/modules/svsmotd.c b/src/modules/svsmotd.c
@@ -1,112 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svsmotd.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   SVSMOTD command
- *
- *   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_svsmotd);
-
-#define MSG_SVSMOTD 	"SVSMOTD"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsmotd",
-	"5.0",
-	"command /svsmotd", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSMOTD, cmd_svsmotd, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_svsmotd
-**
-*/
-CMD_FUNC(cmd_svsmotd)
-{
-	FILE *conf = NULL;
-
-	if (!IsSvsCmdOk(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[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]);
-
-	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;
-	}
-
-	/* 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
@@ -1,122 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svsnick.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   svsnick command
- *
- *   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_svsnick);
-
-#define MSG_SVSNICK 	"SVSNICK"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsnick",
-	"5.0",
-	"command /svsnick", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSNICK, cmd_svsnick, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-/*
-** cmd_svsnick
-**      parv[1] = old nickname
-**      parv[2] = new nickname
-**      parv[3] = timestamp
-*/
-CMD_FUNC(cmd_svsnick)
-{
-	Client *acptr;
-	Client *ocptr; /* Other client */
-	MessageTag *mtags = NULL;
-	char nickname[NICKLEN+1];
-	char oldnickname[NICKLEN+1];
-	time_t ts;
-
-	if (!IsSvsCmdOk(client) || parc < 4 || (strlen(parv[2]) > NICKLEN))
-		return; /* This looks like an error anyway -Studded */
-
-	if (hunt_server(client, NULL, "SVSNICK", 1, parc, parv) != HUNTED_ISME)
-		return; /* Forwarded, done */
-
-	strlcpy(nickname, parv[2], sizeof(nickname));
-	if (do_nick_name(nickname) == 0)
-		return;
-
-	if (!(acptr = find_user(parv[1], NULL)))
-		return; /* User not found, bail out */
-
-	if ((ocptr = find_client(nickname, NULL)) && ocptr != acptr) /* Collision */
-	{
-		exit_client(acptr, NULL,
-		                   "Nickname collision due to forced "
-		                   "nickname change, your nick was overruled");
-		return;
-	}
-
-	/* if the new nickname is identical to the old one, ignore it */
-	if (!strcmp(acptr->name, nickname))
-		return;
-
-	strlcpy(oldnickname, acptr->name, sizeof(oldnickname));
-
-	if (acptr != ocptr)
-		acptr->umodes &= ~UMODE_REGNICK;
-	ts = atol(parv[3]);
-
-	/* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */
-	new_message(acptr, NULL, &mtags);
-	mtag_add_issued_by(&mtags, client, recv_mtags);
-	RunHook(HOOKTYPE_LOCAL_NICKCHANGE, acptr, mtags, nickname);
-	sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, nickname);
-	sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, nickname);
-	sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%lld", acptr->id, nickname, (long long)ts);
-
-	add_history(acptr, 1, WHOWAS_EVENT_NICK_CHANGE);
-	acptr->lastnick = ts; /* needs to be done AFTER add_history() */
-	del_from_client_hash_table(acptr->name, acptr);
-
-	unreal_log(ULOG_INFO, "nick", "FORCED_NICK_CHANGE", acptr,
-	           "$client.details has been forced to change their nickname to $new_nick_name",
-	           log_data_string("new_nick_name", nickname));
-
-	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
@@ -1,157 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svsnline.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   SVSNLINE Command
- *
- *   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_svsnline);
-
-#define MSG_SVSNLINE 	"SVSNLINE"	/* svsnline */
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsnline",	/* Name of module */
-	"5.0", /* Version */
-	"command /svsnline", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSNLINE, cmd_svsnline, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void wipe_svsnlines(void)
-{
-	ConfigItem_ban *bconf, *next;
-	
-	for (bconf = conf_ban; bconf; bconf = next)
-	{
-		next = bconf->next;
-		if ((bconf->flag.type == CONF_BAN_REALNAME) &&
-			(bconf->flag.type2 == CONF_BAN_TYPE_AKILL))
-		{
-			DelListItem(bconf, conf_ban);
-			safe_free(bconf->mask);
-			safe_free(bconf->reason);
-			safe_free(bconf);
-		}
-	}
-}
-
-/*
- * cmd_svsnline
- * SVSNLINE + reason_where_is_space :realname mask with spaces
- * SVSNLINE - :realname mask
- * SVSNLINE *     Wipes
- * -Stskeeps
-*/
-CMD_FUNC(cmd_svsnline)
-{
-	ConfigItem_ban *bconf;
-	char		*s;
-
-	if (parc < 2)
-		return;
-
-	switch (*parv[1])
-	{
-		  /* Allow non-U-Lines to send ONLY SVSNLINE +, but don't send it out
-		   * unless it is from a U-Line -- codemastr */
-	  case '+':
-	  {
-		  if (parc < 4)
-			  return;
-		 
-		  if (!find_banEx(NULL, parv[3], CONF_BAN_REALNAME, CONF_BAN_TYPE_AKILL))
-		  {
-			bconf = safe_alloc(sizeof(ConfigItem_ban));
-			bconf->flag.type = CONF_BAN_REALNAME;
-			safe_strdup(bconf->mask, parv[3]);
-			safe_strdup(bconf->reason, parv[2]);
-			for (s = bconf->reason; *s; s++)
-				if (*s == '_')
-					*s = ' ';
-			bconf->flag.type2 = CONF_BAN_TYPE_AKILL;
-			AddListItem(bconf, conf_ban);
-		  } 
-		 
-		  if (IsSvsCmdOk(client))
-			sendto_server(client, 0, 0, NULL, ":%s SVSNLINE + %s :%s",
-			    client->id, parv[2], parv[3]);
-		  break;
-	  }
-	  case '-':
-	  {
-		  if (!IsSvsCmdOk(client))
-			  return;
-		  if (parc < 3)
-			  return;
-		  
-		  for (bconf = conf_ban; bconf; bconf = bconf->next)
-		  {
-			if (bconf->flag.type != CONF_BAN_REALNAME)
-				continue;
-			if (bconf->flag.type2 != CONF_BAN_TYPE_AKILL)
-				continue;
-			if (!strcasecmp(bconf->mask, parv[2]))
-				break;
-		  }
-		  if (bconf)
-		  {
-		  	DelListItem(bconf, conf_ban);
-	  		safe_free(bconf->mask);
-	  		safe_free(bconf->reason);
-		  	safe_free(bconf);
-		  	
-		  }
-		  sendto_server(client, 0, 0, NULL, ":%s SVSNLINE - %s", client->id, parv[2]);
-		  break;
-	  }
-	  case '*':
-	  {
-		  if (!IsSvsCmdOk(client))
-			  return;
-	          wipe_svsnlines();
-		  sendto_server(client, 0, 0, NULL, ":%s SVSNLINE *", client->id);
-		  break;
-	  }
-
-	}
-}
diff --git a/src/modules/svsnolag.c b/src/modules/svsnolag.c
@@ -1,105 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svsnolag.c
- *   (C) 2006 Alex Berezhnyak and djGrrr
- *
- *   Fake lag exception - SVSNOLAG and SVS2NOLAG commands
- *
- *   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_svsnolag);
-CMD_FUNC(cmd_svs2nolag);
-
-#define MSG_SVSNOLAG 	"SVSNOLAG"	
-#define MSG_SVS2NOLAG 	"SVS2NOLAG"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsnolag",
-	"5.0",
-	"commands /svsnolag and /svs2nolag", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSNOLAG, cmd_svsnolag, MAXPARA, CMD_SERVER);
-	CommandAdd(modinfo->handle, MSG_SVS2NOLAG, cmd_svs2nolag, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void do_svsnolag(Client *client, int parc, const char *parv[], int show_change)
-{
-	Client *target;
-	char *cmd = show_change ? MSG_SVS2NOLAG : MSG_SVSNOLAG;
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if (parc < 3)
-		return;
-
-	if (!(target = find_user(parv[2], NULL)))
-		return;
-
-	if (!MyUser(target))
-	{
-		sendto_one(target, NULL, ":%s %s %s %s", client->name, cmd, parv[1], parv[2]);
-		return;
-	}
-
-	if (*parv[1] == '+')
-	{
-		if (!IsNoFakeLag(target))
-		{
-			SetNoFakeLag(target);
-			if (show_change)
-				sendnotice(target, "You are now exempted from fake lag");
-		}
-	}
-	if (*parv[1] == '-')
-	{
-		if (IsNoFakeLag(target))
-		{
-			ClearNoFakeLag(target);
-			if (show_change)
-				sendnotice(target, "You are no longer exempted from fake lag");
-		}
-	}
-}
-
-CMD_FUNC(cmd_svsnolag)
-{
-	do_svsnolag(client, parc, parv, 0);
-}
-
-CMD_FUNC(cmd_svs2nolag)
-{
-	do_svsnolag(client, parc, parv, 1);
-}
diff --git a/src/modules/svsnoop.c b/src/modules/svsnoop.c
@@ -1,97 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svsnoop.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   svsnoop command
- *
- *   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_svsnoop);
-
-#define MSG_SVSNOOP 	"SVSNOOP"	
-
-
-ModuleHeader MOD_HEADER
-  = {
-	"svsnoop",
-	"5.0",
-	"command /svsnoop", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSNOOP, cmd_svsnoop, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-CMD_FUNC(cmd_svsnoop)
-{
-	Client *acptr;
-
-	if (!(IsSvsCmdOk(client) && parc > 2))
-		return;
-
-	if (hunt_server(client, NULL, "SVSNOOP", 1, parc, parv) == HUNTED_ISME)
-	{
-		if (parv[2][0] == '+')
-		{
-			SVSNOOP = 1;
-			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))
-				{
-					if (IsOper(acptr))
-					{
-						irccounts.operators--;
-						VERIFY_OPERCOUNT(acptr, "svsnoop");
-					}
-
-					if (!list_empty(&acptr->special_node))
-						list_del(&acptr->special_node);
-
-					RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL);
-					remove_oper_privileges(acptr, 1);
-				}
-			}
-		}
-		else
-		{
-			SVSNOOP = 0;
-			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/svso.c b/src/modules/svso.c
@@ -1,141 +0,0 @@
-/* src/modules/svso.c - Grant IRCOp rights (for Services)
- * (C) Copyright 2022 Bram Matthys (Syzop) and the UnrealIRCd team
- * License: GPLv2 or later
- */
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"svso",
-	"6.0.0",
-	"Grant oper privileges via SVSO services command",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-CMD_FUNC(cmd_svso);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	CommandAdd(modinfo->handle, "SVSO", cmd_svso, MAXPARA, CMD_USER|CMD_SERVER);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Syntax: SVSO <uid|nick> <oper account> <operclass> <class> <modes> <snomask> <vhost>
- * All these parameters need to be set, you cannot leave any of them out,
- * HOWEVER some can be set to "-" to skip setting them, this is true for:
- * <class>, <modes>, <snomask>, <vhost>
- *
- * In UnrealIRCd the <operclass> will be prefixed by "services:" if not already
- * present. It is up to you to include or omit it.
- */
-CMD_FUNC(cmd_svso)
-{
-	Client *acptr;
-	char oper_account[64];
-	const char *operclass;
-	const char *clientclass;
-	ConfigItem_class *clientclass_c;
-	const char *modes;
-	long modes_i = 0;
-	const char *snomask;
-	const char *vhost;
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if ((parc < 8) || BadPtr(parv[7]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSO");
-		return;
-	}
-
-	operclass = parv[3];
-	clientclass = parv[4];
-	modes = parv[5];
-	snomask = parv[6];
-	vhost = parv[7];
-
-	acptr = find_user(parv[1], NULL);
-	if (!acptr)
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
-		return;
-	}
-
-	if (!MyUser(acptr))
-	{
-		/* Forward it to the correct server, and we are done... */
-		sendto_one(acptr, recv_mtags, ":%s SVSO %s %s %s %s %s %s %s",
-		           client->name, acptr->id, parv[2], parv[3], parv[4], parv[5], parv[6], parv[7]);
-		return;
-	}
-
-	/* CAVEAT ALERT !
-	 * Don't mix up 'client' and 'acptr' below...
-	 * 'client' is the server or services pseudouser that requests the change
-	 * 'acptr' is the person that will be made OPER
-	 */
-
-	/* If we get here, we validate the request and then make the user oper. */
-	if (!find_operclass(operclass))
-	{
-		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Operclass does not exist");
-		return;
-	}
-
-	/* Set any items to NULL if they are skipped (on request) */
-	if (!strcmp(clientclass, "-"))
-		clientclass = NULL;
-	if (!strcmp(modes, "-"))
-		modes = NULL;
-	if (!strcmp(snomask, "-"))
-		snomask = NULL;
-	if (!strcmp(vhost, "-"))
-		vhost = NULL;
-
-	/* First, maybe the user is oper already? Then de-oper them.. */
-	if (IsOper(acptr))
-	{
-		int was_hidden_oper = IsHideOper(acptr) ? 1 : 0;
-
-		list_del(&acptr->special_node);
-		RunHook(HOOKTYPE_LOCAL_OPER, acptr, 0, NULL, NULL);
-		remove_oper_privileges(acptr, 1);
-
-		if (!was_hidden_oper)
-			irccounts.operators--;
-		VERIFY_OPERCOUNT(acptr, "svso");
-
-	}
-
-	if (vhost && !valid_vhost(vhost))
-		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper: vhost contains illegal characters or is too long");
-
-	/* Prefix the oper block name with "remote:" if it hasn't already */
-	if (!strncmp(parv[2], "remote:", 7))
-		strlcpy(oper_account, parv[2], sizeof(oper_account));
-	else
-		snprintf(oper_account, sizeof(oper_account), "remote:%s", parv[2]);
-
-	/* These needs to be looked up... */
-	clientclass_c = find_class(clientclass); /* NULL is fine! */
-	if (modes)
-		modes_i = set_usermode(modes);
-
-	if (!make_oper(acptr, oper_account, operclass, clientclass_c, modes_i, snomask, vhost))
-		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper");
-}
diff --git a/src/modules/svspart.c b/src/modules/svspart.c
@@ -1,90 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/svspart.c
- *   (C) 2000-2001 Carsten V. Munk 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"
-
-CMD_FUNC(cmd_svspart);
-
-#define MSG_SVSPART       "SVSPART"
-
-ModuleHeader MOD_HEADER
-  = {
-	"svspart",	/* Name of module */
-	"5.0", /* Version */
-	"command /svspart", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSPART, cmd_svspart, 3, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-/* cmd_svspart() - Lamego - Wed Jul 21 20:04:48 1999
-   Copied off PTlink IRCd (C) PTlink coders team.
-  Modified for PART by Stskeeps
-	parv[1] - nick to make part
-	parv[2] - channel(s) to part
-	parv[3] - comment
-*/
-CMD_FUNC(cmd_svspart)
-{
-	Client *target;
-	const char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if (parc < 3 || !(target = find_user(parv[1], NULL))) 
-		return;
-
-	if (MyUser(target))
-	{
-		parv[0] = target->name;
-		parv[1] = parv[2];
-		parv[2] = comment;
-		parv[3] = NULL;
-		do_cmd(target, NULL, "PART", comment ? 3 : 2, parv);
-		/* NOTE: target may be killed now by spamfilter due to the part reason */
-	}
-	else
-	{
-		if (comment)
-			sendto_one(target, NULL, ":%s SVSPART %s %s :%s", client->name,
-			    parv[1], parv[2], parv[3]);
-		else
-			sendto_one(target, NULL, ":%s SVSPART %s %s", client->name,
-			    parv[1], parv[2]);
-	}
-}
diff --git a/src/modules/svssilence.c b/src/modules/svssilence.c
@@ -1,98 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/svssilence.c
- *   (C) 2003 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"
-
-CMD_FUNC(cmd_svssilence);
-
-ModuleHeader MOD_HEADER
-  = {
-	"svssilence",	/* Name of module */
-	"5.0", /* Version */
-	"command /svssilence", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "SVSSILENCE", cmd_svssilence, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-/* cmd_svssilence()
- * written by Syzop (copied a lot from cmd_silence),
- * suggested by <??>.
- * parv[1] - target nick
- * parv[2] - space delimited silence list (+Blah +Blih -Bluh etc)
- * SERVER DISTRIBUTION:
- * Since UnrealIRCd 5 it is directed to the target client (previously: broadcasted).
- */
-CMD_FUNC(cmd_svssilence)
-{
-	Client *target;
-	int mine;
-	char *p, *cp, c;
-	char request[BUFSIZE];
-	
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
-		return;
-	
-	if (!MyUser(target))
-	{
-		sendto_one(target, NULL, ":%s SVSSILENCE %s :%s", client->name, parv[1], parv[2]);
-		return;
-	}
-
-	/* It's for our client */
-	strlcpy(request, parv[2], sizeof(request));
-	for (p = strtok(request, " "); p; p = strtok(NULL, " "))
-	{
-		c = *p;
-		if ((c == '-') || (c == '+'))
-			p++;
-		else if (!(strchr(p, '@') || strchr(p, '.') || strchr(p, '!') || strchr(p, '*')))
-		{
-			/* "no such nick" */
-			continue;
-		}
-		else
-			c = '+';
-		cp = pretty_mask(p);
-		if ((c == '-' && !del_silence(target, cp)) ||
-		    (c != '-' && !add_silence(target, cp, 0)))
-		{
-			sendto_prefix_one(target, target, NULL, ":%s SILENCE %c%s", client->name, c, cp);
-		}
-	}
-}
diff --git a/src/modules/svssno.c b/src/modules/svssno.c
@@ -1,111 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/svssno.c
- *   (C) 2004 Dominick Meglio (codemastr) 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"
-
-CMD_FUNC(cmd_svssno);
-CMD_FUNC(cmd_svs2sno);
-
-#define MSG_SVSSNO 	"SVSSNO"	
-#define MSG_SVS2SNO 	"SVS2SNO"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"svssno",
-	"5.0",
-	"command /svssno", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSSNO, cmd_svssno, MAXPARA, CMD_USER|CMD_SERVER);
-	CommandAdd(modinfo->handle, MSG_SVS2SNO, cmd_svs2sno, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * do_svssno() 
- * parv[1] - username to change snomask for
- * parv[2] - snomasks to change
- * show_change determines whether to show the change to the user
- */
-void do_svssno(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], int show_change)
-{
-	const char *p;
-	Client *target;
-	int what = MODE_ADD, i;
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if (parc < 2)
-		return;
-
-	if (parv[1][0] == '#') 
-		return;
-
-	if (!(target = find_user(parv[1], NULL)))
-		return;
-
-	if (hunt_server(client, recv_mtags, show_change ? "SVS2SNO" : "SVSSNO", 1, parc, parv) != HUNTED_ISME)
-		return;
-
-	if (MyUser(target))
-	{
-		if (parc == 2)
-			set_snomask(target, NULL);
-		else
-			set_snomask(target, parv[2]);
-	}
-
-	if (show_change && target->user->snomask)
-	{
-		MessageTag *mtags = NULL;
-		new_message(client, recv_mtags, &mtags);
-		// TODO: sendnumeric has no mtags support :D
-		sendnumeric(target, RPL_SNOMASK, target->user->snomask);
-		safe_free_message_tags(mtags);
-	}
-}
-
-CMD_FUNC(cmd_svssno)
-{
-	do_svssno(client, recv_mtags, parc, parv, 0);
-}
-
-CMD_FUNC(cmd_svs2sno)
-{
-	do_svssno(client, recv_mtags, parc, parv, 1);
-}
diff --git a/src/modules/svswatch.c b/src/modules/svswatch.c
@@ -1,80 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/svswatch.c
- *   (C) 2003 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"
-
-CMD_FUNC(cmd_svswatch);
-
-/* Place includes here */
-#define MSG_SVSWATCH       "SVSWATCH"
-
-ModuleHeader MOD_HEADER
-  = {
-	"svswatch",	/* Name of module */
-	"5.0", /* Version */
-	"command /svswatch", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SVSWATCH, cmd_svswatch, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-	
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-/* cmd_svswatch() - written by Syzop, suggested by Griever.
- * parv[1] - target nick
- * parv[2] - parameters
- */
-CMD_FUNC(cmd_svswatch)
-{
-	Client *target;
-
-	if (!IsSvsCmdOk(client))
-		return;
-
-	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
-		return;
-
-	if (MyUser(target))
-	{
-		parv[0] = target->name;
-		parv[1] = parv[2];
-		parv[2] = NULL;
-		do_cmd(target, NULL, "WATCH", 2, parv);
-	}
-	else
-		sendto_one(target, NULL, ":%s SVSWATCH %s :%s", client->name, parv[1], parv[2]);
-}
diff --git a/src/modules/swhois.c b/src/modules/swhois.c
@@ -1,110 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/swhois.c
- *   (C) 2001 The UnrealIRCd Team
- *
- *   SWHOIS command
- *
- *   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_swhois);
-
-#define MSG_SWHOIS 	"SWHOIS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"swhois",
-	"5.0",
-	"command /swhois", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_SWHOIS, cmd_swhois, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-/** SWHOIS: add or delete additional whois titles to user.
- * Old syntax:
- * parv[1] = nickname
- * parv[2] = new swhois
- * New syntax (since July 2015, by Syzop):
- * parv[1] = nickname
- * parv[2] = + or -
- * parv[3] = added-by tag
- * parv[4] = priority
- * parv[5] = swhois
- */
-CMD_FUNC(cmd_swhois)
-{
-	Client *target;
-	char tag[HOSTLEN+1];
-	char swhois[SWHOISLEN+1];
-	int add;
-	int priority = 0;
-
-	*tag = *swhois = '\0';
-
-	if (parc < 3)
-		return;
-
-	target = find_user(parv[1], NULL);
-	if (!target)
-		return;
-
-	if ((parc > 5) && !BadPtr(parv[5]))
-	{
-		/* New syntax */
-		add = (*parv[2] == '+') ? 1 : 0;
-		strlcpy(tag, parv[3], sizeof(tag));
-		priority = atoi(parv[4]);
-		strlcpy(swhois, parv[5], sizeof(swhois));
-	} else {
-		/* Old syntax */
-		strlcpy(tag, client->name, sizeof(tag));
-		if (BadPtr(parv[2]))
-		{
-			/* Delete. Hmmmm. Let's just delete anything with that tag. */
-			strcpy(swhois, "*");
-			add = 0;
-		} else {
-			/* Add */
-			add = 1;
-			strlcpy(swhois, parv[2], sizeof(swhois));
-		}
-	}
-
-	if (add)
-		swhois_add(target, tag, priority, swhois, client, client);
-	else
-		swhois_delete(target, tag, swhois, client, client);
-}
diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c
@@ -1,323 +0,0 @@
-/* Target flood protection
- * (C)Copyright 2020 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- */
-   
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"targetfloodprot",
-	"5.0",
-	"Target flood protection (set::anti-flood::target-flood)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-#define TFP_PRIVMSG	0
-#define TFP_NOTICE	1
-#define TFP_TAGMSG	2
-#define TFP_MAX		3
-
-typedef struct TargetFlood TargetFlood;
-struct TargetFlood {
-	unsigned short cnt[TFP_MAX];
-	time_t t[TFP_MAX];
-};
-
-typedef struct TargetFloodConfig TargetFloodConfig;
-struct TargetFloodConfig {
-	int cnt[TFP_MAX];
-	int t[TFP_MAX];
-};
-
-/* Forward declarations */
-int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-void targetfloodprot_mdata_free(ModData *m);
-int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, 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;
-ModDataInfo *targetfloodprot_channel_md = NULL;
-TargetFloodConfig *channelcfg = NULL;
-TargetFloodConfig *privatecfg = NULL;
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, targetfloodprot_config_test);
-	return MOD_SUCCESS;
-}
-
-/** Allocate config and set default configuration */
-void targetfloodprot_defaults(void)
-{
-	channelcfg = safe_alloc(sizeof(TargetFloodConfig));
-	privatecfg = safe_alloc(sizeof(TargetFloodConfig));
-
-	/* set::anti-flood::target-flood::channel-privmsg */
-	channelcfg->cnt[TFP_PRIVMSG] = 45;
-	channelcfg->t[TFP_PRIVMSG] = 5;
-	/* set::anti-flood::target-flood::channel-notice */
-	channelcfg->cnt[TFP_NOTICE] = 15;
-	channelcfg->t[TFP_NOTICE] = 5;
-	/* set::anti-flood::target-flood::channel-tagmsg */
-	channelcfg->cnt[TFP_TAGMSG] = 15;
-	channelcfg->t[TFP_TAGMSG] = 5;
-
-	/* set::anti-flood::target-flood::private-privmsg */
-	privatecfg->cnt[TFP_PRIVMSG] = 30;
-	privatecfg->t[TFP_PRIVMSG] = 5;
-	/* set::anti-flood::target-flood::private-notice */
-	privatecfg->cnt[TFP_NOTICE] = 10;
-	privatecfg->t[TFP_NOTICE] = 5;
-	/* set::anti-flood::target-flood::private-tagmsg */
-	privatecfg->cnt[TFP_TAGMSG] = 10;
-	privatecfg->t[TFP_TAGMSG] = 5;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, targetfloodprot_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, targetfloodprot_can_send_to_channel);
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, targetfloodprot_can_send_to_user);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "targetfloodprot";
-	mreq.serialize = NULL;
-	mreq.unserialize = NULL;
-	mreq.free = targetfloodprot_mdata_free;
-	mreq.sync = 0;
-	mreq.type = MODDATATYPE_LOCAL_CLIENT;
-	targetfloodprot_client_md = ModDataAdd(modinfo->handle, mreq);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "targetfloodprot";
-	mreq.serialize = NULL;
-	mreq.unserialize = NULL;
-	mreq.free = targetfloodprot_mdata_free;
-	mreq.sync = 0;
-	mreq.type = MODDATATYPE_CHANNEL;
-	targetfloodprot_channel_md = ModDataAdd(modinfo->handle, mreq);
-
-	targetfloodprot_defaults();
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	safe_free(channelcfg);
-	safe_free(privatecfg);
-	return MOD_SUCCESS;
-}
-
-int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	if (type != CONFIG_SET_ANTI_FLOOD)
-		return 0;
-
-	/* We are only interrested in set::anti-flood::target-flood.. */
-	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		CheckNull(cep);
-
-		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->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->file->filename, cep->line_number,
-				             cep->name);
-				errors++;
-			}
-		} else
-		{
-			config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
-				cep->file->filename, cep->line_number, cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep, *cepp;
-
-	if (type != CONFIG_SET_ANTI_FLOOD)
-		return 0;
-
-	/* We are only interrested in set::anti-flood::target-flood.. */
-	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		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;
-}
-
-/** UnrealIRCd internals: free object. */
-void targetfloodprot_mdata_free(ModData *m)
-{
-	/* we don't have any members to free, so this is easy */
-	safe_free(m->ptr);
-}
-
-int sendtypetowhat(SendType sendtype)
-{
-	if (sendtype == SEND_TYPE_PRIVMSG)
-		return 0;
-	if (sendtype == SEND_TYPE_NOTICE)
-		return 1;
-	if (sendtype == SEND_TYPE_TAGMSG)
-		return 2;
-#ifdef DEBUGMODE
-	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, const char **msg, const char **errmsg, SendType sendtype)
-{
-	TargetFlood *flood;
-	static char errbuf[256];
-	int what;
-
-	/* This is redundant, right? */
-	if (!MyUser(client))
-		return HOOK_CONTINUE;
-
-	/* U-Lines, servers and IRCOps override */
-	if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
-		return HOOK_CONTINUE;
-
-	what = sendtypetowhat(sendtype);
-
-	if (moddata_channel(channel, targetfloodprot_channel_md).ptr == NULL)
-	{
-		/* Alloc a new entry if it doesn't exist yet */
-		moddata_channel(channel, targetfloodprot_channel_md).ptr = safe_alloc(sizeof(TargetFlood));
-	}
-
-	flood = (TargetFlood *)moddata_channel(channel, targetfloodprot_channel_md).ptr;
-
-	if ((TStime() - flood->t[what]) >= channelcfg->t[what])
-	{
-		/* Reset due to moving into a new time slot */
-		flood->t[what] = TStime();
-		flood->cnt[what] = 1;
-		return HOOK_CONTINUE; /* forget about it.. */
-	}
-
-	if (flood->cnt[what] >= channelcfg->cnt[what])
-	{
-		/* Flood detected */
-		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
-			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $channel",
-			   log_data_string("flood_type", "target-flood-channel"),
-			   log_data_channel("channel", channel));
-		snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
-		*errmsg = errbuf;
-		return HOOK_DENY;
-	}
-
-	flood->cnt[what]++;
-	return HOOK_CONTINUE;
-}
-
-int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
-{
-	TargetFlood *flood;
-	static char errbuf[256];
-	int what;
-
-	/* Check if it is our TARGET ('target'), so yeah
-	 * be aware that 'client' may be remote client in all the code that follows!
-	 */
-	if (!MyUser(target))
-		return HOOK_CONTINUE;
-
-	/* U-Lines, servers and IRCOps override */
-	if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
-		return HOOK_CONTINUE;
-
-	what = sendtypetowhat(sendtype);
-
-	if (moddata_local_client(target, targetfloodprot_client_md).ptr == NULL)
-	{
-		/* Alloc a new entry if it doesn't exist yet */
-		moddata_local_client(target, targetfloodprot_client_md).ptr = safe_alloc(sizeof(TargetFlood));
-	}
-
-	flood = (TargetFlood *)moddata_local_client(target, targetfloodprot_client_md).ptr;
-
-	if ((TStime() - flood->t[what]) >= privatecfg->t[what])
-	{
-		/* Reset due to moving into a new time slot */
-		flood->t[what] = TStime();
-		flood->cnt[what] = 1;
-		return HOOK_CONTINUE; /* forget about it.. */
-	}
-
-	if (flood->cnt[what] >= privatecfg->cnt[what])
-	{
-		/* Flood detected */
-		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
-			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $target",
-			   log_data_string("flood_type", "target-flood-user"),
-			   log_data_client("target", target));
-		snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
-		*errmsg = errbuf;
-		return HOOK_DENY;
-	}
-
-	flood->cnt[what]++;
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/third/Makefile.in b/src/modules/third/Makefile.in
@@ -1,50 +0,0 @@
-#************************************************************************
-#*   IRC - Internet Relay Chat, src/modules/chanmodes/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/dns.h \
-	../../include/resource.h ../../include/setup.h \
-	../../include/struct.h ../../include/sys.h \
-	../../include/types.h \
-	../../include/version.h ../../include/whowas.h
-
-MODULEFLAGS=@MODULEFLAGS@
-RM=@RM@
-
-.SUFFIXES:
-.SUFFIXES: .c .h .so
-
-all: build
-
-build:
-	../../buildmod $(MAKE)
-
-custommodule: $(MODULEFILE).c
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o $(MODULEFILE).so $(MODULEFILE).c $(EXLIBS)
-
-clean:
-	$(RM) -f *.o *.so *~ core
diff --git a/src/modules/time.c b/src/modules/time.c
@@ -1,66 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/time.c
- *   (C) 2004 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"
-
-CMD_FUNC(cmd_time);
-
-/* Place includes here */
-#define MSG_TIME	"TIME"
-
-ModuleHeader MOD_HEADER
-  = {
-	"time",	/* Name of module */
-	"5.0", /* Version */
-	"command /time", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_TIME, cmd_time, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_time
-**	parv[1] = servername
-*/
-CMD_FUNC(cmd_time)
-{
-	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
@@ -1,5437 +0,0 @@
-/*
- * Unreal Internet Relay Chat Daemon, src/modules/tkl.c
- * TKL Commands: server bans, spamfilters, etc.
- * (C) 1999-2019 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
-= {
-	"tkl",
-	"5.0",
-	"Server ban commands such as /GLINE, /SPAMFILTER, etc.",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Forward declarations */
-int tkl_config_test_spamfilter(ConfigFile *, ConfigEntry *, int, int *);
-int tkl_config_run_spamfilter(ConfigFile *, ConfigEntry *, int);
-int tkl_config_test_ban(ConfigFile *, ConfigEntry *, int, int *);
-int tkl_config_run_ban(ConfigFile *, ConfigEntry *, int);
-int tkl_config_test_except(ConfigFile *, ConfigEntry *, int, int *);
-int tkl_config_run_except(ConfigFile *, ConfigEntry *, int);
-int tkl_config_test_set(ConfigFile *, ConfigEntry *, int, int *);
-int tkl_config_run_set(ConfigFile *, ConfigEntry *, int);
-int tkl_ip_change(Client *client, const char *oldip);
-int tkl_accept(Client *client);
-void check_set_spamfilter_utf8_setting_changed(void);
-CMD_FUNC(cmd_gline);
-CMD_FUNC(cmd_shun);
-CMD_FUNC(cmd_tempshun);
-CMD_FUNC(cmd_gzline);
-CMD_FUNC(cmd_kline);
-CMD_FUNC(cmd_zline);
-CMD_FUNC(cmd_spamfilter);
-CMD_FUNC(cmd_eline);
-void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type);
-int _tkl_hash(unsigned int c);
-char _tkl_typetochar(int type);
-int _tkl_chartotype(char c);
-char _tkl_configtypetochar(const char *name);
-int tkl_banexception_chartotype(char c);
-char *_tkl_type_string(TKL *tk);
-char *_tkl_type_config_string(TKL *tk);
-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);
-TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match,
-                           char *reason, char *set_by,
-                           time_t expire_at, time_t set_at, int soft, char *bantypes, int flags);
-TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
-                          time_t expire_at, time_t set_at, int flags);
-TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by,
-                             time_t expire_at, time_t set_at,
-                             time_t spamf_tkl_duration, char *spamf_tkl_reason,
-                             int flags);
-void _sendnotice_tkl_del(char *removed_by, TKL *tkl);
-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);
-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, 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, 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(const char *rmask, Client *client, int options);
-int _unreal_match_iplist(Client *client, NameList *l);
-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);
-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, BanAction action, unsigned short target);
-int _find_tkl_exception(int ban_type, Client *client);
-int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
-int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
-static void add_default_exempts(void);
-int parse_extended_server_ban(const char *mask_in, Client *client, char **error, int skip_checking, char *buf1, size_t buf1len, char *buf2, size_t buf2len);
-void _tkl_added(Client *client, TKL *tkl);
-
-/* Externals (only for us :D) */
-extern int MODVAR spamf_ugly_vchanoverride;
-
-typedef struct TKLTypeTable TKLTypeTable;
-struct TKLTypeTable
-{
-	char *config_name;        /**< The name as used in the configuration file */
-	char letter;              /**< The letter ised in the TKL S2S command */
-	int type;                 /**< TKL_xxx, optionally OR'ed with TKL_GLOBAL */
-	char *log_name;           /**< Used for logging and server notices */
-	unsigned tkltype:1;       /**< Is a type available in cmd_tkl() and friends */
-	unsigned exceptiontype:1; /**< Is a type available for exceptions */
-	unsigned needip:1;        /**< When using this exempt option, only IP addresses are permitted (processed before DNS/ident lookups etc) */
-};
-
-/** This table which defines all TKL types and TKL exception types.
- * If you wonder about the messy order: gline/kline/gzline/zline
- * are at the top for performance reasons. They make up 99% of the TKLs.
- *
- * IMPORTANT IF YOU ARE ADDING A NEW TYPE TO THIS TABLE:
- * - also update eline_syntax()
- * - update help.conf (HELPOP ELINE)
- * - more?
- */
-TKLTypeTable tkl_types[] = {
-	/* <config name> <letter> <TKL_xxx type>               <logging name> <tkl option?> <exempt option?> <need ip address?> */
-	{ "gline",                'G', TKL_KILL       | TKL_GLOBAL, "G-Line",               1, 1, 0 },
-	{ "kline",                'k', TKL_KILL,                    "K-Line",               1, 1, 0 },
-	{ "gzline",               'Z', TKL_ZAP        | TKL_GLOBAL, "Global Z-Line",        1, 1, 1 },
-	{ "zline",                'z', TKL_ZAP,                     "Z-Line",               1, 1, 1 },
-	{ "spamfilter",           'F', TKL_SPAMF      | TKL_GLOBAL, "Spamfilter",           1, 1, 0 },
-	{ "qline",                'Q', TKL_NAME       | TKL_GLOBAL, "Q-Line",               1, 1, 0 },
-	{ "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-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 },
-	{ "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD,    "Handshake data flood", 0, 1, 1 },
-	{ "antirandom",           'r', TKL_ANTIRANDOM,              "Antirandom",           0, 1, 0 },
-	{ "antimixedutf8",        '8', TKL_ANTIMIXEDUTF8,           "Antimixedutf8",        0, 1, 0 },
-	{ "ban-version",          'v', TKL_BAN_VERSION,             "Ban Version",          0, 1, 0 },
-	{ NULL,                   '\0', 0,                          NULL,                   0, 0, 0 },
-};
-#define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version"
-
-/* Global variables for this module */
-
-int max_stats_matches = 1000;
-int mtag_spamfilters_present = 0; /**< Are any spamfilters with type SPAMF_MTAG present? */
-long previous_spamfilter_utf8 = 0;
-static int firstboot = 0;
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_spamfilter);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_ban);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_except);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_set);
-	EfunctionAdd(modinfo->handle, EFUNC_TKL_HASH, _tkl_hash);
-#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));
-	EfunctionAdd(modinfo->handle, EFUNC_TKL_CONFIGTYPETOCHAR, TO_INTFUNC(_tkl_configtypetochar));
-#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));
-	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SPAMFILTER, TO_PVOIDFUNC(_tkl_add_spamfilter));
-	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_DEL_LINE, _tkl_del_line);
-	EfunctionAddVoid(modinfo->handle, EFUNC_FREE_TKL, _free_tkl);
-	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, _tkl_check_local_remove_shun);
-	EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
-	EfunctionAdd(modinfo->handle, EFUNC_FIND_SHUN, _find_shun);
-	EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
-	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
-	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKLINE_MATCH_ZAP, TO_PVOIDFUNC(_find_tkline_match_zap));
-	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SERVERBAN, TO_PVOIDFUNC(_find_tkl_serverban));
-	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_BANEXCEPTION, TO_PVOIDFUNC(_find_tkl_banexception));
-	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_NAMEBAN, TO_PVOIDFUNC(_find_tkl_nameban));
-	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SPAMFILTER, TO_PVOIDFUNC(_find_tkl_spamfilter));
-	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_STATS, _tkl_stats);
-	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_SYNCH, _tkl_sync);
-	EfunctionAddVoid(modinfo->handle, EFUNC_CMD_TKL, _cmd_tkl);
-	EfunctionAdd(modinfo->handle, EFUNC_PLACE_HOST_BAN, _place_host_ban);
-	EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER, _match_spamfilter);
-	EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER_MTAGS, _match_spamfilter_mtags);
-	EfunctionAdd(modinfo->handle, EFUNC_JOIN_VIRUSCHAN, _join_viruschan);
-	EfunctionAddVoid(modinfo->handle, EFUNC_SPAMFILTER_BUILD_USER_STRING, _spamfilter_build_user_string);
-	EfunctionAdd(modinfo->handle, EFUNC_MATCH_USER, _match_user);
-	EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH, _tkl_ip_hash);
-	EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH_TYPE, _tkl_ip_hash_type);
-	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);
-	EfunctionAdd(modinfo->handle, EFUNC_UNREAL_MATCH_IPLIST, _unreal_match_iplist);
-	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_PARSE_MASK, TO_INTFUNC(_server_ban_parse_mask));
-	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_EXCEPTION_PARSE_MASK, TO_INTFUNC(_server_ban_exception_parse_mask));
-	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_ADDED, _tkl_added);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	if (loop.booted == 0)
-		firstboot = 1;
-	LoadPersistentLong(modinfo, previous_spamfilter_utf8);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_spamfilter);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_set);
-	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 2000000000, tkl_ip_change);
-	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -1000, tkl_accept);
-	CommandAdd(modinfo->handle, "GLINE", cmd_gline, 3, CMD_OPER);
-	CommandAdd(modinfo->handle, "SHUN", cmd_shun, 3, CMD_OPER);
-	CommandAdd(modinfo->handle, "TEMPSHUN", cmd_tempshun, 2, CMD_OPER);
-	CommandAdd(modinfo->handle, "ZLINE", cmd_zline, 3, CMD_OPER);
-	CommandAdd(modinfo->handle, "KLINE", cmd_kline, 3, CMD_OPER);
-	CommandAdd(modinfo->handle, "GZLINE", cmd_gzline, 3, CMD_OPER);
-	CommandAdd(modinfo->handle, "SPAMFILTER", cmd_spamfilter, 7, CMD_OPER);
-	CommandAdd(modinfo->handle, "ELINE", cmd_eline, 4, CMD_OPER);
-	CommandAdd(modinfo->handle, "TKL", _cmd_tkl, MAXPARA, CMD_OPER|CMD_SERVER);
-	add_default_exempts();
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	check_mtag_spamfilters_present();
-	check_set_spamfilter_utf8_setting_changed();
-	EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	SavePersistentLong(modinfo, previous_spamfilter_utf8);
-	return MOD_SUCCESS;
-}
-
-/** Test a spamfilter { } block in the configuration file */
-int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep, *cepp;
-	int errors = 0;
-	char *match = NULL, *reason = NULL;
-	char has_target = 0, has_match = 0, has_action = 0, has_reason = 0, has_bantime = 0, has_match_type = 0;
-	int match_type = 0;
-
-	/* We are only interested in spamfilter { } blocks */
-	if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "target"))
-		{
-			if (has_target)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "spamfilter::target");
-				continue;
-			}
-			has_target = 1;
-			if (cep->value)
-			{
-				if (!spamfilter_getconftargets(cep->value))
-				{
-					config_error("%s:%i: unknown spamfiler target type '%s'",
-						cep->file->filename, cep->line_number, cep->value);
-					errors++;
-				}
-			}
-			else if (cep->items)
-			{
-				for (cepp = cep->items; cepp; cepp = cepp->next)
-				{
-					if (!spamfilter_getconftargets(cepp->name))
-					{
-						config_error("%s:%i: unknown spamfiler target type '%s'",
-							cepp->file->filename,
-							cepp->line_number, cepp->name);
-						errors++;
-					}
-				}
-			}
-			else
-			{
-				config_error_empty(cep->file->filename,
-					cep->line_number, "spamfilter", cep->name);
-				errors++;
-			}
-			continue;
-		}
-		if (!cep->value)
-		{
-			config_error_empty(cep->file->filename, cep->line_number,
-				"spamfilter", cep->name);
-			errors++;
-			continue;
-		}
-		if (!strcmp(cep->name, "reason"))
-		{
-			if (has_reason)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "spamfilter::reason");
-				continue;
-			}
-			has_reason = 1;
-			reason = cep->value;
-		}
-		else if (!strcmp(cep->name, "match"))
-		{
-			if (has_match)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "spamfilter::match");
-				continue;
-			}
-			has_match = 1;
-			match = cep->value;
-		}
-		else if (!strcmp(cep->name, "action"))
-		{
-			if (has_action)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "spamfilter::action");
-				continue;
-			}
-			has_action = 1;
-			if (!banact_stringtoval(cep->value))
-			{
-				config_error("%s:%i: spamfilter::action has unknown action type '%s'",
-					cep->file->filename, cep->line_number, cep->value);
-				errors++;
-			}
-		}
-		else if (!strcmp(cep->name, "ban-time"))
-		{
-			if (has_bantime)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "spamfilter::ban-time");
-				continue;
-			}
-			has_bantime = 1;
-		}
-		else if (!strcmp(cep->name, "match-type"))
-		{
-			if (has_match_type)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "spamfilter::match-type");
-				continue;
-			}
-			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->file->filename, ce->line_number);
-				errors++;
-				*errs = errors;
-				return -1; /* return now, otherwise there will be issues */
-			}
-			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->file->filename, cep->line_number,
-				             cep->value);
-				errors++;
-				continue;
-			}
-			has_match_type = 1;
-		}
-		else
-		{
-			config_error_unknown(cep->file->filename, cep->line_number,
-				"spamfilter", cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	if (match && match_type)
-	{
-		Match *m;
-		char *err;
-
-		m = unreal_create_match(match_type, match, &err);
-		if (!m)
-		{
-			config_error("%s:%i: spamfilter::match contains an invalid regex: %s",
-				ce->file->filename,
-				ce->line_number,
-				err);
-			errors++;
-		} else
-		{
-			unreal_delete_match(m);
-		}
-	}
-
-	if (!has_match)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"spamfilter::match");
-		errors++;
-	}
-	if (!has_target)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"spamfilter::target");
-		errors++;
-	}
-	if (!has_action)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"spamfilter::action");
-		errors++;
-	}
-	if (match && reason && (strlen(match) + strlen(reason) > 505))
-	{
-		config_error("%s:%i: spamfilter block problem: match + reason field are together over 505 bytes, "
-		             "please choose a shorter regex or reason",
-		             ce->file->filename, ce->line_number);
-		errors++;
-	}
-	if (!has_match_type)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"spamfilter::match-type");
-		errors++;
-	}
-
-	if (match && !strcmp(match, "^LOL! //echo -a \\$\\(\\$decode\\(.+,m\\),[0-9]\\)$"))
-	{
-		config_warn("*** IMPORTANT ***");
-		config_warn("You have old examples in your spamfilter.conf. "
-		             "We suggest you to edit this file and replace the examples.");
-		config_warn("Please read https://www.unrealircd.org/docs/FAQ#old-spamfilter-conf !!!");
-		config_warn("*****************");
-	}
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/** Process a spamfilter { } block in the configuration file */
-int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-	ConfigEntry *cepp;
-	char *word = NULL;
-	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->name, "spamfilter"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "match"))
-		{
-			word = cep->value;
-		}
-		else if (!strcmp(cep->name, "target"))
-		{
-			if (cep->value)
-				target = spamfilter_getconftargets(cep->value);
-			else
-			{
-				for (cepp = cep->items; cepp; cepp = cepp->next)
-					target |= spamfilter_getconftargets(cepp->name);
-			}
-		}
-		else if (!strcmp(cep->name, "action"))
-		{
-			action = banact_stringtoval(cep->value);
-		}
-		else if (!strcmp(cep->name, "reason"))
-		{
-			banreason = cep->value;
-		}
-		else if (!strcmp(cep->name, "ban-time"))
-		{
-			bantime = config_checkval(cep->value, CFG_TIME);
-		}
-		else if (!strcmp(cep->name, "match-type"))
-		{
-			match_type = unreal_match_method_strtoval(cep->value);
-		}
-	}
-
-	m = unreal_create_match(match_type, word, NULL);
-	tkl_add_spamfilter(TKL_SPAMF,
-	                    target,
-	                    action,
-	                    m,
-	                    "-config-",
-	                    0,
-	                    TStime(),
-	                    bantime,
-	                    banreason,
-	                    TKL_FLAG_CONFIG);
-	return 1;
-}
-
-/** Test a ban { } block in the configuration file */
-int tkl_config_test_ban(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep;
-	int errors = 0;
-	char has_mask = 0, has_reason = 0;
-
-	/* We are only interested in ban { } blocks */
-	if (type != CONFIG_BAN)
-		return 0;
-
-	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
-	    strcmp(ce->value, "ip"))
-	{
-		return 0;
-	}
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (config_is_blankorempty(cep, "ban"))
-		{
-			errors++;
-			continue;
-		}
-		if (!strcmp(cep->name, "mask"))
-		{
-			if (has_mask)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "ban::mask");
-				continue;
-			}
-			has_mask = 1;
-		}
-		else if (!strcmp(cep->name, "reason"))
-		{
-			if (has_reason)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "ban::reason");
-				continue;
-			}
-			has_reason = 1;
-		}
-		else
-		{
-			config_error("%s:%i: unknown directive ban %s::%s",
-				cep->file->filename, cep->line_number,
-				ce->value,
-				cep->name);
-			errors++;
-		}
-	}
-
-	if (!has_mask)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"ban::mask");
-		errors++;
-	}
-
-	if (!has_reason)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"ban::reason");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/** Process a ban { } block in the configuration file */
-int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
-{
-	ConfigEntry *cep;
-	char *usermask = NULL;
-	char *hostmask = NULL;
-	char *reason = NULL;
-	int tkltype;
-
-	/* We are only interested in ban { } blocks */
-	if (configtype != CONFIG_BAN)
-		return 0;
-
-	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
-	    strcmp(ce->value, "ip"))
-	{
-		return 0;
-	}
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "mask"))
-		{
-			if (is_extended_server_ban(cep->value))
-			{
-				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: Could not add extended server ban '%s': %s",
-						cep->file->filename, cep->line_number, cep->value, err);
-					goto tcrb_end;
-				}
-				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)
-				{
-					*p++ = '\0';
-					safe_strdup(usermask, buf);
-					safe_strdup(hostmask, p);
-				} else {
-					safe_strdup(hostmask, cep->value);
-				}
-			}
-		} else
-		if (!strcmp(cep->name, "reason"))
-		{
-			safe_strdup(reason, cep->value);
-		}
-	}
-
-	if (!usermask)
-		safe_strdup(usermask, "*");
-
-	if (!reason)
-		safe_strdup(reason, "-");
-
-	if (!strcmp(ce->value, "nick"))
-		tkltype = TKL_NAME;
-	else if (!strcmp(ce->value, "user"))
-		tkltype = TKL_KILL;
-	else if (!strcmp(ce->value, "ip"))
-		tkltype = TKL_ZAP;
-	else
-		abort(); /* impossible */
-
-	if (TKLIsNameBanType(tkltype))
-		tkl_add_nameban(tkltype, hostmask, 0, reason, "-config-", 0, TStime(), TKL_FLAG_CONFIG);
-	else if (TKLIsServerBanType(tkltype))
-		tkl_add_serverban(tkltype, usermask, hostmask, reason, "-config-", 0, TStime(), 0, TKL_FLAG_CONFIG);
-
-tcrb_end:
-	safe_free(usermask);
-	safe_free(hostmask);
-	safe_free(reason);
-	return 1;
-}
-
-int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
-{
-	ConfigEntry *cep, *cepp;
-	int errors = 0;
-	char has_mask = 0, has_match = 0;
-
-	/* We are only interested in except { } blocks */
-	if (configtype != CONFIG_EXCEPT)
-		return 0;
-
-	/* These are the types that we handle */
-	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->value, "tkl"))
-	{
-		config_error("%s:%d: except tkl { } has been renamed to except ban { }",
-			ce->file->filename, ce->line_number);
-		config_status("Please rename your block in the configuration file.");
-		*errs = 1;
-		return -1;
-	}
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "mask"))
-		{
-			if (cep->value || cep->items)
-			{
-				has_mask = 1;
-				test_match_block(cf, cep, &errors);
-			}
-		} else
-		if (!strcmp(cep->name, "match"))
-		{
-			if (cep->value || cep->items)
-			{
-				has_match = 1;
-				test_match_block(cf, cep, &errors);
-			}
-		} else
-		if (!strcmp(cep->name, "type"))
-		{
-			if (cep->items)
-			{
-				/* type { x; y; z; }; */
-				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->file->filename, cepp->line_number, cepp->name,
-							ALL_VALID_EXCEPTION_TYPES);
-						errors++;
-					}
-			} else
-			if (cep->value)
-			{
-				/* type x; */
-				if (!tkl_banexception_configname_to_chars(cep->value))
-				{
-					config_error("%s:%d: except ban::type '%s' unknown. Must be one of: %s",
-						cep->file->filename, cep->line_number, cep->value,
-						ALL_VALID_EXCEPTION_TYPES);
-					errors++;
-				}
-			}
-		} else {
-			config_error_unknown(cep->file->filename,
-				cep->line_number, "except", cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	if (!has_mask && !has_match)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"except ban::match");
-		errors++;
-	}
-	if (has_mask && has_match)
-	{
-		config_error("%s:%d: You cannot have both ::mask and ::match. "
-		             "You should only use except::match.",
-		             ce->file->filename, ce->line_number);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
-{
-	ConfigEntry *cep, *cepp;
-	SecurityGroup *match = NULL;
-	char bantypes[64];
-
-	/* We are only interested in except { } blocks */
-	if (configtype != CONFIG_EXCEPT)
-		return 0;
-
-	/* These are the types that we handle */
-	if (strcmp(ce->value, "ban") && strcmp(ce->value, "throttle") &&
-	    strcmp(ce->value, "blacklist") &&
-	    strcmp(ce->value, "spamfilter"))
-	{
-		return 0;
-	}
-
-	*bantypes = '\0';
-
-	/* First configure all the types */
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "type"))
-		{
-			if (cep->items)
-			{
-				/* type { x; y; z; }; */
-				for (cepp = cep->items; cepp; cepp = cepp->next)
-				{
-					char *str = tkl_banexception_configname_to_chars(cepp->name);
-					strlcat(bantypes, str, sizeof(bantypes));
-				}
-			} else
-			if (cep->value)
-			{
-				/* type x; */
-				char *str = tkl_banexception_configname_to_chars(cep->value);
-				strlcat(bantypes, str, sizeof(bantypes));
-			}
-		} else
-		if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
-		{
-			conf_match_block(cf, cep, &match);
-		}
-	}
-
-	if (!*bantypes)
-	{
-		/* Default setting if no 'type' is specified: */
-		if (!strcmp(ce->value, "ban"))
-			strlcpy(bantypes, "kGzZs", sizeof(bantypes));
-		else if (!strcmp(ce->value, "throttle"))
-			strlcpy(bantypes, "c", sizeof(bantypes));
-		else if (!strcmp(ce->value, "blacklist"))
-			strlcpy(bantypes, "b", sizeof(bantypes));
-		else if (!strcmp(ce->value, "spamfilter"))
-			strlcpy(bantypes, "f", sizeof(bantypes));
-		else
-			abort(); /* someone can't code */
-	}
-
-	tkl_add_banexception(TKL_EXCEPTION, "-", "-", match, "Added in configuration file",
-	                     "-config-", 0, TStime(), 0, bantypes, TKL_FLAG_CONFIG);
-
-	return 1;
-}
-
-int tkl_config_test_set(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
-{
-	int errors = 0;
-
-	/* We are only interested in set { } blocks */
-	if (configtype != CONFIG_SET)
-		return 0;
-
-	if (!strcmp(ce->name, "max-stats-matches"))
-	{
-		if (!ce->value)
-		{
-			config_error("%s:%i: set::max-stats-matches: no value specified",
-				ce->file->filename, ce->line_number);
-			errors++;
-		}
-		// allow any other value, including 0 and negative.
-		*errs = errors;
-		return errors ? -1 : 1;
-	}
-	return 0;
-}
-
-int tkl_config_run_set(ConfigFile *cf, ConfigEntry *ce, int configtype)
-{
-	/* We are only interested in set { } blocks */
-	if (configtype != CONFIG_SET)
-		return 0;
-
-	if (!strcmp(ce->name, "max-stats-matches"))
-	{
-		max_stats_matches = atoi(ce->value);
-		return 1;
-	}
-
-	return 0;
-}
-
-/** Recompile all spamfilters due to set::spamfilter::utf8 setting change */
-void recompile_spamfilters(void)
-{
-	TKL *tkl;
-	Match *m;
-	char *err;
-	int converted = 0;
-	int index;
-
-	index = tkl_hash('F');
-	for (tkl = tklines[index]; tkl; tkl = tkl->next)
-	{
-		if (!TKLIsSpamfilter(tkl) || (tkl->ptr.spamfilter->match->type != MATCH_PCRE_REGEX))
-			continue;
-		m = unreal_create_match(MATCH_PCRE_REGEX, tkl->ptr.spamfilter->match->str, &err);
-		if (!m)
-		{
-			unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_COMPILE_ERROR", NULL,
-			           "Spamfilter no longer compiles upon utf8 change, error: $error. "
-			           "Spamfilter '$tkl' ($tkl.reason). "
-			           "Spamfilter not transformed to/from utf8.",
-			           log_data_tkl("tkl", tkl),
-			           log_data_string("error", err ? err : "Unknown"));
-			continue;
-		}
-
-		unreal_delete_match(tkl->ptr.spamfilter->match); /* unset old one */
-		tkl->ptr.spamfilter->match = m; /* set new one */
-		converted++;
-	}
-	unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_UTF8_CONVERTED", NULL,
-	           "Spamfilter: Recompiled $count spamfilters due to set::spamfilter::utf8 change.",
-	           log_data_integer("count", converted));
-}
-
-void check_set_spamfilter_utf8_setting_changed(void)
-{
-	if (firstboot)
-	{
-		/* First boot, not a rehash */
-		previous_spamfilter_utf8 = iConf.spamfilter_utf8;
-		return;
-	}
-
-	if (previous_spamfilter_utf8 != iConf.spamfilter_utf8)
-		recompile_spamfilters();
-
-	previous_spamfilter_utf8 = iConf.spamfilter_utf8;
-}
-
-/** Return unique spamfilter id for TKL */
-char *spamfilter_id(TKL *tk)
-{
-	static char buf[128];
-
-	snprintf(buf, sizeof(buf), "%p", (void *)tk);
-	return buf;
-}
-
-int tkl_ip_change(Client *client, const char *oldip)
-{
-	TKL *tkl;
-	if ((tkl = find_tkline_match_zap(client)))
-		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
-	return 0;
-}
-
-int tkl_accept(Client *client)
-{
-	TKL *tkl;
-	if ((tkl = find_tkline_match_zap(client)))
-	{
-		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
-		return HOOK_DENY;
-	}
-	return 0;
-}
-
-/** GLINE - Global kline.
-** Syntax: /gline [+|-]u@h mask time :reason
-**
-** parv[1] = [+|-]u@h mask
-** parv[2] = for how long
-** parv[3] = reason
-*/
-CMD_FUNC(cmd_gline)
-{
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:gline",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc == 1)
-	{
-		const char *parv[3];
-		parv[0] = NULL;
-		parv[1] = "gline";
-		parv[2] = NULL;
-		do_cmd(client, recv_mtags, "STATS", 2, parv);
-	} else
-	{
-		cmd_tkl_line(client, parc, parv, "G");
-	}
-}
-
-/** GZLINE - Global zline.
- */
-CMD_FUNC(cmd_gzline)
-{
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:zline:global",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc == 1)
-	{
-		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;
-		do_cmd(client, recv_mtags, "STATS", 2, parv);
-	} else {
-		cmd_tkl_line(client, parc, parv, "Z");
-	}
-}
-
-/** SHUN - Shun a user so it can no longer execute any meaningful commands.
- */
-CMD_FUNC(cmd_shun)
-{
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:shun",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc == 1)
-	{
-		const char *parv[3];
-		parv[0] = NULL;
-		parv[1] = "shun";
-		parv[2] = NULL;
-		do_cmd(client, recv_mtags, "STATS", 2, parv);
-	} else {
-		cmd_tkl_line(client, parc, parv, "s");
-	}
-}
-
-/** TEMPSHUN - Temporarily shun a user so it can no longer execute
- *  any meaningful commands - until the user disconnects (session only).
- */
-CMD_FUNC(cmd_tempshun)
-{
-	Client *target;
-	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)))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "TEMPSHUN");
-		return;
-	}
-	if (parv[1][0] == '+')
-		name = parv[1]+1;
-	else if (parv[1][0] == '-')
-	{
-		name = parv[1]+1;
-		remove = 1;
-	} else
-		name = parv[1];
-
-	target = find_user(name, NULL);
-	if (!target)
-	{
-		sendnumeric(client, ERR_NOSUCHNICK, name);
-		return;
-	}
-	if (!MyUser(target))
-	{
-		sendto_one(target, NULL, ":%s TEMPSHUN %c%s :%s",
-		           client->id, remove ? '-' : '+', target->id, comment);
-	} else {
-		char buf[1024];
-		if (!remove)
-		{
-			if (IsShunned(target))
-			{
-				sendnotice(client, "User '%s' already shunned", target->name);
-			} else if (ValidatePermissionsForPath("immune:server-ban:shun",target,NULL,NULL,NULL))
-			{
-				sendnotice(client, "You cannot tempshun '%s' because (s)he is an oper with 'immune:server-ban:shun' privilege", target->name);
-			} else
-			{
-				SetShunned(target);
-				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))
-			{
-				sendnotice(client, "User '%s' is not shunned", target->name);
-			} else {
-				ClearShunned(target);
-				unreal_log(ULOG_INFO, "tkl", "TKL_DEL_TEMPSHUN", client,
-					   "Temporary shun removed from user $target.details [by: $client]",
-					   log_data_client("target", target));
-			}
-		}
-	}
-}
-
-/** KLINE - Kill line (ban user from local server)
- */
-CMD_FUNC(cmd_kline)
-{
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:kline:local:add",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc == 1)
-	{
-		const char *parv[3];
-		parv[0] = NULL;
-		parv[1] = "kline";
-		parv[2] = NULL;
-		do_cmd(client, recv_mtags, "STATS", 2, parv);
-		return;
-	}
-
-	if (!ValidatePermissionsForPath("server-ban:kline:remove",client,NULL,NULL,NULL) && *parv[1] == '-')
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	cmd_tkl_line(client, parc, parv, "k");
-}
-
-/** Generate stats for '/GLINE -stats' and such */
-void tkl_general_stats(Client *client)
-{
-	int index, index2;
-	TKL *tkl;
-	int total = 0;
-	int subtotal;
-
-	/* First, hashed entries.. */
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			subtotal = 0;
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-				subtotal++;
-			if (subtotal > 0)
-				sendnotice(client, "Slot %d:%d has %d item(s)", index, index2, subtotal);
-			total += subtotal;
-		}
-	}
-	sendnotice(client, "Hashed TKL items: %d item(s)", total);
-
-	/* Now normal entries.. */
-	subtotal = 0;
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-			subtotal++;
-	}
-	sendnotice(client, "Standard TKL items: %d item(s)", subtotal);
-	total += subtotal;
-	sendnotice(client, "Grand total TKL items: %d item(s)", total);
-}
-
-/** 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 TLS handshake, etc.)
- */
-CMD_FUNC(cmd_zline)
-{
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:zline:local:add",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc == 1)
-	{
-		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;
-		do_cmd(client, recv_mtags, "STATS", 2, parv);
-		return;
-	}
-
-	if ((parc > 1) && !BadPtr(parv[1]) && !strcasecmp(parv[1], "-stats"))
-	{
-		/* Print some statistics */
-		tkl_general_stats(client);
-		return;
-	}
-
-	cmd_tkl_line(client, parc, parv, "z");
-}
-
-/** Check if a ban is placed with a too broad mask (like '*') */
-int ban_too_broad(char *usermask, char *hostmask)
-{
-	char *p;
-	int cnt = 0;
-
-	/* Scary config setting. Hmmm. */
-	if (ALLOW_INSANE_BANS)
-		return 0;
-
-	/* Allow things like clone@*, dsfsf@*, etc.. */
-	if (!strchr(usermask, '*') && !strchr(usermask, '?'))
-		return 0;
-
-	/* If it's a CIDR, then check /mask first.. */
-	p = strchr(hostmask, '/');
-	if (p)
-	{
-		int cidrlen = atoi(p+1);
-		if (strchr(hostmask, ':'))
-		{
-			if (cidrlen < 48)
-				return 1; /* too broad IPv6 CIDR mask */
-		} else {
-			if (cidrlen < 16)
-				return 1; /* too broad IPv4 CIDR mask */
-		}
-	}
-
-	/* Must at least contain 4 non-wildcard/non-dot characters.
-	 * This will deal with non-CIDR and hosts, but any correct
-	 * CIDR mask will also pass this test (which is fine).
-	 */
-	for (p = hostmask; *p; p++)
-		if (*p != '*' && *p != '.' && *p != '?' && *p != ':')
-			cnt++;
-
-	if (cnt >= 4)
-		return 0;
-
-	return 1;
-}
-
-/** Ugly function, only meant to be called by cmd_tkl_line() */
-static int xline_exists(char *type, char *usermask, char *hostmask)
-{
-	char *umask = usermask;
-	int softban = 0;
-	int tpe = tkl_chartotype(type[0]);
-
-	if (*umask == '%')
-	{
-		umask++;
-		softban = 1;
-	}
-
-	return find_tkl_serverban(tpe, umask, hostmask, softban) ? 1 : 0;
-}
-
-/** 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;
-}
-
-/** Parse a server ban request such as 'blah@blah.com' or '~account:EvilUser'
- * @param client	Client requesting the operation (can be NULL)
- * @param add		Set to 1 for add ban, 0 for remove ban
- * @param type		TKL type (character), see 2nd column of tkl_types[], eg 'G' for gline.
- * @param str		The input string
- * @param usermask_out	Will be set to the TKL usermask
- * @param hostmask_out	Will be set to the TKL hostmask
- * @param soft		Will be set to 1 if it's a softban, otherwise 0
- * @param error		On failure, this will contain the error string
- * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
- * @retval 0	Failed: error is set appropriately
- */
-int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
-{
-	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE];
-	char *hostmask = NULL, *usermask = NULL;
-	char *mask, *p;
-
-	/* Set defaults */
-	*usermask_out = *hostmask_out = NULL;
-	*soft = 0;
-
-	strlcpy(maskbuf, str, sizeof(maskbuf));
-	mask = maskbuf;
-
-	if ((*mask != '~') && strchr(mask, '!'))
-	{
-		*error = "Cannot have '!' in masks.";
-		return 0;
-	}
-
-	if (*mask == ':')
-	{
-		*error = "Mask cannot start with a ':'.";
-		return 0;
-	}
-
-	if (strchr(mask, ' '))
-	{
-		*error = "Mask may not contain spaces";
-		return 0;
-	}
-
-	/* Check if it's a softban */
-	if (*mask == '%')
-	{
-		*soft = 1;
-		if (!strchr("kGs", type))
-		{
-			*error = "The %% prefix (soft ban) is only available for KLINE, GLINE and SHUN. "
-			         "For technical reasons this will not work for (G)ZLINE.";
-			return 0;
-		}
-	}
-
-	/* Check if it's an extended server ban */
-	if (is_extended_server_ban(mask))
-	{
-		char *err;
-
-		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
-		{
-			/* If adding, reject it */
-			if (add)
-			{
-				*error = err;
-				return 0;
-			} else
-			{
-				/* 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 */
-			}
-		}
-		if (add && ((type == 'z') || (type == 'Z')))
-		{
-			*error = "(G)Zlines must be placed at *@\037IPMASK\037. "
-			         "Extended server bans don't work here because (g)zlines are processed "
-			         "BEFORE dns and ident lookups are done and before reading any client data. "
-			         "If you want to use extended server bans then use a KLINE/GLINE instead.";
-			return 0;
-		}
-		usermask = mask1buf; /* eg ~S: */
-		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
-	} else
-	{
-		/* Check if it's a hostmask and legal .. */
-		p = strchr(mask, '@');
-		if (p) {
-			if ((p == mask) || !p[1])
-			{
-				*error = "No user@host specified";
-				return 0;
-			}
-			usermask = strtok(mask, "@");
-			hostmask = strtok(NULL, "");
-			if (BadPtr(hostmask))
-			{
-				if (BadPtr(usermask))
-				{
-					*error = "Invalid mask";
-					return 0;
-				}
-				hostmask = usermask;
-				usermask = "*";
-			}
-			if (*hostmask == ':')
-			{
-				*error = "For technical reasons you cannot start the host with a ':', sorry";
-				return 0;
-			}
-			if (add && ((type == 'z') || (type == 'Z')))
-			{
-				/* It's a (G)ZLINE, make sure the user isn't specyfing a HOST.
-				 * Just a warning in 3.2.3, but an error in 3.2.4.
-				 */
-				if (strcmp(usermask, "*"))
-				{
-					*error = "(G)Zlines must be placed at \037*\037@ipmask, not \037user\037@ipmask. This is "
-					         "because (g)zlines are processed BEFORE dns and ident lookups are done. "
-					         "If you want to use usermasks, use a KLINE/GLINE instead.";
-					return 0;
-				}
-				for (p=hostmask; *p; p++)
-				{
-					if (isalpha(*p) && !isxdigit(*p))
-					{
-						*error = "ERROR: (g)zlines must be placed at *@\037IPMASK\037, not *@\037HOSTMASK\037 "
-						         "(so for example *@192.168.* is ok, but *@*.aol.com is not). "
-						         "This is because (g)zlines are processed BEFORE dns and ident lookups are done. "
-						         "If you want to use hostmasks instead of ipmasks, use a KLINE/GLINE instead.";
-						return 0;
-					}
-				}
-			}
-		}
-		else
-		{
-			/* It's seemingly a nick .. let's see if we can find the user */
-			Client *acptr;
-			if ((acptr = find_user(mask, NULL)))
-			{
-				BanAction action = BAN_ACT_KLINE; // just a dummy default
-				if ((type == 'z') || (type == 'Z'))
-					action = BAN_ACT_ZLINE; // to indicate zline (no hostname, no dns, etc)
-				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
-			}
-			else
-			{
-				*error = "Nickname not found";
-				return 0;
-			}
-		}
-	}
-
-	/* Success! */
-	*usermask_out = usermask;
-	*hostmask_out = hostmask;
-	return 1;
-}
-
-/** Intermediate layer between user functions such as KLINE/GLINE
- * and the TKL layer (cmd_tkl).
- * This allows us doing some syntax checking and other helpful
- * things that are the same for many types of *LINES.
- */
-void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
-{
-	time_t secs;
-	int add = 1, soft;
-	time_t i;
-	Client *acptr = NULL;
-	const char *mask;
-	const char *error;
-	char mo[64], mo2[64];
-	char *p, *usermask, *hostmask;
-	const char *tkllayer[10] = {
-		me.name,		/*0  server.name */
-		NULL,			/*1  +|- */
-		NULL,			/*2  G   */
-		NULL,			/*3  user */
-		NULL,			/*4  host */
-		NULL,			/*5  set_by */
-		"0",			/*6  expire_at */
-		NULL,			/*7  set_at */
-		"no reason",	/*8  reason */
-		NULL
-	};
-	struct tm *t;
-
-	if ((parc == 1) || BadPtr(parv[1]))
-		return; /* shouldn't happen */
-
-	mask = parv[1];
-
-	if (*mask == '-')
-	{
-		add = 0;
-		mask++;
-	}
-	else if (*mask == '+')
-	{
-		add = 1;
-		mask++;
-	}
-
-	if (!server_ban_parse_mask(client, add, *type, mask, &usermask, &hostmask, &soft, &error))
-	{
-		sendnotice(client, "[ERROR] %s", error);
-		return;
-	}
-
-	if (add && ban_too_broad(usermask, hostmask))
-	{
-		sendnotice(client, "*** [error] Too broad mask");
-		return;
-	}
-
-	secs = 0;
-
-	if (add && (parc > 3))
-	{
-		secs = config_checkval(parv[2], CFG_TIME);
-		if (secs < 0)
-		{
-			sendnotice(client, "*** [error] The time you specified is out of range!");
-			return;
-		}
-	}
-	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 (add)
-	{
-		if (secs == 0)
-		{
-			if (DEFAULT_BANTIME && (parc <= 3))
-				ircsnprintf(mo, sizeof(mo), "%lld", (long long)(DEFAULT_BANTIME + TStime()));
-			else
-				ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
-		}
-		else
-			ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
-		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
-		tkllayer[6] = mo;
-		tkllayer[7] = mo2;
-		if (parc > 3) {
-			tkllayer[8] = parv[3];
-		} else if (parc > 2) {
-			tkllayer[8] = parv[2];
-		}
-		/* Blerghhh... */
-		i = atol(mo);
-		t = gmtime(&i);
-		if (!t)
-		{
-			sendnotice(client, "*** [error] The time you specified is out of range");
-			return;
-		}
-
-		/* Some stupid checking */
-		if (xline_exists(type, usermask, hostmask))
-		{
-			sendnotice(client, "ERROR: Ban for %s@%s already exists.", usermask, hostmask);
-			return;
-		}
-
-		/* call the tkl layer .. */
-		cmd_tkl(&me, NULL, 9, tkllayer);
-	}
-	else
-	{
-		/* call the tkl layer .. */
-		cmd_tkl(&me, NULL, 6, tkllayer);
-
-	}
-}
-
-void eline_syntax(Client *client)
-{
-	sendnotice(client, " Syntax: /ELINE <user@host> <bantypes> <expiry-time> <reason>");
-	sendnotice(client, "     Or: /ELINE <extserverban> <bantypes> <expiry-time> <reason>");
-	sendnotice(client, "Valid bantypes are:");
-	sendnotice(client, "k: K-Line     G: G-Line");
-	sendnotice(client, "z: Z-Line     Z: Global Z-Line");
-	sendnotice(client, "Q: Q-Line");
-	sendnotice(client, "s: Shun");
-	sendnotice(client, "F: Spamfilter");
-	sendnotice(client, "b: Blacklist checking");
-	sendnotice(client, "c: Connect flood (bypass set::anti-flood::connect-flood))");
-	sendnotice(client, "d: Handshake data flood (no ZLINE on too much data before registration)");
-	sendnotice(client, "m: Bypass allow::maxperip restriction");
-	sendnotice(client, "r: Bypass antirandom module");
-	sendnotice(client, "8: Bypass antimixedutf8 module");
-	sendnotice(client, "v: Bypass ban version { } blocks");
-	sendnotice(client, "Examples:");
-	sendnotice(client, "/ELINE *@unrealircd.org kGF 0 This user is exempt");
-	sendnotice(client, "/ELINE ~S:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef kGF 0 Trusted user with this certificate fingerprint");
-	sendnotice(client, "-");
-	sendnotice(client, "To get a list of all current ELINEs, type: /STATS except");
-}
-
-/** Check if any of the specified types require the
- * exception to be placed on *@ip rather than
- * user@host or *@host. For eg zlines.
- */
-TKLTypeTable *eline_type_requires_ip(const char *bantypes)
-{
-	int i;
-
-	for (i=0; tkl_types[i].config_name; i++)
-		if (tkl_types[i].needip && strchr(bantypes, tkl_types[i].letter))
-			return &tkl_types[i];
-	return NULL;
-}
-
-/** Checks a string to see if it contains invalid ban exception types */
-int contains_invalid_server_ban_exception_type(const char *str, char *c)
-{
-	const char *p;
-	for (p = str; *p; p++)
-	{
-		if (!tkl_banexception_chartotype(*p))
-		{
-			*c = *p;
-			return 1;
-		}
-	}
-	return 0;
-}
-
-/** Parse a server ban exception (ELINE) request such as 'blah@blah.com' or '~account:EvilUser'
- * @param client	Client requesting the operation (can be NULL)
- * @param add		Set to 1 for add ban, 0 for remove ban
- * @param bantypes	Ban types to exempt from
- * @param str		The input string
- * @param usermask_out	Will be set to the TKL usermask
- * @param hostmask_out	Will be set to the TKL hostmask
- * @param soft		Will be set to 1 if it's a softban, otherwise 0
- * @param error		On failure, this will contain the error string
- * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
- * @retval 0	Failed: error is set appropriately
- */
-int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
-{
-	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE], errbuf[BUFSIZE];
-	char *hostmask = NULL, *usermask = NULL;
-	char *mask, *p;
-	TKLTypeTable *t;
-
-	/* Set defaults */
-	*usermask_out = *hostmask_out = NULL;
-	*soft = 0;
-
-	strlcpy(maskbuf, str, sizeof(maskbuf));
-	mask = maskbuf;
-
-	if ((*mask != '~') && strchr(mask, '!'))
-	{
-		*error = "Cannot have '!' in masks.";
-		return 0;
-	}
-
-	if (*mask == ':')
-	{
-		*error = "Mask cannot start with a ':'.";
-		return 0;
-	}
-
-	if (strchr(mask, ' '))
-	{
-		*error = "Mask may not contain spaces";
-		return 0;
-	}
-
-	if (*mask == '%')
-	{
-		*soft = 1;
-		/* do we need more sanity checks here? */
-	}
-
-	/* Check if it's an extended server ban */
-	if (is_extended_server_ban(mask))
-	{
-		char *err;
-
-		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
-		{
-			/* If adding, reject it */
-			if (add)
-			{
-				*error = err;
-				return 0;
-			} else
-			{
-				/* 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 */
-			}
-		}
-		if (add && (t = eline_type_requires_ip(bantypes)))
-		{
-			snprintf(errbuf, sizeof(errbuf),
-			         "ERROR: Ban exception with type '%c' does not work on extended server bans. "
-			         "This is because checking for %s takes places BEFORE "
-			         "extended bans can be checked.", t->letter, t->log_name);
-			*error = errbuf;
-			return 0;
-		}
-		usermask = mask1buf; /* eg ~S: */
-		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
-	} else
-	{
-		/* Check if it's a hostmask and legal .. */
-		p = strchr(mask, '@');
-		if (p) {
-			if ((p == mask) || !p[1])
-			{
-				*error = "No user@host specified";
-				return 0;
-			}
-			usermask = strtok(mask, "@");
-			hostmask = strtok(NULL, "");
-			if (BadPtr(hostmask))
-			{
-				if (BadPtr(usermask))
-				{
-					*error = "Invalid mask";
-					return 0;
-				}
-				hostmask = usermask;
-				usermask = "*";
-			}
-			if (*hostmask == ':')
-			{
-				*error = "For technical reasons you cannot start the host with a ':', sorry";
-				return 0;
-			}
-			if (add && ((t = eline_type_requires_ip(bantypes))))
-			{
-				/* Trying to exempt a user from a (G)ZLINE,
-				 * make sure the user isn't specifying a host then.
-				 */
-				if (strcmp(usermask, "*"))
-				{
-					snprintf(errbuf, sizeof(errbuf),
-					         "Ban exception with type '%c' need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
-					         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
-					         t->letter, t->log_name);
-					*error = errbuf;
-					return 0;
-				}
-				for (p=hostmask; *p; p++)
-				{
-					if (isalpha(*p) && !isxdigit(*p))
-					{
-						snprintf(errbuf, sizeof(errbuf),
-						         "Ban exception with type '%c' needs to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
-						         "(so for example *@192.168.* is OK, but *@*.aol.com is not). "
-						         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
-						         t->letter, t->log_name);
-						*error = errbuf;
-						return 0;
-					}
-				}
-			}
-		}
-		else
-		{
-			/* It's seemingly a nick .. let's see if we can find the user */
-			Client *acptr;
-			if ((acptr = find_user(mask, NULL)))
-			{
-				BanAction action = BAN_ACT_KLINE; // just a dummy default
-				if (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, (const char **)&usermask, (const char **)&hostmask);
-			}
-			else
-			{
-				*error = "Nickname not found";
-				return 0;
-			}
-		}
-	}
-
-	/* Success! */
-	*usermask_out = usermask;
-	*hostmask_out = hostmask;
-	return 1;
-}
-
-CMD_FUNC(cmd_eline)
-{
-	time_t secs = 0;
-	int add = 1;
-	int soft = 0;
-	const char *error = NULL;
-	Client *acptr = NULL;
-	char *mask = NULL;
-	char mo[64], mo2[64];
-	char maskbuf[BUFSIZE];
-	char mask1buf[BUFSIZE];
-	char mask2buf[BUFSIZE];
-	const char *p, *bantypes=NULL, *reason=NULL;
-	char *usermask, *hostmask;
-	const char *tkllayer[11] = {
-		me.name,		/*0  server.name */
-		NULL,			/*1  +|- */
-		NULL,			/*2  E   */
-		NULL,			/*3  user */
-		NULL,			/*4  host */
-		NULL,			/*5  set_by */
-		"0",			/*6  expire_at */
-		"-",			/*7  set_at */
-		"-",			/*8  ban types */
-		"-",			/*9  reason */
-		NULL
-	};
-	TKLTypeTable *t;
-
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:eline",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	/* For del we need at least:
-	 * ELINE -user@host
-	 * The 'add' case is checked later.
-	 */
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		eline_syntax(client);
-		return;
-	}
-
-	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
-	mask = maskbuf;
-	if (*mask == '-')
-	{
-		add = 0;
-		mask++;
-	}
-	else if (*mask == '+')
-	{
-		add = 1;
-		mask++;
-	}
-
-	/* For add we need more:
-	 * ELINE user@host bantypes expiry :reason
-	 */
-	if (add)
-	{
-		if ((parc < 5) || BadPtr(parv[4]))
-		{
-			eline_syntax(client);
-			return;
-		}
-		bantypes = parv[2];
-		reason = parv[4];
-	}
-
-	if (!server_ban_exception_parse_mask(client, add, bantypes, mask, &usermask, &hostmask, &soft, &error))
-	{
-		sendnotice(client, "[ERROR] %s", error);
-		return;
-	}
-
-	if (add)
-	{
-		secs = config_checkval(parv[3], CFG_TIME);
-		if ((secs <= 0) && (*parv[3] != '0'))
-		{
-			sendnotice(client, "*** [error] The expiry time you specified is out of range!");
-			eline_syntax(client);
-			return;
-		}
-	}
-
-	tkllayer[1] = add ? "+" : "-";
-	tkllayer[2] = "E";
-	tkllayer[3] = usermask;
-	tkllayer[4] = hostmask;
-	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
-
-	if (add)
-	{
-		char c;
-		/* Add ELINE */
-		if (secs == 0)
-			ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
-		else
-			ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
-		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
-		tkllayer[6] = mo;
-		tkllayer[7] = mo2;
-		tkllayer[8] = bantypes;
-		if (contains_invalid_server_ban_exception_type(bantypes, &c))
-		{
-			sendnotice(client, "ERROR: bantype '%c' is unrecognized (in '%s'). "
-			                   "Note that the bantypes are case sensitive. "
-			                   "Type /ELINE to see a list of all possible bantypes.",
-			                   c, bantypes);
-			return;
-		}
-		tkllayer[9] = reason;
-		/* call the tkl layer .. */
-		cmd_tkl(&me, NULL, 10, tkllayer);
-	}
-	else
-	{
-		/* Remove ELINE */
-		/* call the tkl layer .. */
-		cmd_tkl(&me, NULL, 10, tkllayer);
-
-	}
-}
-
-
-/** Helper function for cmd_spamfilter, explaining usage. */
-void spamfilter_usage(Client *client)
-{
-	sendnotice(client, "Use: /spamfilter [add|del|remove|+|-] [-simple|-regex] [type] [action] [tkltime] [tklreason] [regex]");
-	sendnotice(client, "See '/helpop ?spamfilter' for more information.");
-	sendnotice(client, "For an easy way to remove an existing spamfilter, use '/spamfilter del' without additional parameters");
-}
-
-/** Helper function for cmd_spamfilter, explaining usage has changed. */
-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)",
-	                 parv[2]);
-
-	if (*parv[2] != '-')
-		sendnotice(client, "Using the old 3.2.x /SPAMFILTER syntax? Note the new -regex/-simple field!!");
-
-	spamfilter_usage(client);
-}
-
-/** Delete a spamfilter by ID (the ID can be obtained via '/SPAMFILTER del' */
-void spamfilter_del_by_id(Client *client, const char *id)
-{
-	int index;
-	TKL *tk;
-	int found = 0;
-	char mo[32], mo2[32];
-	const char *tkllayer[13] = {
-		me.name,	/*  0 server.name */
-		NULL,		/*  1 +|- */
-		"F",		/*  2 F   */
-		NULL,		/*  3 usermask (targets) */
-		NULL,		/*  4 hostmask (action) */
-		NULL,		/*  5 set_by */
-		"0",		/*  6 expire_at */
-		"0",		/*  7 set_at */
-		"",			/*  8 tkl time */
-		"",			/*  9 tkl reason */
-		"",			/* 10 match method */
-		"",			/* 11 regex */
-		NULL
-	};
-
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tk = tklines[index]; tk; tk = tk->next)
-		{
-			if (((tk->type & (TKL_GLOBAL|TKL_SPAMF)) == (TKL_GLOBAL|TKL_SPAMF)) && !strcmp(spamfilter_id(tk), id))
-			{
-				found = 1;
-				break;
-			}
-		}
-		if (found)
-			break; /* break outer loop */
-	}
-
-	if (!tk)
-	{
-		sendnotice(client, "Sorry, no spamfilter found with that ID. Did you run '/spamfilter del' to get the appropriate id?");
-		return;
-	}
-
-	/* Spamfilter found. Now fill the tkllayer */
-	tkllayer[1] = "-";
-	tkllayer[3] = spamfilter_target_inttostring(tk->ptr.spamfilter->target); /* target(s) */
-	mo[0] = banact_valtochar(tk->ptr.spamfilter->action);
-	mo[1] = '\0';
-	tkllayer[4] = mo; /* action */
-	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
-	tkllayer[8] = "-";
-	tkllayer[9] = "-";
-	tkllayer[10] = unreal_match_method_valtostr(tk->ptr.spamfilter->match->type); /* matching type */
-	tkllayer[11] = tk->ptr.spamfilter->match->str; /* regex */
-	ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
-	tkllayer[7] = mo2; /* deletion time */
-
-	cmd_tkl(&me, NULL, 12, tkllayer);
-}
-
-/** Spamfilter to fight spam, advertising, worms and other bad things on IRC.
- * See https://www.unrealircd.org/docs/Spamfilter for general documentation.
- *
- * /SPAMFILTER [add|del|remove|+|-] [match-type] [type] [action] [tkltime] [reason] [regex]
- *                   1                    2         3       4        5        6        7
- */
-CMD_FUNC(cmd_spamfilter)
-{
-	int add = 1;
-	char mo[32], mo2[32];
-	const char *tkllayer[13] = {
-		me.name,	/*  0 server.name */
-		NULL,		/*  1 +|- */
-		"F",		/*  2 F   */
-		NULL,		/*  3 usermask (targets) */
-		NULL,		/*  4 hostmask (action) */
-		NULL,		/*  5 set_by */
-		"0",		/*  6 expire_at */
-		"0",		/*  7 set_at */
-		"",			/*  8 tkl time */
-		"",			/*  9 tkl reason */
-		"",			/* 10 match method */
-		"",			/* 11 regex */
-		NULL
-	};
-	int targets = 0, action = 0;
-	char targetbuf[64], actionbuf[2];
-	char reason[512];
-	int n;
-	Match *m;
-	int match_type = 0;
-	char *err = NULL;
-
-	if (IsServer(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server-ban:spamfilter",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (parc == 1)
-	{
-		const char *parv[3];
-		parv[0] = NULL;
-		parv[1] = "spamfilter";
-		parv[2] = NULL;
-		do_cmd(client, recv_mtags, "STATS", 2, parv);
-		return;
-	}
-
-	if ((parc <= 3) && !strcmp(parv[1], "del"))
-	{
-		if (!parv[2])
-		{
-			/* Show STATS with appropriate SPAMFILTER del command */
-			const char *parv[5];
-			parv[0] = NULL;
-			parv[1] = "spamfilter";
-			parv[2] = me.name;
-			parv[3] = "del";
-			parv[4] = NULL;
-			do_cmd(client, recv_mtags, "STATS", 4, parv);
-			return;
-		}
-		spamfilter_del_by_id(client, parv[2]);
-		return;
-	}
-
-	if ((parc == 7) && (*parv[2] != '-'))
-	{
-		spamfilter_new_usage(client,parv);
-		return;
-	}
-
-	if ((parc < 8) || BadPtr(parv[7]))
-	{
-		spamfilter_usage(client);
-		return;
-	}
-
-	/* parv[1]: [add|del|+|-]
-	 * parv[2]: match-type
-	 * parv[3]: type
-	 * parv[4]: action
-	 * parv[5]: tkl time
-	 * parv[6]: tkl reason (or block reason..)
-	 * parv[7]: regex
-	 */
-	if (!strcasecmp(parv[1], "add") || !strcmp(parv[1], "+"))
-		add = 1;
-	else if (!strcasecmp(parv[1], "del") || !strcmp(parv[1], "-") || !strcasecmp(parv[1], "remove"))
-		add = 0;
-	else
-	{
-		sendnotice(client, "1st parameter invalid");
-		spamfilter_usage(client);
-		return;
-	}
-
-	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");
-		return;
-	}
-
-	match_type = unreal_match_method_strtoval(parv[2]+1);
-	if (!match_type)
-	{
-		spamfilter_new_usage(client, parv);
-		return;
-	}
-
-	targets = spamfilter_gettargets(parv[3], client);
-	if (!targets)
-	{
-		spamfilter_usage(client);
-		return;
-	}
-
-	strlcpy(targetbuf, spamfilter_target_inttostring(targets), sizeof(targetbuf));
-
-	action = banact_stringtoval(parv[4]);
-	if (!action)
-	{
-		sendnotice(client, "Invalid 'action' field (%s)", parv[4]);
-		spamfilter_usage(client);
-		return;
-	}
-	actionbuf[0] = banact_valtochar(action);
-	actionbuf[1] = '\0';
-
-	if (add)
-	{
-		/* now check the regex / match field... */
-		m = unreal_create_match(match_type, parv[7], &err);
-		if (!m)
-		{
-			sendnotice(client, "Error in regex '%s': %s", parv[7], err);
-			return;
-		}
-		unreal_delete_match(m);
-	}
-
-	tkllayer[1] = add ? "+" : "-";
-	tkllayer[3] = targetbuf;
-	tkllayer[4] = actionbuf;
-	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
-
-	if (parv[5][0] == '-')
-	{
-		ircsnprintf(mo, sizeof(mo), "%lld", (long long)SPAMFILTER_BAN_TIME);
-		tkllayer[8] = mo;
-	}
-	else
-		tkllayer[8] = parv[5];
-
-	if (parv[6][0] == '-')
-		strlcpy(reason, unreal_encodespace(SPAMFILTER_BAN_REASON), sizeof(reason));
-	else
-		strlcpy(reason, parv[6], sizeof(reason));
-
-	tkllayer[9] = reason;
-	tkllayer[10] = parv[2]+1; /* +1 to skip the '-' */
-	tkllayer[11] = parv[7];
-
-	/* SPAMFILTER LENGTH CHECK.
-	 * We try to limit it here so '/stats f' output shows ok, output of that is:
-	 * :servername 229 destname F <target> <action> <num> <num> <num> <reason> <set_by> :<regex>
-	 * : ^NICKLEN       ^ NICKLEN                                       ^check   ^check   ^check
-	 * And for the other fields (and spacing/etc) we count on max 40 characters.
-	 * We also do >500 instead of >510, since that looks cleaner ;).. so actually we count
-	 * on 50 characters for the rest... -- Syzop
-	 */
-	n = strlen(reason) + strlen(parv[7]) + strlen(tkllayer[6]) + (NICKLEN * 2) + 40;
-	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 (add)
-	{
-		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
-		tkllayer[7] = mo2;
-	}
-
-	cmd_tkl(&me, NULL, 12, tkllayer);
-}
-
-/** tkl hash method.
- * @param c   The tkl type character, see tkl_typetochar().
- * @note      The input value 'c' is assumed to be in range a-z or A-Z!
- *            Also, don't blindly change the hashmethod here, some things
- *            depend on 'z' and 'Z' ending up in the same bucket.
- */
-int _tkl_hash(unsigned int c)
-{
-#ifdef DEBUGMODE
-	if ((c >= 'a') && (c <= 'z'))
-		return c-'a';
-	else if ((c >= 'A') && (c <= 'Z'))
-		return c-'A';
-	else {
-		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
-	return (isupper(c) ? c-'A' : c-'a');
-#endif
-}
-
-/** tkl type to tkl character.
- * NOTE: type is assumed to be valid.
- */
-char _tkl_typetochar(int type)
-{
-	int i;
-	for (i=0; tkl_types[i].config_name; i++)
-		if ((tkl_types[i].type == type) && tkl_types[i].tkltype)
-			return tkl_types[i].letter;
-	unreal_log(ULOG_ERROR, "bug", "TKL_TYPETOCHAR_INVALID", NULL,
-	           "tkl_typetochar(): unknown type $tkl_type!!!",
-	           log_data_integer("tkl_type", type));
-	return 0;
-}
-
-/** tkl character to tkl type
- * Returns 0 if invalid type.
- */
-int _tkl_chartotype(char c)
-{
-	int i;
-	for (i=0; tkl_types[i].config_name; i++)
-		if ((tkl_types[i].letter == c) && tkl_types[i].tkltype)
-			return tkl_types[i].type;
-	return 0;
-}
-
-char _tkl_configtypetochar(const char *name)
-{
-	int i;
-	for (i=0; tkl_types[i].config_name; i++)
-		if (!strcmp(tkl_types[i].config_name, name))
-			return tkl_types[i].letter;
-	return 0;
-}
-
-int tkl_banexception_chartotype(char c)
-{
-	int i;
-	for (i=0; tkl_types[i].config_name; i++)
-		if ((tkl_types[i].letter == c) && tkl_types[i].exceptiontype)
-			return tkl_types[i].type;
-	return 0;
-}
-
-char *tkl_banexception_configname_to_chars(char *name)
-{
-	static char buf[128];
-	int i;
-
-	if (!strcasecmp(name, "all"))
-	{
-		/* 'all' means everything except qline: */
-		char *p = buf;
-		for (i=0; tkl_types[i].config_name; i++)
-		{
-			if (tkl_types[i].exceptiontype && !(tkl_types[i].type & TKL_NAME))
-				*p++ = tkl_types[i].letter;
-		}
-		*p = '\0';
-		return buf;
-	}
-
-	for (i=0; tkl_types[i].config_name; i++)
-	{
-		if (!strcasecmp(name, tkl_types[i].config_name) && tkl_types[i].exceptiontype)
-		{
-			buf[0] = tkl_types[i].letter;
-			buf[1] = '\0';
-			return buf;
-		}
-	}
-	return NULL;
-}
-
-/** Show TKL type as a string (used when adding/removing) */
-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));
-
-	for (i=0; tkl_types[i].config_name; i++)
-	{
-		if ((tkl_types[i].type == tkl->type) && tkl_types[i].tkltype)
-		{
-			strlcat(txt, tkl_types[i].log_name, sizeof(txt));
-			return txt;
-		}
-	}
-
-	strlcpy(txt, "Unknown *-Line", sizeof(txt));
-	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;
-	int extype;
-
-	if (!TKLIsBanException(except))
-		abort();
-
-	for (p = except->ptr.banexception->bantypes; *p; p++)
-	{
-		extype = tkl_banexception_chartotype(*p);
-		if ((extype & TKL_SPAMF) || (extype & TKL_SHUN) || (extype & TKL_NAME))
-		{
-			/* For spamfilter, shun and qline we don't care
-			 * whether they are global or not. That would only
-			 * be confusing to the admin.
-			 */
-			extype &= ~TKL_GLOBAL;
-			if (bantype & extype)
-				return 1;
-		} else {
-			/* Rest requires an exact match */
-			if (bantype == extype)
-				return 1;
-		}
-	}
-
-	return 0;
-}
-
-/** Used for finding out which element of the tkl_ip hash table is used (primary element) */
-int _tkl_ip_hash(char *ip)
-{
-	char ipbuf[64], *p;
-
-	for (p = ip; *p; p++)
-	{
-		if ((*p == '?') || (*p == '*') || (*p == '/'))
-			return -1; /* not an entry suitable for the ip hash table */
-	}
-	if (inet_pton(AF_INET, ip, &ipbuf) == 1)
-	{
-		/* IPv4 */
-		unsigned int v = (ipbuf[0] << 24) +
-		                 (ipbuf[1] << 16) +
-		                 (ipbuf[2] << 8)  +
-		                 ipbuf[3];
-		return v % TKLIPHASHLEN2;
-	} else
-	if (inet_pton(AF_INET6, ip, &ipbuf) == 1)
-	{
-		/* IPv6 (only upper 64 bits) */
-		unsigned int v1 = (ipbuf[0] << 24) +
-		                 (ipbuf[1] << 16) +
-		                 (ipbuf[2] << 8)  +
-		                 ipbuf[3];
-		unsigned int v2 = (ipbuf[4] << 24) +
-		                 (ipbuf[5] << 16) +
-		                 (ipbuf[6] << 8)  +
-		                 ipbuf[7];
-		return (v1 ^ v2) % TKLIPHASHLEN2;
-	} else
-	{
-		return -1;
-	}
-}
-
-// TODO: consider efunc
-int tkl_ip_hash_tkl(TKL *tkl)
-{
-	if (TKLIsServerBan(tkl))
-		return tkl_ip_hash(tkl->ptr.serverban->hostmask);
-	if (TKLIsBanException(tkl))
-		return tkl_ip_hash(tkl->ptr.banexception->hostmask);
-	return -1;
-}
-
-/** Used for finding out which tkl_ip hash table needs to be used (secondary element).
- * NOTE: Returns -1 for types that are never on the TKL ip hash table, such as spamfilter.
- *       This can be used by the caller as a quick way to find out if the type is supported.
- */
-int _tkl_ip_hash_type(int type)
-{
-	if ((type == 'Z') || (type == 'z'))
-		return 0;
-	else if (type == 'G')
-		return 1;
-	else if (type == 'k')
-		return 2;
-	else if ((type == 'e') || (type == 'E'))
-		return 3;
-	else
-		return -1;
-}
-
-/* Find the appropriate list 'head' that we need to iterate.
- * This is simply a helper that is used at 3 places and I hate duplicate code.
- * NOTE: this function may return NULL.
- */
-TKL *tkl_find_head(char type, char *hostmask, TKL *def)
-{
-	int index, index2;
-
-	/* First, check ip hash table TKL's... */
-	index = tkl_ip_hash_type(type);
-	if (index >= 0)
-	{
-		index2 = tkl_ip_hash(hostmask);
-		if (index2 >= 0)
-		{
-			/* iterate tklines_ip_hash[index][index2] */
-			return tklines_ip_hash[index][index2];
-		}
-	}
-	/* Fallback to the default */
-	return def;
-}
-
-/** Add a spamfilter entry to the list.
- * @param type                TKL_SPAMF or TKL_SPAMF|TKL_GLOBAL.
- * @param target              The spamfilter target (SPAMF_*)
- * @param action              The spamfilter action (BAN_ACT_*)
- * @param match               The match (this struct may contain a regex for example)
- * @param set_by              Who (or what) set the ban
- * @param expire_at           When will the ban expire (0 for permanent)
- * @param set_at              When was the ban set
- * @param spamf_tkl_duration  When will the ban placed by spamfilter expire
- * @param spamf_tkl_reason    What is the reason for bans placed by spamfilter
- * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
- * @returns                   The TKL entry, or NULL in case of a problem,
- *                            such as a regex failing to compile, memory problem, ..
- */
-TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by,
-                             time_t expire_at, time_t set_at,
-                             time_t tkl_duration, char *tkl_reason,
-                             int flags)
-{
-	TKL *tkl;
-	int index;
-
-	if (!(type & TKL_SPAMF))
-		abort();
-
-	tkl = safe_alloc(sizeof(TKL));
-	/* First the common fields */
-	tkl->type = type;
-	tkl->flags = flags;
-	tkl->set_at = set_at;
-	safe_strdup(tkl->set_by, set_by);
-	tkl->expire_at = expire_at;
-	/* Then the spamfilter fields */
-	tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
-	tkl->ptr.spamfilter->target = target;
-	tkl->ptr.spamfilter->action = action;
-	tkl->ptr.spamfilter->match = match;
-	safe_strdup(tkl->ptr.spamfilter->tkl_reason, tkl_reason);
-	tkl->ptr.spamfilter->tkl_duration = tkl_duration;
-
-	if (tkl->ptr.spamfilter->target & SPAMF_USER)
-		loop.do_bancheck_spamf_user = 1;
-	if (tkl->ptr.spamfilter->target & SPAMF_AWAY)
-		loop.do_bancheck_spamf_away = 1;
-
-	/* Spamfilters go via the normal TKL list... */
-	index = tkl_hash(tkl_typetochar(type));
-	AddListItem(tkl, tklines[index]);
-
-	if (target & SPAMF_MTAG)
-		mtag_spamfilters_present = 1;
-
-	return tkl;
-}
-
-/** Add a server ban TKL entry.
- * @param type                The TKL type, one of TKL_*,
- *                            optionally OR'ed with TKL_GLOBAL.
- * @param usermask            The user mask
- * @param hostmask            The host mask
- * @param reason              The reason for the ban
- * @param set_by              Who (or what) set the ban
- * @param expire_at           When will the ban expire (0 for permanent)
- * @param set_at              When was the ban set
- * @param soft                Whether it's a soft-ban
- * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
- * @returns                   The TKL entry, or NULL in case of a problem,
- *                            such as a regex failing to compile, memory problem, ..
- * @note
- * Be sure not to call this function for spamfilters,
- * qlines or exempts, which have their own function!
- */
-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)
-{
-	TKL *tkl;
-	int index, index2;
-
-	if (!TKLIsServerBanType(type))
-		abort();
-
-	tkl = safe_alloc(sizeof(TKL));
-	/* First the common fields */
-	tkl->type = type;
-	tkl->flags = flags;
-	tkl->set_at = set_at;
-	safe_strdup(tkl->set_by, set_by);
-	tkl->expire_at = expire_at;
-	/* Now the server ban fields */
-	tkl->ptr.serverban = safe_alloc(sizeof(ServerBan));
-	safe_strdup(tkl->ptr.serverban->usermask, usermask);
-	safe_strdup(tkl->ptr.serverban->hostmask, hostmask);
-	if (soft)
-		tkl->ptr.serverban->subtype = TKL_SUBTYPE_SOFT;
-	safe_strdup(tkl->ptr.serverban->reason, reason);
-
-	/* For ip hash table TKL's... */
-	index = tkl_ip_hash_type(tkl_typetochar(type));
-	if (index >= 0)
-	{
-		index2 = tkl_ip_hash_tkl(tkl);
-		if (index2 >= 0)
-		{
-			AddListItem(tkl, tklines_ip_hash[index][index2]);
-			return tkl;
-		}
-	}
-
-	/* If we get here it's just for our normal list.. */
-	index = tkl_hash(tkl_typetochar(type));
-	AddListItem(tkl, tklines[index]);
-
-	return tkl;
-}
-
-/** Add a ban exception TKL entry.
- * @param type                TKL_EXCEPTION or TKLEXCEPT|TKL_GLOBAL.
- * @param usermask            The user mask
- * @param hostmask            The host mask
- * @param match               A securitygroup used for matching (can be NULL,
- *                            if not NULL then this field is used as-is and not copied
- *                            so caller should not free!)
- * @param reason              The reason for the ban
- * @param set_by              Who (or what) set the ban
- * @param expire_at           When will the ban expire (0 for permanent)
- * @param set_at              When was the ban set
- * @param soft                Whether it's a soft-ban
- * @param bantypes            The ban types to exempt from
- * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
- * @returns                   The TKL entry, or NULL in case of a problem,
- *                            such as a regex failing to compile, memory problem, ..
- * @note
- * Be sure not to call this function for spamfilters,
- * qlines or exempts, which have their own function!
- */
-TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match,
-                           char *reason, char *set_by,
-                           time_t expire_at, time_t set_at, int soft, char *bantypes, int flags)
-{
-	TKL *tkl;
-	int index, index2;
-
-	if (!TKLIsBanExceptionType(type))
-		abort();
-	tkl = safe_alloc(sizeof(TKL));
-	/* First the common fields */
-	tkl->type = type;
-	tkl->flags = flags;
-	tkl->set_at = set_at;
-	safe_strdup(tkl->set_by, set_by);
-	tkl->expire_at = expire_at;
-	/* Now the ban except fields */
-	tkl->ptr.banexception = safe_alloc(sizeof(BanException));
-	safe_strdup(tkl->ptr.banexception->usermask, usermask);
-	safe_strdup(tkl->ptr.banexception->hostmask, hostmask);
-	tkl->ptr.banexception->match = match;
-	if (soft)
-		tkl->ptr.banexception->subtype = TKL_SUBTYPE_SOFT;
-	safe_strdup(tkl->ptr.banexception->bantypes, bantypes);
-	safe_strdup(tkl->ptr.banexception->reason, reason);
-
-	/* For ip hash table TKL's... */
-	index = tkl_ip_hash_type(tkl_typetochar(type));
-	if (index >= 0)
-	{
-		index2 = tkl_ip_hash_tkl(tkl);
-		if (index2 >= 0)
-		{
-			AddListItem(tkl, tklines_ip_hash[index][index2]);
-			return tkl;
-		}
-	}
-
-	/* If we get here it's just for our normal list.. */
-	index = tkl_hash(tkl_typetochar(type));
-	AddListItem(tkl, tklines[index]);
-
-	return tkl;
-}
-
-/** Add a name ban TKL entry (Q-Line), used for banning nicks and channels.
- * @param type                The TKL type, one of TKL_*,
- *                            optionally OR'ed with TKL_GLOBAL.
- * @param name                The nick or channel to be banned (wildcards accepted)
- * @param hold                Flag to indicate services hold
- * @param reason              The reason for the ban
- * @param set_by              Who (or what) set the ban
- * @param expire_at           When will the ban expire (0 for permanent)
- * @param set_at              When was the ban set
- * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
- * @returns                   The TKL entry, or NULL in case of a problem,
- *                            such as a regex failing to compile, memory problem, ..
- * @note
- * Be sure not to call this function for spamfilters,
- * qlines or exempts, which have their own function!
- */
-TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
-                          time_t expire_at, time_t set_at, int flags)
-{
-	TKL *tkl;
-	int index;
-
-	if (!TKLIsNameBanType(type))
-		abort();
-
-	tkl = safe_alloc(sizeof(TKL));
-	/* First the common fields */
-	tkl->type = type;
-	tkl->flags = flags;
-	tkl->set_at = set_at;
-	safe_strdup(tkl->set_by, set_by);
-	tkl->expire_at = expire_at;
-	/* Now the name ban fields */
-	tkl->ptr.nameban = safe_alloc(sizeof(ServerBan));
-	safe_strdup(tkl->ptr.nameban->name, name);
-	tkl->ptr.nameban->hold = hold;
-	safe_strdup(tkl->ptr.nameban->reason, reason);
-
-	/* Name bans go via the normal TKL list.. */
-	index = tkl_hash(tkl_typetochar(type));
-	AddListItem(tkl, tklines[index]);
-
-	return tkl;
-}
-
-
-/** Free a TKL entry but do not remove from the list.
- * (this assumes that it was not added yet or is already removed)
- * Most people will use tkl_del_line() instead.
- */
-void _free_tkl(TKL *tkl)
-{
-	/* Free the entry */
-	/* First, the common fields */
-	safe_free(tkl->set_by);
-	/* Now the type specific fields */
-	if (TKLIsServerBan(tkl) && tkl->ptr.serverban)
-	{
-		safe_free(tkl->ptr.serverban->usermask);
-		safe_free(tkl->ptr.serverban->hostmask);
-		safe_free(tkl->ptr.serverban->reason);
-		safe_free(tkl->ptr.serverban);
-	} else
-	if (TKLIsNameBan(tkl) && tkl->ptr.nameban)
-	{
-		safe_free(tkl->ptr.nameban->name);
-		safe_free(tkl->ptr.nameban->reason);
-		safe_free(tkl->ptr.nameban);
-	} else
-	if (TKLIsSpamfilter(tkl) && tkl->ptr.spamfilter)
-	{
-		/* Spamfilter */
-		safe_free(tkl->ptr.spamfilter->tkl_reason);
-		if (tkl->ptr.spamfilter->match)
-			unreal_delete_match(tkl->ptr.spamfilter->match);
-		safe_free(tkl->ptr.spamfilter);
-	} else
-	if (TKLIsBanException(tkl) && tkl->ptr.banexception)
-	{
-		safe_free(tkl->ptr.banexception->usermask);
-		safe_free(tkl->ptr.banexception->hostmask);
-		if (tkl->ptr.banexception->match)
-			free_security_group(tkl->ptr.banexception->match);
-		safe_free(tkl->ptr.banexception->bantypes);
-		safe_free(tkl->ptr.banexception->reason);
-		safe_free(tkl->ptr.banexception);
-	}
-	safe_free(tkl);
-}
-
-/** Delete a TKL entry from the list and free it.
- * @param tkl The TKL entry.
- */
-void _tkl_del_line(TKL *tkl)
-{
-	int index, index2;
-	int found = 0;
-
-	/* Try to find it in the ip TKL hash table first
-	 * (this only applies to server bans)
-	 */
-	index = tkl_ip_hash_type(tkl_typetochar(tkl->type));
-	if (index >= 0)
-	{
-		index2 = tkl_ip_hash_tkl(tkl);
-		if (index2 >= 0)
-		{
-#if 1
-			/* Temporary validation until an rmtkl(?) bug is fixed */
-			TKL *d;
-			int really_found = 0;
-			for (d = tklines_ip_hash[index][index2]; d; d = d->next)
-				if (d == tkl)
-				{
-					really_found = 1;
-					break;
-				}
-			if (!really_found)
-			{
-				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
-			DelListItem(tkl, tklines_ip_hash[index][index2]);
-			found = 1;
-		}
-	}
-
-	if (!found)
-	{
-		/* If we get here it's just for our normal list.. */
-		index = tkl_hash(tkl_typetochar(tkl->type));
-		DelListItem(tkl, tklines[index]);
-	}
-
-	/* Finally, free the entry */
-	free_tkl(tkl);
-	check_mtag_spamfilters_present();
-}
-
-/** Add some default ban exceptions - for localhost */
-static void add_default_exempts(void)
-{
-	/* The exempted ban types are only ones that will affect other connections as well,
-	 * such as gline, and not policy decissions such as maxperip exempt or bypass qlines.
-	 * Currently the list is: gline, kline, gzline, zline, shun, blacklist,
-	 *                        connect-flood, handshake-data-flood.
-	 */
-	tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.1", NULL, "localhost is always exempt",
-	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
-	tkl_add_banexception(TKL_EXCEPTION, "*", "::1", NULL, "localhost is always exempt",
-	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
-}
-
-/*
- * tkl_check_local_remove_shun:
- * removes shun from currently connected users affected by tmp.
- */
-// TODO / FIXME: audit this function, it looks crazy
-void _tkl_check_local_remove_shun(TKL *tmp)
-{
-	long i;
-	char *chost, *cname, *cip;
-	int is_ip;
-	Client *client;
-
-	TKL *tk;
-	int keep_shun;
-
-	for (i = 0; i <= 5; i++)
-	{
-		list_for_each_entry(client, &lclient_list, lclient_node)
-			if (MyUser(client) && IsShunned(client))
-			{
-				chost = client->local->sockhost;
-				cname = client->user->username;
-
-				cip = GetIP(client);
-
-				if ((*tmp->ptr.serverban->hostmask >= '0') && (*tmp->ptr.serverban->hostmask <= '9'))
-					is_ip = 1;
-				else
-					is_ip = 0;
-
-				if (is_ip == 0 ?
-				    (match_simple(tmp->ptr.serverban->hostmask, chost) && match_simple(tmp->ptr.serverban->usermask, cname)) :
-				    (match_simple(tmp->ptr.serverban->hostmask, chost) || match_simple(tmp->ptr.serverban->hostmask, cip))
-				    && match_simple(tmp->ptr.serverban->usermask, cname))
-				{
-					/*
-					  before blindly marking this user as un-shunned, we need to check
-					  if the user is under any other existing shuns. (#0003906)
-					  Unfortunately, this requires crazy amounts of indentation ;-).
-
-					  This enumeration code is based off of _tkl_stats()
-					 */
-					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->ptr.serverban->hostmask >= '0') && (*tk->ptr.serverban->hostmask <= '9')
-							    /* the hostmask is an IP */
-							    && (match_simple(tk->ptr.serverban->hostmask, chost) || match_simple(tk->ptr.serverban->hostmask, cip)))
-								keep_shun = 1;
-							else
-								/* the hostmask is not an IP */
-								if (match_simple(tk->ptr.serverban->hostmask, chost) && match_simple(tk->ptr.serverban->usermask, cname))
-									keep_shun = 1;
-						}
-
-					if (!keep_shun)
-					{
-						ClearShunned(client);
-					}
-				}
-			}
-	}
-}
-
-
-/** This returns something like user@host, or %user@host, or ~a:Trusted
- * 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)
-{
-	if (TKLIsServerBan(tkl))
-	{
-		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)) ? "%" : "",
-				tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
-		} else {
-			ircsnprintf(buf, buflen, "%s%s@%s",
-				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
-				tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
-		}
-	} else
-	if (TKLIsBanException(tkl))
-	{
-		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)) ? "%" : "",
-				tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask);
-		} else {
-			ircsnprintf(buf, buflen, "%s%s@%s",
-				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
-				tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask);
-		}
-	} else
-		abort();
-
-	return buf;
-}
-
-/** Deal with expiration of a specific TKL entry.
- * This is a helper function for tkl_check_expire().
- */
-void tkl_expire_entry(TKL *tkl)
-{
-	if (TKLIsServerBan(tkl))
-	{
-		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)
-		{
-			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))
-	{
-		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);
-
-	RunHook(HOOKTYPE_TKL_DEL, NULL, tkl);
-	tkl_del_line(tkl);
-}
-
-/** Regularly check TKL entries for expiration */
-EVENT(tkl_check_expire)
-{
-	TKL *tkl, *next;
-	time_t nowtime;
-	int index, index2;
-
-	nowtime = TStime();
-
-	/* First, hashed entries.. */
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = next)
-			{
-				next = tkl->next;
-				if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
-				{
-					tkl_expire_entry(tkl);
-				}
-			}
-		}
-	}
-
-	/* Now normal entries.. */
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = next)
-		{
-			next = tkl->next;
-			if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
-			{
-				tkl_expire_entry(tkl);
-			}
-		}
-	}
-}
-
-/* This is just a helper function for find_tkl_exception() */
-static int find_tkl_exception_matcher(Client *client, int ban_type, TKL *except_tkl)
-{
-	char uhost[NICKLEN+HOSTLEN+1];
-
-	if (!TKLIsBanException(except_tkl))
-		return 0;
-
-	if (!tkl_banexception_matches_type(except_tkl, ban_type))
-		return 0;
-
-	/* For config file except ban { } we use security groups instead of simple user/host */
-	if (except_tkl->ptr.banexception->match)
-		return user_allowed_by_security_group(client, except_tkl->ptr.banexception->match);
-
-	tkl_uhost(except_tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
-
-	if (match_user(uhost, client, MATCH_CHECK_REAL))
-	{
-		if (!(except_tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT))
-			return 1; /* hard ban exempt */
-		if ((except_tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) && IsLoggedIn(client))
-			return 1; /* soft ban exempt - only matches if user is logged in */
-	}
-
-	return 0; /* not found */
-}
-
-/** Search for TKL Exceptions for this user.
- * @param ban_type   The ban type to check, normally ban_tkl->type.
- * @param client     The user
- * @returns 1 if ban exempt, 0 if not.
- * @note
- * If you have a TKL ban that matched, say, 'ban_tkl'.
- * Then you call this function like this:
- * if (find_tkl_exception(ban_tkl->type, client))
- *     return 0; // User is exempt
- * [.. continue and ban the user..]
- */
-int _find_tkl_exception(int ban_type, Client *client)
-{
-	TKL *tkl;
-	int index, index2;
-	Hook *hook;
-
-	if (IsServer(client) || IsMe(client))
-		return 1;
-
-	/* First, the TKL ip hash table entries.. */
-	index = tkl_ip_hash_type('e');
-	index2 = tkl_ip_hash(GetIP(client));
-	if (index2 >= 0)
-	{
-		for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-		{
-			if (find_tkl_exception_matcher(client, ban_type, tkl))
-				return 1; /* exempt */
-		}
-	}
-
-	/* If not banned (yet), then check regular entries.. */
-	for (tkl = tklines[tkl_hash('e')]; tkl; tkl = tkl->next)
-	{
-			if (find_tkl_exception_matcher(client, ban_type, tkl))
-				return 1; /* exempt */
-	}
-
-	for (hook = Hooks[HOOKTYPE_TKL_EXCEPT]; hook; hook = hook->next)
-	{
-		if (hook->func.intfunc(client, ban_type) > 0)
-			return 1; /* exempt by hook */
-	}
-	return 0; /* Not exempt */
-}
-
-/** Helper function for find_tkline_match() */
-int find_tkline_match_matcher(Client *client, int skip_soft, TKL *tkl)
-{
-	char uhost[NICKLEN+HOSTLEN+1];
-
-	if (!TKLIsServerBan(tkl) || (tkl->type & TKL_SHUN))
-		return 0;
-
-	if (skip_soft && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT))
-		return 0;
-
-	tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
-
-	if (match_user(uhost, client, MATCH_CHECK_REAL))
-	{
-		/* If hard-ban, or soft-ban&unauthenticated.. */
-		if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
-		    ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(client)))
-		{
-			/* Found match. Now check for exception... */
-			if (find_tkl_exception(tkl->type, client))
-				return 0; /* exempted */
-			return 1; /* banned */
-		}
-	}
-
-	return 0; /* no match */
-}
-
-/** Check if user matches a *LINE. If so, kill the user.
- * @retval 1 if client is banned, 0 if not
- * @note Do not continue processing if the client is killed (0 return value).
- * @note Return value changed with regards to UnrealIRCd 4!
- */
-int _find_tkline_match(Client *client, int skip_soft)
-{
-	TKL *tkl;
-	int banned = 0;
-	int index, index2;
-
-	if (IsServer(client) || IsMe(client))
-		return 0;
-
-	/* First, the TKL ip hash table entries.. */
-	index2 = tkl_ip_hash(GetIP(client));
-	if (index2 >= 0)
-	{
-		for (index = 0; index < TKLIPHASHLEN1; index++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				banned = find_tkline_match_matcher(client, skip_soft, tkl);
-				if (banned)
-					break;
-			}
-			if (banned)
-				break;
-		}
-	}
-
-	/* If not banned (yet), then check regular entries.. */
-	if (!banned)
-	{
-		for (index = 0; index < TKLISTLEN; index++)
-		{
-			for (tkl = tklines[index]; tkl; tkl = tkl->next)
-			{
-				banned = find_tkline_match_matcher(client, skip_soft, tkl);
-				if (banned)
-					break;
-			}
-			if (banned)
-				break;
-		}
-	}
-
-	if (!banned)
-		return 0;
-
-	/* User is banned... */
-
-	RunHookReturnInt(HOOKTYPE_FIND_TKLINE_MATCH, !=99, client, tkl);
-
-	if (tkl->type & TKL_KILL)
-	{
-		ircstats.is_ref++;
-		if (tkl->type & TKL_GLOBAL)
-			banned_client(client, "G-Lined", tkl->ptr.serverban->reason, 1, 0);
-		else
-			banned_client(client, "K-Lined", tkl->ptr.serverban->reason, 0, 0);
-		return 1; /* killed */
-	} else
-	if (tkl->type & TKL_ZAP)
-	{
-		ircstats.is_ref++;
-		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
-		return 1; /* killed */
-	}
-
-	return 0;
-}
-
-/** Check if user is shunned.
- * @param client   Client to check.
- * @returns 1 if shunned, 0 if not.
- */
-int _find_shun(Client *client)
-{
-	TKL *tkl;
-
-	if (IsServer(client) || IsMe(client))
-		return 0;
-
-	if (IsShunned(client))
-		return 1;
-
-	if (ValidatePermissionsForPath("immune:server-ban:shun",client,NULL,NULL,NULL))
-		return 0;
-
-	for (tkl = tklines[tkl_hash('s')]; tkl; tkl = tkl->next)
-	{
-		char uhost[NICKLEN+HOSTLEN+1];
-
-		if (!(tkl->type & TKL_SHUN))
-			continue;
-
-		tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
-
-		if (match_user(uhost, client, MATCH_CHECK_REAL))
-		{
-			/* If hard-ban, or soft-ban&unauthenticated.. */
-			if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
-			    ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(client)))
-			{
-				/* Found match. Now check for exception... */
-				if (find_tkl_exception(TKL_SHUN, client))
-					return 0;
-				SetShunned(client);
-				return 1;
-			}
-		}
-	}
-
-	return 0;
-}
-
-/** Helper function for spamfilter_build_user_string().
- * This ensures IPv6 hosts are in brackets.
- */
-char *SpamfilterMagicHost(char *i)
-{
-	static char buf[256];
-
-	if (!strchr(i, ':'))
-		return i;
-
-	/* otherwise, it's IPv6.. prepend it with [ and append a ] */
-	ircsnprintf(buf, sizeof(buf), "[%s]", i);
-	return buf;
-}
-
-/** Build the nick:user@host:realname string
- * @param buf     The buffer used for storage, the size of
- *                which should be at least NICKLEN+USERLEN+HOSTLEN+1.
- * @param nick    The nickname (because client can be nick-changing).
- * @param client  The affected client.
- */
-void _spamfilter_build_user_string(char *buf, char *nick, Client *client)
-{
-	snprintf(buf, NICKLEN+USERLEN+HOSTLEN+1, "%s!%s@%s:%s",
-		nick, client->user->username, SpamfilterMagicHost(client->user->realhost), client->info);
-}
-
-
-/** Checks if the user matches a spamfilter of type 'u' (user,
- * nick!user@host:realname ban).
- * Written by: Syzop
- * Assumes: only call for clients, possible assume on local clients [?]
- * Return values: see match_spamfilter()
- */
-int _find_spamfilter_user(Client *client, int flags)
-{
-	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
-
-	if (ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL))
-		return 0;
-
-	spamfilter_build_user_string(spamfilter_user, client->name, client);
-	return match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, flags, NULL);
-}
-
-/** Check a spamfilter against all local users and print a message.
- * This is only used for the 'warn' action (BAN_ACT_WARN).
- */
-int spamfilter_check_users(TKL *tkl)
-{
-	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
-	char buf[1024];
-	int matches = 0;
-	Client *client;
-
-	list_for_each_entry_reverse(client, &lclient_list, lclient_node)
-	{
-		if (MyUser(client))
-		{
-			spamfilter_build_user_string(spamfilter_user, client->name, client);
-			if (!unreal_match(tkl->ptr.spamfilter->match, spamfilter_user))
-				continue; /* No match */
-
-			/* matched! */
-			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));
-
-			RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, spamfilter_user, spamfilter_user, SPAMF_USER, NULL, tkl);
-			matches++;
-		}
-	}
-
-	return matches;
-}
-
-/** Check if the nick or channel name is banned (Q-Line).
- * @param client   The possibly affected user.
- * @param name     The nick or channel to check.
- * @param is_hold  This will be SET (so OUT) if it's a services hold.
- *
- * @note Special handling:
- * #*ble* will match with #bbleh
- * *ble* will NOT match with #bbleh, will with bbleh
- */
-TKL *_find_qline(Client *client, char *name, int *ishold)
-{
-	TKL *tkl;
-	int	points = 0;
-	*ishold = 0;
-
-	if (IsServer(client) || IsMe(client))
-		return NULL;
-
-	for (tkl = tklines[tkl_hash('q')]; tkl; tkl = tkl->next)
-	{
-		points = 0;
-
-		if (!TKLIsNameBan(tkl))
-			continue;
-
-		if (((*tkl->ptr.nameban->name == '#' && *name == '#') || (*tkl->ptr.nameban->name != '#' && *name != '#'))
-		    && match_simple(tkl->ptr.nameban->name, name))
-		{
-			points = 1;
-			break;
-		}
-	}
-
-	if (points != 1)
-		return NULL;
-
-	/* It's a services hold (except bans don't override this) */
-	if (tkl->ptr.nameban->hold)
-	{
-		*ishold = 1;
-		return tkl;
-	}
-
-	if (find_tkl_exception(TKL_NAME, client))
-		return NULL; /* exempt */
-
-	return tkl;
-}
-
-/** Helper function for find_tkline_match_zap() */
-TKL *find_tkline_match_zap_matcher(Client *client, TKL *tkl)
-{
-	if (!(tkl->type & TKL_ZAP))
-		return NULL;
-
-	if (match_user(tkl->ptr.serverban->hostmask, client, MATCH_CHECK_IP))
-	{
-		if (find_tkl_exception(TKL_ZAP, client))
-			return NULL; /* exempt */
-		return tkl; /* banned */
-	}
-
-	return NULL; /* no match */
-}
-
-/** Find matching (G)ZLINE, if any.
- * Note: function prototype changed as per UnrealIRCd 4.2.0.
- * @retval The (G)Z-Line that matched, or NULL if no such ban was found.
- */
-TKL *_find_tkline_match_zap(Client *client)
-{
-	TKL *tkl, *ret;
-	int index, index2;
-
-	if (IsServer(client) || IsMe(client))
-		return NULL;
-
-	/* First, the TKL ip hash table entries.. */
-	index = tkl_ip_hash_type('z');
-	index2 = tkl_ip_hash(GetIP(client));
-	if (index2 >= 0)
-	{
-		for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-		{
-			ret = find_tkline_match_zap_matcher(client, tkl);
-			if (ret)
-				return ret;
-		}
-	}
-
-	/* If not banned (yet), then check regular entries.. */
-	for (tkl = tklines[tkl_hash('z')]; tkl; tkl = tkl->next)
-	{
-		ret = find_tkline_match_zap_matcher(client, tkl);
-		if (ret)
-			return ret;
-	}
-
-	return NULL;
-}
-
-#define BY_MASK 0x1
-#define BY_REASON 0x2
-#define NOT_BY_MASK 0x4
-#define NOT_BY_REASON 0x8
-#define BY_SETBY 0x10
-#define NOT_BY_SETBY 0x20
-
-typedef struct {
-	int flags;
-	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(const char *para, TKLFlag *flag)
-{
-	static char paratmp[512]; /* <- copy of para, because it gets fragged by strtok() */
-	char *flags, *tmp;
-	char what = '+';
-
-	memset(flag, 0, sizeof(TKLFlag));
-	strlcpy(paratmp, para, sizeof(paratmp));
-	flags = strtok(paratmp, " ");
-	if (!flags)
-		return;
-
-	for (; *flags; flags++)
-	{
-		switch (*flags)
-		{
-			case '+':
-				what = '+';
-				break;
-			case '-':
-				what = '-';
-				break;
-			case 'm':
-				if (flag->mask || !(tmp = strtok(NULL, " ")))
-					continue;
-				if (what == '+')
-					flag->flags |= BY_MASK;
-				else
-					flag->flags |= NOT_BY_MASK;
-				flag->mask = tmp;
-				break;
-			case 'r':
-				if (flag->reason || !(tmp = strtok(NULL, " ")))
-					continue;
-				if (what == '+')
-					flag->flags |= BY_REASON;
-				else
-					flag->flags |= NOT_BY_REASON;
-				flag->reason = tmp;
-				break;
-			case 's':
-				if (flag->set_by || !(tmp = strtok(NULL, " ")))
-					continue;
-				if (what == '+')
-					flag->flags |= BY_SETBY;
-				else
-					flag->flags |= NOT_BY_SETBY;
-				flag->set_by = tmp;
-				break;
-		}
-	}
-}
-
-/** Does this TKL entry match the search terms?
- * This is a helper function for tkl_stats().
- */
-int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklflags, TKL *tkl)
-{
-	/***** First, handle the selection ******/
-
-	if (!BadPtr(para))
-	{
-		if (tklflags->flags & BY_SETBY)
-			if (!match_simple(tklflags->set_by, tkl->set_by))
-				return 0;
-		if (tklflags->flags & NOT_BY_SETBY)
-			if (match_simple(tklflags->set_by, tkl->set_by))
-				return 0;
-		if (TKLIsServerBan(tkl))
-		{
-			if (tklflags->flags & BY_MASK)
-			{
-				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
-					return 0;
-			}
-			if (tklflags->flags & NOT_BY_MASK)
-			{
-				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
-					return 0;
-			}
-			if (tklflags->flags & BY_REASON)
-				if (!match_simple(tklflags->reason, tkl->ptr.serverban->reason))
-					return 0;
-			if (tklflags->flags & NOT_BY_REASON)
-				if (match_simple(tklflags->reason, tkl->ptr.serverban->reason))
-					return 0;
-		} else
-		if (TKLIsNameBan(tkl))
-		{
-			if (tklflags->flags & BY_MASK)
-			{
-				if (!match_simple(tklflags->mask, tkl->ptr.nameban->name))
-					return 0;
-			}
-			if (tklflags->flags & NOT_BY_MASK)
-			{
-				if (match_simple(tklflags->mask, tkl->ptr.nameban->name))
-					return 0;
-			}
-			if (tklflags->flags & BY_REASON)
-				if (!match_simple(tklflags->reason, tkl->ptr.nameban->reason))
-					return 0;
-			if (tklflags->flags & NOT_BY_REASON)
-				if (match_simple(tklflags->reason, tkl->ptr.nameban->reason))
-					return 0;
-		} else
-		if (TKLIsBanException(tkl))
-		{
-			if (tklflags->flags & BY_MASK)
-			{
-				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
-					return 0;
-			}
-			if (tklflags->flags & NOT_BY_MASK)
-			{
-				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
-					return 0;
-			}
-			if (tklflags->flags & BY_REASON)
-				if (!match_simple(tklflags->reason, tkl->ptr.banexception->reason))
-					return 0;
-			if (tklflags->flags & NOT_BY_REASON)
-				if (match_simple(tklflags->reason, tkl->ptr.banexception->reason))
-					return 0;
-		}
-	}
-
-	/***** If we are still here then we have a match and will will send the STATS entry */
-	if (TKLIsServerBan(tkl))
-	{
-		char uhostbuf[BUFSIZE];
-		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-		if (tkl->type == (TKL_KILL | TKL_GLOBAL))
-		{
-			sendnumeric(client, RPL_STATSGLINE, 'G', uhost,
-				   (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) ? (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) ? (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) ? (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) ? (long long)(tkl->expire_at - TStime()) : 0,
-				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
-		}
-	} else
-	if (TKLIsSpamfilter(tkl))
-	{
-		sendnumeric(client, RPL_STATSSPAMF,
-			(tkl->type & TKL_GLOBAL) ? 'F' : 'f',
-			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) ? (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"))
-		{
-			char *hash = spamfilter_id(tkl);
-			if (tkl->type & TKL_GLOBAL)
-			{
-				sendtxtnumeric(client, "To delete this spamfilter, use /SPAMFILTER del %s", hash);
-				sendtxtnumeric(client, "-");
-			} else {
-				sendtxtnumeric(client, "This spamfilter is stored in the configuration file and cannot be removed with /SPAMFILTER del");
-				sendtxtnumeric(client, "-");
-			}
-		}
-	} else
-	if (TKLIsNameBan(tkl))
-	{
-		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))
-	{
-		if (tkl->ptr.banexception->match)
-		{
-			/* Config-added: uses security groups */
-			NameValuePrioList *m;
-			for (m = tkl->ptr.banexception->match->printable_list; m; m = m->next)
-			{
-				sendnumeric(client, RPL_STATSEXCEPTTKL, namevalue_nospaces(m),
-					   tkl->ptr.banexception->bantypes,
-					   (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 {
-			/* IRC-added: uses simple user/host mask */
-			char uhostbuf[BUFSIZE];
-			char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
-			sendnumeric(client, RPL_STATSEXCEPTTKL, uhost,
-				   tkl->ptr.banexception->bantypes,
-				   (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 */
-		return 0;
-	}
-	return 1;
-}
-
-/* TKL Stats. This is used by /STATS gline and all the others */
-void _tkl_stats(Client *client, int type, const char *para, int *cnt)
-{
-	TKL *tk;
-	TKLFlag tklflags;
-	int index, index2;
-
-	if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
-		return;
-
-	if (!BadPtr(para))
-		parse_stats_params(para, &tklflags);
-
-	/* First the IP hashed entries (if applicable).. */
-	index = tkl_ip_hash_type(tkl_typetochar(type));
-	if (index >= 0)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tk = tklines_ip_hash[index][index2]; tk; tk = tk->next)
-			{
-				if (type && tk->type != type)
-					continue;
-				if (tkl_stats_matcher(client, type, para, &tklflags, tk))
-				{
-					*cnt += 1;
-					if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
-					{
-						sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
-						sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
-						return;
-					}
-				}
-			}
-		}
-	}
-
-	/* Then the normal entries... */
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tk = tklines[index]; tk; tk = tk->next)
-		{
-			if (type && tk->type != type)
-				continue;
-			if (tkl_stats_matcher(client, type, para, &tklflags, tk))
-			{
-				*cnt += 1;
-				if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
-				{
-					sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
-					sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
-					return;
-				}
-			}
-		}
-	}
-
-	if ((type == (TKL_SPAMF|TKL_GLOBAL)) && (!para || strcasecmp(para, "del")))
-	{
-		/* If requesting spamfilter stats and not spamfilter del, then suggest it. */
-		sendnotice(client, "Tip: if you are looking for an easy way to remove a spamfilter, run '/SPAMFILTER del'.");
-	}
-}
-
-/** Synchronize a TKL entry with the other server.
- * @param sender  The sender (eg: &me).
- * @param to      The remote server.
- * @param tkl     The TKL entry.
- */
-void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
-{
-	char typ;
-
-	if (!(tkl->type & TKL_GLOBAL))
-		return; /* nothing to sync */
-
-	typ = tkl_typetochar(tkl->type);
-
-	if (TKLIsServerBan(tkl))
-	{
-		sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld :%s", sender->name,
-			   add ? '+' : '-',
-			   typ,
-			   (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
-			   *tkl->ptr.serverban->usermask ? tkl->ptr.serverban->usermask : "*",
-			   tkl->ptr.serverban->hostmask, tkl->set_by,
-			   (long long)tkl->expire_at, (long long)tkl->set_at,
-			   tkl->ptr.serverban->reason);
-	} else
-	if (TKLIsNameBan(tkl))
-	{
-		sendto_one(to, NULL, ":%s TKL %c %c %c %s %s %lld %lld :%s", sender->name,
-			   add ? '+' : '-',
-			   typ,
-			   tkl->ptr.nameban->hold ? 'H' : '*',
-			   tkl->ptr.nameban->name,
-			   tkl->set_by,
-			   (long long)tkl->expire_at, (long long)tkl->set_at,
-			   tkl->ptr.nameban->reason);
-	} else
-	if (TKLIsSpamfilter(tkl))
-	{
-		sendto_one(to, NULL, ":%s TKL %c %c %s %c %s %lld %lld %lld %s %s :%s", sender->name,
-			   add ? '+' : '-',
-			   typ,
-			   spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
-			   banact_valtochar(tkl->ptr.spamfilter->action),
-			   tkl->set_by,
-			   (long long)tkl->expire_at, (long long)tkl->set_at,
-			   (long long)tkl->ptr.spamfilter->tkl_duration, tkl->ptr.spamfilter->tkl_reason,
-			   unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
-			   tkl->ptr.spamfilter->match->str);
-	} else
-	if (TKLIsBanException(tkl))
-	{
-		sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld %s :%s", sender->name,
-			   add ? '+' : '-',
-			   typ,
-			   (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
-			   *tkl->ptr.banexception->usermask ? tkl->ptr.banexception->usermask : "*",
-			   tkl->ptr.banexception->hostmask, tkl->set_by,
-			   (long long)tkl->expire_at, (long long)tkl->set_at,
-			   tkl->ptr.banexception->bantypes,
-			   tkl->ptr.banexception->reason);
-	} else
-	{
-		unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_SYNC_SEND_ENTRY", NULL,
-			   "[BUG] tkl_sync_send_entry() called, but unknown type: $tkl.type_string ($tkl_type_int)",
-			   log_data_tkl("tkl", tkl),
-			   log_data_integer("tkl_type_int", typ));
-		abort();
-	}
-}
-
-/** Broadcast a TKL entry.
- * @param sender  The sender, eg &me
- * @param skip    The client to skip, eg 'client' or NULL.
- * @param tkl     The TKL entry to synchronize with the other servers.
- */
-void tkl_broadcast_entry(int add, Client *sender, Client *skip, TKL *tkl)
-{
-	Client *acptr;
-
-	/* Silly fix for RPC calls that lead to broadcasts from this sender */
-	if (!IsUser(sender) && !IsServer(sender))
-		sender = &me;
-
-	list_for_each_entry(acptr, &server_list, special_node)
-	{
-		if (skip && acptr == skip->direction)
-			continue;
-
-		tkl_sync_send_entry(add, sender, acptr, tkl);
-	}
-}
-
-/** Synchronize all TKL entries with this server.
- * @param client The server to synchronize with.
- */
-void _tkl_sync(Client *client)
-{
-	TKL *tkl;
-	int index, index2;
-
-	/* First, hashed entries.. */
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				tkl_sync_send_entry(1, &me, client, tkl);
-			}
-		}
-	}
-
-	/* Then, regular entries.. */
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			tkl_sync_send_entry(1, &me, client, tkl);
-		}
-	}
-}
-
-/** Find a server ban TKL - only used to prevent duplicates and for deletion */
-TKL *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban)
-{
-	char tpe = tkl_typetochar(type);
-	TKL *head, *tkl;
-
-	if (!TKLIsServerBanType(type))
-		abort();
-
-	head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
-	for (tkl = head; tkl; tkl = tkl->next)
-	{
-		if (tkl->type == type)
-		{
-			if (!strcasecmp(tkl->ptr.serverban->hostmask, hostmask) &&
-			    !strcasecmp(tkl->ptr.serverban->usermask, usermask))
-			{
-				/* And an extra check for soft/hard ban mismatches.. */
-				if ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) == softban)
-					return tkl;
-			}
-		}
-	}
-	return NULL; /* Not found */
-}
-
-/** Find a ban exception TKL - only used to prevent duplicates and for deletion */
-TKL *_find_tkl_banexception(int type, char *usermask, char *hostmask, int softban)
-{
-	char tpe = tkl_typetochar(type);
-	TKL *head, *tkl;
-
-	if (!TKLIsBanExceptionType(type))
-		abort();
-
-	head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
-	for (tkl = head; tkl; tkl = tkl->next)
-	{
-		if (tkl->type == type)
-		{
-			if (!strcasecmp(tkl->ptr.banexception->hostmask, hostmask) &&
-			    !strcasecmp(tkl->ptr.banexception->usermask, usermask))
-			{
-				/* And an extra check for soft/hard ban mismatches.. */
-				if ((tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) == softban)
-					return tkl;
-			}
-		}
-	}
-	return NULL; /* Not found */
-}
-
-/** Find a name ban TKL (qline) - only used to prevent duplicates and for deletion */
-TKL *_find_tkl_nameban(int type, char *name, int hold)
-{
-	char tpe = tkl_typetochar(type);
-	TKL *tkl;
-
-	if (!TKLIsNameBanType(type))
-		abort();
-
-	for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
-	{
-		if ((tkl->type == type) && !strcasecmp(tkl->ptr.nameban->name, name))
-			return tkl;
-	}
-	return NULL; /* Not found */
-}
-
-/** Find a spamfilter TKL - only used to prevent duplicates and for deletion */
-TKL *_find_tkl_spamfilter(int type, char *match_string, BanAction action, unsigned short target)
-{
-	char tpe = tkl_typetochar(type);
-	TKL *tkl;
-
-	if (!TKLIsSpamfilterType(type))
-		abort();
-
-	for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
-	{
-		if ((type == tkl->type) &&
-		    !strcmp(match_string, tkl->ptr.spamfilter->match->str) &&
-		    (action == tkl->ptr.spamfilter->action) &&
-		    (target == tkl->ptr.spamfilter->target))
-		{
-			return tkl;
-		}
-	}
-	return NULL; /* Not found */
-}
-
-/** Send a notice to opers about the TKL that is being added */
-void _sendnotice_tkl_add(TKL *tkl)
-{
-	/* Don't show notices for temporary nick holds (issued by services) */
-	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
-		return;
-
-	if (TKLIsServerBan(tkl))
-	{
-		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))
-	{
-		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))
-	{
-		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))
-	{
-		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
-	{
-		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
-		           "[BUG] TKL added of unknown type, unhandled in sendnotice_tkl_add()!!!!");
-	}
-}
-
-/** Send a notice to opers about the TKL that is being deleted */
-void _sendnotice_tkl_del(char *removed_by, TKL *tkl)
-{
-	/* Don't show notices for temporary nick holds (issued by services) */
-	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
-		return;
-
-	if (TKLIsServerBan(tkl))
-	{
-		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))
-	{
-		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))
-	{
-		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))
-	{
-		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
-	{
-		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
-		           "[BUG] TKL removed of unknown type, unhandled in sendnotice_tkl_del()!!!!");
-	}
-}
-
-/** Called when a TKL is added by a remote user, local user, RPC user, ..
- * (but not when a TKL is added via the config)
- */
-void _tkl_added(Client *client, TKL *tkl)
-{
-	RunHook(HOOKTYPE_TKL_ADD, client, tkl);
-
-	sendnotice_tkl_add(tkl);
-
-	/* spamfilter 'warn' action is special */
-	if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
-		spamfilter_check_users(tkl);
-
-	/* Ban checking executes during run loop for efficiency */
-	loop.do_bancheck = 1;
-
-	if (tkl->type & TKL_GLOBAL)
-		tkl_broadcast_entry(1, client, client, tkl);
-}
-
-/** Add a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
-CMD_FUNC(cmd_tkl_add)
-{
-	TKL *tkl;
-	int type;
-	time_t expire_at, set_at;
-	const char *set_by;
-	char tkl_entry_exists = 0;
-
-	/* we rely on servers to be failsafe.. */
-	if (!IsServer(client) && !IsMe(client))
-		return;
-
-	if (parc < 9)
-		return;
-
-	type = tkl_chartotype(parv[2][0]);
-	if (!type)
-		return;
-
-	/* All TKL types have the following fields in common when adding:
-	 * parv[5]: set_by
-	 * parv[6]: expire_at
-	 * parv[7]: set_at
-	 * ... so we validate them here at the beginning.
-	 */
-
-	set_by = parv[5];
-	expire_at = atol(parv[6]);
-	set_at = atol(parv[7]);
-
-	/* Validate set and expiry time */
-	if ((set_at < 0) || !short_date(set_at, NULL))
-	{
-		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))
-	{
-		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;
-	}
-
-	/* Now comes type-specific validation
-	 * and we check if the TKL entry already exists and needs updating too.
-	 */
-
-	if (TKLIsServerBanType(type))
-	{
-		/* Validate server ban TKL fields */
-		int softban = 0;
-		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
-		 * linked servers are known to have sent this in the past.
-		 */
-		if (strchr(usermask, '@') || strchr(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;
-		}
-
-		/* In case of a soft ban, strip the percent sign early,
-		 * so parv[3] (username) is really the username without any prefix.
-		 * Set the 'softban' flag if this is the case.
-		 */
-		if (*usermask == '%')
-		{
-			usermask++;
-			softban = 1;
-		}
-
-		tkl = find_tkl_serverban(type, usermask, hostmask, softban);
-		if (tkl)
-		{
-			tkl_entry_exists = 1;
-		} else {
-			tkl = tkl_add_serverban(type, usermask, hostmask, reason,
-			                        set_by, expire_at, set_at, softban, 0);
-		}
-	} else
-	if (TKLIsBanExceptionType(type))
-	{
-		/* Validate ban exception TKL fields */
-		int softban = 0;
-		const char *usermask = parv[3];
-		const char *hostmask = parv[4];
-		const char *bantypes = parv[8];
-		const char *reason;
-
-		if (parc < 10)
-			return;
-
-		reason = parv[9];
-
-		/* Some simple validation on usermask and hostmask:
-		 * may not contain an @. Yeah, some services or self-written
-		 * linked servers are known to have sent this in the past.
-		 */
-		if (strchr(usermask, '@') || strchr(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;
-		}
-
-		/* In case of a soft ban, strip the percent sign early,
-		 * so parv[3] (username) is really the username without any prefix.
-		 * Set the 'softban' flag if this is the case.
-		 */
-		if (*usermask == '%')
-		{
-			usermask++;
-			softban = 1;
-		}
-
-		/* At this moment we do not validate 'bantypes' since a missing
-		 * or wrong type does not cause harm anyway.
-		 */
-		tkl = find_tkl_banexception(type, usermask, hostmask, softban);
-		if (tkl)
-		{
-			tkl_entry_exists = 1;
-		} else {
-			tkl = tkl_add_banexception(type, usermask, hostmask, NULL, reason,
-			                           set_by, expire_at, set_at, softban, bantypes, 0);
-		}
-	} else
-	if (TKLIsNameBanType(type))
-	{
-		/* Validate name ban TKL fields */
-		int hold = 0;
-		const char *name = parv[4];
-		const char *reason = parv[8];
-
-		if (*parv[3] == 'H')
-			hold = 1;
-
-		tkl = find_tkl_nameban(type, name, hold);
-		if (tkl)
-		{
-			tkl_entry_exists = 1;
-		} else {
-			tkl = tkl_add_nameban(type, name, hold, reason, set_by, expire_at,
-			                      set_at, 0);
-		}
-	} else
-	if (TKLIsSpamfilterType(type))
-	{
-		/* Validate spamfilter-specific TKL fields */
-		MatchType match_method;
-		const char *match_string;
-		Match *m; /* compiled match_string */
-		time_t tkl_duration;
-		const char *tkl_reason;
-		BanAction action;
-		unsigned short target;
-		/* helper variables */
-		char *err;
-
-		if (parc < 12)
-		{
-			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];
-
-		match_method = unreal_match_method_strtoval(parv[10]);
-		if (match_method == 0)
-		{
-			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
-				"Invalid TKL entry from $client: "
-				"Spamfilter '$spamfilter_string' has unknown 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)))
-		{
-			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
-				"Invalid TKL entry from $client: "
-				"Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'",
-				log_data_string("spamfilter_string", match_string),
-				log_data_string("spamfilter_targets", parv[3]));
-			return;
-		}
-
-		if (!(action = banact_chartoval(*parv[4])))
-		{
-			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
-				"Invalid TKL entry from $client: "
-				"Spamfilter '$spamfilter_string' has unknown action '$spamfilter_action'",
-				log_data_string("spamfilter_string", match_string),
-				log_data_string("spamfilter_action", parv[4]));
-			return;
-		}
-
-		tkl_duration = config_checkval(parv[8], CFG_TIME);
-		tkl_reason = parv[9];
-
-		tkl = find_tkl_spamfilter(type, match_string, action, target);
-
-		if (tkl)
-		{
-			tkl_entry_exists = 1;
-		} else {
-			m = unreal_create_match(match_method, match_string, &err);
-			if (!m)
-			{
-				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,
-			                         tkl_duration, tkl_reason, 0);
-		}
-	} else
-	{
-		/* Unhandled, should never happen */
-		abort();
-	}
-
-	if (!tkl)
-		return;
-
-	if (tkl_entry_exists)
-	{
-		/* Let's see if we need to update the existing entry.
-		 * Note that we only update common fields,
-		 * which is acceptable to me. -- Syzop
-		 */
-		if ((set_at < tkl->set_at) || (expire_at != tkl->expire_at) || strcmp(tkl->set_by, parv[5]))
-		{
-			/* here's how it goes:
-			 * set_at: oldest wins
-			 * expire_at: longest wins
-			 * set_by: highest strcmp wins
-			 *
-			 * We broadcast the result of this back to all servers except
-			 * sptr->direction, because that side will do the same thing and
-			 * send it back to his servers (except us)... no need for a
-			 * double networkwide flood ;p. -- Syzop
-			 */
-			tkl->set_at = MIN(tkl->set_at, set_at);
-
-			if (!tkl->expire_at || !expire_at)
-				tkl->expire_at = 0;
-			else
-				tkl->expire_at = MAX(tkl->expire_at, expire_at);
-
-			if (strcmp(tkl->set_by, parv[5]) < 0)
-				safe_strdup(tkl->set_by, parv[5]);
-
-			if (type & TKL_GLOBAL)
-				tkl_broadcast_entry(1, client, client, tkl);
-		}
-		return;
-	}
-
-	tkl_added(client, tkl);
-}
-
-/** Delete a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
-CMD_FUNC(cmd_tkl_del)
-{
-	TKL *tkl;
-	int type;
-	const char *removed_by;
-
-	if (!IsServer(client) && !IsMe(client))
-		return;
-
-	if (parc < 6)
-		return;
-
-	type = tkl_chartotype(parv[2][0]);
-	if (type == 0)
-		return;
-
-	removed_by = parv[5];
-
-	if (TKLIsServerBanType(type))
-	{
-		const char *usermask = parv[3];
-		const char *hostmask = parv[4];
-		int softban = 0;
-
-		if (*usermask == '%')
-		{
-			usermask++;
-			softban = 1;
-		}
-
-		tkl = find_tkl_serverban(type, usermask, hostmask, softban);
-	}
-	else if (TKLIsBanExceptionType(type))
-	{
-		const char *usermask = parv[3];
-		const char *hostmask = parv[4];
-		int softban = 0;
-		/* other parameters are ignored */
-
-		if (*usermask == '%')
-		{
-			usermask++;
-			softban = 1;
-		}
-
-		tkl = find_tkl_banexception(type, usermask, hostmask, softban);
-	}
-	else if (TKLIsNameBanType(type))
-	{
-		int hold = 0;
-		const char *name = parv[4];
-
-		if (*parv[3] == 'H')
-			hold = 1;
-		tkl = find_tkl_nameban(type, name, hold);
-	}
-	else if (TKLIsSpamfilterType(type))
-	{
-		const char *match_string;
-		unsigned short target;
-		BanAction action;
-
-		if (parc < 9)
-		{
-			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)
-			match_string = parv[11];
-		else if (parc >= 11)
-			match_string = parv[10];
-		else
-			match_string = parv[8];
-
-		if (!(target = spamfilter_gettargets(parv[3], NULL)))
-		{
-			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
-				"Invalid TKL deletion request from $client: "
-				"Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'",
-				log_data_string("spamfilter_string", match_string),
-				log_data_string("spamfilter_targets", parv[3]));
-			return;
-		}
-
-		if (!(action = banact_chartoval(*parv[4])))
-		{
-			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
-				"Invalid TKL deletion request from $client: "
-				"Spamfilter '$spamfilter_string' has unknown 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);
-	} else
-	{
-		/* This can never happen, unless someone added a TKL type
-		 * to UnrealIRCd but forgot to add the removal code :D.
-		 */
-		abort();
-	}
-
-	if (!tkl)
-		return; /* Item not found, nothing to remove. */
-
-	if (tkl->flags & TKL_FLAG_CONFIG)
-		return; /* Item is in the configuration file (persistent) */
-
-	/* broadcast remove msg to opers... */
-	sendnotice_tkl_del(removed_by, tkl);
-
-	if (type & TKL_SHUN)
-		tkl_check_local_remove_shun(tkl);
-
-	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
-
-	if (type & TKL_GLOBAL)
-	{
-		/* This is a bit of a hack for #5629. Will consider real fix post-release. */
-		safe_strdup(tkl->set_by, removed_by);
-		tkl_broadcast_entry(0, client, client, tkl);
-	}
-
-	if (TKLIsBanException(tkl))
-	{
-		/* Since an exception has been removed we have to re-check if
-		 * any connected user is now matched by a ban.
-		 * Set flag here, actual checking takes place in main loop.
-		 */
-		loop.do_bancheck = 1;
-	}
-
-	tkl_del_line(tkl);
-}
-
-/** TKL command: server to server handling of *LINEs and SPAMFILTERs.
- * HISTORY:
- * This was originall called Timed KLines, but today it's
- * used by various *line types eg: zline, gline, gzline, shun,
- * but also by spamfilter etc...
- * DOCUMENTATION
- * See (also) https://www.unrealircd.org/docs/Server_protocol:TKL_command
- * USAGE:
- * This routine is used both internally by the ircd (to
- * for example add local klines, zlines, etc) and over the
- * network (glines, gzlines, spamfilter, etc).
- *
- *           serverban  serverban  spamfilter      spamfilter         sqline:    ban exception:
- *           add:       remove:    remove in U4:   with TKLEXT2:
- * parv[ 1]: +          -          -               +/-                +/-        +/-
- * parv[ 2]: type       type       type            type               type       type
- * parv[ 3]: user       user       target          target             hold       user
- * parv[ 4]: host       host       action          action             host       host
- * parv[ 5]: set_by     removedby  (un)set_by      set_by/unset_by    set_by     set_by
- * parv[ 6]: expire_at             expire_at (0)   expire_at (0)      expire_at  expire_at
- * parv[ 7]: set_at                set_at          set_at             set_at     set_at
- * parv[ 8]: reason                regex           tkl duration       reason     except_type
- * parv[ 9]:                                       tkl reason [A]                reason
- * parv[10]:                                       match-type [B]
- * parv[11]:                                       match-string [C]
- *
- * [A] tkl reason field must be escaped by caller [eg: use unreal_encodespace()
- *     if cmd_tkl is called internally].
- * [B] match-type must be one of: regex, simple.
- * [C] Could be a regex or a regular string with wildcards, depending on [B]
- */
-CMD_FUNC(_cmd_tkl)
-{
-	if (!IsServer(client) && !IsOper(client) && !IsMe(client))
-		return;
-
-	if (parc < 2)
-		return;
-
-	switch (*parv[1])
-	{
-		case '+':
-			CALL_CMD_FUNC(cmd_tkl_add);
-			break;
-		case '-':
-			CALL_CMD_FUNC(cmd_tkl_del);
-			break;
-		default:
-			break;
-	}
-}
-
-/** 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, const char **tkl_username, const char **tkl_hostname)
-{
-	static char username[USERLEN+1];
-	static char hostname[HOSTLEN+8];
-
-	if ((action == BAN_ACT_ZLINE) || (action == BAN_ACT_GZLINE))
-		ban_target = BAN_TARGET_IP; /* The only possible choice with ZLINE/GZLINE, other info is unavailable */
-
-	if (ban_target == BAN_TARGET_ACCOUNT)
-	{
-		if (IsLoggedIn(client) && (*client->user->account != ':'))
-		{
-			/* Place a ban on ~a:Accountname */
-			strlcpy(username, "~a:", sizeof(username));
-			strlcpy(hostname, client->user->account, sizeof(hostname));
-			*tkl_username = username;
-			*tkl_hostname = hostname;
-			return;
-		}
-		ban_target = BAN_TARGET_IP; /* fallback */
-	} else
-	if (ban_target == BAN_TARGET_CERTFP)
-	{
-		const char *fp = moddata_client_get(client, "certfp");
-		if (fp)
-		{
-			/* Place a ban on ~S:sha256sumofclientcertificate */
-			strlcpy(username, "~S:", sizeof(username));
-			strlcpy(hostname, fp, sizeof(hostname));
-			*tkl_username = username;
-			*tkl_hostname = hostname;
-			return;
-		}
-		ban_target = BAN_TARGET_IP; /* fallback */
-	}
-
-	/* Below we deal with the more common choices... */
-
-	/* First, set the username */
-	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));
-
-	/* Now set the host-portion of the TKL */
-	if (((ban_target == BAN_TARGET_HOST) || (ban_target == BAN_TARGET_USERHOST)) && client->user && *client->user->realhost)
-		strlcpy(hostname, client->user->realhost, sizeof(hostname));
-	else
-		strlcpy(hostname, GetIP(client), sizeof(hostname));
-
-	*tkl_username = username;
-	*tkl_hostname = hostname;
-}
-
-/** Take an action on the user, such as banning or killing.
- * @author Bram Matthys (Syzop), 2003-present
- * @param client     The client which is affected.
- * @param action   The type of ban (one of BAN_ACT_*).
- * @param reason   The ban reason.
- * @param duration The ban duration in seconds.
- * @note This function assumes that client is a locally connected user.
- * @retval 1 if action is taken, 0 if user is exempted.
- * @note Be sure to check IsDead(client) if return value is 1 and you are
- *       considering to continue processing.
- */
-int _place_host_ban(Client *client, BanAction action, char *reason, long duration)
-{
-	/* If this is a soft action and the user is logged in, then the ban does not apply.
-	 * NOTE: Actually in such a case it would be better if place_host_ban() would not
-	 * be called at all. Or at least, the caller should not take any action
-	 * (eg: the message should be delivered, the user may connect, etc..)
-	 * The following is more like secondary protection in case the caller forgets...
-	 */
-	if (IsSoftBanAction(action) && IsLoggedIn(client))
-		return 0;
-
-	switch(action)
-	{
-		case BAN_ACT_TEMPSHUN:
-			/* We simply mark this connection as shunned and do not add a ban record */
-			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:
-		case BAN_ACT_GLINE:
-		case BAN_ACT_SOFT_GLINE:
-		case BAN_ACT_ZLINE:
-		case BAN_ACT_KLINE:
-		case BAN_ACT_SOFT_KLINE:
-		case BAN_ACT_SHUN:
-		case BAN_ACT_SOFT_SHUN:
-		{
-			char ip[128], user[USERLEN+3], mo[100], mo2[100];
-			const char *tkllayer[9] = {
-				me.name,	/*0  server.name */
-				"+",		/*1  +|- */
-				"?",		/*2  type */
-				"*",		/*3  user */
-				NULL,		/*4  host */
-				NULL,
-				NULL,		/*6  expire_at */
-				NULL,		/*7  set_at */
-				NULL		/*8  reason */
-			};
-
-			ban_target_to_tkl_layer(iConf.automatic_ban_target, action, client, &tkllayer[3], &tkllayer[4]);
-
-			/* For soft bans we need to prefix the % in the username */
-			if (IsSoftBanAction(action))
-			{
-				char tmp[USERLEN+3];
-				snprintf(tmp, sizeof(tmp), "%%%s", tkllayer[3]);
-				strlcpy(user, tmp, sizeof(user));
-				tkllayer[3] = user;
-			}
-
-			if ((action == BAN_ACT_KLINE) || (action == BAN_ACT_SOFT_KLINE))
-				tkllayer[2] = "k";
-			else if (action == BAN_ACT_ZLINE)
-				tkllayer[2] = "z";
-			else if (action == BAN_ACT_GZLINE)
-				tkllayer[2] = "Z";
-			else if ((action == BAN_ACT_GLINE) || (action == BAN_ACT_SOFT_GLINE))
-				tkllayer[2] = "G";
-			else if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
-				tkllayer[2] = "s";
-			tkllayer[5] = me.name;
-			if (!duration)
-				strlcpy(mo, "0", sizeof(mo)); /* perm */
-			else
-				ircsnprintf(mo, sizeof(mo), "%lld", (long long)(duration + TStime()));
-			ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
-			tkllayer[6] = mo;
-			tkllayer[7] = mo2;
-			tkllayer[8] = reason;
-			cmd_tkl(&me, NULL, 9, tkllayer);
-			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
-			if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
-			{
-				find_shun(client);
-				return 1;
-			} /* else.. */
-			return find_tkline_match(client, 0);
-		}
-		case BAN_ACT_SOFT_KILL:
-		case BAN_ACT_KILL:
-		default:
-			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
-			exit_client(client, NULL, reason);
-			return 1;
-	}
-	return 0; /* no action taken (weird) */
-}
-
-/** This function compares two spamfilters ('one' and 'two') and will return
- * a 'winner' based on which one has the strongest action.
- * If both have equal action then some additional logic is applied simply
- * to ensure we (almost) always return the same winner regardless of the
- * order of the spamfilters (which may differ between servers).
- */
-TKL *choose_winning_spamfilter(TKL *one, TKL *two)
-{
-	int n;
-
-	if (!TKLIsSpamfilter(one) || !TKLIsSpamfilter(two))
-		abort();
-
-	/* First, see if the action field differs... */
-	if (one->ptr.spamfilter->action != two->ptr.spamfilter->action)
-	{
-		/* We can simply compare the action. Highest (strongest) wins. */
-		if (one->ptr.spamfilter->action > two->ptr.spamfilter->action)
-			return one;
-		else
-			return two;
-	}
-
-	/* Ok, try comparing the regex then.. */
-	n = strcmp(one->ptr.spamfilter->match->str, two->ptr.spamfilter->match->str);
-	if (n < 0)
-		return one;
-	if (n > 0)
-		return two;
-
-	/* Hmm.. regex is identical. Try the 'reason' field. */
-	n = strcmp(one->ptr.spamfilter->tkl_reason, two->ptr.spamfilter->tkl_reason);
-	if (n < 0)
-		return one;
-	if (n > 0)
-		return two;
-
-	/* Hmm.. 'reason' is identical as well.
-	 * Make a final decision, could still be identical but would be unlikely.
-	 */
-	return (one->ptr.spamfilter->target > two->ptr.spamfilter->target) ? one : two;
-}
-
-/** Checks if 'target' is on the spamfilter exception list.
- * RETURNS 1 if found in list, 0 if not.
- */
-static int target_is_spamexcept(const char *target)
-{
-	SpamExcept *e;
-
-	for (e = iConf.spamexcept; e; e = e->next)
-	{
-		if (match_simple(e->name, target))
-			return 1;
-	}
-	return 0;
-}
-
-/** Make user join the virus channel.
- * @param client  The user that was doing something bad.
- * @param tk    The TKL entry that matched this user.
- * @param type  The spamfilter type (SPAMF_*)
- *              TODO: Looks redundant?
- */
-int _join_viruschan(Client *client, TKL *tkl, int type)
-{
-	const char *xparv[3];
-	char chbuf[CHANNELLEN + 16], buf[2048];
-	Channel *channel;
-	int ret;
-
-	snprintf(buf, sizeof(buf), "0,%s", SPAMFILTER_VIRUSCHAN);
-	xparv[0] = NULL;
-	xparv[1] = buf;
-	xparv[2] = NULL;
-
-	/* RECURSIVE CAUTION in case we ever add blacklisted chans */
-	spamf_ugly_vchanoverride = 1;
-	do_cmd(client, NULL, "JOIN", 2, xparv);
-	spamf_ugly_vchanoverride = 0;
-
-	if (IsDead(client))
-		return 0; /* killed due to JOIN */
-
-	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);
-	if (channel)
-	{
-		MessageTag *mtags = NULL;
-		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, "o",
-		               0, SEND_ALL|SKIP_DEAF, mtags,
-		               ":%s NOTICE %s :%s", me.name, chbuf, buf);
-		free_message_tags(mtags);
-	}
-	SetVirus(client);
-	return 1;
-}
-
-/** match_spamfilter: executes the spamfilter on the input string.
- * @param str		The text (eg msg text, notice text, part text, quit text, etc
- * @param target	The spamfilter target (SPAMF_*)
- * @param cmd		The command (eg: "PRIVMSG")
- * @param destination	The destination as a text string (eg: "somenick", can be NULL.. eg for away)
- * @param flags		Any flags (SPAMFLAG_*)
- * @param rettkl	Pointer to an aTKLline struct, _used for special circumstances only_
- * RETURN VALUE:
- * 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, const char *str_in, int target, const char *cmd, const char *destination, int flags, TKL **rettkl)
-{
-	TKL *tkl;
-	TKL *winner_tkl = NULL;
-	const char *str;
-	int ret = -1;
-	char *reason = NULL;
-#ifdef SPAMFILTER_DETECTSLOW
-	struct rusage rnow, rprev;
-	long ms_past;
-#endif
-
-	if (rettkl)
-		*rettkl = NULL; /* initialize to NULL */
-
-	if (!cmd)
-		cmd = cmdname_by_spamftarget(target);
-
-	if (target == SPAMF_USER)
-		str = str_in;
-	else
-		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.
-	 */
-	if (!client->user || ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL) || IsULine(client))
-		return 0;
-
-	/* Client exempt from spamfilter checking?
-	 * Let's check that early: going through elines is likely faster than running the regex(es).
-	 */
-	if (find_tkl_exception(TKL_SPAMF, client))
-		return 0;
-
-	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
-	{
-		if (!(tkl->ptr.spamfilter->target & target))
-			continue;
-
-		if ((flags & SPAMFLAG_NOWARN) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN))
-			continue;
-
-		/* If the action is 'soft' (for non-logged in users only) then
-		 * don't bother running the spamfilter if the user is logged in.
-		 */
-		if (IsSoftBanAction(tkl->ptr.spamfilter->action) && IsLoggedIn(client))
-			continue;
-
-#ifdef SPAMFILTER_DETECTSLOW
-		memset(&rnow, 0, sizeof(rnow));
-		memset(&rprev, 0, sizeof(rnow));
-
-		getrusage(RUSAGE_SELF, &rprev);
-#endif
-
-		ret = unreal_match(tkl->ptr.spamfilter->match, str);
-
-#ifdef SPAMFILTER_DETECTSLOW
-		getrusage(RUSAGE_SELF, &rnow);
-
-		ms_past = ((rnow.ru_utime.tv_sec - rprev.ru_utime.tv_sec) * 1000) +
-		          ((rnow.ru_utime.tv_usec - rprev.ru_utime.tv_usec) / 1000);
-
-		if ((SPAMFILTER_DETECTSLOW_FATAL > 0) && (ms_past > SPAMFILTER_DETECTSLOW_FATAL))
-		{
-			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))
-		{
-			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! But.. perhaps it's on the exceptions list? */
-			if (!winner_tkl && destination && target_is_spamexcept(destination))
-				return 0; /* No problem! */
-
-			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
-			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
-				   log_data_tkl("tkl", tkl),
-				   log_data_string("command", cmd),
-				   log_data_string("_space", destination ? " " : ""),
-				   log_data_string("destination", destination ? destination : ""),
-				   log_data_string("str", str));
-
-			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)
-			{
-				winner_tkl = tkl;
-				break;
-			}
-
-			/* Otherwise.. we set 'winner_tkl' to the spamfilter with the strongest action. */
-			if (!winner_tkl)
-				winner_tkl = tkl;
-			else
-				winner_tkl = choose_winning_spamfilter(tkl, winner_tkl);
-
-			/* and continue.. */
-		}
-	}
-
-	tkl = winner_tkl;
-
-	if (!tkl)
-		return 0; /* NOMATCH, we are done */
-
-	/* Spamfilter matched, take action: */
-
-	reason = unreal_decodespace(tkl->ptr.spamfilter->tkl_reason);
-	if ((tkl->ptr.spamfilter->action == BAN_ACT_BLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_BLOCK))
-	{
-		switch(target)
-		{
-			case SPAMF_USERMSG:
-			case SPAMF_USERNOTICE:
-			{
-				char errmsg[512];
-				ircsnprintf(errmsg, sizeof(errmsg), "Message blocked: %s", reason);
-				sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg);
-				break;
-			}
-			case SPAMF_CHANNOTICE:
-				break; /* no replies to notices */
-			case SPAMF_CHANMSG:
-			{
-				sendto_one(client, NULL, ":%s 404 %s %s :Message blocked: %s",
-					me.name, client->name, destination, reason);
-				break;
-			}
-			case SPAMF_MTAG:
-			{
-				sendnumericfmt(client, ERR_CANNOTDOCOMMAND, "%s :Command blocked: %s",
-					cmd, reason);
-				break;
-			}
-			case SPAMF_DCC:
-			{
-				char errmsg[512];
-				ircsnprintf(errmsg, sizeof(errmsg), "DCC blocked: %s", reason);
-				sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg);
-				break;
-			}
-			case SPAMF_AWAY:
-				/* hack to deal with 'after-away-was-set-filters' */
-				if (client->user->away && !strcmp(str_in, client->user->away))
-				{
-					/* free away & broadcast the unset */
-					safe_free(client->user->away);
-					client->user->away = NULL;
-					sendto_server(client, 0, 0, NULL, ":%s AWAY", client->id);
-				}
-				break;
-			case SPAMF_TOPIC:
-				//...
-				sendnotice(client, "Setting of topic on %s to that text is blocked: %s",
-					destination, reason);
-				break;
-			default:
-				break;
-		}
-		return 1;
-	} else
-	if ((tkl->ptr.spamfilter->action == BAN_ACT_WARN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_WARN))
-	{
-		if ((target != SPAMF_USER) && (target != SPAMF_QUIT))
-			sendnumeric(client, RPL_SPAMCMDFWD, cmd, reason);
-		return 0;
-	} else
-	if ((tkl->ptr.spamfilter->action == BAN_ACT_DCCBLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_DCCBLOCK))
-	{
-		if (target == SPAMF_DCC)
-		{
-			sendnotice(client, "DCC to %s blocked: %s", destination, reason);
-			sendnotice(client, "*** You have been blocked from sending files, reconnect to regain permission to send files");
-			SetDCCBlock(client);
-		}
-		return 1;
-	} else
-	if ((tkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN))
-	{
-		if (IsVirus(client)) /* Already tagged */
-			return 0;
-
-		/* There's a race condition for SPAMF_USER, so 'rettk' is used for SPAMF_USER
-		 * when a user is currently connecting and filters are checked:
-		 */
-		if (!IsUser(client))
-		{
-			if (rettkl)
-				*rettkl = tkl;
-			return 1;
-		}
-
-		join_viruschan(client, tkl, target);
-		return 1;
-	} else
-	{
-		return place_host_ban(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration);
-	}
-
-	return 0; /* NOTREACHED */
-}
-
-/** Check message-tag spamfilters.
- * @param client	The client
- * @param mtags		Message tags sent by client
- * @param cmd		Command to be executed (can be NULL)
- * @retval Return 1 to stop processing the command (ignore it) or 0 to allow/continue as normal
- */
-int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd)
-{
-	MessageTag *m;
-	char buf[4096];
-	char *str;
-
-	/* This is a shortcut: if there are no spamfilters present
-	 * on message tags then we can return immediately.
-	 * Saves a lot of CPU and it is quite likely too!
-	 */
-	if (mtag_spamfilters_present == 0)
-		return 0;
-
-	for (m = mtags; m; m = m->next)
-	{
-		if (m->value)
-		{
-			snprintf(buf, sizeof(buf), "%s=%s", m->name, m->value);
-			str = buf;
-		} else {
-			str = m->name;
-		}
-		if (match_spamfilter(client, str, SPAMF_MTAG, cmd, NULL, 0, NULL))
-			return 1;
-	}
-	return 0;
-}
-
-/** Updates 'mtag_spamfilters_present' based on if any spamfilters
- * are present with the SPAMF_MTAG target.
- */
-int check_mtag_spamfilters_present(void)
-{
-	TKL *tkl;
-
-	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
-	{
-		if (tkl->ptr.spamfilter->target & SPAMF_MTAG)
-		{
-			mtag_spamfilters_present = 1;
-			return 1;
-		}
-	}
-
-	mtag_spamfilters_present = 0;
-	return 0;
-}
-
-/** CIDR function to compare the first 'mask' bits.
- * @author Taken from atheme
- * @returns 1 if equal, 0 if not.
- */
-static int comp_with_mask(void *addr, void *dest, u_int mask)
-{
-	if (memcmp(addr, dest, mask / 8) == 0)
-	{
-		int n = mask / 8;
-		int m = (0xffff << (8 - (mask % 8)));
-		if (mask % 8 == 0 || (((u_char *) addr)[n] & m) == (((u_char *) dest)[n] & m))
-		{
-			return (1);
-		}
-	}
-	return (0);
-}
-
-#define IPSZ 16
-
-/** Match a user against a mask.
- * This will deal with 'nick!user@host', 'user@host' and just 'host'.
- * We try to match the 'host' portion against the client IP, real host, etc...
- * 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(const char *rmask, Client *client, int options)
-{
-	char mask[NICKLEN+USERLEN+HOSTLEN+8];
-	char clientip[IPSZ], maskip[IPSZ];
-	char *p = NULL;
-	char *nmask = NULL, *umask = NULL, *hmask = NULL;
-	int cidr = -1; /* CIDR length, -1 for no CIDR */
-
-	strlcpy(mask, rmask, sizeof(mask));
-
-	if ((options & MATCH_CHECK_EXTENDED) &&
-	    is_extended_server_ban(mask) &&
-	    client->user)
-	{
-		/* Check user properties / extbans style */
-		return _match_user_extended_server_ban(rmask, client);
-	}
-
-	if (!(options & MATCH_MASK_IS_UHOST))
-	{
-		p = strchr(mask, '!');
-		if (p)
-		{
-			*p++ = '\0';
-			if (!*mask)
-				return 0; /* NOMATCH: '!...' */
-			nmask = mask;
-			umask = p;
-
-			/* Could just as well check nick right now */
-			if (!match_simple(nmask, client->name))
-				return 0; /* NOMATCH: nick mask did not match */
-		}
-	}
-
-	if (!(options & (MATCH_MASK_IS_HOST)))
-	{
-		p = strchr(p ? p : mask, '@');
-		if (p)
-		{
-			char *client_username = (client->user && *client->user->username) ? client->user->username : client->ident;
-
-			*p++ = '\0';
-			if (!*p || !*mask)
-				return 0; /* NOMATCH: '...@' or '@...' */
-			hmask = p;
-			if (!umask)
-				umask = mask;
-
-			/* Check user portion right away */
-			if (!match_simple(umask, client_username))
-				return 0; /* NOMATCH: user mask did not match */
-		} else {
-			if (nmask)
-				return 0; /* NOMATCH: 'abc!def' (or even just 'abc!') */
-			hmask = mask;
-		}
-	} else {
-		hmask = mask;
-	}
-
-	/* If we get here then we have done checking nick / ident (if it was needed)
-	 * and now need to match the 'host' portion.
-	 */
-
-	/**** Check visible host ****/
-	if (options & MATCH_CHECK_VISIBLE_HOST)
-	{
-		char *hostname = client->user ? GetHost(client) : (MyUser(client) ? client->local->sockhost : NULL);
-		if (hostname && match_simple(hmask, hostname))
-			return 1; /* MATCH: visible host */
-	}
-
-	/**** Check cloaked host ****/
-	if (options & MATCH_CHECK_CLOAKED_HOST)
-	{
-		if (client->user && match_simple(hmask, client->user->cloakedhost))
-			return 1; /* MATCH: cloaked host */
-	}
-
-	/**** check on IP ****/
-	if (options & MATCH_CHECK_IP)
-	{
-		p = strchr(hmask, '/');
-		if (p)
-		{
-			*p++ = '\0';
-			cidr = atoi(p);
-			if (cidr <= 0)
-				return 0; /* NOMATCH: invalid CIDR */
-		}
-
-		if (strchr(hmask, '?') || strchr(hmask, '*'))
-		{
-			/* Wildcards */
-			if (client->ip && match_simple(hmask, client->ip))
-				return 1; /* MATCH (IP with wildcards) */
-		} else
-		if (strchr(hmask, ':'))
-		{
-			/* IPv6 hostmask */
-
-			/* We can actually return here on match/nomatch as we don't need to check the
-			 * virtual host and things like that since ':' can never be in a hostname.
-			 */
-			if (!client->ip || !strchr(client->ip, ':'))
-				return 0; /* NOMATCH: hmask is IPv6 address and client is not IPv6 */
-			if (!inet_pton(AF_INET6, client->ip, clientip))
-				return 0; /* NOMATCH: unusual failure */
-			if (!inet_pton(AF_INET6, hmask, maskip))
-				return 0; /* NOMATCH: invalid IPv6 IP in hostmask */
-
-			if (cidr < 0)
-				return comp_with_mask(clientip, maskip, 128); /* MATCH/NOMATCH by exact IP */
-
-			if (cidr > 128)
-				return 0; /* NOMATCH: invalid CIDR */
-
-			return comp_with_mask(clientip, maskip, cidr);
-		} else
-		{
-			/* Host is not IPv6 and does not contain wildcards.
-			 * So could be a literal IPv4 address or IPv4 CIDR.
-			 * NOTE: could also be neither (like a real hostname), so don't return 0 on nomatch,
-			 * in that case we should just continue...
-			 * The exception is CIDR. If we have CIDR mask then don't bother checking for
-			 * virtual hosts and things like that since '/' can never be in a hostname.
-			 */
-			if (client->ip && inet_pton(AF_INET, client->ip, clientip) && inet_pton(AF_INET, hmask, maskip))
-			{
-				if (cidr < 0)
-				{
-					if (comp_with_mask(clientip, maskip, 32))
-						return 1; /* MATCH: exact IP */
-				}
-				else if (cidr > 32)
-					return 0; /* NOMATCH: invalid CIDR */
-				else
-					return comp_with_mask(clientip, maskip, cidr); /* MATCH/NOMATCH by CIDR */
-			}
-		}
-	}
-
-	/**** Check real host ****/
-	if (options & MATCH_CHECK_REAL_HOST)
-	{
-		char *hostname = client->user ? client->user->realhost : (MyUser(client) ? client->local->sockhost : NULL);
-		if (hostname && match_simple(hmask, hostname))
-			return 1; /* MATCH: hostname match */
-	}
-
-	return 0; /* NOMATCH: nothing of the above matched */
-}
-
-/** Returns 1 if the user is allowed by any of the security groups in the named list.
- * This is only used by security-group::security-group and
- * security-group::exclude-security-group.
- * @param client	Client to check
- * @param l		The NameList
- * @returns 1 if any of the security groups match, 0 if none of them matched.
- */
-int _unreal_match_iplist(Client *client, NameList *l)
-{
-	char client_ipv6 = 0;
-	char clientip[IPSZ], maskip[IPSZ];
-
-	if (!client->ip)
-		return 0; /* unusual, maybe services? */
-
-	if (strchr(client->ip, ':'))
-	{
-		client_ipv6 = 1;
-		if (!inet_pton(AF_INET6, client->ip, clientip))
-			return 0; /* unusual failure */
-	} else {
-		if (!inet_pton(AF_INET, client->ip, clientip))
-			return 0; /* unusual failure */
-	}
-
-	for (; l; l = l->next)
-	{
-		char mask[512], *p;
-		int cidr = -1; /* CIDR length, -1 for no CIDR */
-
-		strlcpy(mask, l->name, sizeof(mask));
-		p = strchr(mask, '/');
-		if (p)
-		{
-			*p++ = '\0';
-			cidr = atoi(p);
-			if (cidr <= 0)
-				return 0; /* NOMATCH: invalid CIDR */
-		}
-
-		/* Three possible types: wildcard, ipv6, ipv4 */
-
-		if (strchr(mask, '*') || strchr(mask, '?'))
-		{
-			/* Wildcards */
-			if (match_simple(mask, client->ip))
-				return 1; /* MATCH by wildcard IP */
-		}
-		else if (strchr(mask, ':'))
-		{
-			/* IPv6 */
-			if (!client_ipv6)
-				continue; /* NOMATCH: client is IPv4 */
-			if (!inet_pton(AF_INET6, mask, maskip))
-				continue; /* NOMATCH: invalid IPv6 IP in mask */
-			if (cidr < 0)
-			{
-				/* Try to match by exact IP */
-				if (comp_with_mask(clientip, maskip, 128))
-					return 1; /* MATCH by exact IP */
-			} else
-			if (cidr > 128)
-			{
-				continue; /* NOMATCH: invalid CIDR */
-			} else
-			if (comp_with_mask(clientip, maskip, cidr))
-			{
-				return 1; /* MATCH by CIDR */
-			}
-		} else
-		{
-			/* IPv4 */
-			if (client_ipv6)
-				continue; /* NOMATCH: client is IPv6 */
-			if (!inet_pton(AF_INET, mask, maskip))
-				continue; /* NOMATCH: invalid IPv6 IP in mask */
-			if (cidr < 0)
-			{
-				/* Try to match by exact IP */
-				if (comp_with_mask(clientip, maskip, 32))
-					return 1; /* MATCH: by exact IP */
-			} else
-			if (cidr > 32)
-			{
-				continue; /* NOMATCH: invalid CIDR */
-			} else
-			if (comp_with_mask(clientip, maskip, cidr))
-			{
-				return 1; /* MATCH by CIDR */
-			}
-		}
-	}
-	return 0;
-}
-
-
-int _match_user_extended_server_ban(const char *banstr, Client *client)
-{
-	const char *nextbanstr;
-	Extban *extban;
-	BanContext *b;
-	int ret;
-
-	if (!is_extended_server_ban(banstr))
-		return 0; /* we should never have been called */
-
-	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) */
-	}
-
-	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
@@ -1,761 +0,0 @@
-/*
- * Stores active *-Lines (G-Lines etc) inside a .db file for persistency
- * (C) Copyright 2019 Gottem 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 = {
-	"tkldb",
-	"1.10",
-	"Stores active TKL entries (*-Lines) persistently/across IRCd restarts",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-#define TKLDB_MAGIC 0x10101010
-/* Database version */
-#define TKLDB_VERSION 4999
-/* Save tkls to file every <this> seconds */
-#define TKLDB_SAVE_EVERY 300
-/* The very first save after boot, apply this delta, this
- * so we don't coincide with other (potentially) expensive
- * I/O events like saving channeldb.
- */
-#define TKLDB_SAVE_EVERY_DELTA +15
-
-// #undef BENCHMARK
-/* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
- * 100,000 zlines:
- * - load db: 510 ms
- * - save db:  72 ms
- * Thus, saving does not take much time and can be done by a timer
- * which executes every 5 minutes.
- * Of course, exact figures will depend on the machine.
- */
-
-#define FreeTKLRead() \
- 	do { \
-		/* Some of these might be NULL */ \
-		if (tkl) \
-			free_tkl(tkl); \
-	} while(0)
-
-#define WARN_WRITE_ERROR(fname) \
-	do { \
-		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) \
-	do { \
-		if (!(x)) { \
-			config_warn("[tkldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
-			unrealdb_close(db); \
-			FreeTKLRead(); \
-			return 0; \
-		} \
-	} while(0)
-
-#define W_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			WARN_WRITE_ERROR(tmpfname); \
-			unrealdb_close(db); \
-			return 0; \
-		} \
-	} while(0)
-
-#define IsMDErr(x, y, z) \
-	do { \
-		if (!(x)) { \
-			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
-			return MOD_FAILED; \
-		} \
-	} while(0)
-
-/* Structs */
-struct cfgstruct {
-	char *database;
-	char *db_secret;
-};
-
-/* Forward declarations */
-void tkldb_moddata_free(ModData *md);
-void setcfg(struct cfgstruct *cfg);
-void freecfg(struct cfgstruct *cfg);
-int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int tkldb_config_posttest(int *errs);
-int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-EVENT(write_tkldb_evt);
-int write_tkldb(void);
-int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl);
-int read_tkldb(void);
-
-/* Globals variables */
-const uint32_t tkldb_version = TKLDB_VERSION;
-static struct cfgstruct cfg;
-static struct cfgstruct test;
-
-static long tkldb_next_event = 0;
-
-MOD_TEST()
-{
-	memset(&cfg, 0, sizeof(cfg));
-	memset(&test, 0, sizeof(test));
-	setcfg(&test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkldb_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, tkldb_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -9999);
-
-	LoadPersistentLong(modinfo, tkldb_next_event);
-
-	setcfg(&cfg);
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkldb_config_run);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (!tkldb_next_event)
-	{
-		/* If this is the first time that our module is loaded, then
-		 * read the TKL DB and add all *-Lines.
-		 */
-		if (!read_tkldb())
-		{
-			char fname[512];
-			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
-			if (rename(cfg.database, fname) == 0)
-				config_warn("[tkldb] Existing database renamed to %s and starting a new one...", fname);
-			else
-				config_warn("[tkldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
-		}
-		tkldb_next_event = TStime() + TKLDB_SAVE_EVERY + TKLDB_SAVE_EVERY_DELTA;
-	}
-	EventAdd(modinfo->handle, "tkldb_write_tkldb", write_tkldb_evt, NULL, 1000, 0);
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	if (loop.terminating)
-		write_tkldb();
-	freecfg(&test);
-	freecfg(&cfg);
-	SavePersistentLong(modinfo, tkldb_next_event);
-	return MOD_SUCCESS;
-}
-
-void tkldb_moddata_free(ModData *md)
-{
-	if (md->i)
-		md->i = 0;
-}
-
-void setcfg(struct cfgstruct *cfg)
-{
-	// Default: data/tkl.db
-	safe_strdup(cfg->database, "tkl.db");
-	convert_to_absolute_path(&cfg->database, PERMDATADIR);
-}
-
-void freecfg(struct cfgstruct *cfg)
-{
-	safe_free(cfg->database);
-	safe_free(cfg->db_secret);
-}
-
-int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	// We are only interested in set::tkldb::database
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "tkldb"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->value)
-		{
-			config_error("%s:%i: blank set::tkldb::%s without value", cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		} else
-		if (!strcmp(cep->name, "database"))
-		{
-			convert_to_absolute_path(&cep->value, PERMDATADIR);
-			safe_strdup(test.database, cep->value);
-		} else
-		if (!strcmp(cep->name, "db-secret"))
-		{
-			const char *err;
-			if ((err = unrealdb_test_secret(cep->value)))
-			{
-				config_error("%s:%i: set::tkldb::db-secret: %s", cep->file->filename, cep->line_number, err);
-				errors++;
-				continue;
-			}
-			safe_strdup(test.db_secret, cep->value);
-		} else
-		{
-			config_error("%s:%i: unknown directive set::tkldb::%s", cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int tkldb_config_posttest(int *errs)
-{
-	int errors = 0;
-	char *errstr;
-
-	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
-	{
-		config_error("[tkldb] %s", errstr);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	// We are only interested in set::tkldb::database
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "tkldb"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "database"))
-			safe_strdup(cfg.database, cep->value);
-		else if (!strcmp(cep->name, "db-secret"))
-			safe_strdup(cfg.db_secret, cep->value);
-	}
-	return 1;
-}
-
-EVENT(write_tkldb_evt)
-{
-	if (tkldb_next_event > TStime())
-		return;
-	tkldb_next_event = TStime() + TKLDB_SAVE_EVERY;
-	write_tkldb();
-}
-
-int write_tkldb(void)
-{
-	char tmpfname[512];
-	UnrealDB *db;
-	uint64_t tklcount;
-	int index, index2;
-	TKL *tkl;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	// Write to a tempfile first, then rename it if everything succeeded
-	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
-	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
-	if (!db)
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-	W_SAFE(unrealdb_write_int32(db, TKLDB_MAGIC));
-	W_SAFE(unrealdb_write_int32(db, tkldb_version));
-
-	// Count the *-Lines
-	tklcount = 0;
-
-	// First the ones in the hash table
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				if (tkl->flags & TKL_FLAG_CONFIG)
-					continue; /* config entry */
-				tklcount++;
-			}
-		}
-	}
-	// Then the regular *-Lines
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			if (tkl->flags & TKL_FLAG_CONFIG)
-				continue; /* config entry */
-			tklcount++;
-		}
-	}
-	W_SAFE(unrealdb_write_int64(db, tklcount));
-
-	// Now write the actual *-Lines, first the ones in the hash table
-	for (index = 0; index < TKLIPHASHLEN1; index++)
-	{
-		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
-		{
-			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
-			{
-				if (tkl->flags & TKL_FLAG_CONFIG)
-					continue; /* config entry */
-				if (!write_tkline(db, tmpfname, tkl)) // write_tkline() closes the db on errors itself
-					return 0;
-			}
-		}
-	}
-	// Then the regular *-Lines
-	for (index = 0; index < TKLISTLEN; index++)
-	{
-		for (tkl = tklines[index]; tkl; tkl = tkl->next)
-		{
-			if (tkl->flags & TKL_FLAG_CONFIG)
-				continue; /* config entry */
-			if (!write_tkline(db, tmpfname, tkl))
-				return 0;
-		}
-	}
-
-	// Everything seems to have gone well, attempt to close and rename the tempfile
-	if (!unrealdb_close(db))
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-#ifdef _WIN32
-	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
-	unlink(cfg.database);
-#endif
-	if (rename(tmpfname, cfg.database) < 0)
-	{
-		config_error("[tkldb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
-		return 0;
-	}
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	config_status("[tkldb] Benchmark: SAVE DB: %lld microseconds",
-		(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
-#endif
-	return 1;
-}
-
-/** Write a TKL entry */
-int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl)
-{
-	char tkltype;
-	char buf[256];
-
-	/* First, write the common attributes */
-	tkltype = tkl_typetochar(tkl->type);
-	W_SAFE(unrealdb_write_char(db, tkltype)); // TKL char
-
-	W_SAFE(unrealdb_write_str(db, tkl->set_by));
-	W_SAFE(unrealdb_write_int64(db, tkl->set_at));
-	W_SAFE(unrealdb_write_int64(db, tkl->expire_at));
-
-	if (TKLIsServerBan(tkl))
-	{
-		char *usermask = tkl->ptr.serverban->usermask;
-		if (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)
-		{
-			snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.serverban->usermask);
-			usermask = buf;
-		}
-		W_SAFE(unrealdb_write_str(db, usermask));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->hostmask));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->reason));
-	} else
-	if (TKLIsBanException(tkl))
-	{
-		char *usermask = tkl->ptr.banexception->usermask;
-		if (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)
-		{
-			snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.banexception->usermask);
-			usermask = buf;
-		}
-		W_SAFE(unrealdb_write_str(db, usermask));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->hostmask));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->bantypes));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->reason));
-	} else
-	if (TKLIsNameBan(tkl))
-	{
-		char *hold = tkl->ptr.nameban->hold ? "H" : "*";
-		W_SAFE(unrealdb_write_str(db, hold));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->name));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->reason));
-	} else
-	if (TKLIsSpamfilter(tkl))
-	{
-		char *match_type = unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type);
-		char *target = spamfilter_target_inttostring(tkl->ptr.spamfilter->target);
-		char action = banact_valtochar(tkl->ptr.spamfilter->action);
-
-		W_SAFE(unrealdb_write_str(db, match_type));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->match->str));
-		W_SAFE(unrealdb_write_str(db, target));
-		W_SAFE(unrealdb_write_char(db, action));
-		W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->tkl_reason));
-		W_SAFE(unrealdb_write_int64(db, tkl->ptr.spamfilter->tkl_duration));
-	}
-
-	return 1;
-}
-
-/** Read all entries from the TKL db */
-int read_tkldb(void)
-{
-	UnrealDB *db;
-	TKL *tkl = NULL;
-	uint32_t magic = 0;
-	uint32_t version;
-	uint64_t cnt;
-	uint64_t tklcount = 0;
-	uint64_t v;
-	int added_cnt = 0;
-	char c;
-	char *str;
-
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
-	if (!db)
-	{
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
-		{
-			/* Database does not exist. Could be first boot */
-			config_warn("[tkldb] No database present at '%s', will start a new one", cfg.database);
-			return 1;
-		} else
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
-		{
-			/* Re-open as unencrypted */
-			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
-			if (!db)
-			{
-				/* This should actually never happen, unless some weird I/O error */
-				config_warn("[tkldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
-				return 0;
-			}
-		} else
-		{
-			config_warn("[tkldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
-			return 0;
-		}
-	}
-
-	/* The database starts with a "magic value" - unless it's some old version or corrupt */
-	R_SAFE(unrealdb_read_int32(db, &magic));
-	if (magic != TKLDB_MAGIC)
-	{
-		config_warn("[tkldb] Database '%s' uses an old and unsupported format OR is corrupt", cfg.database);
-		config_status("If you are upgrading from UnrealIRCd 4 (or 5.0.0-alpha1) then we suggest you to "
-		              "delete the existing database. Just keep at least 1 server linked during the upgrade "
-		              "process to preserve your global *LINES and Spamfilters.");
-		unrealdb_close(db);
-		return 0;
-	}
-
-	/* Now do a version check */
-	R_SAFE(unrealdb_read_int32(db, &version));
-	if (version < 4999)
-	{
-		config_warn("[tkldb] Database '%s' uses an unsupport - possibly old - format (%ld).", cfg.database, (long)version);
-		unrealdb_close(db);
-		return 0;
-	}
-	if (version > tkldb_version)
-	{
-		config_warn("[tkldb] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
-			cfg.database, (unsigned long)tkldb_version, (unsigned long)version);
-		unrealdb_close(db);
-		return 0;
-	}
-
-	R_SAFE(unrealdb_read_int64(db, &tklcount));
-
-	for (cnt = 0; cnt < tklcount; cnt++)
-	{
-		int do_not_add = 0;
-
-		tkl = safe_alloc(sizeof(TKL));
-
-		/* First, fetch the TKL type.. */
-		R_SAFE(unrealdb_read_char(db, &c));
-		tkl->type = tkl_chartotype(c);
-		if (!tkl->type)
-		{
-			/* We can't continue reading the DB if we don't know the TKL type,
-			 * since we don't know how long the entry will be, we can't skip it.
-			 * This is "impossible" anyway, unless we some day remove a TKL type
-			 * in core UnrealIRCd. In which case we should add some skipping code
-			 * here to gracefully handle that situation ;)
-			 */
-			config_warn("[tkldb] Invalid type '%c' encountered - STOPPED READING DATABASE!", tkl->type);
-			FreeTKLRead();
-			break; /* we MUST stop reading */
-		}
-
-		/* Read the common types (same for all TKLs) */
-		R_SAFE(unrealdb_read_str(db, &tkl->set_by));
-		R_SAFE(unrealdb_read_int64(db, &v));
-		tkl->set_at = v;
-		R_SAFE(unrealdb_read_int64(db, &v));
-		tkl->expire_at = v;
-
-		/* Save some CPU... if it's already expired then don't bother adding */
-		if (tkl->expire_at != 0 && tkl->expire_at <= TStime())
-			do_not_add = 1;
-
-		/* Now handle all the specific types */
-		if (TKLIsServerBan(tkl))
-		{
-			int softban = 0;
-
-			tkl->ptr.serverban = safe_alloc(sizeof(ServerBan));
-
-			/* Usermask - but taking into account that the
-			 * %-prefix means a soft ban.
-			 */
-			R_SAFE(unrealdb_read_str(db, &str));
-			if (*str == '%')
-			{
-				softban = 1;
-				safe_strdup(tkl->ptr.serverban->usermask, str+1);
-			} else {
-				safe_strdup(tkl->ptr.serverban->usermask, str);
-			}
-			safe_free(str);
-
-			/* And the other 2 fields.. */
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->hostmask));
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->reason));
-
-			if (find_tkl_serverban(tkl->type, tkl->ptr.serverban->usermask,
-			                       tkl->ptr.serverban->hostmask, softban))
-			{
-				do_not_add = 1;
-			}
-
-			if (!do_not_add)
-			{
-				tkl_add_serverban(tkl->type, tkl->ptr.serverban->usermask,
-				                  tkl->ptr.serverban->hostmask,
-				                  tkl->ptr.serverban->reason,
-				                  tkl->set_by, tkl->expire_at,
-				                  tkl->set_at, softban, 0);
-			}
-		} else
-		if (TKLIsBanException(tkl))
-		{
-			int softban = 0;
-
-			tkl->ptr.banexception = safe_alloc(sizeof(BanException));
-
-			/* Usermask - but taking into account that the
-			 * %-prefix means a soft ban.
-			 */
-			R_SAFE(unrealdb_read_str(db, &str));
-			if (*str == '%')
-			{
-				softban = 1;
-				safe_strdup(tkl->ptr.banexception->usermask, str+1);
-			} else {
-				safe_strdup(tkl->ptr.banexception->usermask, str);
-			}
-			safe_free(str);
-
-			/* And the other 3 fields.. */
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->hostmask));
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->bantypes));
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->reason));
-
-			if (find_tkl_banexception(tkl->type, tkl->ptr.banexception->usermask,
-			                          tkl->ptr.banexception->hostmask, softban))
-			{
-				do_not_add = 1;
-			}
-
-			if (!do_not_add)
-			{
-				tkl_add_banexception(tkl->type, tkl->ptr.banexception->usermask,
-				                     tkl->ptr.banexception->hostmask,
-				                     NULL,
-				                     tkl->ptr.banexception->reason,
-				                     tkl->set_by, tkl->expire_at,
-				                     tkl->set_at, softban,
-				                     tkl->ptr.banexception->bantypes,
-				                     0);
-			}
-		} else
-		if (TKLIsNameBan(tkl))
-		{
-			tkl->ptr.nameban = safe_alloc(sizeof(NameBan));
-
-			R_SAFE(unrealdb_read_str(db, &str));
-			if (*str == 'H')
-				tkl->ptr.nameban->hold = 1;
-			safe_free(str);
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->name));
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->reason));
-
-			if (find_tkl_nameban(tkl->type, tkl->ptr.nameban->name,
-			                     tkl->ptr.nameban->hold))
-			{
-				do_not_add = 1;
-			}
-
-			if (!do_not_add)
-			{
-				tkl_add_nameban(tkl->type, tkl->ptr.nameban->name,
-				                tkl->ptr.nameban->hold,
-				                tkl->ptr.nameban->reason,
-				                tkl->set_by, tkl->expire_at,
-				                tkl->set_at, 0);
-			}
-		} else
-		if (TKLIsSpamfilter(tkl))
-		{
-			int match_method;
-			char *err = NULL;
-
-			tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
-
-			/* Match method */
-			R_SAFE(unrealdb_read_str(db, &str));
-			match_method = unreal_match_method_strtoval(str);
-			if (!match_method)
-			{
-				config_warn("[tkldb] Unhandled spamfilter match method '%s' -- spamfilter entry not added", str);
-				do_not_add = 1;
-			}
-			safe_free(str);
-
-			/* Match string (eg: regex) */
-			R_SAFE(unrealdb_read_str(db, &str));
-			tkl->ptr.spamfilter->match = unreal_create_match(match_method, str, &err);
-			if (!tkl->ptr.spamfilter->match)
-			{
-				config_warn("[tkldb] Spamfilter '%s' does not compile: %s -- spamfilter entry not added", str, err);
-				do_not_add = 1;
-			}
-			safe_free(str);
-
-			/* Target (eg: cpn) */
-			R_SAFE(unrealdb_read_str(db, &str));
-			tkl->ptr.spamfilter->target = spamfilter_gettargets(str, NULL);
-			if (!tkl->ptr.spamfilter->target)
-			{
-				config_warn("[tkldb] Spamfilter '%s' without any valid targets (%s) -- spamfilter entry not added",
-					tkl->ptr.spamfilter->match->str, str);
-				do_not_add = 1;
-			}
-			safe_free(str);
-
-			/* Action */
-			R_SAFE(unrealdb_read_char(db, &c));
-			tkl->ptr.spamfilter->action = banact_chartoval(c);
-			if (!tkl->ptr.spamfilter->action)
-			{
-				config_warn("[tkldb] Spamfilter '%s' without valid action (%c) -- spamfilter entry not added",
-					tkl->ptr.spamfilter->match->str, c);
-				do_not_add = 1;
-			}
-
-			R_SAFE(unrealdb_read_str(db, &tkl->ptr.spamfilter->tkl_reason));
-			R_SAFE(unrealdb_read_int64(db, &v));
-			tkl->ptr.spamfilter->tkl_duration = v;
-
-			if (find_tkl_spamfilter(tkl->type, tkl->ptr.spamfilter->match->str,
-			                        tkl->ptr.spamfilter->action,
-			                        tkl->ptr.spamfilter->target))
-			{
-				do_not_add = 1;
-			}
-
-			if (!do_not_add)
-			{
-				tkl_add_spamfilter(tkl->type, tkl->ptr.spamfilter->target,
-				                   tkl->ptr.spamfilter->action,
-				                   tkl->ptr.spamfilter->match,
-				                   tkl->set_by, tkl->expire_at, tkl->set_at,
-				                   tkl->ptr.spamfilter->tkl_duration,
-				                   tkl->ptr.spamfilter->tkl_reason,
-				                   0);
-				/* tkl_add_spamfilter() does not copy the match but assign it.
-				 * so set to NULL here to avoid a read-after-free later on.
-				 */
-				tkl->ptr.spamfilter->match = NULL;
-			}
-		} else
-		{
-			config_warn("[tkldb] Unhandled type!! TKLDB is missing support for type %ld -- STOPPED reading db entries!", (long)tkl->type);
-			FreeTKLRead();
-			break; /* we MUST stop reading */
-		}
-
-		if (!do_not_add)
-			added_cnt++;
-
-		FreeTKLRead();
-	}
-
-	unrealdb_close(db);
-
-	if (added_cnt)
-		config_status("[tkldb] Re-added %d *-Lines", added_cnt);
-
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	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/tline.c b/src/modules/tline.c
@@ -1,87 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/tline.c
- *   (C) 2022 Noisytoot & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-#define MSG_TLINE "TLINE"
-
-CMD_FUNC(cmd_tline);
-
-ModuleHeader MOD_HEADER
-  = {
-	"tline",
-	"1.0",
-	"TLINE command to show amount of clients matching a server ban mask",
-	"UnrealIRCd team",
-	"unrealircd-6",
-	};
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_TLINE, cmd_tline, 1, CMD_OPER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_tline
-**	parv[1] = mask to test
-*/
-CMD_FUNC(cmd_tline)
-{
-	Client *acptr;
-	int matching_lclients = 0;
-	int matching_clients = 0;
-
-	if ((parc < 1) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, MSG_TLINE);
-		return;
-	}
-
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (match_user(parv[1], acptr, MATCH_CHECK_REAL))
-		{
-			if (MyUser(acptr))
-				matching_lclients++;
-			matching_clients++;
-		}
-	}
-
-	sendnotice(client,
-	    "*** TLINE: Users matching mask '%s': global: %d/%d (%.2f%%), local: %d/%d (%.2f%%).",
-	    parv[1], matching_clients, irccounts.clients,
-	    (double)matching_clients / irccounts.clients * 100,
-	    matching_lclients, irccounts.me_clients,
-	    (double)matching_lclients / irccounts.me_clients * 100);
-}
diff --git a/src/modules/tls_antidos.c b/src/modules/tls_antidos.c
@@ -1,111 +0,0 @@
-/*
- * 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.
- * 
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"tls_antidos",
-	"5.0",
-	"TLS Renegotiation DoS protection",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-#define HANDSHAKE_LIMIT_COUNT 3
-#define HANDSHAKE_LIMIT_SECS 300
-
-typedef struct SAD SAD;
-struct SAD {
-	Client *client; /**< client */
-	time_t ts; /**< time */
-	int n; /**< number of times */
-};
-
-int tls_antidos_index = 0; /* slot# we acquire from OpenSSL. Hmm.. looks awfully similar to our moddata system ;) */
-
-/* Forward declaration */
-int tls_antidos_handshake(Client *client);
-
-void tls_antidos_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp);
-
-MOD_INIT()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, tls_antidos_handshake);
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
-	/* Note that we cannot be MOD_OPT_PERM_RELOADABLE as we use OpenSSL functions to register
-	 * an index and callback function.
-	 */
-	
-	tls_antidos_index = SSL_get_ex_new_index(0, "tls_antidos", NULL, NULL, tls_antidos_free);
-	
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Called upon handshake (and other events) */
-void ssl_info_callback(const SSL *ssl, int where, int ret)
-{
-	if (where & SSL_CB_HANDSHAKE_START)
-	{
-		SAD *e = SSL_get_ex_data(ssl, tls_antidos_index);
-		Client *client = e->client;
-		
-		if (IsServer(client) || IsDeadSocket(client))
-			return; /* if it's a server, or already pending to be killed off then we don't care */
-
-		if (e->ts < TStime() - HANDSHAKE_LIMIT_SECS)
-		{
-			e->ts = TStime();
-			e->n = 1;
-		} else {
-			e->n++;
-			if (e->n >= HANDSHAKE_LIMIT_COUNT)
-			{
-				unreal_log(ULOG_INFO, "flood", "TLS_HANDSHAKE_FLOOD", client, "TLS Handshake flood detected from $client.details -- killed");
-				dead_socket(client, "TLS Handshake flood detected");
-			}
-		}
-	}
-}
-
-/** Called when a client has just connected to us.
- * This function is called quite quickly after accept(),
- * in any case very likely before any data has been received.
- */
-int tls_antidos_handshake(Client *client)
-{
-	if (client->local->ssl)
-	{
-		SAD *sad = safe_alloc(sizeof(SAD));
-		sad->client = client;
-		SSL_set_info_callback(client->local->ssl, ssl_info_callback);
-		SSL_set_ex_data(client->local->ssl, tls_antidos_index, sad);
-	}
-	return 0;
-}
-
-/** 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
@@ -1,118 +0,0 @@
-/*
- * Store TLS cipher in ModData
- * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
- * License: GPLv2 or later
- */
-
-#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);
-int tls_json_expand_client(Client *client, int detail, json_t *j);
-
-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);
-
-	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, tls_json_expand_client);
-
-	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);
-}
-
-int tls_json_expand_client(Client *client, int detail, json_t *j)
-{
-	json_t *tls;
-	const char *str;
-
-	if (detail < 2)
-		return 0;
-
-	str = moddata_client_get(client, "tls_cipher");
-	if (!str)
-		return 0;
-
-	tls = json_object_get(j, "tls");
-	if (!tls)
-	{
-		tls = json_object();
-		json_object_set_new(j, "tls", tls);
-	}
-
-	json_object_set_new(tls, "cipher", json_string_unreal(str));
-
-	return 0;
-}
diff --git a/src/modules/topic.c b/src/modules/topic.c
@@ -1,317 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/topic.c
- *   (C) 2004-present 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_topic);
-
-#define MSG_TOPIC 	"TOPIC"
-
-ModuleHeader MOD_HEADER
-  = {
-	"topic",
-	"5.0",
-	"command /topic", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Forward declarations */
-void _set_channel_topic(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_SET_CHANNEL_TOPIC, _set_channel_topic);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_TOPIC, cmd_topic, 4, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-void topic_operoverride_msg(Client *client, Channel *channel, const char *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.
- *
- * Syntax for clients:
- * parv[1] = channel
- * parv[2] = new topic
- *
- * Syntax for server to server traffic:
- * parv[1] = channel name
- * parv[2] = topic nickname
- * parv[3] = topic time
- * parv[4] = topic text
- */
-CMD_FUNC(cmd_topic)
-{
-	Channel *channel = NULL;
-	const char *topic = NULL;
-	const char *name, *tnick = client->name;
-	const char *errmsg = NULL;
-	time_t ttime = 0;
-	int i = 0;
-	Hook *h;
-	MessageTag *mtags = NULL;
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "TOPIC");
-		return;
-	}
-
-	name = parv[1];
-
-	channel = find_channel(parv[1]);
-	if (!channel)
-	{
-		sendnumeric(client, ERR_NOSUCHCHANNEL, name);
-		return;
-	}
-
-	if (parc > 2 || SecretChannel(channel))
-	{
-		if (!IsMember(client, channel) && !IsServer(client)
-		    && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL) && !IsULine(client))
-		{
-			sendnumeric(client, ERR_NOTONCHANNEL, name);
-			return;
-		}
-		if (parc > 2)
-			topic = parv[2];
-	}
-
-	if (parc > 4)
-	{
-		if (MyUser(client))
-		{
-			sendnumeric(client, ERR_CANNOTDOCOMMAND, "TOPIC", "Invalid parameters. Usage is TOPIC #channel :topic here");
-			return;
-		}
-		tnick = parv[2];
-		ttime = atol(parv[3]);
-		topic = parv[4];
-	}
-
-	/* Only asking for the topic */
-	if (!topic)
-	{
-		if (IsServer(client))
-			return; /* Servers must maintain state, not ask */
-
-		for (h = Hooks[HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(client,channel);
-			if (i != HOOK_CONTINUE)
-				break;
-		}
-
-		/* If you're not a member, and you can't view outside channel, deny */
-		if ((!IsMember(client, channel) && i == HOOK_DENY) ||
-		    (is_banned(client,channel,BANCHK_JOIN,NULL,NULL) &&
-		     !ValidatePermissionsForPath("channel:see:topic",client,NULL,channel,NULL)))
-		{
-			sendnumeric(client, ERR_NOTONCHANNEL, name);
-			return;
-		}
-
-		if (!channel->topic)
-			sendnumeric(client, RPL_NOTOPIC, channel->name);
-		else
-		{
-			sendnumeric(client, RPL_TOPIC, channel->name, channel->topic);
-			sendnumeric(client, RPL_TOPICWHOTIME, channel->name,
-			            channel->topic_nick, (long long)channel->topic_time);
-		}
-		return;
-	}
-
-	if (ttime && topic && (IsServer(client) || IsULine(client)))
-	{
-		if (!channel->topic_time || ttime > channel->topic_time || IsULine(client))
-		/* The IsUline is to allow services to use an old TS. Apparently
-		 * some services do this in their topic enforcement -- codemastr 
-		 */
-		{
-			/* Set the topic */
-			safe_strldup(channel->topic, topic, iConf.topic_length+1);
-			safe_strldup(channel->topic_nick, tnick, NICKLEN+USERLEN+HOSTLEN+5);
-			channel->topic_time = ttime;
-
-			new_message(client, recv_mtags, &mtags);
-			RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
-			sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
-			    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->name, channel->topic);
-			free_message_tags(mtags);
-		}
-		return;
-	}
-
-	/* Topic change. Either locally (check permissions!) or remote, check permissions: */
-	if (IsUser(client))
-	{
-		const char *newtopic = NULL;
-		const char *errmsg = NULL;
-		int ret = EX_ALLOW;
-		int operoverride = 0;
-
-		for (h = Hooks[HOOKTYPE_CAN_SET_TOPIC]; h; h = h->next)
-		{
-			int n = (*(h->func.intfunc))(client, channel, topic, &errmsg);
-
-			if (n == EX_DENY)
-			{
-				ret = n;
-			} else
-			if (n == EX_ALWAYS_DENY)
-			{
-				ret = n;
-				break;
-			}
-		}
-
-		if (ret == EX_ALWAYS_DENY)
-		{
-			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))
-			{
-				if (errmsg)
-					sendto_one(client, NULL, "%s", errmsg);
-				return; /* reject */
-			} else {
-				operoverride = 1; /* allow */
-			}
-		}
-
-		/* 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))
-			{
-				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->name, 0, NULL))
-				return;
-
-			for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_TOPIC]; tmphook; tmphook = tmphook->next) {
-				topic = (*(tmphook->func.stringfunc))(client, channel, topic);
-				if (!topic)
-					return;
-			}
-		}
-
-		/* At this point 'tnick' is set to client->name.
-		 * If set::topic-setter nick-user-host; is set
-		 * then we update it here to nick!user@host.
-		 */
-		if (iConf.topic_setter == SETTER_NICK_USER_HOST)
-			tnick = make_nick_user_host(client->name, client->user->username, GetHost(client));
-	}
-
-	_set_channel_topic(client, channel, recv_mtags, topic, tnick, ttime);
-}
-
-/** Set topic on a channel.
- * @param client	The client setting the topic
- * @param channel	The channel
- * @param recv_mtags	Message tags
- * @param topic		The new topic (TODO: this function does not support unsetting yet)
- * @param set_by	Who set the topic (can be NULL, means client->name)
- * @param set_at	When the topic was set (can be 0, means now)
- */
-void _set_channel_topic(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at)
-{
-	MessageTag *mtags = NULL;
-
-	/* Set default values when needed */
-	if (set_by == NULL)
-		set_by = client->name;
-	if (set_at == 0)
-		set_at = TStime();
-
-	/* Set the topic */
-	safe_strldup(channel->topic, topic, iConf.topic_length+1);
-	safe_strldup(channel->topic_nick, set_by, NICKLEN+USERLEN+HOSTLEN+5);
-	channel->topic_time = set_at;
-
-	/* And broadcast the change - locally and remote */
-	new_message(client, recv_mtags, &mtags);
-	RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
-	sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
-	    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->name, channel->topic);
-	free_message_tags(mtags);
-}
diff --git a/src/modules/trace.c b/src/modules/trace.c
@@ -1,234 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/trace.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_trace);
-
-#define MSG_TRACE 	"TRACE"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"trace",
-	"5.0",
-	"command /trace", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_TRACE, cmd_trace, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-** cmd_trace
-**	parv[1] = servername
-*/
-CMD_FUNC(cmd_trace)
-{
-	int  i;
-	Client *acptr;
-	ConfigItem_class *cltmp;
-	const char *tname;
-	int  doall, link_s[MAXCONNECTIONS], link_u[MAXCONNECTIONS];
-	int  cnt = 0, wilds, dow;
-	time_t now;
-
-	/* This is one of the (few) commands that cannot be handled
-	 * by labeled-response because responses from multiple servers
-	 * are involved.
-	 */
-	labeled_response_inhibit = 1;
-
-	if (parc > 2)
-		if (hunt_server(client, NULL, "TRACE", 2, parc, parv))
-			return;
-
-	if (parc > 1)
-		tname = parv[1];
-	else
-		tname = me.name;
-
-	if (!ValidatePermissionsForPath("client:see:trace:global",client,NULL,NULL,NULL))
-	{
-		if (ValidatePermissionsForPath("client:see:trace:local",client,NULL,NULL,NULL))
-		{
-			/* local opers may not /TRACE remote servers! */
-			if (strcasecmp(tname, me.name))
-			{
-				sendnotice(client, "You can only /TRACE local servers as a locop");
-				sendnumeric(client, ERR_NOPRIVILEGES);
-				return;
-			}
-		} else {
-			sendnumeric(client, ERR_NOPRIVILEGES);
-			return;
-		}
-	}
-
-	switch (hunt_server(client, NULL, "TRACE", 1, parc, parv))
-	{
-	  case HUNTED_PASS:	/* note: gets here only if parv[1] exists */
-	  {
-		  Client *ac2ptr;
-
-		  ac2ptr = find_client(tname, NULL);
-		  sendnumeric(client, RPL_TRACELINK,
-		      version, debugmode, tname, ac2ptr->direction->name);
-		  return;
-	  }
-	  case HUNTED_ISME:
-		  break;
-	  default:
-		  return;
-	}
-
-	doall = (parv[1] && (parc > 1)) ? match_simple(tname, me.name) : TRUE;
-	wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
-	dow = wilds || doall;
-
-	for (i = 0; i < MAXCONNECTIONS; i++)
-		link_s[i] = 0, link_u[i] = 0;
-
-
-	if (doall) {
-		list_for_each_entry(acptr, &client_list, client_node)
-		{
-			if (acptr->direction->local->fd < 0)
-				continue;
-			if (IsUser(acptr))
-				link_u[acptr->direction->local->fd]++;
-			else if (IsServer(acptr))
-				link_s[acptr->direction->local->fd]++;
-		}
-	}
-
-	/* report all direct connections */
-
-	now = TStime();
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-	{
-		const char *name;
-		const char *class;
-
-		if (!ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL) && (acptr != client))
-			continue;
-		if (!doall && wilds && !match_simple(tname, acptr->name))
-			continue;
-		if (!dow && mycmp(tname, acptr->name))
-			continue;
-		name = get_client_name(acptr, FALSE);
-		class = acptr->local->class ? acptr->local->class->name : "default";
-		switch (acptr->status)
-		{
-			case CLIENT_STATUS_CONNECTING:
-				sendnumeric(client, RPL_TRACECONNECTING, class, name);
-				cnt++;
-				break;
-
-			case CLIENT_STATUS_HANDSHAKE:
-				sendnumeric(client, RPL_TRACEHANDSHAKE, class, name);
-				cnt++;
-				break;
-
-			case CLIENT_STATUS_ME:
-				break;
-
-			case CLIENT_STATUS_UNKNOWN:
-				sendnumeric(client, RPL_TRACEUNKNOWN, class, name);
-				cnt++;
-				break;
-
-			case CLIENT_STATUS_USER:
-				/* Only opers see users if there is a wildcard
-				 * but anyone can see all the opers.
-				 */
-				if (ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL) ||
-				    (!IsInvisible(acptr) && ValidatePermissionsForPath("client:see:trace",client,acptr,NULL,NULL)))
-				{
-					if (ValidatePermissionsForPath("client:see:trace",client,acptr,NULL,NULL) || ValidatePermissionsForPath("client:see:trace:invisible-users",client,acptr,NULL,NULL))
-						sendnumeric(client, RPL_TRACEOPERATOR,
-						    class, acptr->name,
-						    GetHost(acptr),
-						    (long long)(now - acptr->local->last_msg_received));
-					else
-						sendnumeric(client, RPL_TRACEUSER,
-						    class, acptr->name,
-						    acptr->user->realhost,
-						    (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->server->by) ?
-				    acptr->server->by : "*", "*", me.name,
-				    (long long)(now - acptr->local->last_msg_received));
-				cnt++;
-				break;
-
-			case CLIENT_STATUS_LOG:
-				sendnumeric(client, RPL_TRACELOG, LOGFILE, acptr->local->port);
-				cnt++;
-				break;
-
-			case CLIENT_STATUS_TLS_CONNECT_HANDSHAKE:
-				sendnumeric(client, RPL_TRACENEWTYPE, "TLS-Connect-Handshake", name);
-				cnt++;
-				break;
-
-			case CLIENT_STATUS_TLS_ACCEPT_HANDSHAKE:
-				sendnumeric(client, RPL_TRACENEWTYPE, "TLS-Accept-Handshake", name);
-				cnt++;
-				break;
-
-			default:	/* ...we actually shouldn't come here... --msa */
-				sendnumeric(client, RPL_TRACENEWTYPE, "<newtype>", name);
-				cnt++;
-				break;
-		}
-	}
-	/*
-	 * Add these lines to summarize the above which can get rather long
-	 * and messy when done remotely - Avalon
-	 */
-	if (!ValidatePermissionsForPath("client:see:trace",client,acptr,NULL,NULL) || !cnt)
-		return;
-
-	for (cltmp = conf_class; doall && cltmp; cltmp = cltmp->next)
-	/*	if (cltmp->clients > 0) */
-			sendnumeric(client, RPL_TRACECLASS, cltmp->name ? cltmp->name : "[noname]", cltmp->clients);
-}
diff --git a/src/modules/tsctl.c b/src/modules/tsctl.c
@@ -1,74 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/tsctl.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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
-  = {
-	"tsctl",	/* Name of module */
-	"5.0", /* Version */
-	"command /tsctl", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-CMD_FUNC(cmd_tsctl);
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, "TSCTL", cmd_tsctl, MAXPARA, CMD_USER|CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-CMD_FUNC(cmd_tsctl)
-{
-	if (!ValidatePermissionsForPath("server:tsctl:view",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (MyUser(client) && (!parv[1] || strcasecmp(parv[1], "alltime")))
-	{
-		sendnotice(client, "/TSCTL now shows the time on all servers. You can no longer modify the time.");
-		parv[1] = "alltime";
-	}
-
-	if (parv[1] && !strcasecmp(parv[1], "alltime"))
-	{
-		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
@@ -1,105 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/typing-indicator.c
- *   (C) 2020 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"typing-indicator",
-	"5.0",
-	"+typing client tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-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()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "+typing";
-	mtag.is_ok = ti_mtag_is_ok;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "+draft/typing";
-	mtag.is_ok = ti_mtag_is_ok;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_ti);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending the mtag is permitted to do so.
- */
-int ti_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	/* Require a non-empty parameter */
-	if (BadPtr(value))
-		return 0;
-
-	/* These are the only valid values: */
-	if (!strcmp(value, "active") || !strcmp(value, "paused") || !strcmp(value, "done"))
-		return 1;
-
-	/* All the rest is considered illegal */
-	return 0;
-}
-
-void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m;
-
-	if (IsUser(client))
-	{
-		m = find_mtag(recv_mtags, "+typing");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-			AddListItem(m, *mtag_list);
-		}
-		m = find_mtag(recv_mtags, "+draft/typing");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-			AddListItem(m, *mtag_list);
-		}
-	}
-}
diff --git a/src/modules/umode2.c b/src/modules/umode2.c
@@ -1,74 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/umode2.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_umode2);
-
-#define MSG_UMODE2 	"UMODE2"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"umode2",
-	"5.0",
-	"command /umode2", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_UMODE2, cmd_umode2, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
-    cmd_umode2 added by Stskeeps
-    parv[1] - modes to change
-    Small wrapper to save bandwidth
-*/
-
-CMD_FUNC(cmd_umode2)
-{
-	const char *xparv[5] = {
-		client->name,
-		client->name,
-		parv[1],
-		(parc > 3) ? parv[3] : NULL,
-		NULL
-	};
-
-	if (!parv[1])
-		return;
-	cmd_umode(client, recv_mtags, (parc > 3) ? 4 : 3, xparv);
-}
diff --git a/src/modules/unreal_server_compat.c b/src/modules/unreal_server_compat.c
@@ -1,319 +0,0 @@
-/*
- * unreal_server_compat - Compatibility with pre-U6 servers
- * (C) Copyright 2016-2021 Bram Matthys (Syzop)
- * License: GPLv2 or later
- *
- * 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
@@ -1,74 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/unsqline.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   Moved to modules by Fish (Justin Hammond)
- *
- *   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_unsqline);
-
-#define MSG_UNSQLINE    "UNSQLINE"      /* UNSQLINE */
-
-ModuleHeader MOD_HEADER
-  = {
-	"unsqline",	/* Name of module */
-	"5.0", /* Version */
-	"command /unsqline", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_UNSQLINE, cmd_unsqline, MAXPARA, CMD_SERVER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* cmd_unsqline
-**	parv[1] = nickmask
-*/
-CMD_FUNC(cmd_unsqline)
-{
-	const char *tkllayer[6] = {
-		me.name,           /*0  server.name */
-		"-",               /*1  - */
-		"Q",               /*2  Q   */
-		"*",               /*3  unused */
-		parv[1],           /*4  host */
-		client->name       /*5  whoremoved */
-	};
-
-	if (parc < 2)
-		return;
-
-	cmd_tkl(&me, NULL, 6, tkllayer);
-}
diff --git a/src/modules/user.c b/src/modules/user.c
@@ -1,113 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/user.c
- *   (C) 2005 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_user);
-
-#define MSG_USER 	"USER"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"user",
-	"5.0",
-	"command /user", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_USER, cmd_user, 4, CMD_UNREGISTERED);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** The USER command, together with NICK this will register a user.
- * As per UnrealIRCd 5 this command is only available to local clients.
- * Intraserver traffic is handled through the UID command.
- *	parv[1] = username
- *	parv[2] = client host name (ignored)
- *	parv[3] = server host name (ignored)
- *	parv[4] = real name / gecos
- *
- * NOTE: Be advised that multiple USER messages are possible,
- *       hence, always check if a certain struct is already allocated... -- Syzop
- */
-CMD_FUNC(cmd_user)
-{
-	const char *username;
-	const char *realname;
-	char *p;
-
-	if (!MyConnect(client) || IsServer(client))
-		return;
-
-	if (MyConnect(client) && (client->local->listener->options & LISTENER_SERVERSONLY))
-	{
-		exit_client(client, NULL, "This port is for servers only");
-		return;
-	}
-
-	if ((parc < 5) || BadPtr(parv[4]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "USER");
-		return;
-	}
-
-	username = parv[1];
-	realname = parv[4];
-	
-	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, 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))
-	{
-		/* NICK and no-spoof already received, now we have USER... */
-		if (USE_BAN_VERSION && MyConnect(client))
-		{
-			sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
-				me.name, client->name);
-		}
-		register_user(client);
-		return;
-	}
-}
diff --git a/src/modules/userhost-tag.c b/src/modules/userhost-tag.c
@@ -1,111 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/userhost-tag.c
- *   (C) 2020 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"userhost-tag",
-	"5.0",
-	"userhost message tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Variables */
-long CAP_ACCOUNT_TAG = 0L;
-
-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()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "unrealircd.org/userhost";
-	mtag.is_ok = userhost_mtag_is_ok;
-	mtag.should_send_to_client = userhost_mtag_should_send_to_client;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_userhost);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending
- * 'userhost-tag' is permitted to do so and uses a permitted
- * syntax.
- * We simply allow userhost-tag ONLY from servers and with any syntax.
- */
-int userhost_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client))
-		return 1;
-
-	return 0;
-}
-
-void mtag_add_userhost(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m;
-
-	if (IsUser(client))
-	{
-		MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/userhost");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-		} else {
-			char nuh[USERLEN+HOSTLEN+1];
-
-			snprintf(nuh, sizeof(nuh), "%s@%s", client->user->username, client->user->realhost);
-
-			m = safe_alloc(sizeof(MessageTag));
-			safe_strdup(m->name, "unrealircd.org/userhost");
-			safe_strdup(m->value, nuh);
-		}
-		AddListItem(m, *mtag_list);
-	}
-}
-
-/** Outgoing filter for this message tag */
-int userhost_mtag_should_send_to_client(Client *target)
-{
-	if (IsServer(target) || IsOper(target))
-		return 1;
-	return 0;
-}
diff --git a/src/modules/userhost.c b/src/modules/userhost.c
@@ -1,115 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/userhost.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_userhost);
-
-#define MSG_USERHOST 	"USERHOST"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"userhost",
-	"5.0",
-	"command /userhost", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_USERHOST, cmd_userhost, 1, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * cmd_userhost added by Darren Reed 13/8/91 to aid clients and reduce
- * the need for complicated requests like WHOIS. It returns user/host
- * information only (no spurious AWAY labels or channels).
- * Re-written by Dianora 1999
- */
-/* Keep this at 5!!!! */
-#define MAXUSERHOSTREPLIES 5
-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;
-
-	if (parc < 2)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "USERHOST");
-		return;
-	}
-
-	/* The idea is to build up the response string out of pieces
-	 * none of this strlen() nonsense.
-	 * MAXUSERHOSTREPLIES * (NICKLEN*2+CHANNELLEN+USERLEN+HOSTLEN+30) is still << sizeof(buf)
-	 * 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';
-
-	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_user(cn, NULL)))
-		{
-			ircsnprintf(response[w], NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30,
-                            "%s%s=%c%s@%s",
-			    acptr->name,
-			    (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
-				? "*" : "",
-			    (acptr->user->away) ? '-' : '+',
-			    acptr->user->username,
-			    ((acptr != client) && !IsOper(client)
-			    && IsHidden(acptr) ? acptr->user->virthost :
-			    acptr->user->realhost));
-			w++;
-		}
-		if (p)
-			p++;
-		cn = p;
-	}
-
-	sendnumeric(client, RPL_USERHOST, response[0], response[1], response[2], response[3], response[4]);
-}
diff --git a/src/modules/userip-tag.c b/src/modules/userip-tag.c
@@ -1,111 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/userip-tag.c
- *   (C) 2020 Syzop & The UnrealIRCd Team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"userip-tag",
-	"5.0",
-	"userip message tag",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-	};
-
-/* Variables */
-long CAP_ACCOUNT_TAG = 0L;
-
-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()
-{
-	MessageTagHandlerInfo mtag;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mtag, 0, sizeof(mtag));
-	mtag.name = "unrealircd.org/userip";
-	mtag.is_ok = userip_mtag_is_ok;
-	mtag.should_send_to_client = userip_mtag_should_send_to_client;
-	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
-	MessageTagHandlerAdd(modinfo->handle, &mtag);
-
-	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_userip);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** This function verifies if the client sending
- * 'userip-tag' is permitted to do so and uses a permitted
- * syntax.
- * We simply allow userip-tag ONLY from servers and with any syntax.
- */
-int userip_mtag_is_ok(Client *client, const char *name, const char *value)
-{
-	if (IsServer(client))
-		return 1;
-
-	return 0;
-}
-
-void mtag_add_userip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
-{
-	MessageTag *m;
-
-	if (IsUser(client) && client->ip)
-	{
-		MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/userip");
-		if (m)
-		{
-			m = duplicate_mtag(m);
-		} else {
-			char nuh[USERLEN+HOSTLEN+1];
-
-			snprintf(nuh, sizeof(nuh), "%s@%s", client->user->username, GetIP(client));
-
-			m = safe_alloc(sizeof(MessageTag));
-			safe_strdup(m->name, "unrealircd.org/userip");
-			safe_strdup(m->value, nuh);
-		}
-		AddListItem(m, *mtag_list);
-	}
-}
-
-/** Outgoing filter for this message tag */
-int userip_mtag_should_send_to_client(Client *target)
-{
-	if (IsServer(target) || IsOper(target))
-		return 1;
-	return 0;
-}
diff --git a/src/modules/userip.c b/src/modules/userip.c
@@ -1,126 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/userip.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_userip);
-
-#define MSG_USERIP 	"USERIP"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"userip",
-	"5.0",
-	"command /userip", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_USERIP, cmd_userip, 1, CMD_USER);
-	ISupportAdd(modinfo->handle, "USERIP", NULL);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * cmd_userip is based on cmd_userhost
- * cmd_userhost added by Darren Reed 13/8/91 to aid clients and reduce
- * the need for complicated requests like WHOIS. It returns user/host
- * information only (no spurious AWAY labels or channels).
- * Re-written by Dianora 1999
- */
-/* Keep this at 5!!!! */
-#define MAXUSERHOSTREPLIES 5
-CMD_FUNC(cmd_userip)
-{
-
-	char *p;		/* scratch end pointer */
-	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;
-
-	if (!MyUser(client))
-		return;
-		
-	if (parc < 2)
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "USERIP");
-		return;
-	}
-
-	/* The idea is to build up the response string out of pieces
-	 * none of this strlen() nonsense.
-	 * MAXUSERHOSTREPLIES * (NICKLEN*2+CHANNELLEN+USERLEN+HOSTLEN+30) is still << sizeof(buf)
-	 * 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';
-
-	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_user(cn, NULL)))
-		{
-			if (!(ip = GetIP(acptr)))
-				ip = "<unknown>";
-			if (client != acptr && !ValidatePermissionsForPath("client:see:ip",client,acptr,NULL,NULL) && IsHidden(acptr))
-			{
-				make_cloakedhost(acptr, GetIP(acptr), ipbuf, sizeof(ipbuf));
-				ip = ipbuf;
-			}
-
-			ircsnprintf(response[w], NICKLEN * 2 + CHANNELLEN + USERLEN + HOSTLEN + 30, "%s%s=%c%s@%s",
-			    acptr->name,
-			    (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
-				? "*" : "",
-			    (acptr->user->away) ? '-' : '+',
-			    acptr->user->username, ip);
-			w++;
-		}
-		if (p)
-			p++;
-		cn = p;
-	}
-
-	sendnumeric(client, RPL_USERIP, response[0], response[1], response[2], response[3], response[4]);
-}
diff --git a/src/modules/usermodes/Makefile.in b/src/modules/usermodes/Makefile.in
@@ -1,54 +0,0 @@
-#************************************************************************
-#*   IRC - Internet Relay Chat, src/modules/usermodes/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/dns.h \
-	../../include/resource.h ../../include/setup.h \
-	../../include/struct.h ../../include/sys.h \
-	../../include/types.h \
-	../../include/version.h ../../include/whowas.h
-
-R_MODULES=\
-	noctcp.so censor.so bot.so showwhois.so nokick.so servicebot.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)
-
-clean:
-	$(RM) -f *.o *.so *~ core
-
-%.so: %.c $(INCLUDES)
-	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-		-o $@ $<
diff --git a/src/modules/usermodes/bot.c b/src/modules/usermodes/bot.c
@@ -1,105 +0,0 @@
-/*
- * Bot user mode (User mode +B)
- * (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"
-
-#define IsBot(cptr)    (cptr->umodes & UMODE_BOT)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/bot",
-	"4.2",
-	"User Mode +B",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-long UMODE_BOT = 0L;
-
-/* Forward declarations */
-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()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'B', UMODE_GLOBAL, 0, NULL, &UMODE_BOT);
-	ISupportAdd(modinfo->handle, "BOT", "B");
-	
-	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, bot_whois);
-	HookAdd(modinfo->handle, HOOKTYPE_WHO_STATUS, 0, bot_who_status);
-	HookAdd(modinfo->handle, HOOKTYPE_UMODE_CHANGE, 0, bot_umode_change);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int bot_whois(Client *client, Client *target, NameValuePrioList **list)
-{
-	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 *client, Client *target, Channel *channel, Member *cm, const char *status, int cansee)
-{
-	if (IsBot(target))
-		return 'B';
-	
-	return 0;
-}
-
-int bot_umode_change(Client *client, long oldmode, long newmode)
-{
-	if ((newmode & UMODE_BOT) && !(oldmode & UMODE_BOT) && MyUser(client))
-	{
-		/* now +B */
-		const char *parv[2];
-		parv[0] = client->name;
-		parv[1] = NULL;
-		do_cmd(client, NULL, "BOTMOTD", 1, parv);
-	}
-
-	return 0;
-}
diff --git a/src/modules/usermodes/censor.c b/src/modules/usermodes/censor.c
@@ -1,271 +0,0 @@
-/*
- * User Mode +G
- * (C) Copyright 2005-current Bram Matthys and The UnrealIRCd team.
- */
-
-#include "unrealircd.h"
-
-
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/censor",
-	"4.2",
-	"User Mode +G",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-
-long UMODE_CENSOR = 0L;
-
-#define IsCensored(x) (x->umodes & UMODE_CENSOR)
-
-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);
-
-ModuleInfo *ModInfo = NULL;
-
-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, const char *para);
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, censor_config_test);
-	return MOD_SUCCESS;
-}
-	
-MOD_INIT()
-{
-	ModInfo = modinfo;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	UmodeAdd(modinfo->handle, 'G', UMODE_GLOBAL, 0, NULL, &UMODE_CENSOR);
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, censor_can_send_to_user);
-	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, censor_stats_badwords_user);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, censor_config_run);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-MOD_UNLOAD()
-{
-ConfigItem_badword *badword, *next;
-
-	for (badword = conf_badword_message; badword; badword = next)
-	{
-		next = badword->next;
-		DelListItem(badword, conf_badword_message);
-		badword_config_free(badword);
-	}
-	return MOD_SUCCESS;
-}
-
-int censor_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-	char has_word = 0, has_replace = 0, has_action = 0, action = 'r';
-
-	if (type != CONFIG_MAIN)
-		return 0;
-	
-	if (!ce || !ce->name || strcmp(ce->name, "badword"))
-		return 0; /* not interested */
-
-	if (!ce->value)
-	{
-		config_error("%s:%i: badword without type",
-			ce->file->filename, ce->line_number);
-		return 1;
-	}
-	else if (strcmp(ce->value, "message") && strcmp(ce->value, "all")) {
-/*			config_error("%s:%i: badword with unknown type",
-				ce->file->filename, ce->line_number); -- can't do that.. */
-		return 0; /* unhandled */
-	}
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (config_is_blankorempty(cep, "badword"))
-		{
-			errors++;
-			continue;
-		}
-		if (!strcmp(cep->name, "word"))
-		{
-			const char *errbuf;
-			if (has_word)
-			{
-				config_warn_duplicate(cep->file->filename, 
-					cep->line_number, "badword::word");
-				continue;
-			}
-			has_word = 1;
-			if ((errbuf = badword_config_check_regex(cep->value,1,1)))
-			{
-				config_error("%s:%i: badword::%s contains an invalid regex: %s",
-					cep->file->filename,
-					cep->line_number,
-					cep->name, errbuf);
-				errors++;
-			}
-		}
-		else if (!strcmp(cep->name, "replace"))
-		{
-			if (has_replace)
-			{
-				config_warn_duplicate(cep->file->filename, 
-					cep->line_number, "badword::replace");
-				continue;
-			}
-			has_replace = 1;
-		}
-		else if (!strcmp(cep->name, "action"))
-		{
-			if (has_action)
-			{
-				config_warn_duplicate(cep->file->filename, 
-					cep->line_number, "badword::action");
-				continue;
-			}
-			has_action = 1;
-			if (!strcmp(cep->value, "replace"))
-				action = 'r';
-			else if (!strcmp(cep->value, "block"))
-				action = 'b';
-			else
-			{
-				config_error("%s:%d: Unknown badword::action '%s'",
-					cep->file->filename, cep->line_number,
-					cep->value);
-				errors++;
-			}
-				
-		}
-		else
-		{
-			config_error_unknown(cep->file->filename, cep->line_number,
-				"badword", cep->name);
-			errors++;
-		}
-	}
-
-	if (!has_word)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"badword::word");
-		errors++;
-	}
-	if (has_action)
-	{
-		if (has_replace && action == 'b')
-		{
-			config_error("%s:%i: badword::action is block but badword::replace exists",
-				ce->file->filename, ce->line_number);
-			errors++;
-		}
-	}
-	
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-
-int censor_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep, *word = NULL;
-	ConfigItem_badword *ca;
-
-	if (type != CONFIG_MAIN)
-		return 0;
-	
-	if (!ce || !ce->name || strcmp(ce->name, "badword"))
-		return 0; /* not interested */
-
-	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->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "action"))
-		{
-			if (!strcmp(cep->value, "block"))
-			{
-				ca->action = BADWORD_BLOCK;
-			}
-		}
-		else if (!strcmp(cep->name, "replace"))
-		{
-			safe_strdup(ca->replace, cep->value);
-		}
-		else if (!strcmp(cep->name, "word"))
-		{
-			word = cep;
-		}
-	}
-
-	badword_config_process(ca, word->value);
-
-	if (!strcmp(ce->value, "message"))
-	{
-		AddListItem(ca, conf_badword_message);
-	} else
-	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 */
-	}
-
-	return 1;
-}
-
-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, const char **text, const char **errmsg, SendType sendtype)
-{
-	int blocked = 0;
-
-	if (MyUser(client) && IsCensored(target))
-	{
-		*text = stripbadwords_message(*text, &blocked);
-		if (blocked)
-		{
-			*errmsg = "User does not accept private messages containing swearing";
-			return HOOK_DENY;
-		}
-	}
-
-	return HOOK_CONTINUE;
-}
-
-int censor_stats_badwords_user(Client *client, const char *para)
-{
-	ConfigItem_badword *words;
-
-	if (!para || !(!strcmp(para, "b") || !strcasecmp(para, "badword")))
-		return 0;
-
-	for (words = conf_badword_message; words; words = words->next)
-	{
-		sendtxtnumeric(client, "m %c %s%s%s %s", words->type & BADW_TYPE_REGEX ? 'R' : 'F',
-		           (words->type & BADW_TYPE_FAST_L) ? "*" : "", words->word,
-		           (words->type & BADW_TYPE_FAST_R) ? "*" : "",
-		           words->action == BADWORD_REPLACE ? (words->replace ? words->replace : "<censored>") : "");
-	}
-	return 1;
-}
diff --git a/src/modules/usermodes/noctcp.c b/src/modules/usermodes/noctcp.c
@@ -1,86 +0,0 @@
-/*
- * Block user-to-user CTCP UnrealIRCd Module (User Mode +T)
- * (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"
-
-CMD_FUNC(noctcp);
-
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/noctcp",
-	"4.2",
-	"User Mode +T",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-long UMODE_NOCTCP = 0L;
-
-#define IsNoCTCP(client)    (client->umodes & UMODE_NOCTCP)
-
-int noctcp_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-CmodeInfo req;
-
-	UmodeAdd(modinfo->handle, 'T', UMODE_GLOBAL, 0, NULL, &UMODE_NOCTCP);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, noctcp_can_send_to_user);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-static int IsACTCP(const char *s)
-{
-	if (!s)
-		return 0;
-
-	if ((*s == '\001') && strncmp(&s[1], "ACTION ", 7) && strncmp(&s[1], "DCC ", 4))
-		return 1;
-
-	return 0;
-}
-
-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))
-	{
-		*errmsg = "User does not accept CTCPs";
-		return HOOK_DENY;
-	}
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/usermodes/nokick.c b/src/modules/usermodes/nokick.c
@@ -1,100 +0,0 @@
-/*
- * Prevents you from being kicked (User mode +q)
- * (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"
-
-#define IsNokick(client)    (client->umodes & UMODE_NOKICK)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/nokick",
-	"4.2",
-	"User Mode +q",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-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,
-                    const char *comment, const char *client_member_modes, const char *target_member_modes, const char **reject_reason);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'q', UMODE_GLOBAL, 1, umode_allow_unkickable_oper, &UMODE_NOKICK);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_KICK, 0, nokick_can_kick);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int umode_allow_unkickable_oper(Client *client, int what)
-{
-	if (MyUser(client))
-	{
-		if (IsOper(client) && ValidatePermissionsForPath("self:unkickablemode",client,NULL,NULL,NULL))
-			return 1;
-		return 0;
-	}
-	/* Always allow remotes: */
-	return 1;
-}
-
-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];
-
-	if (IsNokick(target) && !IsULine(client) && MyUser(client) && !ValidatePermissionsForPath("channel:override:kick:nokick",client,target,channel,NULL))
-	{
-		ircsnprintf(errmsg, sizeof(errmsg), ":%s %d %s %s :%s",
-		            me.name, ERR_CANNOTDOCOMMAND, client->name, "KICK",
-				   "user is unkickable (user mode +q)");
-
-		*reject_reason = errmsg;
-
-		sendnotice(target,
-			"*** umode q: %s tried to kick you from channel %s (%s)",
-			client->name, channel->name, comment);
-		
-		return EX_ALWAYS_DENY;
-	}
-
-	return EX_ALLOW;
-}
diff --git a/src/modules/usermodes/privacy.c b/src/modules/usermodes/privacy.c
@@ -1,69 +0,0 @@
-/*
- * Privacy - hide channels in /WHOIS (User mode +p)
- * (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"
-
-#define IsPrivacy(client)    (client->umodes & UMODE_PRIVACY)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/privacy",
-	"4.2",
-	"User Mode +p",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-long UMODE_PRIVACY = 0L;
-
-/* Forward declarations */
-int privacy_see_channel_in_whois(Client *client, Client *target, Channel *channel);
-                    
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'p', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_PRIVACY);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_SEE_CHANNEL_IN_WHOIS, 0, privacy_see_channel_in_whois);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* This hides channels in /WHOIS output, unless the requestor is in the same channel
- * or some IRCOp is overriding.
- */
-int privacy_see_channel_in_whois(Client *client, Client *target, Channel *channel)
-{
-	if (IsPrivacy(target) && !IsMember(client, channel))
-		return EX_DENY;
-	
-	return EX_ALLOW;
-}
diff --git a/src/modules/usermodes/privdeaf.c b/src/modules/usermodes/privdeaf.c
@@ -1,59 +0,0 @@
-/*
- * usermode +D: makes it so you cannot receive private messages/notices
- * except from opers, U-lines and servers. -- Syzop
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-= {
-	"usermodes/privdeaf",
-	"1.2",
-	"Private Messages Deaf (+D) -- by Syzop",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-static long UMODE_PRIVDEAF = 0;
-static Umode *UmodePrivdeaf = NULL;
-
-int privdeaf_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	UmodePrivdeaf = UmodeAdd(modinfo->handle, 'D', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_PRIVDEAF);
-	if (!UmodePrivdeaf)
-	{
-		/* I use config_error() here because it's printed to stderr in case of a load
-		 * on cmd line, and to all opers in case of a /rehash.
-		 */
-		config_error("privdeaf: Could not add usermode 'D': %s", ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-	
-	 HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, privdeaf_can_send_to_user);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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))
-	{
-		*errmsg = "User does not accept private messages";
-		return HOOK_DENY;
-	}
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/usermodes/regonlymsg.c b/src/modules/usermodes/regonlymsg.c
@@ -1,72 +0,0 @@
-/*
- * Recieve private messages only from registered users (User mode +R)
- * (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"
-
-#define IsRegOnlyMsg(client)    (client->umodes & UMODE_REGONLYMSG)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/regonlymsg",
-	"4.2",
-	"User Mode +R",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-long UMODE_REGONLYMSG = 0L;
-
-/* Forward declarations */
-int regonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
-                    
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'R', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_REGONLYMSG);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, regonlymsg_can_send_to_user);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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?*text:NULL))
-			return HOOK_CONTINUE; /* bypass this restriction */
-
-		*errmsg = "You must identify to a registered nick to private message this user";
-		return HOOK_DENY;
-	}
-
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/usermodes/secureonlymsg.c b/src/modules/usermodes/secureonlymsg.c
@@ -1,86 +0,0 @@
-/*
- * 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>
- *
- * 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 IsSecureOnlyMsg(client)    (client->umodes & UMODE_SECUREONLYMSG)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/secureonlymsg",
-	"4.2",
-	"User Mode +Z",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-long UMODE_SECUREONLYMSG = 0L;
-
-/* Forward declarations */
-int secureonlymsg_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
-                    
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'Z', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_SECUREONLYMSG);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, secureonlymsg_can_send_to_user);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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?*text:NULL))
-			return HOOK_CONTINUE; /* bypass this restriction */
-
-		*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?*text:NULL))
-			return HOOK_CONTINUE; /* bypass this restriction */
-		
-		/* Similar to above but in this case we are +Z and are trying to message
-		 * 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 TLS and you are +Z";
-		return HOOK_DENY;
-	}
-
-	return HOOK_CONTINUE;
-}
diff --git a/src/modules/usermodes/servicebot.c b/src/modules/usermodes/servicebot.c
@@ -1,144 +0,0 @@
-/*
- * Prevents you from being kicked (User mode +q)
- * (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"
-
-#define IsServiceBot(client)    (client->umodes & UMODE_SERVICEBOT)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/servicebot",
-	"4.2",
-	"User Mode +S",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-long UMODE_SERVICEBOT = 0L;
-
-/* Forward declarations */
-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);
-int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
-                    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()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'S', UMODE_GLOBAL, 1, umode_allow_none, &UMODE_SERVICEBOT);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_CAN_KICK, 0, servicebot_can_kick);
-	HookAdd(modinfo->handle, HOOKTYPE_MODE_DEOP, 0, servicebot_mode_deop);
-	HookAdd(modinfo->handle, HOOKTYPE_PRE_KILL, 0, servicebot_pre_kill);
-	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, servicebot_whois);
-	HookAdd(modinfo->handle, HOOKTYPE_SEE_CHANNEL_IN_WHOIS, 0, servicebot_see_channel_in_whois);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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];
-
-	if (MyUser(client) && !IsULine(client) && IsServiceBot(target))
-	{
-		char errmsg2[NICKLEN+32];
-		snprintf(errmsg2, sizeof(errmsg2), "%s is a Service Bot", target->name);
-		
-		snprintf(errmsg, sizeof(errmsg), ":%s %d %s %s :%s",
-		         me.name, ERR_CANNOTDOCOMMAND, client->name, "KICK", errmsg2);
-
-		*reject_reason = errmsg;
-
-		return EX_DENY;
-	}
-
-	return EX_ALLOW;
-}
-
-int servicebot_mode_deop(Client *client, Client *target, Channel *channel,
-                    u_int what, int modechar, const char *client_access, const char *target_access, const char **reject_reason)
-{
-	static char errmsg[NICKLEN+256];
-	
-	if (IsServiceBot(target) && MyUser(client) && !ValidatePermissionsForPath("services:servicebot:deop",client,target,channel,NULL) && (what == MODE_DEL))
-	{
-		snprintf(errmsg, sizeof(errmsg), ":%s %d %s %c :%s is a Service Bot",
-			me.name, ERR_CANNOTCHANGECHANMODE, client->name, (char)modechar, target->name);
-		
-		*reject_reason = errmsg;
-		
-		return EX_DENY;
-	}
-	
-	return EX_ALLOW;
-}
-
-int servicebot_pre_kill(Client *client, Client *target, const char *reason)
-{
-	if (IsServiceBot(target) && !(ValidatePermissionsForPath("services:servicebot:kill",client,target,NULL,NULL) || IsULine(client)))
-	{
-		sendnumeric(client, ERR_KILLDENY, target->name);
-		return EX_ALWAYS_DENY;
-	}
-	return EX_ALLOW;
-}
-
-int servicebot_whois(Client *client, Client *target, NameValuePrioList **list)
-{
-	int hideoper = (IsHideOper(target) && (client != target) && !IsOper(client)) ? 1 : 0;
-
-	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;
-}
-
-/* This hides the servicebot, even if you are in the same channel, unless oper overriding */
-int servicebot_see_channel_in_whois(Client *client, Client *target, Channel *channel)
-{
-	if (IsServiceBot(target))
-		return EX_DENY;
-	
-	return EX_ALLOW;
-}
diff --git a/src/modules/usermodes/showwhois.c b/src/modules/usermodes/showwhois.c
@@ -1,76 +0,0 @@
-/*
- * Show when someone does a /WHOIS on you (User mode +W)
- * (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"
-
-#define IsWhois(cptr)    (cptr->umodes & UMODE_SHOWWHOIS)
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"usermodes/showwhois",
-	"4.2",
-	"User Mode +W",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* Global variables */
-long UMODE_SHOWWHOIS = 0L;
-
-/* Forward declarations */
-int showwhois_whois(Client *requester, Client *target, NameValuePrioList **list);
-
-MOD_TEST()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	UmodeAdd(modinfo->handle, 'W', UMODE_GLOBAL, 1, umode_allow_opers, &UMODE_SHOWWHOIS);
-	
-	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, showwhois_whois);
-	
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int showwhois_whois(Client *requester, Client *target, NameValuePrioList **list)
-{
-	if (IsWhois(target) && (requester != target))
-	{
-		sendnotice(target,
-			"*** %s (%s@%s) did a /whois on you.",
-			requester->name,
-			requester->user->username, requester->user->realhost);
-	}
-
-	return 0;
-}
diff --git a/src/modules/usermodes/wallops.c b/src/modules/usermodes/wallops.c
@@ -1,111 +0,0 @@
-/*
- *   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
@@ -1,182 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/vhost.c
- *   (C) 2000-2001 Carsten V. Munk 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"
-
-CMD_FUNC(cmd_vhost);
-
-/* Place includes here */
-#define MSG_VHOST       "VHOST"
-
-ModuleHeader MOD_HEADER
-  = {
-	"vhost",	/* Name of module */
-	"5.0", /* Version */
-	"command /vhost", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_VHOST, cmd_vhost, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;	
-}
-
-CMD_FUNC(cmd_vhost)
-{
-	ConfigItem_vhost *vhost;
-	char login[HOSTLEN+1];
-	const char *password;
-	char olduser[USERLEN+1];
-
-	if (!MyUser(client))
-		return;
-
-	if ((parc < 2) || BadPtr(parv[1]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "VHOST");
-		return;
-
-	}
-
-	/* 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.
-	 */
-	strlcpy(login, parv[1], sizeof(login));
-
-	password = (parc > 2) ? parv[2] : "";
-
-	if (!(vhost = find_vhost(login)))
-	{
-		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 (!user_allowed_by_security_group(client, vhost->match))
-	{
-		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))
-	{
-		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;
-	}
-
-	/* Authentication passed, but.. there could still be other restrictions: */
-	switch (UHOST_ALLOWED)
-	{
-		case UHALLOW_NEVER:
-			if (MyUser(client))
-			{
-				sendnotice(client, "*** /vhost is disabled");
-				return;
-			}
-			break;
-		case UHALLOW_ALWAYS:
-			break;
-		case UHALLOW_NOCHANS:
-			if (MyUser(client) && client->user->joined)
-			{
-				sendnotice(client, "*** /vhost can not be used while you are on a channel");
-				return;
-			}
-			break;
-		case UHALLOW_REJOIN:
-			/* join sent later when the host has been changed */
-			break;
-	}
-
-	/* All checks passed, now let's go ahead and change the host */
-
-	userhost_save_current(client);
-
-	safe_strdup(client->user->virthost, vhost->virthost);
-	if (vhost->virtuser)
-	{
-		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);
-	}
-	client->umodes |= UMODE_HIDE;
-	client->umodes |= UMODE_SETHOST;
-	sendto_server(client, 0, 0, NULL, ":%s SETHOST %s", client->id, client->user->virthost);
-	sendto_one(client, NULL, ":%s MODE %s :+tx", client->name, client->name);
-	if (vhost->swhois)
-	{
-		SWhois *s;
-		for (s = vhost->swhois; s; s = s->next)
-			swhois_add(client, "vhost", -100, s->line, &me, NULL);
-	}
-	sendnotice(client, "*** Your vhost is now %s%s%s",
-		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/watch-backend.c b/src/modules/watch-backend.c
@@ -1,392 +0,0 @@
-/*
- *   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 = NULL;
-static char *siphashkey_watch = NULL;
-
-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",
-	"6.0.3",
-	"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;
-}
-
-void watch_generic_free(ModData *m)
-{
-	safe_free(m->ptr);
-}
-
-MOD_INIT()
-{	
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1); /* or do a complex memory freeing algorithm instead */
-
-	LoadPersistentPointer(modinfo, siphashkey_watch, watch_generic_free);
-	if (siphashkey_watch == NULL)
-	{
-		siphashkey_watch = safe_alloc(SIPHASH_KEY_LENGTH);
-		siphash_generate_key(siphashkey_watch);
-	}
-	LoadPersistentPointer(modinfo, watchTable, watch_generic_free);
-	if (watchTable == NULL)
-		watchTable = safe_alloc(sizeof(Watch *) * WATCH_HASH_TABLE_SIZE);
-
-	memset(&mreq, 0 , sizeof(mreq));
-	mreq.type = MODDATATYPE_LOCAL_CLIENT;
-	mreq.name = "watchCount",
-	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()
-{
-	SavePersistentPointer(modinfo, siphashkey_watch);
-	SavePersistentPointer(modinfo, watchTable);
-	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 = watchTable[hashv]))
-		while (watch && mycmp(watch->nick, nick))
-		 watch = watch->hnext;
-	
-	/* If found NULL (no header for this nick), make one... */
-	if (!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 = 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 = 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
@@ -1,429 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/watch.c
- *   (C) 2005 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_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
-  = {
-	"watch",
-	"5.0",
-	"command /watch", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{	
-	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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/*
- * RPL_NOWON	- Online at the moment (Successfully added to WATCH-list)
- * RPL_NOWOFF	- Offline at the moement (Successfully added to WATCH-list)
- */
-static void show_watch(Client *client, char *name, int awaynotify)
-{
-	Client *target;
-
-	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,
-			    (long long)target->user->away_since);
-			return;
-		}
-		
-		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,
-		    (long long)target->lastnick);
-	}
-	else
-	{
-		sendnumeric(client, RPL_WATCHOFF, name, "*", "*", 0LL);
-	}
-}
-
-#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];
-	char buf[BUFSIZE];
-	Client *target;
-	char *s, *user;
-	char *p = NULL, *def = "l";
-	int awaynotify = 0;
-	int did_l=0, did_s=0;
-
-	if (!MyUser(client))
-		return;
-
-	if (parc < 2)
-	{
-		/*
-		 * Default to 'l' - list who's currently online
-		 */
-		parc = 2;
-		parv[1] = def;
-	}
-
-
-	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 */
-			
-		if (!strcmp(s, "A") && WATCH_AWAY_NOTIFICATION)
-			awaynotify = 1;
-
-		/*
-		 * Prefix of "+", they want to add a name to their WATCH
-		 * list.
-		 */
-		if (*s == '+')
-		{
-			if (!*(s+1))
-				continue;
-			if (do_nick_name(s + 1))
-			{
-				if (WATCHES(client) >= MAXWATCH)
-				{
-					sendnumeric(client, ERR_TOOMANYWATCH, s + 1);
-					continue;
-				}
-
-				watch_add(s + 1, client,
-					WATCH_FLAG_TYPE_WATCH | (awaynotify ? WATCH_FLAG_AWAYNOTIFY : 0)
-					);
-			}
-
-			show_watch(client, s + 1, awaynotify);
-			continue;
-		}
-
-		/*
-		 * Prefix of "-", coward wants to remove somebody from their
-		 * WATCH list.  So do it. :-)
-		 */
-		if (*s == '-')
-		{
-			if (!*(s+1))
-				continue;
-			watch_del(s + 1, client, WATCH_FLAG_TYPE_WATCH);
-			show_watch_removed(client, s + 1);
-			continue;
-		}
-
-		/*
-		 * Fancy "C" or "c", they want to nuke their WATCH list and start
-		 * over, so be it.
-		 */
-		if (*s == 'C' || *s == 'c')
-		{
-			watch_del_list(client, WATCH_FLAG_TYPE_WATCH);
-			continue;
-		}
-
-		/*
-		 * Now comes the fun stuff, "S" or "s" returns a status report of
-		 * their WATCH list.  I imagine this could be CPU intensive if its
-		 * done alot, perhaps an auto-lag on this?
-		 */
-		if ((*s == 'S' || *s == 's') && !did_s)
-		{
-			Link *lp;
-			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. This will also include
-			 * other WATCH types if present - we're not checking for
-			 * WATCH_FLAG_TYPE_*.
-			 */
-			watch = watch_get(client->name);
-			if (watch)
-				for (lp = watch->watch, count = 1;
-				    (lp = lp->next); count++)
-					;
-			sendnumeric(client, RPL_WATCHSTAT, WATCHES(client), count);
-
-			/*
-			 * Send a list of everybody in their WATCH list. Be careful
-			 * not to buffer overflow.
-			 */
-			lp = WATCH(client);
-			*buf = '\0';
-			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)
-				{
-					sendnumeric(client, RPL_WATCHLIST, buf);
-					*buf = '\0';
-					count = strlen(client->name) + strlen(me.name) + 10;
-				}
-				strcat(buf, " ");
-				strcat(buf, lp->value.wptr->nick);
-				count += (strlen(lp->value.wptr->nick) + 1);
-				
-				lp = lp->next;
-			}
-			if (*buf)
-				/* anything to send */
-				sendnumeric(client, RPL_WATCHLIST, buf);
-
-			sendnumeric(client, RPL_ENDOFWATCHLIST, *s);
-			continue;
-		}
-
-		/*
-		 * Well that was fun, NOT.  Now they want a list of everybody in
-		 * their WATCH list AND if they are online or offline? Sheesh,
-		 * greedy arn't we?
-		 */
-		if ((*s == 'L' || *s == 'l') && !did_l)
-		{
-			Link *lp = WATCH(client);
-
-			did_l = 1;
-
-			while (lp)
-			{
-				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,
-					    (long long)target->lastnick);
-				}
-				/*
-				 * But actually, only show them offline if its a capital
-				 * 'L' (full list wanted).
-				 */
-				else if (isupper(*s))
-					sendnumeric(client, RPL_NOWOFF,
-					    lp->value.wptr->nick, "*", "*",
-					    (long long)lp->value.wptr->lasttime);
-				lp = lp->next;
-			}
-
-			sendnumeric(client, RPL_ENDOFWATCHLIST, *s);
-
-			continue;
-		}
-
-		/*
-		 * Hmm.. unknown prefix character.. Ignore it. :-)
-		 */
-	}
-}
-
-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
@@ -1,482 +0,0 @@
-/*
- * WebIRC / CGI:IRC Support
- * (C) Copyright 2006-.. 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"
-
-/* Types */
-typedef struct ConfigItem_webirc ConfigItem_webirc;
-
-typedef enum {
-	WEBIRC_PASS=1, WEBIRC_WEBIRC=2
-} WEBIRCType;
-
-struct ConfigItem_webirc {
-	ConfigItem_webirc *prev, *next;
-	ConfigFlag flag;
-	ConfigItem_mask *mask;
-	WEBIRCType type;
-	AuthConfig *auth;
-};
-
-/* Module header */
-ModuleHeader MOD_HEADER
-= {
-	"webirc",
-	"5.0",
-	"WebIRC/CGI:IRC Support",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Global variables */
-ModDataInfo *webirc_md = NULL;
-ConfigItem_webirc *conf_webirc = NULL;
-
-/* Forward declarations */
-CMD_FUNC(cmd_webirc);
-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);
-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);
-
-#define IsWEBIRC(x)			(moddata_client(x, webirc_md).l)
-#define IsWEBIRCSecure(x)	(moddata_client(x, webirc_md).l == 2)
-#define ClearWEBIRC(x)		do { moddata_client(x, webirc_md).l = 0; } while(0)
-#define SetWEBIRC(x)		do { moddata_client(x, webirc_md).l = 1; } while(0)
-#define SetWEBIRCSecure(x)	do { moddata_client(x, webirc_md).l = 2; } while(0)
-
-#define MSG_WEBIRC "WEBIRC"
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, webirc_config_test);
-	return MOD_SUCCESS;
-}
-
-/** Called upon module init */
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "webirc";
-	mreq.type = MODDATATYPE_CLIENT;
-	mreq.serialize = webirc_md_serialize;
-	mreq.unserialize = webirc_md_unserialize;
-	mreq.free = webirc_md_free;
-	mreq.sync = MODDATA_SYNC_EARLY;
-	webirc_md = ModDataAdd(modinfo->handle, mreq);
-	if (!webirc_md)
-	{
-		config_error("could not register webirc moddata");
-		return MOD_FAILED;
-	}
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webirc_config_run);
-	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PASS, 0, webirc_local_pass);
-	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, webirc_secure_connect);
-
-	CommandAdd(modinfo->handle, MSG_WEBIRC, cmd_webirc, MAXPARA, CMD_UNREGISTERED);
-		
-	return MOD_SUCCESS;
-}
-
-/** Called upon module load */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** Called upon unload */
-MOD_UNLOAD()
-{
-	webirc_free_conf();
-	return MOD_SUCCESS;
-}
-
-void webirc_free_conf(void)
-{
-	ConfigItem_webirc *webirc_ptr, *next;
-
-	for (webirc_ptr = conf_webirc; webirc_ptr; webirc_ptr = next)
-	{
-		next = webirc_ptr->next;
-		delete_webircblock(webirc_ptr);
-	}
-	conf_webirc = NULL;
-}
-
-void delete_webircblock(ConfigItem_webirc *e)
-{
-	unreal_delete_masks(e->mask);
-	if (e->auth)
-		Auth_FreeAuthConfig(e->auth);
-	DelListItem(e, conf_webirc);
-	safe_free(e);
-}
-
-int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	ConfigEntry *cep;
-	int errors = 0;
-	char has_mask = 0; /* mandatory */
-	char has_password = 0; /* mandatory */
-	char has_type = 0; /* optional (used for dup checking) */
-	WEBIRCType webirc_type = WEBIRC_WEBIRC; /* the default */
-
-	if (type != CONFIG_MAIN)
-		return 0;
-	
-	if (!ce)
-		return 0;
-	
-	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->file->filename, ce->line_number);
-		*errs = 1;
-		return -1;
-	}
-
-	if (strcmp(ce->name, "webirc"))
-		return 0; /* not interested in non-webirc stuff.. */
-
-	/* Now actually go parse the webirc { } block */
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->value)
-		{
-			config_error_empty(cep->file->filename, cep->line_number,
-				"webirc", cep->name);
-			errors++;
-			continue;
-		}
-		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
-		{
-			if (cep->value || cep->items)
-				has_mask = 1;
-		}
-		else if (!strcmp(cep->name, "password"))
-		{
-			if (has_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->name, "type"))
-		{
-			if (has_type)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "webirc::type");
-			}
-			has_type = 1;
-			if (!strcmp(cep->value, "webirc"))
-				webirc_type = WEBIRC_WEBIRC;
-			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->file->filename, cep->line_number, cep->value);
-				errors++;
-			}
-		}
-		else
-		{
-			config_error_unknown(cep->file->filename, cep->line_number,
-				"webirc", cep->name);
-			errors++;
-		}
-	}
-	if (!has_mask)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"webirc::mask");
-		errors++;
-	}
-
-	if (!has_password && (webirc_type == WEBIRC_WEBIRC))
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"webirc::password");
-		errors++;
-	}
-	
-	if (has_password && (webirc_type == WEBIRC_PASS))
-	{
-		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->file->filename, ce->line_number);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-	ConfigItem_webirc *webirc = NULL;
-	
-	if (type != CONFIG_MAIN)
-		return 0;
-	
-	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->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
-			unreal_add_masks(&webirc->mask, cep);
-		else if (!strcmp(cep->name, "password"))
-			webirc->auth = AuthBlockToAuthConfig(cep);
-		else if (!strcmp(cep->name, "type"))
-		{
-			if (!strcmp(cep->value, "webirc"))
-				webirc->type = WEBIRC_WEBIRC;
-			else if (!strcmp(cep->value, "old"))
-				webirc->type = WEBIRC_PASS;
-			else
-				abort();
-		}
-	}
-	
-	AddListItem(webirc, conf_webirc);
-	
-	return 0;
-}
-
-const char *webirc_md_serialize(ModData *m)
-{
-	static char buf[32];
-	if (m->i == 0)
-		return NULL; /* not set */
-	snprintf(buf, sizeof(buf), "%d", m->i);
-	return buf;
-}
-
-void webirc_md_unserialize(const char *str, ModData *m)
-{
-	m->i = atoi(str);
-}
-
-void webirc_md_free(ModData *md)
-{
-	/* we have nothing to free actually, but we must set to zero */
-	md->l = 0;
-}
-
-ConfigItem_webirc *find_webirc(Client *client, const char *password, WEBIRCType type, char **errorstr)
-{
-	ConfigItem_webirc *e;
-	char *error = NULL;
-
-	for (e = conf_webirc; e; e = e->next)
-	{
-		if ((e->type == type) && unreal_mask_match(client, e->mask))
-		{
-			if (type == WEBIRC_WEBIRC)
-			{
-				/* Check password */
-				if (!Auth_Check(client, e->auth, password))
-					error = "CGI:IRC -- Invalid password";
-				else
-					return e; /* Found matching block, return straight away */
-			} else {
-				return e; /* The WEBIRC_PASS type has no password checking */
-			}
-		}
-	}
-
-	if (error)
-		*errorstr = error; /* Invalid password (this error was delayed) */
-	else
-		*errorstr = "CGI:IRC -- No access"; /* No match found */
-
-	return NULL;
-}
-
-#define WEBIRC_STRING     "WEBIRC_"
-#define WEBIRC_STRINGLEN  (sizeof(WEBIRC_STRING)-1)
-
-/* Does the CGI:IRC host spoofing work */
-void dowebirc(Client *client, const char *ip, const char *host, const char *options)
-{
-	char oldip[64];
-	char scratch[64];
-
-	if (IsWEBIRC(client))
-	{
-		exit_client(client, NULL, "Double CGI:IRC request (already identified)");
-		return;
-	}
-
-	if (host && !strcmp(ip, host))
-		host = NULL; /* host did not resolve, make it NULL */
-
-	/* STEP 1: Update client->local->ip
-	   inet_pton() returns 1 on success, 0 on bad input, -1 on bad AF */
-	if (!is_valid_ip(ip))
-	{
-		/* then we have an invalid IP */
-		exit_client(client, NULL, "Invalid IP address");
-		return;
-	}
-
-	/* STEP 2: Update GetIP() */
-	strlcpy(oldip, client->ip, sizeof(oldip));
-	safe_strdup(client->ip, ip);
-		
-	/* STEP 3: Update client->local->hostp */
-	/* (free old) */
-	if (client->local->hostp)
-	{
-		unreal_free_hostent(client->local->hostp);
-		client->local->hostp = NULL;
-	}
-	/* (create new) */
-	if (host && valid_host(host, 1))
-		client->local->hostp = unreal_create_hostent(host, client->ip);
-
-	/* STEP 4: Update sockhost
-	   Make sure that if this any IPv4 address is _not_ prefixed with
-	   "::ffff:" by using Inet_ia2p().
-	 */
-	// Hmm I ignored above warning. May be bad during transition period.
-	strlcpy(client->local->sockhost, client->ip, sizeof(client->local->sockhost));
-
-	SetWEBIRC(client);
-
-	if (options)
-	{
-		char optionsbuf[BUFSIZE];
-		char *name, *p = NULL, *p2;
-		strlcpy(optionsbuf, options, sizeof(optionsbuf));
-		for (name = strtoken(&p, optionsbuf, " "); name; name = strtoken(&p, NULL, " "))
-		{
-			p2 = strchr(name, '=');
-			if (p2)
-				*p2 = '\0';
-			if (!strcmp(name, "secure") && IsSecure(client))
-			{
-				/* The entire [client]--[webirc gw]--[server] chain is secure */
-				SetWEBIRCSecure(client);
-			}
-		}
-	}
-
-	RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
-}
-
-/* WEBIRC <pass> "cgiirc" <hostname> <ip> [:option1 [option2...]]*/
-CMD_FUNC(cmd_webirc)
-{
-	const char *ip, *host, *password, *options;
-	ConfigItem_webirc *e;
-	char *error = NULL;
-
-	if ((parc < 5) || BadPtr(parv[4]))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "WEBIRC");
-		return;
-	}
-
-	password = parv[1];
-	host = !DONT_RESOLVE ? parv[3] : parv[4];
-	ip = parv[4];
-	options = parv[5]; /* can be NULL */
-
-	/* Check if allowed host */
-	e = find_webirc(client, password, WEBIRC_WEBIRC, &error);
-	if (!e)
-	{
-		exit_client(client, NULL, error);
-		return;
-	}
-
-	/* And do our job.. */
-	dowebirc(client, ip, host, options);
-}
-
-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)
-		{
-			/* Ok now we got that sorted out, proceed:
-			 * Syntax: WEBIRC_<ip>_<resolvedhostname>
-			 * The <resolvedhostname> has been checked ip->host AND host->ip by CGI:IRC itself
-			 * already so we trust it.
-			 */
-			ip = buf + WEBIRC_STRINGLEN;
-			host = strchr(ip, '_');
-			if (!host)
-			{
-				exit_client(client, NULL, "Invalid CGI:IRC IP received");
-				return HOOK_DENY;
-			}
-			*host++ = '\0';
-		
-			dowebirc(client, ip, host, NULL);
-			return HOOK_DENY;
-		}
-		/* fallthrough if not in webirc block.. */
-	}
-
-	return HOOK_CONTINUE; /* not webirc */
-}
-
-/** Called from register_user() right after setting user +z */
-int webirc_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 (IsWEBIRC(client) && IsSecureConnect(client) && !IsWEBIRCSecure(client))
-		client->umodes &= ~UMODE_SECURE;
-	return 0;
-}
diff --git a/src/modules/webredir.c b/src/modules/webredir.c
@@ -1,201 +0,0 @@
-/*
- * webredir UnrealIRCd module
- * (C) Copyright 2019 i <info@servx.org> and the UnrealIRCd team
- *
- * This module will 301-redirect any clients issuing GET's/POST's during pre-connect stage.
- *
- * 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(webredir);
-
-ModuleHeader MOD_HEADER
-  = {
-	"webredir",
-	"1.0",
-	"Do 301 redirect for HEAD/GET/POST/PUT commands", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-struct {
-	char *url;
-} cfg;
-
-static int nowebredir = 1;
-
-static void free_config(void);
-static void init_config(void);
-int webredir_config_posttest(int *errs);
-int webredir_config_test(ConfigFile *, ConfigEntry *, int, int *);
-int webredir_config_run(ConfigFile *, ConfigEntry *, int);
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, webredir_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, webredir_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	init_config();
-	CommandAdd(modinfo->handle, "HEAD", webredir, MAXPARA, CMD_UNREGISTERED);
-	CommandAdd(modinfo->handle, "GET", webredir, MAXPARA, CMD_UNREGISTERED);
-	CommandAdd(modinfo->handle, "POST", webredir, MAXPARA, CMD_UNREGISTERED);
-	CommandAdd(modinfo->handle, "PUT", webredir, MAXPARA, CMD_UNREGISTERED);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webredir_config_run);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (SHOWCONNECTINFO)
-	{
-		config_warn("I'm disabling set::options::show-connect-info for you "
-			    "as this setting is incompatible with the webredir module.");
-		SHOWCONNECTINFO = 0;
-	}
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	free_config();
-	return MOD_SUCCESS;
-}
-
-static void init_config(void)
-{
-	memset(&cfg, 0, sizeof(cfg));
-}
-
-static void free_config(void)
-{
-	safe_free(cfg.url);
-
-	memset(&cfg, 0, sizeof(cfg)); /* needed! */
-}
-
-int webredir_config_posttest(int *errs)
-{
-	int errors = 0;
-
-	if (nowebredir)
-	{
-		config_error("set::webredir is missing!");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int webredir_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	int has_url = 0;
-	ConfigEntry *cep;
-
-	if (type != CONFIG_SET)
-		return 0;
-
-	/* We are only interrested in set::webredir... */
-	if (!ce || !ce->name || strcmp(ce->name, "webredir"))
-		return 0;
-
-	nowebredir = 0;
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->value)
-		{
-			config_error("%s:%i: set::webredir::%s with no value",
-				cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		}
-		else if (!strcmp(cep->name, "url"))
-		{
-			if (!*cep->value || strchr(cep->value, ' '))
-			{
-				config_error("%s:%i: set::webredir::%s with empty value",
-					cep->file->filename, cep->line_number, cep->name);
-				errors++;
-			}
-			if (!strstr(cep->value, "://") || !strcmp(cep->value, "https://..."))
-			{
-				config_error("%s:%i: set::webredir::url needs to be a valid URL",
-					cep->file->filename, cep->line_number);
-				errors++;
-			}
-			if (has_url)
-			{
-				config_warn_duplicate(cep->file->filename,
-					cep->line_number, "set::webredir::url");
-				continue;
-			}
-			has_url = 1;
-		}
-		else
-		{
-			config_error("%s:%i: unknown directive set::webredir::%s",
-				cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		}
-	}
-
-	if (!has_url)
-	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"set::webredir::url");
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int webredir_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	if (type != CONFIG_SET)
-		return 0;
-	
-	/* We are only interrested in set::webredir... */
-	if (!ce || !ce->name || strcmp(ce->name, "webredir"))
-		return 0;
-	
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "url"))
-		{
-			safe_strdup(cfg.url, cep->value);
-		}
-	}
-	return 1;
-}
-
-CMD_FUNC(webredir)
-{
-	if (!MyConnect(client))
-		return;
-
-	sendto_one(client, NULL, "HTTP/1.1 301 Moved Permanently");
-	sendto_one(client, NULL, "Location: %s\r\n\r\n", cfg.url);
-	dead_socket(client, "Connection closed");
-}
diff --git a/src/modules/webserver.c b/src/modules/webserver.c
@@ -1,675 +0,0 @@
-/*
- * Webserver
- * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- */
-   
-#include "unrealircd.h"
-#include "dns.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"webserver",
-	"1.0.0",
-	"Webserver",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-#if CHAR_MIN < 0
- #error "In UnrealIRCd char should always be unsigned. Check your compiler"
-#endif
-
-/* How many seconds to wait with closing after sending the response */
-#define WEB_CLOSE_TIME 1
-
-/* The "Server: xyz" in the response */
-#define WEB_SOFTWARE "UnrealIRCd"
-
-/* Macros */
-#define WEB(client)		((WebRequest *)moddata_client(client, webserver_md).ptr)
-#define WEBSERVER(client)	((client->local && client->local->listener) ? client->local->listener->webserver : NULL)
-#define reset_handshake_timeout(client, delta)  do { client->local->creationtime = TStime() - iConf.handshake_timeout + delta; } while(0)
-
-/* Forward declarations */
-int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
-int webserver_packet_in(Client *client, const char *readbuf, int *length);
-void webserver_mdata_free(ModData *m);
-int webserver_handle_packet(Client *client, const char *readbuf, int length);
-int webserver_handle_handshake(Client *client, const char *readbuf, int *length);
-int webserver_handle_request_header(Client *client, const char *readbuf, int *length);
-void _webserver_send_response(Client *client, int status, char *msg);
-void _webserver_close_client(Client *client);
-int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int length);
-
-/* Global variables */
-ModDataInfo *webserver_md;
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_SEND_RESPONSE, _webserver_send_response);
-	EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_CLOSE_CLIENT, _webserver_close_client);
-	EfunctionAdd(modinfo->handle, EFUNC_WEBSERVER_HANDLE_BODY, _webserver_handle_body);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	//HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, webserver_packet_out);
-	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, webserver_packet_in);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "web";
-	mreq.serialize = NULL;
-	mreq.unserialize = NULL;
-	mreq.free = webserver_mdata_free;
-	mreq.sync = 0;
-	mreq.type = MODDATATYPE_CLIENT;
-	webserver_md = ModDataAdd(modinfo->handle, mreq);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** UnrealIRCd internals: free WebRequest object. */
-void webserver_mdata_free(ModData *m)
-{
-	WebRequest *wsu = (WebRequest *)m->ptr;
-	if (wsu)
-	{
-		safe_free(wsu->uri);
-		free_nvplist(wsu->headers);
-		safe_free(wsu->lefttoparse);
-		safe_free(wsu->request_buffer);
-		safe_free(m->ptr);
-	}
-}
-
-/** Outgoing packet hook.
- * Do we need this?
- */
-int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
-{
-	static char utf8buf[510];
-
-	if (MyConnect(to) && WEB(to))
-	{
-		// TODO: Inhibit all?
-		// Websocket can override though?
-		return 0;
-	}
-	return 0;
-}
-
-HttpMethod webserver_get_method(const char *buf)
-{
-	if (!strncmp(buf, "HEAD ", 5))
-		return HTTP_METHOD_HEAD;
-	if (!strncmp(buf, "GET ", 4))
-		return HTTP_METHOD_GET;
-	if (!strncmp(buf, "PUT ", 4))
-		return HTTP_METHOD_PUT;
-	if (!strncmp(buf, "POST ", 5))
-		return HTTP_METHOD_POST;
-	return HTTP_METHOD_NONE; /* invalid */
-}
-
-void webserver_possible_request(Client *client, const char *buf, int len)
-{
-	HttpMethod method;
-
-	if (len < 8)
-		return;
-
-	/* Probably redundant, but just to be sure, if already tagged, then don't change it! */
-	if (WEB(client))
-		return;
-
-	method = webserver_get_method(buf);
-	if (method == HTTP_METHOD_NONE)
-		return; /* invalid */
-
-	moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
-	WEB(client)->method = method;
-
-	/* Set some default values: */
-	WEB(client)->content_length = -1;
-	WEB(client)->config_max_request_buffer_size = 4096; /* 4k */
-}
-
-/** Incoming packet hook. This processes web requests.
- * NOTE The different return values:
- * -1 means: don't touch this client anymore, it has or might have been killed!
- * 0 means: don't process this data, but you can read another packet if you want
- * >0 means: process this data (regular IRC data, non-web stuff)
- */
-int webserver_packet_in(Client *client, const char *readbuf, int *length)
-{
-	if ((client->local->traffic.messages_received == 0) && WEBSERVER(client))
-		webserver_possible_request(client, readbuf, *length);
-
-	if (!WEB(client))
-		return 1; /* "normal" IRC client */
-
-	if (WEB(client)->request_header_parsed)
-		return WEBSERVER(client)->handle_body(client, WEB(client), readbuf, *length);
-
-	/* else.. */
-	return webserver_handle_request_header(client, readbuf, length);
-}
-
-/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
-int webserver_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request)
-{
-	static char buf[4096], *nextptr;
-	char *p;
-	char *k = NULL, *v = NULL;
-	int foundlf = 0;
-
-	if (buffer)
-	{
-		/* Initialize */
-		if (len > sizeof(buf) - 1)
-			len = sizeof(buf) - 1;
-
-		memcpy(buf, buffer, len);
-		buf[len] = '\0';
-		nextptr = buf;
-	}
-
-	*end_of_request = 0;
-
-	p = nextptr;
-
-	if (!p)
-	{
-		*key = *value = NULL;
-		return 0; /* done processing data */
-	}
-
-	if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2))
-	{
-		*key = *value = NULL;
-		*end_of_request = 1;
-		return 0;
-	}
-
-	/* Note: p *could* point to the NUL byte ('\0') */
-
-	/* Special handling for GET line itself. */
-	if (webserver_get_method(p) != HTTP_METHOD_NONE)
-	{
-		k = "REQUEST";
-		p = strchr(p, ' ') + 1; /* space (0x20) is guaranteed to be there, see strncmp above */
-		v = p; /* SET VALUE */
-		nextptr = NULL; /* set to "we are done" in case next for loop fails */
-		for (; *p; p++)
-		{
-			if (*p == ' ')
-			{
-				*p = '\0'; /* terminate before "HTTP/1.X" part */
-			}
-			else if (*p == '\r')
-			{
-				*p = '\0'; /* eat silently, but don't consider EOL */
-			}
-			else if (*p == '\n')
-			{
-				*p = '\0';
-				nextptr = p+1; /* safe, there is data or at least a \0 there */
-				break;
-			}
-		}
-		*key = k;
-		*value = v;
-		return 1;
-	}
-
-	/* Header parsing starts here.
-	 * Example line "Host: www.unrealircd.org"
-	 */
-	k = p; /* SET KEY */
-
-	/* First check if the line contains a terminating \n. If not, don't process it
-	 * as it may have been a cut header.
-	 */
-	for (; *p; p++)
-	{
-		if (*p == '\n')
-		{
-			foundlf = 1;
-			break;
-		}
-	}
-
-	if (!foundlf)
-	{
-		*key = *value = NULL;
-		*lastloc = k;
-		return 0;
-	}
-
-	p = k;
-
-	for (; *p; p++)
-	{
-		if ((*p == '\n') || (*p == '\r'))
-		{
-			/* Reached EOL but 'value' not found */
-			*p = '\0';
-			break;
-		}
-		if (*p == ':')
-		{
-			*p++ = '\0';
-			if (*p++ != ' ')
-				break; /* missing mandatory space after ':' */
-
-			v = p; /* SET VALUE */
-			nextptr = NULL; /* set to "we are done" in case next for loop fails */
-			for (; *p; p++)
-			{
-				if (*p == '\r')
-				{
-					*p = '\0'; /* eat silently, but don't consider EOL */
-				}
-				else if (*p == '\n')
-				{
-					*p = '\0';
-					nextptr = p+1; /* safe, there is data or at least a \0 there */
-					break;
-				}
-			}
-			/* A key-value pair was succesfully parsed, return it */
-			*key = k;
-			*value = v;
-			return 1;
-		}
-	}
-
-	/* Fatal parse error */
-	*key = *value = NULL;
-	return 0;
-}
-
-/** Check if there is any data at the end of the request */
-char *find_end_of_request(char *header, int totalsize, int *remaining_bytes)
-{
-	char *nextframe1;
-	char *nextframe2;
-	char *nextframe = NULL;
-
-	// find first occurance, yeah this is just stupid, but it works.
-	nextframe1 = strstr(header, "\r\n\r\n"); // = +4
-	nextframe2 = strstr(header, "\n\n");     // = +2
-	if (nextframe1 && nextframe2)
-	{
-		if (nextframe1 < nextframe2)
-		{
-			nextframe = nextframe1 + 4;
-		} else {
-			nextframe = nextframe2 + 2;
-		}
-	} else
-	if (nextframe1)
-	{
-		nextframe = nextframe1 + 4;
-	} else
-	if (nextframe2)
-	{
-		nextframe = nextframe2 + 2;
-	}
-	if (nextframe)
-	{
-		*remaining_bytes = totalsize - (nextframe - header);
-		if (*remaining_bytes > 0)
-			return nextframe;
-	}
-	return NULL;
-}
-
-/** Handle HTTP request
- * Yes, I'm going to assume that the header fits in one packet and one packet only.
- */
-int webserver_handle_request_header(Client *client, const char *readbuf, int *length)
-{
-	char *key, *value;
-	int r, end_of_request;
-	static char netbuf[16384];
-	static char netbuf2[16384];
-	char *lastloc = NULL;
-	int n, maxcopy, nprefix=0;
-	int totalsize;
-
-	/* Totally paranoid: */
-	memset(netbuf, 0, sizeof(netbuf));
-	memset(netbuf2, 0, sizeof(netbuf2));
-
-	/** Frame re-assembling starts here **/
-	if (WEB(client)->lefttoparse)
-	{
-		strlcpy(netbuf, WEB(client)->lefttoparse, sizeof(netbuf));
-		nprefix = strlen(netbuf);
-	}
-	maxcopy = sizeof(netbuf) - nprefix - 1;
-	/* (Need to do some manual checking here as strlen() can't be safely used
-	 *  on readbuf. Same is true for strlncat since it uses strlen().)
-	 */
-	n = *length;
-	if (n > maxcopy)
-		n = maxcopy;
-	if (n <= 0)
-	{
-		webserver_close_client(client); // Oversized line
-		return -1;
-	}
-	memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
-	totalsize = n + nprefix;
-	netbuf[totalsize] = '\0';
-	memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc".
-	safe_free(WEB(client)->lefttoparse);
-
-	/** Now step through the lines.. **/
-	for (r = webserver_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
-	     r;
-	     r = webserver_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request))
-	{
-		if (BadPtr(value))
-			continue; /* skip empty values */
-
-		if (!strcasecmp(key, "REQUEST"))
-		{
-			safe_strdup(WEB(client)->uri, value);
-		} else
-		{
-			if (!strcasecmp(key, "Content-Length"))
-			{
-				WEB(client)->content_length = atoll(value);
-			} else
-			if (!strcasecmp(key, "Transfer-Encoding"))
-			{
-				if (!strcasecmp(value, "chunked"))
-					WEB(client)->transfer_encoding = TRANSFER_ENCODING_CHUNKED;
-			}
-			add_nvplist(&WEB(client)->headers, WEB(client)->num_headers, key, value);
-		}
-	}
-
-	if (end_of_request)
-	{
-		int n;
-		int remaining_bytes = 0;
-		char *nextframe;
-
-		/* Some sanity checks */
-		if (!WEB(client)->uri)
-		{
-			webserver_send_response(client, 400, "Malformed HTTP request");
-			return -1;
-		}
-
-		WEB(client)->request_header_parsed = 1;
-		n = WEBSERVER(client)->handle_request(client, WEB(client));
-		if ((n <= 0) || IsDead(client))
-			return n; /* byebye */
-		
-		/* There could be data directly after the request header (eg for
-		 * a POST or PUT), check for it here so it isn't lost.
-		 */
-		nextframe = find_end_of_request(netbuf2, totalsize, &remaining_bytes);
-		if (nextframe)
-			return WEBSERVER(client)->handle_body(client, WEB(client), nextframe, remaining_bytes);
-		return 0;
-	}
-
-	if (lastloc)
-	{
-		/* Last line was cut somewhere, save it for next round. */
-		safe_strdup(WEB(client)->lefttoparse, lastloc);
-	}
-	return 0; /* don't let UnrealIRCd process this */
-}
-
-/** Send a HTTP(S) response.
- * @param client	Client to send to
- * @param status	HTTP status code
- * @param msg		The message body.
- * @note if 'msgs' is NULL then don't close the connection.
- */
-void _webserver_send_response(Client *client, int status, char *msg)
-{
-	char buf[512];
-	char *statusmsg = "???";
-
-	if (status == 200)
-		statusmsg = "OK";
-	else if (status == 201)
-		statusmsg = "Created";
-	else if (status == 500)
-		statusmsg = "Internal Server Error";
-	else if (status == 400)
-		statusmsg = "Bad Request";
-	else if (status == 401)
-		statusmsg = "Unauthorized";
-	else if (status == 403)
-		statusmsg = "Forbidden";
-	else if (status == 404)
-		statusmsg = "Not Found";
-	else if (status == 416)
-		statusmsg = "Range Not Satisfiable";
-
-	snprintf(buf, sizeof(buf),
-		"HTTP/1.1 %d %s\r\nServer: %s\r\nConnection: close\r\n\r\n",
-		status, statusmsg, WEB_SOFTWARE);
-	if (msg)
-	{
-		strlcat(buf, msg, sizeof(buf));
-		strlcat(buf, "\n", sizeof(buf));
-	}
-
-	dbuf_put(&client->local->sendQ, buf, strlen(buf));
-	if (msg)
-		webserver_close_client(client);
-}
-
-/** Close a web client softly, after data has been sent. */
-void _webserver_close_client(Client *client)
-{
-	send_queued(client);
-	if (DBufLength(&client->local->sendQ) == 0)
-	{
-		exit_client(client, NULL, "End of request");
-		//dead_socket(client, "");
-	} else {
-		send_queued(client);
-		reset_handshake_timeout(client, WEB_CLOSE_TIME);
-	}
-}
-
-int webserver_handle_body_append_buffer(Client *client, const char *buf, int len)
-{
-	/* Guard.. */
-	if (len <= 0)
-	{
-		dead_socket(client, "HTTP request error");
-		return 0;
-	}
-
-	if (WEB(client)->request_buffer)
-	{
-		long long newsize = WEB(client)->request_buffer_size + len + 1;
-		if (newsize > WEB(client)->config_max_request_buffer_size)
-		{
-			/* We would overflow */
-			unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
-			           "[webserver] Client $client: request body too large ($length)",
-			           log_data_integer("length", newsize));
-			dead_socket(client, "");
-			return 0;
-		}
-		WEB(client)->request_buffer = realloc(WEB(client)->request_buffer, newsize);
-	} else
-	{
-		if (len + 1 > WEB(client)->config_max_request_buffer_size)
-		{
-			/* We would overflow */
-			unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
-			           "[webserver] Client $client: request body too large ($length)",
-			           log_data_integer("length", len+1));
-			dead_socket(client, "");
-			return 0;
-		}
-		WEB(client)->request_buffer = malloc(len+1);
-	}
-	memcpy(WEB(client)->request_buffer + WEB(client)->request_buffer_size, buf, len);
-	WEB(client)->request_buffer_size += len;
-	WEB(client)->request_buffer[WEB(client)->request_buffer_size] = '\0';
-	return 1;
-}
-
-/** Handle HTTP body parsing, eg for a PUT request, concatting it all together.
- * @param client	The client
- * @param web		The WEB(client)
- * @param readbuf	Packet in the read buffer
- * @param pktsize	Packet size of the read buffer
- * @return 1 to continue processing, 0 if client is killed.
- */
-int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int pktsize)
-{
-	char *buf;
-	long long n;
-	char *free_this_buffer = NULL;
-
-	if (WEB(client)->transfer_encoding == TRANSFER_ENCODING_NONE)
-	{
-		if (!webserver_handle_body_append_buffer(client, readbuf, pktsize))
-			return 0;
-
-		if ((WEB(client)->content_length >= 0) &&
-		    (WEB(client)->request_buffer_size >= WEB(client)->content_length))
-		{
-			WEB(client)->request_body_complete = 1;
-		}
-		return 1;
-	}
-
-	/* Fill 'buf' nd set 'buflen' with what we had + what we have now.
-	 * Makes things easy.
-	 */
-	if (WEB(client)->lefttoparse)
-	{
-		n = WEB(client)->lefttoparselen + pktsize;
-		free_this_buffer = buf = safe_alloc(n);
-		memcpy(buf, WEB(client)->lefttoparse, WEB(client)->lefttoparselen);
-		memcpy(buf+WEB(client)->lefttoparselen, readbuf, pktsize);
-		safe_free(WEB(client)->lefttoparse);
-		WEB(client)->lefttoparselen = 0;
-	} else {
-		n = pktsize;
-		free_this_buffer = buf = safe_alloc(n);
-		memcpy(buf, readbuf, n);
-	}
-
-	/* Chunked transfers.. yayyyy.. */
-	while (n > 0)
-	{
-		if (WEB(client)->chunk_remaining > 0)
-		{
-			/* Eat it */
-			int eat = MIN(WEB(client)->chunk_remaining, n);
-			if (!webserver_handle_body_append_buffer(client, buf, eat))
-			{
-				/* fatal error such as size exceeded */
-				safe_free(free_this_buffer);
-				return 0;
-			}
-			n -= eat;
-			buf += eat;
-			WEB(client)->chunk_remaining -= eat;
-		} else
-		{
-			int gotlf = 0;
-			int i;
-
-			/* First check if it is a (trailing) empty line,
-			 * eg from a previous chunk. Skip over.
-			 */
-			if ((n >= 2) && !strncmp(buf, "\r\n", 2))
-			{
-				buf += 2;
-				n -= 2;
-			} else
-			if ((n >= 1) && !strncmp(buf, "\n", 1))
-			{
-				buf++;
-				n--;
-			}
-
-			/* Now we are (possibly) at the chunk size line,
-			 * this is or example '7f' + newline.
-			 * So first, check if we have a newline at all.
-			 */
-			for (i=0; i < n; i++)
-			{
-				if (buf[i] == '\n')
-				{
-					gotlf = 1;
-					break;
-				}
-			}
-			if (!gotlf)
-			{
-				/* The line telling us the chunk size is incomplete,
-				 * as it does not contain an \n. Wait for more data
-				 * from the network socket.
-				 */
-				if (n > 0)
-				{
-					/* Store what we have first.. */
-					WEB(client)->lefttoparselen = n;
-					WEB(client)->lefttoparse = safe_alloc(n);
-					memcpy(WEB(client)->lefttoparse, buf, n);
-				}
-				safe_free(free_this_buffer);
-				return 1; /* WE WANT MORE! */
-			}
-			buf[i] = '\0'; /* cut at LF */
-			i++; /* point to next data */
-			WEB(client)->chunk_remaining = strtol(buf, NULL, 16);
-			if (WEB(client)->chunk_remaining < 0)
-			{
-				unreal_log(ULOG_WARNING, "webserver", "WEB_NEGATIVE_CHUNK", client,
-				           "Webrequest from $client: Negative chunk encountered");
-				safe_free(free_this_buffer);
-				dead_socket(client, "");
-				return 0;
-			}
-			if (WEB(client)->chunk_remaining == 0)
-			{
-				/* DONE! */
-				WEB(client)->request_body_complete = 1;
-				safe_free(free_this_buffer);
-				return 1;
-			}
-			buf += i;
-			n -= i;
-		}
-	}
-
-	safe_free(free_this_buffer);
-	return 1;
-}
diff --git a/src/modules/websocket.c b/src/modules/websocket.c
@@ -1,688 +0,0 @@
-/*
- * websocket - WebSocket support (RFC6455)
- * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- * This module was sponsored by Aberrant Software Inc.
- */
-   
-#include "unrealircd.h"
-#include "dns.h"
-
-#define WEBSOCKET_VERSION "1.1.0"
-
-ModuleHeader MOD_HEADER
-  = {
-	"websocket",
-	WEBSOCKET_VERSION,
-	"WebSocket support (RFC6455)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-#if CHAR_MIN < 0
- #error "In UnrealIRCd char should always be unsigned. Check your compiler"
-#endif
-
-#ifndef WEBSOCKET_SEND_BUFFER_SIZE
- #define WEBSOCKET_SEND_BUFFER_SIZE 16384
-#endif
-
-#define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
-
-#define WEBSOCKET_PORT(client)	((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
-#define WEBSOCKET_TYPE(client)	(WSU(client)->type)
-
-/* 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_posttest(int *);
-int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
-int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
-int websocket_handle_handshake(Client *client, const char *readbuf, int *length);
-int websocket_handshake_send_response(Client *client);
-int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
-int websocket_secure_connect(Client *client);
-struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input);
-int websocket_ip_compare(const char *ip1, const char *ip2);
-int websocket_handle_request(Client *client, WebRequest *web);
-
-/* Global variables */
-ModDataInfo *websocket_md;
-static int ws_text_mode_available = 1;
-
-MOD_TEST()
-{
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, websocket_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, websocket_config_posttest);
-
-	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
-	if (!websocket_md)
-		config_warn("The 'websocket_common' module is not loaded, even though it was promised to be ???");
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, websocket_config_run_ex);
-	HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, websocket_packet_out);
-	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, websocket_secure_connect);
-
-	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (non_utf8_nick_chars_in_use || (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY))
-		ws_text_mode_available = 0;
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-	int has_type = 0;
-	static char errored_once_nick = 0;
-
-	if (type != CONFIG_LISTEN_OPTIONS)
-		return 0;
-
-	/* We are only interrested in listen::options::websocket.. */
-	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "type"))
-		{
-			CheckNull(cep);
-			has_type = 1;
-			if (!strcmp(cep->value, "text"))
-			{
-				if (non_utf8_nick_chars_in_use && !errored_once_nick)
-				{
-					/* This one is a hard error, since the consequences are grave */
-					config_error("You have a websocket listener with type 'text' AND your set::allowed-nickchars contains at least one non-UTF8 character set.");
-					config_error("This is a very BAD idea as this makes your websocket vulnerable to UTF8 conversion attacks. "
-					             "This can cause things like unkickable users and 'ghosts' for websocket users.");
-					config_error("You have 4 options: 1) Remove the websocket listener, 2) Use websocket type 'binary', "
-					             "3) Remove the non-UTF8 character set from set::allowed-nickchars, 4) Replace the non-UTF8 with an UTF8 character set in set::allowed-nickchars");
-					config_error("For more details see https://www.unrealircd.org/docs/WebSocket_support#websockets-and-non-utf8");
-					errored_once_nick = 1;
-					errors++;
-				}
-			}
-			else if (!strcmp(cep->value, "binary"))
-			{
-			}
-			else
-			{
-				config_error("%s:%i: listen::options::websocket::type must be either 'binary' or 'text' (not '%s')",
-					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->file->filename, cep->line_number, cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	if (!has_type)
-	{
-		config_error("%s:%i: websocket set, but type unspecified. Use something like: listen { ip *; port 443; websocket { type text; } }",
-			ce->file->filename, ce->line_number);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
-{
-	ConfigEntry *cep, *cepp;
-	ConfigItem_listen *l;
-	static char warned_once_channel = 0;
-
-	if (type != CONFIG_LISTEN_OPTIONS)
-		return 0;
-
-	/* We are only interrested in listen::options::websocket.. */
-	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
-		return 0;
-
-	l = (ConfigItem_listen *)ptr;
-	l->webserver = safe_alloc(sizeof(WebServer));
-	l->webserver->handle_request = websocket_handle_request;
-	l->webserver->handle_body = websocket_handle_body_websocket;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "type"))
-		{
-			if (!strcmp(cep->value, "binary"))
-				l->websocket_options = WEBSOCKET_TYPE_BINARY;
-			else if (!strcmp(cep->value, "text"))
-			{
-				l->websocket_options = WEBSOCKET_TYPE_TEXT;
-				if ((tempiConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY) && !warned_once_channel)
-				{
-					/* This one is a warning, since the consequences are less grave than with nicks */
-					config_warn("You have a websocket listener with type 'text' AND your set::allowed-channelchars is set to 'any'.");
-					config_warn("This is not a recommended combination as this makes your websocket vulnerable to UTF8 conversion attacks. "
-					            "This can cause things like unpartable channels for websocket users.");
-					config_warn("It is highly recommended that you use set { allowed-channelchars utf8; }");
-					config_warn("For more details see https://www.unrealircd.org/docs/WebSocket_support#websockets-and-non-utf8");
-					warned_once_channel = 1;
-				}
-			}
-		} else if (!strcmp(cep->name, "forward"))
-		{
-			safe_strdup(l->websocket_forward, cep->value);
-		}
-	}
-	return 1;
-}
-
-int websocket_config_posttest(int *errs)
-{
-	int errors = 0;
-	char webserver_module = 1, websocket_common_module = 1;
-
-	if (!is_module_loaded("webserver"))
-	{
-		config_error("The 'websocket' module requires the 'webserver' module to be loaded, otherwise websocket connections will not work!");
-		webserver_module = 0;
-		errors++;
-	}
-
-	if (!is_module_loaded("websocket_common"))
-	{
-		config_error("The 'websocket' module requires the 'websocket_common' module to be loaded, otherwise websocket connections will not work!");
-		websocket_common_module = 0;
-		errors++;
-	}
-
-	/* Is nicer for the admin when these are grouped... */
-	if (!webserver_module)
-		config_error("Add the following line to your config file: loadmodule \"webserver\";");
-	if (!websocket_common_module)
-		config_error("Add the following line to your config file: loadmodule \"websocket_common\";");
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-/* Add LF (if needed) to a buffer. Max 4K. */
-void add_lf_if_needed(char **buf, int *len)
-{
-	static char newbuf[MAXLINELENGTH];
-	char *b = *buf;
-	int l = *len;
-
-	if (l <= 0)
-		return; /* too short */
-
-	if (b[l - 1] == '\n')
-		return; /* already contains \n */
-
-	if (l >= sizeof(newbuf)-2)
-		l = sizeof(newbuf)-2; /* cut-off if necessary */
-
-	memcpy(newbuf, b, l);
-	newbuf[l] = '\n';
-	newbuf[l + 1] = '\0'; /* not necessary, but I like zero termination */
-	l++;
-	*buf = newbuf; /* new buffer */
-	*len = l; /* new length */
-}
-
-/** Called on decoded websocket frame (INPUT).
- * Should contain exactly 1 IRC line (command)
- */
-int websocket_irc_callback(Client *client, char *buf, int len)
-{
-	add_lf_if_needed(&buf, &len);
-	if (!process_packet(client, buf, len, 1)) /* Let UnrealIRCd handle this as usual */
-		return 0; /* client killed */
-	return 1;
-}
-
-int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
-{
-	return websocket_handle_websocket(client, web, readbuf2, length2, websocket_irc_callback);
-}
-
-/** Outgoing packet hook.
- * This transforms the output to be Websocket-compliant, if necessary.
- */
-int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
-{
-	static char utf8buf[510];
-
-	if (MyConnect(to) && !IsRPC(to) && websocket_md && WSU(to) && WSU(to)->handshake_completed)
-	{
-		if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
-			websocket_create_packet(WSOP_BINARY, msg, length);
-		else if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_TEXT)
-		{
-			/* Some more conversions are needed */
-			char *safe_msg = unrl_utf8_make_valid(*msg, utf8buf, sizeof(utf8buf), 1);
-			*msg = safe_msg;
-			*length = *msg ? strlen(safe_msg) : 0;
-			websocket_create_packet(WSOP_TEXT, msg, length);
-		}
-		return 0;
-	}
-	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;
-}
-
-/** We got a HTTP(S) request and we need to check if we can upgrade the connection
- * to a websocket connection.
- */
-int websocket_handle_request(Client *client, WebRequest *web)
-{
-	NameValuePrioList *r;
-	const char *key, *value;
-
-	/* Allocate a new WebSocketUser struct for this session */
-	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
-	/* ...and set the default protocol (text or binary) */
-	WSU(client)->type = client->local->listener->websocket_options;
-
-	/** Now step through the lines.. **/
-	for (r = web->headers; r; r = r->next)
-	{
-		key = r->name;
-		value = r->value;
-		if (!strcasecmp(key, "Sec-WebSocket-Key"))
-		{
-			if (strchr(value, ':'))
-			{
-				/* This would cause unserialization issues. Should be base64 anyway */
-				webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
-				return -1;
-			}
-			safe_strdup(WSU(client)->handshake_key, value);
-		} else
-		if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
-		{
-			/* Save it here, will be processed later */
-			safe_strdup(WSU(client)->sec_websocket_protocol, value);
-		} else
-		if (!strcasecmp(key, "Forwarded"))
-		{
-			/* will be processed later too */
-			safe_strdup(WSU(client)->forwarded, value);
-		}
-	}
-
-	/** Finally, validate the websocket request (handshake) and proceed or reject. */
-
-	/* Not websocket and webredir loaded? Let that module serve a redirect. */
-	if (!WSU(client)->handshake_key)
-	{
-		if (is_module_loaded("webredir"))
-		{
-			const char *parx[2] = { NULL, NULL };
-			do_cmd(client, NULL, "GET", 1, parx);
-		}
-		webserver_send_response(client, 404, "This port is for IRC WebSocket only");
-		return 0;
-	}
-
-	/* Sec-WebSocket-Protocol (optional) */
-	if (WSU(client)->sec_websocket_protocol)
-	{
-		char *p = NULL, *name;
-		int negotiated = 0;
-
-		for (name = strtoken(&p, WSU(client)->sec_websocket_protocol, ",");
-		     name;
-		     name = strtoken(&p, NULL, ","))
-		{
-			skip_whitespace(&name);
-			if (!strcmp(name, "binary.ircv3.net"))
-			{
-				negotiated = WEBSOCKET_TYPE_BINARY;
-				break; /* First hit wins */
-			} else
-			if (!strcmp(name, "text.ircv3.net") && ws_text_mode_available)
-			{
-				negotiated = WEBSOCKET_TYPE_TEXT;
-				break; /* First hit wins */
-			}
-		}
-		if (negotiated == WEBSOCKET_TYPE_BINARY)
-		{
-			WSU(client)->type = WEBSOCKET_TYPE_BINARY;
-			safe_strdup(WSU(client)->sec_websocket_protocol, "binary.ircv3.net");
-		} else
-		if (negotiated == WEBSOCKET_TYPE_TEXT)
-		{
-			WSU(client)->type = WEBSOCKET_TYPE_TEXT;
-			safe_strdup(WSU(client)->sec_websocket_protocol, "text.ircv3.net");
-		} else
-		{
-			/* Negotiation failed, fallback to the default (don't set it here) */
-			safe_free(WSU(client)->sec_websocket_protocol);
-		}
-	}
-
-	/* Check forwarded header (by k4be) */
-	if (WSU(client)->forwarded)
-	{
-		struct HTTPForwardedHeader *forwarded;
-		char oldip[64];
-
-		/* check for source ip */
-		if (BadPtr(client->local->listener->websocket_forward) || !websocket_ip_compare(client->local->listener->websocket_forward, client->ip))
-		{
-			unreal_log(ULOG_WARNING, "websocket", "UNAUTHORIZED_FORWARDED_HEADER", client, "Received unauthorized Forwarded header from $ip", log_data_string("ip", client->ip));
-			webserver_send_response(client, 403, "Forwarded: no access");
-			return 0;
-		}
-		/* parse the header */
-		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));
-			webserver_send_response(client, 400, "Forwarded: invalid IP");
-			return 0;
-		}
-		/* store data */
-		WSU(client)->secure = forwarded->secure;
-		strlcpy(oldip, client->ip, sizeof(oldip));
-		safe_strdup(client->ip, forwarded->ip);
-		/* Update client->local->hostp */
-		strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
-		/* (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 */
-			}
-		}
-		RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
-	}
-
-	websocket_handshake_send_response(client);
-	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) && websocket_md && WSU(client) && WSU(client)->forwarded && !WSU(client)->secure)
-		client->umodes &= ~UMODE_SECURE;
-	return 0;
-}
-
-/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
-int websocket_handshake_send_response(Client *client)
-{
-	char buf[512], hashbuf[64];
-	char sha1out[20]; /* 160 bits */
-
-	WSU(client)->handshake_completed = 1;
-
-	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
-	sha1hash_binary(sha1out, buf, strlen(buf));
-	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
-
-	snprintf(buf, sizeof(buf),
-	         "HTTP/1.1 101 Switching Protocols\r\n"
-	         "Upgrade: websocket\r\n"
-	         "Connection: Upgrade\r\n"
-	         "Sec-WebSocket-Accept: %s\r\n",
-	         hashbuf);
-
-	if (WSU(client)->sec_websocket_protocol)
-	{
-		/* using strlen() is safe here since above buffer will not
-		 * cause it to be >=512 and thus we won't get into negatives.
-		 */
-		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
-		         "Sec-WebSocket-Protocol: %s\r\n",
-		         WSU(client)->sec_websocket_protocol);
-	}
-
-	strlcat(buf, "\r\n", sizeof(buf));
-
-	/* Caution: we bypass sendQ flood checking by doing it this way.
-	 * Risk is minimal, though, as we only permit limited text only
-	 * once per session.
-	 */
-	dbuf_put(&client->local->sendQ, buf, strlen(buf));
-	send_queued(client);
-
-	return 0;
-}
-
-/** 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/websocket_common.c b/src/modules/websocket_common.c
@@ -1,523 +0,0 @@
-/*
- * websocket_common - Common WebSocket functions (RFC6455)
- * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
- * License: GPLv2 or later
- * The websocket module was sponsored by Aberrant Software Inc.
- */
-   
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER
-  = {
-	"websocket_common",
-	"6.0.0",
-	"WebSocket support (RFC6455)",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-#if CHAR_MIN < 0
- #error "In UnrealIRCd char should always be unsigned. Check your compiler"
-#endif
-
-#ifndef WEBSOCKET_SEND_BUFFER_SIZE
- #define WEBSOCKET_SEND_BUFFER_SIZE 16384
-#endif
-
-#define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
-
-/* used to parse http Forwarded header (RFC 7239) */
-#define IPLEN 48
-#define FHEADER_NAMELEN	20
-
-struct HTTPForwardedHeader
-{
-	int secure;
-	char hostname[HOSTLEN+1];
-	char ip[IPLEN+1];
-};
-
-/* Forward declarations - public functions */
-int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
-int _websocket_create_packet(int opcode, char **buf, int *len);
-int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
-int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
-/* Forward declarations - other */
-int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
-int websocket_handle_packet_ping(Client *client, const char *buf, int len);
-int websocket_handle_packet_pong(Client *client, const char *buf, int len);
-int websocket_send_pong(Client *client, const char *buf, int len);
-const char *websocket_mdata_serialize(ModData *m);
-void websocket_mdata_unserialize(const char *str, ModData *m);
-void websocket_mdata_free(ModData *m);
-
-/* Global variables */
-ModDataInfo *websocket_md;
-static int ws_text_mode_available = 1;
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
-	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
-	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
-	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);
-
-	/* Init first, since we manage sockets */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT);
-
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "websocket";
-	mreq.serialize = websocket_mdata_serialize;
-	mreq.unserialize = websocket_mdata_unserialize;
-	mreq.free = websocket_mdata_free;
-	mreq.sync = MODDATA_SYNC_EARLY;
-	mreq.type = MODDATATYPE_CLIENT;
-	websocket_md = ModDataAdd(modinfo->handle, mreq);
-
-	/* Unload last, since we manage sockets */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD);
-
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
-{
-	int n;
-	char *ptr;
-	int length;
-	int length1 = WSU(client)->lefttoparselen;
-	char readbuf[MAXLINELENGTH];
-
-	length = length1 + length2;
-	if (length > sizeof(readbuf)-1)
-	{
-		dead_socket(client, "Illegal buffer stacking/Excess flood");
-		return 0;
-	}
-
-	if (length1 > 0)
-		memcpy(readbuf, WSU(client)->lefttoparse, length1);
-	memcpy(readbuf+length1, readbuf2, length2);
-
-	safe_free(WSU(client)->lefttoparse);
-	WSU(client)->lefttoparselen = 0;
-
-	ptr = readbuf;
-	do {
-		n = websocket_handle_packet(client, ptr, length, callback);
-		if (n < 0)
-			return -1; /* killed -- STOP processing */
-		if (n == 0)
-		{
-			/* Short read. Stop processing for now, but save data for next time */
-			safe_free(WSU(client)->lefttoparse);
-			WSU(client)->lefttoparse = safe_alloc(length);
-			WSU(client)->lefttoparselen = length;
-			memcpy(WSU(client)->lefttoparse, ptr, length);
-			return 0;
-		}
-		length -= n;
-		ptr += n;
-		if (length < 0)
-			abort(); /* less than 0 is impossible */
-	} while(length > 0);
-
-	return 0;
-}
-
-/** WebSocket packet handler.
- * For more information on the format, check out page 28 of RFC6455.
- * @returns The number of bytes processed (the size of the frame)
- *          OR 0 to indicate a possible short read (want more data)
- *          OR -1 in case of an error.
- */
-int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len))
-{
-	char opcode; /**< Opcode */
-	char masked; /**< Masked */
-	int len; /**< Length of the packet */
-	char maskkey[4]; /**< Key used for masking */
-	const char *p;
-	int total_packet_size;
-	char *payload = NULL;
-	static char payloadbuf[READBUF_SIZE];
-	int maskkeylen = 4;
-
-	if (length < 4)
-	{
-		/* WebSocket packet too short */
-		return 0;
-	}
-
-	/* fin    = readbuf[0] & 0x80; -- unused */
-	opcode = readbuf[0] & 0x7F;
-	masked = readbuf[1] & 0x80;
-	len    = readbuf[1] & 0x7F;
-	p = &readbuf[2]; /* point to next element */
-
-	/* actually 'fin' is unused.. we don't care. */
-
-	/* Masked. According to RFC6455 page 29:
-	 * "All frames sent from client to server have this bit set to 1."
-	 * But in practice i see that for PONG this may not always be
-	 * true, so let's make an exception for that...
-	 */
-	if (!masked && (opcode != WSOP_PONG))
-	{
-		dead_socket(client, "WebSocket packet not masked");
-		return -1; /* Having the masked bit set is required (RFC6455 p29) */
-	}
-
-	if (!masked)
-		maskkeylen = 0;
-
-	if (len == 127)
-	{
-		dead_socket(client, "WebSocket packet with insane size");
-		return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
-	}
-
-	total_packet_size = len + 2 + maskkeylen; /* 2 for header, 4 for mask key, rest for payload */
-
-	/* Early (minimal) length check */
-	if (length < total_packet_size)
-	{
-		/* WebSocket frame too short */
-		return 0;
-	}
-
-	/* Len=126 is special. It indicates the data length is actually "126 or more" */
-	if (len == 126)
-	{
-		/* Extended payload length (16 bit). For packets of >=126 bytes */
-		len = (readbuf[2] << 8) + readbuf[3];
-		if (len < 126)
-		{
-			dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
-			return -1; /* This is a violation (not a short read), see page 29 */
-		}
-		p += 2; /* advance pointer 2 bytes */
-
-		/* Need to check the length again, now it has changed: */
-		if (length < len + 4 + maskkeylen)
-		{
-			/* WebSocket frame too short */
-			return 0;
-		}
-		/* And update the packet size */
-		total_packet_size = len + 4 + maskkeylen; /* 4 for header, 4 for mask key, rest for payload */
-	}
-
-	if (masked)
-	{
-		memcpy(maskkey, p, maskkeylen);
-		p+= maskkeylen;
-	}
-
-	if (len > 0)
-	{
-		memcpy(payloadbuf, p, len);
-		payload = payloadbuf;
-	} /* else payload is NULL */
-
-	if (masked && (len > 0))
-	{
-		/* Unmask this thing (page 33, section 5.3) */
-		int n;
-		char v;
-		char *p;
-		for (p = payload, n = 0; n < len; n++)
-		{
-			v = *p;
-			*p++ = v ^ maskkey[n % 4];
-		}
-	}
-
-	switch(opcode)
-	{
-		case WSOP_CONTINUATION:
-		case WSOP_TEXT:
-		case WSOP_BINARY:
-			if (len > 0)
-			{
-				if (!callback(client, payload, len))
-					return -1; /* fatal error occured (such as flood kill) */
-			}
-			return total_packet_size;
-
-		case WSOP_CLOSE:
-			dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
-			return -1;
-
-		case WSOP_PING:
-			if (websocket_handle_packet_ping(client, payload, len) < 0)
-				return -1;
-			return total_packet_size;
-
-		case WSOP_PONG:
-			if (websocket_handle_packet_pong(client, payload, len) < 0)
-				return -1;
-			return total_packet_size;
-
-		default:
-			dead_socket(client, "WebSocket: Unknown opcode");
-			return -1;
-	}
-
-	return -1; /* NOTREACHED */
-}
-
-int websocket_handle_packet_ping(Client *client, const char *buf, int len)
-{
-	if (len > 500)
-	{
-		dead_socket(client, "WebSocket: oversized PING request");
-		return -1;
-	}
-	websocket_send_pong(client, buf, len);
-	add_fake_lag(client, 1000); /* lag penalty of 1 second */
-	return 0;
-}
-
-int websocket_handle_packet_pong(Client *client, const char *buf, int len)
-{
-	/* We only care about pongs for RPC websocket connections.
-	 * Also, we don't verify the content, actually,
-	 * so don't use this for security like a pingpong cookie.
-	 */
-	if (IsRPC(client))
-	{
-		client->local->last_msg_received = TStime();
-		ClearPingSent(client);
-	}
-	return 0;
-}
-
-/** Create a simple websocket packet that is ready to be sent.
- * This is the simple version that is used ONLY for WSOP_PONG,
- * as it does not take \r\n into account.
- */
-int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
-{
-	static char sendbuf[8192];
-
-	sendbuf[0] = opcode | 0x80; /* opcode & final */
-
-	if (*len > sizeof(sendbuf) - 8)
-		return -1; /* should never happen (safety) */
-
-	if (*len < 126)
-	{
-		/* Short payload */
-		sendbuf[1] = (char)*len;
-		memcpy(&sendbuf[2], *buf, *len);
-		*buf = sendbuf;
-		*len += 2;
-	} else {
-		/* Long payload */
-		sendbuf[1] = 126;
-		sendbuf[2] = (char)((*len >> 8) & 0xFF);
-		sendbuf[3] = (char)(*len & 0xFF);
-		memcpy(&sendbuf[4], *buf, *len);
-		*buf = sendbuf;
-		*len += 4;
-	}
-	return 0;
-}
-
-/** Create a websocket packet that is ready to be send.
- * This version takes into account stripping off \r and \n,
- * and possibly multi line due to labeled-response.
- * It is used for WSOP_TEXT and WSOP_BINARY.
- * The end result is one or more websocket frames,
- * all in a single packet *buf with size *len.
- *
- * This is the version that uses the specified buffer,
- * it is used from the JSON-RPC code,
- * and indirectly from websocket_create_packet().
- */
-int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
-{
-	char *s = *buf; /* points to start of current line */
-	char *s2; /* used for searching of end of current line */
-	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
-	int bytes_to_copy;
-	char newline;
-	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
-	int bytes_in_sendbuf = 0;
-	int bytes_single_frame;
-
-	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
-	if (*len == 0)
-		return -1;
-
-	do {
-		/* Find next \r or \n */
-		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
-		{
-			if ((*s2 == '\n') || (*s2 == '\r'))
-				break;
-		}
-
-		/* Now 's' points to start of line and 's2' points to beyond end of the line
-		 * (either at \r, \n or beyond the buffer).
-		 */
-		bytes_to_copy = s2 - s;
-
-		if (bytes_to_copy < 126)
-			bytes_single_frame = 2 + bytes_to_copy;
-		else if (bytes_to_copy < 65536)
-			bytes_single_frame = 4 + bytes_to_copy;
-		else
-			bytes_single_frame = 10 + bytes_to_copy;
-
-		if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
-		{
-			/* Overflow. This should never happen. */
-			unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
-			           "[BUG] [websocket] Overflow prevented in _websocket_create_packet(): "
-			           "$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
-			           log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
-			           log_data_integer("bytes_single_frame", bytes_single_frame),
-			           log_data_integer("sendbuf_size", sendbufsize));
-			return -1;
-		}
-
-		/* Create the new frame */
-		o[0] = opcode | 0x80; /* opcode & final */
-
-		if (bytes_to_copy < 126)
-		{
-			/* Short payload */
-			o[1] = (char)bytes_to_copy;
-			memcpy(&o[2], s, bytes_to_copy);
-		} else
-		if (bytes_to_copy < 65536)
-		{
-			/* Long payload */
-			o[1] = 126;
-			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
-			o[3] = (char)(bytes_to_copy & 0xFF);
-			memcpy(&o[4], s, bytes_to_copy);
-		} else {
-			/* Longest payload */
-			// XXX: yeah we don't support sending more than 4GB.
-			o[1] = 127;
-			o[2] = 0;
-			o[3] = 0;
-			o[4] = 0;
-			o[5] = 0;
-			o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
-			o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
-			o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
-			o[9] = (char)(bytes_to_copy & 0xFF);
-			memcpy(&o[10], s, bytes_to_copy);
-		}
-
-		/* Advance destination pointer and counter */
-		o += bytes_single_frame;
-		bytes_in_sendbuf += bytes_single_frame;
-
-		/* Advance source pointer and skip all trailing \n and \r */
-		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
-	} while(s <= lastbyte);
-
-	*buf = sendbuf;
-	*len = bytes_in_sendbuf;
-	return 0;
-}
-
-/** Create a websocket packet that is ready to be send.
- * This version takes into account stripping off \r and \n,
- * and possibly multi line due to labeled-response.
- * It is used for WSOP_TEXT and WSOP_BINARY.
- * The end result is one or more websocket frames,
- * all in a single packet *buf with size *len.
- *
- * This is the version that uses a static sendbuf buffer,
- * it is used from IRC websockets.
- */
-int _websocket_create_packet(int opcode, char **buf, int *len)
-{
-	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
-	return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
-}
-
-/** Create and send a WSOP_PONG frame */
-int websocket_send_pong(Client *client, const char *buf, int len)
-{
-	const char *b = buf;
-	int l = len;
-
-	if (_websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
-		return -1;
-
-	if (DBufLength(&client->local->sendQ) > get_sendq(client))
-	{
-		dead_socket(client, "Max SendQ exceeded");
-		return -1;
-	}
-
-	dbuf_put(&client->local->sendQ, b, l);
-	send_queued(client);
-	return 0;
-}
-
-/** UnrealIRCd internals: free WebSocketUser object. */
-void websocket_mdata_free(ModData *m)
-{
-	WebSocketUser *wsu = (WebSocketUser *)m->ptr;
-	if (wsu)
-	{
-		safe_free(wsu->handshake_key);
-		safe_free(wsu->lefttoparse);
-		safe_free(wsu->sec_websocket_protocol);
-		safe_free(wsu->forwarded);
-		safe_free(m->ptr);
-	}
-}
-
-/** This only serializes wsu->type atm */
-const char *websocket_mdata_serialize(ModData *m)
-{
-	static char buf[32];
-	WebSocketUser *wsu = m->ptr;
-
-	if (!wsu)
-		return NULL; /* not set */
-
-	snprintf(buf, sizeof(buf), "%d", wsu->type);
-	return buf;
-}
-
-/** This only sets wsu->type atm */
-void websocket_mdata_unserialize(const char *str, ModData *m)
-{
-	WebSocketUser *wsu;
-	if (m->ptr)
-		websocket_mdata_free(m);
-	if (BadPtr(str))
-		return; /* empty/freed */
-	m->ptr = wsu = safe_alloc(sizeof(WebSocketUser));
-	wsu->type = atoi(str);
-}
diff --git a/src/modules/who_old.c b/src/modules/who_old.c
@@ -1,842 +0,0 @@
-/*   src/modules/who_old.c
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Computing Center
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers.
- *
- *   This program is free softwmare; 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.
- */
-
-/* rewritten 06/02 by larne, the old one was unreadable. */
-/* changed indentation + some parts rewritten by Syzop. */
-
-#include "unrealircd.h"
-
-CMD_FUNC(cmd_who);
-
-/* Place includes here */
-#define MSG_WHO 	"WHO"
-
-ModuleHeader MOD_HEADER
-  = {
-	"who_old",	/* Name of module */
-	"5.0", /* Version */
-	"command /who (old version)", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-/* This is called on module init, before Server Ready */
-MOD_INIT()
-{
-	if (!CommandAdd(modinfo->handle, MSG_WHO, cmd_who, MAXPARA, CMD_USER))
-	{
-		config_warn("You cannot load both the cmd_whox and cmd_who module. You should ONLY load the cmd_whox module.");
-		return MOD_FAILED;
-	}
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-/* Is first run when server is 100% ready */
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-
-/* Called when module is unloaded */
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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, 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 */
-#define WF_ONCHANNEL 0x02 /**< we're on the channel we're /who'ing */
-#define WF_WILDCARD  0x04 /**< a wildcard /who */
-#define WF_REALHOST  0x08 /**< want real hostnames */
-#define WF_IP	     0x10 /**< want IP addresses */
-
-static int who_flags;
-
-#define WHO_CANTSEE 0x01 /**< set if we can't see them */
-#define WHO_CANSEE  0x02 /**< set if we can */
-#define WHO_OPERSEE 0x04 /**< set if we only saw them because we're an oper */
-
-#define FVC_HIDDEN  0x01
-
-#define WHO_WANT 1
-#define WHO_DONTWANT 2
-#define WHO_DONTCARE 0
-
-struct {
-	int want_away;
-	int want_channel;
-	const char *channel; /**< if they want one */
-	int want_gecos;
-	const char *gecos;
-	int want_server;
-	const char *server;
-	int want_host;
-	const char *host;
-	int want_nick;
-	const char *nick;
-	int want_user;
-	const char *user;
-	int want_ip;
-	const char *ip;
-	int want_port;
-	int port;
-	int want_umode;
-	int umodes_dontwant;
-	int umodes_want;
-	int common_channels_only;
-} wfl;
-
-/** The /who command: retrieves information from users. */
-CMD_FUNC(cmd_who)
-{
-	Channel *target_channel;
-	const char *mask = parv[1];
-	char maskbuf[512];
-	int i = 0;
-
-	if (!MyUser(client))
-		return;
-
-	who_flags = 0;
-	memset(&wfl, 0, sizeof(wfl));
-
-	if (parc > 1)
-	{
-		i = parse_who_options(client, parc - 1, parv + 1);
-		if (i < 0)
-		{
-			sendnumeric(client, RPL_ENDOFWHO, mask);
-			return;
-		}
-	}
-
-	if (parc-i < 2 || strcmp(parv[1 + i], "0") == 0)
-		mask = "*";
-	else
-		mask = parv[1 + i];
-
-	if (!i && parc > 2 && *parv[2] == 'o')
-		who_flags |= WF_OPERONLY;
-
-	/* Pfff... collapse... hate it! */
-	strlcpy(maskbuf, mask, sizeof(maskbuf));
-	collapse(maskbuf);
-	mask = maskbuf;
-
-	if (*mask == '\0')
-	{
-		/* no mask given */
-		sendnumeric(client, RPL_ENDOFWHO, "*");
-		return;
-	}
-
-	if ((target_channel = find_channel(mask)) != NULL)
-	{
-		do_channel_who(client, target_channel, mask);
-		sendnumeric(client, RPL_ENDOFWHO, mask);
-		return;
-	}
-
-	if (wfl.channel && wfl.want_channel == WHO_WANT && 
-	    (target_channel = find_channel(wfl.channel)) != NULL)
-	{
-		do_channel_who(client, target_channel, mask);
-		sendnumeric(client, RPL_ENDOFWHO, mask);
-		return;
-	}
-	else
-	{
-		do_other_who(client, mask);
-		sendnumeric(client, RPL_ENDOFWHO, mask);
-		return;
-	}
-
-	return;
-}
-
-static void who_sendhelp(Client *client)
-{
-  char *who_help[] = {
-    "/WHO [+|-][achmnsuM] [args]",
-    "Flags are specified like channel modes, the flags chmnsu all have arguments",
-    "Flags are set to a positive check by +, a negative check by -",
-    "The flags work as follows:",
-    "Flag a: user is away",
-    "Flag c <channel>:       user is on <channel>,",
-    "                        no wildcards accepted",
-    "Flag h <host>:          user has string <host> in their hostname,",
-    "                        wildcards accepted",
-    "Flag m <usermodes>:     user has <usermodes> set, only",
-    "                        O/o/C/A/a/N/B are allowed",
-    "Flag n <nick>:          user has string <nick> in their nickname,",
-    "                        wildcards accepted",
-    "Flag s <server>:        user is on server <server>,",
-    "                        wildcards not accepted",
-    "Flag u <user>:          user has string <user> in their username,",
-    "                        wildcards accepted",
-    "Behavior flags:",
-    "Flag M: check for user in channels I am a member of",
-    NULL
-  };
-
-  char *who_oper_help[] = {
-    "/WHO [+|-][acghimnsuMRI] [args]",
-    "Flags are specified like channel modes, the flags chigmnsu all have arguments",
-    "Flags are set to a positive check by +, a negative check by -",
-    "The flags work as follows:",
-    "Flag a: user is away",
-    "Flag c <channel>:       user is on <channel>,",
-    "                        no wildcards accepted",
-    "Flag g <gcos/realname>: user has string <gcos> in their GCOS,",
-    "                        wildcards accepted",
-    "Flag h <host>:          user has string <host> in their hostname,",
-    "                        wildcards accepted",
-    "Flag i <ip>:            user has string <ip> in their IP address,",
-    "                        wildcards accepted",
-    "Flag p <port>:          user is connecting on port <port>,",
-    "                        local connections only",
-    "Flag m <usermodes>:     user has <usermodes> set",
-    "Flag n <nick>:          user has string <nick> in their nickname,",
-    "                        wildcards accepted",
-    "Flag s <server>:        user is on server <server>,",
-    "                        wildcards not accepted",
-    "Flag u <user>:          user has string <user> in their username,",
-    "                        wildcards accepted",
-    "Behavior flags:",
-    "Flag M: check for user in channels I am a member of",
-    "Flag R: show users' real hostnames",
-    "Flag I: show users' IP addresses",
-    NULL
-  };
-  char **s;
-
-	if (IsOper(client))
-		s = who_oper_help;
-	else
-		s = who_help;
-
-	for (; *s; s++)
-		sendnumeric(client, RPL_LISTSYNTAX, *s);
-}
-
-#define WHO_ADD 1
-#define WHO_DEL 2
-
-static int parse_who_options(Client *client, int argc, const char **argv)
-{
-	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. */
-
-/** function requiress a parameter: check if there's one, if not: return -1. */
-#define REQUIRE_PARAM() { if (i >= argc) { \
-                           who_sendhelp(client); \
-                           return -1; \
-                      } } while(0);
-/** set option 'x' depending on 'what' (add/want or del/dontwant) */
-#define SET_OPTION(x) { if (what == WHO_ADD) \
-                           x = WHO_WANT; \
-                      else \
-                           x = WHO_DONTWANT; \
-                      } while(0);
-/** Eat a param, set the param in memory and set the option to want or dontwant */
-#define DOIT(x,y) { REQUIRE_PARAM(); x = argv[i]; SET_OPTION(y); i++; } while(0);
-
-	if (*s != '-' && *s != '+')
-		return 0;
-
-	while (*s)
- 	{
-		switch (*s)
-		{
-			case '+':
-	  			what = WHO_ADD;
-	  			break;
-			case '-':
-				what = WHO_DEL;
-				break;
-			case 'a':
-				SET_OPTION(wfl.want_away);
-				break;
-			case 'c':
-				DOIT(wfl.channel, wfl.want_channel);
-				break;
-			case 'g':
-				REQUIRE_PARAM()
-				if (!IsOper(client))
-					break; /* oper-only */
-				wfl.gecos = argv[i];
-				SET_OPTION(wfl.want_gecos);
-				i++;
-				break;
-			case 's':
-				DOIT(wfl.server, wfl.want_server);
-				break;
-			case 'h':
-				DOIT(wfl.host, wfl.want_host);
-				break;
-			case 'i':
-				REQUIRE_PARAM()
-				if (!IsOper(client))
-					break; /* oper-only */
-				wfl.ip = argv[i];
-				SET_OPTION(wfl.want_ip);
-				i++;
-				break;
-			case 'n':
-				DOIT(wfl.nick, wfl.want_nick);
-				break;
-			case 'u':
-				DOIT(wfl.user, wfl.want_user);
-				break;
-			case 'm':
-				REQUIRE_PARAM()
-				{
-					const char *s = argv[i];
-					int *umodes;
-
-					if (what == WHO_ADD)
-						umodes = &wfl.umodes_want;
-					else
-						umodes = &wfl.umodes_dontwant;
-
-					*umodes = set_usermode(s);
-
-					if (!IsOper(client))
-						*umodes = *umodes & UMODE_OPER; /* these are usermodes regular users may search for. just oper now. */
-					if (*umodes == 0)
-						return -1;
-				}
-				i++;
-				break;
-			case 'p':
-				REQUIRE_PARAM()
-				if (!IsOper(client))
-					break; /* oper-only */
-				wfl.port = atoi(argv[i]);
-				SET_OPTION(wfl.want_port);
-				i++;
-				break;
-			case 'M':
-				SET_OPTION(wfl.common_channels_only);
-				break;
-			case 'R':
-				if (!IsOper(client))
-					break;
-				if (what == WHO_ADD)
-					who_flags |= WF_REALHOST;
-				else
-					who_flags &= ~WF_REALHOST;
-				break;
-			case 'I':
-				if (!IsOper(client))
-					break;
-				if (what == WHO_ADD)
-					who_flags |= WF_IP;
-				
-				else
-					who_flags &= ~WF_IP;
-				break;
-			default:
-				who_sendhelp(client);
-				return -1;
-		}
-		s++;
-    }
-
-  return i;
-#undef REQUIRE_PARAM
-#undef SET_OPTION
-#undef DOIT
-}
-
-static int can_see(Client *requester, Client *target, Channel *channel)
-{
-	int ret = 0;
-	char has_common_chan = 0;
-
-	do {
-		/* can only see people */
-		if (!IsUser(target))
-			return WHO_CANTSEE;
-
-		/* can only see opers if thats what they want */
-		if (who_flags & WF_OPERONLY)
-		{
-			if (!IsOper(target))
-				return ret | WHO_CANTSEE;
-			if (IsHideOper(target)) {
-				if (IsOper(requester))
-					ret |= WHO_OPERSEE;
-				else
-					return ret | WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people who are away */
-		if ((wfl.want_away == WHO_WANT && !target->user->away) ||
-		    (wfl.want_away == WHO_DONTWANT && target->user->away))
-			return WHO_CANTSEE;
-
-		/* if they only want people on a certain channel. */
-		if (wfl.want_channel != WHO_DONTCARE)
- 		{
-			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))
-				return WHO_CANTSEE;
-			if ((wfl.want_channel == WHO_DONTWANT) && IsMember(target, chan))
-				return WHO_CANTSEE;
-		}
-
-		/* if they only want people with a certain gecos */
-		if (wfl.want_gecos != WHO_DONTCARE)
-		{
-			if (((wfl.want_gecos == WHO_WANT) && !match_simple(wfl.gecos, target->info)) ||
-			    ((wfl.want_gecos == WHO_DONTWANT) && match_simple(wfl.gecos, target->info)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people with a certain server */
-		if (wfl.want_server != WHO_DONTCARE)
-		{
-			if (((wfl.want_server == WHO_WANT) && strcasecmp(wfl.server, target->user->server)) ||
-			    ((wfl.want_server == WHO_DONTWANT) && !strcasecmp(wfl.server, target->user->server)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people with a certain host */
-		if (wfl.want_host != WHO_DONTCARE)
-		{
-			char *host;
-
-			if (IsOper(requester))
-				host = target->user->realhost;
-			else
-				host = GetHost(target);
-
-			if (((wfl.want_host == WHO_WANT) && !match_simple(wfl.host, host)) ||
-			    ((wfl.want_host == WHO_DONTWANT) && match_simple(wfl.host, host)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people with a certain IP */
-		if (wfl.want_ip != WHO_DONTCARE)
-		{
-			char *ip;
-
-			ip = target->ip;
-			if (!ip)
-				return WHO_CANTSEE;
-
-			if (((wfl.want_ip == WHO_WANT) && !match_simple(wfl.ip, ip)) ||
-			    ((wfl.want_ip == WHO_DONTWANT) && match_simple(wfl.ip, ip)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people connecting on a certain port */
-		if (wfl.want_port != WHO_DONTCARE)
-		{
-			int port;
-			
-			if (!MyUser(target))
-				return WHO_CANTSEE;
-
-			port = target->local->listener->port;
-
-			if (((wfl.want_port == WHO_WANT) && wfl.port != port) ||
-			    ((wfl.want_port == WHO_DONTWANT) && wfl.port == port))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people with a certain nick.. */
-		if (wfl.want_nick != WHO_DONTCARE)
-		{
-			if (((wfl.want_nick == WHO_WANT) && !match_simple(wfl.nick, target->name)) ||
-			    ((wfl.want_nick == WHO_DONTWANT) && match_simple(wfl.nick, target->name)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people with a certain username */
-		if (wfl.want_user != WHO_DONTCARE)
-		{
-			if (((wfl.want_user == WHO_WANT) && !match_simple(wfl.user, target->user->username)) ||
-			    ((wfl.want_user == WHO_DONTWANT) && match_simple(wfl.user, target->user->username)))
-			{
-				return WHO_CANTSEE;
-			}
-		}
-
-		/* if they only want people with a certain umode */
-		if (wfl.umodes_want)
-		{
-			if (!(target->umodes & wfl.umodes_want) || (!IsOper(requester) && (target->umodes & UMODE_HIDEOPER)))
-				return WHO_CANTSEE;
-		}
-
-		if (wfl.umodes_dontwant)
-		{
-			if ((target->umodes & wfl.umodes_dontwant) && (!(target->umodes & UMODE_HIDEOPER) || IsOper(requester)))
-				return WHO_CANTSEE;
-		}
-
-		/* if they only want common channels */
-		if (wfl.common_channels_only)
-		{
-			if (!has_common_channels(requester, target))
-				return WHO_CANTSEE;
-			has_common_chan = 1;
-		}
-
-		if (channel)
-		{
-			int member = who_flags & WF_ONCHANNEL;
-
-			if (SecretChannel(channel) || HiddenChannel(channel))
-			{
-				/* if they aren't on it.. they can't see it */
-				if (!(who_flags & WF_ONCHANNEL))
-					break;
-			}
-			if (IsInvisible(target) && !member)
-				break;
-
-			if (!user_can_see_member(requester, target, channel))
-				break; /* invisible (eg: due to delayjoin) */
-		}
-		else
-		{
-			/* a user/mask who */
-
-			/* If the common channel info hasn't been set, set it now */
-			if (!wfl.common_channels_only)
-				has_common_chan = has_common_channels(requester, target);
-
-			if (IsInvisible(target) && !has_common_chan)
-			{
-				/* don't show them unless it's an exact match 
-				   or it is the user requesting the /who */
-				if ((who_flags & WF_WILDCARD) && requester != target)
-					break;
-			}
-		}
-
-		/* phew.. show them. */
-		return WHO_CANSEE;
-	} while (0);
-
-	/* if we get here, it's oper-dependant. */
-	if (IsOper(requester))
-		return ret | WHO_OPERSEE | WHO_CANSEE;
-	else
-	{
-		if (requester == target)
-			return ret | WHO_CANSEE;
-		else
-			return ret | WHO_CANTSEE;
-	}
-}
-
-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))
-		who_flags |= WF_ONCHANNEL;
-
-	for (cm = channel->members; cm; cm = cm->next)
-	{
-		Client *acptr = cm->client;
-		char status[32];
-		int cansee;
-		if ((cansee = can_see(client, acptr, channel)) & WHO_CANTSEE)
-			continue;
-
-		make_who_status(client, acptr, channel, cm, status, cansee);
-		send_who_reply(client, acptr, channel->name, status, "");
-    }
-}
-
-static void make_who_status(Client *client, Client *acptr, Channel *channel, 
-			    Member *cm, char *status, int cansee)
-{
-	int i = 0;
-	Hook *h;
-
-	if (acptr->user->away)
-		status[i++] = 'G';
-	else
-		status[i++] = 'H';
-
-	if (IsRegNick(acptr))
-		status[i++] = 'r';
-
-	if (IsSecureConnect(acptr))
-		status[i++] = 's';
-
-	for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
-	{
-		int ret = (*(h->func.intfunc))(client, acptr, channel, cm, status, cansee);
-		if (ret != 0)
-			status[i++] = (char)ret;
-	}
-	
-	if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
-		status[i++] = '*';
-
-	if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client)))
-		status[i++] = '!';
-  
-	if (cansee & WHO_OPERSEE)
-		status[i++] = '?';
-
-	if (cm)
-	{
-		if (HasCapability(client, "multi-prefix"))
-		{
-			/* 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, const char *mask)
-{
-int oper = IsOper(client);
-
-	if (strchr(mask, '*') || strchr(mask, '?'))
-	{
-		int i = 0;
-		/* go through all users.. */
-		Client *acptr;
-		who_flags |= WF_WILDCARD;
-
-		list_for_each_entry(acptr, &client_list, client_node)
-		{
-		int cansee;
-		char status[20];
-		const char *channel;
-		int flg;
-
-			if (!IsUser(acptr))
-				continue;
-			if (!oper) {
-				/* non-opers can only search on nick here */
-				if (!match_simple(mask, acptr->name))
-					continue;
-			} else {
-				/* opers can search on name, ident, virthost, ip and realhost.
-				 * Yes, I like readable if's -- Syzop.
-				 */
-				if (match_simple(mask, acptr->name) || match_simple(mask, acptr->user->realhost) ||
-				    match_simple(mask, acptr->user->username))
-					goto matchok;
-				if (IsHidden(acptr) && match_simple(mask, acptr->user->virthost))
-					goto matchok;
-				if (acptr->ip && match_simple(mask, acptr->ip))
-					goto matchok;
-				/* nothing matched... */
-				continue;
-			}
-matchok:
-			if ((cansee = can_see(client, acptr, NULL)) & WHO_CANTSEE)
-				continue;
-			if (WHOLIMIT && !IsOper(client) && ++i > WHOLIMIT)
-			{
-				sendnumeric(client, ERR_WHOLIMEXCEED, WHOLIMIT);
-				return;
-			}
-
-			channel = first_visible_channel(client, acptr, &flg);
-			make_who_status(client, acptr, NULL, NULL, status, cansee);
-			send_who_reply(client, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : "");
-		}
-	}
-	else
-	{
-		/* just a single client (no wildcards detected) */
-		Client *acptr = find_client(mask, NULL);
-		int cansee;
-		char status[20];
-		const char *channel;
-		int flg;
-
-		if (!acptr)
-			return;
-
-		if ((cansee = can_see(client, acptr, NULL)) == WHO_CANTSEE)
-			return;
-
-		channel = first_visible_channel(client, acptr, &flg);
-		make_who_status(client, acptr, NULL, NULL, status, cansee);
-		send_who_reply(client, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : "");
-	}
-}
-
-static void send_who_reply(Client *client, Client *acptr, 
-			   const char *channel, const char *status, const char *xstat)
-{
-	char *stat;
-	const char *host;
-	int flat = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
-
-	stat = safe_alloc(strlen(status) + strlen(xstat) + 1);
-	sprintf(stat, "%s%s", status, xstat);
-
-	if (IsOper(client))
-	{
-		if (who_flags & WF_REALHOST)
-			host = acptr->user->realhost;
-		else if (who_flags & WF_IP)
-			host = (acptr->ip ? acptr->ip : acptr->user->realhost);
-		else
-			host = GetHost(acptr);
-	}
-	else
-		host = GetHost(acptr);
-					
-
-	if (IsULine(acptr) && !IsOper(client) && !ValidatePermissionsForPath("server:info:map:ulines",client,acptr,NULL,NULL) && HIDE_ULINES)
-	{
-	        sendnumeric(client, RPL_WHOREPLY,
-        	     channel,       /* channel name */
-	             acptr->user->username, /* user name */
-        	     host,		    /* hostname */
-	             "hidden",              /* let's hide the server from normal users if the server is a uline and HIDE_ULINES is on */
-        	     acptr->name,           /* nick */
-	             stat,                  /* status */
-        	     0,                     /* hops (hidden) */
-	             acptr->info            /* realname */
-             	);
-
-	} else {
-		sendnumeric(client, RPL_WHOREPLY,
-		     channel,       /* channel name */
-		     acptr->user->username,      /* user name */
-		     host,		         /* hostname */
-		     acptr->user->server,        /* server name */
-		     acptr->name,                /* nick */
-		     stat,                       /* status */
-		     flat ? 0 : acptr->hopcount, /* hops */ 
-		     acptr->info                 /* realname */
-		     );
-	}
-	safe_free(stat);
-}
-
-static const char *first_visible_channel(Client *client, Client *acptr, int *flg)
-{
-	Membership *lp;
-
-	*flg = 0;
-
-	for (lp = acptr->user->channel; lp; lp = lp->next)
-	{
-		Channel *channel = lp->channel;
-		Hook *h;
-		int ret = EX_ALLOW;
-		int operoverride = 0;
-		int showchannel = 0;
-		
-		/* Note that the code below is almost identical to the one in /WHOIS */
-
-		if (ShowChannel(client, channel))
-			showchannel = 1;
-
-		for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next)
-		{
-			int n = (*(h->func.intfunc))(client, acptr, channel);
-			/* Hook return values:
-			 * EX_ALLOW means 'yes is ok, as far as modules are concerned'
-			 * EX_DENY means 'hide this channel, unless oper overriding'
-			 * EX_ALWAYS_DENY means 'hide this channel, always'
-			 * ... with the exception that we always show the channel if you /WHOIS yourself
-			 */
-			if (n == EX_DENY)
-			{
-				ret = EX_DENY;
-			}
-			else if (n == EX_ALWAYS_DENY)
-			{
-				ret = EX_ALWAYS_DENY;
-				break;
-			}
-		}
-		
-		if (ret == EX_DENY)
-			showchannel = 0;
-		
-		if (!showchannel && (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,channel,NULL) || ValidatePermissionsForPath("channel:see:whois",client,NULL,channel,NULL)))
-		{
-			showchannel = 1; /* OperOverride */
-			operoverride = 1;
-		}
-		
-		if ((ret == EX_ALWAYS_DENY) && (acptr != client))
-			continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */
-
-		if (acptr == client)
-			showchannel = 1;
-
-		if (operoverride)
-			*flg |= FVC_HIDDEN;
-
-		if (showchannel)
-			return channel->name;
-	}
-
-	/* no channels that they can see */
-	return "*";
-}
diff --git a/src/modules/whois.c b/src/modules/whois.c
@@ -1,663 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/modules/whois.c
- *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
- *   (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
- *   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"
-
-/* Structs */
-ModuleHeader MOD_HEADER
-  = {
-	"whois",	/* Name of module */
-	"5.0", /* Version */
-	"command /whois", /* Short description of module */
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-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 */
-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()
-{
-	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;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-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;
-}
-
-/* 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("security-groups", 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;
-	char *p = NULL;
-	int len, mlen;
-	char querybuf[BUFSIZE];
-	char buf[BUFSIZE];
-	int ntargets = 0;
-	int maxtargets = max_targets_for_command("WHOIS");
-
-	if (parc < 2)
-	{
-		sendnumeric(client, ERR_NONICKNAMEGIVEN);
-		return;
-	}
-
-	if (parc > 2)
-	{
-		if (hunt_server(client, recv_mtags, "WHOIS", 1, parc, parv) != HUNTED_ISME)
-			return;
-		parv[1] = parv[2];
-	}
-
-	strlcpy(querybuf, parv[1], sizeof(querybuf));
-
-	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))
-		{
-			sendnumeric(client, ERR_TOOMANYTARGETS, nick, maxtargets, "WHOIS");
-			break;
-		}
-
-		/* We do not support "WHOIS *" */
-		wilds = (strchr(nick, '?') || strchr(nick, '*'));
-		if (wilds)
-			continue;
-
-		target = find_user(nick, NULL);
-		if (!target)
-		{
-			sendnumeric(client, ERR_NOSUCHNICK, nick);
-			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.
-		 */
-
-		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);
-		}
-
-		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 (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);
-		}
-
-		/* 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;
-				int ret = EX_ALLOW;
-				int operoverride = 0;
-				
-				channel = lp->channel;
-				showchannel = 0;
-
-				if (ShowChannel(client, channel))
-					showchannel = 1;
-
-				for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next)
-				{
-					int n = (*(h->func.intfunc))(client, target, channel);
-					/* Hook return values:
-					 * EX_ALLOW means 'yes is ok, as far as modules are concerned'
-					 * EX_DENY means 'hide this channel, unless oper overriding'
-					 * EX_ALWAYS_DENY means 'hide this channel, always'
-					 * ... with the exception that we always show the channel if you /WHOIS yourself
-					 */
-					if (n == EX_DENY)
-					{
-						ret = EX_DENY;
-					}
-					else if (n == EX_ALWAYS_DENY)
-					{
-						ret = EX_ALWAYS_DENY;
-						break;
-					}
-				}
-				
-				if (ret == EX_DENY)
-					showchannel = 0;
-				
-				/* 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;
-				}
-				
-				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). */
-
-				/* 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)
-				{
-					if (len + strlen(channel->name) > (size_t)BUFSIZE - 4 - mlen)
-					{
-						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;
-					}
-
-					if (operoverride)
-					{
-						/* '?' and '!' both mean we can see the channel in /WHOIS and normally wouldn't,
-						 * but there's still a slight difference between the two...
-						 */
-						if (!PubChannel(channel))
-						{
-							/* '?' means it's a secret/private channel (too) */
-							*(buf + len++) = '?';
-						}
-						else
-						{
-							/* public channel but hidden in WHOIS (umode +p, service bot, etc) */
-							*(buf + len++) = '!';
-						}
-					}
-
-					if (!MyUser(client) || !HasCapability(client, "multi-prefix"))
-					{
-						/* Standard NAMES reply (single character) */
-						char c = mode_to_prefix(*lp->member_modes);
-						if (c)
-							*(buf + len++) = c;
-					}
-					else
-					{
-						/* 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->name);
-					len += strlen(channel->name);
-					strcat(buf + len, " ");
-					len++;
-				}
-			}
-
-			if (buf[0] != '\0')
-			{
-				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) &&
-		    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 && (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)
-		{
-			policy = whois_get_policy(client, target, "oper");
-			if (policy == WHOIS_CONFIG_DETAILS_FULL)
-			{
-				const char *operlogin = get_operlogin(target);
-				const char *operclass = get_operclass(target);
-
-				if (operlogin && operclass)
-				{
-					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)
-		{
-			policy = whois_get_policy(client, target, "secure");
-			if (policy == WHOIS_CONFIG_DETAILS_LIMITED)
-			{
-				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");
-				}
-			}
-		}
-
-		/* The following code deals with security-groups */
-		policy = whois_get_policy(client, target, "security-groups");
-		if ((policy > WHOIS_CONFIG_DETAILS_NONE) && !IsULine(target))
-		{
-			SecurityGroup *s;
-			int security_groups_whois_lines = 0;
-
-			mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name) + strlen("is in security-groups: ");
-
-			if (user_allowed_by_security_group_name(target, "known-users"))
-				strlcpy(buf, "known-users,", sizeof(buf));
-			else
-				strlcpy(buf, "unknown-users,", sizeof(buf));
-			len = strlen(buf);
-
-			for (s = securitygroups; s; s = s->next)
-			{
-				if (len + strlen(s->name) > (size_t)BUFSIZE - 4 - mlen)
-				{
-					buf[len-1] = '\0';
-					add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups",
-					                        target, RPL_WHOISSPECIAL,
-								"%s :is in security-groups: %s", target->name, buf);
-					security_groups_whois_lines++;
-					*buf = '\0';
-					len = 0;
-				}
-				if (strcmp(s->name, "known-users") && user_allowed_by_security_group(target, s))
-				{
-					strcpy(buf + len, s->name);
-					len += strlen(buf+len);
-					strcpy(buf + len, ",");
-					len++;
-				}
-			}
-
-			if (*buf)
-			{
-				buf[len-1] = '\0';
-				add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups",
-				                        client, RPL_WHOISSPECIAL,
-							"%s :is in security-groups: %s", target->name, buf);
-				security_groups_whois_lines++;
-			}
-		}
-		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 && (whois_get_policy(client, target, "swhois") > WHOIS_CONFIG_DETAILS_NONE))
-		{
-			SWhois *s;
-			int swhois_lines = 0;
-
-			for (s = target->user->swhois; s; s = s->next)
-			{
-				if (hideoper && !IsOper(client) && s->setby && !strcmp(s->setby, "oper"))
-					continue; /* hide oper-based swhois entries */
-				add_nvplist_numeric(&list, 100000+swhois_lines, "swhois", client, RPL_WHOISSPECIAL,
-				                    target->name, s->line);
-				swhois_lines++;
-			}
-		}
-
-		/* 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);
-		}
-
-		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 ((policy == WHOIS_CONFIG_DETAILS_FULL) ||
-			    ((policy == WHOIS_CONFIG_DETAILS_LIMITED) && !hide_idle_time(client, target)))
-			{
-				add_nvplist_numeric(&list, 500000, "idle", client, RPL_WHOISIDLE,
-				                    target->name,
-				                    (long long)(TStime() - target->local->idle_since),
-				                    (long long)target->local->creationtime);
-			}
-		}
-
-		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
@@ -1,133 +0,0 @@
-/*
- *   IRC - Internet Relay Chat, src/modules/out.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_whowas);
-
-#define MSG_WHOWAS 	"WHOWAS"	
-
-ModuleHeader MOD_HEADER
-  = {
-	"whowas",
-	"5.0",
-	"command /whowas", 
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-MOD_INIT()
-{
-	CommandAdd(modinfo->handle, MSG_WHOWAS, cmd_whowas, MAXPARA, CMD_USER);
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/* externally defined functions */
-extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
-extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
-
-/*
-** cmd_whowas
-**      parv[1] = nickname queried
-*/
-CMD_FUNC(cmd_whowas)
-{
-	char request[BUFSIZE];
-	WhoWas *temp;
-	int  cur = 0;
-	int  max = -1, found = 0;
-	char *p, *nick;
-
-	if (parc < 2)
-	{
-		sendnumeric(client, ERR_NONICKNAMEGIVEN);
-		return;
-	}
-
-	if (parc > 2)
-		max = atoi(parv[2]);
-
-	if (parc > 3)
-	{
-		if (hunt_server(client, recv_mtags, "WHOWAS", 3, parc, parv))
-			return; /* Not for us */
-	}
-
-	if (!MyConnect(client) && (max > 20))
-		max = 20;
-
-	strlcpy(request, parv[1], sizeof(request));
-	p = strchr(request, ',');
-	if (p)
-		*p = '\0'; /* cut off at first */
-
-	nick = request;
-	temp = WHOWASHASH[hash_whowas_name(nick)];
-	found = 0;
-	for (; temp; temp = temp->next)
-	{
-		if (!mycmp(nick, temp->name))
-		{
-			sendnumeric(client, RPL_WHOWASUSER, temp->name,
-			    temp->username,
-			    BadPtr(temp->virthost) ? temp->hostname : temp->virthost,
-			    temp->realname);
-			if (!BadPtr(temp->ip) && ValidatePermissionsForPath("client:see:ip",client,NULL,NULL,NULL))
-			{
-				sendnumericfmt(client, RPL_WHOISHOST, "%s :was connecting from %s@%s %s",
-					temp->name,
-					temp->username, temp->hostname,
-					temp->ip ? temp->ip : "");
-			}
-			if (IsOper(client) && !BadPtr(temp->account))
-			{
-				sendnumericfmt(client, RPL_WHOISLOGGEDIN, "%s %s :was logged in as",
-					temp->name,
-					temp->account);
-			}
-			if (!((find_uline(temp->servername)) && !IsOper(client) && HIDE_ULINES))
-			{
-				sendnumeric(client, RPL_WHOISSERVER, temp->name, temp->servername,
-				            myctime(temp->logoff));
-			}
-			cur++;
-			found++;
-		}
-		if (max > 0 && cur >= max)
-			break;
-	}
-	if (!found)
-		sendnumeric(client, ERR_WASNOSUCHNICK, nick);
-
-	sendnumeric(client, RPL_ENDOFWHOWAS, request);
-}
diff --git a/src/modules/whowasdb.c b/src/modules/whowasdb.c
@@ -1,641 +0,0 @@
-/*
- * Stores WHOWAS history in a .db file
- * (C) Copyright 2023 Syzop
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-ModuleHeader MOD_HEADER = {
-	"whowasdb",
-	"1.0",
-	"Stores and retrieves WHOWAS history",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-};
-
-/* Our header */
-#define WHOWASDB_HEADER		0x57484F57
-/* Database version */
-#define WHOWASDB_VERSION 100
-/* Save whowas of users to file every <this> seconds */
-#define WHOWASDB_SAVE_EVERY 300
-/* The very first save after boot, apply this delta, this
- * so we don't coincide with other (potentially) expensive
- * I/O events like saving tkldb.
- */
-#define WHOWASDB_SAVE_EVERY_DELTA -60
-
-#define MAGIC_WHOWASDB_START	0x11111111
-#define MAGIC_WHOWASDB_END		0x22222222
-
-// #undef BENCHMARK
-
-/* Yeah, W_SAFE_PROPERTY raises "the address .. will always evaluate to true" warnings,
- * disabling it in the entire file for now...
- */
-#if defined(__GNUC__)
-#pragma GCC diagnostic ignored "-Waddress"
-#endif
-
-#define WARN_WRITE_ERROR(fname) \
-	do { \
-		unreal_log(ULOG_ERROR, "whowasdb", "WHOWASDB_FILE_WRITE_ERROR", NULL, \
-			   "[whowasdb] Error writing to temporary database file $filename: $system_error", \
-			   log_data_string("filename", fname), \
-			   log_data_string("system_error", unrealdb_get_error_string())); \
-	} while(0)
-
-#define W_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			WARN_WRITE_ERROR(tmpfname); \
-			unrealdb_close(db); \
-			return 0; \
-		} \
-	} while(0)
-
-#define W_SAFE_PROPERTY(db, x, y) \
-	do { \
-		if (x && y && (!unrealdb_write_str(db, x) || !unrealdb_write_str(db, y))) \
-		{ \
-			WARN_WRITE_ERROR(tmpfname); \
-			unrealdb_close(db); \
-			return 0; \
-		} \
-	} while(0)
-
-#define IsMDErr(x, y, z) \
-	do { \
-		if (!(x)) { \
-			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
-			return MOD_FAILED; \
-		} \
-	} while(0)
-
-/* Structs */
-struct cfgstruct {
-	char *database;
-	char *db_secret;
-};
-
-/* Forward declarations */
-void whowasdb_moddata_free(ModData *md);
-void setcfg(struct cfgstruct *cfg);
-void freecfg(struct cfgstruct *cfg);
-int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int whowasdb_config_posttest(int *errs);
-int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
-EVENT(write_whowasdb_evt);
-int write_whowasdb(void);
-int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e);
-int read_whowasdb(void);
-
-/* External variables */
-extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
-extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
-extern MODVAR int whowas_next;
-
-/* Global variables */
-static uint32_t whowasdb_version = WHOWASDB_VERSION;
-static struct cfgstruct cfg;
-static struct cfgstruct test;
-
-static long whowasdb_next_event = 0;
-
-MOD_TEST()
-{
-	memset(&cfg, 0, sizeof(cfg));
-	memset(&test, 0, sizeof(test));
-	setcfg(&test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whowasdb_config_test);
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, whowasdb_config_posttest);
-	return MOD_SUCCESS;
-}
-
-MOD_INIT()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	LoadPersistentLong(modinfo, whowasdb_next_event);
-
-	setcfg(&cfg);
-
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whowasdb_config_run);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	if (!whowasdb_next_event)
-	{
-		/* If this is the first time that our module is loaded, then read the database. */
-		if (!read_whowasdb())
-		{
-			char fname[512];
-			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
-			if (rename(cfg.database, fname) == 0)
-				config_warn("[whowasdb] Existing database renamed to %s and starting a new one...", fname);
-			else
-				config_warn("[whowasdb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
-		}
-		whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY + WHOWASDB_SAVE_EVERY_DELTA;
-	}
-	EventAdd(modinfo->handle, "whowasdb_write_whowasdb", write_whowasdb_evt, NULL, 1000, 0);
-	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
-	{
-		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
-		return MOD_FAILED;
-	}
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	if (loop.terminating)
-		write_whowasdb();
-	freecfg(&test);
-	freecfg(&cfg);
-	SavePersistentLong(modinfo, whowasdb_next_event);
-	return MOD_SUCCESS;
-}
-
-void whowasdb_moddata_free(ModData *md)
-{
-	if (md->i)
-		md->i = 0;
-}
-
-void setcfg(struct cfgstruct *cfg)
-{
-	// Default: data/whowas.db
-	safe_strdup(cfg->database, "whowas.db");
-	convert_to_absolute_path(&cfg->database, PERMDATADIR);
-}
-
-void freecfg(struct cfgstruct *cfg)
-{
-	safe_free(cfg->database);
-	safe_free(cfg->db_secret);
-}
-
-int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	// We are only interested in set::whowasdb::database
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "whowasdb"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!cep->value)
-		{
-			config_error("%s:%i: blank set::whowasdb::%s without value", cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		} else
-		if (!strcmp(cep->name, "database"))
-		{
-			convert_to_absolute_path(&cep->value, PERMDATADIR);
-			safe_strdup(test.database, cep->value);
-		} else
-		if (!strcmp(cep->name, "db-secret"))
-		{
-			const char *err;
-			if ((err = unrealdb_test_secret(cep->value)))
-			{
-				config_error("%s:%i: set::whowasdb::db-secret: %s", cep->file->filename, cep->line_number, err);
-				errors++;
-				continue;
-			}
-			safe_strdup(test.db_secret, cep->value);
-		} else
-		{
-			config_error("%s:%i: unknown directive set::whowasdb::%s", cep->file->filename, cep->line_number, cep->name);
-			errors++;
-		}
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int whowasdb_config_posttest(int *errs)
-{
-	int errors = 0;
-	char *errstr;
-
-	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
-	{
-		config_error("[whowasdb] %s", errstr);
-		errors++;
-	}
-
-	*errs = errors;
-	return errors ? -1 : 1;
-}
-
-int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
-	ConfigEntry *cep;
-
-	// We are only interested in set::whowasdb::database
-	if (type != CONFIG_SET)
-		return 0;
-
-	if (!ce || strcmp(ce->name, "whowasdb"))
-		return 0;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "database"))
-			safe_strdup(cfg.database, cep->value);
-		else if (!strcmp(cep->name, "db-secret"))
-			safe_strdup(cfg.db_secret, cep->value);
-	}
-	return 1;
-}
-
-EVENT(write_whowasdb_evt)
-{
-	if (whowasdb_next_event > TStime())
-		return;
-	whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY;
-	write_whowasdb();
-}
-
-int count_whowas_and_user_entries(void)
-{
-	int i;
-	int cnt = 0;
-	Client *client;
-
-	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
-	{
-		WhoWas *e = &WHOWAS[i];
-		if (e->name)
-			cnt++;
-	}
-
-	list_for_each_entry(client, &client_list, client_node)
-		if (IsUser(client))
-			cnt++;
-
-	return cnt;
-}
-
-int write_whowasdb(void)
-{
-	char tmpfname[512];
-	UnrealDB *db;
-	WhoWas *e;
-	Client *client;
-	int cnt, i;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	// Write to a tempfile first, then rename it if everything succeeded
-	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
-	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
-	if (!db)
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-	W_SAFE(unrealdb_write_int32(db, WHOWASDB_HEADER));
-	W_SAFE(unrealdb_write_int32(db, whowasdb_version));
-
-	cnt = count_whowas_and_user_entries();
-	W_SAFE(unrealdb_write_int64(db, cnt));
-
-	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
-	{
-		WhoWas *e = &WHOWAS[i];
-		if (e->name)
-		{
-			if (!write_whowas_entry(db, tmpfname, e))
-				return 0;
-		}
-	}
-
-	/* Add all the currently connected users to WHOWAS history (as if they left just now) */
-	list_for_each_entry(client, &client_list, client_node)
-	{
-		if (IsUser(client))
-		{
-			WhoWas *e = safe_alloc(sizeof(WhoWas));
-			int ret;
-
-			create_whowas_entry(client, e, WHOWAS_EVENT_SERVER_TERMINATING);
-			ret = write_whowas_entry(db, tmpfname, e);
-			free_whowas_fields(e);
-			safe_free(e);
-
-			if (ret == 0)
-				return 0;
-		}
-	}
-
-
-	// Everything seems to have gone well, attempt to close and rename the tempfile
-	if (!unrealdb_close(db))
-	{
-		WARN_WRITE_ERROR(tmpfname);
-		return 0;
-	}
-
-#ifdef _WIN32
-	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
-	unlink(cfg.database);
-#endif
-	if (rename(tmpfname, cfg.database) < 0)
-	{
-		config_error("[whowasdb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
-		return 0;
-	}
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	config_status("[whowasdb] Benchmark: SAVE DB: %ld microseconds",
-		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
-#endif
-	return 1;
-}
-
-int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e)
-{
-	char connected_since[64];
-	char logontime[64];
-	char logofftime[64];
-	char event[16];
-
-	snprintf(connected_since, sizeof(connected_since), "%lld", (long long)e->connected_since);
-	snprintf(logontime, sizeof(logontime), "%lld", (long long)e->logon);
-	snprintf(logofftime, sizeof(logofftime), "%lld", (long long)e->logoff);
-	snprintf(event, sizeof(event), "%d", e->event);
-
-	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_START));
-	W_SAFE_PROPERTY(db, "nick", e->name);
-	W_SAFE_PROPERTY(db, "event", event);
-	W_SAFE_PROPERTY(db, "connected_since", connected_since);
-	W_SAFE_PROPERTY(db, "logontime", logontime);
-	W_SAFE_PROPERTY(db, "logofftime", logofftime);
-	W_SAFE_PROPERTY(db, "username", e->username);
-	W_SAFE_PROPERTY(db, "hostname", e->hostname);
-	W_SAFE_PROPERTY(db, "ip", e->ip);
-	W_SAFE_PROPERTY(db, "realname", e->realname);
-	W_SAFE_PROPERTY(db, "server", e->servername);
-	W_SAFE_PROPERTY(db, "virthost", e->virthost);
-	W_SAFE_PROPERTY(db, "account", e->account);
-	W_SAFE_PROPERTY(db, "end", "");
-	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_END));
-	return 1;
-}
-
-#define FreeWhowasEntry() \
- 	do { \
-		/* Some of these might be NULL */ \
-		safe_free(key); \
-		safe_free(value); \
-		safe_free(nick); \
-		safe_free(username); \
-		safe_free(hostname); \
-		safe_free(ip); \
-		safe_free(realname); \
-		connected_since = logontime = logofftime = 0; \
-		event = 0; \
-		safe_free(server); \
-		safe_free(virthost); \
-		safe_free(account); \
-	} while(0)
-
-#define R_SAFE(x) \
-	do { \
-		if (!(x)) { \
-			config_warn("[whowasdb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
-			unrealdb_close(db); \
-			FreeWhowasEntry(); \
-			return 0; \
-		} \
-	} while(0)
-
-int read_whowasdb(void)
-{
-	UnrealDB *db;
-	uint32_t version;
-	int added = 0;
-	int i;
-	uint64_t count = 0;
-	uint32_t magic;
-	char *key = NULL;
-	char *value = NULL;
-	char *nick = NULL;
-	char *username = NULL;
-	char *hostname = NULL;
-	char *ip = NULL;
-	char *realname = NULL;
-	long long connected_since = 0;
-	long long logontime = 0;
-	long long logofftime = 0;
-	int event = 0;
-	char *server = NULL;
-	char *virthost = NULL;
-	char *account = NULL;
-#ifdef BENCHMARK
-	struct timeval tv_alpha, tv_beta;
-
-	gettimeofday(&tv_alpha, NULL);
-#endif
-
-	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
-	if (!db)
-	{
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
-		{
-			/* Database does not exist. Could be first boot */
-			config_warn("[whowasdb] No database present at '%s', will start a new one", cfg.database);
-			return 1;
-		} else
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
-		{
-			/* Re-open as unencrypted */
-			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
-			if (!db)
-			{
-				/* This should actually never happen, unless some weird I/O error */
-				config_warn("[whowasdb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
-				return 0;
-			}
-		} else
-		{
-			config_warn("[whowasdb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
-			return 0;
-		}
-	}
-
-	R_SAFE(unrealdb_read_int32(db, &version));
-	if (version != WHOWASDB_HEADER)
-	{
-		config_warn("[whowasdb] Database '%s' is not a whowas db (incorrect header)", cfg.database);
-		unrealdb_close(db);
-		return 0;
-	}
-	R_SAFE(unrealdb_read_int32(db, &version));
-	if (version > whowasdb_version)
-	{
-		config_warn("[whowasdb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, whowasdb_version, version);
-		unrealdb_close(db);
-		return 0;
-	}
-
-	R_SAFE(unrealdb_read_int64(db, &count));
-
-	for (i=1; i <= count; i++)
-	{
-		// Variables
-		key = value = NULL;
-		nick = username = hostname = ip = realname = virthost = account = server = NULL;
-		connected_since = logontime = logofftime = 0;
-		event = 0;
-
-		R_SAFE(unrealdb_read_int32(db, &magic));
-		if (magic != MAGIC_WHOWASDB_START)
-		{
-			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic start is 0x%x. Further reading aborted.", cfg.database, magic);
-			break;
-		}
-		while(1)
-		{
-			R_SAFE(unrealdb_read_str(db, &key));
-			R_SAFE(unrealdb_read_str(db, &value));
-			if (!strcmp(key, "nick"))
-			{
-				safe_strdup(nick, value);
-			} else
-			if (!strcmp(key, "username"))
-			{
-				safe_strdup(username, value);
-			} else
-			if (!strcmp(key, "hostname"))
-			{
-				safe_strdup(hostname, value);
-			} else
-			if (!strcmp(key, "ip"))
-			{
-				safe_strdup(ip, value);
-			} else
-			if (!strcmp(key, "realname"))
-			{
-				safe_strdup(realname, value);
-			} else
-			if (!strcmp(key, "connected_since"))
-			{
-				connected_since = atoll(value);
-				safe_free(value);
-			} else
-			if (!strcmp(key, "logontime"))
-			{
-				logontime = atoll(value);
-				safe_free(value);
-			} else
-			if (!strcmp(key, "logofftime"))
-			{
-				logofftime = atoll(value);
-				safe_free(value);
-			} else
-			if (!strcmp(key, "event"))
-			{
-				event = atoi(value);
-				if ((event < WHOWAS_LOWEST_EVENT) || (event > WHOWAS_HIGHEST_EVENT))
-					event = WHOWAS_EVENT_QUIT; /* safety */
-				safe_free(value);
-			} else
-			if (!strcmp(key, "server"))
-			{
-				safe_strdup(server, value);
-			} else
-			if (!strcmp(key, "virthost"))
-			{
-				safe_strdup(virthost, value);
-			} else
-			if (!strcmp(key, "account"))
-			{
-				safe_strdup(account, value);
-			} else
-			if (!strcmp(key, "end"))
-			{
-				safe_free(key);
-				safe_free(value);
-				break; /* DONE! */
-			}
-			safe_free(key);
-			safe_free(value);
-		}
-		R_SAFE(unrealdb_read_int32(db, &magic));
-		if (magic != MAGIC_WHOWASDB_END)
-		{
-			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic end is 0x%x. Further reading aborted.", cfg.database, magic);
-			FreeWhowasEntry();
-			break;
-		}
-
-		if (nick && username && hostname && realname)
-		{
-			WhoWas *e = &WHOWAS[whowas_next];
-			if (e->hashv != -1)
-				free_whowas_fields(e);
-			/* Set values */
-			//unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_READ_RECORD", NULL,
-			//           "[whowasdb] Adding '$nick'...",
-			//           log_data_string("nick", nick));
-			e->hashv = hash_whowas_name(nick);
-			e->event = event;
-			e->connected_since = connected_since;
-			e->logon = logontime;
-			e->logoff = logofftime;
-			safe_strdup(e->name, nick);
-			safe_strdup(e->username, username);
-			safe_strdup(e->hostname, hostname);
-			safe_strdup(e->ip, ip);
-			if (virthost)
-				safe_strdup(e->virthost, virthost);
-			else
-				safe_strdup(e->virthost, "");
-			e->servername = find_or_add(server); /* scache */
-			safe_strdup(e->realname, realname);
-			safe_strdup(e->account, account);
-			e->online = NULL;
-			/* Server is special - scache shit */
-			/* Add to hash table */
-			add_whowas_to_list(&WHOWASHASH[e->hashv], e);
-			/* And advance pointer (well, integer) */
-			whowas_next++;
-			if (whowas_next == NICKNAMEHISTORYLENGTH)
-				whowas_next = 0;
-		}
-
-		FreeWhowasEntry();
-		added++;
-	}
-
-	unrealdb_close(db);
-
-	if (added)
-		config_status("[whowasdb] Added %d WHOWAS items", added);
-#ifdef BENCHMARK
-	gettimeofday(&tv_beta, NULL);
-	unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_BENCHMARK", NULL,
-	           "[whowasdb] Benchmark: LOAD DB: $time_msec microseconds",
-	           log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
-#endif
-	return 1;
-}
-#undef FreeWhowasEntry
-#undef R_SAFE
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -1,988 +0,0 @@
-/* cmd_whox.c / WHOX.
- * based on code from charybdis and ircu.
- * was originally made for tircd and modified to work with u4.
- * - 2018 i <ircd@servx.org>
- * - 2019 Bram Matthys (Syzop) <syzop@vulnscan.org>
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-/* Module header */
-ModuleHeader MOD_HEADER
-  = {
-	"whox",
-	"5.0",
-	"command /who",
-	"UnrealIRCd Team",
-	"unrealircd-6",
-    };
-
-
-/* Defines */
-#define FIELD_CHANNEL	0x0001
-#define FIELD_HOP	0x0002
-#define FIELD_FLAGS	0x0004
-#define FIELD_HOST	0x0008
-#define FIELD_IP	0x0010
-#define FIELD_IDLE	0x0020
-#define FIELD_NICK	0x0040
-#define FIELD_INFO	0x0080
-#define FIELD_SERVER	0x0100
-#define FIELD_QUERYTYPE	0x0200 /* cookie for client */
-#define FIELD_USER	0x0400
-#define FIELD_ACCOUNT	0x0800
-#define FIELD_OPLEVEL	0x1000 /* meaningless and stupid, but whatever */
-#define FIELD_REALHOST	0x2000
-#define FIELD_MODES	0x4000
-#define FIELD_REPUTATION	0x8000
-
-#define WMATCH_NICK	0x0001
-#define WMATCH_USER	0x0002
-#define WMATCH_OPER	0x0004
-#define WMATCH_HOST	0x0008
-#define WMATCH_INFO	0x0010
-#define WMATCH_SERVER	0x0020
-#define WMATCH_ACCOUNT	0x0040
-#define WMATCH_IP	0x0080
-#define WMATCH_MODES	0x0100
-#define WMATCH_CONTIME	0x0200
-
-#define RPL_WHOSPCRPL	354
-
-#define WHO_ADD 1
-#define WHO_DEL 0
-
-#define HasField(x, y) ((x)->fields & (y))
-#define IsMatch(x, y) ((x)->matchsel & (y))
-
-#define IsMarked(x)           (moddata_client(x, whox_md).l)
-#define SetMark(x)            do { moddata_client(x, whox_md).l = 1; } while(0)
-#define ClearMark(x)          do { moddata_client(x, whox_md).l = 0; } while(0)
-
-/* Structs */
-struct who_format
-{
-	int fields;
-	int matchsel;
-	int umodes;
-	int noumodes;
-	const char *querytype;
-	int show_realhost;
-	int show_ip;
-	time_t contimemin;
-	time_t contimemax;
-};
-
-/* Global variables */
-ModDataInfo *whox_md = NULL;
-
-/* Forward declarations */
-CMD_FUNC(cmd_whox);
-static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt);
-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, 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()
-{
-	ModDataInfo mreq;
-
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	if (!CommandAdd(modinfo->handle, "WHO", cmd_whox, MAXPARA, CMD_USER))
-	{
-		config_warn("You cannot load both cmd_whox and cmd_who. You should ONLY load the cmd_whox module.");
-		return MOD_FAILED;
-	}
-
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "whox";
-	mreq.type = MODDATATYPE_CLIENT;
-	mreq.serialize = whox_md_serialize;
-	mreq.unserialize = whox_md_unserialize;
-	mreq.free = whox_md_free;
-	mreq.sync = 0;
-	whox_md = ModDataAdd(modinfo->handle, mreq);
-	if (!whox_md)
-	{
-		config_error("could not register whox moddata");
-		return MOD_FAILED;
-	}
-
-	ISupportAdd(modinfo->handle, "WHOX", NULL);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
-	return MOD_SUCCESS;
-}
-
-MOD_UNLOAD()
-{
-	return MOD_SUCCESS;
-}
-
-/** whox module data operations: serialize (rare) */
-const char *whox_md_serialize(ModData *m)
-{
-	static char buf[32];
-	if (m->i == 0)
-		return NULL; /* not set */
-	snprintf(buf, sizeof(buf), "%d", m->i);
-	return buf;
-}
-
-/** whox module data operations: unserialize (rare) */
-void whox_md_unserialize(const char *str, ModData *m)
-{
-	m->i = atoi(str);
-}
-
-/** whox module data operations: free */
-void whox_md_free(ModData *md)
-{
-	/* we have nothing to free actually, but we must set to zero */
-	md->l = 0;
-}
-
-/** cmd_whox: standardized "extended" version of WHO.
- * The good thing about WHOX is that it allows the client to define what
- * output they want to see. Another good thing is that it is standardized
- * (ok, actually it isn't... but hey!).
- * The bad thing is that it's a lot less flexible than the original WHO
- * we had in UnrealIRCd in 3.2.x and 4.0.x and that the WHOX spec defines
- * that there are two ways to specify a mask (neither one is logical):
- * WHO <mask> <flags>
- * WHO <mask> <flags> <mask2>
- * In case the latter is present then mask2 overrides the mask
- * (and the original 'mask' is ignored)
- * Yes, this indeed damn ugly. It shows (yet again) that they should never
- * have put the mask before the flags.. what kind of logic was that?
- * I wonder if the person who invented this also writes C functions like:
- * int (char *param)func
- * or types:
- * cp mode,time=--preserve
- * or creates configuration files like:
- * Syzop oper {
- * 5 maxlogins;
- * I mean... really...? -- Syzop
- * So, we will try to abide to the WHOX spec as much as possible but
- * try to warn our users in case they use the 'original' UnrealIRCd
- * WHO syntax which was WHO [+-]<flags> <mask>.
- * Note that we won't catch all cases, but we do our best.
- * When in doubt, we will assume WHOX so to not break the spec
- * (which again.. doesn't exist... but hey..)
- */
-CMD_FUNC(cmd_whox)
-{
-	char *mask;
-	const char *orig_mask;
-	char ch; /* Scratch char register */
-	const char *p; /* Scratch char pointer */
-	int member;
-	int operspy = 0;
-	struct who_format fmt;
-	const char *s;
-	char maskcopy[BUFSIZE];
-	Membership *lp;
-	Client *acptr;
-
-	memset(&fmt, 0, sizeof(fmt));
-
-	if (!MyUser(client))
-		return;
-
-	if ((parc < 2))
-	{
-		sendnumeric(client, ERR_NEEDMOREPARAMS, "WHO");
-		return;
-	}
-
-	if ((parc > 3) && parv[3])
-		orig_mask = parv[3];
-	else
-		orig_mask = parv[1];
-
-	if (!convert_classical_who_request(client, &parc, parv, &orig_mask, &fmt))
-		return;
-
-	/* Evaluate the flags now, we consider the second parameter
-	 * as "matchFlags%fieldsToInclude,querytype" */
-
-	if ((parc > 2) && parv[2] && *parv[2])
-	{
-		p = parv[2];
-		while (((ch = *(p++))) && (ch != '%') && (ch != ','))
-		{
-			switch (ch)
-			{
-				case 'o': fmt.matchsel |= WMATCH_OPER; continue;
-				case 'n': fmt.matchsel |= WMATCH_NICK; continue;
-				case 'u': fmt.matchsel |= WMATCH_USER; continue;
-				case 'h': fmt.matchsel |= WMATCH_HOST; continue;
-				case 'i': fmt.matchsel |= WMATCH_IP; continue;
-				case 'r': fmt.matchsel |= WMATCH_INFO; continue;
-				case 's': fmt.matchsel |= WMATCH_SERVER; continue;
-				case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue;
-				case 'm': fmt.matchsel |= WMATCH_MODES; continue;
-				case 't': fmt.matchsel |= WMATCH_CONTIME; continue;
-				case 'R':
-					if (IsOper(client))
-						fmt.show_realhost = 1;
-					continue;
-				case 'I':
-					if (IsOper(client))
-						fmt.show_ip = 1;
-					continue;
-			}
-		}
-	}
-
-	if ((parc > 2) && (s = strchr(parv[2], '%')) != NULL)
-	{
-		s++;
-		for (; *s != '\0'; s++)
-		{
-			switch (*s)
-			{
-				case 'c': fmt.fields |= FIELD_CHANNEL; break;
-				case 'd': fmt.fields |= FIELD_HOP; break;
-				case 'f': fmt.fields |= FIELD_FLAGS; break;
-				case 'h': fmt.fields |= FIELD_HOST; break;
-				case 'H': fmt.fields |= FIELD_REALHOST; break;
-				case 'i': fmt.fields |= FIELD_IP; break;
-				case 'l': fmt.fields |= FIELD_IDLE; break;
-				case 'n': fmt.fields |= FIELD_NICK; break;
-				case 'r': fmt.fields |= FIELD_INFO; break;
-				case 's': fmt.fields |= FIELD_SERVER; break;
-				case 't': fmt.fields |= FIELD_QUERYTYPE; break;
-				case 'u': fmt.fields |= FIELD_USER; break;
-				case 'a': fmt.fields |= FIELD_ACCOUNT; break;
-				case 'm': fmt.fields |= FIELD_MODES; break;
-				case 'o': fmt.fields |= FIELD_OPLEVEL; break;
-				case 'R': fmt.fields |= FIELD_REPUTATION; break;
-				case ',':
-					s++;
-					fmt.querytype = s;
-					s += strlen(s);
-					s--;
-					break;
-			}
-		}
-		if (BadPtr(fmt.querytype) || (strlen(fmt.querytype) > 3))
-			fmt.querytype = "0";
-	}
-
-	strlcpy(maskcopy, orig_mask, sizeof maskcopy);
-	mask = maskcopy;
-
-	collapse(mask);
-
-	if ((ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) &&
-	     ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL)))
-	{
-		operspy = 1;
-	}
-
-	if (fmt.matchsel & WMATCH_MODES)
-	{
-		char *s = mask;
-		int *umodes;
-		int what = WHO_ADD;
-	
-		while (*s)
-		{
-			Umode *um;
-
-			switch (*s)
-			{
-				case '+':
-					what = WHO_ADD;
-					s++;
-					break;
-				case '-':
-					what = WHO_DEL;
-					s++;
-					break;
-			}
-
-			if (!*s)
-				break;
-
-			if (what == WHO_ADD)
-				umodes = &fmt.umodes;
-			else
-				umodes = &fmt.noumodes;
-
-			for (um = usermodes; um; um = um->next)
-			{
-				if (um->letter == *s)
-				{
-					*umodes |= um->mode;
-					break;
-				}
-			}
-			s++;
-		}
-
-		if (!IsOper(client))
-		{
-			/* these are usermodes regular users may search for. just oper now. */
-			fmt.umodes &= UMODE_OPER;
-			fmt.noumodes &= UMODE_OPER;
-		}
-	}
-
-	/* match connect time */
-	if (fmt.matchsel & WMATCH_CONTIME)
-	{
-		char *s = mask;
-		time_t currenttime = TStime();
-
-		fmt.contimemin = 0;
-		fmt.contimemax = 0;
-
-		switch (*s)
-		{
-			case '<':
-				if (*s++)
-					fmt.contimemin = currenttime - config_checkval(s, CFG_TIME);
-				break;
-			case '>':
-				if (*s++)
-					fmt.contimemax = currenttime - config_checkval(s, CFG_TIME);
-				break;
-		}
-	}
-
-	/* '/who #some_channel' */
-	if (IsChannelName(mask))
-	{
-		Channel *channel = NULL;
-
-		/* List all users on a given channel */
-		if ((channel = find_channel(orig_mask)) != NULL)
-		{
-			if (IsMember(client, channel) || operspy)
-				do_who_on_channel(client, channel, 1, operspy, &fmt);
-			else if (!SecretChannel(channel))
-				do_who_on_channel(client, channel, 0, operspy, &fmt);
-		}
-
-		sendnumeric(client, RPL_ENDOFWHO, mask);
-		return;
-	}
-
-	if (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) ||
-                ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL))
-	{
-		operspy = 1;
-	}
-
-	/* '/who 0' for a global list.  this forces clients to actually
-	 * request a full list.  I presume its because of too many typos
-	 * with "/who" ;) --fl
-	 */
-	if (!strcmp(mask, "0"))
-		who_global(client, NULL, 0, &fmt);
-	else
-		who_global(client, mask, operspy, &fmt);
-
-	sendnumeric(client, RPL_ENDOFWHO, mask);
-}
-
-/* do_match
- * inputs	- pointer to client requesting who
- *		- pointer to client to do who on
- *		- char * mask to match
- *		- format options
- * output	- 1 if match, 0 if no match
- * side effects	- NONE
- */
-static int do_match(Client *client, Client *acptr, char *mask, struct who_format *fmt)
-{
-	if (mask == NULL)
-		return 1;
-
-	/* default */
-	if (fmt->matchsel == 0 && (match_simple(mask, acptr->name) ||
-		match_simple(mask, acptr->user->username) ||
-		match_simple(mask, GetHost(acptr)) ||
-		(IsOper(client) &&
-		(match_simple(mask, acptr->user->realhost) ||
-		(acptr->ip &&
-		match_simple(mask, acptr->ip))))))
-	{
-		return 1;
-	}
-
-	/* match nick */
-	if (IsMatch(fmt, WMATCH_NICK) && match_simple(mask, acptr->name))
-		return 1;
-
-	/* match username */
-	if (IsMatch(fmt, WMATCH_USER) && match_simple(mask, acptr->user->username))
-		return 1;
-
-	/* match server */
-	if (IsMatch(fmt, WMATCH_SERVER) && IsOper(client) && match_simple(mask, acptr->user->server))
-		return 1;
-
-	/* match hostname */
-	if (IsMatch(fmt, WMATCH_HOST) && (match_simple(mask, GetHost(acptr)) ||
-		(IsOper(client) && (match_simple(mask, acptr->user->realhost) ||
-		(acptr->ip && match_simple(mask, acptr->ip))))))
-	{
-		return 1;
-	}
-
-	/* match realname */
-	if (IsMatch(fmt, WMATCH_INFO) && match_simple(mask, acptr->info))
-		return 1;
-
-	/* match ip address */
-	if (IsMatch(fmt, WMATCH_IP) && IsOper(client) && acptr->ip &&
-		match_user(mask, acptr, MATCH_CHECK_IP))
-		return 1;
-
-	/* match account */
-	if (IsMatch(fmt, WMATCH_ACCOUNT) && IsLoggedIn(acptr) && match_simple(mask, acptr->user->account))
-	{
-		return 1;
-	}
-
-	/* match usermodes */
-	if (IsMatch(fmt, WMATCH_MODES) && (fmt->umodes || fmt->noumodes))
-	{
-		long umodes = acptr->umodes;
-		if ((acptr->umodes & UMODE_HIDEOPER) && !IsOper(client))
-			umodes &= ~UMODE_OPER; /* pretend -o if +H */
-		/* Now check 'umodes' (not acptr->umodes),
-		 * If multiple conditions are specified it is an
-		 * AND condition and not an OR.
-		 */
-		if (((umodes & fmt->umodes) == fmt->umodes) &&
-		    ((umodes & fmt->noumodes) == 0))
-		{
-			return 1;
-		}
-	}
-
-	/* match connect time */
-	if (IsMatch(fmt, WMATCH_CONTIME) && MyConnect(acptr) && (fmt->contimemin || fmt->contimemax))
-	{
-		if (fmt->contimemin && (acptr->local->creationtime > fmt->contimemin))
-			return 1;
-
-		if (fmt->contimemax && (acptr->local->creationtime < fmt->contimemax))
-			return 1;
-	}
-
-	return 0;
-}
-
-/* who_common_channel
- * inputs		- pointer to client requesting who
- *			- pointer to channel.
- *			- char * mask to match
- *			- int if oper on a server or not
- *			- pointer to int maxmatches
- *			- format options
- * output		- NONE
- * side effects 	- lists matching clients on specified channel,
- *			  marks matched clients.
- *
- * NOTE: only call this from who_global() due to client marking!
- */
-
-static void who_common_channel(Client *client, Channel *channel,
-	char *mask, int *maxmatches, struct who_format *fmt)
-{
-	Member *cm = channel->members;
-	Client *acptr;
-	Hook *h;
-	int i = 0;
-
-	for (cm = channel->members; cm; cm = cm->next)
-	{
-		acptr = cm->client;
-
-		if (IsMarked(acptr))
-			continue;
-
-		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
-			continue;
-
-		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(acptr,channel);
-			if (i != 0)
-				break;
-		}
-
-		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 (do_match(client, acptr, mask, fmt))
-			{
-				do_who(client, acptr, NULL, fmt);
-				--(*maxmatches);
-			}
-		}
-	}
-}
-
-/*
- * who_global
- *
- * inputs		- pointer to client requesting who
- *			- char * mask to match
- *			- int if oper on a server or not
- *			- int if operspy or not
- *			- format options
- * output		- NONE
- * side effects		- do a global scan of all clients looking for match
- *			  this is slightly expensive on EFnet ...
- *			  marks assumed cleared for all clients initially
- *			  and will be left cleared on return
- */
-
-static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt)
-{
-	Client *hunted = NULL;
-	Client *acptr;
-	int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
-
-	/* If searching for a nick explicitly, then include it later on in the result: */
-	if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0)))
-		hunted = find_user(mask, NULL);
-
-	/* Initialize the markers to zero */
-	list_for_each_entry(acptr, &client_list, client_node)
-		ClearMark(acptr);
-
-	/* First, if not operspy, then list all matching clients on common channels */
-	if (!operspy)
-	{
-		Membership *lp;
-
-		for (lp = client->user->channel; lp; lp = lp->next)
-			who_common_channel(client, lp->channel, mask, &maxmatches, fmt);
-	}
-
-	/* Second, list all matching visible clients. */
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (!IsUser(acptr))
-			continue;
-
-		if (IsInvisible(acptr) && !operspy && (client != acptr) && (acptr != hunted))
-			continue;
-
-		if (IsMarked(acptr))
-			continue;
-
-		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
-			continue;
-
-		if (maxmatches > 0)
-		{
-			if (do_match(client, acptr, mask, fmt))
- 			{
-				do_who(client, acptr, NULL, fmt);
-				--maxmatches;
- 			}
- 		}
-	}
-
-	if (maxmatches <= 0)
-		sendnumeric(client, ERR_TOOMANYMATCHES, "WHO", "output too large, truncated");
-}
-
-/*
- * do_who_on_channel
- *
- * inputs		- pointer to client requesting who
- *			- pointer to channel to do who on
- *			- The "real name" of this channel
- *			- int if client is a server oper or not
- *			- int if client is member or not
- *			- format options
- * output		- NONE
- * side effects		- do a who on given channel
- */
-
-static void do_who_on_channel(Client *client, Channel *channel,
-	int member, int operspy, struct who_format *fmt)
-{
-	Member *cm = channel->members;
-	Hook *h;
-	int i = 0;
-
-	for (cm = channel->members; cm; cm = cm->next)
-	{
-		Client *acptr = cm->client;
-
-		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
-			continue;
-
-		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
-		{
-			i = (*(h->func.intfunc))(acptr,channel);
-			if (i != 0)
-				break;
-		}
-
-		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))
-			do_who(client, acptr, channel, fmt);
-	}
-}
-
-/*
- * append_format
- *
- * inputs		- pointer to buffer
- *			- size of buffer
- *			- pointer to position
- *			- format string
- *			- arguments for format
- * output		- NONE
- * side effects 	- position incremented, possibly beyond size of buffer
- *			  this allows detecting overflow
- */
-
-static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...)
-{
-	size_t max, result;
-	va_list ap;
-
-	max = *pos >= bufsize ? 0 : bufsize - *pos;
-	va_start(ap, fmt);
-	result = vsnprintf(buf + *pos, max, fmt, ap);
-	va_end(ap);
-	*pos += result;
-}
-
-/*
- * show_ip()		- asks if the true IP should be shown when source is
- *			  asking for info about target
- *
- * Inputs		- client who is asking
- *			- acptr who do we want the info on
- * Output		- returns 1 if clear IP can be shown, otherwise 0
- * Side Effects		- none
- */
-
-static int show_ip(Client *client, Client *acptr)
-{
-	if (IsServer(acptr))
-		return 0;
-	else if ((client != NULL) && (MyConnect(client) && !IsOper(client)) && (client == acptr))
-		return 1;
-	else if (IsHidden(acptr) && ((client != NULL) && !IsOper(client)))
-		return 0;
-	else
-		return 1;
-}
-
-/*
- * do_who
- *
- * inputs	- pointer to client requesting who
- *		- pointer to client to do who on
- *		- channel or NULL
- *		- format options
- * output	- NONE
- * side effects - do a who on given person
- */
-
-static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt)
-{
-	char status[20];
-	char str[510 + 1];
-	size_t pos;
-	int hide = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
-	int i = 0;
-	Hook *h;
-
-	if (acptr->user->away)
- 		status[i++] = 'G';
- 	else
- 		status[i++] = 'H';
-
-	if (IsRegNick(acptr))
-		status[i++] = 'r';
-
-	if (IsSecureConnect(acptr))
-		status[i++] = 's';
-
-	for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
-	{
-		int ret = (*(h->func.intfunc))(client, acptr, NULL, NULL, status, 0);
-		if (ret != 0)
-			status[i++] = (char)ret;
-	}
-
-	if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
-		status[i++] = '*';
-
-	if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client)))
-		status[i++] = '!';
-
-	if (channel)
-	{
-		Membership *lp;
-
-		if ((lp = find_membership_link(acptr->user->channel, channel)))
-		{
-			if (!(fmt->fields || HasCapability(client, "multi-prefix")))
-			{
-				/* 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) */
-				strcpy(&status[i], modes_to_prefix(lp->member_modes));
-				i += strlen(&status[i]);
-			}
-		}
-	}
-
-	status[i] = '\0';
- 
-	if (fmt->fields == 0)
-	{
-		char *host;
-		if (fmt->show_realhost)
-			host = acptr->user->realhost;
-		else if (fmt->show_ip)
-			host = GetIP(acptr);
-		else
-			host = GetHost(acptr);
-		sendnumeric(client, RPL_WHOREPLY,
-			channel ? channel->name : "*",
-			acptr->user->username, host,
-			hide ? "*" : acptr->user->server,
-			acptr->name, status, hide ? 0 : acptr->hopcount, acptr->info);
-	} else
-	{
-		str[0] = '\0';
-		pos = 0;
-		append_format(str, sizeof str, &pos, ":%s %d %s", me.name, RPL_WHOSPCRPL, client->name);
-		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->name : "*");
-		if (HasField(fmt, FIELD_USER))
-			append_format(str, sizeof str, &pos, " %s", acptr->user->username);
-		if (HasField(fmt, FIELD_IP))
-		{
-			if (show_ip(client, acptr) && acptr->ip)
-				append_format(str, sizeof str, &pos, " %s", acptr->ip);
-			else
-				append_format(str, sizeof str, &pos, " %s", "255.255.255.255");
-		}
-
-		if (HasField(fmt, FIELD_HOST) || HasField(fmt, FIELD_REALHOST))
-		{
-			if (IsOper(client) && HasField(fmt, FIELD_REALHOST))
-				append_format(str, sizeof str, &pos, " %s", acptr->user->realhost);
-			else
-				append_format(str, sizeof str, &pos, " %s", GetHost(acptr));
-		}
-
-		if (HasField(fmt, FIELD_SERVER))
-			append_format(str, sizeof str, &pos, " %s", hide ? "*" : acptr->user->server);
-		if (HasField(fmt, FIELD_NICK))
-			append_format(str, sizeof str, &pos, " %s", acptr->name);
-		if (HasField(fmt, FIELD_FLAGS))
-			append_format(str, sizeof str, &pos, " %s", status);
-		if (HasField(fmt, FIELD_MODES))
-		{
-			if (IsOper(client))
-			{
-				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->idle_since) : 0));
-		}
-		if (HasField(fmt, FIELD_ACCOUNT))
-			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 && check_channel_access(acptr, channel, "hoaq")) ? "999" : "n/a");
-		if (HasField(fmt, FIELD_REPUTATION))
-		{
-			if (IsOper(client))
-				append_format(str, sizeof str, &pos, " %d", GetReputation(acptr));
-			else
-				append_format(str, sizeof str, &pos, " %s", "*");
-		}
-		if (HasField(fmt, FIELD_INFO))
-			append_format(str, sizeof str, &pos, " :%s", acptr->info);
-
-		sendto_one(client, NULL, "%s", str);
-	}
-}
-
-/* Yeah, this is fun. Thank you WHOX !!! */
-static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt)
-{
-	const char *p;
-	static char pbuf1[512], pbuf2[512];
-	int points;
-
-	/* Figure out if the user is doing a 'classical' UnrealIRCd request,
-	 * which can be recognized as follows:
-	 * 1) Always a + or - as 1st character for the 1st parameter.
-	 * 2) Unlikely to have a % (percent sign) in the 2nd parameter
-	 * 3) Unlikely to have a 3rd parameter
-	 * On the other hand WHOX requests are:
-	 * 4) never going to have a '*', '?' or '.' as 2nd parameter
-	 * 5) never going to have a '+' or '-' as 1st character in 2nd parameter
-	 * Additionally, WHOX requests are useless - and thus unlikely -
-	 * to search for a mask mask starting with + or -  except when:
-	 * 6) searching for 'm' (mode)
-	 * 7) or 'r' (info, realname)
-	 * ..for which this would be a meaningful request.
-	 * The end result is that we can do quite some useful heuristics
-	 * except for some corner cases.
-	 */
-	if (((*parv[1] == '+') || (*parv[1] == '-')) &&
-	    (!parv[2] || !strchr(parv[2], '%')) &&
-	    (*parc < 4))
-	{
-		/* Conditions 1-3 of above comment are met, now we deal
-		 * with conditions 4-7.
-		 */
-		if (parv[2] &&
-		    !strchr(parv[2], '*') && !strchr(parv[2], '?') &&
-		    !strchr(parv[2], '.') &&
-		    !strchr(parv[2], '+') && !strchr(parv[2], '-') &&
-		    (strchr(parv[2], 'm') || strchr(parv[2], 'r')))
-		{
-			/* 'WHO +something m" or even
-			 * 'WHO +c #something' (which includes 'm')
-			 * could mean either WHO or WHOX style
-			 */
-		} else {
-			/* If we get here then it's an classical
-			 * UnrealIRCd-style WHO request which has
-			 * the order: WHO <options> <mask>
-			 */
-			char oldrequest[256];
-			snprintf(oldrequest, sizeof(oldrequest), "WHO %s%s%s",
-			         parv[1], parv[2] ? " " : "", parv[2] ? parv[2] : "");
-			if (parv[2])
-			{
-				const char *swap = parv[1];
-				parv[1] = parv[2];
-				parv[2] = swap;
-			} else {
-				/* A request like 'WHO +I' or 'WHO +R' */
-				parv[2] = parv[1];
-				parv[1] = "*";
-				parv[3] = NULL;
-				*parc = 3;
-			}
-
-			/* Ok, that was the first step, now we need to convert the
-			 * flags since they have changed a little as well:
-			 * Flag a: user is away                                            << no longer exists
-			 * Flag c <channel>: user is on <channel>                          << no longer exists
-			 * Flag g <gcos/realname>: user has string <gcos> in his/her GCOS  << now called 'r'
-			 * Flag h <host>: user has string <host> in his/her hostname       << no change
-			 * Flag i <ip>: user has string <ip> in his/her IP address         << no change
-			 * Flag m <usermodes>: user has <usermodes> set                    << behavior change
-			 * Flag n <nick>: user has string <nick> in his/her nickname       << no change
-			 * Flag s <server>: user is on server <server>                     << no change
-			 * Flag u <user>: user has string <user> in his/her username       << no change
-			 * Behavior flags:
-			 * Flag M: check for user in channels I am a member of             << no longer exists
-			 * Flag R: show users' real hostnames                              << no change (re-added)
-			 * Flag I: show users' IP addresses                                << no change (re-added)
-			 */
-
-			if (strchr(parv[2], 'a'))
-			{
-				sendnotice(client, "WHO request '%s' failed: flag 'a' no longer exists with WHOX.", oldrequest);
-				return 0;
-			}
-			if (strchr(parv[2], 'c'))
-			{
-				sendnotice(client, "WHO request '%s' failed: flag 'c' no longer exists with WHOX.", oldrequest);
-				return 0;
-			}
-			if (strchr(parv[2], 'g'))
-			{
-				char *w;
-				strlcpy(pbuf2, parv[2], sizeof(pbuf2));
-				for (w = pbuf2; *w; w++)
-				{
-					if (*w == 'g')
-					{
-						*w = 'r';
-						break;
-					}
-				}
-				parv[2] = pbuf2;
-			}
-
-			/* "WHO -m xyz" (now: xyz -m) should become "WHO -xyz m"
-			 * Wow, this seems overly complex, but okay...
-			 */
-			points = 0;
-			for (p = parv[2]; *p; p++)
-			{
-				if (*p == '-')
-					points = 1;
-				else if (*p == '+')
-					points = 0;
-				else if (points && (*p == 'm'))
-				{
-					points = 2;
-					break;
-				}
-			}
-			if (points == 2)
-			{
-				snprintf(pbuf1, sizeof(pbuf1), "-%s", parv[1]);
-				parv[1] = pbuf1;
-			}
-
-			if ((*parv[2] == '+') || (*parv[2] == '-'))
-				parv[2] = parv[2]+1; /* strip '+'/'-' prefix, which does not exist in WHOX */
-
-			sendnotice(client, "WHO request '%s' changed to match new WHOX syntax: 'WHO %s %s'",
-				oldrequest, parv[1], parv[2]);
-			*orig_mask = parv[1];
-		}
-	}
-	return 1;
-}
diff --git a/src/openssl_hostname_validation.c b/src/openssl_hostname_validation.c
@@ -1,402 +0,0 @@
-/* This file contains both code from cURL and hostname
- * validation code from the ssl conservatory (in that order).
- *
- * The goal is that all this code won't be needed anymore once
- * everyone is running a recent OpenSSL/LibreSSL that has
- * X509_check_host(). Until that time, unfortunately, we need
- * these 400+ lines to do just that... -- Syzop, Sep/2017
- */
-
-// Get rid of OSX 10.7 and greater deprecation warnings.
-#if defined(__APPLE__) && defined(__clang__)
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#endif
-
-#include <openssl/x509v3.h>
-#include <openssl/ssl.h>
-#include <string.h>
-
-#include "openssl_hostname_validation.h"
-
-#define HOSTNAME_MAX_SIZE 255
-
-#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
-#define ASN1_STRING_get0_data ASN1_STRING_data
-#endif
-
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ***************************************************************************/
-
-/* This file is an amalgamation of hostcheck.c and most of rawstr.c
-   from cURL.  The contents of the COPYING file mentioned above are:
-
-COPYRIGHT AND PERMISSION NOTICE
-
-Copyright (c) 1996 - 2013, Daniel Stenberg, <daniel@haxx.se>.
-
-All rights reserved.
-
-Permission to use, copy, modify, and distribute this software for any purpose
-with or without fee is hereby granted, provided that the above copyright
-notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
-NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
-OR OTHER DEALINGS IN THE SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder shall not
-be used in advertising or otherwise to promote the sale, use or other dealings
-in this Software without prior written authorization of the copyright holder.
-*/
-
-/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because
-   its behavior is altered by the current locale. */
-static char Curl_raw_toupper(char in)
-{
-  switch (in) {
-  case 'a':
-    return 'A';
-  case 'b':
-    return 'B';
-  case 'c':
-    return 'C';
-  case 'd':
-    return 'D';
-  case 'e':
-    return 'E';
-  case 'f':
-    return 'F';
-  case 'g':
-    return 'G';
-  case 'h':
-    return 'H';
-  case 'i':
-    return 'I';
-  case 'j':
-    return 'J';
-  case 'k':
-    return 'K';
-  case 'l':
-    return 'L';
-  case 'm':
-    return 'M';
-  case 'n':
-    return 'N';
-  case 'o':
-    return 'O';
-  case 'p':
-    return 'P';
-  case 'q':
-    return 'Q';
-  case 'r':
-    return 'R';
-  case 's':
-    return 'S';
-  case 't':
-    return 'T';
-  case 'u':
-    return 'U';
-  case 'v':
-    return 'V';
-  case 'w':
-    return 'W';
-  case 'x':
-    return 'X';
-  case 'y':
-    return 'Y';
-  case 'z':
-    return 'Z';
-  }
-  return in;
-}
-
-/*
- * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant
- * to be locale independent and only compare strings we know are safe for
- * this.  See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for
- * some further explanation to why this function is necessary.
- *
- * The function is capable of comparing a-z case insensitively even for
- * non-ascii.
- */
-
-static int Curl_raw_equal(const char *first, const char *second)
-{
-  while(*first && *second) {
-    if (Curl_raw_toupper(*first) != Curl_raw_toupper(*second))
-      /* get out of the loop as soon as they don't match */
-      break;
-    first++;
-    second++;
-  }
-  /* we do the comparison here (possibly again), just to make sure that if the
-     loop above is skipped because one of the strings reached zero, we must not
-     return this as a successful match */
-  return (Curl_raw_toupper(*first) == Curl_raw_toupper(*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)) {
-      break;
-    }
-    max--;
-    first++;
-    second++;
-  }
-  if (0 == max)
-    return 1; /* they are equal this far */
-
-  return Curl_raw_toupper(*first) == Curl_raw_toupper(*second);
-}
-
-/*
- * Match a hostname against a wildcard pattern.
- * E.g.
- *  "foo.host.com" matches "*.host.com".
- *
- * We use the matching rule described in RFC6125, section 6.4.3.
- * http://tools.ietf.org/html/rfc6125#section-6.4.3
- */
-
-static int hostmatch(const char *hostname, const char *pattern)
-{
-  const char *pattern_label_end, *pattern_wildcard, *hostname_label_end;
-  int wildcard_enabled;
-  size_t prefixlen, suffixlen;
-  pattern_wildcard = strchr(pattern, '*');
-  if (pattern_wildcard == NULL)
-    return Curl_raw_equal(pattern, hostname) ?
-      CURL_HOST_MATCH : CURL_HOST_NOMATCH;
-
-  /* We require at least 2 dots in pattern to avoid too wide wildcard
-     match. */
-  wildcard_enabled = 1;
-  pattern_label_end = strchr(pattern, '.');
-  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)
-    return Curl_raw_equal(pattern, hostname) ?
-      CURL_HOST_MATCH : CURL_HOST_NOMATCH;
-
-  hostname_label_end = strchr(hostname, '.');
-  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)
-    return CURL_HOST_NOMATCH;
-
-  prefixlen = pattern_wildcard - pattern;
-  suffixlen = pattern_label_end - (pattern_wildcard+1);
-  return Curl_raw_nequal(pattern, hostname, prefixlen) &&
-    Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen,
-                    suffixlen) ?
-    CURL_HOST_MATCH : CURL_HOST_NOMATCH;
-}
-
-int Curl_cert_hostcheck(const char *match_pattern, const char *hostname)
-{
-  if (!match_pattern || !*match_pattern ||
-      !hostname || !*hostname) /* sanity check */
-    return 0;
-
-  if (Curl_raw_equal(hostname, match_pattern)) /* trivial case */
-    return 1;
-
-  if (hostmatch(hostname,match_pattern) == CURL_HOST_MATCH)
-    return 1;
-  return 0;
-}
-/* End of cURL related functions */
-
-/* Start of host validation code */
-/* Obtained from: https://github.com/iSECPartners/ssl-conservatory */
-
-/*
-Copyright (C) 2012, iSEC Partners.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
- */
-
-/*
- * Helper functions to perform basic hostname validation using OpenSSL.
- *
- * Please read "everything-you-wanted-to-know-about-openssl.pdf" before
- * attempting to use this code. This whitepaper describes how the code works,
- * how it should be used, and what its limitations are.
- *
- * Author:  Alban Diquet
- * License: See LICENSE
- *
- */
-
-/**
-* Tries to find a match for hostname in the certificate's Common Name field.
-*
-* Returns MatchFound if a match was found.
-* Returns MatchNotFound if no matches were found.
-* Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
-* Returns Error if the Common Name could not be extracted.
-*/
-static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
-        int common_name_loc = -1;
-        X509_NAME_ENTRY *common_name_entry = NULL;
-        ASN1_STRING *common_name_asn1 = NULL;
-        const char *common_name_str = NULL;
-
-        // Find the position of the CN field in the Subject field of the certificate
-        common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
-        if (common_name_loc < 0) {
-                return Error;
-        }
-
-        // Extract the CN field
-        common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
-        if (common_name_entry == NULL) {
-                return Error;
-        }
-
-        // Convert the CN field to a C string
-        common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
-        if (common_name_asn1 == NULL) {
-                return Error;
-        }
-        common_name_str = (char *) ASN1_STRING_get0_data(common_name_asn1);
-
-        // Make sure there isn't an embedded NUL character in the CN
-        if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
-                return MalformedCertificate;
-        }
-
-        // Compare expected hostname with the CN
-        if (Curl_cert_hostcheck(common_name_str, hostname) == CURL_HOST_MATCH) {
-                return MatchFound;
-        }
-        else {
-                return MatchNotFound;
-        }
-}
-
-
-/**
-* Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
-*
-* Returns MatchFound if a match was found.
-* Returns MatchNotFound if no matches were found.
-* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
-* Returns NoSANPresent if the SAN extension was not present in the certificate.
-*/
-static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
-        HostnameValidationResult result = MatchNotFound;
-        int i;
-        int san_names_nb = -1;
-        STACK_OF(GENERAL_NAME) *san_names = NULL;
-
-        // Try to extract the names within the SAN extension from the certificate
-        san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
-        if (san_names == NULL) {
-                return NoSANPresent;
-        }
-        san_names_nb = sk_GENERAL_NAME_num(san_names);
-
-        // Check each name within the extension
-        for (i=0; i<san_names_nb; i++) {
-                const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
-
-                if (current_name->type == GEN_DNS) {
-                        // Current name is a DNS name, let's check it
-                        const char *dns_name = (char *) ASN1_STRING_get0_data(current_name->d.dNSName);
-
-                        // Make sure there isn't an embedded NUL character in the DNS name
-                        if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
-                                result = MalformedCertificate;
-                                break;
-                        }
-                        else { // Compare expected hostname with the DNS name
-                                if (Curl_cert_hostcheck(dns_name, hostname)
-                                    == CURL_HOST_MATCH) {
-                                        result = MatchFound;
-                                        break;
-                                }
-                        }
-                }
-        }
-        sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
-
-        return result;
-}
-
-
-/**
-* Validates the server's identity by looking for the expected hostname in the
-* server's certificate. As described in RFC 6125, it first tries to find a match
-* in the Subject Alternative Name extension. If the extension is not present in
-* the certificate, it checks the Common Name instead.
-*
-* Returns MatchFound if a match was found.
-* Returns MatchNotFound if no matches were found.
-* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
-* Returns Error if there was an error.
-*/
-HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert) {
-        HostnameValidationResult result;
-
-        if ((hostname == NULL) || (server_cert == NULL))
-                return Error;
-
-        // First try the Subject Alternative Names extension
-        result = matches_subject_alternative_name(hostname, server_cert);
-        if (result == NoSANPresent) {
-                // Extension was not found: try the Common Name
-                result = matches_common_name(hostname, server_cert);
-        }
-
-        return result;
-}
diff --git a/src/operclass.c b/src/operclass.c
@@ -1,348 +0,0 @@
-/** Oper classes code.
- * (C) Copyright 2015-present tmcarthur and the UnrealIRCd team
- * License: GPLv2 or later
- */
-
-#include "unrealircd.h"
-
-typedef struct OperClassPathNode OperClassPathNode;
-typedef struct OperClassCallbackNode OperClassCallbackNode;
-
-struct OperClassPathNode
-{
-	OperClassPathNode *prev,*next;
-	OperClassPathNode *children;
-	char *identifier;
-	OperClassCallbackNode *callbacks;
-};
-
-struct OperClassCallbackNode
-{
-	OperClassCallbackNode *prev, *next;
-	OperClassPathNode *parent;
-	OperClassEntryEvalCallback callback;
-};
-
-struct OperClassValidator
-{
-	Module *owner;
-	OperClassCallbackNode *node;
-};
-
-OperClassACLPath *OperClass_parsePath(const char *path);
-void OperClass_freePath(OperClassACLPath *path);
-OperClassPathNode *OperClass_findPathNodeForIdentifier(char *identifier, OperClassPathNode *head);
-
-OperClassPathNode *rootEvalNode = NULL;
-
-OperClassValidator *OperClassAddValidator(Module *module, char *pathStr, OperClassEntryEvalCallback callback)
-{
-	OperClassPathNode *node,*nextNode;
-	OperClassCallbackNode *callbackNode;
-	OperClassValidator *validator; 
-	OperClassACLPath *path = OperClass_parsePath(pathStr);
-
-	if (!rootEvalNode)
-	{
-		rootEvalNode = safe_alloc(sizeof(OperClassPathNode));
-	}
-
-	node = rootEvalNode;
-
-	while (path)
-	{
-		nextNode = OperClass_findPathNodeForIdentifier(path->identifier,node->children);
-		if (!nextNode)
-		{
-			nextNode = safe_alloc(sizeof(OperClassPathNode));
-			safe_strdup(nextNode->identifier, path->identifier);
-			AddListItem(nextNode,node->children);
-		}
-		node = nextNode;
-		path = path->next;
-	}
-
-	callbackNode = safe_alloc(sizeof(OperClassCallbackNode));
-	callbackNode->callback = callback;
-	callbackNode->parent = node;	
-	AddListItem(callbackNode,node->callbacks);
-
-	validator = safe_alloc(sizeof(OperClassValidator));
-	validator->node = callbackNode;	
-	validator->owner = module;
-
-	if (module)
-	{
-		ModuleObject *mobj = safe_alloc(sizeof(ModuleObject));
-		mobj->object.validator = validator;
-		mobj->type = MOBJ_VALIDATOR;
-		AddListItem(mobj, module->objects);
-		module->errorcode = MODERR_NOERROR;
-	}
-
-	OperClass_freePath(path);
-
-	return validator;
-}
-
-void OperClassValidatorDel(OperClassValidator *validator)
-{
-	if (validator->owner)
-	{
-		ModuleObject *mdobj;
-		for (mdobj = validator->owner->objects; mdobj; mdobj = mdobj->next)
-		{
-			if ((mdobj->type == MOBJ_VALIDATOR) && (mdobj->object.validator == validator))
-			{
-				DelListItem(mdobj, validator->owner->objects);
-				safe_free(mdobj);
-				break;
-			}
-		}
-		validator->owner = NULL;
-	}
-	
-	/* Technically, the below leaks memory if you don't re-register
-	 * another validator at same path, but it is cheaper than walking
-	 * back up and doing cleanup in practice, since this tree is very small
-	 */
-	DelListItem(validator->node,validator->node->parent->callbacks);
-	safe_free(validator->node);
-	safe_free(validator);	
-}
-
-OperClassACLPath *OperClass_parsePath(const char *path)
-{
-	char *pathCopy = raw_strdup(path);
-	OperClassACLPath *pathHead = NULL;
-	OperClassACLPath *tmpPath;
-	char *str = strtok(pathCopy,":");
-	while (str)
-	{
-		tmpPath = safe_alloc(sizeof(OperClassACLPath));
-		safe_strdup(tmpPath->identifier, str);
-		AddListItem(tmpPath,pathHead);
-		str = strtok(NULL,":");
-	}
-
-	while (pathHead->next)
-	{
-		tmpPath = pathHead->next;
-		pathHead->next = pathHead->prev;
-		pathHead->prev = tmpPath;
-		pathHead = tmpPath;
-	}
-	pathHead->next = pathHead->prev;
-	pathHead->prev = NULL;	
-
-	safe_free(pathCopy);
-	return pathHead;
-}
-
-void OperClass_freePath(OperClassACLPath *path)
-{
-	OperClassACLPath *next;
-	while (path)
-	{
-		next = path->next;
-		safe_free(path->identifier);
-		safe_free(path);
-		path = next;
-	}	
-}
-
-OperClassACL *OperClass_FindACL(OperClassACL *acl, char *name)
-{
-	for (;acl;acl = acl->next)
-	{
-		if (!strcmp(acl->name,name))
-		{ 
-			return acl;
-		}
-	}
-	return NULL;
-}
-
-OperClassPathNode *OperClass_findPathNodeForIdentifier(char *identifier, OperClassPathNode *head)
-{
-	for (; head; head = head->next)
-	{
-		if (!strcmp(head->identifier,identifier))
-		{
-			return head;
-		}
-	}
-	return NULL;
-}
-
-unsigned char OperClass_evaluateACLEntry(OperClassACLEntry *entry, OperClassACLPath *path, OperClassCheckParams *params)
-{
-	OperClassPathNode *node = rootEvalNode;	
-	OperClassCallbackNode *callbackNode = NULL;
-	unsigned char eval = 0;
-
-	/* If no variables, always match */
-	if (!entry->variables)
-	{
-		return 1;
-	}
-
-	/* Go as deep as possible */
-	while (path->next && node)
-	{
-		node = OperClass_findPathNodeForIdentifier(path->identifier,node);	
-		/* If we can't find a node we need, and we have vars, no match */
-		if (!node)
-		{
-			return 0;
-		}
-		node = node->children;
-		path = path->next;
-	}
-
-	/* If no evals for full path, no match */
-	if (path->next)
-	{
-		return 0;
-	}
-
-
-	/* We have a valid node, execute all callback nodes */
-	for (callbackNode = node->callbacks; callbackNode; callbackNode = callbackNode->next)
-	{
-		eval = callbackNode->callback(entry->variables,params);
-	}
-
-	return eval;	
-}
-
-OperPermission ValidatePermissionsForPathEx(OperClassACL *acl, OperClassACLPath *path, OperClassCheckParams *params)
-{
-	/** Evaluate into ACL struct as deep as possible **/
-	OperClassACLPath *basePath = path;
-	OperClassACL *tmp;
-	OperClassACLEntry *entry;
-	unsigned char allow = 0;
-	unsigned char deny = 0;
-	unsigned char aclNotFound = 0;
-
-	path = path->next; /* Avoid first level since we have resolved it */
-	while (path && acl->acls)
-	{
-		tmp = OperClass_FindACL(acl->acls,path->identifier);
-		if (!tmp)
-		{
-			aclNotFound = 1;
-			break;
-		}
-		path = path->next;
-		acl = tmp;
-	}
-	/** If node does not exist, but most specific one has other ACLs, deny **/
-	if (acl->acls && aclNotFound)
-	{
-		return OPER_DENY;
-	}
-
-	/** If node exists for this but has no ACL entries, allow **/
-	if (!acl->entries)
-	{
-		return OPER_ALLOW;
-	}
-	/** Process entries **/
-	for (entry = acl->entries; entry; entry = entry->next)
-	{
-		unsigned char result;
-		/* Short circuit if we already have valid block */
-		if (entry->type == OPERCLASSENTRY_ALLOW && allow)
-			continue;
-		if (entry->type == OPERCLASSENTRY_DENY && deny)
-			continue;
-
-		result = OperClass_evaluateACLEntry(entry,basePath,params);
-		if (entry->type == OPERCLASSENTRY_ALLOW)
-		{
-			allow = result;
-		}
-		else
-		{
-			deny = result;
-		}
-	}
-
-	/** We only permit if an allow matched AND no deny matched **/
-	if (allow && !deny)
-	{
-		return OPER_ALLOW;
-	}
-
-	return OPER_DENY;
-}
-
-OperPermission ValidatePermissionsForPath(const char *path, Client *client, Client *victim, Channel *channel, const void *extra)
-{
-	ConfigItem_oper *ce_oper;
-	const char *operclass;
-	ConfigItem_operclass *ce_operClass;
-	OperClass *oc = NULL;
-	OperClassACLPath *operPath;
-
-	if (!client)
-		return OPER_DENY;
-
-	/* Trust Servers, U-Lines and remote opers */
-	if (IsServer(client) || IsMe(client) || IsULine(client) || (IsOper(client) && !MyUser(client)))
-		return OPER_ALLOW;
-
-	if (!IsOper(client))
-		return OPER_DENY;
-
-	ce_oper = find_oper(client->user->operlogin);
-	if (!ce_oper)
-	{
-		operclass = moddata_client_get(client, "operclass");
-		if (!operclass)
-			return OPER_DENY;
-	} else
-	{
-		operclass = ce_oper->operclass;
-	}
-
-	ce_operClass = find_operclass(operclass);
-	if (!ce_operClass)
-		return OPER_DENY;
-
-	oc = ce_operClass->classStruct;
-	operPath = OperClass_parsePath(path);
-	while (oc && operPath)
-	{
-		OperClassACL *acl = OperClass_FindACL(oc->acls,operPath->identifier);
-		if (acl)
-		{
-			OperPermission perm;
-			OperClassCheckParams *params = safe_alloc(sizeof(OperClassCheckParams));
-			params->client = client;
-			params->victim = victim;
-			params->channel = channel;
-			params->extra = extra;
-			
-			perm = ValidatePermissionsForPathEx(acl, operPath, params);
-			OperClass_freePath(operPath);
-			safe_free(params);
-			return perm;
-		}
-		if (!oc->ISA)
-		{
-			break;
-		}
-		ce_operClass = find_operclass(oc->ISA);
-		if (ce_operClass)
-		{
-			oc = ce_operClass->classStruct;
-		} else {
-			break; /* parent not found */
-		}
-	}
-	OperClass_freePath(operPath);
-	return OPER_DENY;
-}
diff --git a/src/parse.c b/src/parse.c
@@ -1,782 +0,0 @@
-/************************************************************************
- *   Unreal Internet Relay Chat Daemon, src/parse.c
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Computing Center
- *
- *   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 Main line parsing functions - for incoming lines from clients.
- */
-#include "unrealircd.h"
-
-/** Last (or current) command that we processed. Useful for post-mortem. */
-char backupbuf[8192];
-
-static char *para[MAXPARA + 2];
-
-/* Forward declarations of functions that are local (static) */
-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, 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);
-
-/** Put a packet in the client receive queue and process the data (if
- * the 'fake lag' rules permit doing so).
- * @param client      The client
- * @param readbuf     The read buffer
- * @param length      The length of the data
- * @param killsafely  If 1 then we may call exit_client() if the client
- *                    is flooding. If 0 then we use dead_socket().
- * @returns 1 in normal circumstances, 0 if client was killed.
- * @note  If killsafely is 1 and the return value is 0 then
- *        the client was killed - IsDead() is true.
- *        If this is a problem, then set killsafely to 0 when calling.
- */
-int process_packet(Client *client, char *readbuf, int length, int killsafely)
-{
-	dbuf_put(&client->local->recvQ, readbuf, length);
-
-	/* parse some of what we have (inducing fakelag, etc) */
-	parse_client_queued(client);
-
-	/* We may be killed now, so check for it.. */
-	if (IsDead(client))
-		return 0;
-
-	/* flood from unknown connection */
-	if (IsUnknown(client) && (DBufLength(&client->local->recvQ) > iConf.handshake_data_flood_amount))
-	{
-		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
-			dead_socket(client, "Handshake data flood detected");
-		return 0;
-	}
-
-	/* excess flood check */
-	if (IsUser(client) && 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
-			dead_socket(client, "Excess Flood");
-		return 0;
-	}
-
-	return 1;
-}
-
-/** Parse any queued data for 'client', if permitted.
- * @param client	The client.
- */
-void parse_client_queued(Client *client)
-{
-	int dolen = 0;
-	char buf[READBUFSIZE];
-
-	if (IsDNSLookup(client))
-		return; /* we delay processing of data until the host is resolved */
-
-	if (IsIdentLookup(client))
-		return; /* we delay processing of data until identd has replied */
-
-	if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) &&
-	    !IsNoHandshakeDelay(client) &&
-	    !IsUnixSocket(client) &&
-	    (TStime() - client->local->creationtime < iConf.handshake_delay))
-	{
-		return; /* we delay processing of data until set::handshake-delay is reached */
-	}
-
-	while (DBufLength(&client->local->recvQ) && !client_lagged_up(client))
-	{
-		dolen = dbuf_getmsg(&client->local->recvQ, buf);
-
-		if (dolen == 0)
-			return;
-
-		dopacket(client, buf, dolen);
-		
-		if (IsDead(client))
-			return;
-	}
-}
-
-/*
-** dopacket
-**	client - pointer to client structure for which the buffer data
-**	       applies.
-**	buffer - pointr to the buffer containing the newly read data
-**	length - number of valid bytes of data in the buffer
-**
-** Note:
-**	It is implicitly assumed that dopacket is called only
-**	with client of "local" variation, which contains all the
-**	necessary fields (buffer etc..)
-**
-** Rewritten for linebufs, 19th May 2013. --kaniini
-*/
-void dopacket(Client *client, char *buffer, int length)
-{
-	client->local->traffic.bytes_received += length;
-	me.local->traffic.bytes_received += length;
-
-	client->local->traffic.messages_received++;
-	me.local->traffic.messages_received++;
-
-	parse(client, buffer, length);
-}
-
-
-/** Parse an incoming line.
- * A line was received previously, buffered via dbuf, now popped from the dbuf stack,
- * and we should now process it.
- * @param cptr    The client from which the message was received
- * @param buffer  The buffer
- * @param length  The length of the buffer
- * @note parse() cannot not be called recusively by any other functions!
- */
-void parse(Client *cptr, char *buffer, int length)
-{
-	Hook *h;
-	Client *from = cptr;
-	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.
-	 * This, while all the rest of the IRCd code assumes a maximum length
-	 * of BUFSIZE, which is 512 (including NUL byte).
-	 */
-	for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
-	{
-		(*(h->func.intfunc))(from, &me, NULL, &buffer, &length);
-		if (!buffer)
-			return;
-	}
-
-	if (IsDeadSocket(cptr))
-		return;
-
-	if ((cptr->local->traffic.bytes_received >= iConf.handshake_data_flood_amount) && IsUnknown(cptr))
-	{
-		unreal_log(ULOG_INFO, "flood", "HANDSHAKE_DATA_FLOOD", cptr,
-		           "Handshake data flood detected from $client.details [$client.ip]");
-		ban_handshake_data_flooder(cptr);
-		return;
-	}
-
-	/* This stores the last executed command in 'backupbuf', useful for debugging crashes */
-	strlcpy(backupbuf, buffer, sizeof(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 */
-	for (i = 0; i < MAXPARA+2; i++)
-		para[i] = (char *)DEADBEEF_ADDR;
-
-	/* First, skip any whitespace */
-	for (ch = buffer; *ch == ' '; ch++)
-		;
-
-	/* 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, mtags_bytes, ch);
-
-	if (IsDead(cptr))
-		RunHook(HOOKTYPE_POST_COMMAND, NULL, mtags, ch);
-	else
-		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 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, int mtags_bytes, char *ch)
-{
-	Client *from = cptr;
-	char *s;
-	int len, i, numeric = 0, paramcount;
-#ifdef DEBUGMODE
-	time_t then, ticks;
-	int retval;
-#endif
-	RealCommand *cmptr = NULL;
-	int bytes;
-
-	*fromptr = cptr; /* The default, unless a source is specified (and permitted) */
-
-	/* The remaining part should never be more than 510 bytes
-	 * (that is 512 minus CR LF, as specified in RFC1459 section 2.3).
-	 * If it is too long, then we cut it off here.
-	 */
-	if (strlen(ch) > 510)
-	{
-		ch[510] = '\0';
-	}
-
-	para[0] = (char *)DEADBEEF_ADDR; /* helps us catch bugs :) */
-
-	if (*ch == ':' || *ch == '@')
-	{
-		char sender[HOSTLEN + 1];
-		s = sender;
-		*s = '\0';
-
-		/* Deal with :sender ... */
-		for (++ch, i = 0; *ch && *ch != ' '; ++ch)
-		{
-			if (s < sender + sizeof(sender) - 1)
-				*s++ = *ch;
-		}
-		*s = '\0';
-
-		/* For servers we lookup the sender and change 'from' accordingly.
-		 * For other clients we ignore the sender.
-		 */
-		if (*sender && IsServer(cptr))
-		{
-			from = find_client(sender, NULL);
-
-			if (!from && strchr(sender, '@'))
-				from = hash_find_nickatserver(sender, NULL);
-
-			/* Sender not found. Possibly a ghost, so kill it.
-			 * This can happen in normal circumstances. For example
-			 * in case of A-B-C where we are B. If a KILL came from C
-			 * for a client on A and we processed it at B, then until
-			 * A has processed it we may still receive messages from A
-			 * about it's soon-to-be-killed-client (all due to lag).
-			 */
-			if (!from)
-			{
-				ircstats.is_unpf++;
-				remove_unknown(cptr, sender);
-				return;
-			}
-			/* This is more severe. The server gave a source of a client
-			 * that cannot exist from that direction.
-			 * Eg in case of a topology of A-B-C-D and we are B,
-			 * we got a message from A with ":D MODE...".
-			 * In that case we send a SQUIT to that direction telling to
-			 * unlink D from that side. This will likely lead to a
-			 * problematic situation, though.
-			 * This is, by the way, also why we try to prevent this situation
-			 * in the first place by using PROTOCTL SERVERS=...
-			 * in which case we reject such a flawed link very early
-			 * in the server handshake process. -- Syzop
-			 */
-			if (from->direction != cptr)
-			{
-				ircstats.is_wrdi++;
-				cancel_clients(cptr, from, ch);
-				return;
-			}
-			*fromptr = from; /* Update source client */
-		}
-		while (*ch == ' ')
-			ch++;
-	}
-
-	RunHook(HOOKTYPE_PRE_COMMAND, from, mtags, ch);
-
-	if (*ch == '\0')
-	{
-		if (!IsServer(cptr))
-			cptr->local->fake_lag++; /* 1s fake lag */
-		return;
-	}
-
-	/* Recalculate string length, now that we have skipped the sender */
-	bytes = strlen(ch);
-
-	/* Now let's figure out the command (or numeric)... */
-	s = strchr(ch, ' ');	/* s -> End of the command code */
-	len = (s) ? (s - ch) : 0;
-
-	if (len == 3 && isdigit(*ch) && isdigit(*(ch + 1)) && isdigit(*(ch + 2)))
-	{
-		/* Numeric (eg: 311) */
-		cmptr = NULL;
-		numeric = (*ch - '0') * 100 + (*(ch + 1) - '0') * 10 + (*(ch + 2) - '0');
-		paramcount = MAXPARA;
-		ircstats.is_num++;
-		parse_addlag(cptr, bytes, mtags_bytes);
-	}
-	else
-	{
-		/* Command (eg: PRIVMSG) */
-		int flags = 0;
-		if (s)
-			*s++ = '\0';
-
-		/* Set the appropriate flags for the command lookup */
-		if (!IsRegistered(from))
-			flags |= CMD_UNREGISTERED;
-		if (IsUser(from))
-			flags |= CMD_USER;
-		if (IsServer(from))
-			flags |= CMD_SERVER;
-		if (IsShunned(from))
-			flags |= CMD_SHUN;
-		if (IsVirus(from))
-			flags |= CMD_VIRUS;
-		if (IsOper(from))
-			flags |= CMD_OPER;
-		if (IsControl(from))
-			flags |= CMD_CONTROL;
-		cmptr = find_command(ch, flags);
-		if (!cmptr || !(cmptr->flags & CMD_NOLAG))
-		{
-			/* Add fake lag (doing this early in the code, so we don't forget) */
-			parse_addlag(cptr, bytes, mtags_bytes);
-		}
-		if (!cmptr)
-		{
-			if (IsControl(from))
-			{
-				sendto_one(from, NULL, "ERROR UNKNOWN_COMMAND: %s", ch);
-				sendto_one(from, NULL, "END 1");
-				return;
-			}
-			/* Don't send error messages in response to NOTICEs
-			 * in pre-connection state.
-			 */
-			if (!IsRegistered(cptr) && strcasecmp(ch, "NOTICE"))
-			{
-				sendnumericfmt(from, ERR_NOTREGISTERED, ":You have not registered");
-				return;
-			}
-			/* If the user is shunned then don't send anything back in case
-			 * of an unknown command, since we want to save data.
-			 */
-			if (IsShunned(cptr))
-				return;
-				
-			if (ch[0] != '\0')
-			{
-				if (IsUser(from))
-				{
-					sendto_one(from, NULL, ":%s %d %s %s :Unknown command",
-					                       me.name, ERR_UNKNOWNCOMMAND,
-					                       from->name, ch);
-				}
-			}
-			ircstats.is_unco++;
-			return;
-		}
-		if (cmptr->flags != 0) { /* temporary until all commands are updated */
-
-		/* Logic in comparisons below is a bit complicated, see notes */
-
-		/* If you're a user, and this command does not permit users or opers, deny */
-		if ((flags & CMD_USER) && !(cmptr->flags & CMD_USER) && !(cmptr->flags & CMD_OPER))
-		{
-			if (cmptr->flags & CMD_UNREGISTERED)
-				sendnumeric(cptr, ERR_ALREADYREGISTRED); /* only for unregistered phase */
-			else
-				sendnumeric(cptr, ERR_NOTFORUSERS, cmptr->cmd); /* really never for users */
-			return;
-		}
-
-		/* If you're a server, but command doesn't want servers, deny */
-		if ((flags & CMD_SERVER) && !(cmptr->flags & CMD_SERVER))
-			return;
-		}
-
-		/* If you're a user, but not an operator, and this requires operators, deny */
-		if ((cmptr->flags & CMD_OPER) && (flags & CMD_USER) && !(flags & CMD_OPER))
-		{
-			sendnumeric(cptr, ERR_NOPRIVILEGES);
-			return;
-		}
-		paramcount = cmptr->parameters;
-		cmptr->bytes += bytes;
-	}
-	/*
-	   ** Must the following loop really be so devious? On
-	   ** surface it splits the message to parameters from
-	   ** blank spaces. But, if paramcount has been reached,
-	   ** the rest of the message goes into this last parameter
-	   ** (about same effect as ":" has...) --msa
-	 */
-
-	/* Note initially true: s==NULL || *(s-1) == '\0' !! */
-
-	i = 0;
-	if (s)
-	{
-		/*
-		if (paramcount > MAXPARA)
-			paramcount = MAXPARA;
-		We now use functions to create commands, so we can just check this 
-		once when the command is created rather than each time the command
-		is used -- codemastr
-		*/
-		for (;;)
-		{
-			/*
-			   ** Never "FRANCE " again!! ;-) Clean
-			   ** out *all* blanks.. --msa
-			 */
-			while (*s == ' ')
-				*s++ = '\0';
-
-			if (*s == '\0')
-				break;
-			if (*s == ':')
-			{
-				/*
-				   ** The rest is single parameter--can
-				   ** include blanks also.
-				 */
-				para[++i] = s + 1;
-				break;
-			}
-			para[++i] = s;
-			if (i >= paramcount)
-				break;
-			for (; *s != ' ' && *s; s++)
-				;
-		}
-	}
-	para[++i] = NULL;
-
-	/* Check if one of the message tags are rejected by spamfilter */
-	if (MyConnect(from) && !IsServer(from) && match_spamfilter_mtags(from, mtags, cmptr ? cmptr->cmd : NULL))
-		return;
-
-	if (cmptr == NULL)
-	{
-		do_numeric(numeric, from, mtags, i, (const char **)para);
-		return;
-	}
-	cmptr->count++;
-	if (IsUser(cptr) && (cmptr->flags & CMD_RESETIDLE))
-		cptr->local->idle_since = TStime();
-
-	/* Now ready to execute the command */
-#ifndef DEBUGMODE
-	if (cmptr->flags & CMD_ALIAS)
-	{
-		(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
-	} else {
-		if (!cmptr->overriders)
-			(*cmptr->func) (from, mtags, i, (const char **)para);
-		else
-			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
-	}
-#else
-	then = clock();
-	if (cmptr->flags & CMD_ALIAS)
-	{
-		(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
-	} else {
-		if (!cmptr->overriders)
-			(*cmptr->func) (from, mtags, i, (const char **)para);
-		else
-			(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
-	}
-	if (!IsDead(cptr))
-	{
-		ticks = (clock() - then);
-		if (IsServer(cptr))
-			cmptr->rticks += ticks;
-		else
-			cmptr->lticks += ticks;
-	}
-#endif
-}
-
-/** Ban user that is "flooding from an unknown connection".
- * This is basically a client sending lots of data but not registering.
- * Note that "lots" in terms of IRC is a few KB's, since more is rather unusual.
- * @param client The client.
- */
-static void ban_handshake_data_flooder(Client *client)
-{
-	if (find_tkl_exception(TKL_HANDSHAKE_DATA_FLOOD, client))
-	{
-		/* If the user is exempt we will still KILL the client, since it is
-		 * clearly misbehaving. We just won't ZLINE the host, so it won't
-		 * affect any other connections from the same IP address.
-		 */
-		exit_client(client, NULL, "Handshake data flood detected");
-	}
-	else
-	{
-		/* place_host_ban also takes care of removing any other clients with same host/ip */
-		place_host_ban(client, iConf.handshake_data_flood_ban_action, "Handshake data flood detected", iConf.handshake_data_flood_ban_time);
-	}
-}
-
-/** Add "fake lag" if needed.
- * The main purpose of fake lag is to create artificial lag when
- * processing incoming data from the client. So, if a client sends
- * a lot of commands, then next command will be processed at a rate
- * of 1 per second, or even slower. The exact algorithm is defined in this function.
- *
- * Servers are exempt from fake lag, so are IRCOps and clients tagged as
- * 'no fake lag' by services (rarely used). Finally, there is also an
- * option called class::options::nofakelag which exempts fakelag.
- * Exemptions should be granted with extreme care, since a client will
- * 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 command_bytes	Command length in bytes (excluding message tagss)
- * @param mtags_bytes	Length of message tags in bytes
- */
-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))
-	{
-		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
- * @returns 1 if client is lagged up and data should not be parsed, 0 otherwise.
- */
-static int client_lagged_up(Client *client)
-{
-	if (client->status < CLIENT_STATUS_UNKNOWN)
-		return 0;
-	if (IsServer(client))
-		return 0;
-	if (ValidatePermissionsForPath("immune:lag",client,NULL,NULL,NULL))
-		return 0;
-	if (client->local->fake_lag - TStime() < 10)
-		return 0;
-	return 1;
-}
-
-
-/** Numeric received from a connection.
- * @param numeric     The numeric code (range 000-999)
- * @param cptr        The client
- * @param recv_mtags  Received message tags
- * @param parc        Parameter count
- * @param parv        Parameters
- * @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, 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->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.
-		 */
-
-		/* STARTTLS: unknown command */
-		if ((numeric == 451) && (parc > 2) && strstr(parv[1], "STARTTLS"))
-		{
-			if (client->server->conf && (client->server->conf->outgoing.options & CONNECT_INSECURE))
-				start_server_handshake(client);
-			else
-				reject_insecure_server(client);
-			return 0;
-		}
-
-		/* STARTTLS failed */
-		if (numeric == 691)
-		{
-			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;
-		}
-
-		/* STARTTLS OK */
-		if (numeric == 670)
-		{
-			int ret = client_starttls(client);
-			if (ret < 0)
-			{
-				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;
-			}
-			/* We don't call start_server_handshake() here. First the TLS handshake will
-			 * be completed, then completed_connection() will be called for a second time,
-			 * which will call completed_connection() from there.
-			 */
-			return 0;
-		}
-	}
-
-	/* Other than the (strange) code from above, we actually
-	 * don't process numerics from non-servers. So return here.
-	 */
-	if ((parc < 2) || BadPtr(parv[1]) || !IsServer(client))
-		return 0;
-
-	/* Remap low number numerics. */
-	if (numeric < 100)
-		numeric += 100;
-
-	/* Convert parv[] back to a string 'buffer', since that is
-	 * what we use in the sendto_* functions below.
-	 */
-	concat_params(buffer, sizeof(buffer), parc, parv);
-
-	/* Now actually process the numeric, IOTW: send it on */
-	strlcpy(targets, parv[1], sizeof(targets));
-	for (nick = strtoken(&p, targets, ","); nick; nick = strtoken(&p, NULL, ","))
-	{
-		if ((acptr = find_client(nick, NULL)))
-		{
-			if (!IsMe(acptr) && IsUser(acptr))
-			{
-				if (MyConnect(acptr) && isdigit(*nick))
-				{
-					/* Hack to prevent leaking UID */
-					char *skip = strchr(buffer, ' ');
-					if (skip)
-					{
-						sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s %s",
-						    client->name, numeric, acptr->name, skip+1);
-					} /* else.. malformed (no content) */
-				} else {
-					sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
-					    client->name, numeric, buffer);
-				}
-			}
-			else if (IsServer(acptr) && acptr->direction != client->direction)
-				sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
-				    client->name, numeric, buffer);
-		}
-		else if ((acptr = find_server_quick(nick)))
-		{
-			if (!IsMe(acptr) && acptr->direction != client->direction)
-				sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
-				    client->name, numeric, buffer);
-		}
-		else if ((channel = find_channel(nick)))
-		{
-			sendto_channel(channel, client, client->direction,
-			               0, 0, SEND_ALL, recv_mtags,
-			               ":%s %d %s", client->name, numeric, buffer);
-		}
-	}
-
-	return 0;
-}
-
-// FIXME: aren't we exiting the wrong client?
-static void cancel_clients(Client *cptr, Client *client, char *cmd)
-{
-	if (IsServer(cptr) || IsServer(client) || IsMe(client))
-		return;
-	exit_client(cptr, NULL, "Fake prefix");
-}
-
-static void remove_unknown(Client *client, char *sender)
-{
-	if (!IsRegistered(client) || IsUser(client))
-		return;
-	/*
-	 * Not from a server so don't need to worry about it.
-	 */
-	if (!IsServer(client))
-		return;
-
-	/*
-	 * 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
-	 */
-	if ((isdigit(*sender) && strlen(sender) <= SIDLEN) || strchr(sender, '.'))
-		sendto_one(client, NULL, ":%s SQUIT %s :Unknown prefix (%s) from %s",
-		    me.id, sender, sender, client->name);
-	else
-		sendto_one(client, NULL, ":%s KILL %s :Ghost user", me.id, sender);
-}
diff --git a/src/proc_io_client.c b/src/proc_io_client.c
@@ -1,199 +0,0 @@
-/************************************************************************
- *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_client.c
- *   (c) 2022- Bram Matthys and The UnrealIRCd team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers. 
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/** @file
- * @brief Inter-process I/O
- */
-#include "unrealircd.h"
-
-int procio_client_connect(const char *file)
-{
-	int fd;
-	struct sockaddr_un addr;
-
-	fd = socket(AF_UNIX, SOCK_STREAM, 0);
-	if (fd < 0)
-	{
-#ifdef _WIN32
-		fprintf(stderr, "Your Windows version does not support UNIX sockets, "
-		                "so cannot communicate to UnrealIRCd.\n"
-		                "Windows 10 version 1803 (April 2018) or later is needed.\n");
-#else
-		fprintf(stderr, "Cannot communicate to UnrealIRCd: %s\n"
-		                "Perhaps your operating system does not support UNIX Sockets?\n",
-				strerror(ERRNO));
-#endif
-		return -1;
-	}
-
-	memset(&addr, 0, sizeof(addr));
-	addr.sun_family = AF_UNIX;
-	strlcpy(addr.sun_path, file, sizeof(addr.sun_path));
-
-	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
-	{
-		fprintf(stderr, "Could not connect to '%s': %s\n",
-			CONTROLFILE, strerror(errno));
-		fprintf(stderr, "The IRC server does not appear to be running.\n");
-		close(fd);
-		return -1;
-	}
-
-	return fd;
-}
-
-int procio_send(int fd, const char *command)
-{
-	char buf[512];
-	int n;
-	snprintf(buf, sizeof(buf), "%s\r\n", command);
-	n = strlen(buf);
-	if (send(fd, buf, n, 0) != n)
-		return 0;
-	return 1;
-}
-
-const char *recolor_logs(const char *str)
-{
-	static char retbuf[2048];
-	char buf[2048], *p;
-	const char *color = NULL;
-
-	strlcpy(buf, str, sizeof(buf));
-	p = strchr(buf, ' ');
-	if ((*str != '[') || !p)
-		return str;
-	*p++ = '\0';
-
-	if (!strcmp(buf, "[debug]"))
-		color = log_level_terminal_color(ULOG_DEBUG);
-	else if (!strcmp(buf, "[info]"))
-		color = log_level_terminal_color(ULOG_INFO);
-	else if (!strcmp(buf, "[warning]"))
-		color = log_level_terminal_color(ULOG_WARNING);
-	else if (!strcmp(buf, "[error]"))
-		color = log_level_terminal_color(ULOG_ERROR);
-	else if (!strcmp(buf, "[fatal]"))
-		color = log_level_terminal_color(ULOG_FATAL);
-	else
-		color = log_level_terminal_color(ULOG_INVALID);
-
-	snprintf(retbuf, sizeof(retbuf), "%s%s%s %s",
-	         color, buf, TERMINAL_COLOR_RESET, p);
-	return retbuf;
-}
-
-const char *recolor_split(const char *str)
-{
-	static char retbuf[2048];
-	char buf[2048], *p;
-	const char *color = NULL;
-
-	strlcpy(buf, str, sizeof(buf));
-	p = strchr(buf, ' ');
-	if (!p)
-		return str;
-	*p++ = '\0';
-
-	snprintf(retbuf, sizeof(retbuf), "%s%s %s%s%s",
-	         "\033[92m", buf,
-	         "\033[93m", p,
-	         TERMINAL_COLOR_RESET);
-	return retbuf;
-}
-
-int procio_client(const char *command, int auto_color_logs)
-{
-	int fd;
-	char buf[READBUFSIZE];
-	int n;
-	dbuf queue;
-
-	if (auto_color_logs && !terminal_supports_color())
-		auto_color_logs = 0;
-
-	fd = procio_client_connect(CONTROLFILE);
-	if (fd < 0)
-		return -1;
-
-	/* Expect the welcome message */
-	memset(buf, 0, sizeof(buf));
-	n = recv(fd, buf, sizeof(buf), 0);
-	if ((n < 0) || strncmp(buf, "READY", 4))
-	{
-		fprintf(stderr, "Error while communicating to IRCd via '%s': %s\n"
-		                "Maybe the IRC server is not running?\n",
-		                CONTROLFILE, strerror(errno));
-		close(fd);
-		return -1;
-	}
-
-	if (!procio_send(fd, command))
-	{
-		fprintf(stderr, "Error while sending command to IRCd via '%s'. Strange!\n",
-		                CONTROLFILE);
-		close(fd);
-		return -1;
-	}
-
-	*buf = '\0';
-	dbuf_queue_init(&queue);
-	while(1)
-	{
-		n = recv(fd, buf, sizeof(buf)-1, 0);
-		if (n <= 0)
-			break;
-		buf[n] = '\0'; /* terminate the string */
-		dbuf_put(&queue, buf, n);
-
-		/* And try to read all complete lines: */
-		do
-		{
-			n = dbuf_getmsg(&queue, buf);
-			if (n > 0)
-			{
-				if (!strncmp(buf, "REPLY ", 6))
-				{
-					char *reply = buf+6;
-					if (auto_color_logs == 0)
-						printf("%s\n", reply);
-					else if (auto_color_logs == 1)
-						printf("%s\n", recolor_logs(reply));
-					else
-						printf("%s\n", recolor_split(reply));
-				} else
-				if (!strncmp(buf, "END ", 4))
-				{
-					int exitcode = atoi(buf+4);
-					close(fd);
-					return exitcode;
-				}
-			}
-		} while(n > 0);
-	}
-
-	/* IRCd hung up without saying goodbye, possibly problematic,
-	 * or at least we cannot determine, so exit with status 66.
-	 */
-	close(fd);
-	return 66;
-}
diff --git a/src/proc_io_server.c b/src/proc_io_server.c
@@ -1,191 +0,0 @@
-/************************************************************************
- *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_server.c
- *   (c) 2022- Bram Matthys and The UnrealIRCd team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers. 
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/** @file
- * @brief Inter-process I/O
- */
-#include "unrealircd.h"
-#include <ares.h>
-
-/* Forward declarations */
-CMD_FUNC(procio_status);
-CMD_FUNC(procio_modules);
-CMD_FUNC(procio_rehash);
-CMD_FUNC(procio_exit);
-CMD_FUNC(procio_help);
-void start_of_control_client_handshake(Client *client);
-int procio_accept(Client *client);
-
-/** Create the unrealircd.ctl socket (server-side) */
-void add_proc_io_server(void)
-{
-	ConfigItem_listen *listener;
-
-#ifdef _WIN32
-	/* Ignore silently on Windows versions older than W10 build 17061 */
-	if (!unix_sockets_capable())
-		return;
-#endif
-	listener = safe_alloc(sizeof(ConfigItem_listen));
-	safe_strdup(listener->file, CONTROLFILE);
-	listener->socket_type = SOCKET_TYPE_UNIX;
-	listener->options = LISTENER_CONTROL|LISTENER_NO_CHECK_CONNECT_FLOOD|LISTENER_NO_CHECK_ZLINED;
-	listener->start_handshake = start_of_control_client_handshake;
-	listener->fd = -1;
-	AddListItem(listener, conf_listen);
-	if (add_listener(listener) == -1)
-		exit(-1);
-	CommandAdd(NULL, "STATUS", procio_status, MAXPARA, CMD_CONTROL);
-	CommandAdd(NULL, "MODULES", procio_modules, MAXPARA, CMD_CONTROL);
-	CommandAdd(NULL, "REHASH", procio_rehash, MAXPARA, CMD_CONTROL);
-	CommandAdd(NULL, "EXIT", procio_exit, MAXPARA, CMD_CONTROL);
-	CommandAdd(NULL, "HELP", procio_help, MAXPARA, CMD_CONTROL);
-	HookAdd(NULL, HOOKTYPE_ACCEPT, -1000000, procio_accept);
-}
-
-int procio_accept(Client *client)
-{
-	if (client->local->listener->options & LISTENER_CONTROL)
-	{
-		irccounts.unknown--;
-		client->status = CLIENT_STATUS_CONTROL;
-		list_del(&client->lclient_node);
-		list_add(&client->lclient_node, &control_list);
-	}
-	return 0;
-}
-
-/** Start of "control channel" client handshake - this is minimal
- * @param client	The client
- */
-void start_of_control_client_handshake(Client *client)
-{
-	sendto_one(client, NULL, "READY %s %s", me.name, version);
-	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
-}
-
-CMD_FUNC(procio_status)
-{
-	sendto_one(client, NULL, "REPLY servername %s", me.name);
-	sendto_one(client, NULL, "REPLY unrealircd_version %s", version);
-	sendto_one(client, NULL, "REPLY libssl_version %s", SSLeay_version(SSLEAY_VERSION));
-	sendto_one(client, NULL, "REPLY libsodium_version %s", sodium_version_string());
-#ifdef USE_LIBCURL
-	sendto_one(client, NULL, "REPLY libcurl_version %s", curl_version());
-#endif
-	sendto_one(client, NULL, "REPLY libcares_version %s", ares_version(NULL));
-	sendto_one(client, NULL, "REPLY libpcre2_version %s", pcre2_version());
-#if JANSSON_VERSION_HEX >= 0x020D00
-	sendto_one(client, NULL, "REPLY libjansson %s\n", jansson_version_str());
-#endif
-	sendto_one(client, NULL, "REPLY global_clients %ld", (long)irccounts.clients);
-	sendto_one(client, NULL, "REPLY local_clients %ld", (long)irccounts.me_clients);
-	sendto_one(client, NULL, "REPLY operators %ld", (long)irccounts.operators);
-	sendto_one(client, NULL, "REPLY servers %ld", (long)irccounts.servers);
-	sendto_one(client, NULL, "REPLY channels %ld", (long)irccounts.channels);
-	sendto_one(client, NULL, "END 0");
-}
-
-extern MODVAR Module *Modules;
-CMD_FUNC(procio_modules)
-{
-	char tmp[1024];
-	Module *m;
-
-	for (m = Modules; m; m = m->next)
-	{
-		tmp[0] = '\0';
-		if (m->flags & MODFLAG_DELAYED)
-			strlcat(tmp, "[Unloading] ", sizeof(tmp));
-		if (m->options & MOD_OPT_PERM_RELOADABLE)
-			strlcat(tmp, "[PERM-BUT-RELOADABLE] ", sizeof(tmp));
-		if (m->options & MOD_OPT_PERM)
-			strlcat(tmp, "[PERM] ", sizeof(tmp));
-		if (!(m->options & MOD_OPT_OFFICIAL))
-			strlcat(tmp, "[3RD] ", sizeof(tmp));
-		sendto_one(client, NULL, "REPLY %s %s - %s - by %s %s",
-		           m->header->name,
-		           m->header->version,
-		           m->header->description,
-		           m->header->author,
-		           tmp);
-	}
-	sendto_one(client, NULL, "END 0");
-}
-
-CMD_FUNC(procio_rehash)
-{
-	if (loop.rehashing)
-	{
-		sendto_one(client, NULL, "REPLY ERROR: A rehash is already in progress");
-		sendto_one(client, NULL, "END 1");
-		return;
-	}
-	
-
-	if (parv[1] && !strcmp(parv[1], "-tls"))
-	{
-		int ret;
-		SetMonitorRehash(client);
-		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", NULL, "Reloading all TLS related data (./unrealircd reloadtls)");
-		ret = reinit_tls();
-		sendto_one(client, NULL, "END %d", ret == 0 ? -1 : 0);
-		ClearMonitorRehash(client);
-	} else {
-		SetMonitorRehash(client);
-		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]");
-		request_rehash(client);
-		/* completion will go via procio_post_rehash() */
-	}
-}
-
-CMD_FUNC(procio_exit)
-{
-	sendto_one(client, NULL, "END 0");
-	exit_client(client, NULL, "");
-}
-
-CMD_FUNC(procio_help)
-{
-	sendto_one(client, NULL, "REPLY Commands available:");
-	sendto_one(client, NULL, "REPLY EXIT");
-	sendto_one(client, NULL, "REPLY HELP");
-	sendto_one(client, NULL, "REPLY REHASH");
-	sendto_one(client, NULL, "REPLY STATUS");
-	sendto_one(client, NULL, "REPLY MODULES");
-	sendto_one(client, NULL, "END 0");
-}
-
-/** Called upon REHASH completion (with or without failure) */
-void procio_post_rehash(int failure)
-{
-	Client *client;
-
-	list_for_each_entry(client, &control_list, lclient_node)
-	{
-		if (IsMonitorRehash(client))
-		{
-			sendto_one(client, NULL, "END %d", failure);
-			ClearMonitorRehash(client);
-		}
-	}
-}
diff --git a/src/random.c b/src/random.c
@@ -1,520 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, random.c
- *   (C) 2004-2019 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.
- *
- */
-
-#include "unrealircd.h"
-
-#ifndef HAVE_ARC4RANDOM
-
-#define KEYSTREAM_ONLY
-
-/* chacha_private.h from openssh/openssh-portable
- * "chacha-merged.c version 20080118
- *  D. J. Bernstein
- *  Public domain."
- */
-
-/* $OpenBSD: chacha_private.h,v 1.2 2013/10/04 07:02:27 djm Exp $ */
-
-typedef unsigned char u8;
-typedef unsigned int u32;
-
-typedef struct
-{
-  u32 input[16]; /* could be compressed */
-} chacha_ctx;
-
-#define U8C(v) (v##U)
-#define U32C(v) (v##U)
-
-#define U8V(v) ((u8)(v) & U8C(0xFF))
-#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
-
-#define ROTL32(v, n) \
-  (U32V((v) << (n)) | ((v) >> (32 - (n))))
-
-#define U8TO32_LITTLE(p) \
-  (((u32)((p)[0])      ) | \
-   ((u32)((p)[1]) <<  8) | \
-   ((u32)((p)[2]) << 16) | \
-   ((u32)((p)[3]) << 24))
-
-#define U32TO8_LITTLE(p, v) \
-  do { \
-    (p)[0] = U8V((v)      ); \
-    (p)[1] = U8V((v) >>  8); \
-    (p)[2] = U8V((v) >> 16); \
-    (p)[3] = U8V((v) >> 24); \
-  } while (0)
-
-#define ROTATE(v,c) (ROTL32(v,c))
-#define XOR(v,w) ((v) ^ (w))
-#define PLUS(v,w) (U32V((v) + (w)))
-#define PLUSONE(v) (PLUS((v),1))
-
-#define QUARTERROUND(a,b,c,d) \
-  a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
-  c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
-  a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
-  c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
-
-static const char sigma[16] = "expand 32-byte k";
-static const char tau[16] = "expand 16-byte k";
-
-static void
-chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits)
-{
-  const char *constants;
-
-  x->input[4] = U8TO32_LITTLE(k + 0);
-  x->input[5] = U8TO32_LITTLE(k + 4);
-  x->input[6] = U8TO32_LITTLE(k + 8);
-  x->input[7] = U8TO32_LITTLE(k + 12);
-  if (kbits == 256) { /* recommended */
-    k += 16;
-    constants = sigma;
-  } else { /* kbits == 128 */
-    constants = tau;
-  }
-  x->input[8] = U8TO32_LITTLE(k + 0);
-  x->input[9] = U8TO32_LITTLE(k + 4);
-  x->input[10] = U8TO32_LITTLE(k + 8);
-  x->input[11] = U8TO32_LITTLE(k + 12);
-  x->input[0] = U8TO32_LITTLE(constants + 0);
-  x->input[1] = U8TO32_LITTLE(constants + 4);
-  x->input[2] = U8TO32_LITTLE(constants + 8);
-  x->input[3] = U8TO32_LITTLE(constants + 12);
-}
-
-static void
-chacha_ivsetup(chacha_ctx *x,const u8 *iv)
-{
-  x->input[12] = 0;
-  x->input[13] = 0;
-  x->input[14] = U8TO32_LITTLE(iv + 0);
-  x->input[15] = U8TO32_LITTLE(iv + 4);
-}
-
-static void
-chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
-{
-  u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
-  u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
-  u8 *ctarget = NULL;
-  u8 tmp[64];
-  u_int i;
-
-  if (!bytes) return;
-
-  j0 = x->input[0];
-  j1 = x->input[1];
-  j2 = x->input[2];
-  j3 = x->input[3];
-  j4 = x->input[4];
-  j5 = x->input[5];
-  j6 = x->input[6];
-  j7 = x->input[7];
-  j8 = x->input[8];
-  j9 = x->input[9];
-  j10 = x->input[10];
-  j11 = x->input[11];
-  j12 = x->input[12];
-  j13 = x->input[13];
-  j14 = x->input[14];
-  j15 = x->input[15];
-
-  for (;;) {
-    if (bytes < 64) {
-      for (i = 0;i < bytes;++i) tmp[i] = m[i];
-      m = tmp;
-      ctarget = c;
-      c = tmp;
-    }
-    x0 = j0;
-    x1 = j1;
-    x2 = j2;
-    x3 = j3;
-    x4 = j4;
-    x5 = j5;
-    x6 = j6;
-    x7 = j7;
-    x8 = j8;
-    x9 = j9;
-    x10 = j10;
-    x11 = j11;
-    x12 = j12;
-    x13 = j13;
-    x14 = j14;
-    x15 = j15;
-    for (i = 20;i > 0;i -= 2) {
-      QUARTERROUND( x0, x4, x8,x12)
-      QUARTERROUND( x1, x5, x9,x13)
-      QUARTERROUND( x2, x6,x10,x14)
-      QUARTERROUND( x3, x7,x11,x15)
-      QUARTERROUND( x0, x5,x10,x15)
-      QUARTERROUND( x1, x6,x11,x12)
-      QUARTERROUND( x2, x7, x8,x13)
-      QUARTERROUND( x3, x4, x9,x14)
-    }
-    x0 = PLUS(x0,j0);
-    x1 = PLUS(x1,j1);
-    x2 = PLUS(x2,j2);
-    x3 = PLUS(x3,j3);
-    x4 = PLUS(x4,j4);
-    x5 = PLUS(x5,j5);
-    x6 = PLUS(x6,j6);
-    x7 = PLUS(x7,j7);
-    x8 = PLUS(x8,j8);
-    x9 = PLUS(x9,j9);
-    x10 = PLUS(x10,j10);
-    x11 = PLUS(x11,j11);
-    x12 = PLUS(x12,j12);
-    x13 = PLUS(x13,j13);
-    x14 = PLUS(x14,j14);
-    x15 = PLUS(x15,j15);
-
-#ifndef KEYSTREAM_ONLY
-    x0 = XOR(x0,U8TO32_LITTLE(m + 0));
-    x1 = XOR(x1,U8TO32_LITTLE(m + 4));
-    x2 = XOR(x2,U8TO32_LITTLE(m + 8));
-    x3 = XOR(x3,U8TO32_LITTLE(m + 12));
-    x4 = XOR(x4,U8TO32_LITTLE(m + 16));
-    x5 = XOR(x5,U8TO32_LITTLE(m + 20));
-    x6 = XOR(x6,U8TO32_LITTLE(m + 24));
-    x7 = XOR(x7,U8TO32_LITTLE(m + 28));
-    x8 = XOR(x8,U8TO32_LITTLE(m + 32));
-    x9 = XOR(x9,U8TO32_LITTLE(m + 36));
-    x10 = XOR(x10,U8TO32_LITTLE(m + 40));
-    x11 = XOR(x11,U8TO32_LITTLE(m + 44));
-    x12 = XOR(x12,U8TO32_LITTLE(m + 48));
-    x13 = XOR(x13,U8TO32_LITTLE(m + 52));
-    x14 = XOR(x14,U8TO32_LITTLE(m + 56));
-    x15 = XOR(x15,U8TO32_LITTLE(m + 60));
-#endif
-
-    j12 = PLUSONE(j12);
-    if (!j12) {
-      j13 = PLUSONE(j13);
-      /* stopping at 2^70 bytes per nonce is user's responsibility */
-    }
-
-    U32TO8_LITTLE(c + 0,x0);
-    U32TO8_LITTLE(c + 4,x1);
-    U32TO8_LITTLE(c + 8,x2);
-    U32TO8_LITTLE(c + 12,x3);
-    U32TO8_LITTLE(c + 16,x4);
-    U32TO8_LITTLE(c + 20,x5);
-    U32TO8_LITTLE(c + 24,x6);
-    U32TO8_LITTLE(c + 28,x7);
-    U32TO8_LITTLE(c + 32,x8);
-    U32TO8_LITTLE(c + 36,x9);
-    U32TO8_LITTLE(c + 40,x10);
-    U32TO8_LITTLE(c + 44,x11);
-    U32TO8_LITTLE(c + 48,x12);
-    U32TO8_LITTLE(c + 52,x13);
-    U32TO8_LITTLE(c + 56,x14);
-    U32TO8_LITTLE(c + 60,x15);
-
-    if (bytes <= 64) {
-      if (bytes < 64) {
-        for (i = 0;i < bytes;++i) ctarget[i] = c[i];
-      }
-      x->input[12] = j12;
-      x->input[13] = j13;
-      return;
-    }
-    bytes -= 64;
-    c += 64;
-#ifndef KEYSTREAM_ONLY
-    m += 64;
-#endif
-  }
-}
-
-/* The following is taken from arc4random.c
- * from openssh/openssh-portable/, which has an ISC license,
- * which is GPL compatible.
- */
-
-/* OPENBSD ORIGINAL: lib/libc/crypto/arc4random.c */
-/* $OpenBSD: arc4random.c,v 1.25 2013/10/01 18:34:57 markus Exp $	*/
-/*
- * Copyright (c) 1996, David Mazieres <dm@uun.org>
- * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
- * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-/* Modified for UnrealIRCd by Bram Matthys ("Syzop") in 2019.
- * Things like taking out #if (n)def's for openssl (which we always
- * compile with), re-indenting, removing various stuff, etc.
- */
-
-
-
-#define KEYSZ	32
-#define IVSZ	8
-#define BLOCKSZ	64
-#define RSBUFSZ	(16*BLOCKSZ)
-static int rs_initialized;
-static chacha_ctx rs;		/* chacha context for random keystream */
-static u_char rs_buf[RSBUFSZ];	/* keystream blocks */
-static size_t rs_have;		/* valid bytes at end of rs_buf */
-static size_t rs_count;		/* bytes till reseed */
-
-static inline void _rs_rekey(u_char *dat, size_t datlen);
-
-static inline void _rs_init(u_char *buf, size_t n)
-{
-	if (n < KEYSZ + IVSZ)
-		return;
-	chacha_keysetup(&rs, buf, KEYSZ * 8, 0);
-	chacha_ivsetup(&rs, buf + KEYSZ);
-}
-
-static void _rs_stir(void)
-{
-	u_char rnd[KEYSZ + IVSZ];
-
-	if (RAND_bytes(rnd, sizeof(rnd)) <= 0)
-	{
-		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();
-	}
-
-	if (!rs_initialized) {
-		rs_initialized = 1;
-		_rs_init(rnd, sizeof(rnd));
-	} else
-		_rs_rekey(rnd, sizeof(rnd));
-#ifdef HAVE_EXPLICIT_BZERO
-	explicit_bzero(rnd, sizeof(rnd));
-#else
-	/* Not terribly important in this case */
-	memset(rnd, 0, sizeof(rnd));
-#endif
-	/* invalidate rs_buf */
-	rs_have = 0;
-	memset(rs_buf, 0, RSBUFSZ);
-
-	rs_count = 1600000;
-}
-
-static inline void _rs_stir_if_needed(size_t len)
-{
-	if (rs_count <= len || !rs_initialized) {
-		_rs_stir();
-	} else
-		rs_count -= len;
-}
-
-static inline void _rs_rekey(u_char *dat, size_t datlen)
-{
-#ifndef KEYSTREAM_ONLY
-	memset(rs_buf, 0,RSBUFSZ);
-#endif
-	/* fill rs_buf with the keystream */
-	chacha_encrypt_bytes(&rs, rs_buf, rs_buf, RSBUFSZ);
-	/* mix in optional user provided data */
-	if (dat) {
-		size_t i, m;
-
-		m = MIN(datlen, KEYSZ + IVSZ);
-		for (i = 0; i < m; i++)
-			rs_buf[i] ^= dat[i];
-	}
-	/* immediately reinit for backtracking resistance */
-	_rs_init(rs_buf, KEYSZ + IVSZ);
-	memset(rs_buf, 0, KEYSZ + IVSZ);
-	rs_have = RSBUFSZ - KEYSZ - IVSZ;
-}
-
-static inline void _rs_random_buf(void *_buf, size_t n)
-{
-	u_char *buf = (u_char *)_buf;
-	size_t m;
-
-	_rs_stir_if_needed(n);
-	while (n > 0) {
-		if (rs_have > 0) {
-			m = MIN(n, rs_have);
-			memcpy(buf, rs_buf + RSBUFSZ - rs_have, m);
-			memset(rs_buf + RSBUFSZ - rs_have, 0, m);
-			buf += m;
-			n -= m;
-			rs_have -= m;
-		}
-		if (rs_have == 0)
-			_rs_rekey(NULL, 0);
-	}
-}
-
-static inline void _rs_random_u32(uint32_t *val)
-{
-	_rs_stir_if_needed(sizeof(*val));
-	if (rs_have < sizeof(*val))
-		_rs_rekey(NULL, 0);
-	memcpy(val, rs_buf + RSBUFSZ - rs_have, sizeof(*val));
-	memset(rs_buf + RSBUFSZ - rs_have, 0, sizeof(*val));
-	rs_have -= sizeof(*val);
-	return;
-}
-
-void arc4random_stir(void)
-{
-	_rs_stir();
-}
-
-void arc4random_doaddrandom(u_char *dat, int datlen)
-{
-	int m;
-
-	if (!rs_initialized)
-		_rs_stir();
-	while (datlen > 0) {
-		m = MIN(datlen, KEYSZ + IVSZ);
-		_rs_rekey(dat, m);
-		dat += m;
-		datlen -= m;
-	}
-}
-
-#endif /* !HAVE_ARC4RANDOM */
-
-/* UnrealIRCd-specific functions follow */
-
-static void arc4_addrandom(void *dat, int datlen)
-{
-	arc4random_doaddrandom((unsigned char *)dat, datlen);
-	return;
-}
-
-void add_entropy_configfile(struct stat *st, char *buf)
-{
-	char sha256buf[SHA256_DIGEST_LENGTH];
-
-	arc4_addrandom(&st->st_size, sizeof(st->st_size));
-	arc4_addrandom(&st->st_mtime, sizeof(st->st_mtime));
-	sha256hash_binary(sha256buf, buf, strlen(buf));
-	arc4_addrandom(sha256buf, sizeof(sha256buf));
-}
-
-/*
- * init_random, written by Syzop.
- * This function tries to initialize the arc4 random number generator securely.
- */
-void init_random()
-{
-	struct {
-#ifndef _WIN32
-		struct timeval nowt;		/* time */
-		char rnd[32];			/* /dev/urandom */
-#else
-		struct _timeb nowt;		/* time */
-		MEMORYSTATUS mstat;		/* memory status */
-#endif
-	} rdat;
-
-#ifndef _WIN32
-	int fd;
-#endif
-
-	_rs_stir();
-
-	/* Grab OS specific "random" data */
-#ifndef _WIN32
-	gettimeofday(&rdat.nowt, NULL);
-	fd = open("/dev/urandom", O_RDONLY);
-	if (fd >= 0)
-	{
-		int n = read(fd, &rdat.rnd, sizeof(rdat.rnd));
-		close(fd);
-	}
-#else
-	_ftime(&rdat.nowt);
-	GlobalMemoryStatus (&rdat.mstat);
-#endif	
-
-	arc4_addrandom(&rdat, sizeof(rdat));
-
-	/* NOTE: addtional entropy is added by add_entropy_* function(s) */
-}
-
-uint32_t arc4random(void)
-{
-	uint32_t val;
-
-	_rs_random_u32(&val);
-	return val;
-}
-
-/** Get 8 bits (1 byte) of randomness */
-u_char getrandom8()
-{
-	return arc4random() & 0xff;
-}
-
-/** Get 16 bits (2 bytes) of randomness */
-uint16_t getrandom16()
-{
-	return arc4random() & 0xffff;
-}
-
-/** Get 32 bits (4 bytes) of randomness */
-uint32_t getrandom32()
-{
-	return arc4random();
-}
-
-/** Generate an alphanumeric string (eg: XzHe5G).
- * @param buf      The buffer
- * @param numbytes The number of random bytes.
- * @note  Note that numbytes+1 bytes are written to buf.
- *        In other words, numbytes does NOT include the NUL byte.
- */
-void gen_random_alnum(char *buf, int numbytes)
-{
-	for (; numbytes > 0; numbytes--)
-	{
-		unsigned char v = getrandom8() % (26+26+10);
-		if (v < 26)
-			*buf++ = 'a'+v;
-		else if (v < 52)
-			*buf++ = 'A'+(v-26);
-		else
-			*buf++ = '0'+(v-52);
-	}
-	*buf = '\0';
-}
diff --git a/src/scache.c b/src/scache.c
@@ -1,99 +0,0 @@
-/* License: GPLv1 */
-
-/** @file
- * @brief String cache - only used for server names.
- */
-
-#include "unrealircd.h"
-
-/*
- * ircd used to store full servernames in User as well as in the
- * whowas info.  there can be some 40k such structures alive at any
- * given time, while the number of unique server names a server sees in
- * its lifetime is at most a few hundred.  by tokenizing server names
- * internally, the server can easily save 2 or 3 megs of RAM.
- * -orabidoo
- */
-/*
- * I could have tucked this code into hash.c I suppose but lets keep it
- * separate for now -Dianora
- */
-
-#define SCACHE_HASH_SIZE 257
-
-typedef struct SCACHE SCACHE;
-struct SCACHE {
-	char name[HOSTLEN + 1];
-	SCACHE *next;
-};
-
-static SCACHE *scache_hash[SCACHE_HASH_SIZE];
-
-/*
- * renamed to keep it consistent with the other hash functions -Dianora 
- */
-/*
- * orabidoo had named it init_scache_hash(); 
- */
-
-void clear_scache_hash_table(void)
-{
-	memset((char *)scache_hash, '\0', sizeof(scache_hash));
-}
-
-static int hash(char *string)
-{
-	int  hash_value;
-
-	hash_value = 0;
-	while (*string)
-		hash_value += (*string++ & 0xDF);
-
-	return hash_value % SCACHE_HASH_SIZE;
-}
-
-/** Add a string to the string cache.
- * this takes a server name, and returns a pointer to the same string
- * (up to case) in the server name token list, adding it to the list if
- * it's not there.  care must be taken not to call this with
- * user-supplied arguments that haven't been verified to be a valid,
- * existing, servername.  use the hash in list.c for those.  -orabidoo
- * @param name	A valid server name
- * @returns Pointer to the server name
- */
-char *find_or_add(char *name)
-{
-	int  hash_index;
-	SCACHE *ptr, *newptr;
-
-	ptr = scache_hash[hash_index = hash(name)];
-	while (ptr)
-	{
-		if (!mycmp(ptr->name, name))
-		{
-			return (ptr->name);
-		}
-		else
-		{
-			ptr = ptr->next;
-		}
-	}
-
-	/*
-	 * not found -- add it 
-	 */
-	if ((ptr = scache_hash[hash_index]))
-	{
-		newptr = scache_hash[hash_index] = safe_alloc(sizeof(SCACHE));
-		strlcpy(newptr->name, name, sizeof(newptr->name));
-		newptr->next = ptr;
-		return (newptr->name);
-	}
-	else
-	{
-		ptr = scache_hash[hash_index] = safe_alloc(sizeof(SCACHE));
-		strlcpy(ptr->name, name, sizeof(newptr->name));
-		ptr->next = (SCACHE *) NULL;
-		return (ptr->name);
-	}
-}
diff --git a/src/securitygroup.c b/src/securitygroup.c
@@ -1,864 +0,0 @@
-/*
- * Mask & security-group routines.
- * (C) Copyright 2015-.. 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 2
- * of the License, 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.
- */
-
-#include "unrealircd.h"
-
-/* Global variables */
-SecurityGroup *securitygroups = NULL;
-
-/** Free all masks in the mask list */
-void unreal_delete_masks(ConfigItem_mask *m)
-{
-	ConfigItem_mask *m_next;
-
-	for (; m; m = m_next)
-	{
-		m_next = m->next;
-
-		safe_free(m->mask);
-
-		safe_free(m);
-	}
-}
-
-/** Internal function to add one individual mask to the list */
-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->value)
-		safe_strdup(m->mask, ce->value);
-	else
-		safe_strdup(m->mask, ce->name);
-
-	add_ListItem((ListStruct *)m, (ListStruct **)head);
-}
-
-/** Add mask entries from config */
-void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce)
-{
-	if (ce->items)
-	{
-		ConfigEntry *cep;
-		for (cep = ce->items; cep; cep = cep->next)
-			unreal_add_mask(head, cep);
-	} else
-	{
-		unreal_add_mask(head, ce);
-	}
-}
-
-/** 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)
-{
-	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_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
-			{
-				retval = 1;
-				break;
-			}
-		}
-	}
-
-	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;
-}
-
-#define CheckNullX(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); *errors = *errors + 1; return 0; }
-int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors)
-{
-	ConfigEntry *cepp;
-
-	if (!strcmp(cep->name, "webirc") || !strcmp(cep->name, "exclude-webirc"))
-	{
-		CheckNullX(cep);
-	} else
-	if (!strcmp(cep->name, "websocket") || !strcmp(cep->name, "exclude-websocket"))
-	{
-		CheckNullX(cep);
-	} else
-	if (!strcmp(cep->name, "identified") || !strcmp(cep->name, "exclude-identified"))
-	{
-		CheckNullX(cep);
-	} else
-	if (!strcmp(cep->name, "tls") || !strcmp(cep->name, "exclude-tls"))
-	{
-		CheckNullX(cep);
-	} else
-	if (!strcmp(cep->name, "reputation-score") || !strcmp(cep->name, "exclude-reputation-score"))
-	{
-		const char *str = cep->value;
-		int v;
-		CheckNullX(cep);
-		if (*str == '<')
-			str++;
-		v = atoi(str);
-		if ((v < 1) || (v > 10000))
-		{
-			config_error("%s:%i: %s needs to be a value of 1-10000",
-				cep->file->filename, cep->line_number, cep->name);
-			*errors = *errors + 1;
-		}
-	} else
-	if (!strcmp(cep->name, "connect-time") || !strcmp(cep->name, "exclude-connect-time"))
-	{
-		const char *str = cep->value;
-		long v;
-		CheckNullX(cep);
-		if (*str == '<')
-			str++;
-		v = config_checkval(str, CFG_TIME);
-		if (v < 1)
-		{
-			config_error("%s:%i: %s needs to be a time value (and more than 0 seconds)",
-				cep->file->filename, cep->line_number, cep->name);
-			*errors = *errors + 1;
-		}
-	} else
-	if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "exclude-mask"))
-	{
-		for (cepp = cep->items; cepp; cepp = cepp->next)
-		{
-			if (!strcmp(cepp->name, "mask"))
-				continue;
-			if (cepp->items || cepp->value)
-			{
-				config_error("%s:%i: security-group::mask should contain hostmasks only. "
-				             "Perhaps you meant to use it in security-group { %s ... } directly?",
-				             cepp->file->filename, cepp->line_number,
-				             cepp->name);
-				*errors = *errors + 1;
-			}
-		}
-	} else
-	if (!strcmp(cep->name, "ip"))
-	{
-	} else
-	if (!strcmp(cep->name, "security-group") || !strcmp(cep->name, "exclude-security-group"))
-	{
-	} else
-	{
-		/* Let's see if an extended server ban exists for this item... */
-		Extban *extban;
-		if (!strncmp(cep->name, "exclude-", 8))
-			extban = findmod_by_bantype_raw(cep->name+8, strlen(cep->name+8));
-		else
-			extban = findmod_by_bantype_raw(cep->name, strlen(cep->name));
-		if (extban && (extban->options & EXTBOPT_TKL) && (extban->is_banned_events & BANCHK_TKL))
-		{
-			test_extended_list(extban, cep, errors);
-			return 1; /* Yup, handled */
-		}
-		return 0; /* Unhandled: unknown item for us */
-	}
-	return 1; /* Handled, but there could be errors */
-}
-
-int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	/* (If there is only a ce->value, trust that it is OK) */
-
-	/* Test ce->items... */
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		/* Only complain about things with values,
-		 * as valueless things like "10.0.0.0/8" are treated as a mask.
-		 */
-		if (!test_match_item(conf, cep, &errors) && cep->value)
-		{
-			config_error_unknown(cep->file->filename, cep->line_number,
-				ce->name, cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	*errors_out = *errors_out + errors;
-	return errors ? 0 : 1;
-}
-
-#define tmbbw_is_wildcard(x)	(!strcmp(x, "*") || !strcmp(x, "*@*"))
-int test_match_block_too_broad(ConfigFile *conf, ConfigEntry *ce)
-{
-	ConfigEntry *cep, *cepp;
-
-	// match *;
-	if (ce->value && tmbbw_is_wildcard(ce->value))
-		return 1;
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		// match { *; }
-		if (!cep->value && tmbbw_is_wildcard(cep->name))
-			return 1;
-		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "ip"))
-		{
-			// match { mask *; }
-			if (cep->value && tmbbw_is_wildcard(cep->value))
-				return 1;
-			// match { mask { *; } }
-			for (cepp = cep->items; cepp; cepp = cepp->next)
-				if (tmbbw_is_wildcard(cepp->name))
-					return 1;
-		}
-	}
-
-	return 0;
-}
-
-int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
-{
-	int errors = 0;
-	ConfigEntry *cep;
-
-	/* First, check the name of the security group */
-	if (!ce->value)
-	{
-		config_error("%s:%i: security-group block needs a name, eg: security-group web-users {",
-			ce->file->filename, ce->line_number);
-		errors++;
-	} else {
-		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->file->filename, ce->line_number);
-			errors++;
-			return errors;
-		}
-		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->file->filename, ce->line_number, ce->value);
-			errors++;
-		}
-	}
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!test_match_item(conf, cep, &errors))
-		{
-			config_error_unknown(cep->file->filename, cep->line_number,
-				"security-group", cep->name);
-			errors++;
-			continue;
-		}
-	}
-
-	return errors;
-}
-
-int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
-{
-	int errors = 0; /* unused */
-	SecurityGroup *s = *block;
-
-	/* The following code is there so we don't create a security group
-	 * unless there is actually a valid config item for it encountered.
-	 * This so the security group '*s' can stay NULL if there are zero
-	 * items, so we don't waste any CPU if it is unused.
-	 */
-	if (*block == NULL)
-	{
-		/* Yeah we call a TEST routine from a CONFIG RUN routine ;). */
-		if (!test_match_item(conf, cep, &errors))
-			return 0; /* not for us */
-		/* If we are still here then we must create the security group */
-		*block = s = safe_alloc(sizeof(SecurityGroup));
-	}
-
-	if (!strcmp(cep->name, "webirc"))
-		s->webirc = config_checkval(cep->value, CFG_YESNO);
-	if (!strcmp(cep->name, "websocket"))
-		s->websocket = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "identified"))
-		s->identified = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "tls"))
-		s->tls = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "reputation-score"))
-	{
-		if (*cep->value == '<')
-			s->reputation_score = 0 - atoi(cep->value+1);
-		else
-			s->reputation_score = atoi(cep->value);
-	}
-	else if (!strcmp(cep->name, "connect-time"))
-	{
-		if (*cep->value == '<')
-			s->connect_time = 0 - config_checkval(cep->value+1, CFG_TIME);
-		else
-			s->connect_time = config_checkval(cep->value, CFG_TIME);
-	}
-	else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask"))
-	{
-		unreal_add_masks(&s->mask, cep);
-	}
-	else if (!strcmp(cep->name, "ip"))
-	{
-		unreal_add_names(&s->ip, cep);
-	}
-	else if (!strcmp(cep->name, "security-group"))
-	{
-		unreal_add_names(&s->security_group, cep);
-	}
-	else if (!strcmp(cep->name, "exclude-webirc"))
-		s->exclude_webirc = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "exclude-websocket"))
-		s->exclude_websocket = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "exclude-identified"))
-		s->exclude_identified = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "exclude-tls"))
-		s->exclude_tls = config_checkval(cep->value, CFG_YESNO);
-	else if (!strcmp(cep->name, "exclude-reputation-score"))
-	{
-		if (*cep->value == '<')
-			s->exclude_reputation_score = 0 - atoi(cep->value+1);
-		else
-			s->exclude_reputation_score = atoi(cep->value);
-	}
-	else if (!strcmp(cep->name, "exclude-mask"))
-	{
-		unreal_add_masks(&s->exclude_mask, cep);
-	}
-	else if (!strcmp(cep->name, "exclude-security-group"))
-	{
-		unreal_add_names(&s->security_group, cep);
-	}
-	else
-	{
-		/* Let's see if an extended server ban exists for this item... this needs to be LAST! */
-		Extban *extban;
-		const char *name = cep->name;
-
-		if (!strncmp(cep->name, "exclude-", 8))
-		{
-			/* Extended (exclusive) ? */
-			name = cep->name + 8;
-			if (findmod_by_bantype_raw(name, strlen(name)))
-				unreal_add_name_values(&s->exclude_extended, name, cep);
-			else
-				return 0; /* Unhandled */
-		} else {
-			/* Extended (inclusive) */
-			if (findmod_by_bantype_raw(name, strlen(name)))
-				unreal_add_name_values(&s->extended, name, cep);
-			else
-				return 0; /* Unhandled */
-		}
-	}
-
-	/* And update the printable list */
-	if (cep->items)
-	{
-		ConfigEntry *cep2;
-		for (cep2 = cep->items; cep2; cep2 = cep2->next)
-			add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep2->name);
-	} else {
-		add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value);
-	}
-
-	return 1; /* Handled by us (guaranteed earlier) */
-}
-
-int conf_match_block(ConfigFile *conf, ConfigEntry *ce, SecurityGroup **block)
-{
-	ConfigEntry *cep;
-	SecurityGroup *s = *block;
-
-	if (*block == NULL)
-		*block = s = safe_alloc(sizeof(SecurityGroup));
-
-	/* Check for simple form: match *; / mask *; */
-	if (ce->value)
-	{
-		unreal_add_masks(&s->mask, ce);
-		add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", ce->value);
-	}
-
-	/* Check for long form: match { .... } / mask { .... } */
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!conf_match_item(conf, cep, &s) && !cep->value && !cep->items)
-		{
-			/* Valueless? Then it must be a mask like 10.0.0.0/8 */
-			unreal_add_masks(&s->mask, cep);
-			add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", cep->name);
-		}
-	}
-	return 1;
-}
-
-int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
-{
-	ConfigEntry *cep;
-	SecurityGroup *s = add_security_group(ce->value, 1);
-
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "priority"))
-		{
-			s->priority = atoi(cep->value);
-			DelListItem(s, securitygroups);
-			AddListItemPrio(s, securitygroups, s->priority);
-		} else
-			conf_match_item(conf, cep, &s);
-	}
-	return 1;
-}
-
-/** Check if the name of the security-group contains only valid characters.
- * @param name	The name of the group
- * @returns 1 if name is valid, 0 if not (eg: illegal characters)
- */
-int security_group_valid_name(const char *name)
-{
-	const char *p;
-
-	if (strlen(name) > SECURITYGROUPLEN)
-		return 0; /* Too long */
-
-	for (p = name; *p; p++)
-	{
-		if (!isalnum(*p) && !strchr("_-", *p))
-			return 0; /* Character not allowed */
-	}
-	return 1;
-}
-
-/** Find a security-group.
- * @param name	The name of the security group
- * @returns A SecurityGroup struct, or NULL if not found.
- */
-SecurityGroup *find_security_group(const char *name)
-{
-	SecurityGroup *s;
-	for (s = securitygroups; s; s = s->next)
-		if (!strcasecmp(name, s->name))
-			return s;
-	return NULL;
-}
-
-/** Checks if a security-group exists.
- * This function takes the 'unknown-users' magic group into account as well.
- * @param name	The name of the security group
- * @returns 1 if it exists, 0 if not
- */
-int security_group_exists(const char *name)
-{
-	if (!strcmp(name, "unknown-users") || find_security_group(name))
-		return 1;
-	return 0;
-}
-
-/** Add a new security-group and add it to the list, but search for existing one first.
- * @param name	The name of the security group
- * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list)
- */
-SecurityGroup *add_security_group(const char *name, int priority)
-{
-	SecurityGroup *s = find_security_group(name);
-
-	/* Existing? */
-	if (s)
-		return s;
-
-	/* Otherwise, create a new entry */
-	s = safe_alloc(sizeof(SecurityGroup));
-	strlcpy(s->name, name, sizeof(s->name));
-	s->priority = priority;
-	AddListItemPrio(s, securitygroups, priority);
-	return s;
-}
-
-/** Free a SecurityGroup struct */
-void free_security_group(SecurityGroup *s)
-{
-	if (s == NULL)
-		return;
-	unreal_delete_masks(s->mask);
-	unreal_delete_masks(s->exclude_mask);
-	free_entire_name_list(s->security_group);
-	free_entire_name_list(s->exclude_security_group);
-	free_entire_name_list(s->ip);
-	free_entire_name_list(s->exclude_ip);
-	free_nvplist(s->extended);
-	free_nvplist(s->exclude_extended);
-	free_nvplist(s->printable_list);
-	safe_free(s);
-}
-
-/** Initialize the default security-group blocks */
-void set_security_group_defaults(void)
-{
-	SecurityGroup *s, *s_next;
-
-	/* First free all security groups */
-	for (s = securitygroups; s; s = s_next)
-	{
-		s_next = s->next;
-		free_security_group(s);
-	}
-	securitygroups = NULL;
-
-	/* Default group: webirc */
-	s = add_security_group("webirc-users", 50);
-	s->webirc = 1;
-
-	/* Default group: websocket */
-	s = add_security_group("websocket-users", 51);
-	s->websocket = 1;
-
-	/* Default group: known-users */
-	s = add_security_group("known-users", 100);
-	s->identified = 1;
-	s->reputation_score = 25;
-	s->webirc = 0;
-
-	/* Default group: tls-and-known-users */
-	s = add_security_group("tls-and-known-users", 200);
-	s->identified = 1;
-	s->reputation_score = 25;
-	s->webirc = 0;
-	s->tls = 1;
-
-	/* Default group: tls-users */
-	s = add_security_group("tls-users", 300);
-	s->tls = 1;
-}
-
-int user_matches_extended_list(Client *client, NameValuePrioList *e)
-{
-	Extban *extban;
-	BanContext b;
-
-	for (; e; e = e->next)
-	{
-		extban = findmod_by_bantype_raw(e->name, strlen(e->name));
-		if (!extban ||
-		    !(extban->options & EXTBOPT_TKL) ||
-		    !(extban->is_banned_events & BANCHK_TKL))
-		{
-			continue; /* extban not found or of incorrect type */
-		}
-
-		memset(&b, 0, sizeof(BanContext));
-		b.client = client;
-		b.banstr = e->value;
-		b.ban_check_types = BANCHK_TKL;
-		if (extban->is_banned(&b))
-			return 1;
-	}
-
-	return 0;
-}
-
-int test_extended_list(Extban *extban, ConfigEntry *cep, int *errors)
-{
-	BanContext b;
-
-	if (cep->value)
-	{
-		memset(&b, 0, sizeof(BanContext));
-		b.banstr = cep->value;
-		b.ban_check_types = BANCHK_TKL;
-		b.what = MODE_ADD;
-		if (!extban->conv_param(&b, extban))
-		{
-			config_error("%s:%i: %s has an invalid value",
-			             cep->file->filename, cep->line_number, cep->name);
-			*errors = *errors + 1;
-			return 0;
-		}
-	}
-
-	for (cep = cep->items; cep; cep = cep->next)
-	{
-		memset(&b, 0, sizeof(BanContext));
-		b.banstr = cep->name;
-		b.ban_check_types = BANCHK_TKL;
-		b.what = MODE_ADD;
-		if (!extban->conv_param(&b, extban))
-		{
-			config_error("%s:%i: %s has an invalid value",
-			             cep->file->filename, cep->line_number, cep->name);
-			*errors = *errors + 1;
-			return 0;
-		}
-	}
-
-	return 1;
-}
-
-/** Returns 1 if the user is allowed by any of the security groups in the named list.
- * This is only used by security-group::security-group and
- * security-group::exclude-security-group.
- * @param client	Client to check
- * @param l		The NameList
- * @returns 1 if any of the security groups match, 0 if none of them matched.
- */
-int user_allowed_by_security_group_list(Client *client, NameList *l)
-{
-	for (; l; l = l->next)
-		if (user_allowed_by_security_group_name(client, l->name))
-			return 1;
-	return 0;
-}
-
-/** Returns 1 if the user is OK as far as the security-group is concerned.
- * @param client	The client to check
- * @param s		The security-group to check against
- * @retval 1 if user is allowed by security-group, 0 if not.
- */
-int user_allowed_by_security_group(Client *client, SecurityGroup *s)
-{
-	static int recursion_security_group = 0;
-
-	/* Allow NULL securitygroup, makes it easier in the code elsewhere */
-	if (!s)
-		return 0;
-
-	if (recursion_security_group > 8)
-	{
-		unreal_log(ULOG_WARNING, "main", "SECURITY_GROUP_LOOP_DETECTED", client,
-		           "Loop detected while processing security-group '$security_group' -- "
-		           "are you perhaps referencing a security-group from a security-group?",
-		           log_data_string("security_group", s->name));
-		return 0;
-	}
-	recursion_security_group++;
-
-	/* DO NOT USE 'return' IN CODE BELOW!!!!!!!!!
-	 * - use 'goto user_not_allowed' to reject
-	 * - use 'goto user_allowed' to accept
-	 */
-
-	/* Process EXCLUSION criteria first... */
-	if (s->exclude_identified && IsLoggedIn(client))
-		goto user_not_allowed;
-	if (s->exclude_webirc && moddata_client_get(client, "webirc"))
-		goto user_not_allowed;
-	if (s->exclude_websocket && moddata_client_get(client, "websocket"))
-		goto user_not_allowed;
-	if ((s->exclude_reputation_score > 0) && (GetReputation(client) >= s->exclude_reputation_score))
-		goto user_not_allowed;
-	if ((s->exclude_reputation_score < 0) && (GetReputation(client) < 0 - s->exclude_reputation_score))
-		goto user_not_allowed;
-	if (s->exclude_connect_time != 0)
-	{
-		long connect_time = get_connected_time(client);
-		if ((s->exclude_connect_time > 0) && (connect_time >= s->exclude_connect_time))
-			goto user_not_allowed;
-		if ((s->exclude_connect_time < 0) && (connect_time < 0 - s->exclude_connect_time))
-			goto user_not_allowed;
-	}
-	if (s->exclude_tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
-		goto user_not_allowed;
-	if (s->exclude_mask && unreal_mask_match(client, s->exclude_mask))
-		goto user_not_allowed;
-	if (s->exclude_ip && unreal_match_iplist(client, s->exclude_ip))
-		goto user_not_allowed;
-	if (s->exclude_security_group && user_allowed_by_security_group_list(client, s->exclude_security_group))
-		goto user_not_allowed;
-	if (s->exclude_extended && user_matches_extended_list(client, s->exclude_extended))
-		goto user_not_allowed;
-
-	/* Then process INCLUSION criteria... */
-	if (s->identified && IsLoggedIn(client))
-		goto user_allowed;
-	if (s->webirc && moddata_client_get(client, "webirc"))
-		goto user_allowed;
-	if (s->websocket && moddata_client_get(client, "websocket"))
-		goto user_allowed;
-	if ((s->reputation_score > 0) && (GetReputation(client) >= s->reputation_score))
-		goto user_allowed;
-	if ((s->reputation_score < 0) && (GetReputation(client) < 0 - s->reputation_score))
-		goto user_allowed;
-	if (s->connect_time != 0)
-	{
-		long connect_time = get_connected_time(client);
-		if ((s->connect_time > 0) && (connect_time >= s->connect_time))
-			goto user_allowed;
-		if ((s->connect_time < 0) && (connect_time < 0 - s->connect_time))
-			goto user_allowed;
-	}
-	if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
-		goto user_allowed;
-	if (s->mask && unreal_mask_match(client, s->mask))
-		goto user_allowed;
-	if (s->ip && unreal_match_iplist(client, s->ip))
-		goto user_allowed;
-	if (s->security_group && user_allowed_by_security_group_list(client, s->security_group))
-		goto user_allowed;
-	if (s->extended && user_matches_extended_list(client, s->extended))
-		goto user_allowed;
-
-user_not_allowed:
-	recursion_security_group--;
-	return 0;
-
-user_allowed:
-	recursion_security_group--;
-	return 1;
-}
-
-/** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version.
- * @param client	The client to check
- * @param secgroupname	The name of the security-group to check against
- * @retval 1 if user is allowed by security-group, 0 if not.
- */
-int user_allowed_by_security_group_name(Client *client, const char *secgroupname)
-{
-	SecurityGroup *s;
-
-	/* Handle the magical 'unknown-users' case. */
-	if (!strcmp(secgroupname, "unknown-users"))
-	{
-		/* This is simply the inverse of 'known-users' */
-		s = find_security_group("known-users");
-		if (!s)
-			return 0; /* that's weird!? pretty impossible. */
-		return !user_allowed_by_security_group(client, s);
-	}
-
-	/* Find the group and evaluate it */
-	s = find_security_group(secgroupname);
-	if (!s)
-		return 0; /* security group not found: no match */
-	return user_allowed_by_security_group(client, s);
-}
-
-/** 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;
-}
diff --git a/src/send.c b/src/send.c
@@ -1,1193 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/send.c
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *		      University of Oulu, Computing Center
- *
- *   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.
- */
-
-/* send.c 2.32 2/28/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
-
-/** @file
- * @brief The sending functions to users, channels, servers.
- */
-
-#include "unrealircd.h"
-
-/* 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) __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)
-
-/* These are two local (static) buffers used by the various send functions */
-static char sendbuf[2048];
-static char sendbuf2[MAXLINELENGTH];
-
-/** This is used to ensure no duplicate messages are sent
- * to the same server uplink/direction. In send functions
- * that deliver to multiple users or servers the value is
- * increased by 1 and then for each delivery in the loop
- * it is checked if to->direction->local->serial == current_serial
- * and if so, sending is skipped.
- */
-MODVAR int  current_serial;
-
-/** Mark the socket as "dead".
- * This is used when exit_client() cannot be used from the
- * current code because doing so would be (too) unexpected.
- * The socket is closed later in the main loop.
- * NOTE: this function is becoming less important, now that
- *       exit_client() will not actively free the client.
- *       Still, sometimes we need to use dead_socket()
- *       since we don't want to be doing IsDead() checks after
- *       each and every sendto...().
- * @param to		Client to mark as dead
- * @param notice	The quit reason to use
- */
-int dead_socket(Client *to, const char *notice)
-{
-	DBufClear(&to->local->recvQ);
-	DBufClear(&to->local->sendQ);
-
-	if (IsDeadSocket(to))
-		return -1; /* already pending to be closed */
-
-	SetDeadSocket(to);
-
-	/* We may get here because of the 'CPR' in check_deadsockets().
-	 * In which case, we return -1 as well.
-	 */
-	if (to->local->error_str)
-		return -1; /* don't overwrite & don't send multiple times */
-	
-	if (!IsUser(to) && !IsUnknown(to) && !IsRPC(to) && !IsControl(to) && !IsClosing(to))
-	{
-		/* 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;
-}
-
-/** This is a callback function from the event loop.
- * All it does is call send_queued().
- */
-void send_queued_cb(int fd, int revents, void *data)
-{
-	Client *to = data;
-
-	if (IsDeadSocket(to))
-		return;
-
-	send_queued(to);
-}
-
-/** This function is called when queued data might be ready to be
- * sent to the client. It is called from the event loop and also
- * a couple of other places (such as when closing the connection).
- */
-int send_queued(Client *to)
-{
-	int  len, rlen;
-	dbufbuf *block;
-	int want_read;
-
-	/* We NEVER write to dead sockets. */
-	if (IsDeadSocket(to))
-		return -1;
-
-	while (DBufLength(&to->local->sendQ) > 0)
-	{
-		block = container_of(to->local->sendQ.dbuf_list.next, dbufbuf, dbuf_node);
-		len = block->size;
-
-		/* Deliver it and check for fatal error.. */
-		if ((rlen = deliver_it(to, block->data, len, &want_read)) < 0)
-		{
-			char buf[256];
-			snprintf(buf, 256, "Write error: %s", STRERROR(ERRNO));
-			return dead_socket(to, buf);
-		}
-		dbuf_delete(&to->local->sendQ, rlen);
-		if (want_read)
-		{
-			/* SSL_write indicated that it cannot write data at this
-			 * time and needs to READ data first. Let's stop talking
-			 * to the user and ask to notify us when there's data
-			 * to read.
-			 */
-			fd_setselect(to->local->fd, FD_SELECT_READ, send_queued_cb, to);
-			fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to);
-			break;
-		}
-		/* Restore handling of reads towards read_packet(), since
-		 * it may be overwritten in an earlier call to send_queued(),
-		 * to handle reads by send_queued_cb(), see directly above.
-		 */
-		fd_setselect(to->local->fd, FD_SELECT_READ, read_packet, to);
-		if (rlen < len)
-		{
-			/* incomplete write due to EWOULDBLOCK, reschedule */
-			fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to);
-			break;
-		}
-	}
-	
-	/* Nothing left to write, stop asking for write-ready notification. */
-	if ((DBufLength(&to->local->sendQ) == 0) && (to->local->fd >= 0))
-		fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to);
-
-	return (IsDeadSocket(to)) ? -1 : 0;
-}
-
-/** Mark "to" with "there is data to be send" */
-void mark_data_to_send(Client *to)
-{
-	if (!IsDeadSocket(to) && (to->local->fd >= 0) && (DBufLength(&to->local->sendQ) > 0))
-	{
-		fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to);
-	}
-}
-
-/** Send data to clients, servers, channels, IRCOps, etc.
- * There are a lot of send functions. The most commonly functions
- * are: sendto_one() to send to an individual user,
- * sendnumeric() to send a numeric to an individual user
- * and sendto_channel() to send a message to a channel.
- * @defgroup SendFunctions Send functions
- * @{
- */
-
-/** Send a message to a single client.
- * This function is used quite a lot, after sendnumeric() it is the most-used send function.
- * @param to		The client to send to
- * @param mtags		Any message tags associated with this message (can be NULL)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- * @section sendto_one_examples Examples
- * @subsection sendto_one_mode_r Send "MODE -r"
- * This will send the `:serv.er.name MODE yournick -r` message.
- * Note that it will send only this message to illustrate the sendto_one() function.
- * It does *not* set anyone actually -r.
- * @code
- * sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name);
- * @endcode
- */
-void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	va_start(vl, pattern);
-	vsendto_one(to, mtags, pattern, vl);
-	va_end(vl);
-}
-
-/** Send a message to a single client - va_list variant.
- * This function is similar to sendto_one() except that it
- * doesn't use varargs but uses a va_list instead.
- * Generally this function is NOT used outside send.c, so not by modules.
- * @param to		The client to send to
- * @param mtags		Any message tags associated with this message (can be NULL)
- * @param pattern	The format string / pattern to use.
- * @param vl		Format string parameters.
- */
-void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl)
-{
-	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)-3, pattern, vl);
-#if defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-
-	if (BadPtr(mtags_str))
-	{
-		/* Simple message without message tags */
-		sendbufto_one(to, sendbuf, 0);
-	} else {
-		/* Message tags need to be prepended */
-		snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
-		sendbufto_one(to, sendbuf2, 0);
-	}
-}
-
-
-/** Send a line buffer to the client.
- * This function is used (usually indirectly) for pretty much all
- * cases where a line needs to be sent to a client.
- * @param to    The client to which the buffer should be send.
- * @param msg   The message.
- * @param quick Normally set to 0, see the notes.
- * @note
- * - Neither 'to' or 'msg' may be NULL.
- * - If quick is set to 0 then the length is calculated,
- *   the string is cut off at 510 bytes if needed, and
- *   CR+LF is added if needed.
- *   If quick is >0 then it is assumed the message already
- *   is within boundaries and passed all safety checks and
- *   contains CR+LF at the end. This if, for example, used in
- *   channel broadcasts to save some CPU cycles. It is NOT
- *   recommended as normal usage since you need to be very
- *   careful to take everything into account, including side-
- *   effects not mentioned here.
- */
-void sendbufto_one(Client *to, char *msg, unsigned int quick)
-{
-	int len;
-	Hook *h;
-	Client *intended_to = to;
-	
-	if (to->direction)
-		to = to->direction;
-	if (IsDeadSocket(to))
-		return;		/* This socket has already
-				   been marked as dead */
-	if (to->local->fd < 0)
-	{
-		/* This is normal when 'to' was being closed (via exit_client
-		 *  and close_connection) --Run
-		 */
-		return;
-	}
-
-	/* Unless 'quick' is set, we do some safety checks,
-	 * cut the string off at the appropriate place and add
-	 * CR+LF if needed (nearly always).
-	 */
-	if (!quick)
-	{
-		char *p = msg;
-		if (*msg == '@')
-		{
-			/* The message includes one or more message tags:
-			 * Spec-wise the rules allow about 8K for message tags
-			 * (MAXTAGSIZE) and then 512 bytes for
-			 * the remainder of the message (BUFSIZE).
-			 */
-			p = strchr(msg+1, ' ');
-			if (!p)
-			{
-				unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_MALFORMED_MSG", to,
-				           "Malformed message to $client: $buf",
-				           log_data_string("buf", msg));
-				return;
-			}
-			if (p - msg > MAXTAGSIZE)
-			{
-				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 */
-		}
-		len = strlen(p);
-		if (!len || (p[len - 1] != '\n'))
-		{
-			if (len > 510)
-				len = 510;
-			p[len++] = '\r';
-			p[len++] = '\n';
-			p[len] = '\0';
-		}
-		len = strlen(msg); /* (note: we could use pointer jugling to avoid a strlen here) */
-	} else {
-		len = quick;
-	}
-
-	if (len >= MAXLINELENGTH)
-	{
-		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))
-	{
-		char tmp_msg[500];
-
-		strlcpy(tmp_msg, msg, sizeof(tmp_msg));
-		stripcrlf(tmp_msg);
-		unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_ME_MESSAGE", to,
-			   "Trying to send data to myself: $buf",
-			   log_data_string("buf", tmp_msg));
-		return;
-	}
-
-	for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
-	{
-		(*(h->func.intfunc))(&me, to, intended_to, &msg, &len);
-		if (!msg)
-			return;
-	}
-
-#if defined(RAWCMDLOGGING)
-	{
-		char copy[512], *p;
-		strlcpy(copy, msg, len > sizeof(copy) ? sizeof(copy) : len);
-		p = strchr(copy, '\n');
-		if (p) *p = '\0';
-		p = strchr(copy, '\r');
-		if (p) *p = '\0';
-		unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_OUT", to,
-		           "-> $client: $data",
-		           log_data_string("data", copy));
-	}
-#endif
-
-	if (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;
-	}
-
-	dbuf_put(&to->local->sendQ, msg, len);
-
-	/*
-	 * Update statistics. The following is slightly incorrect
-	 * because it counts messages even if queued, but bytes
-	 * 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->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
-	 * that there is data to send.
-	 */
-	if (IsControl(to))
-		send_queued(to); /* send this one ASAP */
-	else
-		mark_data_to_send(to);
-}
-
-/** A single function to send data to a channel.
- * Previously there were 6 different functions to send channel data,
- * now there is 1 single function. This also means that you most
- * likely will pass NULL or 0 as some parameters.
- * @param channel       The channel to send to
- * @param from          The source of the message
- * @param skip          The client to skip (can be NULL).
- *                      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
- *                     assume the server there will handle it)
- * @param sendflags   Determines whether to send the message to local/remote users
- * @param mtags       The message tags to attach to this message
- * @param pattern     The pattern (eg: ":%s PRIVMSG %s :%s")
- * @param ...         The parameters for the pattern.
- * @note For all channel messages, it is important to attach the correct
- *       message tags (mtags) via a new_message() call, as can be seen
- *       in the example.
- * @section sendto_channel_examples Examples
- * @subsection sendto_channel_privmsg Send a PRIVMSG to a channel
- * This command will send the message "Hello everyone!!!" to the channel when executed.
- * @code
- * CMD_FUNC(cmd_sayhello)
- * {
- *     MessageTag *mtags = NULL;
- *     Channel *channel = NULL;
- *     if ((parc < 2) || BadPtr(parv[1]))
- *     {
- *         sendnumeric(client, ERR_NEEDMOREPARAMS, "SAYHELLO");
- *         return;
- *     }
- *     channel = find_channel(parv[1]);
- *     if (!channel)
- *     {
- *         sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
- *         return;
- *     }
- *     new_message(client, recv_mtags, &mtags);
- *     sendto_channel(channel, client, client->direction, NULL, 0,
- *                    SEND_LOCAL|SEND_REMOTE, mtags,
- *                    ":%s PRIVMSG %s :Hello everyone!!!",
- *                    client->name, channel->name);
- *     free_message_tags(mtags);
- * }
- * @endcode
- */
-void sendto_channel(Channel *channel, Client *from, Client *skip,
-                    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)
-	{
-		acptr = lp->client;
-
-		/* Skip sending to 'skip' */
-		if ((acptr == skip) || (acptr->direction == skip))
-			continue;
-		/* Don't send to deaf clients (unless 'senddeaf' is set) */
-		if (IsDeaf(acptr) && (sendflags & SKIP_DEAF))
-			continue;
-		/* Don't send to NOCTCP clients */
-		if (has_user_mode(acptr, 'T') && (sendflags & SKIP_CTCP))
-			continue;
-		/* 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;
-
-		if (MyUser(acptr))
-		{
-			/* Local client */
-			if (sendflags & SEND_LOCAL)
-			{
-				va_start(vl, pattern);
-				vsendto_prefix_one(acptr, from, mtags, pattern, vl);
-				va_end(vl);
-			}
-		}
-		else
-		{
-			/* Remote client */
-			if (sendflags & SEND_REMOTE)
-			{
-				/* Message already sent to remote link? */
-				if (acptr->direction->local->serial != current_serial)
-				{
-					va_start(vl, pattern);
-					vsendto_prefix_one(acptr, from, mtags, pattern, vl);
-					va_end(vl);
-
-					acptr->direction->local->serial = current_serial;
-				}
-			}
-		}
-	}
-
-	if (sendflags & SEND_REMOTE)
-	{
-		/* For the remaining uplinks that we have not sent a message to yet...
-		 * broadcast-channel-messages=never: don't send it to them
-		 * broadcast-channel-messages=always: always send it to them
-		 * broadcast-channel-messages=auto: send it to them if the channel is set +H (history)
-		 */
-
-		if ((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_ALWAYS) ||
-		    ((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_AUTO) && has_channel_mode(channel, 'H')))
-		{
-			list_for_each_entry(acptr, &server_list, special_node)
-			{
-				if ((acptr == skip) || (acptr->direction == skip))
-					continue; /* still obey this rule.. */
-				if (acptr->direction->local->serial != current_serial)
-				{
-					va_start(vl, pattern);
-					vsendto_prefix_one(acptr, from, mtags, pattern, vl);
-					va_end(vl);
-
-					acptr->direction->local->serial = current_serial;
-				}
-			}
-		}
-	}
-}
-
-/** Send a message to a server, taking into account server options if needed.
- * @param one		The client to skip (can be NULL)
- * @param servercaps	Server capabilities which must be present (OR'd together, if multiple)
- * @param noservercaps	Server capabilities which must NOT be present (OR'd together, if multiple)
- * @param mtags		The message tags to attach to this message.
- * @param format	The format string / pattern, such as ":%s NICK %s".
- * @param ...		The parameters for the format string
- */
-void sendto_server(Client *one, unsigned long servercaps, unsigned long noservercaps, MessageTag *mtags, FORMAT_STRING(const char *format), ...)
-{
-	Client *acptr;
-
-	/* noone to send to.. */
-	if (list_empty(&server_list))
-		return;
-
-	list_for_each_entry(acptr, &server_list, special_node)
-	{
-		va_list vl;
-
-		if (one && acptr == one->direction)
-			continue;
-
-		if (servercaps && !CHECKSERVERPROTO(acptr, servercaps))
-			continue;
-
-		if (noservercaps && CHECKSERVERPROTO(acptr, noservercaps))
-			continue;
-
-		va_start(vl, format);
-		vsendto_one(acptr, mtags, format, vl);
-		va_end(vl);
-	}
-}
-
-/** Send a message to all local users on all channels where
- * the user 'user' is on.
- * This is used for events such as a nick change and quit.
- * @param user        The user and source of the message.
- * @param skip        The client to skip (can be NULL)
- * @param clicap      Client capability the recipient should have
- *                    (this only works for local clients, we will
- *                     always send the message to remote clients and
- *                     assume the server there will handle it)
- * @param mtags       The message tags to attach to this message.
- * @param pattern     The pattern (eg: ":%s NICK %s").
- * @param ...         The parameters for the pattern.
- */
-void sendto_local_common_channels(Client *user, Client *skip, long clicap, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Membership *channels;
-	Member *users;
-	Client *acptr;
-
-	/* We now create the buffer _before_ we send it to the clients. -- Syzop */
-	*sendbuf = '\0';
-	va_start(vl, pattern);
-	vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, user, pattern, vl);
-	va_end(vl);
-
-	++current_serial;
-
-	if (user->user)
-	{
-		for (channels = user->user->channel; channels; channels = channels->next)
-		{
-			for (users = channels->channel->members; users; users = users->next)
-			{
-				acptr = users->client;
-
-				if (!MyConnect(acptr))
-					continue; /* only process local clients */
-
-				if (acptr->local->serial == current_serial)
-					continue; /* message already sent to this client */
-
-				if (clicap && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
-					continue; /* client does not have the specified capability */
-
-				if (acptr == skip)
-					continue; /* the one to skip */
-
-				if (!user_can_see_member(acptr, user, channels->channel))
-					continue; /* the sending user (quit'ing or nick changing) is 'invisible' -- skip */
-
-				acptr->local->serial = current_serial;
-				sendto_one(acptr, mtags, "%s", sendbuf);
-			}
-		}
-	}
-}
-
-/** Send a QUIT message to all local users on all channels where
- * the user 'user' is on.
- * This is used for events such as a nick change and quit.
- * @param user        The user and source of the message.
- * @param skip        The client to skip (can be NULL)
- * @param clicap      Client capability the recipient should have
- *                    (this only works for local clients, we will
- *                     always send the message to remote clients and
- *                     assume the server there will handle it)
- * @param mtags       The message tags to attach to this message.
- * @param pattern     The pattern (eg: ":%s NICK %s").
- * @param ...         The parameters for the pattern.
- */
-void quit_sendto_local_common_channels(Client *user, MessageTag *mtags, const char *reason)
-{
-	va_list vl;
-	Membership *channels;
-	Member *users;
-	Client *acptr;
-	char sender[512];
-	MessageTag *m;
-	const char *real_quit_reason = NULL;
-
-	m = find_mtag(mtags, "unrealircd.org/real-quit-reason");
-	if (m && m->value)
-		real_quit_reason = m->value;
-
-	if (IsUser(user))
-	{
-		snprintf(sender, sizeof(sender), "%s!%s@%s",
-		         user->name, user->user->username, GetHost(user));
-	} else {
-		strlcpy(sender, user->name, sizeof(sender));
-	}
-
-	++current_serial;
-
-	if (user->user)
-	{
-		for (channels = user->user->channel; channels; channels = channels->next)
-		{
-			for (users = channels->channel->members; users; users = users->next)
-			{
-				acptr = users->client;
-
-				if (!MyConnect(acptr))
-					continue; /* only process local clients */
-
-				if (acptr->local->serial == current_serial)
-					continue; /* message already sent to this client */
-
-				if (!user_can_see_member(acptr, user, channels->channel))
-					continue; /* the sending user (QUITing) is 'invisible' -- skip */
-
-				acptr->local->serial = current_serial;
-				if (!reason)
-					sendto_one(acptr, mtags, ":%s QUIT", sender);
-				else if (!IsOper(acptr) || !real_quit_reason)
-					sendto_one(acptr, mtags, ":%s QUIT :%s", sender, reason);
-				else
-					sendto_one(acptr, mtags, ":%s QUIT :%s", sender, real_quit_reason);
-			}
-		}
-	}
-}
-
-/*
-** send a msg to all ppl on servers/hosts that match a specified mask
-** (used for enhanced PRIVMSGs)
-**
-** addition -- Armin, 8jun90 (gruner@informatik.tu-muenchen.de)
-*/
-
-static int match_it(Client *one, const char *mask, int what)
-{
-	switch (what)
-	{
-		case MATCH_HOST:
-			return match_simple(mask, one->user->realhost);
-		case MATCH_SERVER:
-		default:
-			return match_simple(mask, one->user->server);
-	}
-}
-
-/** Send to all clients which match the mask.
- * This function is rarely used.
- * @param one		The client to skip
- * @param from		The sender
- * @param mask		The mask
- * @param what		One of MATCH_HOST or MATCH_SERVER
- * @param mtags		Message tags associated with the message
- * @param pattern	Format string
- * @param ...		Parameters to the format string
- */
-void sendto_match_butone(Client *one, Client *from, const char *mask, int what,
-                         MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	char cansendlocal, cansendglobal;
-
-	if (MyConnect(from))
-	{
-		cansendlocal = (ValidatePermissionsForPath("chat:notice:local",from,NULL,NULL,NULL)) ? 1 : 0;
-		cansendglobal = (ValidatePermissionsForPath("chat:notice:global",from,NULL,NULL,NULL)) ? 1 : 0;
-	}
-	else
-		cansendlocal = cansendglobal = 1;
-
-	/* To servers... */
-	if (cansendglobal)
-	{
-		char buf[512];
-
-		va_start(vl, pattern);
-		ircvsnprintf(buf, sizeof(buf), pattern, vl);
-		va_end(vl);
-
-		sendto_server(one, 0, 0, mtags, "%s", buf);
-	}
-
-	/* To local clients... */
-	if (cansendlocal)
-	{
-		list_for_each_entry(acptr, &lclient_list, lclient_node)
-		{
-			if (!IsMe(acptr) && (acptr != one) && IsUser(acptr) && match_it(acptr, mask, what))
-			{
-				va_start(vl, pattern);
-				vsendto_prefix_one(acptr, from, mtags, pattern, vl);
-				va_end(vl);
-			}
-		}
-	}
-}
-
-/** Send a message to all locally connected users with specified user mode.
- * @param umodes	The umode that the recipient should have set (one of UMODE_)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_umode(int umodes, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	char nbuf[1024];
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-		if (IsUser(acptr) && (acptr->umodes & umodes) == umodes)
-		{
-			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 users with specified user mode (local & remote users).
- * @param umodes	The umode that the recipient should have set (one of UMODE_*)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	Umode *um;
-	char nbuf[1024];
-	char modestr[128];
-	char *p;
-
-	/* Convert 'umodes' (int) to 'modestr' (string) */
-	get_usermode_string_raw_r(umodes, modestr, sizeof(modestr));
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-	{
-		if (IsUser(acptr) && (acptr->umodes & umodes) == umodes)
-		{
-			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);
-		} else
-		if (IsServer(acptr) && *modestr)
-		{
-			snprintf(nbuf, sizeof(nbuf), ":%s SENDUMODE %s :%s", me.id, modestr, pattern);
-			va_start(vl, pattern);
-			vsendto_one(acptr, NULL, nbuf, vl);
-			va_end(vl);
-		}
-	}
-}
-
-/** 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, const char *token)
-{
-	Client *client;
-	ClientCapability *clicap = ClientCapabilityFindReal(token);
-	long CAP_NOTIFY = ClientCapabilityBit("cap-notify");
-
-	list_for_each_entry(client, &lclient_list, lclient_node)
-	{
-		if (HasCapabilityFast(client, CAP_NOTIFY))
-		{
-			if (add)
-			{
-				const char *args = NULL;
-				if (clicap)
-				{
-					if (clicap->visible && !clicap->visible(client))
-						continue; /* invisible CAP, so don't announce it */
-					if (clicap->parameter && (client->local->cap_protocol >= 302))
-						args = clicap->parameter(client);
-				}
-				if (!args)
-				{
-					sendto_one(client, NULL, ":%s CAP %s NEW :%s",
-						me.name, (*client->name ? client->name : "*"), token);
-				} else {
-					sendto_one(client, NULL, ":%s CAP %s NEW :%s=%s",
-						me.name, (*client->name ? client->name : "*"), token, args);
-				}
-			} else {
-				sendto_one(client, NULL, ":%s CAP %s DEL :%s",
-					me.name, (*client->name ? client->name : "*"), token);
-			}
-		}
-	}
-}
-
-/* Prepare buffer based on format string and 'from' for LOCAL delivery.
- * The prefix (:<something>) will be expanded to :nick!user@host if 'from'
- * is a person, taking into account the rules for hidden/cloaked host.
- * NOTE: Do not send this prepared buffer to remote clients or servers,
- *       they do not want or need the expanded prefix. In that case, simply
- *       use ircvsnprintf() directly.
- */
-static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl)
-{
-	int len;
-
-	/* This expands the ":%s " part of the pattern
-	 * into ":nick!user@host ".
-	 * In case of a non-person (server) it doesn't do
-	 * anything since no expansion is needed.
-	 */
-	if (from && from->user && !strncmp(pattern, ":%s ", 4))
-	{
-		va_arg(vl, char *); /* eat first parameter */
-
-		*buf = ':';
-		strlcpy(buf+1, from->name, buflen-1);
-
-		if (IsUser(from))
-		{
-			char *username = from->user->username;
-			char *host = GetHost(from);
-
-			if (*username)
-			{
-				strlcat(buf, "!", buflen);
-				strlcat(buf, username, buflen);
-			}
-			if (*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
-	{
-		ircvsnprintf(buf, buflen, pattern, vl);
-	}
-
-	len = strlen(buf);
-	ADD_CRLF(buf, len);
-	return len;
-}
-
-/** Send a message to a client, expand the sender prefix.
- * This is similar to sendto_one() except that it will expand the source part :%s
- * to :nick!user@host if needed, while with sendto_one() it will be :nick.
- * @param to		The client to send to
- * @param mtags		Any message tags associated with this message (can be NULL)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	va_start(vl, pattern);
-	vsendto_prefix_one(to, from, mtags, pattern, vl);
-	va_end(vl);
-}
-
-/** Send a message to a single client, expand the sender prefix - va_list variant.
- * This is similar to vsendto_one() except that it will expand the source part :%s
- * to :nick!user@host if needed, while with sendto_one() it will be :nick.
- * This function is also similar to sendto_prefix_one(), but this is the va_list
- * variant.
- * @param to		The client to send to
- * @param mtags		Any message tags associated with this message (can be NULL)
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl)
-{
-	const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
-
-	if (to && from && MyUser(to) && from->user)
-		vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf)-3, from, pattern, vl);
-	else
-		ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
-
-	if (BadPtr(mtags_str))
-	{
-		/* Simple message without message tags */
-		sendbufto_one(to, sendbuf, 0);
-	} else {
-		/* Message tags need to be prepended */
-		snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
-		sendbufto_one(to, sendbuf2, 0);
-	}
-}
-
-/** 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, MessageTag *mtags, Client *client, const char *umodes)
-{
-	Client *acptr;
-
-	list_for_each_entry(acptr, &server_list, special_node)
-	{
-		if (one && acptr == one->direction)
-			continue;
-		
-		sendto_one_nickcmd(acptr, mtags, client, umodes);
-	}
-}
-
-/** Introduce user to a server.
- * @param server  Server to send to (locally connected!)
- * @param client  Client to introduce
- * @param umodes  User modes of client
- */
-void sendto_one_nickcmd(Client *server, MessageTag *mtags, Client *client, const char *umodes)
-{
-	char *vhost;
-	char mtags_generated = 0;
-
-	if (!*umodes)
-		umodes = "+";
-
-	if (SupportVHP(server))
-	{
-		if (IsHidden(client))
-			vhost = client->user->virthost;
-		else
-			vhost = client->user->realhost;
-	}
-	else
-	{
-		if (IsHidden(client) && client->umodes & UMODE_SETHOST)
-			vhost = client->user->virthost;
-		else
-			vhost = "*";
-	}
-
-	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->uplink->id, client->name, client->hopcount,
-		(long long)client->lastnick,
-		client->user->username, client->user->realhost, client->id,
-		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
- * has a % in their nick, which is a safe assumption since % is illegal.
- */
- 
-/** Send a server notice to a client.
- * @param to		The client to send to
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- */
-void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...)
-{
-	static char realpattern[1024];
-	va_list vl;
-	char *name = *to->name ? to->name : "*";
-
-	ircsnprintf(realpattern, sizeof(realpattern), ":%s NOTICE %s :%s", me.name, name, pattern);
-
-	va_start(vl, pattern);
-	vsendto_one(to, NULL, realpattern, vl);
-	va_end(vl);
-}
-
-/** Send MultiLine list as a notice, one for each line.
- * @param client	The client to send to
- * @param m		The MultiLine list.
- */
-void sendnotice_multiline(Client *client, MultiLine *m)
-{
-	for (; m; m = m->next)
-		sendnotice(client, "%s", m->line);
-}
-
-/** Send numeric message to a client - format to user specific needs.
- * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
- * followed by the pattern and format string you choose.
- * @param to		The recipient
- * @param mtags     NULL, or NULL-terminated array of message tags
- * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
- */
-void sendtaggednumericfmt(Client *to, MessageTag *mtags, 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);
-	vsendto_one(to, mtags, realpattern, vl);
-	va_end(vl);
-}
-
-/** Send text numeric message to a client (RPL_TEXT).
- * Because this generic output numeric is commonly used it got a special function for it.
- * @param to		The recipient
- * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
- * @param pattern	The format string / pattern to use.
- * @param ...		Format string parameters.
- * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
- */
-void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
-{
-	static char realpattern[1024];
-	va_list vl;
-
-	ircsnprintf(realpattern, sizeof(realpattern), ":%s %d %s :%s", me.name, RPL_TEXT, to->name, pattern);
-
-	va_start(vl, pattern);
-	vsendto_one(to, NULL, realpattern, vl);
-	va_end(vl);
-}
-
-/** 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
- * Z-Line check via the codepath to banned_client().
- * YOU SHOULD NEVER USE THIS FUNCTION.
- * If you want to send raw data (without formatting) to a client
- * then have a look at sendbufto_one() instead.
- *
- * Side-effects:
- * Too many to list here. Only in the early accept code the
- * "if's" and side-effects are under control.
- *
- * By the way, did I already mention that you SHOULD NOT USE THIS
- * FUNCTION? ;)
- */
-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);
-	va_end(vl);
-	(void)send(user->local->fd, sendbuf, sendlen, 0);
-}
-
-/** @} */
diff --git a/src/serv.c b/src/serv.c
@@ -1,1264 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/serv.c
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Computing Center
- *
- *   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 Server-related functions
- */
-
-/* s_serv.c 2.55 2/7/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
-
-#include "unrealircd.h"
-#include <ares.h>
-#ifndef _WIN32
-/* for uname(), is POSIX so should be OK... */
-#include <sys/utsname.h>
-#endif
-
-MODVAR int  max_connection_count = 1, max_client_count = 1;
-extern int do_garbage_collect;
-/* We need all these for cached MOTDs -- codemastr */
-extern char *buildid;
-MOTDFile opermotd;
-MOTDFile rules;
-MOTDFile motd;
-MOTDFile svsmotd;
-MOTDFile botmotd;
-MOTDFile smotd;
-
-/** Hash list of TKL entries */
-MODVAR TKL *tklines[TKLISTLEN];
-/** 2D hash list of TKL entries + IP address */
-MODVAR TKL *tklines_ip_hash[TKLIPHASHLEN1][TKLIPHASHLEN2];
-int MODVAR spamf_ugly_vchanoverride = 0;
-
-void read_motd(const char *filename, MOTDFile *motd);
-void do_read_motd(const char *filename, MOTDFile *themotd);
-
-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
- * @param command	The command (eg: "NOTICE")
- * @param server	This indicates parv[server] contains the destination
- * @param parc		Parameter count (MAX 8!!)
- * @param parv		Parameter values (MAX 8!!)
- * @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, const char *command, int server, int parc, const char *parv[])
-{
-	Client *acptr;
-	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]))
-		return HUNTED_ISME;
-
-	acptr = find_client(parv[server], NULL);
-
-	/* find_client() may find a variety of clients. Only servers/persons please, no 'unknowns'. */
-	if (acptr && MyConnect(acptr) && !IsMe(acptr) && !IsUser(acptr) && !IsServer(acptr))
-		acptr = NULL;
-
-	if (!acptr)
-	{
-		sendnumeric(client, ERR_NOSUCHSERVER, parv[server]);
-		return HUNTED_NOSUCH;
-	}
-
-	if (IsMe(acptr) || MyUser(acptr))
-		return HUNTED_ISME;
-
-	/* Never send the message back from where it came from */
-	if (acptr->direction == client->direction)
-	{
-		sendnumeric(client, ERR_NOSUCHSERVER, parv[server]);
-		return HUNTED_NOSUCH;
-	}
-
-	/* This puts all parv[] arguments in 'buf'
-	 * Taken from concat_params() but this one is
-	 * with parv[server] magic replacement.
-	 */
-	*buf = '\0';
-	for (i = 1; i < parc; i++)
-	{
-		const char *param = parv[i];
-
-		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));
-	}
-
-	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)
-{
-	static char buf[1024];
-	struct utsname osinf;
-	char *p;
-
-	memset(&osinf, 0, sizeof(osinf));
-	if (uname(&osinf) != 0)
-		return "<unknown>";
-	snprintf(buf, sizeof(buf), "%s %s %s %s %s",
-		osinf.sysname,
-		osinf.nodename,
-		osinf.release,
-		osinf.version,
-		osinf.machine);
-	/* get rid of cr/lf */
-	for (p=buf; *p; p++)
-		if ((*p == '\n') || (*p == '\r'))
-		{
-			*p = '\0';
-			break;
-		}
-	return buf;
-}
-#endif
-
-/** Helper function to send version strings */
-void send_version(Client *client, int remote)
-{
-	int i;
-
-	for (i = 0; ISupportStrings[i]; i++)
-	{
-		if (remote)
-			sendnumeric(client, RPL_REMOTEISUPPORT, ISupportStrings[i]);
-		else
-			sendnumeric(client, RPL_ISUPPORT, ISupportStrings[i]);
-	}
-}
-
-/** VERSION command:
- * Syntax: VERSION [server]
- */
-CMD_FUNC(cmd_version)
-{
-	/* Only allow remote VERSIONs if registered -- Syzop */
-	if (!IsUser(client) && !IsServer(client))
-	{
-		send_version(client, 0);
-		return;
-	}
-
-	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"),
-			    extraflags ? extraflags : "",
-			    tainted ? "3" : "",
-			    (ValidatePermissionsForPath("server:info",client,NULL,NULL,NULL) ? MYOSNAME : "*"),
-			    UnrealProtocol);
-		if (ValidatePermissionsForPath("server:info",client,NULL,NULL,NULL))
-		{
-			sendnotice(client, "%s", SSLeay_version(SSLEAY_VERSION));
-			sendnotice(client, "libsodium %s", sodium_version_string());
-#ifdef USE_LIBCURL
-			sendnotice(client, "%s", curl_version());
-#endif
-			sendnotice(client, "c-ares %s", ares_version(NULL));
-			sendnotice(client, "%s", pcre2_version());
-#if JANSSON_VERSION_HEX >= 0x020D00
-			sendnotice(client, "jansson %s\n", jansson_version_str());
-#endif
-		}
-		if (MyUser(client))
-			send_version(client,0);
-		else
-			send_version(client,1);
-	}
-}
-
-char *num = NULL;
-
-/** Send all our PROTOCTL messages to remote server.
- * We send multiple PROTOCTL's since 4.x. If this breaks your services
- * because you fail to maintain PROTOCTL state, then fix them!
- */
-void send_proto(Client *client, ConfigItem_link *aconf)
-{
-	ISupport *prefix = ISupportFind("PREFIX");
-
-	/* CAUTION: If adding a token to an existing PROTOCTL line below,
-	 *          then ensure that MAXPARA is not reached!
-	 */
-
-	/* First line */
-	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 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 */
-	sendto_one(client, NULL, "PROTOCTL NICKCHARS=%s CHANNELCHARS=%s",
-		charsys_get_current_languages(),
-		allowed_channelchars_valtostr(iConf.allowed_channelchars));
-}
-
-#ifndef IRCDTOTALVERSION
-#define IRCDTOTALVERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5 PATCH6 PATCH7 PATCH8 PATCH9
-#endif
-
-/** Special filter for remote commands */
-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]))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return 1; /* STOP */
-	}
-
-	/* same as above, but in case an old server forwards a request to us: we ignore it */
-	if (!MyUser(client) && !ValidatePermissionsForPath("server:remote",client,NULL,NULL,NULL))
-		return 1; /* STOP (return) */
-	
-	return 0; /* Continue */
-}
-
-/** Output for /INFO */
-char *unrealinfo[] =
-{
-	"This release was brought to you by the following people:",
-	"",
-	"Head coder:",
-	"* Bram Matthys (Syzop) <syzop@unrealircd.org>",
-	"",
-	"Coders:",
-	"* Krzysztof Beresztant (k4be) <k4be@unrealircd.org>",
-	"* Gottem <gottem@unrealircd.org>",
-	"* i <i@unrealircd.org>",
-	"",
-	"Past UnrealIRCd 4.x coders/contributors:",
-	"* Heero, binki, nenolod, ..",
-	"",
-	"Past UnrealIRCd 3.2.x coders/contributors:",
-	"* Stskeeps (ret. head coder / project leader)",
-	"* codemastr (ret. u3.2 head coder)",
-	"* aquanight, WolfSage, ..",
-	"* McSkaf, Zogg, NiQuiL, chasm, llthangel, nighthawk, ..",
-	NULL
-};
-
-/** Send /INFO output */
-void cmd_info_send(Client *client)
-{
-	char **text = unrealinfo;
-
-	sendnumericfmt(client, RPL_INFO, ":========== %s ==========", IRCDTOTALVERSION);
-
-	while (*text)
-		sendnumericfmt(client, RPL_INFO, ":| %s", *text++);
-
-	sendnumericfmt(client, RPL_INFO, ":|");
-	sendnumericfmt(client, RPL_INFO, ":|");
-	sendnumericfmt(client, RPL_INFO, ":| Credits - Type /CREDITS");
-	sendnumericfmt(client, RPL_INFO, ":|");
-	sendnumericfmt(client, RPL_INFO, ":| This is an UnrealIRCd-style server");
-	sendnumericfmt(client, RPL_INFO, ":| If you find any bugs, please report them at:");
-	sendnumericfmt(client, RPL_INFO, ":|  https://bugs.unrealircd.org/");
-	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->creationtime));
-	sendnumericfmt(client, RPL_INFO, ":ReleaseID (%s)", buildid);
-	sendnumeric(client, RPL_ENDOFINFO);
-}
-
-/** The INFO command.
- * Syntax: INFO [server]
- */
-CMD_FUNC(cmd_info)
-{
-	if (remotecmdfilter(client, parc, parv))
-		return;
-
-	if (hunt_server(client, recv_mtags, "INFO", 1, parc, parv) == HUNTED_ISME)
-		cmd_info_send(client);
-}
-
-/** LICENSE command
- * Syntax: LICENSE [server]
- */
-CMD_FUNC(cmd_license)
-{
-	char **text = gnulicense;
-
-	if (remotecmdfilter(client, parc, parv))
-		return;
-
-	if (hunt_server(client, recv_mtags, "LICENSE", 1, parc, parv) == HUNTED_ISME)
-	{
-		while (*text)
-			sendnumeric(client, RPL_INFO, *text++);
-
-		sendnumeric(client, RPL_INFO, "");
-		sendnumeric(client, RPL_ENDOFINFO);
-	}
-}
-
-/** CREDITS command
- * Syntax: CREDITS [servername]
- */
-CMD_FUNC(cmd_credits)
-{
-	char **text = unrealcredits;
-
-	if (remotecmdfilter(client, parc, parv))
-		return;
-
-	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->creationtime));
-		sendnumeric(client, RPL_ENDOFINFO);
-	}
-}
-
-/** 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;
-
-	*p = '\0';
-	*p++ = '[';
-	if (IsListening(client))
-	{
-		if (client->umodes & LISTENER_NORMAL)
-			*p++ = '*';
-		if (client->umodes & LISTENER_SERVERSONLY)
-			*p++ = 'S';
-		if (client->umodes & LISTENER_CLIENTSONLY)
-			*p++ = 'C';
-		if (client->umodes & LISTENER_TLS)
-			*p++ = 's';
-	}
-	else
-	{
-		if (IsTLS(client))
-			*p++ = 's';
-	}
-	*p++ = ']';
-	*p++ = '\0';
-	return buf;
-}
-
-/** ERROR command - used by servers to indicate errors.
- * Syntax: ERROR :<reason>
- */
-CMD_FUNC(cmd_error)
-{
-	const char *para;
-
-	if (!MyConnect(client))
-		return;
-
-	para = (parc > 1 && *parv[1] != '\0') ? parv[1] : "<>";
-
-	/* 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->server)
-		return;
-
-	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) */
-EVENT(save_tunefile)
-{
-	FILE *tunefile;
-
-	tunefile = fopen(conf_files->tune_file, "w");
-	if (!tunefile)
-	{
-		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");
-	fprintf(tunefile, "%d\n", irccounts.me_max);
-	fclose(tunefile);
-}
-
-/** Load the tunefile (such as: highest seen connection count) */
-void load_tunefile(void)
-{
-	FILE *tunefile;
-	char buf[1024];
-
-	tunefile = fopen(conf_files->tune_file, "r");
-	if (!tunefile)
-		return;
-	/* 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);
-}
-
-/** Rehash motd and rule files (motd_file/rules_file and all tld entries). */
-void rehash_motdrules()
-{
-ConfigItem_tld *tlds;
-
-	reread_motdsandrules();
-	for (tlds = conf_tld; tlds; tlds = tlds->next)
-	{
-		/* read_motd() accepts NULL in first arg and acts sanely */
-		read_motd(tlds->motd_file, &tlds->motd);
-		read_motd(tlds->rules_file, &tlds->rules);
-		read_motd(tlds->smotd_file, &tlds->smotd);
-		read_motd(tlds->opermotd_file, &tlds->opermotd);
-		read_motd(tlds->botmotd_file, &tlds->botmotd);
-	}
-}
-
-/** Rehash motd and rules (only the default files) */
-void reread_motdsandrules()
-{
-	read_motd(conf_files->motd_file, &motd);
-	read_motd(conf_files->rules_file, &rules);
-	read_motd(conf_files->smotd_file, &smotd);
-	read_motd(conf_files->botmotd_file, &botmotd);
-	read_motd(conf_files->opermotd_file, &opermotd);
-	read_motd(conf_files->svsmotd_file, &svsmotd);
-}
-
-extern void reinit_resolver(Client *client);
-
-/** REHASH command - reload configuration file on server(s).
- * Syntax: see HELPOP REHASH
- */
-CMD_FUNC(cmd_rehash)
-{
-	int x;
-
-	/* This is one of the (few) commands that cannot be handled
-	 * by labeled-response accurately in all circumstances.
-	 */
-	labeled_response_inhibit = 1;
-
-	if (!ValidatePermissionsForPath("server:rehash",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if ((parc < 3) || BadPtr(parv[2])) {
-		/* If the argument starts with a '-' (like -motd, -opermotd, etc) then it's
-		 * assumed not to be a server. -- Syzop
-		 */
-		if (parv[1] && (parv[1][0] == '-'))
-			x = HUNTED_ISME;
-		else
-			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, "REHASH", 1, parc, parv);
-		}
-	}
-	if (x != HUNTED_ISME)
-		return; /* Now forwarded or server didnt exist */
-
-	if (!MyConnect(client))
-	{
-#ifndef REMOTE_REHASH
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-#endif
-		if (parv[2] == NULL)
-		{
-			if (loop.rehashing)
-			{
-				sendnotice(client, "A rehash is already in progress");
-				return;
-			}
-			remote_rehash_client = client;
-			/* fallthrough... so we deal with this the same way as local rehashes */
-		}
-		parv[1] = parv[2];
-	} else {
-		/* Ok this is in an 'else' because it should be only executed for local clients,
-		 * but it's totally unrelated to the above ;).
-		 */
-		if (parv[1] && match_simple("-glob*", parv[1]))
-		{
-			/* /REHASH -global [options] */
-			Client *acptr;
-			
-			/* Shift parv's to the left */
-			parv[1] = parv[2];
-			parv[2] = NULL;
-			parc--;
-			if (parv[1] && *parv[1] != '-')
-			{
-				sendnotice(client, "You cannot specify a server name after /REHASH -global, for obvious reasons");
-				return;
-			}
-			/* Broadcast it in an inefficient, but backwards compatible way. */
-			list_for_each_entry(acptr, &global_server_list, client_node)
-			{
-				if (acptr == &me)
-					continue;
-				sendto_one(acptr, NULL, ":%s REHASH %s %s",
-					client->name,
-					acptr->name,
-					parv[1] ? parv[1] : "-all");
-			}
-			/* Don't return, continue, because we need to REHASH ourselves as well. */
-		}
-	}
-
-	if (!BadPtr(parv[1]) && strcasecmp(parv[1], "-all"))
-	{
-		if (*parv[1] == '-')
-		{
-			if (!strncasecmp("-gar", parv[1], 4))
-			{
-				loop.do_garbage_collect = 1;
-				RunHook(HOOKTYPE_REHASHFLAG, client, parv[1]);
-				return;
-			}
-			if (!strncasecmp("-dns", parv[1], 4))
-			{
-				reinit_resolver(client);
-				return;
-			}
-			if (match_simple("-ssl*", parv[1]) || match_simple("-tls*", parv[1]))
-			{
-				unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", client, "Reloading all TLS related data. [by: $client.details]");
-				reinit_tls();
-				return;
-			}
-			RunHook(HOOKTYPE_REHASHFLAG, client, parv[1]);
-			return;
-		}
-	}
-	else
-	{
-		if (loop.rehashing)
-		{
-			sendnotice(client, "ERROR: A rehash is already in progress");
-			return;
-		}
-		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);
-	request_rehash(client);
-}
-
-/** RESTART command - restart the server (discouraged command)
- * parv[1] - password *OR* reason if no drpass { } block exists
- * parv[2] - reason for restart (optional & only if drpass block exists)
- */
-CMD_FUNC(cmd_restart)
-{
-	const char *reason = parv[1];
-	Client *acptr;
-
-	if (!MyUser(client))
-		return;
-
-	/* Check permissions */
-	if (!ValidatePermissionsForPath("server:restart",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	/* Syntax: /restart */
-	if (parc == 1)
-	{
-		if (conf_drpass)
-		{
-			sendnumeric(client, ERR_NEEDMOREPARAMS, "RESTART");
-			return;
-		}
-	} else
-	if (parc >= 2)
-	{
-		/* Syntax: /restart <pass> [reason] */
-		if (conf_drpass)
-		{
-			if (!Auth_Check(client, conf_drpass->restartauth, parv[1]))
-			{
-				sendnumeric(client, ERR_PASSWDMISMATCH);
-				return;
-			}
-			reason = parv[2];
-		}
-	}
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-	{
-		if (IsUser(acptr))
-			sendnotice(acptr, "Server Restarted by %s", client->name);
-		else if (IsServer(acptr))
-			sendto_one(acptr, NULL, ":%s ERROR :Restarted by %s: %s",
-			    me.name, get_client_name(client, TRUE), reason ? reason : "No reason");
-	}
-
-	server_reboot(reason ? reason : "No reason");
-}
-
-/** Send short message of the day to the client */
-void short_motd(Client *client)
-{
-	ConfigItem_tld *tld;
-	MOTDFile *themotd;
-	MOTDLine *motdline;
-	struct tm *tm;
-	char is_short;
-
-	tm = NULL;
-	is_short = 1;
-
-	tld = find_tld(client);
-
-	/*
-	* Try different sources of short MOTDs, falling back to the
-	* long MOTD.
-	*/
-	themotd = &smotd;
-	if (tld && tld->smotd.lines)
-		themotd = &tld->smotd;
-
-	/* try long MOTDs */
-	if (!themotd->lines)
-	{
-		is_short = 0;
-		if (tld && tld->motd.lines)
-			themotd = &tld->motd;
-		else
-			themotd = &motd;
-	}
-
-	if (!themotd->lines)
-	{
-		sendnumeric(client, ERR_NOMOTD);
-		return;
-	}
-	if (themotd->last_modified.tm_year)
-	{
-		tm = &themotd->last_modified; /* for readability */
-		sendnumeric(client, RPL_MOTDSTART, me.name);
-		sendnumericfmt(client, RPL_MOTD, ":- %d/%d/%d %d:%02d", tm->tm_mday, tm->tm_mon + 1,
-		               1900 + tm->tm_year, tm->tm_hour, tm->tm_min);
-	}
-	if (is_short)
-	{
-		sendnumeric(client, RPL_MOTD, "This is the short MOTD. To view the complete MOTD type /motd");
-		sendnumeric(client, RPL_MOTD, "");
-	}
-
-	motdline = NULL;
-	if (themotd)
-		motdline = themotd->lines;
-	while (motdline)
-	{
-		sendnumeric(client, RPL_MOTD, motdline->line);
-		motdline = motdline->next;
-	}
-
-	if (!is_short)
-	{
-		/* If the admin does not use a short MOTD then we append the SVSMOTD here...
-		 * If we did show a short motd then we don't append SVSMOTD,
-		 * since they want to keep it short.
-		 */
-		motdline = svsmotd.lines;
-		while (motdline)
-		{
-			sendnumeric(client, RPL_MOTD, motdline->line);
-			motdline = motdline->next;
-		}
-	}
-
-	sendnumeric(client, RPL_ENDOFMOTD);
-}
-
-/** Read motd-like file, used for rules/motd/botmotd/opermotd/etc.
- * @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)
-{
-	FILE *fd;
-	struct tm *tm_tmp;
-	time_t modtime;
-
-	char line[512];
-	char *tmp;
-
-	MOTDLine *last, *temp;
-
-	free_motd(themotd);
-
-	if (!filename)
-		return;
-
-	fd = fopen(filename, "r");
-	if (!fd)
-		return;
-
-	/* record file modification time */
-	modtime = unreal_getfilemodtime(filename);
-	tm_tmp = localtime(&modtime);
-	memcpy(&themotd->last_modified, tm_tmp, sizeof(struct tm));
-
-	last = NULL;
-	while (fgets(line, sizeof(line), fd))
-	{
-		if ((tmp = strchr(line, '\n')))
-			*tmp = '\0';
-		if ((tmp = strchr(line, '\r')))
-			*tmp = '\0';
-		
-		if (strlen(line) > 510)
-			line[510] = '\0';
-
-		temp = safe_alloc(sizeof(MOTDLine));
-		safe_strdup(temp->line, line);
-
-		if (last)
-			last->next = temp;
-		else
-			/* handle the special case of the first line */
-			themotd->lines = temp;
-
-		last = temp;
-	}
-	/* the file could be zero bytes long? */
-	if (last)
-		last->next = NULL;
-
-	fclose(fd);
-	
-	return;
-}
-
-/** Free the contents of a MOTDFile structure.
- * The MOTDFile structure itself should be statically
- * allocated and deallocated. If the caller wants, it must
- * manually free the MOTDFile structure itself.
- */
-void free_motd(MOTDFile *themotd)
-{
-	MOTDLine *next, *motdline;
-
-	if (!themotd)
-		return;
-
-	for (motdline = themotd->lines; motdline; motdline = next)
-	{
-		next = motdline->next;
-		safe_free(motdline->line);
-		safe_free(motdline);
-	}
-
-	themotd->lines = NULL;
-	memset(&themotd->last_modified, '\0', sizeof(struct tm));
-}
-
-/** DIE command - terminate the server
- * DIE [password]
- */
-CMD_FUNC(cmd_die)
-{
-	Client *acptr;
-
-	if (!MyUser(client))
-		return;
-
-	if (!ValidatePermissionsForPath("server:die",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
-
-	if (conf_drpass)	/* See if we have and DIE/RESTART password */
-	{
-		if (parc < 2)	/* And if so, require a password :) */
-		{
-			sendnumeric(client, ERR_NEEDMOREPARAMS, "DIE");
-			return;
-		}
-		if (!Auth_Check(client, conf_drpass->dieauth, parv[1]))
-		{
-			sendnumeric(client, ERR_PASSWDMISMATCH);
-			return;
-		}
-	}
-
-	/* Let the +s know what is going on */
-	unreal_log(ULOG_INFO, "main", "UNREALIRCD_STOP", client,
-	           "Terminating server by request of $client.details");
-
-	list_for_each_entry(acptr, &lclient_list, lclient_node)
-	{
-		if (IsUser(acptr))
-			sendnotice(acptr, "Server Terminated by %s", 
-				client->name);
-		else if (IsServer(acptr))
-			sendto_one(acptr, NULL, ":%s ERROR :Terminated by %s",
-			    me.name, get_client_name(client, TRUE));
-	}
-
-	s_die();
-}
-
-/** Server list (network) of pending connections */
-PendingNet *pendingnet = NULL;
-
-/** Add server list (network) from 'client' connection */
-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;
-
-	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
-	{
-		if (!*name)
-			continue;
-		
-		srv = safe_alloc(sizeof(PendingServer));
-		strlcpy(srv->sid, name, sizeof(srv->sid));
-		AddListItem(srv, net->servers);
-	}
-	
-	AddListItem(net, pendingnet);
-}
-
-/** Free server list (network) previously added by 'client' */
-void free_pending_net(Client *client)
-{
-	PendingNet *net, *net_next;
-	PendingServer *srv, *srv_next;
-	
-	for (net = pendingnet; net; net = net_next)
-	{
-		net_next = net->next;
-		if (net->client == client)
-		{
-			for (srv = net->servers; srv; srv = srv_next)
-			{
-				srv_next = srv->next;
-				safe_free(srv);
-			}
-			DelListItem(net, pendingnet);
-			safe_free(net);
-			/* Don't break, there can be multiple objects */
-		}
-	}
-}
-
-/** Find SID in any server list (network) that is pending, except 'exempt' */
-PendingNet *find_pending_net_by_sid_butone(const char *sid, Client *exempt)
-{
-	PendingNet *net;
-	PendingServer *srv;
-
-	if (BadPtr(sid))
-		return NULL;
-
-	for (net = pendingnet; net; net = net->next)
-	{
-		if (net->client == exempt)
-			continue;
-		for (srv = net->servers; srv; srv = srv->next)
-			if (!strcmp(srv->sid, sid))
-				return net;
-	}
-	return NULL;
-}
-
-/** Search the pending connections list for any identical sids */
-Client *find_pending_net_duplicates(Client *cptr, Client **srv, char **sid)
-{
-	PendingNet *net, *other;
-	PendingServer *s;
-
-	*srv = NULL;
-	*sid = NULL;
-	
-	for (net = pendingnet; net; net = net->next)
-	{
-		if (net->client != cptr)
-			continue;
-		/* Ok, found myself */
-		for (s = net->servers; s; s = s->next)
-		{
-			char *curr_sid = s->sid;
-			other = find_pending_net_by_sid_butone(curr_sid, cptr);
-			if (other)
-			{
-				*srv = net->client;
-				*sid = s->sid;
-				return other->client; /* Found another (pending) server with identical numeric */
-			}
-		}
-	}
-	
-	return NULL;
-}
-
-/** Like find_pending_net_duplicates() but the other way around? Eh.. */
-Client *find_non_pending_net_duplicates(Client *client)
-{
-	PendingNet *net;
-	PendingServer *s;
-	Client *acptr;
-
-	for (net = pendingnet; net; net = net->next)
-	{
-		if (net->client != client)
-			continue;
-		/* Ok, found myself */
-		for (s = net->servers; s; s = s->next)
-		{
-			acptr = find_server(s->sid, NULL);
-			if (acptr)
-				return acptr; /* Found another (fully CONNECTED) server with identical numeric */
-		}
-	}
-	
-	return NULL;
-}
-
-/** Parse CHANMODES= in PROTOCTL */
-void parse_chanmodes_protoctl(Client *client, const char *str)
-{
-	char *modes, *p;
-	char copy[256];
-
-	strlcpy(copy, str, sizeof(copy));
-
-	modes = strtoken(&p, copy, ",");
-	if (modes)
-	{
-		safe_strdup(client->server->features.chanmodes[0], modes);
-		modes = strtoken(&p, NULL, ",");
-		if (modes)
-		{
-			safe_strdup(client->server->features.chanmodes[1], modes);
-			modes = strtoken(&p, NULL, ",");
-			if (modes)
-			{
-				safe_strdup(client->server->features.chanmodes[2], modes);
-				modes = strtoken(&p, NULL, ",");
-				if (modes)
-				{
-					safe_strdup(client->server->features.chanmodes[3], modes);
-				}
-			}
-		}
-	}
-}
-
-static char previous_langsinuse[512];
-static int previous_langsinuse_ready = 0;
-
-/** Check the nick character system (set::allowed-nickchars) for changes.
- * If there are changes, then we broadcast the new PROTOCTL NICKCHARS= to all servers.
- */
-void charsys_check_for_changes(void)
-{
-	const char *langsinuse = charsys_get_current_languages();
-	/* already called by charsys_finish() */
-	safe_strdup(me.server->features.nickchars, langsinuse);
-
-	if (!previous_langsinuse_ready)
-	{
-		previous_langsinuse_ready = 1;
-		strlcpy(previous_langsinuse, langsinuse, sizeof(previous_langsinuse));
-		return; /* not booted yet. then we are done here. */
-	}
-
-	if (strcmp(langsinuse, previous_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);
-	}
-
-	strlcpy(previous_langsinuse, langsinuse, sizeof(previous_langsinuse));
-}
-
-/** Check if supplied server name is valid, that is: does not contain forbidden characters etc */
-int valid_server_name(const char *name)
-{
-	const char *p;
-
-	if (!valid_host(name, 0))
-		return 0; /* invalid hostname */
-
-	if (!strchr(name, '.'))
-		return 0; /* no dot */
-
-	return 1;
-}
-
-/** Check if the supplied name is a valid SID, as in: syntax. */
-int valid_sid(const char *name)
-{
-	if (strlen(name) != 3)
-		return 0;
-	if (!isdigit(*name))
-		return 0;
-	if (!isdigit(name[1]) && !isupper(name[1]))
-		return 0;
-	if (!isdigit(name[2]) && !isupper(name[2]))
-		return 0;
-	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)
-{
-	memset(tklines, 0, sizeof(tklines));
-	memset(tklines_ip_hash, 0, sizeof(tklines_ip_hash));
-}
-
-/** 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 *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,
-					   client->server->conf->outgoing.file
-					   ? "Unable to link with server $client [$link_block.file]: $tls_error_string"
-					   : "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,
-					   client->server->conf->outgoing.file
-					   ? "Unable to link with server $client [$link_block.file]: $socket_error"
-					   : "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)
-{
-	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");
-}
-
-/** 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 (!aconf)
-	{
-		/* Should be impossible. */
-		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_HANDSHAKE", client,
-		           "Lost configuration while connecting to $client.details");
-		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
-	 */
-}
diff --git a/src/socket.c b/src/socket.c
@@ -1,1390 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/socket.c
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Computing Center
- *
- *   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 Socket functions such as reading, writing, connecting.
- *
- * The actual data parsing functions (for incoming data) are in
- * src/parse.c.
- */
-
-#include "unrealircd.h"
-#include "dns.h"
-
-int OpenFiles = 0;    /* GLOBAL - number of files currently open */
-int readcalls = 0;
-
-void completed_connection(int, int, void *);
-void set_sock_opts(int, Client *, SocketType);
-void set_ipv6_opts(int);
-void close_listener(ConfigItem_listen *listener);
-static char readbuf[BUFSIZE];
-char zlinebuf[BUFSIZE];
-extern char *version;
-MODVAR time_t last_allinuse = 0;
-
-void start_of_normal_client_handshake(Client *client);
-void proceed_normal_client_handshake(Client *client, struct hostent *he);
-
-/** Close all connections - only used when we terminate the server (eg: /DIE or SIGTERM) */
-void close_connections(void)
-{
-	Client *client;
-
-	list_for_each_entry(client, &lclient_list, lclient_node)
-	{
-		if (client->local->fd >= 0)
-		{
-			fd_close(client->local->fd);
-			client->local->fd = -2;
-		}
-	}
-
-	list_for_each_entry(client, &unknown_list, lclient_node)
-	{
-		if (client->local->fd >= 0)
-		{
-			fd_close(client->local->fd);
-			client->local->fd = -2;
-		}
-
-		if (client->local->authfd >= 0)
-		{
-			fd_close(client->local->authfd);
-			client->local->fd = -1;
-		}
-	}
-
-	list_for_each_entry(client, &control_list, lclient_node)
-	{
-		if (client->local->fd >= 0)
-		{
-			fd_close(client->local->fd);
-			client->local->fd = -2;
-		}
-	}
-
-	close_unbound_listeners();
-
-	OpenFiles = 0;
-
-#ifdef _WIN32
-	WSACleanup();
-#endif
-}
-
-/** Accept an incoming connection.
- * @param listener	The listen { } block configuration data.
- * @returns 1 if the connection was accepted (even if it was rejected),
- * 0 if there is no more work to do (accept returned an error).
- */
-static int listener_accept_wrapper(ConfigItem_listen *listener)
-{
-	int cli_fd;
-
-	if ((cli_fd = fd_accept(listener->fd)) < 0)
-	{
-		if ((ERRNO != P_EWOULDBLOCK) && (ERRNO != P_ECONNABORTED))
-		{
-			/* Trouble! accept() returns a strange error.
-			 * Previously in such a case we would just log/broadcast the error and return,
-			 * causing this message to be triggered at a rate of XYZ per second (100% CPU).
-			 * Now we close & re-start the listener.
-			 * Of course the underlying cause of this issue should be investigated, as this
-			 * is very much a workaround.
-			 */
-			if (listener->file)
-			{
-				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on file $file: $socket_error",
-					   log_data_socket_error(listener->fd),
-					   log_data_string("file", listener->file));
-			} else {
-				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error",
-					   log_data_socket_error(listener->fd),
-					   log_data_string("listen_ip", listener->ip),
-					   log_data_integer("listen_port", listener->port));
-			}
-			close_listener(listener);
-			start_listeners();
-		}
-		return 0;
-	}
-
-	ircstats.is_ac++;
-
-	set_sock_opts(cli_fd, NULL, listener->socket_type);
-
-	/* Allow connections to the control socket, even if maxclients is reached */
-	if (listener->options & LISTENER_CONTROL)
-	{
-		/* ... but not unlimited ;) */
-		if ((++OpenFiles >= maxclients+(CLIENTS_RESERVE/2)) || (cli_fd >= maxclients+(CLIENTS_RESERVE/2)))
-		{
-			ircstats.is_ref++;
-			if (last_allinuse < TStime() - 15)
-			{
-				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
-					   log_data_string("file", listener->file));
-				last_allinuse = TStime();
-			}
-			fd_close(cli_fd);
-			--OpenFiles;
-			return 1;
-		}
-	} else
-	{
-		if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients))
-		{
-			ircstats.is_ref++;
-			if (last_allinuse < TStime() - 15)
-			{
-				if (listener->file)
-				{
-					unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
-						   log_data_string("file", listener->file));
-				} else {
-					unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use",
-						   log_data_string("listen_ip", listener->ip),
-						   log_data_integer("listen_port", listener->port));
-				}
-				last_allinuse = TStime();
-			}
-
-			(void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0);
-
-			fd_close(cli_fd);
-			--OpenFiles;
-			return 1;
-		}
-	}
-
-	/* add_connection() may fail. we just don't care. */
-	add_connection(listener, cli_fd);
-	return 1;
-}
-
-/** Accept an incoming connection.
- * @param listener_fd	The file descriptor of a listen() socket.
- * @param data		The listen { } block configuration data.
- */
-static void listener_accept(int listener_fd, int revents, void *data)
-{
-	int i;
-
-	/* Accept clients, but only up to a maximum in each run,
-	 * as to allow some CPU available to existing clients.
-	 * Better refuse or lag a few new clients than become
-	 * unresponse to existing clients.
-	 */
-	for (i=0; i < 100; i++)
-		if (!listener_accept_wrapper((ConfigItem_listen *)data))
-			break;
-}
-
-int unreal_listen_inet(ConfigItem_listen *listener)
-{
-	const char *ip = listener->ip;
-	int port = listener->port;
-
-	if (BadPtr(ip))
-		ip = "*";
-
-	if (*ip == '*')
-	{
-		if (listener->socket_type == SOCKET_TYPE_IPV6)
-			ip = "::";
-		else
-			ip = "0.0.0.0";
-	}
-
-	/* At first, open a new socket */
-	if (listener->fd >= 0)
-		abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
-
-	if (port == 0)
-		abort(); /* Impossible as well, right? */
-
-	listener->fd = fd_socket(listener->socket_type == SOCKET_TYPE_IPV6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket");
-	if (listener->fd < 0)
-	{
-		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
-		           "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)
-	{
-		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;
-		return -1;
-	}
-
-	set_sock_opts(listener->fd, NULL, listener->socket_type);
-
-	if (!unreal_bind(listener->fd, ip, port, listener->socket_type))
-	{
-		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
-		           "Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
-		           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;
-		return -1;
-	}
-
-	if (listen(listener->fd, LISTEN_SIZE) < 0)
-	{
-		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;
-		return -1;
-	}
-
-#ifdef TCP_DEFER_ACCEPT
-	if (listener->options & LISTENER_DEFER_ACCEPT)
-	{
-		int yes = 1;
-
-		(void)setsockopt(listener->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &yes, sizeof(int));
-	}
-#endif
-
-#ifdef SO_ACCEPTFILTER
-	if (listener->options & LISTENER_DEFER_ACCEPT)
-	{
-		struct accept_filter_arg afa;
-
-		memset(&afa, '\0', sizeof afa);
-		strlcpy(afa.af_name, "dataready", sizeof afa.af_name);
-		(void)setsockopt(listener->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof afa);
-	}
-#endif
-
-	fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
-
-	return 0;
-}
-
-int unreal_listen_unix(ConfigItem_listen *listener)
-{
-	if (listener->socket_type != SOCKET_TYPE_UNIX)
-		abort(); /* "impossible" */
-
-	/* At first, open a new socket */
-	if (listener->fd >= 0)
-		abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
-
-	listener->fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Listener socket (UNIX)");
-	if (listener->fd < 0)
-	{
-		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
-		           "Could not create UNIX domain socket for $file: $socket_error",
-			   log_data_socket_error(-1),
-			   log_data_string("file", listener->file));
-		return -1;
-	}
-
-	if (++OpenFiles >= maxclients)
-	{
-		unreal_log(ULOG_FATAL, "listen", "LISTEN_ERROR_MAXCLIENTS", NULL,
-		           "Could not create UNIX domain socket for $file: all connections in use",
-		           log_data_string("file", listener->file));
-		fd_close(listener->fd);
-		listener->fd = -1;
-		--OpenFiles;
-		return -1;
-	}
-
-	set_sock_opts(listener->fd, NULL, listener->socket_type);
-
-	if (!unreal_bind(listener->fd, listener->file, listener->mode, SOCKET_TYPE_UNIX))
-	{
-		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
-		           "Could not listen on UNIX domain socket $file: $socket_error",
-		           log_data_socket_error(listener->fd),
-		           log_data_string("file", listener->file));
-		fd_close(listener->fd);
-		listener->fd = -1;
-		--OpenFiles;
-		return -1;
-	}
-
-	if (listen(listener->fd, LISTEN_SIZE) < 0)
-	{
-		unreal_log(ULOG_FATAL, "listen", "LISTEN_LISTEN_ERROR", NULL,
-		           "Could not listen on UNIX domain socket $file: $socket_error",
-		           log_data_socket_error(listener->fd),
-		           log_data_string("file", listener->file));
-		fd_close(listener->fd);
-		listener->fd = -1;
-		--OpenFiles;
-		return -1;
-	}
-
-	fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
-
-	return 0;
-}
-
-/** Create a listener port.
- * @param listener	The listen { } block configuration
- * @returns 0 on success and <0 on error. Yeah, confusing.
- */
-int unreal_listen(ConfigItem_listen *listener)
-{
-	if ((listener->socket_type == SOCKET_TYPE_IPV4) || (listener->socket_type == SOCKET_TYPE_IPV6))
-		return unreal_listen_inet(listener);
-	return unreal_listen_unix(listener);
-}
-
-/** Activate a listen { } block */
-int add_listener(ConfigItem_listen *listener)
-{
-	if (unreal_listen(listener))
-	{
-		/* Error is already handled upstream */
-		listener->fd = -2;
-	}
-
-	if (listener->fd >= 0)
-	{
-		listener->options |= LISTENER_BOUND;
-		return 1;
-	}
-	else
-	{
-		listener->fd = -1;
-		return -1;
-	}
-}
-
-/** Close the listener socket, but do not free it (yet).
- * This will only close the socket so no new clients are accepted.
- * It also marks the listener as no longer "bound".
- * Once the last client exits the listener will actually be freed.
- * @param listener	The listen { } block.
- */
-void close_listener(ConfigItem_listen *listener)
-{
-	if (listener->fd >= 0)
-	{
-		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 TLS context, since it is only
-	 * used for new connections, which we no longer accept.
-	 */
-	if (listener->ssl_ctx)
-	{
-		SSL_CTX_free(listener->ssl_ctx);
-		listener->ssl_ctx = NULL;
-	}
-}
-
-/** Close all listeners that were pending to be closed. */
-void close_unbound_listeners(void)
-{
-	ConfigItem_listen *aconf, *aconf_next;
-
-	/* close all 'extra' listening ports we have */
-	for (aconf = conf_listen; aconf != NULL; aconf = aconf_next)
-	{
-		aconf_next = aconf->next;
-		if (aconf->flag.temporary)
-			close_listener(aconf);
-	}
-}
-
-int maxclients = 1024 - CLIENTS_RESERVE;
-
-/** Check the maximum number of sockets (users) that we can handle - called on startup.
- */
-void check_user_limit(void)
-{
-#ifdef RLIMIT_FD_MAX
-	struct rlimit limit;
-	long m;
-
-	if (!getrlimit(RLIMIT_FD_MAX, &limit))
-	{
-		if (limit.rlim_max < MAXCONNECTIONS)
-			m = limit.rlim_max;
-		else
-			m = MAXCONNECTIONS;
-
-		/* Adjust soft limit (if necessary, which is often the case) */
-		if (m != limit.rlim_cur)
-		{
-			limit.rlim_cur = limit.rlim_max = m;
-			if (setrlimit(RLIMIT_FD_MAX, &limit) == -1)
-			{
-				/* HACK: if it's mac os X then don't error... */
-#ifndef OSXTIGER
-				fprintf(stderr, "error setting maximum number of open files to %ld\n",
-					(long)limit.rlim_cur);
-				exit(-1);
-#endif // OSXTIGER
-			}
-		}
-		/* This can only happen if it is due to resource limits (./Config already rejects <100) */
-		if (m < 100)
-		{
-			fprintf(stderr, "\nERROR: Your OS has a limit placed on this account.\n"
-			                "This machine only allows UnrealIRCd to handle a maximum of %ld open connections/files, which is VERY LOW.\n"
-			                "Please check with your system administrator to bump this limit.\n"
-			                "The recommended ulimit -n setting is at least 1024 and "
-			                "preferably 4096.\n"
-			                "Note that this error is often seen on small web shells that are not meant for running IRC servers.\n",
-			                m);
-			exit(-1);
-		}
-		maxclients = m - CLIENTS_RESERVE;
-	}
-#endif // RLIMIT_FD_MAX
-
-#ifndef _WIN32
-#ifdef BACKEND_SELECT
-	if (MAXCONNECTIONS > FD_SETSIZE)
-	{
-		fprintf(stderr, "MAXCONNECTIONS (%d) is higher than FD_SETSIZE (%d)\n", MAXCONNECTIONS, FD_SETSIZE);
-		fprintf(stderr, "You should not see this error on Linux or FreeBSD\n");
-		fprintf(stderr, "You might need to recompile the IRCd and answer a lower value to the MAXCONNECTIONS question in ./Config\n");
-		exit(-1);
-	}
-#endif
-#endif
-#ifdef _WIN32
-	maxclients = MAXCONNECTIONS - CLIENTS_RESERVE;
-#endif
-}
-
-/** Initialize some systems - called on startup */
-void init_sys(void)
-{
-#ifndef _WIN32
-	/* Create new session / set process group */
-	(void)setsid();
-#endif
-
-	init_resolver(1);
-	return;
-}
-
-/** Replace a file descriptor (*NIX only).
- * See close_std_descriptors() as for why.
- * @param oldfd: the old FD to close and re-use
- * @param name: descriptive string of the old fd, eg: "stdin".
- * @param mode: an open() mode, such as O_WRONLY.
- */
-void replacefd(int oldfd, char *name, int mode)
-{
-#ifndef _WIN32
-	int newfd = open("/dev/null", mode);
-	if (newfd < 0)
-	{
-		fprintf(stderr, "Warning: could not open /dev/null\n");
-		return;
-	}
-	if (oldfd < 0)
-	{
-		fprintf(stderr, "Warning: could not replace %s (invalid fd)\n", name);
-		return;
-	}
-	if (dup2(newfd, oldfd) < 0)
-	{
-		fprintf(stderr, "Warning: could not replace %s (dup2 error)\n", name);
-		return;
-	}
-#endif
-}
-
-/** Mass close standard file descriptors (stdin, stdout, stderr).
- * We used to really just close them here (or in init_sys() actually),
- * making the fd's available for other purposes such as internet sockets.
- * For safety we now dup2() them to /dev/null. This in case someone
- * accidentally does a fprintf(stderr,..) somewhere in the code or some
- * library outputs error messages to stderr (such as libc with heap
- * errors). We don't want any IRC client to receive such a thing!
- */
-void close_std_descriptors(void)
-{
-#if !defined(_WIN32) && !defined(NOCLOSEFD)
-	replacefd(fileno(stdin), "stdin", O_RDONLY);
-	replacefd(fileno(stdout), "stdout", O_WRONLY);
-	replacefd(fileno(stderr), "stderr", O_WRONLY);
-#endif
-}
-
-/** Do an ident lookup if necessary.
- * @param client	The incoming client
- */
-void consider_ident_lookup(Client *client)
-{
-	char buf[BUFSIZE];
-
-	/* If ident checking is disabled or it's an outgoing connect, then no ident check */
-	if ((IDENT_CHECK == 0) || (client->server && IsHandshake(client)) || IsUnixSocket(client))
-	{
-		ClearIdentLookupSent(client);
-		ClearIdentLookup(client);
-		return;
-	}
-	RunHook(HOOKTYPE_IDENT_LOOKUP, client);
-
-	return;
-}
-
-
-/** Called when TCP/IP connection is established (outgoing server connect) */
-void completed_connection(int fd, int revents, void *data)
-{
-	Client *client = data;
-	ConfigItem_link *aconf = client->server ? client->server->conf : NULL;
-
-	if (IsHandshake(client))
-	{
-		/* Due to delayed unreal_tls_connect call */
-		start_server_handshake(client);
-		fd_setselect(fd, FD_SELECT_READ, read_packet, client);
-		return;
-	}
-
-	SetHandshake(client);
-
-	if (!aconf)
-	{
-		unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_CONNECT", client,
-		           "Lost configuration while connecting to $client.details");
-		return;
-	}
-
-	if (!client->local->ssl && !(aconf->outgoing.options & CONNECT_INSECURE))
-	{
-		sendto_one(client, NULL, "STARTTLS");
-	} else
-	{
-		start_server_handshake(client);
-	}
-
-	if (!IsDeadSocket(client))
-		consider_ident_lookup(client);
-
-	fd_setselect(fd, FD_SELECT_READ, read_packet, client);
-}
-
-/** Close the physical connection.
- * @param client	The client connection to close (LOCAL!)
- */
-void close_connection(Client *client)
-{
-	RunHook(HOOKTYPE_CLOSE_CONNECTION, client);
-	/* This function must make MyConnect(client) == FALSE,
-	 * and set client->direction == NULL.
-	 */
-	if (IsServer(client))
-	{
-		ircstats.is_sv++;
-		ircstats.is_sti += TStime() - client->local->creationtime;
-	}
-	else if (IsUser(client))
-	{
-		ircstats.is_cl++;
-		ircstats.is_cti += TStime() - client->local->creationtime;
-	}
-	else
-		ircstats.is_ni++;
-
-	/*
-	 * remove outstanding DNS queries.
-	 */
-	unrealdns_delreq_bycptr(client);
-
-	if (client->local->authfd >= 0)
-	{
-		fd_close(client->local->authfd);
-		client->local->authfd = -1;
-		--OpenFiles;
-	}
-
-	if (client->local->fd >= 0)
-	{
-		send_queued(client);
-		if (IsTLS(client) && client->local->ssl) {
-			SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
-			SSL_smart_shutdown(client->local->ssl);
-			SSL_free(client->local->ssl);
-			client->local->ssl = NULL;
-		}
-		fd_close(client->local->fd);
-		client->local->fd = -2;
-		--OpenFiles;
-		DBufClear(&client->local->sendQ);
-		DBufClear(&client->local->recvQ);
-	}
-
-	client->direction = NULL;
-}
-
-/** Set IPv6 socket options, if possible. */
-void set_ipv6_opts(int fd)
-{
-#if defined(IPV6_V6ONLY)
-	int opt = 1;
-	(void)setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&opt, sizeof(opt));
-#endif
-}
-
-/** This sets the *OS* socket buffers.
- * This shouldn't be needed anymore, but I've left the function here.
- */
-void set_socket_buffers(int fd, int rcvbuf, int sndbuf)
-{
-	int opt;
-
-	opt = rcvbuf;
-	setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&opt, sizeof(opt));
-
-	opt = sndbuf;
-	setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&opt, sizeof(opt));
-}
-
-/** Set the appropriate socket options */
-void set_sock_opts(int fd, Client *client, SocketType socket_type)
-{
-	int opt;
-
-	if (socket_type == SOCKET_TYPE_IPV6)
-		set_ipv6_opts(fd);
-
-	if ((socket_type == SOCKET_TYPE_IPV4) || (socket_type == SOCKET_TYPE_IPV6))
-	{
-#ifdef SO_REUSEADDR
-		opt = 1;
-		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0)
-		{
-			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
-				   "Could not setsockopt(SO_REUSEADDR): $socket_error",
-				   log_data_socket_error(-1));
-		}
-#endif
-
-#if defined(SO_USELOOPBACK) && !defined(_WIN32)
-		opt = 1;
-		if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (void *)&opt, sizeof(opt)) < 0)
-		{
-			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
-				   "Could not setsockopt(SO_USELOOPBACK): $socket_error",
-				   log_data_socket_error(-1));
-		}
-#endif
-
-	}
-
-	/* The following code applies to all socket types: IPv4, IPv6, UNIX domain sockets */
-
-	/* Set to non blocking: */
-#if !defined(_WIN32)
-	if ((opt = fcntl(fd, F_GETFL, 0)) == -1)
-	{
-		if (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)
-		{
-			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
-				   "Could not get socket options (F_SETFL): $socket_error",
-				   log_data_socket_error(-1));
-		}
-	}
-#else
-	opt = 1;
-	if (ioctlsocket(fd, FIONBIO, &opt) < 0)
-	{
-		if (client)
-		{
-			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
-				   "Could not ioctlsocket FIONBIO: $socket_error",
-				   log_data_socket_error(-1));
-		}
-	}
-#endif
-}
-
-/** Returns 1 if using a loopback IP (127.0.0.1) or
- * using a local IP number on the same machine (effectively the same;
- * no network traffic travels outside this machine).
- * @param ip	The IP address to check
- * @returns 1 if loopback, 0 if not.
- */
-int is_loopback_ip(char *ip)
-{
-	ConfigItem_listen *e;
-
-	if (!strcmp(ip, "127.0.0.1") || !strcmp(ip, "0:0:0:0:0:0:0:1") || !strcmp(ip, "0:0:0:0:0:ffff:127.0.0.1"))
-		return 1;
-
-	for (e = conf_listen; e; e = e->next)
-	{
-		if ((e->options & LISTENER_BOUND) && e->ip && !strcmp(ip, e->ip))
-			return 1;
-	}
-	return 0;
-}
-
-/** Retrieve the remote IP address and port of a socket.
- * @param client	Client to check
- * @param fd		File descriptor
- * @param port		Remote port (will be written)
- * @returns The IP address
- */
-const char *getpeerip(Client *client, int fd, int *port)
-{
-	static char ret[HOSTLEN+1];
-
-	if (IsIPV6(client))
-	{
-		struct sockaddr_in6 addr;
-		int len = sizeof(addr);
-
-		if (getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
-			return NULL;
-		*port = ntohs(addr.sin6_port);
-		return inetntop(AF_INET6, &addr.sin6_addr.s6_addr, ret, sizeof(ret));
-	} else
-	{
-		struct sockaddr_in addr;
-		int len = sizeof(addr);
-
-		if (getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
-			return NULL;
-		*port = ntohs(addr.sin_port);
-		return inetntop(AF_INET, &addr.sin_addr.s_addr, ret, sizeof(ret));
-	}
-}
-
-/** Process the incoming connection which has just been accepted.
- * This creates a client structure for the user.
- * The sockhost field is initialized with the ip# of the host.
- * The client is added to the linked list of clients but isnt added to any
- * hash tables yuet since it doesnt have a name.
- * @param listener	The listen { } block on which the client was accepted.
- * @param fd		The file descriptor of the client
- * @returns The new client, or NULL in case of trouble.
- * @note  When NULL is returned, the client at socket 'fd' will be
- *        closed by this function and OpenFiles is adjusted appropriately.
- */
-Client *add_connection(ConfigItem_listen *listener, int fd)
-{
-	Client *client;
-	const char *ip;
-	int port = 0;
-	Hook *h;
-
-	client = make_client(NULL, &me);
-	client->local->socket_type = listener->socket_type;
-	client->local->listener = listener;
-	client->local->listener->clients++;
-
-	if (listener->socket_type == SOCKET_TYPE_UNIX)
-		ip = listener->spoof_ip ? listener->spoof_ip : "127.0.0.1";
-	else
-		ip = getpeerip(client, fd, &port);
-
-	if (!ip)
-	{
-		/* On Linux 2.4 and FreeBSD the socket may just have been disconnected
-		 * so it's not a serious error and can happen quite frequently -- Syzop
-		 */
-		if (ERRNO != P_ENOTCONN)
-		{
-			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++;
-			client->local->fd = -2;
-			if (!list_empty(&client->client_node))
-				list_del(&client->client_node);
-			if (!list_empty(&client->lclient_node))
-				list_del(&client->lclient_node);
-			free_client(client);
-			fd_close(fd);
-			--OpenFiles;
-			return NULL;
-	}
-
-	/* Fill in sockhost & ip ASAP */
-	set_sockhost(client, ip);
-	safe_strdup(client->ip, ip);
-	client->local->port = port;
-	client->local->fd = fd;
-
-	/* Tag loopback connections */
-	if (is_loopback_ip(client->ip))
-	{
-		ircstats.is_loc++;
-		SetLocalhost(client);
-	}
-
-	add_client_to_list(client);
-	irccounts.unknown++;
-	client->status = CLIENT_STATUS_UNKNOWN;
-	list_add(&client->lclient_node, &unknown_list);
-
-	for (h = Hooks[HOOKTYPE_ACCEPT]; h; h = h->next)
-	{
-		int value = (*(h->func.intfunc))(client);
-		if (value == HOOK_DENY)
-		{
-			irccounts.unknown--;
-			goto refuse_client;
-		}
-		if (value != HOOK_CONTINUE)
-			break;
-	}
-
-	if ((listener->options & LISTENER_TLS) && ctx_server)
-	{
-		SSL_CTX *ctx = listener->ssl_ctx ? listener->ssl_ctx : ctx_server;
-
-		if (ctx)
-		{
-			SetTLSAcceptHandshake(client);
-			if ((client->local->ssl = SSL_new(ctx)) == NULL)
-			{
-				irccounts.unknown--;
-				goto refuse_client;
-			}
-			SetTLS(client);
-			SSL_set_fd(client->local->ssl, fd);
-			SSL_set_nonblocking(client->local->ssl);
-			SSL_set_ex_data(client->local->ssl, tls_client_index, client);
-			if (!unreal_tls_accept(client, fd))
-			{
-				SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
-				SSL_smart_shutdown(client->local->ssl);
-				SSL_free(client->local->ssl);
-				irccounts.unknown--;
-				goto refuse_client;
-			}
-		}
-	} else
-	{
-		listener->start_handshake(client);
-	}
-	return client;
-}
-
-/** 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 TLS connections this is called after the TLS handshake is completed.
- */
-void start_of_normal_client_handshake(Client *client)
-{
-	struct hostent *he;
-
-	client->status = CLIENT_STATUS_UNKNOWN; /* reset, to be sure (TLS handshake has ended) */
-
-	RunHook(HOOKTYPE_HANDSHAKE, client);
-
-	if (!DONT_RESOLVE && !IsUnixSocket(client))
-	{
-		if (should_show_connect_info(client))
-			sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_DNS);
-		he = unrealdns_doclient(client);
-
-		if (client->local->hostp)
-			goto doauth; /* Race condition detected, DNS has been done, continue with auth */
-
-		if (!he)
-		{
-			/* Resolving in progress */
-			SetDNSLookup(client);
-		} else {
-			/* Host was in our cache */
-			client->local->hostp = he;
-			if (should_show_connect_info(client))
-				sendto_one(client, NULL, ":%s %s", me.name, REPORT_FIN_DNSC);
-		}
-	}
-
-doauth:
-	consider_ident_lookup(client);
-	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
-}
-
-/** Called when DNS lookup has been completed and we can proceed with the client handshake.
- * @param client	The client
- * @param he		The resolved or unresolved host
- */
-void proceed_normal_client_handshake(Client *client, struct hostent *he)
-{
-	ClearDNSLookup(client);
-	client->local->hostp = he;
-	if (should_show_connect_info(client))
-	{
-		sendto_one(client, NULL, ":%s %s",
-		           me.name,
-		           client->local->hostp ? REPORT_FIN_DNS : REPORT_FAIL_DNS);
-	}
-}
-
-/** Read a packet from a client.
- * @param fd		File descriptor
- * @param revents	Read events (ignored)
- * @param data		Associated data (the client)
- */
-void read_packet(int fd, int revents, void *data)
-{
-	Client *client = data;
-	int length = 0;
-	time_t now = TStime();
-	Hook *h;
-	int processdata;
-
-	/* Don't read from dead sockets */
-	if (IsDeadSocket(client))
-	{
-		fd_setselect(fd, FD_SELECT_READ, NULL, client);
-		return;
-	}
-
-	SET_ERRNO(0);
-
-	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 (TLS) writes by read_packet(), see below under
-	 * SSL_ERROR_WANT_WRITE.
-	 */
-	fd_setselect(fd, FD_SELECT_WRITE, send_queued_cb, client);
-
-	while (1)
-	{
-		if (IsTLS(client) && client->local->ssl != NULL)
-		{
-			length = SSL_read(client->local->ssl, readbuf, sizeof(readbuf));
-
-			if (length < 0)
-			{
-				int err = SSL_get_error(client->local->ssl, length);
-
-				switch (err)
-				{
-				case SSL_ERROR_WANT_WRITE:
-					fd_setselect(fd, FD_SELECT_READ, NULL, client);
-					fd_setselect(fd, FD_SELECT_WRITE, read_packet, client);
-					length = -1;
-					SET_ERRNO(P_EWOULDBLOCK);
-					break;
-				case SSL_ERROR_WANT_READ:
-					fd_setselect(fd, FD_SELECT_READ, read_packet, client);
-					length = -1;
-					SET_ERRNO(P_EWOULDBLOCK);
-					break;
-				case SSL_ERROR_SYSCALL:
-					break;
-				case SSL_ERROR_SSL:
-					if (ERRNO == P_EAGAIN)
-						break;
-				default:
-					/*length = 0;
-					SET_ERRNO(0);
-					^^ why this? we should error. -- todo: is errno correct?
-					*/
-					break;
-				}
-			}
-		}
-		else
-			length = recv(client->local->fd, readbuf, sizeof(readbuf), 0);
-
-		if (length <= 0)
-		{
-			if (length < 0 && ((ERRNO == P_EWOULDBLOCK) || (ERRNO == P_EAGAIN) || (ERRNO == P_EINTR)))
-				return;
-
-			if (IsServer(client) || client->server) /* server or outgoing connection */
-				lost_server_link(client, NULL);
-
-			exit_client(client, NULL, ERRNO ? "Read error" : "Connection closed");
-			return;
-		}
-
-		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);
-
-		ClearPingWarning(client);
-
-		processdata = 1;
-		for (h = Hooks[HOOKTYPE_RAWPACKET_IN]; h; h = h->next)
-		{
-			processdata = (*(h->func.intfunc))(client, readbuf, &length);
-			if (processdata == 0)
-				break; /* if hook tells to ignore the data, then break now */
-			if (processdata < 0)
-				return; /* if hook tells client is dead, return now */
-		}
-
-		if (processdata && !process_packet(client, readbuf, length, 0))
-			return;
-
-		/* bail on short read! */
-		if (length < sizeof(readbuf))
-			return;
-	}
-}
-
-/** Process input from clients that may have been deliberately delayed due to fake lag */
-void process_clients(void)
-{
-	Client *client;
-        
-	/* Problem:
-	 * When processing a client, that current client may exit due to eg QUIT.
-	 * Similarly, current->next may be killed due to /KILL.
-	 * When a client is killed, in the past we were not allowed to touch it anymore
-	 * so that was a bit problematic. Now we can touch current->next, but it may
-	 * have been removed from the lclient_list or unknown_list.
-	 * In other words, current->next->next may be NULL even though there are more
-	 * clients on the list.
-	 * This is why the whole thing is wrapped in an additional do { } while() loop
-	 * to make sure we re-run the list if we ended prematurely.
-	 * We could use some kind of 'tagging' to mark already processed clients.
-	 * However, parse_client_queued() already takes care not to read (fake) lagged
-	 * clients, and we don't actually read/recv anything in the meantime, so clients
-	 * in the beginning of the list won't benefit, they won't get higher prio.
-	 * Another alternative is not to run the loop again, but that WOULD be
-	 * unfair to clients later in the list which wouldn't be processed then
-	 * under a heavy (kill) load scenario.
-	 * I think the chosen solution is best, though it remains silly. -- Syzop
-	 */
-
-	do {
-		list_for_each_entry(client, &lclient_list, lclient_node)
-		{
-			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
-			{
-				parse_client_queued(client);
-				if (IsDead(client))
-					break;
-			}
-		}
-	} while(&client->lclient_node != &lclient_list);
-
-	do {
-		list_for_each_entry(client, &unknown_list, lclient_node)
-		{
-			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
-			{
-				parse_client_queued(client);
-				if (IsDead(client) || (client->status > CLIENT_STATUS_UNKNOWN))
-					break;
-			}
-		}
-	} while(&client->lclient_node != &unknown_list);
-
-	do {
-		list_for_each_entry(client, &control_list, lclient_node)
-		{
-			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
-			{
-				parse_client_queued(client);
-				if (IsDead(client))
-					break;
-			}
-		}
-	} while(&client->lclient_node != &control_list);
-
-
-}
-
-/** Check if 'ip' is a valid IP address, and if so what type.
- * @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(const char *ip)
-{
-	char scratch[64];
-
-	if (BadPtr(ip))
-		return 0;
-
-	if (inet_pton(AF_INET, ip, scratch) == 1)
-		return 4; /* IPv4 */
-
-	if (inet_pton(AF_INET6, ip, scratch) == 1)
-		return 6; /* IPv6 */
-
-	return 0; /* not an IP address */
-}
-
-/** 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..
- */
-int ipv6_capable(void)
-{
-	int s = socket(AF_INET6, SOCK_STREAM, 0);
-	if (s < 0)
-		return 0; /* NO ipv6 */
-
-	CLOSE_SOCK(s);
-	return 1; /* YES */
-}
-
-/** Return 1 if UNIX sockets of type SOCK_STREAM are supported, and 0 otherwise */
-int unix_sockets_capable(void)
-{
-	int fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Testing UNIX socket");
-	if (fd < 0)
-		return 0;
-	fd_close(fd);
-	return 1;
-}
-
-/** Attempt to deliver data to a client.
- * This function is only called from send_queued() and will deal
- * with sending to the TLS or plaintext connection.
- * @param cptr The client
- * @param str  The string to send
- * @param len  The length of the string
- * @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
- *                  there is data ready to be READ.
- * @retval <0  Some fatal error occurred, (but not EWOULDBLOCK).
- *             This return is a request to close the socket and
- *             clean up the link.
- * @retval >=0 No real error occurred, returns the number of
- *             bytes actually transferred. EWOULDBLOCK and other
- *             possibly similar conditions should be mapped to
- *             zero return. Upper level routine will have to
- *             decide what to do with those unwritten bytes...
- */
-int deliver_it(Client *client, char *str, int len, int *want_read)
-{
-	int  retval;
-
-	*want_read = 0;
-
-	if (IsDeadSocket(client) ||
-	    (!IsServer(client) && !IsUser(client) && !IsHandshake(client) &&
-	     !IsTLSHandshake(client) && !IsUnknown(client) &&
-	     !IsControl(client) && !IsRPC(client)))
-	{
-		return -1;
-	}
-
-	if (IsTLS(client) && client->local->ssl != NULL)
-	{
-		retval = SSL_write(client->local->ssl, str, len);
-
-		if (retval < 0)
-		{
-			switch (SSL_get_error(client->local->ssl, retval))
-			{
-			case SSL_ERROR_WANT_READ:
-				SET_ERRNO(P_EWOULDBLOCK);
-				*want_read = 1;
-				return 0;
-			case SSL_ERROR_WANT_WRITE:
-				SET_ERRNO(P_EWOULDBLOCK);
-				break;
-			case SSL_ERROR_SYSCALL:
-				break;
-			case SSL_ERROR_SSL:
-				if (ERRNO == P_EAGAIN)
-					break;
-				/* FALLTHROUGH */
-			default:
-				return -1; /* hm.. why was this 0?? we have an error! */
-			}
-		}
-	}
-	else
-		retval = send(client->local->fd, str, len, 0);
-	/*
-	   ** Convert WOULDBLOCK to a return of "0 bytes moved". This
-	   ** should occur only if socket was non-blocking. Note, that
-	   ** all is Ok, if the 'write' just returns '0' instead of an
-	   ** error and errno=EWOULDBLOCK.
-	   **
-	   ** ...now, would this work on VMS too? --msa
-	 */
-# ifndef _WIN32
-	if (retval < 0 && (errno == EWOULDBLOCK || errno == EAGAIN ||
-	    errno == ENOBUFS))
-# else
-		if (retval < 0 && (WSAGetLastError() == WSAEWOULDBLOCK ||
-		    WSAGetLastError() == WSAENOBUFS))
-# endif
-			retval = 0;
-
-	if (retval > 0)
-	{
-		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, const char *ip, int port, SocketType socket_type)
-{
-	int n;
-
-	if (socket_type == SOCKET_TYPE_IPV6)
-	{
-		struct sockaddr_in6 server;
-		memset(&server, 0, sizeof(server));
-		server.sin6_family = AF_INET6;
-		inet_pton(AF_INET6, ip, &server.sin6_addr);
-		server.sin6_port = htons(port);
-		n = connect(fd, (struct sockaddr *)&server, sizeof(server));
-	}
-	else if (socket_type == SOCKET_TYPE_IPV4)
-	{
-		struct sockaddr_in server;
-		memset(&server, 0, sizeof(server));
-		server.sin_family = AF_INET;
-		inet_pton(AF_INET, ip, &server.sin_addr);
-		server.sin_port = htons(port);
-		n = connect(fd, (struct sockaddr *)&server, sizeof(server));
-	} else
-	{
-		struct sockaddr_un server;
-		memset(&server, 0, sizeof(server));
-		server.sun_family = AF_UNIX;
-		strlcpy(server.sun_path, ip, sizeof(server.sun_path));
-		n = connect(fd, (struct sockaddr *)&server, sizeof(server));
-	}
-
-#ifndef _WIN32
-	if (n < 0 && (errno != EINPROGRESS))
-#else
-	if (n < 0 && (WSAGetLastError() != WSAEINPROGRESS) && (WSAGetLastError() != WSAEWOULDBLOCK))
-#endif
-	{
-		return 0; /* FATAL ERROR */
-	}
-
-	return 1; /* SUCCESS (probably still in progress) */
-}
-
-/** Bind to an IP/port (port may be 0 for auto).
- * @returns 0 on failure, other on success.
- */
-int unreal_bind(int fd, const char *ip, int port, SocketType socket_type)
-{
-	if (socket_type == SOCKET_TYPE_IPV4)
-	{
-		struct sockaddr_in server;
-		memset(&server, 0, sizeof(server));
-		server.sin_family = AF_INET;
-		server.sin_port = htons(port);
-		if (inet_pton(AF_INET, ip, &server.sin_addr.s_addr) != 1)
-			return 0;
-		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
-	}
-	else if (socket_type == SOCKET_TYPE_IPV6)
-	{
-		struct sockaddr_in6 server;
-		memset(&server, 0, sizeof(server));
-		server.sin6_family = AF_INET6;
-		server.sin6_port = htons(port);
-		if (inet_pton(AF_INET6, ip, &server.sin6_addr.s6_addr) != 1)
-			return 0;
-		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
-	} else
-	{
-		struct sockaddr_un server;
-		mode_t saved_umask, new_umask;
-		int ret;
-
-		if (port == 0)
-			new_umask = 077;
-		else
-			new_umask = port ^ 0777;
-
-		unlink(ip); /* (ignore errors) */
-
-		memset(&server, 0, sizeof(server));
-		server.sun_family = AF_UNIX;
-		strlcpy(server.sun_path, ip, sizeof(server.sun_path));
-		saved_umask = umask(new_umask);
-		ret = !bind(fd, (struct sockaddr *)&server, sizeof(server));
-		umask(saved_umask);
-
-		return ret;
-	}
-}
-
-#ifdef _WIN32
-void init_winsock(void)
-{
-	WSADATA WSAData;
-	if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
-	{
-		MessageBox(NULL, "Unable to initialize WinSock", "UnrealIRCD Initalization Error", MB_OK);
-		fprintf(stderr, "Unable to initialize WinSock\n");
-		exit(1);
-	}
-}
-#endif
diff --git a/src/support.c b/src/support.c
@@ -1,1534 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/support.c
- *   Copyright (C) 1990, 1991 Armin Gruner
- *
- *   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 Functions that don't always exist on every OS are provided here.
- * That was the original idea, anyway. Right now it also contains some
- * functions that should probably be in src/misc.c instead.
- * In any case, most functions here don't have any special meaning
- * specific for IRC, they could just as well be used in non-IRC code.
- */
-
-/* support.c 2.21 4/13/94 1990, 1991 Armin Gruner; 1992, 1993 Darren Reed */
-
-#include "unrealircd.h"
-
-extern void outofmemory();
-
-#define is_enabled match
-
-/** Convert integer to string */
-const char *my_itoa(int i)
-{
-	static char buf[128];
-	ircsnprintf(buf, sizeof(buf), "%d", i);
-	return buf;
-}
-
-/** Walk through a string of tokens, using a set of separators.
- * @param save	Pointer used for saving between calls
- * @param str	String to parse (will be altered!)
- * @param fs	Separator character(s)
- * @returns substring (token)
- * @note This function works similar to (but not identical?) to strtok_r().
- * @section Ex1 Example
- * @code
- * for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
- *      unreal_log(ULOG_INFO, "test", "TEST", "Got: $name", log_data_string(name));
- * @endcode
- */
-char *strtoken(char **save, char *str, char *fs)
-{
-	char *pos, *tmp;
-
-	if (str)
-		pos = str;	/* new string scan */
-	else
-		pos = *save; /* keep last position across calls */
-
-	while (pos && *pos && strchr(fs, *pos) != NULL)
-		pos++;		/* skip leading separators */
-
-	if (!pos || !*pos)
-		return (pos = *save = NULL);	/* string contains only sep's */
-
-	tmp = pos;		/* now, keep position of the token */
-
-	while (*pos && strchr(fs, *pos) == NULL)
-		pos++;		/* skip content of the token */
-
-	if (*pos)
-		*pos++ = '\0';	/* remove first sep after the token */
-	else
-		pos = NULL;	/* end of string */
-
-	*save = pos;
-	return (tmp);
-}
-
-/** Walk through a string of tokens, using a set of separators.
- * This is the special version that won't skip/merge tokens,
- * eg "a,,c" would return "a", then "" (empty), then "c".
- * This in contrast to strtoken() which would return "a" and then "c".
- * This strtoken_noskip() will also not skip tokens at the
- * beginning, eg ",,c" would return "" (empty), "" (empty), "c".
- *
- * @param save	Pointer used for saving between calls
- * @param str	String to parse (will be altered!)
- * @param fs	Separator character(s)
- * @returns substring (token)
- * @note This function works similar to (but not identical?) to strtok_r().
- */
-char *strtoken_noskip(char **save, char *str, char *fs)
-{
-	char *pos, *tmp;
-
-	if (str)
-	{
-		pos = str;	/* new string scan */
-	} else {
-		if (*save == NULL)
-		{
-			/* We reached the end of the string */
-			return NULL;
-		}
-		pos = *save; /* keep last position across calls */
-	}
-
-	tmp = pos; /* start position, used for returning later */
-
-	/* Hunt for next separator (fs in pos) */
-	while (*pos && !strchr(fs, *pos))
-		pos++;
-
-	if (!*pos)
-	{
-		/* Next call is end of string */
-		*save = NULL;
-		*pos++ = '\0';
-	} else {
-		*pos++ = '\0';
-		*save = pos;
-	}
-
-	return tmp;
-}
-
-/** Convert binary address to an IP string - like inet_ntop but will always return the uncompressed IPv6 form.
- * @param af	Address family (AF_INET, AF_INET6)
- * @param in	Address (binary)
- * @param out	Buffer to use for storing the returned IP string
- * @param size	Size of the 'out' buffer
- * @returns IP address as a string (IPv4 or IPv6, in case of the latter:
- *          always the uncompressed form without ::)
- */
-const char *inetntop(int af, const void *in, char *out, size_t size)
-{
-	char tmp[MYDUMMY_SIZE];
-
-	inet_ntop(af, in, tmp, size);
-	if (!strstr(tmp, "::"))
-	{
-		/* IPv4 or IPv6 that is already uncompressed */
-		strlcpy(out, tmp, size);
-	} else
-	{
-		char cnt = 0, *cp = tmp, *op = out;
-
-		/* It's an IPv6 compressed address that we need to expand */
-		while (*cp)
-		{
-			if (*cp == ':')
-				cnt += 1;
-			if (*cp++ == '.')
-			{
-				cnt += 1;
-				break;
-			}
-		}
-		cp = tmp;
-		while (*cp)
-		{
-			*op++ = *cp++;
-			if (*(cp - 1) == ':' && *cp == ':')
-			{
-				if ((cp - 1) == tmp)
-				{
-					op--;
-					*op++ = '0';
-					*op++ = ':';
-				}
-
-				*op++ = '0';
-				while (cnt++ < 7)
-				{
-					*op++ = ':';
-					*op++ = '0';
-				}
-			}
-		}
-		if (*(op - 1) == ':')
-			*op++ = '0';
-		*op = '\0';
-	}
-	return out;
-}
-
-/** Cut string off at the first occurance of CR or LF */
-void stripcrlf(char *c)
-{
-	for (; *c; c++)
-	{
-		if ((*c == '\n') || (*c == '\r'))
-		{
-			*c = '\0';
-			return;
-		}
-	}
-}
-
-#ifndef HAVE_STRNLEN
-size_t strnlen(const char *s, size_t maxlen)
-{
-	const char *end = memchr (s, 0, maxlen);
-	return end ? (size_t)(end - s) : maxlen;
-}
-#endif
-
-#ifndef HAVE_STRLCPY
-/** BSD'ish strlcpy().
- * The strlcpy() function copies up to size-1 characters from the
- * NUL-terminated string src to dst, NUL-terminating the result.
- * Return: total length of the string tried to create.
- */
-size_t strlcpy(char *dst, const char *src, size_t size)
-{
-	size_t len = strlen(src);
-	size_t ret = len;
-
-	if (size <= 0)
-		return 0;
-	if (len >= size)
-		len = size - 1;
-	memcpy(dst, src, len);
-	dst[len] = 0;
-
-	return ret;
-}
-#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 = strnlen(src, n);
-	size_t ret = len;
-
-	if (size <= 0)
-		return 0;
-	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
- * dst. It will append at most size - strlen(dst) - 1 bytes, NUL-terminating
- * the result.
- */
-size_t strlcat(char *dst, const char *src, size_t size)
-{
-	size_t len1 = strlen(dst);
-	size_t len2 = strlen(src);
-	size_t ret = len1 + len2;
-
-	if (size <= len1)
-		return size;
-	if (len1 + len2 >= size)
-		len2 = size - (len1 + 1);
-
-	if (len2 > 0) {
-		memcpy(dst + len1, src, len2);
-		dst[len1 + len2] = 0;
-	}
-	
-	return ret;
-}
-#endif
-
-#ifndef HAVE_STRLNCAT
-/** BSD'ish strlncat() - similar to strlcat but never cat more then n characters.
- */
-size_t strlncat(char *dst, const char *src, size_t size, size_t n)
-{
-	size_t len1 = strlen(dst);
-	size_t len2 = strnlen(src, n);
-	size_t ret = len1 + len2;
-
-	if (size <= len1)
-		return size;
-		
-	if (len1 + len2 >= size)
-		len2 = size - (len1 + 1);
-
-	if (len2 > 0) {
-		memcpy(dst + len1, src, len2);
-		dst[len1 + len2] = 0;
-	}
-
-	return ret;
-}
-#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.
- * If you wonder why not use strndup() instead?
- * I feel that mixing code with strlcpy() and strndup() would be
- * rather confusing since strlcpy() assumes buffer size INCLUDING
- * the nul byte and strndup() assumes WITHOUT the nul byte and
- * will write one character extra. Hence this strldup(). -- Syzop
- */
-char *strldup(const char *src, size_t max)
-{
-	char *ptr;
-	int n;
-
-	if ((max == 0) || !src)
-		return NULL;
-
-	n = strlen(src);
-	if (n > max-1)
-		n = max-1;
-
-	ptr = safe_alloc(n+1);
-	memcpy(ptr, src, n);
-	ptr[n] = '\0';
-
-	return ptr;
-}
-
-static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-static const char Pad64 = '=';
-
-/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt)
-   The following encoding technique is taken from RFC 1521 by Borenstein
-   and Freed.  It is reproduced here in a slightly edited form for
-   convenience.
-
-   A 65-character subset of US-ASCII is used, enabling 6 bits to be
-   represented per printable character. (The extra 65th character, "=",
-   is used to signify a special processing function.)
-
-   The encoding process represents 24-bit groups of input bits as output
-   strings of 4 encoded characters. Proceeding from left to right, a
-   24-bit input group is formed by concatenating 3 8-bit input groups.
-   These 24 bits are then treated as 4 concatenated 6-bit groups, each
-   of which is translated into a single digit in the base64 alphabet.
-
-   Each 6-bit group is used as an index into an array of 64 printable
-   characters. The character referenced by the index is placed in the
-   output string.
-
-                         Table 1: The Base64 Alphabet
-
-      Value Encoding  Value Encoding  Value Encoding  Value Encoding
-          0 A            17 R            34 i            51 z
-          1 B            18 S            35 j            52 0
-          2 C            19 T            36 k            53 1
-          3 D            20 U            37 l            54 2
-          4 E            21 V            38 m            55 3
-          5 F            22 W            39 n            56 4
-          6 G            23 X            40 o            57 5
-          7 H            24 Y            41 p            58 6
-          8 I            25 Z            42 q            59 7
-          9 J            26 a            43 r            60 8
-         10 K            27 b            44 s            61 9
-         11 L            28 c            45 t            62 +
-         12 M            29 d            46 u            63 /
-         13 N            30 e            47 v
-         14 O            31 f            48 w         (pad) =
-         15 P            32 g            49 x
-         16 Q            33 h            50 y
-
-   Special processing is performed if fewer than 24 bits are available
-   at the end of the data being encoded.  A full encoding quantum is
-   always completed at the end of a quantity.  When fewer than 24 input
-   bits are available in an input group, zero bits are added (on the
-   right) to form an integral number of 6-bit groups.  Padding at the
-   end of the data is performed using the '=' character.
-
-   Since all base64 input is an integral number of octets, only the
-         -------------------------------------------------                       
-   following cases can arise:
-   
-       (1) the final quantum of encoding input is an integral
-           multiple of 24 bits; here, the final unit of encoded
-	   output will be an integral multiple of 4 characters
-	   with no "=" padding,
-       (2) the final quantum of encoding input is exactly 8 bits;
-           here, the final unit of encoded output will be two
-	   characters followed by two "=" padding characters, or
-       (3) the final quantum of encoding input is exactly 16 bits;
-           here, the final unit of encoded output will be three
-	   characters followed by one "=" padding character.
-   */
-
-/** Base64 encode data.
- * @param src		The data to encode (input)
- * @param srclength	The length of the data to encode (input length)
- * @param target	The output buffer to use (output)
- * @param targetsize	The length of the output buffer to use (maximum output length)
- * @returns length of the targetsize, or -1 in case of error.
- */
-int b64_encode(unsigned char const *src, size_t srclength, char *target, size_t targsize)
-{
-	size_t datalength = 0;
-	u_char input[3];
-	u_char output[4];
-	size_t i;
-
-	while (2 < srclength) {
-		input[0] = *src++;
-		input[1] = *src++;
-		input[2] = *src++;
-		srclength -= 3;
-
-		output[0] = input[0] >> 2;
-		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
-		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
-		output[3] = input[2] & 0x3f;
-
-		if (datalength + 4 > targsize)
-			return (-1);
-		target[datalength++] = Base64[output[0]];
-		target[datalength++] = Base64[output[1]];
-		target[datalength++] = Base64[output[2]];
-		target[datalength++] = Base64[output[3]];
-	}
-    
-	/* Now we worry about padding. */
-	if (0 != srclength) {
-		/* Get what's left. */
-		input[0] = input[1] = input[2] = '\0';
-		for (i = 0; i < srclength; i++)
-			input[i] = *src++;
-	
-		output[0] = input[0] >> 2;
-		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
-		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
-
-		if (datalength + 4 > targsize)
-			return (-1);
-		target[datalength++] = Base64[output[0]];
-		target[datalength++] = Base64[output[1]];
-		if (srclength == 1)
-			target[datalength++] = Pad64;
-		else
-			target[datalength++] = Base64[output[2]];
-		target[datalength++] = Pad64;
-	}
-	if (datalength >= targsize)
-		return (-1);
-	target[datalength] = '\0';	/* Returned value doesn't count \0. */
-	return (datalength);
-}
-
-/** Base64 decode a string.
- * @param src		The data to decode (input)
- * @param srclength	The length of the data to decode (input length)
- * @param target	The output buffer to use (output)
- * @param targetsize	The length of the output buffer to use (maximum output length)
- * @returns length of the targetsize, or -1 in case of error.
- * @note Skips whitespace, and hmm.. I think we don't require padding? Not sure.
- */
-int b64_decode(char const *src, unsigned char *target, size_t targsize)
-{
-	int tarindex, state, ch;
-	char *pos;
-
-	state = 0;
-	tarindex = 0;
-
-	while ((ch = *src++) != '\0') {
-		if (isspace(ch))	/* Skip whitespace anywhere. */
-			continue;
-
-		if (ch == Pad64)
-			break;
-
-		pos = strchr(Base64, ch);
-		if (pos == 0) 		/* A non-base64 character. */
-			return (-1);
-
-		switch (state) {
-		case 0:
-			if (target) {
-				if ((size_t)tarindex >= targsize)
-					return (-1);
-				target[tarindex] = (pos - Base64) << 2;
-			}
-			state = 1;
-			break;
-		case 1:
-			if (target) {
-				if ((size_t)tarindex + 1 >= targsize)
-					return (-1);
-				target[tarindex]   |=  (pos - Base64) >> 4;
-				target[tarindex+1]  = ((pos - Base64) & 0x0f)
-							<< 4 ;
-			}
-			tarindex++;
-			state = 2;
-			break;
-		case 2:
-			if (target) {
-				if ((size_t)tarindex + 1 >= targsize)
-					return (-1);
-				target[tarindex]   |=  (pos - Base64) >> 2;
-				target[tarindex+1]  = ((pos - Base64) & 0x03)
-							<< 6;
-			}
-			tarindex++;
-			state = 3;
-			break;
-		case 3:
-			if (target) {
-				if ((size_t)tarindex >= targsize)
-					return (-1);
-				target[tarindex] |= (pos - Base64);
-			}
-			tarindex++;
-			state = 0;
-			break;
-		default:
-			abort();
-		}
-	}
-
-	/*
-	 * We are done decoding Base-64 chars.  Let's see if we ended
-	 * on a byte boundary, and/or with erroneous trailing characters.
-	 */
-
-	if (ch == Pad64) {		/* We got a pad char. */
-		ch = *src++;		/* Skip it, get next. */
-		switch (state) {
-		case 0:		/* Invalid = in first position */
-		case 1:		/* Invalid = in second position */
-			return (-1);
-
-		case 2:		/* Valid, means one byte of info */
-			/* Skip any number of spaces. */
-			for (; ch != '\0'; ch = *src++)
-				if (!isspace(ch))
-					break;
-			/* Make sure there is another trailing = sign. */
-			if (ch != Pad64)
-				return (-1);
-			ch = *src++;		/* Skip the = */
-			/* Fall through to "single trailing =" case. */
-			/* FALLTHROUGH */
-
-		case 3:		/* Valid, means two bytes of info */
-			/*
-			 * We know this char is an =.  Is there anything but
-			 * whitespace after it?
-			 */
-			for (; ch != '\0'; ch = *src++)
-				if (!isspace(ch))
-					return (-1);
-
-			/*
-			 * Now make sure for cases 2 and 3 that the "extra"
-			 * bits that slopped past the last full byte were
-			 * zeros.  If we don't check them, they become a
-			 * subliminal channel.
-			 */
-			if (target && target[tarindex] != 0)
-				return (-1);
-		}
-	} else {
-		/*
-		 * We ended by seeing the end of the string.  Make sure we
-		 * have no partial bytes lying around.
-		 */
-		if (state != 0)
-			return (-1);
-	}
-
-	return (tarindex);
-}
-
-/* Natural sort case comparison routines. The following copyright header applies:
-  strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
-  Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-
- The code was modified / UnrealIRCderized by Bram Matthys ("Syzop").
- We always have unsigned char and this makes it possible to get rid
- of various stuff.. also re-indent this monster.
-*/
-
-static int compare_right(char const *a, char const *b)
-{
-	int bias = 0;
-
-	/* The longest run of digits wins.  That aside, the greatest
-	 * value wins, but we can't know that it will until we've scanned
-	 * both numbers to know that they have the same magnitude, so we
-	 * remember it in BIAS.
-	 */
-	for (;; a++, b++)
-	{
-		if (!isdigit(*a) && !isdigit(*b))
-			return bias;
-		if (!isdigit(*a))
-			return -1;
-		if (!isdigit(*b))
-			return +1;
-		if (*a < *b)
-		{
-			if (!bias)
-				bias = -1;
-		} else
-		if (*a > *b)
-		{
-			if (!bias)
-				bias = +1;
-		} else
-		if (!*a && !*b)
-			return bias;
-	}
-
-	return 0;
-}
-
-
-static int compare_left(char const *a, char const *b)
-{
-	/* Compare two left-aligned numbers: the first to have a
-	 * different value wins.
-	 */
-	for (;; a++, b++)
-	{
-		if (!isdigit(*a) && !isdigit(*b))
-			return 0;
-		if (!isdigit(*a))
-			return -1;
-		if (!isdigit(*b))
-			return +1;
-		if (*a < *b)
-			return -1;
-		if (*a > *b)
-			return +1;
-	}
-
-	return 0;
-}
-
-static int strnatcmp0(char const *a, char const *b, int fold_case)
-{
-	int ai, bi;
-	char ca, cb;
-	int fractional, result;
-
-	ai = bi = 0;
-	while (1)
-	{
-		ca = a[ai]; cb = b[bi];
-
-		/* skip over leading spaces or zeros */
-		while (isspace(ca))
-			ca = a[++ai];
-
-		while (isspace(cb))
-			cb = b[++bi];
-
-		/* process run of digits */
-		if (isdigit(ca)  &&  isdigit(cb))
-		{
-			fractional = (ca == '0' || cb == '0');
-			if (fractional)
-			{
-				if ((result = compare_left(a+ai, b+bi)) != 0)
-					return result;
-			} else {
-				if ((result = compare_right(a+ai, b+bi)) != 0)
-					return result;
-			}
-		}
-
-		if (!ca && !cb)
-		{
-			/* The strings compare the same.  Perhaps the caller
-			 * will want to call strcmp to break the tie.
-			 */
-			return 0;
-		}
-
-		if (fold_case)
-		{
-			ca = toupper(ca);
-			cb = toupper(cb);
-		}
-
-		if (ca < cb)
-			return -1;
-
-		if (ca > cb)
-			return +1;
-
-		ai++;
-		bi++;
-	}
-}
-
-/** Like strcmp() but with "natural sort", so that for example
- * the string "1.4.10" is seen as higher than "1.4.9"
- * This is the case sensitive version.
- */
-int strnatcmp(char const *a, char const *b)
-{
-	return strnatcmp0(a, b, 0);
-}
-
-/** Like strcmp() but with "natural sort", so that for example
- * the string "1.4.10" is seen as higher than "1.4.9"
- * This is the case insensitive version.
- */
-int strnatcasecmp(char const *a, char const *b)
-{
-	return strnatcmp0(a, b, 1);
-}
-
-/* End of natural sort case comparison functions */
-
-/* Memory allocation routines */
-
-/** Allocate memory - should always be used instead of malloc/calloc.
- * @param size How many bytes to allocate
- * @returns A pointer to the newly allocated memory.
- * @note If out of memory then the IRCd will exit.
- */
-void *safe_alloc(size_t size)
-{
-	void *p;
-	if (size == 0)
-		return NULL;
-	p = calloc(1, size);
-	if (!p)
-		outofmemory(size);
-	return p;
-}
-
-/** Safely duplicate a string */
-char *our_strdup(const char *str)
-{
-	char *ret = strdup(str);
-	if (!ret)
-		outofmemory(strlen(str));
-	return ret;
-}
-
-/** Safely duplicate a string with a maximum size */
-char *our_strldup(const char *str, size_t max)
-{
-	char *ret = strldup(str, max);
-	if (!ret)
-		outofmemory(MAX(strlen(str), max));
-	return ret;
-}
-
-/** Called when out of memory */
-void outofmemory(size_t bytes)
-{
-	static int log_attempt = 1;
-
-	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;
-	}
-	exit(7);
-}
-
-/** Allocate sensitive memory - this should only be used for HIGHLY sensitive data, since
- * it wastes 8192+ bytes even if only asked to allocate for example 32 bytes (this is by design).
- * @param size How many bytes to allocate
- * @returns A pointer to the newly allocated memory.
- * @note If out of memory then the IRCd will exit.
- */
-void *safe_alloc_sensitive(size_t size)
-{
-	void *p;
-	if (size == 0)
-		return NULL;
-	p = sodium_malloc(((size/32)*32)+32);
-	if (!p)
-		outofmemory(size);
-	memset(p, 0, size);
-	return p;
-}
-
-/** Safely duplicate a string */
-char *our_strdup_sensitive(const char *str)
-{
-	char *ret = safe_alloc_sensitive(strlen(str)+1);
-	if (!ret)
-		outofmemory(strlen(str));
-	strcpy(ret, str); /* safe, see above */
-	return ret;
-}
-
-/** Returns a unique filename in the specified directory
- * using the specified suffix. The returned value will
- * be of the form <dir>/<random-hex>.<suffix>
- */
-char *unreal_mktemp(const char *dir, const char *suffix)
-{
-	FILE *fd;
-	unsigned int i;
-	static char tempbuf[PATH_MAX+1];
-
-	for (i = 500; i > 0; i--)
-	{
-		snprintf(tempbuf, PATH_MAX, "%s/%X.%s", dir, getrandom32(), suffix);
-		fd = fopen(tempbuf, "r");
-		if (!fd)
-			return tempbuf;
-		fclose(fd);
-	}
-	config_error("Unable to create temporary file in directory '%s': %s",
-		dir, strerror(errno)); /* eg: permission denied :p */
-	return NULL; 
-}
-
-/** Returns the path portion of the given path/file
- * in the specified location (must be at least PATH_MAX bytes).
- */
-char *unreal_getpathname(const char *filepath, char *path)
-{
-	const char *end = filepath+strlen(filepath);
-
-	while (*end != '\\' && *end != '/' && end > filepath)
-		end--;
-	if (end == filepath)
-		path = NULL;
-	else
-	{
-		int size = end-filepath;
-		if (size >= PATH_MAX)
-			path = NULL;
-		else
-		{
-			memcpy(path, filepath, size);
-			path[size] = 0;
-		}
-	}
-	return path;
-}
-
-/** Returns the filename portion of the given path.
- * The original string is not modified
- */
-const char *unreal_getfilename(const char *path)
-{
-	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;
-}
-
-/** Wrapper for mkdir() so you don't need ifdefs everywhere for Windows.
- * @returns 0 on failure!! (like mkdir)
- */
-int unreal_mkdir(const char *pathname, mode_t mode)
-{
-#ifdef _WIN32
-	return mkdir(pathname);
-#else
-	return mkdir(pathname, mode);
-#endif
-}
-
-/** Create the entire directory structure.
- * @param dname	The directory name, eg /home/irc/unrealircd/logs/2022/08/05
- * @param mode	The mode to create with, eg 0777. Ignored on Windows.
- * @returns 1 on success, 0 on failure.
- */
-int unreal_create_directory_structure(const char *dname, mode_t mode)
-{
-	if (unreal_mkdir(dname, mode) == 0)
-	{
-		/* Ok, that failed as well, we have some work to do:
-		 * for every path element run mkdir().
-		 */
-		int lastresult;
-		char buf[512], *p;
-		strlcpy(buf, dname, sizeof(buf)); /* work on a copy */
-		for (p=strchr(buf+1, '/'); p; p=strchr(p+1, '/'))
-		{
-			*p = '\0';
-			unreal_mkdir(buf,mode);
-			*p = '/';
-		}
-		/* Finally, try the complete path */
-		if (unreal_mkdir(dname, mode))
-			return 0; /* failed */
-		/* fallthrough.... */
-	}
-	return 1; /* success */
-}
-
-/** Create entire directory structure for a path with a filename.
- * @param fname	The full path name, eg /home/irc/unrealircd/logs/2022/08/05/ircd.log
- * @param mode	The mode to create with, eg 0777. Ignored on Windows.
- * @notes This is used as an easier way to call unreal_create_directory_structure()
- *        if you have a filename instead of the directory part.
- * @returns 1 on success, 0 on failure.
- */
-int unreal_create_directory_structure_for_file(const char *fname, mode_t mode)
-{
-	char buf[PATH_MAX+1];
-	const char *path = unreal_getpathname(fname, buf);
-	if (!path)
-		return 0;
-	return unreal_create_directory_structure(path, mode);
-}
-
-/** Returns the special module tmp name for a given path.
- * The original string is not modified.
- */
-const char *unreal_getmodfilename(const char *path)
-{
-	static char ret[512];
-	char buf[512];
-	char *p;
-	char *name = NULL;
-	char *directory = NULL;
-	
-	if (BadPtr(path))
-		return path;
-	
-	strlcpy(buf, path, sizeof(buf));
-	
-	/* Backtrack... */
-	for (p = buf + strlen(buf); p >= buf; p--)
-	{
-		if ((*p == '/') || (*p == '\\'))
-		{
-			name = p+1;
-			*p = '\0';
-			directory = buf; /* fallback */
-			for (; p >= buf; p--)
-			{
-				if ((*p == '/') || (*p == '\\'))
-				{
-					directory = p + 1;
-					break;
-				}
-			}
-			break;
-		}
-	}
-	
-	if (!name)
-		name = buf;
-	
-	if (!directory || !strcmp(directory, "modules"))
-		snprintf(ret, sizeof(ret), "%s", name);
-	else
-		snprintf(ret, sizeof(ret), "%s.%s", directory, name);
-	
-	return ret;
-}
-
-/* Returns a consistent filename for the cache/ directory.
- * Returned value will be like: cache/<hash of url>
- */
-const char *unreal_mkcache(const char *url)
-{
-	static char tempbuf[PATH_MAX+1];
-	char tmp2[128];
-	
-	snprintf(tempbuf, PATH_MAX, "%s/%s", CACHEDIR, sha256hash(tmp2, url, strlen(url)));
-	return tempbuf;
-}
-
-/** Returns 1 if a cached version of the url exists, otherwise 0. */
-int has_cached_version(const char *url)
-{
-	return file_exists(unreal_mkcache(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);
-}
-
-/** Copys the contents of the src file to the dest file.
- * The dest file will have permissions r-x------
- */
-int unreal_copyfile(const char *src, const char *dest)
-{
-	char buf[2048];
-	time_t mtime;
-	int srcfd, destfd, len;
-
-	mtime = unreal_getfilemodtime(src);
-
-#ifndef _WIN32
-	srcfd = open(src, O_RDONLY);
-#else
-	srcfd = open(src, _O_RDONLY|_O_BINARY);
-#endif
-
-	if (srcfd < 0)
-	{
-		config_error("Unable to open file '%s': %s", src, strerror(errno));
-		return 0;
-	}
-
-#ifndef _WIN32
-#if defined(DEFAULT_PERMISSIONS) && (DEFAULT_PERMISSIONS != 0)
-	destfd  = open(dest, O_WRONLY|O_CREAT, DEFAULT_PERMISSIONS);
-#else
-	destfd  = open(dest, O_WRONLY|O_CREAT, S_IRUSR | S_IXUSR);
-#endif /* DEFAULT_PERMISSIONS */
-#else
-	destfd = open(dest, _O_BINARY|_O_WRONLY|_O_CREAT, _S_IWRITE);
-#endif /* _WIN32 */
-	if (destfd < 0)
-	{
-		config_error("Unable to create file '%s': %s", dest, strerror(errno));
-		close(srcfd);
-		return 0;
-	}
-
-	while ((len = read(srcfd, buf, 1023)) > 0)
-		if (write(destfd, buf, len) != len)
-		{
-			config_error("Write error to file '%s': %s [not enough free hd space / quota? need several mb's!]",
-				dest, strerror(ERRNO));
-			cancel_copy(srcfd,destfd,dest);
-			return 0;
-		}
-
-	if (len < 0) /* very unusual.. perhaps an I/O error */
-	{
-		config_error("Read error from file '%s': %s", src, strerror(errno));
-		cancel_copy(srcfd,destfd,dest);
-		return 0;
-	}
-
-	close(srcfd);
-	close(destfd);
-	unreal_setfilemodtime(dest, mtime);
-	return 1;
-}
-
-/** Same as unreal_copyfile, but with an option to try hardlinking first */
-int unreal_copyfileex(const char *src, const char *dest, int tryhardlink)
-{
-	unlink(dest);
-#ifndef _WIN32
-	/* Try a hardlink first... */
-	if (tryhardlink && !link(src, dest))
-		return 1; /* success */
-#endif
-	return unreal_copyfile(src, dest);
-}
-
-/** Set the modification time on a file */
-void unreal_setfilemodtime(const char *filename, time_t mtime)
-{
-#ifndef _WIN32
-	struct utimbuf utb;
-	utb.actime = utb.modtime = mtime;
-	utime(filename, &utb);
-#else
-	FILETIME mTime;
-	LONGLONG llValue;
-	HANDLE hFile = CreateFile(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
-				  FILE_ATTRIBUTE_NORMAL, NULL);
-	if (hFile == INVALID_HANDLE_VALUE)
-		return;
-	llValue = Int32x32To64(mtime, 10000000) + 116444736000000000;
-	mTime.dwLowDateTime = (long)llValue;
-	mTime.dwHighDateTime = llValue >> 32;
-	
-	SetFileTime(hFile, &mTime, &mTime, &mTime);
-	CloseHandle(hFile);
-#endif
-}
-
-/** Get the modification time ("last modified") of a file */
-time_t unreal_getfilemodtime(const char *filename)
-{
-#ifndef _WIN32
-	struct stat sb;
-	if (stat(filename, &sb))
-		return 0;
-	return sb.st_mtime;
-#else
-	/* See how much more fun WinAPI programming is??? */
-	FILETIME cTime;
-	SYSTEMTIME sTime, lTime;
-	ULARGE_INTEGER fullTime;
-	time_t result;
-	HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
-				  FILE_ATTRIBUTE_NORMAL, NULL);
-	if (hFile == INVALID_HANDLE_VALUE)
-		return 0;
-	if (!GetFileTime(hFile, NULL, NULL, &cTime))
-		return 0;
-
-	CloseHandle(hFile);
-
-	FileTimeToSystemTime(&cTime, &sTime);
-	SystemTimeToTzSpecificLocalTime(NULL, &sTime, &lTime);
-	SystemTimeToFileTime(&sTime, &cTime);
-
-	fullTime.LowPart = cTime.dwLowDateTime;
-	fullTime.HighPart = cTime.dwHighDateTime;
-	fullTime.QuadPart -= 116444736000000000;
-	fullTime.QuadPart /= 10000000;
-	
-	return fullTime.LowPart;	
-#endif
-}
-
-#ifndef	AF_INET6
-#define	AF_INET6	AF_MAX+1	/* just to let this compile */
-#endif
-
-/** Encode an IP string (eg: "1.2.3.4") to a BASE64 encoded value for S2S traffic */
-const char *encode_ip(const char *ip)
-{
-	static char retbuf[25]; /* returned string */
-	char addrbuf[16];
-
-	if (!ip)
-		return "*";
-
-	if (strchr(ip, ':'))
-	{
-		/* IPv6 (likely) */
-		inet_pton(AF_INET6, ip, addrbuf);
-		/* hack for IPv4-in-IPv6 (::ffff:1.2.3.4) */
-		if (addrbuf[0] == 0 && addrbuf[1] == 0 && addrbuf[2] == 0 && addrbuf[3] == 0
-		    && addrbuf[4] == 0 && addrbuf[5] == 0 && addrbuf[6] == 0
-			&& addrbuf[7] == 0 && addrbuf[8] == 0 && addrbuf[9] == 0
-			&& addrbuf[10] == 0xff && addrbuf[11] == 0xff)
-		{
-			b64_encode(&addrbuf[12], sizeof(struct in_addr), retbuf, sizeof(retbuf));
-		} else {
-			b64_encode(addrbuf, 16, retbuf, sizeof(retbuf));
-		}
-	}
-	else
-	{
-		/* IPv4 */
-		inet_pton(AF_INET, ip, addrbuf);
-		b64_encode((char *)&addrbuf, sizeof(struct in_addr), retbuf, sizeof(retbuf));
-	}
-	return retbuf;
-}
-
-/** Decode a BASE64 encoded string to an IP address string. Used for S2S traffic. */
-const char *decode_ip(const char *buf)
-{
-	int n;
-	char targ[25];
-	static char result[64];
-
-	n = b64_decode(buf, targ, sizeof(targ));
-	if (n == 4) /* should be IPv4 */
-		return inetntop(AF_INET, targ, result, sizeof(result));
-	else if (n == 16) /* should be IPv6 */
-		return inetntop(AF_INET6, targ, result, sizeof(result));
-	else /* Error! */
-		return NULL;
-}
-
-/* IPv6 stuff */
-
-#ifndef IN6ADDRSZ
-#define	IN6ADDRSZ	16
-#endif
-
-#ifndef INT16SZ
-#define	INT16SZ		 2
-#endif
-
-#ifndef INADDRSZ
-#define	INADDRSZ	 4
-#endif
-
-#ifdef _WIN32
-/* Microsoft makes things nice and fun for us! */
-struct u_WSA_errors {
-	int error_code;
-	char *error_string;
-};
-
-/* Must be sorted ascending by error code */
-struct u_WSA_errors WSAErrors[] = {
- { WSAEINTR,              "Interrupted system call" },
- { WSAEBADF,              "Bad file number" },
- { WSAEACCES,             "Permission denied" },
- { WSAEFAULT,             "Bad address" },
- { WSAEINVAL,             "Invalid argument" },
- { WSAEMFILE,             "Too many open sockets" },
- { WSAEWOULDBLOCK,        "Operation would block" },
- { WSAEINPROGRESS,        "Operation now in progress" },
- { WSAEALREADY,           "Operation already in progress" },
- { WSAENOTSOCK,           "Socket operation on non-socket" },
- { WSAEDESTADDRREQ,       "Destination address required" },
- { WSAEMSGSIZE,           "Message too long" },
- { WSAEPROTOTYPE,         "Protocol wrong type for socket" },
- { WSAENOPROTOOPT,        "Bad protocol option" },
- { WSAEPROTONOSUPPORT,    "Protocol not supported" },
- { WSAESOCKTNOSUPPORT,    "Socket type not supported" },
- { WSAEOPNOTSUPP,         "Operation not supported on socket" },
- { WSAEPFNOSUPPORT,       "Protocol family not supported" },
- { WSAEAFNOSUPPORT,       "Address family not supported" },
- { WSAEADDRINUSE,         "Address already in use" },
- { WSAEADDRNOTAVAIL,      "Can't assign requested address" },
- { WSAENETDOWN,           "Network is down" },
- { WSAENETUNREACH,        "Network is unreachable" },
- { WSAENETRESET,          "Net connection reset" },
- { WSAECONNABORTED,       "Software caused connection abort" },
- { WSAECONNRESET,         "Connection reset by peer" },
- { WSAENOBUFS,            "No buffer space available" },
- { WSAEISCONN,            "Socket is already connected" },
- { WSAENOTCONN,           "Socket is not connected" },
- { WSAESHUTDOWN,          "Can't send after socket shutdown" },
- { WSAETOOMANYREFS,       "Too many references, can't splice" },
- { WSAETIMEDOUT,          "Connection timed out" },
- { WSAECONNREFUSED,       "Connection refused" },
- { WSAELOOP,              "Too many levels of symbolic links" },
- { WSAENAMETOOLONG,       "File name too long" },
- { WSAEHOSTDOWN,          "Host is down" },
- { WSAEHOSTUNREACH,       "No route to host" },
- { WSAENOTEMPTY,          "Directory not empty" },
- { WSAEPROCLIM,           "Too many processes" },
- { WSAEUSERS,             "Too many users" },
- { WSAEDQUOT,             "Disc quota exceeded" },
- { WSAESTALE,             "Stale NFS file handle" },
- { WSAEREMOTE,            "Too many levels of remote in path" },
- { WSASYSNOTREADY,        "Network subsystem is unavailable" },
- { WSAVERNOTSUPPORTED,    "Winsock version not supported" },
- { WSANOTINITIALISED,     "Winsock not yet initialized" },
- { WSAHOST_NOT_FOUND,     "Host not found" },
- { WSATRY_AGAIN,          "Non-authoritative host not found" },
- { WSANO_RECOVERY,        "Non-recoverable errors" },
- { WSANO_DATA,            "Valid name, no data record of requested type" },
- { WSAEDISCON,            "Graceful disconnect in progress" },
- { WSASYSCALLFAILURE,     "System call failure" },
- { 0,NULL}
-};
-
-/** Get socket error string */
-const char *sock_strerror(int error)
-{
-	static char unkerr[64];
-	int start = 0;
-	int stop = sizeof(WSAErrors)/sizeof(WSAErrors[0])-1;
-	int mid;
-	
-	if (!error)
-		return "No error";
-
-	if (error < WSABASEERR) /* Just a regular error code */
-		return strerror(error);
-
-	/* Microsoft decided not to use sequential numbers for the error codes,
-	 * so we can't just use the array index for the code. But, at least
-	 * use a binary search to make it as fast as possible. 
-	 */
-	while (start <= stop)
-	{
-		mid = (start+stop)/2;
-		if (WSAErrors[mid].error_code > error)
-			stop = mid-1;
-		
-		else if (WSAErrors[mid].error_code < error)
-			start = mid+1;
-		else
-			return WSAErrors[mid].error_string;	
-	}
-	snprintf(unkerr, sizeof(unkerr), "Unknown Error: %d", error);
-	return unkerr;
-}
-#endif
-
-/** 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 buildvarstring(const char *inbuf, char *outbuf, size_t len, const char *name[], const char *value[])
-{
-	const char *i, *p;
-	char *o;
-	int left = len - 1;
-	int cnt, found;
-
-#ifdef DEBUGMODE
-	if (len <= 0)
-		abort();
-#endif
-
-	for (i = inbuf, o = outbuf; *i; i++)
-	{
-		if (*i == '$')
-		{
-			i++;
-
-			/* $$ = literal $ */
-			if (*i == '$')
-				goto literal;
-
-			if (!isalnum(*i))
-			{
-				/* What do we do with things like '$/' ? -- treat literal */
-				i--;
-				goto literal;
-			}
-			
-			/* find termination */
-			for (p=i; isalnum(*p); p++);
-			
-			/* find variable name in list */
-			found = 0;
-			for (cnt = 0; name[cnt]; cnt++)
-				if (!strncasecmp(name[cnt], i, strlen(name[cnt])))
-				{
-					/* Found */
-					found = 1;
-
-					if (!BadPtr(value[cnt]))
-					{
-						strlcpy(o, value[cnt], left);
-						left -= strlen(value[cnt]); /* may become <0 */
-						if (left <= 0)
-							return; /* return - don't write \0 to 'o'. ensured by strlcpy already */
-						o += strlen(value[cnt]); /* value entirely written */
-					}
-
-					break; /* done */
-				}
-			
-			if (!found)
-			{
-				/* variable name does not exist -- treat literal */
-				i--;
-				goto literal;
-			}
-
-			/* value written. we're done. */
-			i = p - 1;
-			continue;
-		}
-literal:
-		if (!left)
-			break;
-		*o++ = *i;
-		left--;
-		if (!left)
-			break;
-	}
-	*o = '\0';
-}
-
-/** Return the PCRE2 library version in use */
-const char *pcre2_version(void)
-{
-	static char buf[256];
-
-	strlcpy(buf, "PCRE2 ", sizeof(buf));
-	pcre2_config(PCRE2_CONFIG_VERSION, buf+6);
-	return buf;
-}
-
-
-#ifdef _WIN32
-/** POSIX gettimeofday function for Windows (ignoring timezones) */
-int gettimeofday(struct timeval *tp, void *tzp)
-{
-	// This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
-	// until 00:00:00 January 1, 1970
-	static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);
-
-	SYSTEMTIME system_time;
-	FILETIME file_time;
-	uint64_t time;
-
-	GetSystemTime( &system_time );
-	SystemTimeToFileTime( &system_time, &file_time );
-	time =  ((uint64_t)file_time.dwLowDateTime )      ;
-	time += ((uint64_t)file_time.dwHighDateTime) << 32;
-
-	tp->tv_sec  = (long) ((time - EPOCH) / 10000000L);
-	tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
-	return 0;
-}
-#endif
-
-/** Get the numer of characters per line that fit on the terminal (the width) */
-int get_terminal_width(void)
-{
-#if defined(_WIN32) || !defined(TIOCGWINSZ)
-	return 80;
-#else
-	struct winsize w;
-	ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
-	return w.ws_col;
-#endif
-}
-
-#if defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-#endif
-
-/** Like strftime() but easier. */
-char *unreal_strftime(const char *str)
-{
-	time_t t;
-	struct tm *tmp;
-	static char buf[512];
-
-	t = time(NULL);
-	tmp = localtime(&t);
-	if (!tmp || !strftime(buf, sizeof(buf), str, tmp))
-	{
-		/* If anything fails bigtime, then return the format string */
-		strlcpy(buf, str, sizeof(buf));
-		return buf;
-	}
-	return buf;
-}
-
-#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 */
-	size--; /* for \0 */
-
-	for (; *src && size; src++)
-	{
-		*dst++ = tolower(*src);
-		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
@@ -1,1452 +0,0 @@
-/************************************************************************
- *   Unreal Internet Relay Chat Daemon, src/tls.c
- *      (C) 2000 hq.alert.sk (base)
- *      (C) 2000 Carsten V. Munk <stskeeps@tspre.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.
- */
-
-/** @file
- * @brief TLS functions
- */
-
-#include "unrealircd.h"
-#include "openssl_hostname_validation.h"
-
-#ifdef _WIN32
-#define IDC_PASS                        1166
-extern HINSTANCE hInst;
-extern HWND hwIRCDWnd;
-#endif
-
-#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_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 TLS structures */
-SSL_CTX *ctx_server;
-SSL_CTX *ctx_client;
-
-char *TLSKeyPasswd;
-
-typedef struct {
-	int *size;
-	char **buffer;
-} StreamIO;
-
-MODVAR int tls_client_index = 0;
-
-#ifdef _WIN32
-/** 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) {
-		case WM_INITDIALOG:
-			stream = (StreamIO*)lParam;
-			return TRUE;
-		case WM_COMMAND:
-			if (LOWORD(wParam) == IDCANCEL) {
-				*stream->buffer = NULL;
-				EndDialog(hDlg, IDCANCEL);
-			}
-			else if (LOWORD(wParam) == IDOK) {
-				GetDlgItemText(hDlg, IDC_PASS, *stream->buffer, *stream->size);
-				EndDialog(hDlg, IDOK);
-			}
-			return FALSE;
-		case WM_CLOSE:
-			*stream->buffer = NULL;
-			EndDialog(hDlg, IDCANCEL);
-		default:
-			return FALSE;
-	}
-}
-#endif				
-
-/** Return error string for OpenSSL error.
- * @param err		OpenSSL error number to lookup
- * @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.
- */
-const char *ssl_error_str(int err, int my_errno)
-{
-	static char ssl_errbuf[256];
-	char *ssl_errstr = NULL;
-
-	switch(err)
-	{
-		case SSL_ERROR_NONE:
-			ssl_errstr = "OpenSSL: No error";
-			break;
-		case SSL_ERROR_SSL:
-			ssl_errstr = "Internal OpenSSL error or protocol error";
-			break;
-		case SSL_ERROR_WANT_READ:
-			ssl_errstr = "OpenSSL functions requested a read()";
-			break;
-		case SSL_ERROR_WANT_WRITE:
-			ssl_errstr = "OpenSSL functions requested a write()";
-			break;
-		case SSL_ERROR_WANT_X509_LOOKUP:
-			ssl_errstr = "OpenSSL requested a X509 lookup which didn't arrive";
-			break;
-		case SSL_ERROR_SYSCALL:
-			snprintf(ssl_errbuf, sizeof(ssl_errbuf), "%s", STRERROR(my_errno));
-			ssl_errstr = ssl_errbuf;
-			break;
-		case SSL_ERROR_ZERO_RETURN:
-			ssl_errstr = "Underlying socket operation returned zero";
-			break;
-		case SSL_ERROR_WANT_CONNECT:
-			ssl_errstr = "OpenSSL functions wanted a connect()";
-			break;
-		default:
-			ssl_errstr = "Unknown OpenSSL error (huh?)";
-	}
-	return ssl_errstr;
-}
-
-/** 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;
-	static char beforebuf[1024];
-#ifdef _WIN32
-	StreamIO stream;
-	char passbuf[512];	
-	int passsize = 512;
-#endif
-	if (before)
-	{
-		strlcpy(buf, beforebuf, size);
-		return strlen(buf);
-	}
-#ifndef _WIN32
-	pass = getpass("Password for TLS private key: ");
-#else
-	pass = passbuf;
-	stream.buffer = &pass;
-	stream.size = &passsize;
-	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;
-		TLSKeyPasswd = beforebuf;
-		return (strlen(buf));
-	}
-	return 0;
-}
-
-/** Verify certificate callback. */
-static int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
-{
-	/* We accept the connection. Certificate verifiction takes
-	 * place elsewhere, such as in _verify_link().
-	 */
-	return 1;
-}
-
-/** Get Client pointer by SSL pointer */
-Client *get_client_by_ssl(SSL *ssl)
-{
-	return SSL_get_ex_data(ssl, tls_client_index);
-}
-
-/** Set requested server name as indicated by SNI */
-static void set_client_sni_name(SSL *ssl, char *name)
-{
-	Client *client = get_client_by_ssl(ssl);
-	if (client)
-		safe_strdup(client->local->sni_servername, name);
-}
-
-/** Hostname callback, used for SNI */
-static int ssl_hostname_callback(SSL *ssl, int *unk, void *arg)
-{
-	char *name = (char *)SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-	ConfigItem_sni *sni;
-
-	if (name && (sni = find_sni(name)))
-	{
-		SSL_set_SSL_CTX(ssl, sni->ssl_ctx);
-		set_client_sni_name(ssl, name);
-	}
-
-	return SSL_TLSEXT_ERR_OK;
-}
-
-/** Disable TLS protocols as set by config */
-void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
-{
-	/* OpenSSL has three mechanisms for protocol version control... */
-
-#ifdef HAS_SSL_CTX_SET_SECURITY_LEVEL
-	/* The first one is setting a "security level" as introduced
-	 * by OpenSSL 1.1.0. Some Linux distro's like Ubuntu 20.04
-	 * seemingly compile with -DOPENSSL_TLS_SECURITY_LEVEL=2.
-	 * This means the application (UnrealIRCd) is unable to allow
-	 * TLSv1.0/1.1 even if the application is configured to do so.
-	 * So here we set the level to 1, but -again- ONLY if we are
-	 * configured to allow TLSv1.0 or v1.1, of course.
-	 */
-	if ((tlsoptions->protocols & TLS_PROTOCOL_TLSV1) ||
-	    (tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
-	{
-		SSL_CTX_set_security_level(ctx, 0);
-	}
-#endif
-
-	/* 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 TLS version.
-	 *
-	 * And the new way, which only allows setting a
-	 * minimum and maximum protocol version, using:
-	 * SSL_CTX_set_min_proto_version(... <version>)
-	 * SSL_CTX_set_max_proto_version(....<version>)
-	 *
-	 * We prefer the old way, but because OpenSSL 1.0.1 and
-	 * OS's like Debian use system-wide options we are also
-	 * forced to use the new way... or at least to set a
-	 * minimum protocol version to begin with.
-	 */
-#ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION
-	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1) &&
-	    !(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
-	{
-		SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
-	} else
-	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1))
-	{
-		SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION);
-	} else
-	{
-		SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
-	}
-#endif
-	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); /* always disable SSLv2 */
-	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); /* always disable SSLv3 */
-
-#ifdef SSL_OP_NO_TLSv1
-	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1))
-		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
-#endif
-
-#ifdef SSL_OP_NO_TLSv1_1
-	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
-		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
-#endif
-
-#ifdef SSL_OP_NO_TLSv1_2
-	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_2))
-		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
-#endif
-
-#ifdef SSL_OP_NO_TLSv1_3
-	if (!(tlsoptions->protocols & TLS_PROTOCOL_TLSV1_3))
-		SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
-#endif
-}
-
-/** 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 TLS context (SSL_CTX) or NULL in case of error.
- */
-SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
-{
-	SSL_CTX *ctx;
-	char *errstr = NULL;
-
-	if (server)
-		ctx = SSL_CTX_new(SSLv23_server_method());
-	else
-		ctx = SSL_CTX_new(SSLv23_client_method());
-
-	if (!ctx)
-	{
-		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, 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 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).
-		 */
-		SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE | (tlsoptions->options & TLSFLAG_FAILIFNOCERT ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0), ssl_verify_callback);
-	}
-	SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
-#ifndef SSL_OP_NO_TICKET
- #error "Your system has an outdated OpenSSL version. Please upgrade OpenSSL."
-#endif
-	SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
-
-	if (SSL_CTX_use_certificate_chain_file(ctx, tlsoptions->certificate_file) <= 0)
-	{
-		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)
-	{
-		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))
-	{
-		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)
-	{
-		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)
-	{
-		unreal_log(ULOG_ERROR, "config", "TLS_INVALID_CIPHERSUITES_LIST", NULL,
-		           "Failed to set TLS ciphersuites list '$tls_ciphersuites_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))
-	{
-		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))
-	{
-		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;
-	}
-
-	if (server)
-		SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
-
-	if (tlsoptions->trusted_ca_file)
-	{
-		if (!SSL_CTX_load_verify_locations(ctx, tlsoptions->trusted_ca_file, NULL))
-		{
-			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;
-		}
-	}
-
-	if (server)
-	{
-#if defined(SSL_CTX_set_ecdh_auto)
-		/* OpenSSL 1.0.x requires us to explicitly turn this on */
-		SSL_CTX_set_ecdh_auto(ctx, 1);
-#elif OPENSSL_VERSION_NUMBER < 0x10100000L
-		/* Even older versions require require setting a fixed curve.
-		 * NOTE: Don't be confused by the <1.1.x check.
-		 * Yes, it must be there. Do not remove it!
-		 */
-		SSL_CTX_set_tmp_ecdh(ctx, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
-#else
-		/* If we end up here we don't have SSL_CTX_set_ecdh_auto
-		 * and we are on OpenSSL 1.1.0 or later. We don't need to
-		 * do anything then, since auto ecdh is the default.
-		 */
-#endif
-		/* Let's see if we need to (and can) set specific curves */
-		if (tlsoptions->ecdh_curves)
-		{
-#ifdef HAS_SSL_CTX_SET1_CURVES_LIST
-			if (!SSL_CTX_set1_curves_list(ctx, tlsoptions->ecdh_curves))
-			{
-				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:
-			 */
-			unreal_log(ULOG_ERROR, "config", "BUG_ECDH_CURVES", NULL,
-			           "ecdh-curves specified but not supported by library -- BAD!");
-			goto fail;
-#endif
-		}
-		/* We really want the ECDHE/ECDHE to be generated per-session.
-		 * Added in 2015 for safety. Seems OpenSSL was smart enough
-		 * to make this the default in 2016 after a security advisory.
-		 */
-		SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE|SSL_OP_SINGLE_DH_USE);
-	}
-
-	if (server)
-	{
-		SSL_CTX_set_tlsext_servername_callback(ctx, ssl_hostname_callback);
-	}
-
-	return ctx;
-fail:
-	SSL_CTX_free(ctx);
-	return NULL;
-}
-
-#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: */
-	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_tls(void)
-{
-	ctx_server = init_ctx(iConf.tls_options, 1);
-	if (!ctx_server)
-		return 0;
-	ctx_client = init_ctx(iConf.tls_options, 0);
-	if (!ctx_client)
-		return 0;
-	return 1;
-}
-
-/** Reinitialize TLS server and client contexts - after REHASH -tls
- */
-int reinit_tls(void)
-{
-	SSL_CTX *tmp;
-	ConfigItem_listen *listen;
-	ConfigItem_sni *sni;
-	ConfigItem_link *link;
-
-	tmp = init_ctx(iConf.tls_options, 1);
-	if (!tmp)
-	{
-		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
-		           "TLS Reload failed. See previous errors.");
-		return 0;
-	}
-	if (ctx_server)
-		SSL_CTX_free(ctx_server);
-	ctx_server = tmp; /* activate */
-	
-	tmp = init_ctx(iConf.tls_options, 0);
-	if (!tmp)
-	{
-		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
-		           "TLS Reload failed at client context. See previous errors.");
-		return 0;
-	}
-	if (ctx_client)
-		SSL_CTX_free(ctx_client);
-	ctx_client = tmp; /* activate */
-
-	/* listen::tls-options.... */
-	for (listen = conf_listen; listen; listen = listen->next)
-	{
-		if (listen->tls_options)
-		{
-			tmp = init_ctx(listen->tls_options, 1);
-			if (!tmp)
-			{
-				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
-					   "TLS Reload failed at listen::tls-options. See previous errors.");
-				return 0;
-			}
-			if (listen->ssl_ctx)
-				SSL_CTX_free(listen->ssl_ctx);
-			listen->ssl_ctx = tmp; /* activate */
-		}
-	}
-
-	/* sni::tls-options.... */
-	for (sni = conf_sni; sni; sni = sni->next)
-	{
-		if (sni->tls_options)
-		{
-			tmp = init_ctx(sni->tls_options, 1);
-			if (!tmp)
-			{
-				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
-					   "TLS Reload failed at sni::tls-options. See previous errors.");
-				return 0;
-			}
-			if (sni->ssl_ctx)
-				SSL_CTX_free(sni->ssl_ctx);
-			sni->ssl_ctx = tmp; /* activate */
-		}
-	}
-
-	/* link::outgoing::tls-options.... */
-	for (link = conf_link; link; link = link->next)
-	{
-		if (link->tls_options)
-		{
-			tmp = init_ctx(link->tls_options, 0);
-			if (!tmp)
-			{
-				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 0;
-			}
-			if (link->ssl_ctx)
-				SSL_CTX_free(link->ssl_ctx);
-			link->ssl_ctx = tmp; /* activate */
-		}
-	}
-
-	return 1;
-}
-
-/** Set SSL connection as nonblocking */
-void SSL_set_nonblocking(SSL *s)
-{
-	BIO_set_nbio(SSL_get_rbio(s),1);
-	BIO_set_nbio(SSL_get_wbio(s),1);
-}
-
-/** 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(client->local->ssl), sizeof(buf));
-	strlcat(buf, "-", sizeof(buf));
-	strlcat(buf, SSL_get_cipher(client->local->ssl), sizeof(buf));
-
-	return buf;
-}
-
-/** Get the applicable ::tls-options block for this local client,
- * which may be defined in the link block, listen block, or set block.
- */
-TLSOptions *get_tls_options_for_client(Client *client)
-{
-	if (!client->local)
-		return NULL;
-	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 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->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)
-	{
-		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)
-	{
-		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;
-	}
-
-	SSL_set_fd(client->local->ssl, client->local->fd);
-	SSL_set_connect_state(client->local->ssl);
-	SSL_set_nonblocking(client->local->ssl);
-
-	if (tlsoptions->renegotiate_bytes > 0)
-	{
-		BIO_set_ssl_renegotiate_bytes(SSL_get_rbio(client->local->ssl), tlsoptions->renegotiate_bytes);
-		BIO_set_ssl_renegotiate_bytes(SSL_get_wbio(client->local->ssl), tlsoptions->renegotiate_bytes);
-	}
-
-	if (tlsoptions->renegotiate_timeout > 0)
-	{
-		BIO_set_ssl_renegotiate_timeout(SSL_get_rbio(client->local->ssl), tlsoptions->renegotiate_timeout);
-		BIO_set_ssl_renegotiate_timeout(SSL_get_wbio(client->local->ssl), tlsoptions->renegotiate_timeout);
-	}
-
-	if (client->server && client->server->conf)
-	{
-		/* Client: set hostname for SNI */
-		SSL_set_tlsext_host_name(client->local->ssl, client->server->conf->servername);
-	}
-
-	SetTLS(client);
-
-	switch (unreal_tls_connect(client, fd))
-	{
-		case -1:
-			SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
-			SSL_smart_shutdown(client->local->ssl);
-			SSL_free(client->local->ssl);
-			client->local->ssl = NULL;
-			ClearTLS(client);
-			SetDeadSocket(client);
-			fd_close(fd);
-			fd_unnotify(fd);
-			client->local->fd = -1;
-			--OpenFiles;
-			return;
-		case 0: 
-			SetTLSConnectHandshake(client);
-			return;
-		case 1:
-			return;
-		default:
-			return;
-	}
-
-}
-
-/** 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;
-	unreal_tls_accept(client, fd);
-}
-
-/** Accept an TLS connection - that is: do the TLS handshake */
-int unreal_tls_accept(Client *client, int fd)
-{
-	int ssl_err;
-
-#ifdef MSG_PEEK
-	if (!IsNextCall(client))
-	{
-		char buf[1024];
-		int n;
-		
-		n = recv(fd, buf, sizeof(buf), MSG_PEEK);
-		if ((n >= 8) && !strncmp(buf, "STARTTLS", 8))
-		{
-			char buf[512];
-			snprintf(buf, sizeof(buf),
-				"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_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-TLS command received on TLS-only port. Check your connection settings.\r\n");
-			(void)send(fd, buf, strlen(buf), 0);
-			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-TLS command received on TLS-only port. Check your connection settings.\r\n");
-			(void)send(fd, buf, strlen(buf), 0);
-			return fatal_tls_error(SSL_ERROR_SSL, FUNC_TLS_ACCEPT, ERRNO, client);
-		}
-		if (n > 0)
-			SetNextCall(client);
-	}
-#endif
-	if ((ssl_err = SSL_accept(client->local->ssl)) <= 0)
-	{
-		switch (ssl_err = SSL_get_error(client->local->ssl, ssl_err))
-		{
-			case SSL_ERROR_SYSCALL:
-				if (ERRNO == P_EINTR || ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN)
-				{
-					return 1;
-				}
-				return fatal_tls_error(ssl_err, FUNC_TLS_ACCEPT, ERRNO, client);
-			case SSL_ERROR_WANT_READ:
-				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, unreal_tls_accept_retry, client);
-				return 1;
-			default:
-				return fatal_tls_error(ssl_err, FUNC_TLS_ACCEPT, ERRNO, client);
-		}
-		/* NOTREACHED */
-		return -1;
-	}
-
-	client->local->listener->start_handshake(client);
-
-	return 1;
-}
-
-/** Called by the I/O engine to (re)try to connect to a remote host */
-static void unreal_tls_connect_retry(int fd, int revents, void *data)
-{
-	Client *client = data;
-	unreal_tls_connect(client, fd);
-}
-
-/** Connect to a remote host - that is: connect and do the TLS handshake */
-int unreal_tls_connect(Client *client, int fd)
-{
-	int ssl_err;
-
-	if ((ssl_err = SSL_connect(client->local->ssl)) <= 0)
-	{
-		ssl_err = SSL_get_error(client->local->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(fd, FD_SELECT_READ|FD_SELECT_WRITE, unreal_tls_connect_retry, client);
-					return 0;
-				}
-				return fatal_tls_error(ssl_err, FUNC_TLS_CONNECT, ERRNO, client);
-			case SSL_ERROR_WANT_READ:
-				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, unreal_tls_connect_retry, client);
-				return 0;
-			default:
-				return fatal_tls_error(ssl_err, FUNC_TLS_CONNECT, ERRNO, client);
-		}
-		/* NOTREACHED */
-		return -1;
-	}
-
-	fd_setselect(fd, FD_SELECT_READ | FD_SELECT_WRITE, NULL, client);
-	completed_connection(fd, FD_SELECT_READ | FD_SELECT_WRITE, client);
-
-	return 1;
-}
-
-/** Shutdown a TLS connection (gracefully) */
-int SSL_smart_shutdown(SSL *ssl)
-{
-	char i;
-	int rc = 0;
-
-	for(i = 0; i < 4; i++)
-	{
-		if ((rc = SSL_shutdown(ssl)))
-			break;
-	}
-	return rc;
-}
-
-/**
- * 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_tls_error(int ssl_error, int where, int my_errno, Client *client)
-{
-	/* don`t alter ERRNO */
-	int errtmp = ERRNO;
-	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))
-		return -1;
-
-	switch(where)
-	{
-		case FUNC_TLS_READ:
-			ssl_func = "SSL_read()";
-			break;
-		case FUNC_TLS_WRITE:
-			ssl_func = "SSL_write()";
-			break;
-		case FUNC_TLS_ACCEPT:
-			ssl_func = "SSL_accept()";
-			break;
-		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);
-	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);
-
-	SetDeadSocket(client);
-	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 == FUNC_TLS_CONNECT)
-	{
-		char extra[256];
-		*extra = '\0';
-		if (ssl_error == SSL_ERROR_SSL)
-		{
-			snprintf(extra, sizeof(extra),
-			         ". Please verify that listen::options::ssl is enabled on port %d in %s's configuration file.",
-			         (client->server && client->server->conf) ? client->server->conf->outgoing.port : -1,
-			         client->name);
-		}
-		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->server && client->server->conf))
-	{
-		/* Either a trusted fully established server (incoming) or an outgoing server link (established or not) */
-		snprintf(buf, sizeof(buf), "%s: %s%s", ssl_func, ssl_errstr, additional_info);
-		lost_server_link(client, buf);
-	}
-
-	if (errtmp)
-	{
-		SET_ERRNO(errtmp);
-		safe_strdup(client->local->error_str, strerror(errtmp));
-	} else {
-		SET_ERRNO(P_EIO);
-		safe_strdup(client->local->error_str, ssl_errstr);
-	}
-
-	/* deregister I/O notification since we don't care anymore. the actual closing of socket will happen later. */
-	if (client->local->fd >= 0)
-		fd_unnotify(client->local->fd);
-
-	return -1;
-}
-
-/** Do a TLS handshake after a STARTTLS, as a client */
-int client_starttls(Client *client)
-{
-	if ((client->local->ssl = SSL_new(ctx_client)) == NULL)
-		goto fail_starttls;
-
-	SetTLS(client);
-
-	SSL_set_fd(client->local->ssl, client->local->fd);
-	SSL_set_nonblocking(client->local->ssl);
-
-	if (client->server && client->server->conf)
-	{
-		/* Client: set hostname for SNI */
-		SSL_set_tlsext_host_name(client->local->ssl, client->server->conf->servername);
-	}
-
-	if (unreal_tls_connect(client, client->local->fd) < 0)
-	{
-		SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
-		SSL_smart_shutdown(client->local->ssl);
-		SSL_free(client->local->ssl);
-		goto fail_starttls;
-	}
-
-	/* HANDSHAKE IN PROGRESS */
-	return 0;
-fail_starttls:
-	/* Failure */
-	sendnumeric(client, ERR_STARTTLS, "STARTTLS failed");
-	client->local->ssl = NULL;
-	ClearTLS(client);
-	SetUnknown(client);
-	return 0; /* hm. we allow to continue anyway. not sure if we want that. */
-}
-
-/** Find the appropriate TLSOptions structure for a client.
- * 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)
-{
-	ConfigItem_sni *sni;
-	TLSOptions *sslopt = iConf.tls_options; /* default */
-	
-	if (!MyConnect(client) || !IsSecure(client))
-		return NULL;
-
-	/* Different sts-policy depending on SNI: */
-	if (client->local->sni_servername)
-	{
-		sni = find_sni(client->local->sni_servername);
-		if (sni)
-		{
-			sslopt = sni->tls_options;
-		}
-		/* It is perfectly possible that 'name' is not found and 'sni' is NULL,
-		 * if a client used a hostname which we do not know about (eg: 'dummy').
-		 */
-	}
-
-	return sslopt;
-}
-
-/** Verify certificate and make sure the certificate is valid for 'hostname'.
- * @param ssl: The SSL structure of the client or server
- * @param hostname: The hostname we should expect the certificate to be valid for
- * @param errstr: Error will be stored in here (optional)
- * @returns Returns 1 on success and 0 on error.
- */
-int verify_certificate(SSL *ssl, const char *hostname, char **errstr)
-{
-	static char buf[512];
-	X509 *cert;
-	int n;
-
-	*buf = '\0';
-
-	if (errstr)
-		*errstr = NULL; /* default */
-
-	if (!ssl)
-	{
-		strlcpy(buf, "Not using TLS", sizeof(buf));
-		if (errstr)
-			*errstr = buf;
-		return 0; /* Cannot verify a non-TLS connection */
-	}
-
-	if (SSL_get_verify_result(ssl) != X509_V_OK)
-	{
-		// FIXME: there are actually about 25+ different possible errors,
-		// this is only the most common one:
-		strlcpy(buf, "Certificate is not issued by a trusted Certificate Authority", sizeof(buf));
-		if (errstr)
-			*errstr = buf;
-		return 0; /* Certificate verify failed */
-	}
-
-	/* Now verify if the name of the certificate matches hostname */
-	cert = SSL_get_peer_certificate(ssl);
-
-	if (!cert)
-	{
-		strlcpy(buf, "No certificate provided", sizeof(buf));
-		if (errstr)
-			*errstr = buf;
-		return 0;
-	}
-
-#ifdef HAS_X509_check_host
-	n = X509_check_host(cert, hostname, strlen(hostname), 0, NULL);
-	X509_free(cert);
-	if (n == 1)
-		return 1; /* Hostname matched. All tests passed. */
-#else
-	/* Fallback code for OpenSSL <1.0.2.
-	 * Wait... 1.0.1 is out of support since January 2017,
-	 * so why do we even support that in 2023 ?
-	 * An well, TODO: ditch this old TLS support in next major UnrealIRCd
-	 * along with all the other old OpenSSL checks in this tls.c :D
-	 * XXX: Actually our HAS_X509_check_host includes openssl/x509v3.h
-	 * which does not exist in 1.0.2 yet either (it is openssl/x509.h there).
-	 * And 1.0.2 is out of support since January 1st, 2020... just saying.
-	 */
-	n = validate_hostname(hostname, cert);
-	X509_free(cert);
-	if (n == MatchFound)
-		return 1; /* Hostname matched. All tests passed. */
-#endif
-
-	/* Certificate is verified but is issued for a different hostname */
-	snprintf(buf, sizeof(buf), "Certificate '%s' is not valid for hostname '%s'",
-		certificate_name(ssl), hostname);
-	if (errstr)
-		*errstr = buf;
-	return 0;
-}
-
-/** Grab the certificate name */
-const char *certificate_name(SSL *ssl)
-{
-	static char buf[384];
-	X509 *cert;
-	X509_NAME *n;
-
-	if (!ssl)
-		return NULL;
-
-	cert = SSL_get_peer_certificate(ssl);
-	if (!cert)
-		return NULL;
-
-	n = X509_get_subject_name(cert);
-	if (n)
-	{
-		buf[0] = '\0';
-		X509_NAME_oneline(n, buf, sizeof(buf));
-		X509_free(cert);
-		return buf;
-	} else {
-		X509_free(cert);
-		return NULL;
-	}
-}
-
-/** Check if any weak ciphers are in use */
-int cipher_check(SSL_CTX *ctx, char **errstr)
-{
-	SSL *ssl;
-	static char errbuf[256];
-	int i;
-	const char *cipher;
-
-	*errbuf = '\0'; // safety
-
-	if (errstr)
-		*errstr = errbuf;
-
-	/* there isn't an SSL_CTX_get_cipher_list() unfortunately. */
-	ssl = SSL_new(ctx);
-	if (!ssl)
-	{
-		snprintf(errbuf, sizeof(errbuf), "Could not create TLS structure");
-		return 0;
-	}
-
-	/* Very weak */
-	i = 0;
-	while ((cipher = SSL_get_cipher_list(ssl, i++)))
-	{
-		if (strstr(cipher, "DES-"))
-		{
-			snprintf(errbuf, sizeof(errbuf), "DES is enabled but is a weak cipher");
-			SSL_free(ssl);
-			return 0;
-		}
-		else if (strstr(cipher, "3DES-"))
-		{
-			snprintf(errbuf, sizeof(errbuf), "3DES is enabled but is a weak cipher");
-			SSL_free(ssl);
-			return 0;
-		}
-		else if (strstr(cipher, "RC4-"))
-		{
-			snprintf(errbuf, sizeof(errbuf), "RC4 is enabled but is a weak cipher");
-			SSL_free(ssl);
-			return 0;
-		}
-		else if (strstr(cipher, "NULL-"))
-		{
-			snprintf(errbuf, sizeof(errbuf), "NULL cipher provides no encryption");
-			SSL_free(ssl);
-			return 0;
-		}
-	}
-
-	SSL_free(ssl);
-	return 1;
-}
-
-/** 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;
-	RSA *rsa_key;
-	int key_length;
-	static char errbuf[256];
-
-	*errbuf = '\0'; // safety
-
-	if (errstr)
-		*errstr = errbuf;
-
-	/* there isn't an SSL_CTX_get_cipher_list() unfortunately. */
-	ssl = SSL_new(ctx);
-	if (!ssl)
-	{
-		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 TLS certificate");
-		SSL_free(ssl);
-		return 0;
-	}
-
-	public_key = X509_get_pubkey(cert);
-	if (!public_key)
-	{
-		/* Now this is unexpected.. */
-		config_warn("certificate_quality_check(): could not check public key !? BUG?");
-		SSL_free(ssl);
-		return 1;
-	}
-	rsa_key = EVP_PKEY_get1_RSA(public_key);
-	if (!rsa_key)
-	{
-		/* Not an RSA key, then we are done. */
-		EVP_PKEY_free(public_key);
-		SSL_free(ssl);
-		return 1;
-	}
-	key_length = RSA_size(rsa_key) * 8;
-
-	EVP_PKEY_free(public_key);
-	RSA_free(rsa_key);
-	SSL_free(ssl);
-
-	if (key_length < 2048)
-	{
-		snprintf(errbuf, sizeof(errbuf), "Your TLS certificate key is only %d bits, which is insecure", key_length);
-		return 0;
-	}
-
-#endif
-	return 1;
-}
-
-const char *spki_fingerprint_ex(X509 *x509_cert);
-
-/** Return the SPKI Fingerprint for a client.
- *
- * This is basically the same output as
- * openssl x509 -noout -in certificate.pem -pubkey | openssl asn1parse -noout -inform pem -out public.key
- * openssl dgst -sha256 -binary public.key | openssl enc -base64
- * ( from https://tools.ietf.org/html/draft-ietf-websec-key-pinning-21#appendix-A )
- */
-const char *spki_fingerprint(Client *cptr)
-{
-	X509 *x509_cert = NULL;
-	const char *ret;
-
-	if (!MyConnect(cptr) || !cptr->local->ssl)
-		return NULL;
-
-	x509_cert = SSL_get_peer_certificate(cptr->local->ssl);
-	if (!x509_cert)
-		return NULL;
-	ret = spki_fingerprint_ex(x509_cert);
-	X509_free(x509_cert);
-	return ret;
-}
-
-const char *spki_fingerprint_ex(X509 *x509_cert)
-{
-	unsigned char *der_cert = NULL, *p;
-	int der_cert_len, n;
-	static char retbuf[256];
-	unsigned char checksum[SHA256_DIGEST_LENGTH];
-
-	memset(retbuf, 0, sizeof(retbuf));
-
-	der_cert_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509_cert), NULL);
-	if ((der_cert_len > 0) && (der_cert_len < 16384))
-	{
-		der_cert = p = safe_alloc(der_cert_len);
-		n = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509_cert), &p);
-
-		if ((n > 0) && ((p - der_cert) == der_cert_len))
-		{
-			/* 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.
-			 */
-			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));
-			safe_free(der_cert);
-			return retbuf; /* SUCCESS */
-		}
-		safe_free(der_cert);
-	}
-	return NULL;
-}
-
-/** Returns 1 if the client is using an outdated protocol or cipher, 0 otherwise */
-int outdated_tls_client(Client *client)
-{
-	TLSOptions *tlsoptions = get_tls_options_for_client(client);
-	char buf[1024], *name, *p;
-	const char *client_protocol = SSL_get_version(client->local->ssl);
-	const char *client_ciphersuite = SSL_get_cipher(client->local->ssl);
-
-	if (!tlsoptions)
-		return 0; /* odd.. */
-
-	strlcpy(buf, tlsoptions->outdated_protocols, sizeof(buf));
-	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
-	{
-		if (match_simple(name, client_protocol))
-			 return 1; /* outdated protocol */
-	}
-
-	strlcpy(buf, tlsoptions->outdated_ciphers, sizeof(buf));
-	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
-	{
-		if (match_simple(name, client_ciphersuite))
-			return 1; /* outdated cipher */
-	}
-
-	return 0; /* OK, not outdated */
-}
-
-/** Returns the expanded string used for set::outdated-tls-policy::user-message etc. */
-const char *outdated_tls_client_build_string(const char *pattern, Client *client)
-{
-	static char buf[512];
-	const char *name[3], *value[3];
-	const char *str;
-
-	str = SSL_get_version(client->local->ssl);
-	name[0] = "protocol";
-	value[0] = str ? str : "???";
-
-	str = SSL_get_cipher(client->local->ssl);
-	name[1] = "cipher";
-	value[1] = str ? str : "???";
-
-	name[2] = value[2] = NULL;
-
-	buildvarstring(pattern, buf, sizeof(buf), name, value);
-	return buf;
-}
-
-int check_certificate_expiry_ctx(SSL_CTX *ctx, char **errstr)
-{
-#if !defined(HAS_ASN1_TIME_diff) || !defined(HAS_X509_get0_notAfter)
-	return 0;
-#else
-	static char errbuf[512];
-	SSL *ssl;
-	X509 *cert;
-	const ASN1_TIME *cert_expiry_time;
-	int days_expiry = 0, seconds_expiry = 0;
-	long duration;
-
-	*errstr = NULL;
-
-	ssl = SSL_new(ctx);
-	if (!ssl)
-		return 0;
-
-	cert = SSL_get_certificate(ssl);
-	if (!cert)
-	{
-		SSL_free(ssl);
-		return 0;
-	}
-
-	/* get certificate time */
-	cert_expiry_time = X509_get0_notAfter(cert);
-
-	/* calculate difference */
-	ASN1_TIME_diff(&days_expiry, &seconds_expiry, cert_expiry_time, NULL);
-	duration = (days_expiry * 86400) + seconds_expiry;
-
-	/* certificate expiry? */
-	if ((days_expiry > 0) || (seconds_expiry > 0))
-	{
-		snprintf(errbuf, sizeof(errbuf), "certificate expired %s ago", pretty_time_val(duration));
-		SSL_free(ssl);
-		*errstr = errbuf;
-		return 1;
-	} else
-	/* or near-expiry? */
-	if (((days_expiry < 0) || (seconds_expiry < 0)) && (days_expiry > -7))
-	{
-		snprintf(errbuf, sizeof(errbuf), "certificate will expire in %s", pretty_time_val(0 - duration));
-		SSL_free(ssl);
-		*errstr = errbuf;
-		return 1;
-	}
-
-	/* All good */
-	SSL_free(ssl);
-	return 0;
-#endif
-}
-
-void check_certificate_expiry_tlsoptions_and_warn(TLSOptions *tlsoptions)
-{
-	SSL_CTX *ctx;
-	int ret;
-	char *errstr = NULL;
-
-	ctx = init_ctx(tlsoptions, 1);
-	if (!ctx)
-		return;
-
-	if (check_certificate_expiry_ctx(ctx, &errstr))
-	{
-		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);
-}
-
-EVENT(tls_check_expiry)
-{
-	ConfigItem_listen *listen;
-	ConfigItem_sni *sni;
-	ConfigItem_link *link;
-
-	/* set block */
-	check_certificate_expiry_tlsoptions_and_warn(iConf.tls_options);
-
-	for (listen = conf_listen; listen; listen = listen->next)
-		if (listen->tls_options)
-			check_certificate_expiry_tlsoptions_and_warn(listen->tls_options);
-
-	/* sni::tls-options.... */
-	for (sni = conf_sni; sni; sni = sni->next)
-		if (sni->tls_options)
-			check_certificate_expiry_tlsoptions_and_warn(sni->tls_options);
-
-	/* link::outgoing::tls-options.... */
-	for (link = conf_link; link; link = link->next)
-		if (link->tls_options)
-			check_certificate_expiry_tlsoptions_and_warn(link->tls_options);
-}
diff --git a/src/unrealdb.c b/src/unrealdb.c
@@ -1,1174 +0,0 @@
-/************************************************************************
- * src/unrealdb.c
- * Functions for dealing easily with (encrypted) database files.
- * (C) Copyright 2021 Bram Matthys (Syzop)
- *
- * 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"
-
-/** @file
- * @brief UnrealIRCd database API - see @ref UnrealDBFunctions
- */
-
-/**
- * Read and write to database files - encrypted and unencrypted.
- * This provides functions for dealing with (encrypted) database files.
- * - File format: https://www.unrealircd.org/docs/Dev:UnrealDB
- * - KDF: Argon2: https://en.wikipedia.org/wiki/Argon2
- * - Cipher: XChaCha20 from libsodium: https://libsodium.gitbook.io/doc/advanced/stream_ciphers/xchacha20
- * @defgroup UnrealDBFunctions Database functions
- */
-
-/* Benchmarking results:
- * On standard hardware as of 2021 speeds of 150-200 megabytes per second
- * are achieved realisticly for both reading and writing encrypted
- * database files. Of course, YMMV, depending on record sizes, CPU,
- * and I/O speeds of the underlying hardware.
- */
-
-/* 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 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).
- */
-#define UNREALDB_WRITE_V1
-
-/* If a key is specified, it must be this size */
-#define UNREALDB_KEY_LEN	crypto_secretstream_xchacha20poly1305_KEYBYTES
-
-/** Default 'time cost' for Argon2id */
-#define UNREALDB_ARGON2_DEFAULT_TIME_COST             4
-/** Default 'memory cost' for Argon2id. Note that 15 means 1<<15=32M */
-#define UNREALDB_ARGON2_DEFAULT_MEMORY_COST           15
-/** Default 'parallelism cost' for Argon2id. */
-#define UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST      2
-
-#ifdef _WIN32
-/* Ignore this warning on Windows as it is a false positive */
-#pragma warning(disable : 6029)
-#endif
-
-/* 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;
-
-/** Set error condition on unrealdb 'c' (internal function).
- * @param c		The unrealdb file handle
- * @param pattern	The format string
- * @param ...		Any parameters to the format string
- * @note this will also set c->failed=1 to prevent any further reading/writing.
- */
-static void unrealdb_set_error(UnrealDB *c, UnrealDBError errcode, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	char buf[512];
-	va_start(vl, pattern);
-	vsnprintf(buf, sizeof(buf), pattern, vl);
-	va_end(vl);
-	if (c)
-	{
-		c->error_code = errcode;
-		safe_strdup(c->error_string, buf);
-	}
-	unrealdb_last_error_code = errcode;
-	safe_strdup(unrealdb_last_error_string, buf);
-}
-
-/** Free a UnrealDB struct (internal function). */
-static void unrealdb_free(UnrealDB *c)
-{
-	unrealdb_free_config(c->config);
-	safe_free(c->error_string);
-	safe_free_sensitive(c);
-}
-
-static int unrealdb_kdf(UnrealDB *c, Secret *secr)
-{
-	if (c->config->kdf != UNREALDB_KDF_ARGON2ID)
-	{
-		unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Unknown KDF 0x%x", (int)c->config->kdf);
-		return 0;
-	}
-	/* Need to run argon2 to generate key */
-	if (argon2id_hash_raw(c->config->t_cost,
-			      1 << c->config->m_cost,
-			      c->config->p_cost,
-			      secr->password, strlen(secr->password),
-			      c->config->salt, c->config->saltlen,
-			      c->config->key, c->config->keylen) != ARGON2_OK)
-	{
-		/* out of memory or some other very unusual error */
-		unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Could not generate argon2 hash - out of memory or something weird?");
-		return 0;
-	}
-	return 1;
-}
-
-/**
- * @addtogroup UnrealDBFunctions
- * @{
- */
-
-/** Get the error string for last failed unrealdb operation.
- * @returns The error string
- * @note Use the return value only for displaying of errors
- *       to the end-user.
- *       For programmatically checking of error conditions
- *       use unrealdb_get_error_code() instead.
- */
-const char *unrealdb_get_error_string(void)
-{
-	return unrealdb_last_error_string;
-}
-
-/** Get the error code for last failed unrealdb operation
- * @returns An UNREAL_DB_ERROR_*
- */
-UnrealDBError unrealdb_get_error_code(void)
-{
-	return unrealdb_last_error_code;
-}
-
-/** Open an unrealdb file.
- * @param filename	The filename to open
- * @param mode		Either UNREALDB_MODE_READ or UNREALDB_MODE_WRITE
- * @param secret_block	The name of the secret xx { } block (so NOT the actual password!!)
- * @returns A pointer to a UnrealDB structure that can be used in subsequent calls for db read/writes,
- *          and finally unrealdb_close(). Or NULL in case of failure.
- * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
- *       unrealdb_get_error_string() to see the actual error.
- */
-UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block)
-{
-	UnrealDB *c = safe_alloc_sensitive(sizeof(UnrealDB));
-	char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
-	char buf[32]; /* don't change this */
-	Secret *secr=NULL;
-	SecretCache *dbcache;
-	int cached = 0;
-	char *err;
-
-	errno = 0;
-
-	if ((mode != UNREALDB_MODE_READ) && (mode != UNREALDB_MODE_WRITE))
-	{
-		unrealdb_set_error(c, UNREALDB_ERROR_API, "unrealdb_open request for neither read nor write");
-		goto unrealdb_open_fail;
-	}
-
-	/* Do this check early, before we try to create any file */
-	if (secret_block != NULL)
-	{
-		secr = find_secret(secret_block);
-		if (!secr)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Secret block '%s' not found or invalid", secret_block);
-			goto unrealdb_open_fail;
-		}
-
-		if (!valid_secret_password(secr->password, &err))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Password in secret block '%s' does not meet complexity requirements", secr->name);
-			goto unrealdb_open_fail;
-		}
-	}
-
-	c->mode = mode;
-	c->fd = fopen(filename, (c->mode == UNREALDB_MODE_WRITE) ? "wb" : "rb");
-	if (!c->fd)
-	{
-		if (errno == ENOENT)
-			unrealdb_set_error(c, UNREALDB_ERROR_FILENOTFOUND, "File not found: %s", strerror(errno));
-		else
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Could not open file: %s", strerror(errno));
-		goto unrealdb_open_fail;
-	}
-
-	if (secret_block == NULL)
-	{
-		if (mode == UNREALDB_MODE_READ)
-		{
-			/* READ: read header, if any, lots of fallback options here... */
-			if (fgets(buf, sizeof(buf), c->fd))
-			{
-				if (!strncmp(buf, "UnrealIRCd-DB-Crypted", 21))
-				{
-					unrealdb_set_error(c, UNREALDB_ERROR_CRYPTED, "file is encrypted but no password provided");
-					goto unrealdb_open_fail;
-				} else
-				if (!strcmp(buf, "UnrealIRCd-DB-v1"))
-				{
-					/* Skip over the 32 byte header, directly to the creationtime */
-					if (fseek(c->fd, 32L, SEEK_SET) < 0)
-					{
-						unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "file header too short");
-						goto unrealdb_open_fail;
-					}
-					if (!unrealdb_read_int64(c, &c->creationtime))
-					{
-						unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (A4)");
-						goto unrealdb_open_fail;
-					}
-					/* SUCCESS = fallthrough */
-				} else
-				if (!strncmp(buf, "UnrealIRCd-DB", 13)) /* any other version than v1 = not supported by us */
-				{
-					/* We don't support this format, so refuse clearly */
-					unrealdb_set_error(c, UNREALDB_ERROR_HEADER,
-							   "Unsupported version of database. Is this database perhaps created on "
-							   "a new version of UnrealIRCd and are you trying to use it on an older "
-							   "UnrealIRCd version? (Downgrading is not supported!)");
-					goto unrealdb_open_fail;
-				} else
-				{
-					/* Old db format, no header, seek back to beginning */
-					fseek(c->fd, 0L, SEEK_SET);
-					/* SUCCESS = fallthrough */
-				}
-			}
-		} else {
-#ifdef UNREALDB_WRITE_V1
-			/* WRITE */
-			memset(buf, 0, sizeof(buf));
-			snprintf(buf, sizeof(buf), "UnrealIRCd-DB-v1");
-			if ((fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf)) ||
-			    !unrealdb_write_int64(c, TStime()))
-			{
-				unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (A1)");
-				goto unrealdb_open_fail;
-			}
-#endif
-		}
-		safe_free(unrealdb_last_error_string);
-		unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
-		return c;
-	}
-
-	c->crypted = 1;
-
-	if (c->mode == UNREALDB_MODE_WRITE)
-	{
-		/* Write the:
-		 * - generic header ("UnrealIRCd-DB" + some zeroes)
-		 * - the salt
-		 * - the crypto header
-		 */
-		memset(buf, 0, sizeof(buf));
-		snprintf(buf, sizeof(buf), "UnrealIRCd-DB-Crypted-v1");
-		if (fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (1)");
-			goto unrealdb_open_fail; /* Unable to write header nr 1 */
-		}
-
-		if (secr->cache && secr->cache->config)
-		{
-			/* Use first found cached config for this secret */
-			c->config = unrealdb_copy_config(secr->cache->config);
-			cached = 1;
-		} else {
-			/* Create a new config */
-			c->config = safe_alloc(sizeof(UnrealDBConfig));
-			c->config->kdf = UNREALDB_KDF_ARGON2ID;
-			c->config->t_cost = UNREALDB_ARGON2_DEFAULT_TIME_COST;
-			c->config->m_cost = UNREALDB_ARGON2_DEFAULT_MEMORY_COST;
-			c->config->p_cost = UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST;
-			c->config->saltlen = UNREALDB_SALT_LEN;
-			c->config->salt = safe_alloc(c->config->saltlen);
-			randombytes_buf(c->config->salt, c->config->saltlen);
-			c->config->cipher = UNREALDB_CIPHER_XCHACHA20;
-			c->config->keylen = UNREALDB_KEY_LEN;
-			c->config->key = safe_alloc_sensitive(c->config->keylen);
-		}
-
-		if (c->config->kdf == 0)
-			abort();
-
-		/* Write KDF and cipher parameters */
-		if ((fwrite(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
-		    (fwrite(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
-		    (fwrite(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
-		    (fwrite(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
-		    (fwrite(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)) ||
-		    (fwrite(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen) ||
-		    (fwrite(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
-		    (fwrite(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (2)");
-			goto unrealdb_open_fail;
-		}
-		
-		if (cached)
-		{
-#ifdef DEBUGMODE
-			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
-			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))
-			{
-				/* Error already set by called function */
-				goto unrealdb_open_fail;
-			}
-		}
-
-		crypto_secretstream_xchacha20poly1305_init_push(&c->st, header, c->config->key);
-		if (fwrite(header, 1, sizeof(header), c->fd) != sizeof(header))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (3)");
-			goto unrealdb_open_fail; /* Unable to write crypto header */
-		}
-		if (!unrealdb_write_str(c, "UnrealIRCd-DB-Crypted-Now") ||
-		    !unrealdb_write_int64(c, TStime()))
-		{
-			/* error is already set by unrealdb_write_str() */
-			goto unrealdb_open_fail; /* Unable to write crypto header */
-		}
-		if (!cached)
-			unrealdb_add_to_secret_cache(secr, c->config);
-	} else
-	{
-		char *validate = NULL;
-		
-		/* Read file header */
-		if (fread(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file (file too small)");
-			goto unrealdb_open_fail; /* Header too short */
-		}
-		if (strncmp(buf, "UnrealIRCd-DB-Crypted-v1", 24))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file");
-			goto unrealdb_open_fail; /* Invalid header */
-		}
-		c->config = safe_alloc(sizeof(UnrealDBConfig));
-		if ((fread(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
-		    (fread(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
-		    (fread(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
-		    (fread(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
-		    (fread(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid");
-			goto unrealdb_open_fail;
-		}
-		if (c->config->kdf != UNREALDB_KDF_ARGON2ID) 
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown KDF 0x%x", (int)c->config->kdf);
-			goto unrealdb_open_fail;
-		}
-		if (c->config->saltlen > 1024)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (saltlen=%d)", (int)c->config->saltlen);
-			goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
-		}
-		c->config->salt = safe_alloc(c->config->saltlen);
-		if (fread(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (2)");
-			goto unrealdb_open_fail; /* Header too short (read II) */
-		}
-		if ((fread(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
-		    (fread(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid (3)");
-			goto unrealdb_open_fail;
-		}
-		if (c->config->cipher != UNREALDB_CIPHER_XCHACHA20)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown cipher 0x%x", (int)c->config->cipher);
-			goto unrealdb_open_fail;
-		}
-		if (c->config->keylen > 1024)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (keylen=%d)", (int)c->config->keylen);
-			goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
-		}
-		c->config->key = safe_alloc_sensitive(c->config->keylen);
-
-		dbcache = find_secret_cache(secr, c->config);
-		if (dbcache)
-		{
-			/* Use cached key, no need to run expensive argon2.. */
-			memcpy(c->config->key, dbcache->config->key, c->config->keylen);
-#ifdef DEBUGMODE
-			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
-			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))
-			{
-				/* Error already set by called function */
-				goto unrealdb_open_fail;
-			}
-		}
-		/* key is now set */
-		if (fread(header, 1, sizeof(header), c->fd) != sizeof(header))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (3)");
-			goto unrealdb_open_fail; /* Header too short */
-		}
-		if (crypto_secretstream_xchacha20poly1305_init_pull(&c->st, header, c->config->key) != 0)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Crypto error - invalid password or corrupt file");
-			goto unrealdb_open_fail; /* Unusual */
-		}
-		/* Now to validate the key we read a simple string */
-		if (!unrealdb_read_str(c, &validate))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
-			goto unrealdb_open_fail; /* Incorrect key, probably */
-		}
-		if (strcmp(validate, "UnrealIRCd-DB-Crypted-Now"))
-		{
-			safe_free(validate);
-			unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
-			goto unrealdb_open_fail; /* Incorrect key, probably */
-		}
-		safe_free(validate);
-		if (!unrealdb_read_int64(c, &c->creationtime))
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (4)");
-			goto unrealdb_open_fail;
-		}
-		unrealdb_add_to_secret_cache(secr, c->config);
-	}
-	sodium_stackzero(1024);
-	safe_free(unrealdb_last_error_string);
-	unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
-	return c;
-
-unrealdb_open_fail:
-	if (c->fd)
-		fclose(c->fd);
-	unrealdb_free(c);
-	sodium_stackzero(1024);
-	return NULL;
-}
-
-/** Close an unrealdb file.
- * @param c	The struct pointing to an unrealdb file
- * @returns 1 if the final close was graceful and 0 if not (eg: out of disk space on final flush).
- *          In all cases the file handle is closed and 'c' is freed.
- * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
- *       unrealdb_get_error_string() to see the actual error.
- */
-int unrealdb_close(UnrealDB *c)
-{
-	/* If this is file was opened for writing then flush the remaining data with a TAG_FINAL
-	 * (or push a block of 0 bytes with TAG_FINAL)
-	 */
-	if (c->crypted && (c->mode == UNREALDB_MODE_WRITE))
-	{
-		char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
-		unsigned long long out_len = sizeof(buf_out);
-
-		crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, c->buflen, NULL, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL);
-		if (out_len > 0)
-		{
-			if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
-			{
-				/* Final write failed, error condition */
-				unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
-				fclose(c->fd);
-				unrealdb_free(c);
-				return 0;
-			}
-		}
-	}
-
-	if (fclose(c->fd) != 0)
-	{
-		/* Final close failed, error condition */
-		unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
-		unrealdb_free(c);
-		return 0;
-	}
-
-	unrealdb_free(c);
-	return 1;
-}
-
-/** Test if there is something fatally wrong with the configuration of the DB file,
- * in which case we suggest to reject the /rehash or boot request.
- * This tests for "wrong password" and for "trying to open an encrypted file without providing a password"
- * which are clear configuration errors on the admin part.
- * It does NOT test for any other conditions such as missing file, corrupted file, etc.
- * since that usually needs different handling anyway, as they are I/O issues and don't
- * always have a clear solution (if any is needed at all).
- * @param filename	The filename to open
- * @param secret_block	The name of the secret xx { } block (so NOT the actual password!!)
- * @returns 1 if the password was wrong, 0 for any other error or succes.
- */
-char *unrealdb_test_db(const char *filename, char *secret_block)
-{
-	static char buf[512];
-	UnrealDB *db = unrealdb_open(filename, UNREALDB_MODE_READ, secret_block);
-	if (!db)
-	{
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_PASSWORD)
-		{
-			snprintf(buf, sizeof(buf), "Incorrect password specified in secret block '%s' for file %s",
-				secret_block, filename);
-			return buf;
-		}
-		if (unrealdb_get_error_code() == UNREALDB_ERROR_CRYPTED)
-		{
-			snprintf(buf, sizeof(buf), "File '%s' is encrypted but no secret block provided for it",
-				filename);
-			return buf;
-		}
-		return NULL;
-	} else
-	{
-		unrealdb_close(db);
-	}
-	return NULL;
-}
-
-/** @} */
-
-/** Write to an unrealdb file.
- * This code uses extra buffering to avoid writing small records
- * and wasting for example a 32 bytes encryption block for a 8 byte write request.
- * @param c		Database file open for writing
- * @param wbuf		The data to be written (plaintext)
- * @param len		The length of the data to be written
- * @note This is the internal function, api users must use one of the
- *       following functions instead:
- *       unrealdb_write_int64(), unrealdb_write_int32(), unrealdb_write_int16(),
- *       unrealdb_write_char(), unrealdb_write_str().
- */
-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;
-	const char *buf = wbuf;
-
-	if (c->error_code)
-		return 0;
-
-	if (c->mode != UNREALDB_MODE_WRITE)
-	{
-		unrealdb_set_error(c, UNREALDB_ERROR_API, "Write operation requested on a file opened for reading");
-		return 0;
-	}
-
-	if (!c->crypted)
-	{
-		if (fwrite(buf, 1, len, c->fd) != len)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
-			return 0;
-		}
-		return 1;
-	}
-
-	do {
-		if (c->buflen + len < UNREALDB_CRYPT_FILE_CHUNK_SIZE)
-		{
-			/* New data fits in new buffer. Then we are done with writing.
-			 * This can happen both for the first block (never write)
-			 * or the remainder (tail after X writes which is less than
-			 * UNREALDB_CRYPT_FILE_CHUNK_SIZE, a common case)
-			 */
-			memcpy(c->buf + c->buflen, buf, len);
-			c->buflen += len;
-			break; /* Done! */
-		} else
-		{
-			/* Fill up c->buf with UNREALDB_CRYPT_FILE_CHUNK_SIZE
-			 * Note that 'av_bytes' can be 0 here if c->buflen
-			 * happens to be exactly UNREALDB_CRYPT_FILE_CHUNK_SIZE,
-			 * that's okay.
-			 */
-			int av_bytes = UNREALDB_CRYPT_FILE_CHUNK_SIZE - c->buflen;
-			if (av_bytes > 0)
-				memcpy(c->buf + c->buflen, buf, av_bytes);
-			buf += av_bytes;
-			len -= av_bytes;
-		}
-		if (crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, UNREALDB_CRYPT_FILE_CHUNK_SIZE, NULL, 0, 0) != 0)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Failed to encrypt a block");
-			return 0;
-		}
-		if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
-			return 0;
-		}
-		/* Buffer is now flushed for sure */
-		c->buflen = 0;
-	} while(len > 0);
-
-	return 1;
-}
-
-/**
- * @addtogroup UnrealDBFunctions
- * @{
- */
-
-/** Write a string to a database file.
- * @param c	UnrealDB file struct
- * @param x	String to be written
- * @note  This function can write a string up to 65534
- *        characters, which should be plenty for usage
- *        in UnrealIRCd.
- *        Note that 'x' can safely be NULL.
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_write_str(UnrealDB *c, const char *x)
-{
-	uint16_t len;
-
-	/* First, make sure the string is not too large (would be very unusual, though) */
-	if (x)
-	{
-		int stringlen = strlen(x);
-		if (stringlen >= 0xffff)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_API,
-					   "unrealdb_write_str(): string has length %d, while maximum allowed is 65534",
-					   stringlen);
-			return 0;
-		}
-		len = stringlen;
-	} else {
-		len = 0xffff;
-	}
-
-	/* Write length to db as 16 bit integer */
-	if (!unrealdb_write_int16(c, len))
-		return 0;
-
-	/* Then, write the actual string (if any), without NUL terminator. */
-	if ((len > 0) && (len < 0xffff))
-	{
-		if (!unrealdb_write(c, x, len))
-			return 0;
-	}
-
-	return 1;
-}
-
-/** Write a 64 bit integer to a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to write
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_write_int64(UnrealDB *c, uint64_t t)
-{
-#ifdef NATIVE_BIG_ENDIAN
-	t = bswap_64(t);
-#endif
-	return unrealdb_write(c, &t, sizeof(t));
-}
-
-/** Write a 32 bit integer to a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to write
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_write_int32(UnrealDB *c, uint32_t t)
-{
-#ifdef NATIVE_BIG_ENDIAN
-	t = bswap_32(t);
-#endif
-	return unrealdb_write(c, &t, sizeof(t));
-}
-
-/** Write a 16 bit integer to a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to write
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_write_int16(UnrealDB *c, uint16_t t)
-{
-#ifdef NATIVE_BIG_ENDIAN
-	t = bswap_16(t);
-#endif
-	return unrealdb_write(c, &t, sizeof(t));
-}
-
-/** Write a single 8 bit character to a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to write
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_write_char(UnrealDB *c, char t)
-{
-	return unrealdb_write(c, &t, sizeof(t));
-}
-
-/** @} */
-
-/** Read from an UnrealDB file.
- * This code deals with buffering, block reading, etc. so the caller doesn't
- * have to worry about that.
- * @param c		Database file open for reading
- * @param rbuf		The data to be read (will be plaintext)
- * @param len		The length of the data to be read
- * @note This is the internal function, api users must use one of the
- *       following functions instead:
- *       unrealdb_read_int64(), unrealdb_read_int32(), unrealdb_read_int16(),
- *       unrealdb_read_char(), unrealdb_read_str().
- */
-static int unrealdb_read(UnrealDB *c, void *rbuf, int len)
-{
-	char buf_in[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
-	unsigned long long out_len;
-	unsigned char tag;
-	size_t rlen;
-	char *buf = rbuf;
-
-	if (c->error_code)
-		return 0;
-
-	if (c->mode != UNREALDB_MODE_READ)
-	{
-		unrealdb_set_error(c, UNREALDB_ERROR_API, "Read operation requested on a file opened for writing");
-		return 0;
-	}
-
-	if (!c->crypted)
-	{
-		rlen = fread(buf, 1, len, c->fd);
-		if (rlen < len)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file (want:%d, got:%d bytes)",
-				len, (int)rlen);
-			return 0;
-		}
-		return 1;
-	}
-
-	/* First, fill 'buf' up with what we have */
-	if (c->buflen)
-	{
-		int av_bytes = MIN(c->buflen, len);
-		memcpy(buf, c->buf, av_bytes);
-		if (c->buflen - av_bytes > 0)
-			memmove(c->buf, c->buf + av_bytes, c->buflen - av_bytes);
-		c->buflen -= av_bytes;
-		len -= av_bytes;
-		if (len == 0)
-			return 1; /* Request completed entirely */
-		buf += av_bytes;
-	}
-
-	if (c->buflen != 0)
-		abort();
-
-	/* If we get here then we need to read some data */
-	do {
-		rlen = fread(buf_in, 1, sizeof(buf_in), c->fd);
-		if (rlen == 0)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file??");
-			return 0;
-		}
-		if (crypto_secretstream_xchacha20poly1305_pull(&c->st, c->buf, &out_len, &tag, buf_in, rlen, NULL, 0) != 0)
-		{
-			unrealdb_set_error(c, UNREALDB_ERROR_IO, "Failed to decrypt a block - either corrupt or wrong key");
-			return 0;
-		}
-
-		/* This should be impossible as this is guaranteed not to happen by libsodium */
-		if (out_len > UNREALDB_CRYPT_FILE_CHUNK_SIZE)
-			abort();
-
-		if (len > out_len)
-		{
-			/* We eat a big block, but want more in next iteration of the loop */
-			memcpy(buf, c->buf, out_len);
-			buf += out_len;
-			len -= out_len;
-		} else {
-			/* This is the only (or last) block we need, we are satisfied */
-			memcpy(buf, c->buf, len);
-			c->buflen = out_len - len;
-			if (c->buflen > 0)
-				memmove(c->buf, c->buf+len, c->buflen);
-			return 1; /* Done */
-		}
-	} while(!feof(c->fd));
-
-	unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file?");
-	return 0;
-}
-
-/**
- * @addtogroup UnrealDBFunctions
- * @{
- */
-
-/** Read a 64 bit integer from a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to read
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_read_int64(UnrealDB *c, uint64_t *t)
-{
-	if (!unrealdb_read(c, t, sizeof(uint64_t)))
-		return 0;
-#ifdef NATIVE_BIG_ENDIAN
-	*t = bswap_64(*t);
-#endif
-	return 1;
-}
-
-/** Read a 32 bit integer from a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to read
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_read_int32(UnrealDB *c, uint32_t *t)
-{
-	if (!unrealdb_read(c, t, sizeof(uint32_t)))
-		return 0;
-#ifdef NATIVE_BIG_ENDIAN
-	*t = bswap_32(*t);
-#endif
-	return 1;
-}
-
-/** Read a 16 bit integer from a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to read
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_read_int16(UnrealDB *c, uint16_t *t)
-{
-	if (!unrealdb_read(c, t, sizeof(uint16_t)))
-		return 0;
-#ifdef NATIVE_BIG_ENDIAN
-	*t = bswap_16(*t);
-#endif
-	return 1;
-}
-
-/** Read a string from a database file.
- * @param c    UnrealDB file struct
- * @param x    Pointer to string pointer
- * @note  This function will allocate memory for the data
- *        and set the string pointer to this value.
- *        If a NULL pointer was written via write_str()
- *        then read_str() may also return a NULL pointer.
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_read_str(UnrealDB *c, char **x)
-{
-	uint16_t len;
-	size_t size;
-
-	*x = NULL;
-
-	if (!unrealdb_read_int16(c, &len))
-		return 0;
-
-	if (len == 0xffff)
-	{
-		/* Magic value meaning NULL */
-		*x = NULL;
-		return 1;
-	}
-
-	if (len == 0)
-	{
-		/* 0 means empty string */
-		safe_strdup(*x, "");
-		return 1;
-	}
-
-	if (len > 10000)
-		return 0;
-
-	size = len;
-	*x = safe_alloc(size + 1);
-	if (!unrealdb_read(c, *x, size))
-	{
-		safe_free(*x);
-		return 0;
-	}
-	(*x)[len] = 0;
-	return 1;
-}
-
-/** Read a single 8 bit character from a database file.
- * @param c	UnrealDB file struct
- * @param t	The value to read
- * @returns 1 on success, 0 on failure.
- */
-int unrealdb_read_char(UnrealDB *c, char *t)
-{
-	if (!unrealdb_read(c, t, sizeof(char)))
-		return 0;
-	return 1;
-}
-
-/** @} */
-
-#if 0
-void fatal_error(FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	va_start(vl, pattern);
-	vfprintf(stderr, pattern, vl);
-	va_end(vl);
-	fprintf(stderr, "\n");
-	fprintf(stderr, "Exiting with failure\n");
-	exit(-1);
-}
-
-void unrealdb_test_simple(void)
-{
-	UnrealDB *c;
-	char *key = "test";
-	int i;
-	char *str;
-
-
-	fprintf(stderr, "*** WRITE TEST ***\n");
-	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
-	if (!c)
-		fatal_error("Could not open test db for writing: %s", strerror(errno));
-
-	if (!unrealdb_write_str(c, "Hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
-		fatal_error("Error on write 1");
-	if (!unrealdb_write_str(c, "This is a test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
-		fatal_error("Error on write 2");
-	if (!unrealdb_close(c))
-		fatal_error("Error on close");
-	c = NULL;
-	fprintf(stderr, "Done with writing.\n\n");
-
-	fprintf(stderr, "*** READ TEST ***\n");
-	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
-	if (!c)
-		fatal_error("Could not open test db for reading: %s", strerror(errno));
-	if (!unrealdb_read_str(c, &str))
-		fatal_error("Error on read 1: %s", c->error_string);
-	fprintf(stderr, "Got: '%s'\n", str);
-	safe_free(str);
-	if (!unrealdb_read_str(c, &str))
-		fatal_error("Error on read 2: %s", c->error_string);
-	fprintf(stderr, "Got: '%s'\n", str);
-	safe_free(str);
-	if (!unrealdb_close(c))
-		fatal_error("Error on close");
-	fprintf(stderr, "All good.\n");
-}
-
-#define UNREALDB_SPEED_TEST_BYTES 100000000
-void unrealdb_test_speed(char *key)
-{
-	UnrealDB *c;
-	int i, len;
-	char *str;
-	char buf[1024];
-	int written = 0, read = 0;
-	struct timeval tv_start, tv_end;
-
-	fprintf(stderr, "*** WRITE TEST ***\n");
-	gettimeofday(&tv_start, NULL);
-	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
-	if (!c)
-		fatal_error("Could not open test db for writing: %s", strerror(errno));
-	do {
-		
-		len = getrandom32() % 500;
-		//gen_random_alnum(buf, len);
-		for (i=0; i < len; i++)
-			buf[i] = 'a';
-		buf[i] = '\0';
-		if (!unrealdb_write_str(c, buf))
-			fatal_error("Error on writing a string of %d size", len);
-		written += len + 2; /* +2 for length */
-	} while(written < UNREALDB_SPEED_TEST_BYTES);
-	if (!unrealdb_close(c))
-		fatal_error("Error on close");
-	c = NULL;
-	gettimeofday(&tv_end, NULL);
-	fprintf(stderr, "Done with writing: %lld usecs\n\n",
-		(long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
-
-	fprintf(stderr, "*** READ TEST ***\n");
-	gettimeofday(&tv_start, NULL);
-	c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
-	if (!c)
-		fatal_error("Could not open test db for reading: %s", strerror(errno));
-	do {
-		if (!unrealdb_read_str(c, &str))
-			fatal_error("Error on read at position %d/%d: %s", read, written, c->error_string);
-		read += strlen(str) + 2; /* same calculation as earlier */
-		safe_free(str);
-	} while(read < written);
-	if (!unrealdb_close(c))
-		fatal_error("Error on close");
-	gettimeofday(&tv_end, NULL);
-	fprintf(stderr, "Done with reading: %lld usecs\n\n",
-		(long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
-
-	fprintf(stderr, "All good.\n");
-}
-
-void unrealdb_test(void)
-{
-	//unrealdb_test_simple();
-	fprintf(stderr, "**** TESTING ENCRYPTED ****\n");
-	unrealdb_test_speed("test");
-	fprintf(stderr, "**** TESTING UNENCRYPTED ****\n");
-	unrealdb_test_speed(NULL);
-}
-#endif
-
-/** TODO: document and implement
- */
-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 */
-}
-
-UnrealDBConfig *unrealdb_copy_config(UnrealDBConfig *src)
-{
-	UnrealDBConfig *dst = safe_alloc(sizeof(UnrealDBConfig));
-
-	dst->kdf = src->kdf;
-	dst->t_cost = src->t_cost;
-	dst->m_cost = src->m_cost;
-	dst->p_cost = src->p_cost;
-	dst->saltlen = src->saltlen;
-	dst->salt = safe_alloc(dst->saltlen);
-	memcpy(dst->salt, src->salt, dst->saltlen);
-
-	dst->cipher = src->cipher;
-	dst->keylen = src->keylen;
-	if (dst->keylen)
-	{
-		dst->key = safe_alloc_sensitive(dst->keylen);
-		memcpy(dst->key, src->key, dst->keylen);
-	}
-
-	return dst;
-}
-
-UnrealDBConfig *unrealdb_get_config(UnrealDB *db)
-{
-	return unrealdb_copy_config(db->config);
-}
-
-void unrealdb_free_config(UnrealDBConfig *c)
-{
-	if (!c)
-		return;
-	safe_free(c->salt);
-	safe_free_sensitive(c->key);
-	safe_free(c);
-}
-
-static int unrealdb_config_identical(UnrealDBConfig *one, UnrealDBConfig *two)
-{
-	/* NOTE: do not compare 'key' here or all cache lookups will fail */
-	if ((one->kdf == two->kdf) &&
-	    (one->t_cost == two->t_cost) &&
-	    (one->m_cost == two->m_cost) &&
-	    (one->p_cost == two->p_cost) &&
-	    (one->saltlen == two->saltlen) &&
-	    (memcmp(one->salt, two->salt, one->saltlen) == 0) &&
-	    (one->cipher == two->cipher) &&
-	    (one->keylen == two->keylen))
-	{
-		return 1;
-	}
-	return 0;
-}
-
-static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg)
-{
-	SecretCache *c;
-
-	for (c = secr->cache; c; c = c->next)
-	{
-		if (unrealdb_config_identical(c->config, cfg))
-		{
-			c->cache_hit = TStime();
-			return c;
-		}
-	}
-	return NULL;
-}
-
-static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg)
-{
-	SecretCache *c = find_secret_cache(secr, cfg);
-
-	if (c)
-		return; /* Entry already exists in cache */
-
-	/* New entry, add! */
-	c = safe_alloc(sizeof(SecretCache));
-	c->config = unrealdb_copy_config(cfg);
-	c->cache_hit = TStime();
-	AddListItem(c, secr->cache);
-}
-
-#ifdef DEBUGMODE
-#define UNREALDB_EXPIRE_SECRET_CACHE_AFTER	1200
-#else
-#define UNREALDB_EXPIRE_SECRET_CACHE_AFTER	86400
-#endif
-
-/** Expire cached secret entries (previous Argon2 runs) */
-EVENT(unrealdb_expire_secret_cache)
-{
-	Secret *s;
-	SecretCache *c, *c_next;
-	for (s = secrets; s; s = s->next)
-	{
-		for (c = s->cache; c; c = c_next)
-		{
-			c_next = c->next;
-			if (c->cache_hit < TStime() - UNREALDB_EXPIRE_SECRET_CACHE_AFTER)
-			{
-				DelListItem(c, s->cache);
-				free_secret_cache(c);
-			}
-		}
-	}
-}
diff --git a/src/unrealircdctl.c b/src/unrealircdctl.c
@@ -1,268 +0,0 @@
-/************************************************************************
- *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/unrealircdctl
- *   (c) 2022- Bram Matthys and The UnrealIRCd team
- *
- *   See file AUTHORS in IRC package for additional names of
- *   the programmers. 
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 1, or (at your option)
- *   any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/** @file
- * @brief UnrealIRCd Control
- */
-#include "unrealircd.h"
-
-#ifdef _WIN32
- #define UNREALCMD "unrealircdctl"
-#else
- #define UNREALCMD "./unrealircd"
-#endif
-
-
-extern int procio_client(const char *command, int auto_color_logs);
-
-void unrealircdctl_usage(const char *program_name)
-{
-	printf("Usage: %s <option>\n"
-	       "Where <option> is one of:\n"
-	       "rehash         - Rehash the server (reread configuration files)\n"
-	       "reloadtls      - Reload the SSL/TLS certificates\n"
-	       "status         - Show current status of server\n"
-	       "module-status  - Show currently loaded modules\n"
-	       "mkpasswd       - Hash a password\n"
-	       "gencloak       - Display 3 random cloak keys\n"
-	       "spkifp         - Display SPKI Fingerprint\n"
-	       "\n", program_name);
-	exit(-1);
-}
-
-void unrealircdctl_rehash(void)
-{
-	if (procio_client("REHASH", 1) == 0)
-	{
-		printf("Rehashed succesfully.\n");
-		exit(0);
-	}
-	printf("Rehash failed.\n");
-	exit(1);
-}
-
-void unrealircdctl_reloadtls(void)
-{
-	if (procio_client("REHASH -tls", 1) == 0)
-	{
-		printf("Reloading of TLS certificates successful.\n");
-		exit(0);
-	}
-	printf("Reloading TLS certificates failed.\n");
-	exit(1);
-}
-
-void unrealircdctl_status(void)
-{
-	if (procio_client("STATUS", 2) == 0)
-	{
-		printf("UnrealIRCd is up and running.\n");
-		exit(0);
-	}
-	printf("UnrealIRCd status report failed.\n");
-	exit(1);
-}
-
-void unrealircdctl_module_status(void)
-{
-	if (procio_client("MODULES", 2) == 0)
-		exit(0);
-	printf("Could not retrieve complete module list.\n");
-	exit(1);
-}
-
-void unrealircdctl_mkpasswd(int argc, char *argv[])
-{
-	AuthenticationType type;
-	const char *result;
-	char *p = argv[2];
-
-	type = Auth_FindType(NULL, p);
-	if (type == -1)
-	{
-		type = AUTHTYPE_ARGON2;
-	} else {
-		p = argv[3];
-	}
-	if (BadPtr(p))
-	{
-#ifndef _WIN32
-		p = getpass("Enter password to hash: ");
-#else
-		printf("ERROR: You should specify a password to hash");
-		exit(1);
-#endif
-	}
-	if ((type == AUTHTYPE_UNIXCRYPT) && (strlen(p) > 8))
-	{
-		/* Hmmm.. is this warning really still true (and always) ?? */
-		printf("WARNING: Password truncated to 8 characters due to 'crypt' algorithm. "
-		       "You are suggested to use the 'argon2' algorithm instead.");
-		p[8] = '\0';
-	}
-	if (!(result = Auth_Hash(type, p))) {
-		printf("Failed to generate password. Deprecated method? Try 'argon2' instead.\n");
-		exit(0);
-	}
-	printf("Encrypted password is: %s\n", result);
-	exit(0);
-}
-
-void unrealircdctl_gencloak(int argc, char *argv[])
-{
-	#define GENERATE_CLOAKKEY_LEN 80 /* Length of cloak keys to generate. */
-	char keyBuf[GENERATE_CLOAKKEY_LEN + 1];
-	int keyNum;
-	int charIndex;
-
-	short has_upper;
-	short has_lower;
-	short has_num;
-
-	printf("Here are 3 random cloak keys that you can copy-paste to your configuration file:\n\n");
-
-	printf("set {\n\tcloak-keys {\n");
-	for (keyNum = 0; keyNum < 3; ++keyNum)
-	{
-		has_upper = 0;
-		has_lower = 0;
-		has_num = 0;
-
-		for (charIndex = 0; charIndex < sizeof(keyBuf)-1; ++charIndex)
-		{
-			switch (getrandom8() % 3)
-			{
-				case 0: /* Uppercase. */
-					keyBuf[charIndex] = (char)('A' + (getrandom8() % ('Z' - 'A')));
-					has_upper = 1;
-					break;
-				case 1: /* Lowercase. */
-					keyBuf[charIndex] = (char)('a' + (getrandom8() % ('z' - 'a')));
-					has_lower = 1;
-					break;
-				case 2: /* Digit. */
-					keyBuf[charIndex] = (char)('0' + (getrandom8() % ('9' - '0')));
-					has_num = 1;
-					break;
-			}
-		}
-		keyBuf[sizeof(keyBuf)-1] = '\0';
-
-		if (has_upper && has_lower && has_num)
-			printf("\t\t\"%s\";\n", keyBuf);
-		else
-			/* Try again. For this reason, keyNum must be signed. */
-			keyNum--;
-	}
-	printf("\t}\n}\n\n");
-	exit(0);
-}
-
-void unrealircdctl_spkifp(int argc, char *argv[])
-{
-	char *file = argv[2];
-	SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
-	SSL *ssl;
-	X509 *cert;
-	const char *spkifp;
-
-	if (!ctx)
-	{
-		printf("Internal failure while initializing SSL/TLS library context\n");
-		exit(1);
-	}
-
-	if (!file)
-	{
-		printf("NOTE: This script uses the default certificate location (any set::tls settings\n"
-		       "are ignored). If this is not what you want then specify a certificate\n"
-		       "explicitly like this: %s spkifp conf/tls/example.pem\n\n", UNREALCMD);
-		safe_strdup(file, "tls/server.cert.pem");
-		convert_to_absolute_path(&file, CONFDIR);
-	}
-
-	if (!file_exists(file))
-	{
-		printf("Could not open certificate: %s\n"
-		       "You can specify a certificate like this: %s spkifp conf/tls/example.pem\n",
-		       UNREALCMD, file);
-		exit(1);
-	}
-
-	if (SSL_CTX_use_certificate_chain_file(ctx, file) <= 0)
-	{
-		printf("Could not read certificate '%s'\n", file);
-		exit(1);
-	}
-
-	ssl = SSL_new(ctx);
-	if (!ssl)
-	{
-		printf("Something went wrong when generating the SPKI fingerprint.\n");
-		exit(1);
-	}
-
-	cert = SSL_get_certificate(ssl);
-	spkifp = spki_fingerprint_ex(cert);
-	printf("The SPKI fingerprint for certificate '%s' is:\n"
-	       "%s\n"
-	       "\n"
-	       "You normally add this password on the other side of the link as:\n"
-	       "password \"%s\" { spkifp; };\n"
-	       "\n",
-	       file, spkifp, spkifp);
-	exit(0);
-}
-
-int main(int argc, char *argv[])
-{
-#ifdef _WIN32
-	chdir(".."); /* go up one level from "bin" */
-	init_winsock();
-#else
-	alarm(20); /* 20 second timeout */
-#endif
-	dbuf_init();
-	init_random();
-	early_init_tls();
-
-	if (argc == 1)
-		unrealircdctl_usage(argv[0]);
-
-	if (!strcmp(argv[1], "rehash"))
-		unrealircdctl_rehash();
-	else if (!strcmp(argv[1], "reloadtls"))
-		unrealircdctl_reloadtls();
-	else if (!strcmp(argv[1], "status"))
-		unrealircdctl_status();
-	else if (!strcmp(argv[1], "module-status"))
-		unrealircdctl_module_status();
-	else if (!strcmp(argv[1], "mkpasswd"))
-		unrealircdctl_mkpasswd(argc, argv);
-	else if (!strcmp(argv[1], "gencloak"))
-		unrealircdctl_gencloak(argc, argv);
-	else if (!strcmp(argv[1], "spkifp") || !strcmp(argv[1], "spki"))
-		unrealircdctl_spkifp(argc, argv);
-	else
-		unrealircdctl_usage(argv[0]);
-	exit(0);
-}
diff --git a/src/url_curl.c b/src/url_curl.c
@@ -1,340 +0,0 @@
-/*
- *   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
-{
-	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[CURL_ERROR_SIZE];
-	time_t cachetime;
-};
-
-CURLM *multihandle = NULL;
-
-Download *downloads = NULL;
-
-void url_free_handle(Download *handle)
-{
-	DelListItem(handle, downloads);
-	if (handle->file_fd)
-		fclose(handle->file_fd);
-	safe_free(handle->url);
-	safe_free(handle);
-}
-
-void url_cancel_handle_by_callback_data(void *ptr)
-{
-	Download *d, *d_next;
-
-	for (d = downloads; d; d = d_next)
-	{
-		d_next = d->next;
-		if (d->callback_data == ptr)
-		{
-			d->callback = NULL;
-			d->callback_data = NULL;
-		}
-	}
-}
-
-/*
- * Sets up all of the SSL options necessary to support HTTPS/FTPS
- * transfers.
- */
-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 (handle->callback == NULL)
-			{
-				/* Request is already canceled, we don't care about the result, just clean up */
-				remove(handle->filename);
-			} else
-			if (msg->data.result == CURLE_OK)
-			{
-				if (code == 304 || (last_mod != -1 && last_mod <= handle->cachetime))
-				{
-					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;
-	}
-	AddListItem(handle, downloads);
-
-	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
@@ -1,1086 +0,0 @@
-/*
- *   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 */
-
-/* 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 url_cancel_handle_by_callback_data(void *ptr)
-{
-	Download *d, *d_next;
-
-	for (d = downloads; d; d = d_next)
-	{
-		d_next = d->next;
-		if (d->callback_data == ptr)
-		{
-			d->callback = NULL;
-			d->callback_data = NULL;
-		}
-	}
-}
-
-void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	va_start(vl, pattern);
-	vsnprintf(handle->errorbuf, sizeof(handle->errorbuf), pattern, vl);
-	va_end(vl);
-	if (handle->callback)
-		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->callback)
-		; /* No special action, request was cancelled */
-	else if (!handle->got_response)
-		handle->callback(url, NULL, "HTTPS response not received", 0, handle->callback_data);
-	else
-	{
-		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;
-	if (handle->callback)
-		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--;
-
-	if (handle->callback)
-	{
-		/* If still an outstanding request (not cancelled), follow the redirect.. */
-		download_file_async(handle->redirect_new_location, handle->cachetime, handle->callback, handle->callback_data,
-				    handle->url, handle->redirects_remaining);
-	}
-	/* Don't call the hook, just free this, the new redirect from above will call the hook later */
-	url_free_handle(handle);
-}
-
-/** 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
@@ -1,1007 +0,0 @@
-/*
- *   Unreal Internet Relay Chat Daemon, src/user.c
- *   Copyright (C) 1990 Jarkko Oikarinen and
- *                      University of Oulu, Computing Center
- *
- *   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 User-related functions
- */
-
-/* s_user.c 2.74 2/8/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
-
-#include "unrealircd.h"
-
-MODVAR int dontspread = 0;
-
-/** Inhibit labeled/response reply. This means it will result in an empty ACK
- *  because we cannot handle the command via labeled-reponse. Rare, but
- *  possible in for example /TRACE which multiple servers handle and which
- *  has no clear end.
- */
-MODVAR int labeled_response_inhibit = 0;
-
-/** Force a labeled/response reply (of course, only if a label is present etc.).
- * This is used in case the "a remote server is handling the request" was
- * incorrect and there were 0 responses. This is the case for PRIVMSG.
- * It will force an empty ACK.
- * No, this cannot be merged with the other one. Also, the other one
- * (labeled_response_inhibit) has priority over this one (labeled_response_force).
- */
-MODVAR int labeled_response_force = 0;
-
-/** Inhibit labeled/response END. Only used in /LIST.
- */
-MODVAR int labeled_response_inhibit_end = 0;
-
-/** Set to 1 if an UTF8 incompatible nick character set is in use */
-MODVAR int non_utf8_nick_chars_in_use = 0;
-
-/** Set a new vhost on the user
- * @param client	The client (user)
- * @param host		The new vhost
- */
-void iNAH_host(Client *client, const char *host)
-{
-	if (!client->user)
-		return;
-
-	userhost_save_current(client);
-
-	safe_strdup(client->user->virthost, host);
-	if (MyConnect(client))
-		sendto_server(NULL, 0, 0, NULL, ":%s SETHOST :%s", client->id, client->user->virthost);
-	client->umodes |= UMODE_SETHOST|UMODE_HIDE;
-
-	userhost_changed(client);
-}
-
-/** Convert a user mode string to a bitmask - only used by config.
- * @param umode		The user mode string
- * @returns the user mode value (long)
- */
-long set_usermode(const char *umode)
-{
-	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 (um = usermodes; um; um = um->next)
-				{
-					if (um->letter == *m)
-					{
-						if (what == MODE_ADD)
-							newumode |= um->mode;
-						else
-							newumode &= ~um->mode;
-					}
-				}
-		}
-	}
-
-	return newumode;
-}
-
-/** Convert a target pointer to an 8 bit hash, used for target limiting. */
-unsigned char hash_target(void *target)
-{
-	uintptr_t v = (uintptr_t)target;
-	/* ircu does >> 16 and 8 but since our sizeof(Client) is
-	 * towards 512 (and hence the alignment), that bit is useless.
-	 * So we do >> 17 and 9.
-	 */
-	return (unsigned char)((v >> 17) ^ (v >> 9));
-}
-
-/** target_limit_exceeded
- * @param client   The client.
- * @param target The target client
- * @param name   The name of the target client (used in the error message)
- * @retval Returns 1 if too many targets were addressed (do not send!), 0 if ok to send.
- */
-int target_limit_exceeded(Client *client, void *target, const char *name)
-{
-	u_char hash = hash_target(target);
-	int i;
-	int max_concurrent_conversations_users, max_concurrent_conversations_new_user_every;
-	FloodSettings *settings;
-
-	if (ValidatePermissionsForPath("immune:max-concurrent-conversations",client,NULL,NULL,NULL))
-		return 0;
-
-	if (client->local->targets[0] == hash)
-		return 0;
-
-	settings = get_floodsettings_for_user(client, FLD_CONVERSATIONS);
-	max_concurrent_conversations_users = settings->limit[FLD_CONVERSATIONS];
-	max_concurrent_conversations_new_user_every = settings->period[FLD_CONVERSATIONS];
-
-	if (max_concurrent_conversations_users <= 0)
-		return 0; /* unlimited */
-
-	/* Shouldn't be needed, but better check here than access out-of-bounds memory */
-	if (max_concurrent_conversations_users > MAXCCUSERS)
-		max_concurrent_conversations_users = MAXCCUSERS;
-
-	for (i = 1; i < max_concurrent_conversations_users; i++)
-	{
-		if (client->local->targets[i] == hash)
-		{
-			/* Move this target hash to the first position */
-			memmove(&client->local->targets[1], &client->local->targets[0], i);
-			client->local->targets[0] = hash;
-			return 0;
-		}
-	}
-
-	if (TStime() < client->local->nexttarget)
-	{
-		/* Target limit reached */
-		client->local->nexttarget += 2; /* punish them some more */
-		add_fake_lag(client, 2000); /* lag them up as well */
-
-		flood_limit_exceeded_log(client, "max-concurrent-conversations");
-		sendnumeric(client, ERR_TARGETTOOFAST, name, (long long)(client->local->nexttarget - TStime()));
-
-		return 1;
-	}
-
-	/* If not set yet or in the very past, then adjust it.
-	 * This is so client->local->nexttarget=0 will become client->local->nexttarget=currenttime-...
-	 */
-	if (TStime() > client->local->nexttarget +
-	    (max_concurrent_conversations_users * max_concurrent_conversations_new_user_every))
-	{
-		client->local->nexttarget = TStime() - ((max_concurrent_conversations_users-1) * max_concurrent_conversations_new_user_every);
-	}
-
-	client->local->nexttarget += max_concurrent_conversations_new_user_every;
-
-	/* Add the new target (first move the rest, then add us at position 0 */
-	memmove(&client->local->targets[1], &client->local->targets[0], max_concurrent_conversations_users - 1);
-	client->local->targets[0] = hash;
-
-	return 0;
-}
-
-/** De-duplicate a string of "x,x,y,z" to "x,y,z"
- * @param buffer	Input string
- * @returns The new de-duplicated buffer (temporary storage, only valid until next canonize call)
- */
-char *canonize(const char *buffer)
-{
-	static char cbuf[2048];
-	char tbuf[2048];
-	char *s, *t, *cp = cbuf;
-	int  l = 0;
-	char *p = NULL, *p2;
-
-	*cp = '\0';
-
-	if (!buffer)
-		return NULL;
-
-	strlcpy(tbuf, buffer, sizeof(tbuf));
-	for (s = strtoken(&p, tbuf, ","); s; s = strtoken(&p, NULL, ","))
-	{
-		if (l)
-		{
-			for (p2 = NULL, t = strtoken(&p2, cbuf, ","); t;
-			    t = strtoken(&p2, NULL, ","))
-				if (!mycmp(s, t))
-					break;
-				else if (p2)
-					p2[-1] = ',';
-		}
-		else
-			t = NULL;
-		if (!t)
-		{
-			if (l)
-				*(cp - 1) = ',';
-			else
-				l = 1;
-			strcpy(cp, s);
-			if (p)
-				cp += (p - s);
-		}
-		else if (p2)
-			p2[-1] = ',';
-	}
-	return cbuf;
-}
-
-/** Get user modes as a string.
- * @param client	The client
- * @returns string of user modes (temporary storage)
- */
-const char *get_usermode_string(Client *client)
-{
-	static char buf[128];
-	Umode *um;
-
-	strlcpy(buf, "+", sizeof(buf));
-	for (um = usermodes; um; um = um->next)
-		if (client->umodes & um->mode)
-			strlcat_letter(buf, um->letter, sizeof(buf));
-
-	return buf;
-}
-
-/** Get user modes as a string - buffer is specified.
- * @param client	The client
- * @param buf		The buffer to write to
- * @param buflen	The size of the buffer
- * @returns string of user modes (buf)
- */
-const char *get_usermode_string_r(Client *client, char *buf, size_t buflen)
-{
-	Umode *um;
-
-	strlcpy(buf, "+", buflen);
-	for (um = usermodes; um; um = um->next)
-		if (client->umodes & um->mode)
-			strlcat_letter(buf, um->letter, buflen);
-
-	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)
- */
-const char *get_usermode_string_raw(long umodes)
-{
-	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));
-
-	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
- * @param buf		The buffer to write to
- * @param buflen	The size of the buffer
- * @returns string of user modes (buf)
- */
-const char *get_usermode_string_raw_r(long umodes, char *buf, size_t buflen)
-{
-	Umode *um;
-
-	strlcpy(buf, "+", buflen);
-	for (um = usermodes; um; um = um->next)
-		if (umodes & um->mode)
-			strlcat_letter(buf, um->letter, buflen);
-
-	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, const char *snomask)
-{
-	int what = MODE_ADD; /* keep this an int. -- Syzop */
-	const char *p;
-	int i;
-
-	if (snomask == NULL)
-	{
-		remove_all_snomasks(client);
-		return;
-	}
-	
-	for (p = snomask; p && *p; p++)
-	{
-		switch (*p)
-		{
-			case '+':
-				what = MODE_ADD;
-				break;
-			case '-':
-				what = MODE_DEL;
-				break;
-			default:
-				if (what == MODE_ADD)
-				{
-					if (!isalpha(*p) || !is_valid_snomask(*p))
-						continue;
-					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.
- * @author Originally by avalon.
- */
-void build_umode_string(Client *client, long old, long sendmask, char *umode_buf)
-{
-	Umode *um;
-	long flag;
-	char *m;
-	int what = MODE_NULL;
-
-	/*
-	 * build a string in umode_buf to represent the change in the user's
-	 * mode between the new (client->flag) and 'old'.
-	 */
-	m = umode_buf;
-	*m = '\0';
-	for (um = usermodes; um; um = um->next)
-	{
-		flag = um->mode;
-		if (MyUser(client) && !(flag & sendmask))
-			continue;
-		if ((flag & old) && !(client->umodes & flag))
-		{
-			if (what == MODE_DEL)
-				*m++ = um->letter;
-			else
-			{
-				what = MODE_DEL;
-				*m++ = '-';
-				*m++ = um->letter;
-			}
-		}
-		else if (!(flag & old) && (client->umodes & flag))
-		{
-			if (what == MODE_ADD)
-				*m++ = um->letter;
-			else
-			{
-				what = MODE_ADD;
-				*m++ = '+';
-				*m++ = um->letter;
-			}
-		}
-	}
-	*m = '\0';
-}
-
-/** Send usermode change to other servers.
- * @param client	The client
- * @param show_to_user	Set to 1 to show the MODE change to the user
- * @param old		The old user modes set on the client
- */
-void send_umode_out(Client *client, int show_to_user, long old)
-{
-	Client *acptr;
-	char buf[512];
-
-	build_umode_string(client, old, SEND_UMODES, buf);
-
-	list_for_each_entry(acptr, &server_list, special_node)
-	{
-		if ((acptr != client) && (acptr != client->direction) && *buf)
-		{
-			sendto_one(acptr, NULL, ":%s UMODE2 %s",
-			           client->name, buf);
-		}
-	}
-
-	if (MyUser(client) && show_to_user)
-	{
-		build_umode_string(client, old, ALL_UMODES, buf);
-		if (*buf)
-			sendto_one(client, NULL, ":%s MODE %s :%s", client->name, client->name, buf);
-	}
-}
-
-static MaxTarget *maxtargets = NULL; /**< For set::max-targets-per-command configuration */
-
-static void maxtarget_add_sorted(MaxTarget *n)
-{
-	MaxTarget *e;
-
-	if (!maxtargets)
-	{
-		maxtargets = n;
-		return;
-	}
-
-	for (e = maxtargets; e; e = e->next)
-	{
-		if (strcmp(n->cmd, e->cmd) < 0)
-		{
-			/* Insert us before */
-			if (e->prev)
-				e->prev->next = n;
-			else
-				maxtargets = n; /* new head */
-			n->prev = e->prev;
-
-			n->next = e;
-			e->prev = n;
-			return;
-		}
-		if (!e->next)
-		{
-			/* Append us at end */
-			e->next = n;
-			n->prev = e;
-			return;
-		}
-	}
-}
-
-/** Find a maxtarget structure for a cmd (internal) */
-MaxTarget *findmaxtarget(const char *cmd)
-{
-	MaxTarget *m;
-
-	for (m = maxtargets; m; m = m->next)
-		if (!strcasecmp(m->cmd, cmd))
-			return m;
-	return NULL;
-}
-
-/** Set a maximum targets per command restriction */
-void setmaxtargets(const char *cmd, int limit)
-{
-	MaxTarget *m = findmaxtarget(cmd);
-	if (!m)
-	{
-		char cmdupper[64];
-		strlcpy(cmdupper, cmd, sizeof(cmdupper));
-		strtoupper(cmdupper);
-		m = safe_alloc(sizeof(MaxTarget));
-		safe_strdup(m->cmd, cmdupper);
-		maxtarget_add_sorted(m);
-	}
-	m->limit = limit;
-}
-
-/** Free all set::max-targets-per-command configuration (internal) */
-void freemaxtargets(void)
-{
-	MaxTarget *m, *m_next;
-
-	for (m = maxtargets; m; m = m_next)
-	{
-		m_next = m->next;
-		safe_free(m->cmd);
-		safe_free(m);
-	}
-	maxtargets = NULL;
-}
-
-/** Return the maximum number of targets permitted for a command */
-int max_targets_for_command(const char *cmd)
-{
-	MaxTarget *m = findmaxtarget(cmd);
-	if (m)
-		return m->limit;
-	return 1; /* default to 1 */
-}
-
-void set_isupport_targmax(void)
-{
-	char buf[512], tbuf[64];
-	MaxTarget *m;
-
-	*buf = '\0';
-	for (m = maxtargets; m; m = m->next)
-	{
-		if (m->limit == MAXTARGETS_MAX)
-			snprintf(tbuf, sizeof(tbuf), "%s:", m->cmd);
-		else
-			snprintf(tbuf, sizeof(tbuf), "%s:%d", m->cmd, m->limit);
-
-		if (*buf)
-			strlcat(buf, ",", sizeof(buf));
-		strlcat(buf, tbuf, sizeof(buf));
-	}
-	ISupportSet(NULL, "TARGMAX", buf);
-}
-
-/** Called between config test and config run */
-void set_targmax_defaults(void)
-{
-	/* Free existing... */
-	freemaxtargets();
-
-	/* Set the defaults */
-	setmaxtargets("PRIVMSG", 4);
-	setmaxtargets("NOTICE", 1);
-	setmaxtargets("TAGMSG", 1);
-	setmaxtargets("NAMES", 1); // >1 is not supported
-	setmaxtargets("WHOIS", 1);
-	setmaxtargets("WHOWAS", 1); // >1 is not supported
-	setmaxtargets("KICK", 4);
-	setmaxtargets("LIST", MAXTARGETS_MAX);
-	setmaxtargets("JOIN", MAXTARGETS_MAX);
-	setmaxtargets("PART", MAXTARGETS_MAX);
-	setmaxtargets("SAJOIN", MAXTARGETS_MAX);
-	setmaxtargets("SAPART", MAXTARGETS_MAX);
-	setmaxtargets("KILL", MAXTARGETS_MAX);
-	setmaxtargets("DCCALLOW", MAXTARGETS_MAX);
-	/* The following 3 are space-separated (and actually the previous
-	 * mentioned DCCALLOW is both space-and-comma separated).
-	 * It seems most IRCd's don't list space-separated targets list
-	 * in TARGMAX... On the other hand, why not? It says nowhere in
-	 * the TARGMAX specification that it's only for comma-separated
-	 * commands. So let's be nice and consistent and inform the
-	 * clients about the limits for such commands as well:
-	 */
-	setmaxtargets("USERHOST", MAXTARGETS_MAX); // not configurable
-	setmaxtargets("USERIP", MAXTARGETS_MAX); // not configurable
-	setmaxtargets("ISON", MAXTARGETS_MAX); // not configurable
-	setmaxtargets("WATCH", MAXTARGETS_MAX); // not configurable
-}
-
-/** Is the user handshake finished and can register_user() be called?
- * This checks things like: do we have a NICK, USER, nospoof,
- * and any other things modules may add:
- * eg: the cap module checks if client capability negotiation
- * is in progress
- */
-int is_handshake_finished(Client *client)
-{
-	Hook *h;
-	int n;
-
-	for (h = Hooks[HOOKTYPE_IS_HANDSHAKE_FINISHED]; h; h = h->next)
-	{
-		n = (*(h->func.intfunc))(client);
-		if (n == 0)
-			return 0; /* We can stop already */
-	}
-
-	/* I figured these can be here, in the core: */
-	if (client->user && *client->user->username && client->name[0] && IsNotSpoof(client))
-		return 1;
-
-	return 0;
-}
-
-/** Should we show connection info to the user?
- * This depends on the set::show-connect-info setting but also
- * on various other properties, such as serversonly ports,
- * websocket, etc.
- * If someone needs it, then we can also call a hook here. Just tell us.
- */
-int should_show_connect_info(Client *client)
-{
-	if (SHOWCONNECTINFO &&
-	    !client->server &&
-	    !IsServersOnlyListener(client->local->listener) &&
-	    !client->local->listener->websocket_options)
-	{
-		return 1;
-	}
-	return 0;
-}
-
-/* (helper function for uid_get) */
-static char uid_int_to_char(int v)
-{
-	if (v < 10)
-		return '0'+v;
-	else
-		return 'A'+v-10;
-}
-
-/** Acquire a new unique UID */
-const char *uid_get(void)
-{
-	Client *acptr;
-	static char uid[IDLEN];
-	static int uidcounter = 0;
-
-	uidcounter++;
-	if (uidcounter == 36*36)
-		uidcounter = 0;
-
-	do
-	{
-		snprintf(uid, sizeof(uid), "%s%c%c%c%c%c%c",
-			me.id,
-			uid_int_to_char(getrandom8() % 36),
-			uid_int_to_char(getrandom8() % 36),
-			uid_int_to_char(getrandom8() % 36),
-			uid_int_to_char(getrandom8() % 36),
-			uid_int_to_char(uidcounter / 36),
-			uid_int_to_char(uidcounter % 36));
-		acptr = find_client(uid, NULL);
-	} while (acptr);
-
-	return uid;
-}
-
-/** Get cloaked host for user */
-const char *getcloak(Client *client)
-{
-	if (!*client->user->cloakedhost)
-	{
-		/* need to calculate (first-time) */
-		make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
-	}
-
-	return client->user->cloakedhost;
-}
-
-/** Calculate the cloaked host for a client.
- * @param client	The client
- * @param curr		The real host or real IP
- * @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, const char *curr, char *buf, size_t buflen)
-{
-	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++)
-		*q =  tolower(*p);
-	*q = '\0';
-
-	/* Call the cloaking layer */
-	if (RCallbacks[CALLBACKTYPE_CLOAK_EX] != NULL)
-		mask = RCallbacks[CALLBACKTYPE_CLOAK_EX]->func.stringfunc(client, host);
-	else if (RCallbacks[CALLBACKTYPE_CLOAK] != NULL)
-		mask = RCallbacks[CALLBACKTYPE_CLOAK]->func.stringfunc(host);
-	else
-		mask = curr;
-
-	strlcpy(buf, mask, buflen);
-}
-
-/** Called after a user is logged in (or out) of a services account */
-void user_account_login(MessageTag *recv_mtags, Client *client)
-{
-	if (MyConnect(client))
-	{
-		find_shun(client);
-		if (find_tkline_match(client, 0) && IsDead(client))
-			return;
-	}
-	RunHook(HOOKTYPE_ACCOUNT_LOGIN, client, recv_mtags);
-}
-
-/** Should we hide the idle time of 'target' to user 'client'?
- * This depends on the set::hide-idle-time policy.
- */
-int hide_idle_time(Client *client, Client *target)
-{
-	/* First of all, IRCOps bypass the restriction */
-	if (IsOper(client))
-		return 0;
-
-	/* Other than that, it depends on the settings: */
-	switch (iConf.hide_idle_time)
-	{
-		case HIDE_IDLE_TIME_NEVER:
-			return 0;
-		case HIDE_IDLE_TIME_ALWAYS:
-			return 1;
-		case HIDE_IDLE_TIME_USERMODE:
-		case HIDE_IDLE_TIME_OPER_USERMODE:
-			if (target->umodes & UMODE_HIDLE)
-				return 1;
-			return 0;
-		default:
-			return 0;
-	}
-}
-
-/** Get creation time of a client.
- * @param client	The client to check (user, server, anything)
- * @returns the time when the client first connected to IRC, or 0 for unknown.
- */
-time_t get_creationtime(Client *client)
-{
-	const char *str;
-
-	/* Shortcut for local clients */
-	if (client->local)
-		return client->local->creationtime;
-
-	/* Otherwise, hopefully available through this... */
-	str = moddata_client_get(client, "creationtime");
-	if (!BadPtr(str) && (*str != '0'))
-		return atoll(str);
-	return 0;
-}
-
-/** Get how long a client is connected to IRC.
- * @param client	The client to check
- * @returns how long the client is connected to IRC (number of seconds)
- */
-long get_connected_time(Client *client)
-{
-	const char *str;
-	long connect_time = 0;
-
-	/* Shortcut for local clients */
-	if (client->local)
-		return TStime() - client->local->creationtime;
-
-	/* Otherwise, hopefully available through this... */
-	str = moddata_client_get(client, "creationtime");
-	if (!BadPtr(str) && (*str != '0'))
-		return TStime() - atoll(str);
-	return 0;
-}
-
-/** Return extended information about user for the "Client connecting" line.
- * @returns A string such as "[secure] [reputation: 5]", never returns NULL.
- */
-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... */
-	RunHook(HOOKTYPE_CONNECT_EXTINFO, client, &list);
-
-	/* And some built-in: */
-
-	/* "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": 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->account);
-
-	/* security groups */
-	secgroups = get_security_groups(client);
-	if (secgroups)
-		add_nvplist(&list, 100, "security-groups", secgroups);
-	
-	/* tkl shunned */
-	if (IsShunned(client))
-		add_nvplist(&list, 110, "shunned", NULL);
-
-	*retbuf = '\0';
-	for (e = list; e; e = e->next)
-	{
-		if (e->value)
-			snprintf(tmp, sizeof(tmp), "[%s: %s] ", e->name, e->value);
-		else
-			snprintf(tmp, sizeof(tmp), "[%s] ", e->name);
-		strlcat(retbuf, tmp, sizeof(retbuf));
-	}
-	/* Cut off last space (unless empty string) */
-	if (*retbuf)
-		retbuf[strlen(retbuf)-1] = '\0';
-
-	/* Free the list, as it was only used to build retbuf */
-	free_nvplist(list);
-
-	return retbuf;
-}
-
-/** Log a message that flood protection kicked in for the client.
- * This sends to the +f snomask at the moment.
- * @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, const char *floodname)
-{
-	char buf[1024];
-
-	// NOTE: If you ever change this format, there are a few more
-	// direct unreal_log() calls with "FLOOD_BLOCKED" in the file
-	// src/modules/targetfloodprot.c, so update those as well.
-	unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
-	           "Flood blocked ($flood_type) from $client.details [$client.ip]",
-	           log_data_string("flood_type", floodname));
-}
-
-/** Is the flood limit exceeded for an option? eg for away-flood.
- * @param client	The client to check flood for (local user)
- * @param opt		The flood option (eg FLD_AWAY)
- * @note This increments the flood counter as well.
- * @returns 1 if exceeded, 0 if not.
- */
-int flood_limit_exceeded(Client *client, FloodOption opt)
-{
-	FloodSettings *f;
-
-	if (!MyUser(client))
-		return 0;
-
-	f = get_floodsettings_for_user(client, opt);
-	if (f->limit[opt] <= 0)
-		return 0; /* No limit set or unlimited */
-
-	/* Ok, let's do the flood check */
-	if ((client->local->flood[opt].t + f->period[opt]) <= timeofday)
-	{
-		/* Time exceeded, reset */
-		client->local->flood[opt].count = 0;
-		client->local->flood[opt].t = timeofday;
-	}
-	if (client->local->flood[opt].count <= f->limit[opt])
-		client->local->flood[opt].count++;
-	if (client->local->flood[opt].count > f->limit[opt])
-	{
-		flood_limit_exceeded_log(client, floodoption_names[opt]);
-		return 1; /* Flood limit hit! */
-	}
-
-	return 0;
-}
-
-/** Get the appropriate anti-flood settings block for this user.
- * @param client	The client, should be locally connected.
- * @param opt		The flood option we are interested in
- * @returns The FloodSettings for this user, never returns NULL.
- */
-FloodSettings *get_floodsettings_for_user(Client *client, FloodOption opt)
-{
-	SecurityGroup *s;
-	FloodSettings *f;
-
-	/* Go through all security groups by order of priority
-	 * (eg: first "known-users", then "unknown-users").
-	 * For each of these:
-	 * - Check if a set::anti-flood::xxxx block exists for this group
-	 * - Check if the limit is non-zero (eg there is any limit set)
-	 * If any of these are false then we continue with next block
-	 * that matches.
-	 */
-
-	// XXX: alternatively, instead of this double loop,
-	//      do a post-conf thing and sort iConf.floodsettings
-	//      according to the security-group { } order.
-	for (s = securitygroups; s; s = s->next)
-	{
-		if (user_allowed_by_security_group(client, s) &&
-		    ((f = find_floodsettings_block(s->name))) &&
-		    f->limit[opt])
-		{
-			return f;
-		}
-	}
-
-	/* Return default settings block (which may have a zero limit set) */
-	f = find_floodsettings_block("unknown-users");
-	if (!f)
-		abort(); /* impossible */
-
-	return f;
-}
-
-MODVAR const char *floodoption_names[] = {
-	"nick-flood",
-	"join-flood",
-	"away-flood",
-	"invite-flood",
-	"knock-flood",
-	"max-concurrent-conversations",
-	"lag-penalty",
-	"vhost-flood",
-	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
@@ -1,250 +0,0 @@
-#include "unrealircd.h"
-
-/**************** UTF8 HELPER FUNCTIONS START HERE *****************/
-
-/* Operations on UTF-8 strings.
- * This part is taken from "glib" with the following copyright:
- * Copyright (C) 1999 Tom Tromey
- * Copyright (C) 2000 Red Hat, Inc.
- * Taken from the master snapshot on Oct 23, 2018, glib/gutf8.c.
- * The library uses LGPL 2.1. From what I understand this allows me to
- * use this code in a GPLv2-compatible way which fits the rest of
- * the UnrealIRCd project.
- *
- * Code stripped and converted heavily to fit in UnrealIRCd by
- * Bram Matthys ("Syzop") in 2019. Thanks to i <info@servx.org>
- * for all the directions and help with regards to UTF8 handling.
- *
- * Note that with UnrealIRCd, a char is always unsigned char,
- * which allows us to cut some corners and make more readable
- * code without 100 casts.
- */
-
-#define VALIDATE_BYTE(mask, expect) \
-  do {                              \
-    if ((*p & (mask)) != (expect))  \
-      goto error;                   \
-  } while(0)
-
-/* see IETF RFC 3629 Section 4 */
-
-static const char *fast_validate(const char *str)
-{
-	const char *p;
-
-	for (p = str; *p; p++)
-	{
-		if (*p >= 128)
-		{
-			const char *last;
-
-			last = p;
-			if (*p < 0xe0) /* 110xxxxx */
-			{
-				// ehm.. did you forget a ++p ? ;) or whatever
-				if (*p < 0xc2)
-				{
-					goto error;
-				}
-			}
-			else
-			{
-				if (*p < 0xf0) /* 1110xxxx */
-				{
-					switch (*p++ & 0x0f)
-					{
-						case 0:
-							VALIDATE_BYTE(0xe0, 0xa0); /* 0xa0 ... 0xbf */
-							break;
-						case 0x0d:
-							VALIDATE_BYTE(0xe0, 0x80); /* 0x80 ... 0x9f */
-							break;
-						default:
-							VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
-					}
-				}
-				else if (*p < 0xf5) /* 11110xxx excluding out-of-range */
-				{
-					switch (*p++ & 0x07)
-					{
-						case 0:
-							VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
-							if ((*p & 0x30) == 0)
-								goto error;
-							break;
-						case 4:
-							VALIDATE_BYTE(0xf0, 0x80); /* 0x80 ... 0x8f */
-							break;
-						default:
-							VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
-					}
-					p++;
-					VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
-				}
-				else
-				{
-					goto error;
-				}
-			}
-
-			p++;
-			VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
-
-			continue;
-
-error:
-			return last;
-		}
-	}
-
-	return p;
-}
-
-/** Check if a string is valid UTF8.
- * @param str   The string to validate
- * @param end   Pointer to char *, as explained in notes below.
- * @returns 1 if the string is valid UTF8, 0 if not.
- * @note  The variable *end will be set to the first invalid UTF8 sequence.
- *        If no invalid UTF8 sequence is encountered then it points to the NUL byte.
- */
-int unrl_utf8_validate(const char *str, const char **end)
-{
-	const char *p;
-
-	p = fast_validate(str);
-
-	if (end)
-		*end = p;
-
-	if (*p != '\0')
-		return 0;
-	else
-		return 1;
-}
-
-/** Go backwards in a string until we are at the end of an UTF8 sequence.
- * Or more accurately: skip sequences that are part of an UTF8 sequence.
- * @param begin   The string to check
- * @param p       Where to start backtracking
- * @returns Byte that is not in the middle of an UTF8 sequence,
- *          or NULL if we reached the beginning and that isn't valid either.
- */
-char *unrl_utf8_find_prev_char (const char *begin, const char *p)
-{
-	for (--p; p >= begin; --p)
-	{
-		if ((*p & 0xc0) != 0x80)
-			return (char *)p;
-	}
-	return NULL;
-}
-
-/** Return a valid UTF8 string based on the input.
- * @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 *outputbuf, size_t outputbuflen, int strictlen)
-{
-	const char *remainder, *invalid;
-	int remaining_bytes, valid_bytes, len;
-	int replaced = 0; /**< UTF8 string needed replacement (was invalid) */
-
-	if (!str || !outputbuflen)
-		return NULL;
-
-	len = strlen(str);
-
-	*outputbuf = '\0';
-	remainder = str;
-	remaining_bytes = len;
-
-	while (remaining_bytes != 0)
-	{
-		if (unrl_utf8_validate(remainder, &invalid))
-		{
-			if (!replaced)
-			{
-				if (strictlen)
-				{
-					/* Caller wants us to go through the 'replaced' branch */
-					strlcpy(outputbuf, str, outputbuflen);
-					replaced = 1;
-				}
-				break;
-			} else {
-				/* We already replaced earlier, now just put the rest at the end. */
-				strlcat(outputbuf, remainder, outputbuflen);
-				break;
-			}
-		}
-		replaced = 1;
-		valid_bytes = invalid - remainder;
-
-		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;
-	}
-
-	if (!replaced)
-		return (char *)str; /* return original string (no changes needed) */
-
-	/* 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(outputbuf) == outputbuflen-1)
-	{
-		char *cut_at = unrl_utf8_find_prev_char(outputbuf, outputbuf+outputbuflen-1);
-		if (cut_at)
-			*cut_at = '\0';
-	}
-
-#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 outputbuf;
-}
-
-/**************** END OF UTF8 HELPER FUNCTIONS *****************/
-
-/** This is just for internal testing */
-void utf8_test(void)
-{
-	char buf[1024];
-	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, workbuf, workbuflen, 1);
-		if (heapbuf == res)
-		{
-			printf("    %s\n", res);
-		} else {
-			printf("[!] %s\n", res);
-		}
-		free(heapbuf);
-	}
-	safe_free(workbuf);
-}
diff --git a/src/version.c.SH b/src/version.c.SH
@@ -1,282 +0,0 @@
-# $Id$
-
-echo "Extracting src/version.c..."
-
-#id=`grep '$Id: Changes,v' ../Changes`
-#id=`echo $id |sed 's/.* Changes\,v \(.*\) .* Exp .*/\1/'`
-if [ -d ../.git ]; then
-	SUFFIX="-$(git rev-parse --short HEAD)"
-fi
-id="6.1.0$SUFFIX"
-echo "$id"
-
-if test -r version.c
-then
-   generation=`sed -n 's/^char \*generation = \"\(.*\)\";/\1/p' < version.c`
-   if test ! "$generation" ; then generation=0; fi
-else
-   generation=0
-fi
-
-generation=`expr $generation + 1`
-export LANG=C
-export LC_TIME=C
-export LC_ALL=C
-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 \
-         { print $1 " "  $2 " " $3 " " $7 " at " $4 " " $5 " " $6 }}'`
-
-cat >version.c <<!SUB!THIS!
-/*
- *   IRC - Internet Relay Chat, ircd/version.c
- *   Copyright (C) 1990 Chelsea Ashley Dyerman
- *
- *   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.
- * 
- *   $Id$
- */
-
-/*
- * This file is generated by version.c.SH. Any changes made will go away.
- */
-
-#include "struct.h"
-#include "version.h"
-#include "license.h"
-
-char *generation = "$generation";
-char *creation = "$creation";
-#define IRCDTOTALVERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5 PATCH6 PATCH7 PATCH8 PATCH9
-char *version = IRCDTOTALVERSION;
-char *buildid = "$id";
-/* moved to s_serv.c */
-char *infotext[] = 
-{ 0 };
-
-char *unrealcredits[] =
-{
-	"==================[ " IRCDTOTALVERSION " Credits ]===================",
-	"The people on this list are people who have helped us with the",
-	"development of UnrealIRCd and who have made remarkable",
-	"contributions to the project.",
-	" ",
-	"==========================[ Donations ]==========================",
-	"BlueFlame^, [Real] - ChatFIRST.com, Jameno123 - ByteHosting,",
-	"Internet Services, Interlink Access Corp, Jan Knutar, ThePlayer,",
-	"Headband, noriko, powerstorm.net, RedMaxima, IronHelix, xnet.org,",
-	"Pierce - irc.AAcNet.org, Franky75 - Betas-Online.com, irc.vco.se,",
-	"Beldock - irc.coldfront.net, Kusau - chat.tochat.org, Japsclan,",
-	"WolfLord - UplinkCorp, Isaiah - irc.frogstar.us, Kedrin Milborn,",
-	"Dionisios Koutsikos, Tank - irc.scifi-fans.net, irc.P2Pchat.net,",
-	"Leo Zhadanovsky - irc.leozh.net, Lyote - ZodiaCIrC/DecayOnline,",
-	"Jesse Lanning - Secure Technology Networks, HERZ - insiderZ.DE,",
-	"James Yerge - Kronical Internet Solutions, djw - irc.perldev.org,",
-	"MichaelJE2 - irc.xcelor8.com, AfterShock - irc.liquidvoltage.net,",
-	"recycled-irc - www.recycled-irc.org, Alpha - irc.FireWirez.net,",
-	"FrostByghte - irc.coldfront.net, SdgNem - irc.RealmOfGaming.net,",
-	"MagicalTux - irc.FF.st, irc.P2Pchat.net, Ganja51 - irc.lcirc.net,",
-	"^White_Magic^ - irc.chatuniverse.net, Nick - irc.plasmachat.net,",
-	"Matridom - www.WinDrivers.com, anaconda - irc.lightmoon.org,", 
-	"mnslinky - http://www.secure-computing.net, Justin Furnas,",
-	"Andy Hansis - irc.technerd.net, Crimson - www.n00bstories.com",
-	"Devin Reams, Cleggo - irc.ugcentral.net, Tillo - irc.OSirc.net,",
-	"Matthew Burdine - irc.owns.us, Philip Veale - flame.tiefighter.org,",
-	"Latinus - irc.lokanova.com, Vincent Guesnard - irc.rs2i.net,",
-	"Windfyre IRC Network - irc.windfyre.net, SetNine - www.setnine.com,",
-	"|S| - irc.chatuniverse.net, CommTech Inc. - www.commtechusa.com,",
-	"CatNet IRC - irc.catslair.org, IcE-IRC - irc.ice-irc.de,",
-	"Kevin Devaux, Jackal - www.fyrebird.net, Lax - insiderZ.DE,",
-	"The_devil - chat.snyggast.se, Deltaanime Network - deltaanime.net,",
-	"CatNet IRC network - irc.catslair.org, A-Buz.com - a-buz.com,",
-	"LimonCenter Networks - www.limoncenter.com, Jean-Pierre Fournier,",
-	"AloneInTheDark - irc.sleepwalkers.org, El_barto - irc.ircdit.net,",
-	"Pyrexnetworks - www.pyrexnetworks.com, zunnie - www.MP-Gaming.NET,",
-	"All My Data - www.amdwebhost.com, Wulfie - irc.soundsnwaves.net,",
-	"eCoupons.com - www.ecoupons.com, MauritZ - irc.MindForge.org,",
-	"Voodoohosting - www.voodoohosting.com,",
-	"RealityBytez IRC Network - irc.realitybytez.net,",
-	"LFS - www.linuxfromscratch.org, The_devil - chat.snyggast.se,",
-	"Stephen - dirac.betas-online.com, NationWars - www.nationwars.com,",
-	"SurrealChat.net - www.surrealchat.net, NamesDir - www.namesdir.com,",
-	"Muisje - www.elitez0r.com, Jaya Sri - www.krisna.cc,",
-	"DiabloClone.Org - irc.diabloclone.org, RisingNet - www.RisingNet.net,",
-	"DCloneIRC.net - irc.dcloneirc.net, L.E.-Nation - irc.le-nation.de,",
-	"RomeoIRC - irc.romeoirc.net, Comparison Shopping - www.order.com,",
-	"Atak Trucking - www.AtakTrucking.com, www.Gamehostingprovider.com,",
-	"Xzibition.com - irc.xzibition.com, www.TheBookmarkShop.com,",
-	"Compare Prices - www.PriceV.com, Joe Gronlund - www.joegronlund.com,",
-	"Exoware - www.exoware.net, Bitmaster - irc.synIRC.net,",
-	"Angel IRC - irc.angelirc.net, Pixel - http://indavoid.net,",
-	"Grandview Landscape and Masonry - www.grandviewoutdoor.com,",
-	"Saibot Technologies - http://saibottechnologies.com/,",
-	"IRC-IRC.de Community - irc.irc-irc.de,",
-	"Mike Meisner - www.hottubcoverdepot.com, CubaChat - irc.cubachat.org,",
-	"BluePromoCode - www.bluepromocode.com, DontPayFull - www.dontpayfull.com",
-	"Anikwa - ukblabberbox.co.uk, xShellz - www.xshellz.com, wico,",
-	"Magnolia Road Internet Cooperative, FrostWire - www.frostwire.com,",
-	"Bundeld Inc. - couponlawn.com, Chameleon John - www.chameleonjohn.com,",
-	"Ecigsopedia.com - www.ecigsopedia.com/halo-cigs-coupon,",
-	"Hello Voucher Card - www.hellovouchercard.co.uk, Knoji - knoji.com,",
-	"EveryCloud - www.everycloudtech.com, CouponRaven - couponraven.com,",
-	"Dealspotr - dealspotr.com, GuitarFella.com, Coupofy - Coupofy.com,",
-	"Underfloor Heating Systems - www.underfloorheatingsystems.co.uk,",
-	"Project Free TV - newprojectfreetv.com, companyaccountscheck.com,",
-	"Coyote Traffic - www.coyotetraffic.com, www.emailextractor14lite.com,",
-	"bet365 Bonus Code - williamspromocodes.co.uk/bet365-bonus-code-sports/,",
-	"Valerie Amelia Pond",
-	" ",
-	"==========================[ Supporters ]==========================",
-	"Our support staff: Cronus, Jobe, SpaceDog, Stealth",
-	" ",
-	"Thanks to former supporters and anyone who has helped without",
-	"officially joining the staff.",
-	" ",
-	"===========================[ Coders ]=============================",
-	"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",
-	" ",
-	"For a full list of coders & contributors, see /INFO (/QUOTE INFO)",
-	" ",
-	"==================================================================",
-	"Finally, we would like to thank everyone else who has helped",
-	"UnrealIRCd in any way: reporting bugs, testing releases, helping",
-	"others, recommending UnrealIRCd to friends, or simply just by",
-	"running UnrealIRCd to ensure that we are and will continue to be",
-	"the most used IRC daemon in the world.",
-	"==================================================================",
-	0
-}; 
-
-
-char unreallogo[] = 
-{
-32,95,32,32,32,95,32,32,32,32,32,
-32,32,32,32,32,32,32,32,32,32,32,
-32,32,32,32,32,32,95,32,95,95,95,
-95,95,95,95,95,95,95,95,32,32,95,
-95,95,95,95,32,32,32,32,32,95,32,
-10,124,32,124,32,124,32,124,32,32,32,
-32,32,32,32,32,32,32,32,32,32,32,
-32,32,32,32,32,32,124,32,124,95,32,
-32,32,95,124,32,95,95,95,32,92,47,
-32,32,95,95,32,92,32,32,32,124,32,
-124,10,124,32,124,32,124,32,124,95,32,
-95,95,32,32,95,32,95,95,32,95,95,
-95,32,32,95,95,32,95,124,32,124,32,
-124,32,124,32,124,32,124,95,47,32,47,
-124,32,47,32,32,92,47,32,95,95,124,
-32,124,10,124,32,124,32,124,32,124,32,
-39,95,32,92,124,32,39,95,95,47,32,
-95,32,92,47,32,95,96,32,124,32,124,
-32,124,32,124,32,124,32,32,32,32,47,
-32,124,32,124,32,32,32,32,47,32,95,
-96,32,124,10,124,32,124,95,124,32,124,
-32,124,32,124,32,124,32,124,32,124,32,
-32,95,95,47,32,40,95,124,32,124,32,
-124,95,124,32,124,95,124,32,124,92,32,
-92,32,124,32,92,95,95,47,92,32,40,
-95,124,32,124,10,32,92,95,95,95,47,
-124,95,124,32,124,95,124,95,124,32,32,
-92,95,95,95,124,92,95,95,44,95,124,
-95,124,92,95,95,95,47,92,95,124,32,
-92,95,124,32,92,95,95,95,95,47,92,
-95,95,44,95,124,10,0,85,110,114,
-101,97,108,73,82,67,100,0};
-
-char *dalinfotext[] =
-    {
-        "$package --",
-        "Based on the original code written by Jarkko Oikarinen",
-        "Copyright 1988, 1989, 1990, 1991 University of Oulu, Computing Center",
-        "",
-        "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.",
-	"- Any name/comment should never be changed except by the one who made it -",
-	"",
-	"UnrealIRCd contains code developed by:",
-	"Potvin       Chris Wolkowski          potvin@acestar.org",
-	"RogerY       Roger Y.                 rogery@austnet.org",
-	"GZ                                    gz@starchat.net",
-	"binary                                ",
-	"Dianora      Diane Bruce",
-	"lucas", 
-	"Roar Thronaas <roart@nvg.ntnu.no> added IPv6 support.",
-	"",
-	"",
-	"The following people have helped in making the DALnet ircd",
-        "that is based on irc2.8.21.mu3.2 :",
-        "",
-        "Russell      Russell Miller           russell@dal.net",
-        "Donwulff     Jukka Santala            donwulff@dal.net",
-        "Aetobatus    Michael Sawyer           aetobatus@dal.net",
-        "Dalvenjah    Sven Nielsen             dalvenjah@dal.net",
-        "Skandranon   Michael Graff            explorer@flame.org",
-        "Barubary     -                        barubary@dal.net",
-        "white_dragon Chip Norkus              wd@dal.net",
-        "DuffJ        Dafydd James             duffj@dal.net",
-        "taz          David Kopstain           taz@dal.net",
-        "NikB         Nik Bougalis             nikb@dal.net",
-        "Rakarra      -                        rakarra@dal.net",
-        "DarkRot      Lucas Madar              darkrot@dal.net",
-        "Studded      -                        studded@dal.net",
-        "JoelKatz     David Schwartz           joelkatz@dal.net",
-        "",
-        "This product includes software developed by Colin Plumb.",
-        "",
-        "The following persons have made many changes and enhancements to the",
-        "code and still know how IRC really works if you have questions about it:",
-        "",
-        "Run          Carlo Kid                carlo@runaway.xs4all.nl",
-        "Avalon       Darren Reed              avalon@coombs.anu.edu.au",
-        "msa          Markku Savela            Markku.Savela@vtt.fi",
-        "Wumpus       Greg Lindahl             gl8f@virginia.edu",
-        "WiZ          Jarkko Oikarinen         jto@tolsun.oulu.fi",
-        "Argv         Armin Gruner Armin.Gruner@Informatik.TU-Muenchen.de",
-        "",
-        "Thanks to the following people for help with preparing 2.8",
-        "",
-        "phone        Matthew Green            phone@coombs.anu.edu.au",
-        "Sodapop      Chuck Kane               ckane@ece.uiuc.edu",
-        "Skygod       Matt Lyle                matt@oc.com",
-        "Vesa         Vesa Ruokonen            ruokonen@lut.fi",
-        "Nap          Nicolas PIOCH pioch@poly.polytechnique.fr",
-        "",
-        "Those who helped in prior versions and continue to be helpful:",
-        "",
-        "Stellan Klebom      Dan Goodwin         Mike Bolotski",
-        "Ian Frechette       Markku Jarvinen     Kimmo Suominen",
-        "Jeff Trim           Vijay Subramaniam   Karl Kleinpaste",
-        "Bill Wisner         Tom Davis           Hugo Calendar",
-        "Tom Hopkins         Stephen van den Berg",
-        "Bo Adler            Michael Sandrof     Jon Solomon",
-        "Jan Peterson        Helen Rose          Paul Graham",
-        "",
-        "Thanks also goes to those persons not mentioned here who have added",
-        "their advice, opinions, and code to IRC.",
-        "Thanks also to those who provide the kind sys admins who let me and",
-        "others continue to develop IRC.",
-        "",
-
-        0
-    };
-!SUB!THIS!
diff --git a/src/whowas.c b/src/whowas.c
@@ -1,208 +0,0 @@
-/************************************************************************
-*   IRC - Internet Relay Chat, src/whowas.c
-*   Copyright (C) 1990 Markku Savela
-*
-*   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"
-
-// FIXME: move this to cmd_whowas,
-// Consider making add_history an efunc? Or via a hook?
-// Some users may not want to load cmd_whowas at all.
-
-void add_whowas_to_clist(WhoWas **, WhoWas *);
-void del_whowas_from_clist(WhoWas **, WhoWas *);
-void add_whowas_to_list(WhoWas **, WhoWas *);
-void del_whowas_from_list(WhoWas **, WhoWas *);
-
-WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
-WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
-
-MODVAR int whowas_next = 0;
-
-void free_whowas_fields(WhoWas *e)
-{
-	safe_free(e->name);
-	safe_free(e->hostname);
-	safe_free(e->virthost);
-	safe_free(e->realname);
-	safe_free(e->username);
-	safe_free(e->account);
-	safe_free(e->ip);
-	e->servername = NULL;
-	e->event = 0;
-	e->logon = 0;
-	e->logoff = 0;
-	e->connected_since = 0;
-
-	/* Remove from lists and reset hashv */
-	if (e->online)
-		del_whowas_from_clist(&(e->online->user->whowas), e);
-	del_whowas_from_list(&WHOWASHASH[e->hashv], e);
-	e->hashv = -1;
-}
-
-void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event)
-{
-	e->hashv = hash_whowas_name(client->name);
-	e->event = event;
-	e->connected_since = get_creationtime(client);
-	e->logon = client->lastnick;
-	e->logoff = TStime();
-	e->umodes = client->umodes;
-	safe_strdup(e->name, client->name);
-	safe_strdup(e->username, client->user->username);
-	safe_strdup(e->hostname, client->user->realhost);
-	safe_strdup(e->ip, client->ip);
-	if (client->user->virthost)
-		safe_strdup(e->virthost, client->user->virthost);
-	else
-		safe_strdup(e->virthost, "");
-	e->servername = client->user->server;
-	safe_strdup(e->realname, client->info);
-	if (strcmp(client->user->account, "0"))
-		safe_strdup(e->account, client->user->account);
-
-	/* Its not string copied, a pointer to the scache hash is copied
-	   -Dianora
-	 */
-	/*  strlcpy(e->servername, client->user->server,HOSTLEN); */
-	e->servername = client->user->server;
-}
-
-void add_history(Client *client, int online, WhoWasEvent event)
-{
-	WhoWas *new;
-
-	new = &WHOWAS[whowas_next];
-
-	if (new->hashv != -1)
-		free_whowas_fields(new);
-
-	create_whowas_entry(client, new, event);
-
-	if (online)
-	{
-		new->online = client;
-		add_whowas_to_clist(&(client->user->whowas), new);
-	} else {
-		new->online = NULL;
-	}
-	add_whowas_to_list(&WHOWASHASH[new->hashv], new);
-	whowas_next++;
-	if (whowas_next == NICKNAMEHISTORYLENGTH)
-		whowas_next = 0;
-}
-
-void off_history(Client *client)
-{
-	WhoWas *temp, *next;
-
-	for (temp = client->user->whowas; temp; temp = next)
-	{
-		next = temp->cnext;
-		temp->online = NULL;
-		del_whowas_from_clist(&(client->user->whowas), temp);
-	}
-}
-
-Client *get_history(const char *nick, time_t timelimit)
-{
-	WhoWas *temp;
-	int  blah;
-
-	timelimit = TStime() - timelimit;
-	blah = hash_whowas_name(nick);
-	temp = WHOWASHASH[blah];
-	for (; temp; temp = temp->next)
-	{
-		if (mycmp(nick, temp->name))
-			continue;
-		if (temp->logoff < timelimit)
-			continue;
-		return temp->online;
-	}
-	return NULL;
-}
-
-void count_whowas_memory(int *wwu, u_long *wwum)
-{
-	WhoWas *tmp;
-	int  i;
-	int  u = 0;
-	u_long um = 0;
-	/* count the number of used whowas structs in 'u' */
-	/* count up the memory used of whowas structs in um */
-
-	for (i = 0, tmp = &WHOWAS[0]; i < NICKNAMEHISTORYLENGTH; i++, tmp++)
-		if (tmp->hashv != -1)
-		{
-			u++;
-			um += sizeof(WhoWas);
-		}
-	*wwu = u;
-	*wwum = um;
-	return;
-}
-
-void initwhowas()
-{
-	int  i;
-
-	for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
-	{
-		memset(&WHOWAS[i], 0, sizeof(WhoWas));
-		WHOWAS[i].hashv = -1;
-	}
-	for (i = 0; i < WHOWAS_HASH_TABLE_SIZE; i++)
-		WHOWASHASH[i] = NULL;
-}
-
-void add_whowas_to_clist(WhoWas ** bucket, WhoWas * whowas)
-{
-	whowas->cprev = NULL;
-	if ((whowas->cnext = *bucket) != NULL)
-		whowas->cnext->cprev = whowas;
-	*bucket = whowas;
-}
-
-void del_whowas_from_clist(WhoWas ** bucket, WhoWas * whowas)
-{
-	if (whowas->cprev)
-		whowas->cprev->cnext = whowas->cnext;
-	else
-		*bucket = whowas->cnext;
-	if (whowas->cnext)
-		whowas->cnext->cprev = whowas->cprev;
-}
-
-void add_whowas_to_list(WhoWas ** bucket, WhoWas * whowas)
-{
-	whowas->prev = NULL;
-	if ((whowas->next = *bucket) != NULL)
-		whowas->next->prev = whowas;
-	*bucket = whowas;
-}
-
-void del_whowas_from_list(WhoWas ** bucket, WhoWas * whowas)
-{
-	if (whowas->prev)
-		whowas->prev->next = whowas->next;
-	else
-		*bucket = whowas->next;
-	if (whowas->next)
-		whowas->next->prev = whowas->prev;
-}
diff --git a/src/windows/Icon1.ico b/src/windows/Icon1.ico
Binary files differ.
diff --git a/src/windows/UnrealIRCd.exe.manifest b/src/windows/UnrealIRCd.exe.manifest
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
-<assemblyIdentity
-    processorArchitecture="amd64"
-    name="UnrealIRCd.UnrealIRCd.6"
-    version="6.1.0.0"
-    type="win32"
-/>
-<description>Internet Relay Chat Daemon</description>
-<dependency>
-    <dependentAssembly>
-        <assemblyIdentity
-            type="win32"
-            name="Microsoft.Windows.Common-Controls"
-            version="6.0.0.0"
-            processorArchitecture="amd64"
-            publicKeyToken="6595b64144ccf1df"
-            language="*"
-        />
-    </dependentAssembly>
-</dependency>
-</assembly>
diff --git a/src/windows/bar.bmp b/src/windows/bar.bmp
Binary files differ.
diff --git a/src/windows/compilerhelp.c b/src/windows/compilerhelp.c
@@ -1,55 +0,0 @@
-/*
- *
- * A helper program for the compilation process
- *
- */
-
-/* x,y,z,w 
- * | | | `-- private build
- * | | `---- release build
- * | `------ minor version
- * `-------- major version
- */
-
-#include <stdio.h>
-
-void main(int argc,char *argv[])
-{
-	FILE *openme;
-	char inbuf[512];
-	int i,pb=0,rb=0,mi=0,ma=0;
-
-	if (argc == 1)
-		exit(-1);
-
-	if ((openme = fopen(argv[1],"r+"))==NULL)
-	{
-		printf("error\n");
-		exit(-1);
-	}
-
-	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&pb);		/*Read Buffer*/
-	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&rb);
-	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&mi);
-	fscanf(openme,"%s %s %d\n",inbuf,inbuf,&ma);
-
-	pb++;
-	if (argc > 2)
-	if (atoi(argv[2])==0)  /*Public Build*/
-		rb++;
-
-	printf("new version = %d,%d,%d,%d",ma,mi,rb,pb);
-
-	rewind(openme);
-
-	fprintf(openme,"#define pb %d\n",pb);		/*Write Buffer*/
-	fprintf(openme,"#define rb %d\n",rb);
-	fprintf(openme,"#define mi %d\n",mi);
-	fprintf(openme,"#define ma %d\n",ma);
-
-	fprintf(openme,"#define vFILEVERSION ma,mi,rb,pb\n#define vPRODUCTVERSION ma,mi,0,0\n#define vDISPFILEVERSION \"%d,%d,%d,%d\\0\"\n#define vSUBBUILD \"%d\\0\"\n",ma,mi,rb,pb,pb);
-
-	fclose(openme);
-
-
-}
diff --git a/src/windows/config.c b/src/windows/config.c
@@ -1,56 +0,0 @@
-
-#include <stdio.h>
-#include <string.h>
-int main() {
-	FILE *fd = fopen("Changes", "r");
-	FILE *fd2;
-	char buf[1024];
-	int i = 0, space = 0, j = 0;
-	char releaseid[512];
-	int generation = 0;
-
-	*releaseid = '\0';
-
-	i = 0;
-	fd = fopen("src/version.c", "r");
-	if (!fd)
-		generation = 1;
-	else {
-		while (fgets(buf, 1023, fd)) {
-			if (!strstr(buf, "char *generation"))
-				continue;
-			while (!isdigit(buf[i]))
-					i++;
-			j = i;
-			while (isdigit(buf[j])) 
-				j++;
-			buf[j] = 0;
-			generation = (atoi(&buf[i])+1);
-		}
-	}
-	fd = fopen("src/version.c.sh", "r");
-	if (!fd)
-		return 0;
-	fd2 = fopen("src/version.c", "w");
-	if (!fd2)
-		return 0;
-	while (fgets(buf, 1023, fd)) {
-		if (!strncmp("cat >version.c <<!SUB!THIS!",buf,27)) {
-			while (fgets(buf, 1023, fd)) {
-				if (!strncmp("!SUB!THIS!",buf,10))
-					break;
-				if (!strncmp("char *creation = \"$creation\";",buf,29)) 
-					fprintf(fd2,"char *creation = __TIMESTAMP__;\n");
-				else if (!strncmp("char *generation = \"$generation\";",buf,33))
-					fprintf(fd2,"char *generation = \"%d\";\n",generation);
-				else if (!strncmp("char *buildid = \"$id\";",buf,22))
-					fprintf(fd2,"char *buildid = \"%s\";\n",releaseid);
-				else
-					fprintf(fd2,"%s", buf);
-			}
-		}
-	}
-
-
-}
-	
-\ No newline at end of file
diff --git a/src/windows/def-clean.c b/src/windows/def-clean.c
@@ -1,39 +0,0 @@
-/* A small utility to clean a def file */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-int main(int argc, char *argv[]) {
-	FILE *fd, *fdout;
-	char buf[1024];
-
-	if (argc < 3)
-		exit(1);
-
-	if (!(fd = fopen(argv[1], "r")))
-		exit(2);
-	
-	if (!(fdout = fopen(argv[2], "w")))
-		exit(3);
-
-	while (fgets(buf, 1023, fd))
-	{
-		if (*buf == '\t') 
-		{
-			char *symbol = strtok(buf, " ");
-
-			if (!strncmp(symbol, "\t_real@", 7))
-				continue;
-			if (!strncmp(symbol, "\t_xmm@", 6))
-				continue;
-
-			fprintf(fdout, "%s\r\n", symbol);	
-		
-		}
-		else
-			fprintf(fdout, "%s", buf);
-
-	}
-	return 0;
-}
diff --git a/src/windows/editor.c b/src/windows/editor.c
@@ -1,769 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/editor.c
- *   Copyright (C) 2004 Dominick Meglio (codemastr)
- *   
- *   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 <windowsx.h>
-#include <commctrl.h>
-#include <richedit.h>
-#include <commdlg.h>
-#include <stdlib.h>
-#include "resource.h"
-#include "win.h"
-
-LRESULT CALLBACK GotoDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK ColorDLG(HWND, UINT, WPARAM, LPARAM);
-
-HWND hFind;
-
-/* Draws the statusbar for the editor
- * Parameters:
- *  hInstance  - The instance to create the statusbar in
- *  hwndParent - The parent of the statusbar
- *  iId        - The message value used to send messages to the parent
- * Returns:
- *  The handle to the statusbar
- */
-HWND DrawStatusbar(HINSTANCE hInstance, HWND hwndParent, UINT iId)
-{
-	HWND hStatus, hTip;
-	TOOLINFO ti;
-	RECT clrect;
-	hStatus = CreateStatusWindow(WS_CHILD|WS_VISIBLE|SBT_TOOLTIPS, NULL, hwndParent, iId);
-	hTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
- 		WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP, 0, 0, 0, 0, hwndParent, NULL, hInstance, NULL);
-	GetClientRect(hStatus, &clrect);
-	ti.cbSize = sizeof(TOOLINFO);
-	ti.uFlags = TTF_SUBCLASS;
-	ti.hwnd = hStatus;
-	ti.uId = 1;
-	ti.hinst = hInstance;
-	ti.rect = clrect;
-	ti.lpszText = "Go To";
-	SendMessage(hTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
-	return hStatus;
-}
-
-/* Draws the toolbar for the editor
- * Parameters:
- *  hInstance  - The instance to create the toolbar in
- *  hwndParent - The parent of the toolbar
- * Returns:
- *  The handle to the toolbar
- */
-HWND DrawToolbar(HINSTANCE hInstance, HWND hwndParent) 
-{
-	HWND hTool;
-	TBADDBITMAP tbBit;
-	int newidx;
-	TBBUTTON tbButtons[10] = {
-		{ STD_FILENEW, IDM_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ STD_FILESAVE, IDM_SAVE, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
-		{ STD_CUT, IDM_CUT, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ STD_COPY, IDM_COPY, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ STD_PASTE, IDM_PASTE, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
-		{ STD_UNDO, IDM_UNDO, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ STD_REDOW, IDM_REDO, 0, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0}
-	};
-		
-	TBBUTTON tbAddButtons[7] = {
-		{ 0, IDC_BOLD, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, 0},
-		{ 1, IDC_UNDERLINE, TBSTATE_ENABLED, TBSTYLE_CHECK, {0}, 0L, 0},
-		{ 2, IDC_COLOR, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ 3, IDC_BGCOLOR, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
-		{ 4, IDC_GOTO, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
-		{ STD_FIND, IDC_FIND, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0}
-	};
-	hTool = CreateToolbarEx(hwndParent, WS_VISIBLE|WS_CHILD|TBSTYLE_FLAT|TBSTYLE_TOOLTIPS, 
-				IDC_TOOLBAR, 0, HINST_COMMCTRL, IDB_STD_SMALL_COLOR,
-				tbButtons, 10, 0, 0, 100, 30, sizeof(TBBUTTON));
-	tbBit.hInst = hInstance;
-	tbBit.nID = IDB_BITMAP1;
-	newidx = SendMessage(hTool, TB_ADDBITMAP, (WPARAM)5, (LPARAM)&tbBit);
-	tbAddButtons[0].iBitmap += newidx;
-	tbAddButtons[1].iBitmap += newidx;
-	tbAddButtons[2].iBitmap += newidx;
-	tbAddButtons[3].iBitmap += newidx;
-	tbAddButtons[5].iBitmap += newidx;
-	SendMessage(hTool, TB_ADDBUTTONS, (WPARAM)7, (LPARAM)&tbAddButtons);
-	return hTool;
-}
-
-/* Dialog procedure for the color selection dialog
- * Parameters:
- *  hDlg    - The dialog handle
- *  message - The message received
- *  wParam  - The first message parameter
- *  lParam  - The second message parameter
- * Returns:
- *  TRUE if the message was processed, FALSE otherwise
- */
-LRESULT CALLBACK ColorDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
-	static HBRUSH hBrushWhite, hBrushBlack, hBrushDarkBlue, hBrushDarkGreen, hBrushRed,
-		hBrushDarkRed, hBrushPurple, hBrushOrange, hBrushYellow, hBrushGreen, hBrushVDarkGreen,
-		hBrushLightBlue, hBrushBlue, hBrushPink, hBrushDarkGray, hBrushGray;
-	static UINT ResultMsg = 0;
-
-	switch (message) 
-	{
-		case WM_INITDIALOG:
-			hBrushWhite = CreateSolidBrush(RGB(255,255,255));
-			hBrushBlack = CreateSolidBrush(RGB(0,0,0));
-			hBrushDarkBlue = CreateSolidBrush(RGB(0,0,127));
-			hBrushDarkGreen = CreateSolidBrush(RGB(0,147,0));
-			hBrushRed = CreateSolidBrush(RGB(255,0,0));
-			hBrushDarkRed = CreateSolidBrush(RGB(127,0,0));
-			hBrushPurple = CreateSolidBrush(RGB(156,0,156));
-			hBrushOrange = CreateSolidBrush(RGB(252,127,0));
-			hBrushYellow = CreateSolidBrush(RGB(255,255,0));
-			hBrushGreen = CreateSolidBrush(RGB(0,252,0));
-			hBrushVDarkGreen = CreateSolidBrush(RGB(0,147,147));
-			hBrushLightBlue = CreateSolidBrush(RGB(0,255,255));
-			hBrushBlue = CreateSolidBrush(RGB(0,0,252));
-			hBrushPink = CreateSolidBrush(RGB(255,0,255));
-			hBrushDarkGray = CreateSolidBrush(RGB(127,127,127));
-			hBrushGray = CreateSolidBrush(RGB(210,210,210));
-			ResultMsg = (UINT)lParam;
-			SetFocus(NULL);
-			return TRUE;
-		case WM_DRAWITEM: 
-		{
-			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
-			if (wParam == IDC_WHITE) 
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushWhite);
-			if (wParam == IDC_BLACK)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushBlack);
-			if (wParam == IDC_DARKBLUE) 
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkBlue);
-			if (wParam == IDC_DARKGREEN) 
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkGreen);
-			if (wParam == IDC_RED)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushRed);
-			if (wParam == IDC_DARKRED)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkRed);
-			if (wParam == IDC_PURPLE)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushPurple);
-			if (wParam == IDC_ORANGE)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushOrange);
-			if (wParam == IDC_YELLOW)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushYellow);
-			if (wParam == IDC_GREEN)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushGreen);
-			if (wParam == IDC_VDARKGREEN)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushVDarkGreen);
-			if (wParam == IDC_LIGHTBLUE)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushLightBlue);
-			if (wParam == IDC_BLUE)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushBlue);
-			if (wParam == IDC_PINK)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushPink);
-			if (wParam == IDC_DARKGRAY)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushDarkGray);
-			if (wParam == IDC_GRAY)
-				FillRect(lpdis->hDC, &lpdis->rcItem, hBrushGray);
-			DrawEdge(lpdis->hDC, &lpdis->rcItem, EDGE_SUNKEN, BF_RECT);
-			return TRUE;
-		}
-		case WM_COMMAND: 
-		{
-			COLORREF clrref;
-			if (LOWORD(wParam) == IDC_WHITE) 
-				clrref = RGB(255,255,255);
-			else if (LOWORD(wParam) == IDC_BLACK)
-				clrref = RGB(0,0,0);
-			else if (LOWORD(wParam) == IDC_DARKBLUE)
-				clrref = RGB(0,0,127);
-			else if (LOWORD(wParam) == IDC_DARKGREEN)
-				clrref = RGB(0,147,0);
-			else if (LOWORD(wParam) == IDC_RED)
-				clrref = RGB(255,0,0);
-			else if (LOWORD(wParam) == IDC_DARKRED)
-				clrref = RGB(127,0,0);
-			else if (LOWORD(wParam) == IDC_PURPLE)
-				clrref = RGB(156,0,156);
-			else if (LOWORD(wParam) == IDC_ORANGE)
-				clrref = RGB(252,127,0);
-			else if (LOWORD(wParam) == IDC_YELLOW)
-				clrref = RGB(255,255,0);
-			else if (LOWORD(wParam) == IDC_GREEN)
-				clrref = RGB(0,252,0);
-			else if (LOWORD(wParam) == IDC_VDARKGREEN)
-				clrref = RGB(0,147,147);
-			else if (LOWORD(wParam) == IDC_LIGHTBLUE)
-				clrref = RGB(0,255,255);
-			else if (LOWORD(wParam) == IDC_BLUE)
-				clrref = RGB(0,0,252);
-			else if (LOWORD(wParam) == IDC_PINK)
-				clrref = RGB(255,0,255);
-			else if (LOWORD(wParam) == IDC_DARKGRAY)
-				clrref = RGB(127,127,127);
-			else if (LOWORD(wParam) == IDC_GRAY)
-				clrref = RGB(210,210,210);
-			SendMessage(GetParent(hDlg), ResultMsg, (WPARAM)clrref, (LPARAM)hDlg);
-			break;
-		}
-		case WM_CLOSE:
-			EndDialog(hDlg, TRUE);
-		case WM_DESTROY:
-			DeleteObject(hBrushWhite);
-			DeleteObject(hBrushBlack);
-			DeleteObject(hBrushDarkBlue);
-			DeleteObject(hBrushDarkGreen);
-			DeleteObject(hBrushRed);
-			DeleteObject(hBrushDarkRed);
-			DeleteObject(hBrushPurple);
-			DeleteObject(hBrushOrange);
-			DeleteObject(hBrushYellow);
-			DeleteObject(hBrushGreen);
-			DeleteObject(hBrushVDarkGreen);
-			DeleteObject(hBrushLightBlue);
-			DeleteObject(hBrushBlue);
-			DeleteObject(hBrushPink);
-			DeleteObject(hBrushDarkGray);
-			DeleteObject(hBrushGray);
-			break;
-	}
-
-	return FALSE;
-}
-
-/* Dialog procedure for the goto dialog
- * Parameters:
- *  hDlg    - The dialog handle
- *  message - The message received
- *  wParam  - The first message parameter
- *  lParam  - The second message parameter
- * Returns:
- *  TRUE if the message was processed, FALSE otherwise
- */
-LRESULT CALLBACK GotoDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	if (message == WM_COMMAND) 
-	{
-		if (LOWORD(wParam) == IDCANCEL)
-			EndDialog(hDlg, TRUE);
-		else if (LOWORD(wParam) == IDOK) 
-		{
-			HWND hWnd = GetDlgItem(GetParent(hDlg),IDC_TEXT);
-			int line = GetDlgItemInt(hDlg, IDC_GOTO, NULL, FALSE);
-			int pos = SendMessage(hWnd, EM_LINEINDEX, (WPARAM)--line, 0);
-			SendMessage(hWnd, EM_SETSEL, (WPARAM)pos, (LPARAM)pos);
-			SendMessage(hWnd, EM_SCROLLCARET, 0, 0);
-			EndDialog(hDlg, TRUE);
-		}
-	}
-	return FALSE;
-}
-
-LRESULT CALLBACK FromFileDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	HWND hWnd;
-	static FINDREPLACE find;
-	static char findbuf[256];
-	static unsigned char *file;
-	static HWND hTool, hClip, hStatus;
-	static RECT rOld;
-	CHARFORMAT2 chars;
-
-	if (message == WM_FINDMSGSTRING)
-	{
-		FINDREPLACE *fr = (FINDREPLACE *)lParam;
-
-		if (fr->Flags & FR_FINDNEXT)
-		{
-			HWND hRich = GetDlgItem(hDlg, IDC_TEXT);
-			DWORD flags=0;
-			FINDTEXTEX ft;
-			CHARRANGE chrg;
-
-			if (fr->Flags & FR_DOWN)
-				flags |= FR_DOWN;
-			if (fr->Flags & FR_MATCHCASE)
-				flags |= FR_MATCHCASE;
-			if (fr->Flags & FR_WHOLEWORD)
-				flags |= FR_WHOLEWORD;
-			ft.lpstrText = fr->lpstrFindWhat;
-			SendMessage(hRich, EM_EXGETSEL, 0, (LPARAM)&chrg);
-			if (flags & FR_DOWN)
-			{
-				ft.chrg.cpMin = chrg.cpMax;
-				ft.chrg.cpMax = -1;
-			}
-			else
-			{
-				ft.chrg.cpMin = chrg.cpMin;
-				ft.chrg.cpMax = -1;
-			}
-			if (SendMessage(hRich, EM_FINDTEXTEX, flags, (LPARAM)&ft) == -1)
-				MessageBox(NULL, "UnrealIRCd has finished searching the document",
-					"Find", MB_ICONINFORMATION|MB_OK);
-			else
-			{
-				SendMessage(hRich, EM_EXSETSEL, 0, (LPARAM)&(ft.chrgText));
-				SendMessage(hRich, EM_SCROLLCARET, 0, 0);
-				SetFocus(hRich);
-			}
-		}
-		return TRUE;
-	}
-	switch (message) 
-	{
-		case WM_INITDIALOG: 
-		{
-			int fd,len;
-			char *buffer, *string;
-			EDITSTREAM edit;
-			StreamIO *stream = safe_alloc(sizeof(StreamIO));
-			unsigned char szText[256];
-			struct stat sb;
-			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT), hTip;
-			file = (unsigned char *)lParam;
-			if (file)
-				wsprintf(szText, "UnrealIRCd Editor - %s", file);
-			else 
-				strcpy(szText, "UnrealIRCd Editor - New File");
-			SetWindowText(hDlg, szText);
-			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)RESubClassFunc);
-			hTool = DrawToolbar(hInst, hDlg);
-			hStatus = DrawStatusbar(hInst, hDlg, IDC_STATUS);
-			SendMessage(hWnd, EM_SETEVENTMASK, 0, (LPARAM)ENM_SELCHANGE);
-			chars.cbSize = sizeof(CHARFORMAT2);
-			chars.dwMask = CFM_FACE;
-			strcpy(chars.szFaceName,"Fixedsys");
-			SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_ALL, (LPARAM)&chars);
-			if ((fd = open(file, _O_RDONLY|_O_BINARY)) != -1) 
-			{
-				fstat(fd,&sb);
-				/* Only allocate the amount we need */
-				buffer = safe_alloc(sb.st_size+1);
-				len = read(fd, buffer, sb.st_size);
-				buffer[len] = 0;
-				len = CountRTFSize(buffer)+1;
-				string = safe_alloc(len);
-				IRCToRTF(buffer,string);
-				RTFBuf = string;
-				len--;
-				stream->size = &len;
-				stream->buffer = &RTFBuf;
-				edit.dwCookie = (DWORD_PTR)stream;
-				edit.pfnCallback = SplitIt;
-				SendMessage(hWnd, EM_EXLIMITTEXT, 0, (LPARAM)0x7FFFFFFF);
-				SendMessage(hWnd, EM_STREAMIN, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
-				SendMessage(hWnd, EM_SETMODIFY, (WPARAM)FALSE, 0);
-				SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0);
-				close(fd);
-				RTFBuf = NULL;
-				safe_free(buffer);
-				safe_free(string);
-				safe_free(stream);
-				hClip = SetClipboardViewer(hDlg);
-				if (SendMessage(hWnd, EM_CANPASTE, 0, 0)) 
-					SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(TRUE,0));
-				else
-					SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(FALSE,0));
-				SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_UNDO, (LPARAM)MAKELONG(FALSE,0));
-				wsprintf(szText, "Line: 1");
-				SetWindowText(hStatus, szText);
-			}
-			return TRUE;
-		}
-		case WM_WINDOWPOSCHANGING:
-		{
-			GetClientRect(hDlg, &rOld);
-			return FALSE;
-		}
-		case WM_SIZE:
-		{
-			DWORD new_width, new_height;
-			HWND hRich;
-			RECT rOldRich;
-			DWORD old_width, old_height;
-			DWORD old_rich_width, old_rich_height;
-			if (hDlg == hFind)
-				return FALSE;
-			new_width =  LOWORD(lParam);
-			new_height = HIWORD(lParam);
-			hRich  = GetDlgItem(hDlg, IDC_TEXT);
-			SendMessage(hStatus, WM_SIZE, 0, 0);
-			SendMessage(hTool, TB_AUTOSIZE, 0, 0);
-			old_width = rOld.right-rOld.left;
-			old_height = rOld.bottom-rOld.top;
-			new_width = new_width - old_width;
-			new_height = new_height - old_height;
-			GetWindowRect(hRich, &rOldRich);
-			old_rich_width = rOldRich.right-rOldRich.left;
-			old_rich_height = rOldRich.bottom-rOldRich.top;
-			SetWindowPos(hRich, NULL, 0, 0, old_rich_width+new_width, 
-				old_rich_height+new_height,
-				SWP_NOMOVE|SWP_NOREPOSITION|SWP_NOZORDER);
-			memset(&rOld, 0, sizeof(rOld));
-			return TRUE;
-		} 
-
-		case WM_NOTIFY:
-			switch (((NMHDR *)lParam)->code) 
-			{
-				case EN_SELCHANGE: 
-				{
-					HWND hWnd = GetDlgItem(hDlg, IDC_TEXT);
-					DWORD start, end, currline;
-					static DWORD prevline = 0;
-					unsigned char buffer[512];
-					chars.cbSize = sizeof(CHARFORMAT2);
-					SendMessage(hWnd, EM_GETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-					if (chars.dwMask & CFM_BOLD && chars.dwEffects & CFE_BOLD)
-						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_BOLD, (LPARAM)MAKELONG(TRUE,0));
-					else
-						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_BOLD, (LPARAM)MAKELONG(FALSE,0));
-					if (chars.dwMask & CFM_UNDERLINE && chars.dwEffects & CFE_UNDERLINE)
-						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_UNDERLINE, (LPARAM)MAKELONG(TRUE,0));
-					else
-						SendMessage(hTool, TB_CHECKBUTTON, (WPARAM)IDC_UNDERLINE, (LPARAM)MAKELONG(FALSE,0));
-					SendMessage(hWnd, EM_GETSEL,(WPARAM)&start, (LPARAM)&end);
-					if (start == end) 
-					{
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_COPY, (LPARAM)MAKELONG(FALSE,0));
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_CUT, (LPARAM)MAKELONG(FALSE,0));
-					}
-					else 
-					{
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_COPY, (LPARAM)MAKELONG(TRUE,0));
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_CUT, (LPARAM)MAKELONG(TRUE,0));
-					}
-					if (SendMessage(hWnd, EM_CANUNDO, 0, 0)) 
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_UNDO, (LPARAM)MAKELONG(TRUE,0));
-					else
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_UNDO, (LPARAM)MAKELONG(FALSE,0));
-					if (SendMessage(hWnd, EM_CANREDO, 0, 0)) 
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_REDO, (LPARAM)MAKELONG(TRUE,0));
-					else
-						SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_REDO, (LPARAM)MAKELONG(FALSE,0));
-					currline = SendMessage(hWnd, EM_LINEFROMCHAR, (WPARAM)-1, 0);
-					currline++;
-					if (currline != prevline) 
-					{
-						wsprintf(buffer, "Line: %d", currline);
-						SetWindowText(hStatus, buffer);
-						prevline = currline;
-					}
-				return TRUE;
-			}
-			case TTN_GETDISPINFO: 
-			{
-				LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam;
-				lpttt->hinst = NULL;
-				switch (lpttt->hdr.idFrom) 
-				{
-					case IDM_NEW:
-						strcpy(lpttt->szText, "New");
-						break;
-					case IDM_SAVE:
-						strcpy(lpttt->szText, "Save");
-						break;
-					case IDM_CUT:
-						strcpy(lpttt->szText, "Cut");
-						break;
-					case IDM_COPY:
-						strcpy(lpttt->szText, "Copy");
-						break;
-					case IDM_PASTE:
-						strcpy(lpttt->szText, "Paste");
-						break;
-					case IDM_UNDO:
-						strcpy(lpttt->szText, "Undo");
-						break;
-					case IDM_REDO:
-						strcpy(lpttt->szText, "Redo");
-						break;
-					case IDC_BOLD:
-						strcpy(lpttt->szText, "Bold");
-						break;
-					case IDC_UNDERLINE:
-						strcpy(lpttt->szText, "Underline");
-						break;
-					case IDC_COLOR:
-						strcpy(lpttt->szText, "Text Color");
-						break;
-					case IDC_BGCOLOR:
-						strcpy(lpttt->szText, "Background Color");
-						break;
-					case IDC_GOTO:
-						strcpy(lpttt->szText, "Goto");
-						break;
-					case IDC_FIND:
-						strcpy(lpttt->szText, "Find");
-						break;
-				}
-				return TRUE;
-			}
-			case NM_DBLCLK:
-				DialogBox(hInst, "GOTO", hDlg, (DLGPROC)GotoDLG);
-				return (TRUE);
-		}
-				
-				return (TRUE);
-		case WM_COMMAND:
-			if (LOWORD(wParam) == IDC_BOLD) 
-			{
-				hWnd = GetDlgItem(hDlg, IDC_TEXT);
-				if (SendMessage(hTool, TB_ISBUTTONCHECKED, (WPARAM)IDC_BOLD, (LPARAM)0) != 0) 
-				{
-					chars.cbSize = sizeof(CHARFORMAT2);
-					chars.dwMask = CFM_BOLD;
-					chars.dwEffects = CFE_BOLD;
-					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
-					SetFocus(hWnd);
-				}
-				else 
-				{
-					chars.cbSize = sizeof(CHARFORMAT2);
-					chars.dwMask = CFM_BOLD;
-					chars.dwEffects = 0;
-					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
-					SetFocus(hWnd);
-				}
-				return TRUE;
-			}
-			else if (LOWORD(wParam) == IDC_UNDERLINE) 
-			{
-				hWnd = GetDlgItem(hDlg, IDC_TEXT);
-				if (SendMessage(hTool, TB_ISBUTTONCHECKED, (WPARAM)IDC_UNDERLINE, (LPARAM)0) != 0) 
-				{
-					chars.cbSize = sizeof(CHARFORMAT2);
-					chars.dwMask = CFM_UNDERLINETYPE;
-					chars.bUnderlineType = CFU_UNDERLINE;
-					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
-					SetFocus(hWnd);
-				}
-				else 
-				{
-					chars.cbSize = sizeof(CHARFORMAT2);
-					chars.dwMask = CFM_UNDERLINETYPE;
-					chars.bUnderlineType = CFU_UNDERLINENONE;
-					SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-					SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
-					SetFocus(hWnd);
-				}
-				return TRUE;
-			}
-			if (LOWORD(wParam) == IDC_COLOR) 
-			{
-				DialogBoxParam(hInst, "Color", hDlg, (DLGPROC)ColorDLG, (LPARAM)WM_USER+10);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDC_BGCOLOR)
-			{
-				DialogBoxParam(hInst, "Color", hDlg, (DLGPROC)ColorDLG, (LPARAM)WM_USER+11);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDC_GOTO)
-			{
-				DialogBox(hInst, "GOTO", hDlg, (DLGPROC)GotoDLG);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDC_FIND)
-			{
-				static FINDREPLACE fr;
-				memset(&fr, 0, sizeof(fr));
-				fr.lStructSize = sizeof(FINDREPLACE);
-				fr.hwndOwner = hDlg;
-				fr.lpstrFindWhat = findbuf;
-				fr.wFindWhatLen = 255;
-				hFind = FindText(&fr);
-				return 0;
-			}
-				
-			hWnd = GetDlgItem(hDlg, IDC_TEXT);
-			if (LOWORD(wParam) == IDM_COPY) 
-			{
-				SendMessage(hWnd, WM_COPY, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_SELECTALL) 
-			{
-				SendMessage(hWnd, EM_SETSEL, 0, -1);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_PASTE) 
-			{
-				SendMessage(hWnd, WM_PASTE, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_CUT) 
-			{
-				SendMessage(hWnd, WM_CUT, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_UNDO) 
-			{
-				SendMessage(hWnd, EM_UNDO, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_REDO) 
-			{
-				SendMessage(hWnd, EM_REDO, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_DELETE) 
-			{
-				SendMessage(hWnd, WM_CLEAR, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_SAVE) 
-			{
-				int fd;
-				EDITSTREAM edit;
-				OPENFILENAME lpopen;
-				if (!file) 
-				{
-					unsigned char path[MAX_PATH];
-					path[0] = '\0';
-					memset(&lpopen, 0, sizeof(lpopen));
-					lpopen.lStructSize = sizeof(OPENFILENAME);
-					lpopen.hwndOwner = hDlg;
-					lpopen.lpstrFilter = NULL;
-					lpopen.lpstrCustomFilter = NULL;
-					lpopen.nFilterIndex = 0;
-					lpopen.lpstrFile = path;
-					lpopen.nMaxFile = MAX_PATH;
-					lpopen.lpstrFileTitle = NULL;
-					lpopen.lpstrInitialDir = CONFDIR;
-					lpopen.lpstrTitle = NULL;
-					lpopen.Flags = (OFN_ENABLESIZING|OFN_NONETWORKBUTTON|
-							OFN_OVERWRITEPROMPT);
-					if (GetSaveFileName(&lpopen))
-						file = path;
-					else
-						break;
-				}
-				fd = open(file, _O_TRUNC|_O_CREAT|_O_WRONLY|_O_BINARY,_S_IWRITE);
-				edit.dwCookie = 0;
-				edit.pfnCallback = BufferIt;
-				SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_STREAMOUT, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
-				RTFToIRC(fd, RTFBuf, strlen(RTFBuf));
-				safe_free(RTFBuf);
-				RTFBuf = NULL;
-				SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_SETMODIFY, (WPARAM)FALSE, 0);
-	
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_NEW) 
-			{
-				unsigned char text[1024];
-				BOOL newfile = FALSE;
-				int ans;
-				if (SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_GETMODIFY, 0, 0) != 0) 
-				{
-					sprintf(text, "The text in the %s file has changed.\r\n\r\nDo you want to save the changes?", file ? file : "new");
-					ans = MessageBox(hDlg, text, "UnrealIRCd", MB_YESNOCANCEL|MB_ICONWARNING);
-					if (ans == IDNO)
-						newfile = TRUE;
-					if (ans == IDCANCEL)
-						return TRUE;
-					if (ans == IDYES) 
-					{
-						SendMessage(hDlg, WM_COMMAND, MAKEWPARAM(IDM_SAVE,0), 0);
-						newfile = TRUE;
-					}
-				}
-				else
-					newfile = TRUE;
-				if (newfile == TRUE) 
-				{
-					unsigned char szText[256];
-					file = NULL;
-					strcpy(szText, "UnrealIRCd Editor - New File");
-					SetWindowText(hDlg, szText);
-					SetWindowText(GetDlgItem(hDlg, IDC_TEXT), NULL);
-				}
-				break;
-			}
-			break;
-		case WM_USER+10: 
-		{
-			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT);
-			EndDialog((HWND)lParam, TRUE);
-			chars.cbSize = sizeof(CHARFORMAT2);
-			chars.dwMask = CFM_COLOR;
-			chars.crTextColor = (COLORREF)wParam;
-			SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-			SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
-			SetFocus(hWnd);
-			break;
-		}
-		case WM_USER+11: 
-		{
-			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT);
-			EndDialog((HWND)lParam, TRUE);
-			chars.cbSize = sizeof(CHARFORMAT2);
-			chars.dwMask = CFM_BACKCOLOR;
-			chars.crBackColor = (COLORREF)wParam;
-			SendMessage(hWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&chars);
-			SendMessage(hWnd, EM_HIDESELECTION, 0, 0);
-			SetFocus(hWnd);
-			break;
-		}
-		case WM_CHANGECBCHAIN:
-			if ((HWND)wParam == hClip)
-				hClip = (HWND)lParam;
-			else
-				SendMessage(hClip, WM_CHANGECBCHAIN, wParam, lParam);
-			break;
-		case WM_DRAWCLIPBOARD:
-			if (SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_CANPASTE, 0, 0)) 
-				SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(TRUE,0));
-			else
-				SendMessage(hTool, TB_ENABLEBUTTON, (WPARAM)IDM_PASTE, (LPARAM)MAKELONG(FALSE,0));
-			SendMessage(hClip, WM_DRAWCLIPBOARD, wParam, lParam);
-			break;
-		case WM_CLOSE: 
-		{
-			unsigned char text[256];
-			int ans;
-			if (SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_GETMODIFY, 0, 0) != 0) 
-			{
-				sprintf(text, "The text in the %s file has changed.\r\n\r\nDo you want to save the changes?", file ? file : "new");
-				ans = MessageBox(hDlg, text, "UnrealIRCd", MB_YESNOCANCEL|MB_ICONWARNING);
-				if (ans == IDNO)
-					EndDialog(hDlg, TRUE);
-				if (ans == IDCANCEL)
-					return TRUE;
-				if (ans == IDYES) 
-				{
-					SendMessage(hDlg, WM_COMMAND, MAKEWPARAM(IDM_SAVE,0), 0);
-					EndDialog(hDlg, TRUE);
-				}
-			}
-			else
-				EndDialog(hDlg, TRUE);
-			break;
-		}
-		case WM_DESTROY:
-			ChangeClipboardChain(hDlg, hClip);
-			break;
-	}
-
-	return FALSE;
-}
diff --git a/src/windows/gpl.rtf b/src/windows/gpl.rtf
@@ -1,97 +0,0 @@
-{\rtf1\ansi\ansicpg1250\deff0\deflang1033\deflangfe1060{\fonttbl{\f0\fswiss\fprq2\fcharset0 Verdana;}{\f1\fmodern\fprq1\fcharset0 Lucida Console;}}
-{\colortbl ;\red0\green0\blue0;}
-\viewkind4\uc1\pard\nowidctlpar\sb100\sa100\qc\lang1060\kerning36\b\f0\fs28 GNU General Public License
-\par \kerning0\b0\fs16 Version 2, June 1991
-\par \pard\nowidctlpar\f1\fs14 
-\par Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-\par 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
-\par 
-\par Everyone is permitted to copy and distribute verbatim copies
-\par of this license document, but changing it is not allowed.
-\par 
-\par \pard\keepn\nowidctlpar\sb100\sa100\qc\cf1\b\f0\fs20 Preamble\cf0\fs24 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b0\fs16 The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. 
-\par When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 
-\par To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 
-\par For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 
-\par We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 
-\par Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. 
-\par Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 
-\par The precise terms and conditions for copying, distribution and modification follow. 
-\par \pard\keepn\nowidctlpar\sb100\sa100\qc\cf1\b\fs20 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-\par \pard\nowidctlpar\fi142\sb100\sa100\cf0\fs16 0.\b0  This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 
-\par \pard\nowidctlpar\sb100\sa100 Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b 1.\b0  You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 
-\par \pard\nowidctlpar\sb100\sa100 You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b 2.\b0  You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 
-\par \pard\nowidctlpar\li284\sb100\sa100\b a)\b0  You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 
-\par \b b)\b0  You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 
-\par \b c)\b0  If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) 
-\par \pard\nowidctlpar\sb100\sa100 These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 
-\par Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 
-\par In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b 3.\b0  You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: \v <!-- we use this doubled UL to get the sub-sections indented, --><!-- while making the bullets as unobvious as possible. -->\v0 
-\par \pard\nowidctlpar\li284\sb100\sa100\b a)\b0  Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
-\par \b b)\b0  Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
-\par \b c)\b0  Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) 
-\par \pard\nowidctlpar\sb100\sa100 The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 
-\par If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b 4.\b0  You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 
-\par \b 5.\b0  You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 
-\par \b 6.\b0  Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 
-\par \b 7.\b0  If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. 
-\par \pard\nowidctlpar\sb100\sa100 If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 
-\par It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 
-\par This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b 8.\b0  If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 
-\par \b 9.\b0  The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 
-\par \pard\nowidctlpar\sb100\sa100 Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 
-\par \pard\nowidctlpar\fi142\sb100\sa100\b 10.\b0  If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 
-\par \pard\nowidctlpar\sb100\sa100\qc\cf1\fs20 NO WARRANTY
-\par \pard\nowidctlpar\fi142\sb100\sa100\cf0\b\fs16 11.\b0  BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 
-\par \b 12.\b0  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
-\par \pard\keepn\nowidctlpar\sb100\sa100\qc\cf1\b END OF TERMS AND CONDITIONS
-\par \fs20 How to Apply These Terms to Your New Programs
-\par \pard\nowidctlpar\fi142\sb100\sa100\cf0\b0\fs16 If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 
-\par To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 
-\par \pard\nowidctlpar\li284\f1\fs14 
-\par \i one line to give the program's name and an idea of what it does.\i0 
-\par Copyright (C) \i yyyy\i0   \i name of author\i0 
-\par 
-\par This program is free software; you can redistribute it and/or
-\par modify it under the terms of the GNU General Public License
-\par as published by the Free Software Foundation; either version 2
-\par of the License, or (at your option) any later version.
-\par 
-\par This program is distributed in the hope that it will be useful,
-\par but WITHOUT ANY WARRANTY; without even the implied warranty of
-\par MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-\par GNU General Public License for more details.
-\par 
-\par You should have received a copy of the GNU General Public License
-\par along with this program; if not, write to the Free Software
-\par Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-\par 
-\par \pard\nowidctlpar\sb100\sa100\f0\fs16 Also add information on how to contact you by electronic and paper mail. 
-\par If the program is interactive, make it output a short notice like this when it starts in an interactive mode: 
-\par \pard\nowidctlpar\li284\f1\fs14 
-\par Gnomovision version 69, Copyright (C) \i year\i0  \i name of author\i0 
-\par Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
-\par type `show w'.  This is free software, and you are welcome
-\par to redistribute it under certain conditions; type `show c' 
-\par for details.
-\par 
-\par \pard\nowidctlpar\sb100\sa100\f0\fs16 The hypothetical commands \f1\fs14 `show w'\f0\fs16  and \f1\fs14 `show c'\f0\fs16  should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than \f1\fs14 `show w'\f0\fs16  and \f1\fs14 `show c'\f0\fs16 ; they could even be mouse-clicks or menu items--whatever suits your program. 
-\par You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: 
-\par \pard\nowidctlpar\li284\f1\fs14 
-\par Yoyodyne, Inc., hereby disclaims all copyright
-\par interest in the program `Gnomovision'
-\par (which makes passes at compilers) written 
-\par by James Hacker.
-\par 
-\par \i signature of Ty Coon\i0 , 1 April 1989
-\par Ty Coon, President of Vice
-\par 
-\par \pard\nowidctlpar\sb100\sa100\f0\fs16 This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. 
-\par }
-
-\ No newline at end of file
diff --git a/src/windows/gplplusssl.rtf b/src/windows/gplplusssl.rtf
@@ -1,196 +0,0 @@
-{\rtf1\ansi\ansicpg1252\uc1 \deff0\deflang1030\deflangfe1030{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f28\fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Verdana;}
-{\f29\fmodern\fcharset0\fprq1{\*\panose 020b0609040504020204}Lucida Console;}{\f30\froman\fcharset238\fprq2 Times New Roman CE;}{\f31\froman\fcharset204\fprq2 Times New Roman Cyr;}{\f33\froman\fcharset161\fprq2 Times New Roman Greek;}
-{\f34\froman\fcharset162\fprq2 Times New Roman Tur;}{\f35\froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f36\froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f37\froman\fcharset186\fprq2 Times New Roman Baltic;}
-{\f254\fswiss\fcharset238\fprq2 Verdana CE;}{\f255\fswiss\fcharset204\fprq2 Verdana Cyr;}{\f257\fswiss\fcharset161\fprq2 Verdana Greek;}{\f258\fswiss\fcharset162\fprq2 Verdana Tur;}{\f261\fswiss\fcharset186\fprq2 Verdana Baltic;}
-{\f262\fmodern\fcharset238\fprq1 Lucida Console CE;}{\f263\fmodern\fcharset204\fprq1 Lucida Console Cyr;}{\f265\fmodern\fcharset161\fprq1 Lucida Console Greek;}{\f266\fmodern\fcharset162\fprq1 Lucida Console Tur;}}{\colortbl;\red0\green0\blue0;
-\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;
-\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1030\langfe1030\cgrid\langnp1030\langfenp1030 \snext0 Normal;}{
-\s1\qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\outlinelevel0\rin0\lin0\itap0 \b\f28\fs16\lang1060\langfe1030\cgrid\langnp1060\langfenp1030 \sbasedon0 \snext0 heading 1;}{\*\cs10 \additive Default Paragraph Font;}{\*\cs15 \additive \ul\cf2 
-\sbasedon10 Hyperlink;}}{\info{\author Carsten V. Munk}{\operator Carsten V. Munk}{\creatim\yr2002\mo8\dy24\hr14\min55}{\revtim\yr2002\mo8\dy24\hr15\min10}{\version3}{\edmins15}{\nofpages5}{\nofwords2726}{\nofchars15541}{\*\company IRCsystems}
-{\nofcharsws19085}{\vern8249}}\widowctrl\ftnbj\aenddoc\hyphhotz425\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\horzdoc\dghspace120\dgvspace120\dghorigin1701\dgvorigin1984\dghshow0\dgvshow3
-\jcompress\viewkind4\viewscale100\nolnhtadjtbl \fet0\sectd \linex0\headery708\footery708\colsx708\sectdefaultcl {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3
-\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}
-{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain 
-\qc \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 \fs24\lang1030\langfe1030\cgrid\langnp1030\langfenp1030 {\b\f28\fs28\lang1060\langfe1030\kerning36\langnp1060 GNU General Public License
-\par }{\f28\fs16\lang1060\langfe1030\langnp1060 Version 2, June 1991
-\par }\pard \ql \li0\ri0\nowidctlpar\faauto\rin0\lin0\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
-\par Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-\par 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
-\par 
-\par Everyone is permitted to copy and distribute verbatim copies
-\par of this license document, but changing it is not allowed.
-\par 
-\par }\pard \qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs20\cf1\lang1060\langfe1030\langnp1060 Preamble}{\b\f28\lang1060\langfe1030\langnp1060 
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
-The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make s
-ure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the G
-NU Library General Public License instead.) You can apply it to your programs, too. 
-\par When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies
- of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 
-\par To protect your r
-ights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 
-\par For exam
-ple, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know
- their rights. 
-\par We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 
-\par Also, for each author's protection and ours, we want to make 
-certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by other
-s will not reflect on the original authors' reputations. 
-\par Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making th
-e program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 
-\par The precise terms and conditions for copying, distribution and modification follow. 
-\par }\pard \qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs20\cf1\lang1060\langfe1030\langnp1060 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 0.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to an
-y such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into a
-nother language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 Activities other than copying, distribution and modification are not covered by this License; they are outside its s
-cope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what t
-he Program does. 
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 1.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty
-; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 
-
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 2.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 
-
-\par }\pard \ql \li284\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin284\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 a)}{\f28\fs16\lang1060\langfe1030\langnp1060 
- You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 b)}{\f28\fs16\lang1060\langfe1030\langnp1060  You must cause any work that you distribute or pub
-lish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 c)}{\f28\fs16\lang1060\langfe1030\langnp1060  If the modified program normally reads commands interactively when 
-run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warran
-t
-y) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Progr
-am is not required to print an announcement.) 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
-These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, t
-hen this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the ter
-ms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 
-\par Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 
-\par In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 3.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: }{\v\f28\fs16\lang1060\langfe1030\langnp1060 
-<!-- we use this doubled UL to get the sub-sections indented, --><!-- while making the bullets as unobvious as possible. -->}{\f28\fs16\lang1060\langfe1030\langnp1060 
-\par }\pard \ql \li284\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin284\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 a)}{\f28\fs16\lang1060\langfe1030\langnp1060  Accompany it with the complete corresponding machine-readabl
-e source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 b)}{\f28\fs16\lang1060\langfe1030\langnp1060  Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no mo
-re than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 
-
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 c)}{\f28\fs16\lang1060\langfe1030\langnp1060  Ac
-company it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offe
-r, in accord with Subsection b above.) 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
-The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated i
-nterface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary for
-m) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 
-\par If distribution of executable or object code is made by offering access to copy from a
- designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 4.}{\f28\fs16\lang1060\langfe1030\langnp1060  You may not copy, modif
-y, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, partie
-s who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 5.}{\f28\fs16\lang1060\langfe1030\langnp1060  You are not required to accept this License, since you have not signed it. However, nothing els
-e grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicat
-e your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 6.}{\f28\fs16\lang1060\langfe1030\langnp1060  Each time you redistribute the Program (or any work based on the Program), the recipient automatically 
-receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible fo
-r enforcing compliance by third parties to this License. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 7.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreeme
-nt or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, 
-t
-hen as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could sa
-tisfy both it and this License would be to refrain entirely from distribution of the Program. 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 
-If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 
-\par It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting 
-the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of t
-hat system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 
-\par This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 8.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geogra
-phical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 9.}{\f28\fs16\lang1060\langfe1030\langnp1060  The Free Softwar
-e Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 Each version is given a
- distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by th
-e Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 10.}{\f28\fs16\lang1060\langfe1030\langnp1060  If you wish to incorporate parts of the Program into other free programs whose dist
-ribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by t
-he two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 
-\par }\pard \qc \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs20\cf1\lang1060\langfe1030\langnp1060 NO WARRANTY
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\lang1060\langfe1030\langnp1060 11.}{\f28\fs16\lang1060\langfe1030\langnp1060 
- BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EX
-TENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTI
-ES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 
-\par }{\b\f28\fs16\lang1060\langfe1030\langnp1060 12.}{\f28\fs16\lang1060\langfe1030\langnp1060  IN NO EV
-ENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONS
-E
-QUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS
-), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
-\par }\pard \qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\rin0\lin0\itap0 {\b\f28\fs16\cf1\lang1060\langfe1030\langnp1060 END OF TERMS AND CONDITIONS
-\par }{\b\f28\fs20\cf1\lang1060\langfe1030\langnp1060 How to Apply These Terms to Your New Programs
-\par }\pard \ql \fi142\li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 If you develop a new program, and you want it to be of the greatest possible use to the pub
-lic, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 
-\par To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effecti
-vely convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 
-\par }\pard \ql \li284\ri0\nowidctlpar\faauto\rin0\lin284\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
-\par }{\i\f29\fs14\lang1060\langfe1030\langnp1060 one line to give the program's name and an idea of what it does.}{\f29\fs14\lang1060\langfe1030\langnp1060 
-\par Copyright (C) }{\i\f29\fs14\lang1060\langfe1030\langnp1060 yyyy}{\f29\fs14\lang1060\langfe1030\langnp1060   }{\i\f29\fs14\lang1060\langfe1030\langnp1060 name of author}{\f29\fs14\lang1060\langfe1030\langnp1060 
-\par 
-\par This program is free software; you can redistribute it and/or
-\par modify it under the terms of the GNU General Public License
-\par as published by the Free Software Foundation; either version 2
-\par of the License, or (at your option) any later version.
-\par 
-\par This program is distributed in the hope that it will be useful,
-\par but WITHOUT ANY WARRANTY; without even the implied warranty of
-\par MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-\par GNU General Public License for more details.
-\par 
-\par You should have received a copy of the GNU General Public License
-\par along with this program; if not, write to the Free Software
-\par Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-\par 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 Also add information on how to contact you by electronic and paper mail. 
-\par If the program is interactive, make it output a short notice like this when it starts in an interactive mode: 
-\par }\pard \ql \li284\ri0\nowidctlpar\faauto\rin0\lin284\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
-\par Gnomovision version 69, Copyright (C) }{\i\f29\fs14\lang1060\langfe1030\langnp1060 year}{\f29\fs14\lang1060\langfe1030\langnp1060  }{\i\f29\fs14\lang1060\langfe1030\langnp1060 name of author}{\f29\fs14\lang1060\langfe1030\langnp1060 
-\par Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
-\par type `show w'.  This is free software, and you are welcome
-\par to redistribute it under certain conditions; type `show c' 
-\par for details.
-\par 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 The hypothetical commands }{\f29\fs14\lang1060\langfe1030\langnp1060 `show w'}{\f28\fs16\lang1060\langfe1030\langnp1060  and }{
-\f29\fs14\lang1060\langfe1030\langnp1060 `show c'}{\f28\fs16\lang1060\langfe1030\langnp1060  should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than }{
-\f29\fs14\lang1060\langfe1030\langnp1060 `show w'}{\f28\fs16\lang1060\langfe1030\langnp1060  and }{\f29\fs14\lang1060\langfe1030\langnp1060 `show c'}{\f28\fs16\lang1060\langfe1030\langnp1060 
-; they could even be mouse-clicks or menu items--whatever suits your program. 
-\par You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: 
-\par }\pard \ql \li284\ri0\nowidctlpar\faauto\rin0\lin284\itap0 {\f29\fs14\lang1060\langfe1030\langnp1060 
-\par Yoyodyne, Inc., hereby disclaims all copyright
-\par interest in the program `Gnomovision'
-\par (which makes passes at compilers) written 
-\par by James Hacker.
-\par 
-\par }{\i\f29\fs14\lang1060\langfe1030\langnp1060 signature of Ty Coon}{\f29\fs14\lang1060\langfe1030\langnp1060 , 1 April 1989
-\par Ty Coon, President of Vice
-\par 
-\par }\pard \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 {\f28\fs16\lang1060\langfe1030\langnp1060 This Gen
-eral Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do,
- use the GNU Library General Public License instead of this License. 
-\par 
-\par }\pard\plain \s1\qc \li0\ri0\sb100\sa100\keepn\nowidctlpar\faauto\outlinelevel0\rin0\lin0\itap0 \b\f28\fs16\lang1060\langfe1030\cgrid\langnp1060\langfenp1030 {ADDITIONAL INFORMATION WITH REGARDS TO SSL-ENABLED UNREALIRCD
-\par }\pard\plain \ql \li0\ri0\sb100\sa100\nowidctlpar\faauto\rin0\lin0\itap0 \fs24\lang1030\langfe1030\cgrid\langnp1030\langfenp1030 {\f28\fs16\lang1060\langfe1030\langnp1060 
-This software package uses strong cryptography, so even if it is created, maintained and distributed from liberal countries in Europe (w
-here it is legal to do this), it falls under certain export/import and/or use restrictions in some other parts of the world. 
-\par PLEASE REMEMBER THAT EXPORT/IMPORT AND/OR USE OF STRONG CRYPTOGRAPHY SOFTWARE, PROVIDING CRYPTOGRAPHY HOOKS OR EVEN JUST COMMUNICA
-TING TECHNICAL DETAILS ABOUT CRYPTOGRAPHY SOFTWARE IS ILLEGAL IN SOME PARTS OF THE WORLD. SO, WHEN YOU IMPORT THIS PACKAGE TO YOUR COUNTRY, RE-DISTRIBUTE IT FROM THERE OR EVEN JUST EMAIL TECHNICAL SUGGESTIONS OR EVEN SOURCE PATCHES TO THE AUTHOR OR OTHER 
-PEOPLE YOU ARE STRONGLY ADVISED TO PAY CLOSE ATTENTION TO ANY EXPORT/IMPORT AND/OR USE LAWS WHICH APPLY TO YOU. THE AUTHORS OF OPENSSL, LIBRESSL OR UNREALIRCD ARE NOT LIABLE FOR ANY VIOLATIONS YOU MAKE HERE. SO BE CAREFUL, IT IS YOUR RESPONSIBILITY.
-\par CREDIT INFORMATION:
-\par This product includes cryptographic software written by Eric A. Young (eay@cryptsoft.com). This product includes software written by Tim J. Hudson (}{\field{\*\fldinst {\f28\fs16\lang1060\langfe1030\langnp1060  HYPERLINK "mailto:tjh@cryptsoft.com" }{
-\f28\fs16\lang1060\langfe1030\langnp1060 {\*\datafield 
-00d0c9ea79f9bace118c8200aa004ba90b02000000170000001200000074006a00680040006300720079007000740073006f00660074002e0063006f006d000000e0c9ea79f9bace118c8200aa004ba90b320000006d00610069006c0074006f003a0074006a00680040006300720079007000740073006f00660074002e00
-63006f006d000000}}}{\fldrslt {\cs15\f28\fs16\ul\cf2\lang1060\langfe1030\langnp1060 tjh@cryptsoft.com}}}{\f28\fs16\lang1060\langfe1030\langnp1060 ). This product uses the LibreSSL (https://www.libressl.org) or OpenSSL library (https://www.openssl.org).
-\par 
-\par }}
-\ No newline at end of file
diff --git a/src/windows/gui.c b/src/windows/gui.c
@@ -1,1051 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/gui.c
- *   Copyright (C) 2000-2004 David Flynn (DrBin) & Dominick Meglio (codemastr)
- *   
- *   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.
- */
-
-#define WIN32_VERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5
-
-#include "unrealircd.h"
-#include <windowsx.h>
-#include <commctrl.h>
-#include <commdlg.h>
-#include <richedit.h>
-#include <Strsafe.h>
-#include "resource.h"
-#include "win.h"
-
-#define TOOLBAR_START 82
-#define TOOLBAR_STOP (TOOLBAR_START+20)
-
-__inline void ShowDialog(HWND *handle, HINSTANCE inst, char *template, HWND parent, 
-			 DLGPROC proc)
-{
-	if (!IsWindow(*handle)) 
-	{
-		*handle = CreateDialog(inst, template, parent, (DLGPROC)proc); 
-		ShowWindow(*handle, SW_SHOW);
-	}
-	else
-		SetForegroundWindow(*handle);
-}
-
-/* Comments:
- * 
- * DrBin did a great job with the original GUI, but he has been gone a long time.
- * In his absense, it was decided it would be best to continue windows development.
- * The new code is based on his so it will be pretty much similar in features, my
- * main goal is to make it more stable. A lot of what I know about GUI coding 
- * I learned from DrBin so thanks to him for teaching me :) -- codemastr
- */
-
-LRESULT CALLBACK MainDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK LicenseDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK InfoDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK CreditsDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK HelpDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK StatusDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK ConfigErrorDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK FromVarDLG(HWND, UINT, WPARAM, LPARAM, unsigned char *, unsigned char **);
-LRESULT CALLBACK FromFileReadDLG(HWND, UINT, WPARAM, LPARAM);
-LRESULT CALLBACK FromFileDLG(HWND, UINT, WPARAM, LPARAM);
-
-HBRUSH MainDlgBackground;
-
-extern void SocketLoop(void *dummy);
-HINSTANCE hInst;
-NOTIFYICONDATA SysTray;
-HTREEITEM AddItemToTree(HWND, LPSTR, int, short);
-void win_map(Client *, HWND, short);
-extern Link *Servers;
-unsigned char *errors = NULL;
-extern VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv);
-void CleanUp(void)
-{
-	Shell_NotifyIcon(NIM_DELETE ,&SysTray);
-}
-HWND hStatusWnd;
-HWND hwIRCDWnd=NULL;
-HWND hwTreeView;
-HWND hWndMod;
-UINT WM_TASKBARCREATED, WM_FINDMSGSTRING;
-FARPROC lpfnOldWndProc;
-HMENU hContext;
-char OSName[OSVER_SIZE];
-
-void TaskBarCreated() 
-{
-	HICON hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(ICO_MAIN), IMAGE_ICON,16, 16, 0);
-	SysTray.cbSize = sizeof(NOTIFYICONDATA);
-	SysTray.hIcon = hIcon;
-	SysTray.hWnd = hwIRCDWnd;
-	SysTray.uCallbackMessage = WM_USER;
-	SysTray.uFlags = NIF_ICON|NIF_TIP|NIF_MESSAGE;
-	SysTray.uID = 0;
-	strcpy(SysTray.szTip, WIN32_VERSION);
-	Shell_NotifyIcon(NIM_ADD ,&SysTray);
-}
-
-LRESULT LinkSubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) 
-{
-	static HCURSOR hCursor;
-	if (!hCursor)
-		hCursor = LoadCursor(hInst, MAKEINTRESOURCE(CUR_HAND));
-	if (Message == WM_MOUSEMOVE || Message == WM_LBUTTONDOWN)
-		SetCursor(hCursor);
-
-	return CallWindowProc((WNDPROC)lpfnOldWndProc, hWnd, Message, wParam, lParam);
-}
-
-
-
-LRESULT RESubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) 
-{
-	POINT p;
-	RECT r;
-	DWORD start, end;
-	unsigned char string[500];
-
-	if (Message == WM_GETDLGCODE)
-		return DLGC_WANTALLKEYS;
-
-	
-	if (Message == WM_CONTEXTMENU) 
-	{
-		p.x = GET_X_LPARAM(lParam);
-		p.y = GET_Y_LPARAM(lParam);
-		if (GET_X_LPARAM(lParam) == -1 && GET_Y_LPARAM(lParam) == -1) 
-		{
-			GetClientRect(hWnd, &r);
-			p.x = (int)((r.left + r.right)/2);
-			p.y = (int)((r.top + r.bottom)/2);
-			ClientToScreen(hWnd,&p);
-		}
-		if (!SendMessage(hWnd, EM_CANUNDO, 0, 0)) 
-			EnableMenuItem(hContext, IDM_UNDO, MF_BYCOMMAND|MF_GRAYED);
-		else
-			EnableMenuItem(hContext, IDM_UNDO, MF_BYCOMMAND|MF_ENABLED);
-		if (!SendMessage(hWnd, EM_CANPASTE, 0, 0)) 
-			EnableMenuItem(hContext, IDM_PASTE, MF_BYCOMMAND|MF_GRAYED);
-		else
-			EnableMenuItem(hContext, IDM_PASTE, MF_BYCOMMAND|MF_ENABLED);
-		if (GetWindowLong(hWnd, GWL_STYLE) & ES_READONLY) 
-		{
-			EnableMenuItem(hContext, IDM_CUT, MF_BYCOMMAND|MF_GRAYED);
-			EnableMenuItem(hContext, IDM_DELETE, MF_BYCOMMAND|MF_GRAYED);
-		}
-		else 
-		{
-			EnableMenuItem(hContext, IDM_CUT, MF_BYCOMMAND|MF_ENABLED);
-			EnableMenuItem(hContext, IDM_DELETE, MF_BYCOMMAND|MF_ENABLED);
-		}
-		SendMessage(hWnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
-		if (start == end) 
-			EnableMenuItem(hContext, IDM_COPY, MF_BYCOMMAND|MF_GRAYED);
-		else
-			EnableMenuItem(hContext, IDM_COPY, MF_BYCOMMAND|MF_ENABLED);
-		TrackPopupMenu(hContext,TPM_LEFTALIGN|TPM_RIGHTBUTTON,p.x,p.y,0,GetParent(hWnd),NULL);
-		return 0;
-	}
-
-	return CallWindowProc((WNDPROC)lpfnOldWndProc, hWnd, Message, wParam, lParam);
-}
-
-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;
-	DoCloseUnreal(hWnd);
-	exit(0);
-}
-
-int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
-{
-	MSG msg;
-	unsigned char *s;
-	HWND hWnd;
-	HICON hIcon;
-	SC_HANDLE hService, hSCManager;
-	SERVICE_TABLE_ENTRY DispatchTable[] = 
-	{
-		{ "UnrealIRCd", ServiceMain },
-		{ 0, 0 }
-	};
-	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 6"
-	 */
-	chdir("..");
-
-	GetOSName(OSName);
-
-	/* Check if we are running as a service... */
-	hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
-	if ((hService = OpenService(hSCManager, "UnrealIRCd", SC_MANAGER_CONNECT)))
-	{
-		int save_err = 0;
-		StartServiceCtrlDispatcher(DispatchTable);
-		if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
-		{
-			SERVICE_STATUS status;
-			/* Restart handling, it's ugly but it's as
-			 * pretty as it is gonna get :)
-			 */
-			if (__argc == 2 && !strcmp(__argv[1], "restartsvc"))
-			{
-				QueryServiceStatus(hService, &status);
-				if (status.dwCurrentState != SERVICE_STOPPED)
-				{
-					ControlService(hService,
-						SERVICE_CONTROL_STOP, &status);
-					while (status.dwCurrentState == SERVICE_STOP_PENDING)
-					{
-						QueryServiceStatus(hService, &status);
-						if (status.dwCurrentState != SERVICE_STOPPED)
-							Sleep(1000);
-					}
-				}
-			}
-			if (!StartService(hService, 0, NULL))
-				save_err = GetLastError();
-		}
-
-		CloseServiceHandle(hService);
-		CloseServiceHandle(hSCManager);
-		if (save_err != ERROR_SERVICE_DISABLED)
-			exit(0);
-	} else {
-		CloseServiceHandle(hSCManager);
-	}
-	InitCommonControls();
-	WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated");
-	WM_FINDMSGSTRING = RegisterWindowMessage(FINDMSGSTRING);
-	atexit(CleanUp);
-	if (!LoadLibrary("riched20.dll"))
-		LoadLibrary("riched32.dll");
-	InitDebug();
-	init_winsock();
-	hInst = hInstance; 
-
-	MainDlgBackground = CreateSolidBrush(RGB(75, 134, 238)); /* Background of main dialog */
-
-	hWnd = CreateDialog(hInstance, "UnrealIRCd", 0, (DLGPROC)MainDLG); 
-	hwIRCDWnd = hWnd;
-	
-	TaskBarCreated();
-
-	if (InitUnrealIRCd(__argc, __argv) != 1)
-	{
-		MessageBox(NULL, "UnrealIRCd has failed to initialize in InitUnrealIRCd()", "UnrealIRCD Initalization Error" ,MB_OK);
-		return FALSE;
-	}
-	ShowWindow(hWnd, SW_SHOW);
-	_beginthread(SocketLoop, 0, NULL);
-	while (GetMessage(&msg, NULL, 0, 0))
-	{
-		if (!IsWindow(hStatusWnd) || !IsDialogMessage(hStatusWnd, &msg)) 
-		{
-			TranslateMessage(&msg);
-			DispatchMessage(&msg);
-		}
-	}
-	return FALSE;
-
-}
-
-LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
-{
-	static HCURSOR hCursor;
-	static HMENU hRehash, hAbout, hConfig, hTray, hLogs;
-
-	unsigned char *argv[3];
-	Client *pClient;
-	unsigned char *msg;
-	POINT p;
-
-	if (message == WM_TASKBARCREATED)
-	{
-		TaskBarCreated();
-		return TRUE;
-	}
-	
-	switch (message)
-	{
-		case WM_INITDIALOG: 
-		{
-			ShowWindow(hDlg, SW_HIDE);
-			hCursor = LoadCursor(hInst, MAKEINTRESOURCE(CUR_HAND));
-			hContext = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_CONTEXT)),0);
-			/* Rehash popup menu */
-			hRehash = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_REHASH)),0);
-			/* About popup menu */
-			hAbout = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_ABOUT)),0);
-			/* Systray popup menu set the items to point to the other menus*/
-			hTray = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(MENU_SYSTRAY)),0);
-			ModifyMenu(hTray, IDM_REHASH, MF_BYCOMMAND|MF_POPUP|MF_STRING, HandleToUlong(hRehash), "&Rehash");
-			ModifyMenu(hTray, IDM_ABOUT, MF_BYCOMMAND|MF_POPUP|MF_STRING, HandleToUlong(hAbout), "&About");
-			
-			SetWindowText(hDlg, WIN32_VERSION);
-			SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, 
-				(LPARAM)(HICON)LoadImage(hInst, MAKEINTRESOURCE(ICO_MAIN), IMAGE_ICON,16, 16, 0));
-			SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, 
-				(LPARAM)(HICON)LoadImage(hInst, MAKEINTRESOURCE(ICO_MAIN), IMAGE_ICON,32, 32, 0));
-			return TRUE;
-		}
-		case WM_CTLCOLORDLG:
-			return (LONG)HandleToLong(MainDlgBackground);
-		case WM_SIZE: 
-		{
-			if (wParam & SIZE_MINIMIZED)
-				ShowWindow(hDlg,SW_HIDE);
-			return 0;
-		}
-		case WM_CLOSE: 
-			return DoCloseUnreal(hDlg);
-		case WM_USER: 
-		{
-			switch(LOWORD(lParam)) 
-			{
-				case WM_LBUTTONDBLCLK:
-					ShowWindow(hDlg, SW_SHOW);
-					ShowWindow(hDlg,SW_RESTORE);
-					SetForegroundWindow(hDlg);
-				case WM_RBUTTONDOWN:
-					SetForegroundWindow(hDlg);
-					break;
-				case WM_RBUTTONUP: 
-				{
-					unsigned long i = 60000;
-					MENUITEMINFO mii;
-					GetCursorPos(&p);
-					DestroyMenu(hConfig);
-					hConfig = CreatePopupMenu();
-					DestroyMenu(hLogs);
-					hLogs = CreatePopupMenu();
-					AppendMenu(hConfig, MF_STRING, IDM_CONF, CPATH);
-#if 0
-					if (conf_log) 
-					{
-						ConfigItem_log *logs;
-						AppendMenu(hConfig, MF_POPUP|MF_STRING, HandleToUlong(hLogs), "Logs");
-						for (logs = conf_log; logs; logs = logs->next)
-						{
-							AppendMenu(hLogs, MF_STRING, i++, logs->file);
-						}
-					}
-					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-#endif
-					if (conf_files)
-					{
-						AppendMenu(hConfig, MF_STRING, IDM_MOTD, conf_files->motd_file);
-						AppendMenu(hConfig, MF_STRING, IDM_SMOTD, conf_files->smotd_file);
-						AppendMenu(hConfig, MF_STRING, IDM_OPERMOTD, conf_files->opermotd_file);
-						AppendMenu(hConfig, MF_STRING, IDM_BOTMOTD, conf_files->botmotd_file);
-						AppendMenu(hConfig, MF_STRING, IDM_RULES, conf_files->rules_file);
-					}
-						
-					if (conf_tld) 
-					{
-						ConfigItem_tld *tlds;
-						AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-						for (tlds = conf_tld; tlds; tlds = tlds->next)
-						{
-							if (!tlds->flag.motdptr)
-								AppendMenu(hConfig, MF_STRING, i++, tlds->motd_file);
-							if (!tlds->flag.ruleclient)
-								AppendMenu(hConfig, MF_STRING, i++, tlds->rules_file);
-							if (tlds->smotd_file)
-								AppendMenu(hConfig, MF_STRING, i++, tlds->smotd_file);
-						}
-					}
-					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-					AppendMenu(hConfig, MF_STRING, IDM_NEW, "New File");
-					mii.cbSize = sizeof(MENUITEMINFO);
-					mii.fMask = MIIM_SUBMENU;
-					mii.hSubMenu = hConfig;
-					SetMenuItemInfo(hTray, IDM_CONFIG, MF_BYCOMMAND, &mii);
-					TrackPopupMenu(hTray, TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
-					/* Kludge for a win bug */
-					SendMessage(hDlg, WM_NULL, 0, 0);
-					break;
-				}
-			}
-			return 0;
-		}
-		case WM_DESTROY:
-			return 0;
-		case WM_MOUSEMOVE: 
-		{
-			POINT p;
-			p.x = LOWORD(lParam);
-			p.y = HIWORD(lParam);
-			if ((p.x >= 93) && (p.x <= 150) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				SetCursor(hCursor);
-			else if ((p.x >= 160) && (p.x <= 208) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				SetCursor(hCursor);
-			else if ((p.x >= 219) && (p.x <= 267) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				SetCursor(hCursor);
-			else if ((p.x >= 279) && (p.x <= 325) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				SetCursor(hCursor);
-			else if ((p.x >= 336) && (p.x <= 411) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				SetCursor(hCursor);
-			return 0;
-		}
-		case WM_LBUTTONDOWN: 
-		{
-			POINT p;
-			p.x = LOWORD(lParam);
-			p.y = HIWORD(lParam);
-			if ((p.x >= 93) && (p.x <= 150) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP))
-			{
-				ClientToScreen(hDlg,&p);
-				TrackPopupMenu(hRehash,TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
-				return 0;
-			}
-			else if ((p.x >= 160) && (p.x <= 208) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP))
-			{
-				ShowDialog(&hStatusWnd, hInst, "Status", hDlg, StatusDLG);
-				return 0;
-			}
-			else if ((p.x >= 219) && (p.x <= 267) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP))
-			{
-				unsigned long i = 60000;
-				ClientToScreen(hDlg,&p);
-				DestroyMenu(hConfig);
-				hConfig = CreatePopupMenu();
-				DestroyMenu(hLogs);
-				hLogs = CreatePopupMenu();
-
-				AppendMenu(hConfig, MF_STRING, IDM_CONF, CPATH);
-#if 0
-				if (conf_log) 
-				{
-					ConfigItem_log *logs;
-					AppendMenu(hConfig, MF_POPUP|MF_STRING, HandleToUlong(hLogs), "Logs");
-					for (logs = conf_log; logs; logs = logs->next)
-					{
-						AppendMenu(hLogs, MF_STRING, i++, logs->file);
-					}
-				}
-				AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-#endif
-				if (conf_files)
-				{
-					AppendMenu(hConfig, MF_STRING, IDM_MOTD, conf_files->motd_file);
-					AppendMenu(hConfig, MF_STRING, IDM_SMOTD, conf_files->smotd_file);
-					AppendMenu(hConfig, MF_STRING, IDM_OPERMOTD, conf_files->opermotd_file);
-					AppendMenu(hConfig, MF_STRING, IDM_BOTMOTD, conf_files->botmotd_file);
-					AppendMenu(hConfig, MF_STRING, IDM_RULES, conf_files->rules_file);
-				}
-				
-				if (conf_tld) 
-				{
-					ConfigItem_tld *tlds;
-					AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-					for (tlds = conf_tld; tlds; tlds = tlds->next)
-					{
-						if (!tlds->flag.motdptr)
-							AppendMenu(hConfig, MF_STRING, i++, tlds->motd_file);
-						if (!tlds->flag.ruleclient)
-							AppendMenu(hConfig, MF_STRING, i++, tlds->rules_file);
-						if (tlds->smotd_file)
-							AppendMenu(hConfig, MF_STRING, i++, tlds->smotd_file);
-					}
-				}
-				AppendMenu(hConfig, MF_SEPARATOR, 0, NULL);
-				AppendMenu(hConfig, MF_STRING, IDM_NEW, "New File");
-				TrackPopupMenu(hConfig,TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
-
-				return 0;
-			}
-			else if ((p.x >= 279) && (p.x <= 325) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-			{
-				ClientToScreen(hDlg,&p);
-				TrackPopupMenu(hAbout,TPM_LEFTALIGN|TPM_LEFTBUTTON,p.x,p.y,0,hDlg,NULL);
-				return 0;
-			}
-			else if ((p.x >= 336) && (p.x <= 411) && (p.y >= TOOLBAR_START) && (p.y <= TOOLBAR_STOP)) 
-				return AskCloseUnreal(hDlg);
-		}
-		case WM_SYSCOMMAND:
-		{
-			if (wParam == SC_CLOSE)
-			{
-				AskCloseUnreal(hDlg);
-				return 1;
-			}
-			break;
-		}
-		case WM_COMMAND: 
-		{
-			if (LOWORD(wParam) >= 60000 && HIWORD(wParam) == 0 && !lParam) 
-			{
-				unsigned char path[MAX_PATH];
-				if (GetMenuString(hLogs, LOWORD(wParam), path, MAX_PATH, MF_BYCOMMAND))
-					DialogBoxParam(hInst, "FromVar", hDlg, (DLGPROC)FromFileReadDLG, (LPARAM)path);
-				
-				else 
-				{
-					GetMenuString(hConfig,LOWORD(wParam), path, MAX_PATH, MF_BYCOMMAND);
-					if (!url_is_valid(path))
-						DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, (LPARAM)path);
-				}
-				return FALSE;
-			}
-
-			if (!loop.booted)
-			{
-				MessageBox(NULL, "UnrealIRCd not booted due to configuration errors. "
-				                 "Check other window for error details. Then close that window, "
-				                 "fix the errors and start UnrealIRCd again.",
-				                 "UnrealIRCd not started",
-				                 MB_OK);
-				return FALSE;
-			}
-			switch(LOWORD(wParam)) 
-			{
-				case IDM_STATUS:
-					ShowDialog(&hStatusWnd, hInst, "Status", hDlg,StatusDLG);
-					break;
-				case IDM_SHUTDOWN:
-					return AskCloseUnreal(hDlg);
-				case IDM_RHALL:
-					MessageBox(NULL, "Rehashing all files", "Rehashing", MB_OK);
-					dorehash = 1;
-					break;
-				case IDM_LICENSE: 
-					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)LicenseDLG);
-					break;
-				case IDM_INFO:
-					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)InfoDLG);
-					break;
-				case IDM_CREDITS:
-					DialogBox(hInst, "FromVar", hDlg, (DLGPROC)CreditsDLG);
-					break;
-				case IDM_HELP:
-					DialogBox(hInst, "Help", hDlg, (DLGPROC)HelpDLG);
-					break;
-				case IDM_CONF:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, 
-						(LPARAM)CPATH);
-					break;
-				case IDM_MOTD:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, 
-						(LPARAM)conf_files->motd_file);
-					break;
-				case IDM_SMOTD:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, 
-						(LPARAM)conf_files->smotd_file);
-					break;
-				case IDM_OPERMOTD:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG,
-						(LPARAM)conf_files->opermotd_file);
-					break;
-				case IDM_BOTMOTD:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG,
-						(LPARAM)conf_files->botmotd_file);
-					break;
-				case IDM_RULES:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG,
-						(LPARAM)conf_files->rules_file);
-					break;
-				case IDM_NEW:
-					DialogBoxParam(hInst, "FromFile", hDlg, (DLGPROC)FromFileDLG, (LPARAM)NULL);
-					break;
-			}
-		}
-	}
-	return FALSE;
-}
-
-LRESULT CALLBACK LicenseDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	return FromVarDLG(hDlg, message, wParam, lParam, "UnrealIRCd License", gnulicense);
-}
-
-LRESULT CALLBACK InfoDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	return FromVarDLG(hDlg, message, wParam, lParam, "UnrealIRCd Team", unrealinfo);
-}
-
-LRESULT CALLBACK CreditsDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	return FromVarDLG(hDlg, message, wParam, lParam, "UnrealIRCd Credits", unrealcredits);
-}
-
-LRESULT CALLBACK FromVarDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam,
-                            unsigned char *title, unsigned char **s) 
-{
-	HWND hWnd;
-	switch (message) 
-	{
-		case WM_INITDIALOG: 
-		{
-#if 0
-			unsigned char	String[16384];
-			int size;
-			unsigned char *RTFString;
-			StreamIO *stream = safe_alloc(sizeof(StreamIO));
-			EDITSTREAM edit;
-			SetWindowText(hDlg, title);
-			memset(String, 0, sizeof(String));
-			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(GetDlgItem(hDlg, IDC_TEXT), GWLP_WNDPROC, (LONG_PTR)RESubClassFunc);
-			while (*s) 
-			{
-				strcat(String, *s++);
-				if (*s)
-					strcat(String, "\r\n");
-			}
-			size = CountRTFSize(String)+1;
-			RTFString = safe_alloc(size);
-			IRCToRTF(String,RTFString);
-			RTFBuf = RTFString;
-			size--;
-			stream->size = &size;
-			stream->buffer = &RTFBuf;
-			edit.dwCookie = HandleToUlong(stream);
-			edit.pfnCallback = SplitIt;
-			SendMessage(GetDlgItem(hDlg, IDC_TEXT), EM_STREAMIN, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
-			safe_free(RTFString);
-			safe_free(stream);
-			return TRUE;
-#else
-			return FALSE;
-#endif
-		}
-
-		case WM_COMMAND: 
-		{
-			hWnd = GetDlgItem(hDlg, IDC_TEXT);
-			if (LOWORD(wParam) == IDOK)
-				return EndDialog(hDlg, TRUE);
-			if (LOWORD(wParam) == IDM_COPY) 
-			{
-				SendMessage(hWnd, WM_COPY, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_SELECTALL) 
-			{
-				SendMessage(hWnd, EM_SETSEL, 0, -1);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_PASTE) 
-			{
-				SendMessage(hWnd, WM_PASTE, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_CUT) 
-			{
-				SendMessage(hWnd, WM_CUT, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_UNDO) 
-			{
-				SendMessage(hWnd, EM_UNDO, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_DELETE) 
-			{
-				SendMessage(hWnd, WM_CLEAR, 0, 0);
-				return 0;
-			}
-			break;
-		}
-		case WM_CLOSE:
-			EndDialog(hDlg, TRUE);
-			break;
-		case WM_DESTROY:
-			break;
-	}
-	return (FALSE);
-}
-
-LRESULT CALLBACK FromFileReadDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	HWND hWnd;
-	switch (message) 
-	{
-		case WM_INITDIALOG: 
-		{
-			int fd,len;
-			unsigned char *buffer = '\0', *string = '\0';
-			EDITSTREAM edit;
-			StreamIO *stream = safe_alloc(sizeof(StreamIO));
-			unsigned char szText[256];
-			struct stat sb;
-			HWND hWnd = GetDlgItem(hDlg, IDC_TEXT), hTip;
-			StringCbPrintf(szText, sizeof(szText), "UnrealIRCd Viewer - %s", (unsigned char *)lParam);
-			SetWindowText(hDlg, szText);
-			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)RESubClassFunc);
-			if ((fd = open((unsigned char *)lParam, _O_RDONLY|_O_BINARY)) != -1) 
-			{
-				fstat(fd,&sb);
-				/* Only allocate the amount we need */
-				buffer = safe_alloc(sb.st_size+1);
-				buffer[0] = 0;
-				len = read(fd, buffer, sb.st_size); 
-				buffer[len] = 0;
-				len = CountRTFSize(buffer)+1;
-				string = safe_alloc(len);
-				IRCToRTF(buffer,string);
-				RTFBuf = string;
-				len--;
-				stream->size = &len;
-				stream->buffer = &RTFBuf;
-				edit.dwCookie = (DWORD_PTR)stream;
-				edit.pfnCallback = SplitIt;
-				SendMessage(hWnd, EM_EXLIMITTEXT, 0, (LPARAM)0x7FFFFFFF);
-				SendMessage(hWnd, EM_STREAMIN, (WPARAM)SF_RTF|SFF_PLAINRTF, (LPARAM)&edit);
-				close(fd);
-				RTFBuf = NULL;
-				safe_free(buffer);
-				safe_free(string);
-				safe_free(stream);
-			}
-			return TRUE;
-		}
-		case WM_COMMAND: 
-		{
-			hWnd = GetDlgItem(hDlg, IDC_TEXT);
-			if (LOWORD(wParam) == IDOK)
-				return EndDialog(hDlg, TRUE);
-			if (LOWORD(wParam) == IDM_COPY) 
-			{
-				SendMessage(hWnd, WM_COPY, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_SELECTALL) 
-			{
-				SendMessage(hWnd, EM_SETSEL, 0, -1);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_PASTE) 
-			{
-				SendMessage(hWnd, WM_PASTE, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_CUT) 
-			{
-				SendMessage(hWnd, WM_CUT, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_UNDO) 
-			{
-				SendMessage(hWnd, EM_UNDO, 0, 0);
-				return 0;
-			}
-			if (LOWORD(wParam) == IDM_DELETE) 
-			{
-				SendMessage(hWnd, WM_CLEAR, 0, 0);
-				return 0;
-			}
-			break;
-		}
-		case WM_CLOSE:
-			EndDialog(hDlg, TRUE);
-			break;
-		case WM_DESTROY:
-			break;
-	}
-	return FALSE;
-}
-
-LRESULT CALLBACK HelpDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	static HFONT hFont;
-	static HCURSOR hCursor;
-	switch (message) 
-	{
-		case WM_INITDIALOG:
-			hCursor = LoadCursor(hInst, MAKEINTRESOURCE(CUR_HAND));
-			hFont = CreateFont(8,0,0,0,0,0,1,0,ANSI_CHARSET,0,0,PROOF_QUALITY,0,"MS Sans Serif");
-			SendMessage(GetDlgItem(hDlg, IDC_EMAIL), WM_SETFONT, (WPARAM)hFont,TRUE);
-			SendMessage(GetDlgItem(hDlg, IDC_URL), WM_SETFONT, (WPARAM)hFont,TRUE);
-			lpfnOldWndProc = (FARPROC)SetWindowLongPtr(GetDlgItem(hDlg, IDC_EMAIL), GWLP_WNDPROC, (LONG_PTR)LinkSubClassFunc);
-			SetWindowLongPtr(GetDlgItem(hDlg, IDC_URL), GWLP_WNDPROC, (LONG_PTR)LinkSubClassFunc);
-			return TRUE;
-
-		case WM_DRAWITEM: 
-		{
-			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
-			unsigned char text[500];
-			COLORREF oldtext;
-			RECT focus;
-			GetWindowText(lpdis->hwndItem, text, 500);
-			if (wParam == IDC_URL || IDC_EMAIL) 
-			{
-				FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(COLOR_3DFACE));
-				oldtext = SetTextColor(lpdis->hDC, RGB(0,0,255));
-				DrawText(lpdis->hDC, text, strlen(text), &lpdis->rcItem, DT_CENTER|DT_VCENTER);
-				SetTextColor(lpdis->hDC, oldtext);
-				if (lpdis->itemState & ODS_FOCUS) 
-				{
-					CopyRect(&focus, &lpdis->rcItem);
-					focus.left += 2;
-					focus.right -= 2;
-					focus.top += 1;
-					focus.bottom -= 1;
-					DrawFocusRect(lpdis->hDC, &focus);
-				}
-				return TRUE;
-			}
-		}	
-		case WM_COMMAND:
-			if (LOWORD(wParam) == IDOK)
-				EndDialog(hDlg, TRUE);
-			if (HIWORD(wParam) == BN_DBLCLK) 
-			{
-				if (LOWORD(wParam) == IDC_URL) 
-					ShellExecute(NULL, "open", "https://www.unrealircd.org", NULL, NULL, 
-						SW_MAXIMIZE);
-				else if (LOWORD(wParam) == IDC_EMAIL)
-					ShellExecute(NULL, "open", "mailto:unreal-users@lists.sourceforge.net", NULL, NULL, 
-						SW_MAXIMIZE);
-				EndDialog(hDlg, TRUE);
-				return 0;
-			}
-			break;
-		case WM_CLOSE:
-			EndDialog(hDlg, TRUE);
-			break;
-		case WM_DESTROY:
-			DeleteObject(hFont);
-			break;
-
-	}
-	return FALSE;
-}
-
-
-
-
-
-
-
-
-LRESULT CALLBACK StatusDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	switch (message) 
-	{
-		case WM_INITDIALOG: 
-		{
-			hwTreeView = GetDlgItem(hDlg, IDC_TREE);
-			win_map(&me, hwTreeView, 0);
-			SetDlgItemInt(hDlg, IDC_CLIENTS, irccounts.clients, FALSE);
-			SetDlgItemInt(hDlg, IDC_SERVERS, irccounts.servers, FALSE);
-			SetDlgItemInt(hDlg, IDC_INVISO, irccounts.invisible, FALSE);
-			SetDlgItemInt(hDlg, IDC_UNKNOWN, irccounts.unknown, FALSE);
-			SetDlgItemInt(hDlg, IDC_OPERS, irccounts.operators, FALSE);
-			SetDlgItemInt(hDlg, IDC_CHANNELS, irccounts.channels, FALSE);
-			if (irccounts.clients > irccounts.global_max)
-				irccounts.global_max = irccounts.clients;
-			if (irccounts.me_clients > irccounts.me_max)
-					irccounts.me_max = irccounts.me_clients;
-			SetDlgItemInt(hDlg, IDC_MAXCLIENTS, irccounts.global_max, FALSE);
-			SetDlgItemInt(hDlg, IDC_LCLIENTS, irccounts.me_clients, FALSE);
-			SetDlgItemInt(hDlg, IDC_LSERVERS, irccounts.me_servers, FALSE);
-			SetDlgItemInt(hDlg, IDC_LMAXCLIENTS, irccounts.me_max, FALSE);
-			SetTimer(hDlg, 1, 5000, NULL);
-			return TRUE;
-		}
-		case WM_CLOSE:
-			DestroyWindow(hDlg);
-			return TRUE;
-		case WM_TIMER:
-			TreeView_DeleteAllItems(hwTreeView);
-			win_map(&me, hwTreeView, 1);
-			SetDlgItemInt(hDlg, IDC_CLIENTS, irccounts.clients, FALSE);
-			SetDlgItemInt(hDlg, IDC_SERVERS, irccounts.servers, FALSE);
-			SetDlgItemInt(hDlg, IDC_INVISO, irccounts.invisible, FALSE);
-			SetDlgItemInt(hDlg, IDC_INVISO, irccounts.invisible, FALSE);
-			SetDlgItemInt(hDlg, IDC_UNKNOWN, irccounts.unknown, FALSE);
-			SetDlgItemInt(hDlg, IDC_OPERS, irccounts.operators, FALSE);
-			SetDlgItemInt(hDlg, IDC_CHANNELS, irccounts.channels, FALSE);
-			if (irccounts.clients > irccounts.global_max)
-				irccounts.global_max = irccounts.clients;
-			if (irccounts.me_clients > irccounts.me_max)
-					irccounts.me_max = irccounts.me_clients;
-			SetDlgItemInt(hDlg, IDC_MAXCLIENTS, irccounts.global_max, FALSE);
-			SetDlgItemInt(hDlg, IDC_LCLIENTS, irccounts.me_clients, FALSE);
-			SetDlgItemInt(hDlg, IDC_LSERVERS, irccounts.me_servers, FALSE);
-			SetDlgItemInt(hDlg, IDC_LMAXCLIENTS, irccounts.me_max, FALSE);
-			SetTimer(hDlg, 1, 5000, NULL);
-			return TRUE;
-		case WM_COMMAND:
-			if (LOWORD(wParam) == IDOK) 
-			{
-				DestroyWindow(hDlg);
-				return TRUE;
-			}
-			break;
-
-	}
-	return FALSE;
-}
-
-/* This was made by DrBin but I cleaned it up a bunch to make it work better */
-
-HTREEITEM AddItemToTree(HWND hWnd, LPSTR lpszItem, int nLevel, short remap)
-{
-	TVITEM tvi; 
-	TVINSERTSTRUCT tvins; 
-	static HTREEITEM hPrev = (HTREEITEM)TVI_FIRST; 
-	static HTREEITEM hPrevLev[10] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
-	HTREEITEM hti; 
-
-	if (remap) 
-	{
-		hPrev = (HTREEITEM)TVI_FIRST;
-		memset(hPrevLev, 0, sizeof(HTREEITEM)*10);
-	}
-		
-	tvi.mask = TVIF_TEXT|TVIF_PARAM; 
-	tvi.pszText = lpszItem; 
-	tvi.cchTextMax = lstrlen(lpszItem); 
-	tvi.lParam = (LPARAM)nLevel; 
-	tvins.item = tvi; 
-	tvins.hInsertAfter = hPrev; 
-	if (nLevel == 1) 
-		tvins.hParent = TVI_ROOT; 
-	else 
-		tvins.hParent = hPrevLev[nLevel-1];
-	hPrev = (HTREEITEM)SendMessage(hWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT) &tvins); 
-	hPrevLev[nLevel] = hPrev;
-	TreeView_EnsureVisible(hWnd,hPrev);
-	if (nLevel > 1) 
-	{ 
-		hti = TreeView_GetParent(hWnd, hPrev); 
-		tvi.mask = TVIF_IMAGE|TVIF_SELECTEDIMAGE; 
-		tvi.hItem = hti; 
-		TreeView_SetItem(hWnd, &tvi); 
-	} 
-	return hPrev; 
-}
-
-/*
- * Now used to create list of servers for server list tree view -- David Flynn
- * Recoded by codemastr to be faster.
- * I removed the Potvin credit because it no longer uses any original code and I don't
- * even think Potvin actually made the original code
- */
-void win_map(Client *server, HWND hwTreeView, short remap)
-{
-/*
-	Client *acptr;
-	Link *lp;
-
-	AddItemToTree(hwTreeView,server->name,server->hopcount+1, remap);
-
-	for (lp = Servers; lp; lp = lp->next)
-        {
-                acptr = lp->value.client;
-                if (acptr->uplink != server)
-                        continue;
-                win_map(acptr, hwTreeView, 0);
-        }
-FIXME
-*/
-}
-
-/* ugly stuff, but hey it works -- codemastr */
-void win_log(FORMAT_STRING(const char *format), ...)
-{
-	va_list ap;
-	unsigned char buf[2048];
-	FILE *fd;
-
-	va_start(ap, format);
-
-	ircvsnprintf(buf, sizeof(buf), format, ap);
-	stripcrlf(buf);
-
-	if (!IsService) 
-	{
-		strcat(buf, "\r\n");
-		if (errors) 
-		{
-			char *tbuf = safe_alloc(strlen(errors) + strlen(buf) + 1);
-			strcpy(tbuf, errors);
-			strcat(tbuf, buf);
-			safe_free(errors);
-			errors = tbuf;
-		}
-		else 
-		{
-			safe_strdup(errors, buf);
-		}
-	}
-	else 
-	{
-		FILE *fd = fopen("logs\\service.log", "a");
-		if (fd)
-		{
-			char timebuf[256];
-			snprintf(timebuf, sizeof(timebuf), "[%s]", myctime(time(NULL)));
-			fprintf(fd, "%s - %s\n", timebuf, buf);
-			fclose(fd);
-		}
-#ifdef _DEBUG
-		else
-		{
-			OutputDebugString(buf);
-		}
-#endif
-	}
-	va_end(ap);
-}
-
-void win_error() 
-{
-	if (errors && !IsService)
-		DialogBox(hInst, "ConfigError", hwIRCDWnd, (DLGPROC)ConfigErrorDLG);
-}
-
-LRESULT CALLBACK ConfigErrorDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 
-{
-	switch (message) 
-	{
-		case WM_INITDIALOG:
-			MessageBeep(MB_ICONEXCLAMATION);
-			SetDlgItemText(hDlg, IDC_CONFIGERROR, errors);
-			safe_free(errors);
-			errors = NULL;
-			return (TRUE);
-		case WM_COMMAND:
-			if (LOWORD(wParam) == IDOK)
-				EndDialog(hDlg, TRUE);
-			break;
-		case WM_CLOSE:
-			EndDialog(hDlg, TRUE);
-			break;
-		case WM_DESTROY:
-			break;
-
-		}
-	return (FALSE);
-}
diff --git a/src/windows/hand.CUR b/src/windows/hand.CUR
Binary files differ.
diff --git a/src/windows/makecert.bat b/src/windows/makecert.bat
@@ -1,6 +0,0 @@
-@title Certificate Generation
-SET OPENSSL_CONF=tls.cnf
-openssl ecparam -out ../conf/tls/server.key.pem -name secp384r1 -genkey
-openssl req -new -config tls.cnf -out ../conf/tls/server.req.pem -key ../conf/tls/server.key.pem -nodes
-openssl req -x509 -config tls.cnf -days 3650 -sha256 -in ../conf/tls/server.req.pem -key ../conf/tls/server.key.pem -out ../conf/tls/server.cert.pem
-
diff --git a/src/windows/resource.h b/src/windows/resource.h
@@ -1,108 +0,0 @@
-//{{NO_DEPENDENCIES}}
-// Microsoft Developer Studio generated include file.
-// Used by Win32GUI.rc
-//
-#define VER_UNREAL                      1
-#define MANIFEST_RESOURCE_ID            2
-#define ICO_MAIN                        129
-#define BMP_LOGO                        130
-#define BMP_BAR                         133
-#define CUR_HAND                        136
-#define MENU_ABOUT                      137
-#define MENU_CONFIG                     141
-#define MENU_REHASH                     144
-#define MENU_SYSTRAY                    145
-#define MENU_CONTEXT                    146
-#define IDB_BITMAP1                     150
-#define IDC_BAR                         1103
-#define IDC_TOOLBAR                     1104
-#define IDC_STATUS                      1105
-#define IDC_TEXT                        1107
-#define IDC_EMAIL                       1108
-#define IDC_URL                         1109
-#define IDC_TREE                        1111
-#define IDC_CHANNELS                    1112
-#define IDC_CLIENTS                     1113
-#define IDC_SERVERS                     1114
-#define IDC_INVISO                      1115
-#define IDC_OPERS                       1116
-#define IDC_UNKNOWN                     1117
-#define IDC_MAXCLIENTS                  1118
-#define IDC_LCLIENTS                    1122
-#define IDC_LSERVERS                    1123
-#define IDC_LMAXCLIENTS                 1124
-#define IDC_UPTIME                      1125
-#define IDC_CONFIGERROR                 1126
-#define IDC_BOLD                        1130
-#define IDC_UNDERLINE                   1131
-#define IDC_FIND                        1132
-#define IDFIND                          1133
-#define IDC_FINDTEXT                    1135
-#define IDC_GOTO                        1135
-#define IDC_MATCHWHOLE                  1137
-#define IDC_MATCHCASE                   1138
-#define IDC_DIRUP                       1139
-#define IDC_DIRDOWN                     1140
-#define IDC_COLOR                       1141
-#define IDC_BGCOLOR			1142
-#define IDC_WHITE                       1163
-#define IDC_BLACK                       1164
-#define IDC_DARKBLUE                    1165
-#define IDC_DARKGREEN                   1166
-#define IDC_PASS                        1166
-#define IDC_RED                         1167
-#define IDC_DARKRED                     1168
-#define IDC_PURPLE                      1169
-#define IDC_ORANGE                      1170
-#define IDC_YELLOW                      1171
-#define IDC_GREEN                       1172
-#define IDC_VDARKGREEN                  1173
-#define IDC_LIGHTBLUE                   1174
-#define IDC_BLUE                        1175
-#define IDC_PINK                        1176
-#define IDC_DARKGRAY                    1177
-#define IDC_GRAY                        1178
-#define IDM_INFO                        40026
-#define IDM_CREDITS                     40027
-#define IDM_DAL                         40028
-#define IDM_LICENSE                     40029
-#define IDM_HELP                        40030
-#define IDM_SAVE                        40031
-#define IDM_REDO                        40032
-#define IDM_SMOTD			40036
-#define IDM_CONF                        40037
-#define IDM_MOTD                        40038
-#define IDM_BOTMOTD                     40039
-#define IDM_OPERMOTD                    40040
-#define IDM_RULES                       40041
-#define IDM_RHALL                       40042
-#define IDM_RHCONF                      40044
-#define IDM_RHMOTD                      40045
-#define IDM_RHOMOTD                     40046
-#define IDM_RHBMOTD                     40047
-#define IDM_RHRULES                     40048
-#define IDM_REHASH                      40049
-#define IDM_STATUS                      40050
-#define IDM_CONFIG                      40051
-#define IDM_ABOUT                       40052
-#define IDM_SHUTDOWN                    40053
-#define IDM_UNDO                        40054
-#define IDM_CUT                         40055
-#define IDM_COPY                        40056
-#define IDM_PASTE                       40057
-#define IDM_DELETE                      40058
-#define IDM_SELECTALL                   40059
-#define IDM_NEW                         40060
-#define IDC_STATIC                      -1
-
-// Next default values for new objects
-// 
-#ifdef APSTUDIO_INVOKED
-#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NO_MFC                     1
-#define _APS_NEXT_RESOURCE_VALUE        152
-#define _APS_NEXT_COMMAND_VALUE         40061
-#define _APS_NEXT_CONTROL_VALUE         1167
-#define _APS_NEXT_SYMED_VALUE           104
-#endif
-#endif
diff --git a/src/windows/rtf.c b/src/windows/rtf.c
@@ -1,878 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/rtf.c
- *   Copyright (C) 2004 Dominick Meglio (codemastr)
- *   
- *   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 "win.h"
-
-unsigned char *RTFBuf;
-
-#define MIRC_COLORS "{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue127;\\red0\\green147\\blue0;\\red255\\green0\\blue0;\\red127\\green0\\blue0;\\red156\\green0\\blue156;\\red252\\green127\\blue0;\\red255\\green255\\blue0;\\red0\\green252\\blue0;\\red0\\green147\\blue147;\\red0\\green255\\blue255;\\red0\\green0\\blue252;\\red255\\green0\\blue255;\\red127\\green127\\blue127;\\red210\\green210\\blue210;\\red0\\green0\\blue0;}"
-
-/* Splits the file up for the EM_STREAMIN message
- * Parameters:
- *  dwCookie - The file information to split
- *  pbBuff   - The output buffer
- *  cb       - The size of pbBuff
- *  pcb      - The total bytes written to bpBuff
- * Returns:
- *  Returns 0 to indicate success
- */
-DWORD CALLBACK SplitIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
-{
-	StreamIO *stream = (StreamIO*)dwCookie;
-	if (*stream->size == 0)
-	{
-		pcb = 0;
-		*stream->buffer = 0;
-	}
-	else if (cb <= *stream->size) 
-	{
-		memcpy(pbBuff, *stream->buffer, cb);
-		*stream->buffer += cb;
-		*stream->size -= cb;
-		*pcb = cb;
-
-	}
-	else 
-	{
-		memcpy(pbBuff, *stream->buffer, *stream->size);
-		*pcb = *stream->size;
-		*stream->size = 0;
-	}
-	return 0;
-}
-
-/* Reassembles the RTF buffer from EM_STREAMOUT
- * Parameters:
- *  dwCookie - Unused
- *  pbBuff   - The input buffer
- *  cb       - The length of the input buffer
- *  pcb      - The total bytes read from pbBuff
- * Returns:
- *  0 to indicate success
- * Side Effects:
- *  RTFBuf contains the assembled RTF buffer
- */
-DWORD CALLBACK BufferIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
-{
-	unsigned char *buf2;
-	static long size = 0;
-	if (!RTFBuf)
-		size = 0;
-
-	buf2 = safe_alloc(size+cb+1);
-
-	if (RTFBuf)
-		memcpy(buf2,RTFBuf,size);
-
-	memcpy(buf2+size,pbBuff,cb);
-
-	size += cb;
-	safe_free(RTFBuf);
-
-	RTFBuf = buf2;
-
-	pcb = &cb;
-	return 0;
-}
-
-/* Pushes a color onto the stack
- * Parameters:
- *  color - The color to add to the stack
- *  stack - The stack to add the color to
- */
-void ColorPush(unsigned char *color, IRCColor **stack)
-{
-	IRCColor *t = safe_alloc(sizeof(IRCColor));
-	safe_strdup(t->color, color);
-	t->next = *stack;
-	(*stack) = t;
-}
-
-/* Pops a color off of the stack
- * Parameters:
- *  stack - The stack to pop from
- */
-void ColorPop(IRCColor **stack)
-{
-	IRCColor *p = *stack;
-	if (!(*stack))
-		return;
-	safe_free(p->color);
-	
-	*stack = p->next;
-	safe_free(p);
-}
-
-/* Completely empties the color stack
- * Parameters:
- *  stack - The stack to empty
- */
-void ColorEmpty(IRCColor **stack)
-{
-	IRCColor *t, *next;
-	for (t = *stack; t; t = next)
-	{
-		next = t->next;
-		safe_free(t->color);
-		safe_free(t);
-	}
-}
-
-#define iseol(x) ((x) == '\r' || (x) == '\n')
-
-/* Converts a string in RTF format to IRC codes
- * Parameters:
- *  fd     - The file descriptor to write to
- *  pbBuff - The buffer containing the RTF text
- *  cb     - The length of the RTF text
- */
-DWORD CALLBACK RTFToIRC(int fd, unsigned char *pbBuff, long cb) 
-{
-	unsigned char *buffer = safe_alloc(cb*2+2);
-	int colors[17], bold = 0, uline = 0, incolor = 0, inbg = 0;
-	int lastwascf = 0, lastwascf0 = 0;
-	int i = 0;
-
-	IRCColor *TextColors = NULL;
-	IRCColor *BgColors = NULL;
-	
-	memset(buffer, 0, cb);
-
-	for (; *pbBuff; pbBuff++)
-	{
-		if (iseol(*pbBuff) || *pbBuff == '{' || *pbBuff == '}')
-			continue;
-		else if (*pbBuff == '\\')
-		{
-			/* RTF control sequence */
-			pbBuff++;
-			if (*pbBuff == '\\' || *pbBuff == '{' || *pbBuff == '}')
-				buffer[i++] = *pbBuff;
-			else if (*pbBuff == '\'')
-			{
-				/* Extended ASCII character */
-				unsigned char ltr, ultr[3];
-				ultr[0] = *(++pbBuff);
-				ultr[1] = *(++pbBuff);
-				ultr[2] = 0;
-				ltr = strtoul(ultr,NULL,16);
-				buffer[i++] = ltr;
-			}
-			else
-			{
-				int j;
-				char cmd[128];
-				/* Capture the control sequence */
-				for (j = 0; *pbBuff && *pbBuff != '\\' && !isspace(*pbBuff) &&
-					!iseol(*pbBuff); pbBuff++)
-				{
-					cmd[j++] = *pbBuff;
-				}
-				if (*pbBuff != ' ')
-					pbBuff--;
-				cmd[j] = 0;
-				if (!strcmp(cmd, "fonttbl{"))
-				{
-					/* Eat the parameter */
-					while (*pbBuff && *pbBuff != '}')
-						pbBuff++;
-					lastwascf = lastwascf0 = 0;
-				}
-				if (!strcmp(cmd, "colortbl"))
-				{
-					char color[128];
-					int k = 0, m = 1;
-					/* Capture the color table */
-					while (*pbBuff && !isalnum(*pbBuff))
-						pbBuff++;
-					for (; *pbBuff && *pbBuff != '}'; pbBuff++)
-					{
-						if (*pbBuff == ';')
-						{
-							color[k]=0;
-							if (!strcmp(color, "\\red255\\green255\\blue255"))
-								colors[m++] = 0;
-							else if (!strcmp(color, "\\red0\\green0\\blue0"))
-								colors[m++] = 1;
-							else if (!strcmp(color, "\\red0\\green0\\blue127"))
-								colors[m++] = 2;
-							else if (!strcmp(color, "\\red0\\green147\\blue0"))
-								colors[m++] = 3;
-							else if (!strcmp(color, "\\red255\\green0\\blue0"))
-								colors[m++] = 4;
-							else if (!strcmp(color, "\\red127\\green0\\blue0"))
-								colors[m++] = 5;
-							else if (!strcmp(color, "\\red156\\green0\\blue156"))
-								colors[m++] = 6;
-							else if (!strcmp(color, "\\red252\\green127\\blue0"))
-								colors[m++] = 7;
-							else if (!strcmp(color, "\\red255\\green255\\blue0"))
-								colors[m++] = 8;
-							else if (!strcmp(color, "\\red0\\green252\\blue0"))
-								colors[m++] = 9;
-							else if (!strcmp(color, "\\red0\\green147\\blue147"))
-								colors[m++] = 10;
-							else if (!strcmp(color, "\\red0\\green255\\blue255"))
-								colors[m++] = 11;
-							else if (!strcmp(color, "\\red0\\green0\\blue252"))
-								colors[m++] = 12;
-							else if (!strcmp(color, "\\red255\\green0\\blue255"))
-								colors[m++] = 13;
-							else if (!strcmp(color, "\\red127\\green127\\blue127"))
-								colors[m++] = 14;
-							else if (!strcmp(color, "\\red210\\green210\\blue210")) 
-								colors[m++] = 15;
-							k=0;
-						}
-						else
-							color[k++] = *pbBuff;
-					}
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strcmp(cmd, "tab"))
-				{
-					buffer[i++] = '\t';
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strcmp(cmd, "par"))
-				{
-					if (bold || uline || incolor || inbg)
-						buffer[i++] = '\17';
-					buffer[i++] = '\r';
-					buffer[i++] = '\n';
-					if (!*(pbBuff+3) || *(pbBuff+3) != '}')
-					{
-						if (bold)
-							buffer[i++] = '\2';
-						if (uline)
-							buffer[i++] = '\37';
-						if (incolor)
-						{
-							buffer[i++] = '\3';
-							strcat(buffer, TextColors->color);
-							i += strlen(TextColors->color);
-							if (inbg)
-							{
-								buffer[i++] = ',';
-								strcat(buffer, BgColors->color);
-								i += strlen(BgColors->color);
-							}
-						}
-						else if (inbg) 
-						{
-							buffer[i++] = '\3';
-							buffer[i++] = '0';
-							buffer[i++] = '1';
-							buffer[i++] = ',';
-							strcat(buffer, BgColors->color);
-							i += strlen(BgColors->color);
-						}
-					}
-				}
-				else if (!strcmp(cmd, "b"))
-				{
-					bold = 1;
-					buffer[i++] = '\2';
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strcmp(cmd, "b0"))
-				{
-					bold = 0;
-					buffer[i++] = '\2';
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strcmp(cmd, "ul"))
-				{
-					uline = 1;
-					buffer[i++] = '\37';
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strcmp(cmd, "ulnone"))
-				{
-					uline = 0;
-					buffer[i++] = '\37';
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strcmp(cmd, "cf0"))
-				{
-					lastwascf0 = 1;
-					lastwascf = 0;
-				}
-				else if (!strcmp(cmd, "highlight0"))
-				{
-					inbg = 0;
-					ColorPop(&BgColors);
-					buffer[i++] = '\3';
-					if (lastwascf0)
-					{
-						incolor = 0;
-						ColorPop(&TextColors);
-						lastwascf0 = 0;
-					}
-					else if (incolor)
-					{
-						strcat(buffer, TextColors->color);
-						i += strlen(TextColors->color);
-						buffer[i++] = ',';
-						buffer[i++] = '0';
-						buffer[i++] = '0';
-					}
-					lastwascf = lastwascf0 = 0;
-				}
-				else if (!strncmp(cmd, "cf", 2))
-				{
-					unsigned char number[3];
-					int num;
-					incolor = 1;
-					strcpy(number, &cmd[2]);
-					num = atoi(number);
-					buffer[i++] = '\3';
-					if (colors[num] < 10)
-						sprintf(number, "0%d", colors[num]);
-					else
-						sprintf(number, "%d", colors[num]);
-					ColorPush(number, &TextColors);
-					strcat(buffer,number);
-					i += strlen(number);
-					lastwascf = 1;
-					lastwascf0 = 0;
-				}
-				else if (!strncmp(cmd, "highlight", 9))
-				{
-					int num;
-					unsigned char number[3];
-					inbg = 1;
-					num = atoi(&cmd[9]);
-					if (colors[num] < 10)
-						sprintf(number, "0%d", colors[num]);
-					else
-						sprintf(number, "%d", colors[num]);
-					if (incolor && !lastwascf)
-					{
-						buffer[i++] = '\3';
-						strcat(buffer, TextColors->color);
-						i += strlen(TextColors->color);
-					}
-					else if (!incolor)
-					{
-						buffer[i++] = '\3';
-						buffer[i++] = '0';
-						buffer[i++] = '1';
-					}
-					buffer[i++] = ',';
-					strcat(buffer, number);
-					i += strlen(number);
-					ColorPush(number, &BgColors);
-					lastwascf = lastwascf0 = 0;
-				}
-				else
-					lastwascf = lastwascf0 = 0;
-
-				if (lastwascf0 && incolor)
-				{
-					incolor = 0;
-					ColorPop(&TextColors);
-					buffer[i++] = '\3';
-				}
-			}
-		}
-		else
-		{
-			lastwascf = lastwascf0 = 0;
-			buffer[i++] = *pbBuff;
-		}
-				
-	}
-	write(fd, buffer, i);
-	close(fd);
-	ColorEmpty(&TextColors);
-	ColorEmpty(&BgColors);
-	return 0;
-}
-
-/* Determines the size of the buffer needed to convert IRC codes to RTF
- * Parameters:
- *  buffer - The input buffer with IRC codes
- * Returns:
- *  The lenght of the buffer needed to store the RTF translation
- */
-int CountRTFSize(unsigned char *buffer) {
-	int size = 0;
-	char bold = 0, uline = 0, incolor = 0, inbg = 0, reverse = 0;
-	char *buf = buffer;
-
-	for (; *buf; buf++) 
-	{
-		if (*buf == '{' || *buf == '}' || *buf == '\\')
-			size++;
-		else if (*buf == '\r')
-		{
-			if (*(buf+1) && *(buf+1) == '\n')
-			{
-				buf++;
-				if (bold)
-					size += 3;
-				if (uline)
-					size += 7;
-				if (incolor && !reverse)
-					size += 4;
-				if (inbg && !reverse)
-					size += 11;
-				if (reverse)
-					size += 15;
-				if (bold || uline || incolor || inbg || reverse)
-					size++;
-				bold = uline = incolor = inbg = reverse = 0;
-				size +=6;
-				continue;
-			}
-		}
-		else if (*buf == '\n')
-		{
-			if (bold)
-				size += 3;
-			if (uline)
-				size += 7;
-			if (incolor && !reverse)
-				size += 4;
-			if (inbg && !reverse)
-				size += 11;
-			if (reverse)
-				size += 15;
-			if (bold || uline || incolor || inbg || reverse)
-				size++;
-			bold = uline = incolor = inbg = reverse = 0;
-			size +=6;
-			continue;	
-		}
-		else if (*buf == '\2')
-		{
-			if (bold)
-				size += 4;
-			else
-				size += 3;
-			bold = !bold;
-			continue;
-		}
-		else if (*buf == '\3' && reverse)
-		{
-			if (*(buf+1) && isdigit(*(buf+1)))
-			{
-				++buf;
-				if (*(buf+1) && isdigit(*(buf+1)))
-					++buf;
-				if (*(buf+1) && *(buf+1) == ',')
-				{
-					if (*(buf+2) && isdigit(*(buf+2)))
-					{
-						buf+=2;
-						if (*(buf+1) && isdigit(*(buf+1)))
-							++buf;
-					}
-				}
-			}
-			continue;
-		}
-		else if (*buf == '\3' && !reverse)
-		{
-			size += 3;
-			if (*(buf+1) && !isdigit(*(buf+1)))
-			{
-				incolor = 0;
-				size++;
-				if (inbg)
-				{
-					inbg = 0;
-					size += 11;
-				}
-			}
-			else if (*(buf+1))
-			{
-				unsigned char color[3];
-				int number;
-				color[0] = *(++buf);
-				color[1] = 0;
-				if (*(buf+1) && isdigit(*(buf+1)))
-					color[1] = *(++buf);
-				color[2] = 0;
-				number = atoi(color);
-				if (number == 99 || number == 1) 
-					size += 2;
-				else if (number == 0) 
-					size++;
-				else  {
-					number %= 16;
-					_itoa(number, color, 10);
-					size += strlen(color);
-				}
-				color[2] = 0;
-				number = atoi(color);
-				if (*(buf+1) && *(buf+1) == ',')
-				{
-					if (*(buf+2) && isdigit(*(buf+2)))
-					{
-						size += 10;
-						buf++;
-						color[0] = *(++buf);
-						color[1] = 0;
-						if (*(buf+1) && isdigit(*(buf+1)))
-							color[1] = *(++buf);
-						color[2] = 0;
-						number = atoi(color);
-						if (number == 1)
-							size += 2;
-						else if (number == 0 || number == 99)
-							size++;
-						else
-						{
-							number %= 16;
-							_itoa(number, color, 10);
-							size += strlen(color);
-						}
-						inbg = 1;
-					}
-				}
-				incolor = 1;
-			}
-			size++;
-			continue;
-		}
-		else if (*buf == '\17')
-		{
-			if (bold)
-				size += 3;
-			if (uline)
-				size += 7;
-			if (incolor && !reverse)
-				size += 4;
-			if (inbg && !reverse)
-				size += 11;
-			if (reverse)
-				size += 15;
-			if (bold || uline || incolor || inbg || reverse)
-				size++;
-			bold = uline = incolor = inbg = reverse = 0;
-			continue;
-		}
-		else if (*buf == '\26')
-		{
-			if (reverse)
-				size += 16;
-			else
-				size += 17;
-			reverse = !reverse;
-			continue;
-		}
-		else if (*buf == '\37')
-		{
-			if (uline)
-				size += 8;
-			else
-				size += 4;
-			uline = !uline;
-			continue;
-		}
-		size++;
-	}			
-	size += strlen("{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl{\\f0\\fmodern\\fprq1\\"
-		"fcharset0 Fixedsys;}}\r\n"
-		MIRC_COLORS
-		"\\viewkind4\\uc1\\pard\\lang1033\\f0\\fs20")+1;
-	return (size);
-}
-
-/* Converts a string containing IRC codes to RTF
- * Parameters:
- *  buffer - The input buffer containing IRC codes
- *  string - The output buffer in RTF
- */
-void IRCToRTF(unsigned char *buffer, unsigned char *string) 
-{
-	unsigned char *tmp;
-	int i = 0;
-	short bold = 0, uline = 0, incolor = 0, inbg = 0, reverse = 0;
-	sprintf(string, "{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl{\\f0\\fmodern\\fprq1\\"
-		"fcharset0 Fixedsys;}}\r\n"
-		MIRC_COLORS
-		"\\viewkind4\\uc1\\pard\\lang1033\\f0\\fs20");
-	i = strlen(string);
-	for (tmp = buffer; *tmp; tmp++)
-	{
-		if (*tmp == '{')
-		{
-			strcat(string, "\\{");
-			i+=2;
-			continue;
-		}
-		else if (*tmp == '}')
-		{
-			strcat(string, "\\}");
-			i+=2;
-			continue;
-		}
-		else if (*tmp == '\\')
-		{
-			strcat(string, "\\\\");
-			i+=2;
-			continue;
-		}
-		else if (*tmp == '\r')
-		{
-			if (*(tmp+1) && *(tmp+1) == '\n')
-			{
-				tmp++;
-				if (bold)
-				{
-					strcat(string, "\\b0 ");
-					i+=3;
-				}
-				if (uline)
-				{
-					strcat(string, "\\ulnone");
-					i+=7;
-				}
-				if (incolor && !reverse)
-				{
-					strcat(string, "\\cf0");
-					i+=4;
-				}
-				if (inbg && !reverse)
-				{
-					strcat(string, "\\highlight0");
-					i +=11;
-				}
-				if (reverse) {
-					strcat(string, "\\cf0\\highlight0");
-					i += 15;
-				}
-				if (bold || uline || incolor || inbg || reverse)
-					string[i++] = ' ';
-				bold = uline = incolor = inbg = reverse = 0;
-				strcat(string, "\\par\r\n");
-				i +=6;
-			}
-			else
-				string[i++]='\r';
-			continue;
-		}
-		else if (*tmp == '\n')
-		{
-			if (bold)
-			{
-				strcat(string, "\\b0 ");
-				i+=3;
-			}
-			if (uline)
-			{
-				strcat(string, "\\ulnone");
-				i+=7;
-			}
-			if (incolor && !reverse)
-			{
-				strcat(string, "\\cf0");
-				i+=4;
-			}
-			if (inbg && !reverse)
-			{
-				strcat(string, "\\highlight0");
-				i +=11;
-			}
-			if (reverse) {
-				strcat(string, "\\cf0\\highlight0");
-				i += 15;
-			}
-			if (bold || uline || incolor || inbg || reverse)
-				string[i++] = ' ';
-			bold = uline = incolor = inbg = reverse = 0;
-			strcat(string, "\\par\r\n");
-			i +=6;
-			continue;
-		}
-		else if (*tmp == '\2')
-		{
-			if (bold)
-			{
-				strcat(string, "\\b0 ");
-				i+=4;
-			}
-			else
-			{
-				strcat(string, "\\b ");
-				i+=3;
-			}
-			bold = !bold;
-			continue;
-		}
-		else if (*tmp == '\3' && reverse)
-		{
-			if (*(tmp+1) && isdigit(*(tmp+1)))
-			{
-				++tmp;
-				if (*(tmp+1) && isdigit(*(tmp+1)))
-					++tmp;
-				if (*(tmp+1) && *(tmp+1) == ',')
-				{
-					if (*(tmp+2) && isdigit(*(tmp+2)))
-					{
-						tmp+=2;
-						if (*(tmp+1) && isdigit(*(tmp+1)))
-							++tmp;
-					}
-				}
-			}
-			continue;
-		}
-		else if (*tmp == '\3' && !reverse)
-		{
-			strcat(string, "\\cf");
-			i += 3;
-			if (*(tmp+1) && !isdigit(*(tmp+1)))
-			{
-				incolor = 0;
-				string[i++] = '0';
-				if (inbg)
-				{
-					inbg = 0;
-					strcat(string, "\\highlight0");
-					i += 11;
-				}
-			}
-			else if (*(tmp+1))
-			{
-				unsigned char color[3];
-				int number;
-				color[0] = *(++tmp);
-				color[1] = 0;
-				if (*(tmp+1) && isdigit(*(tmp+1)))
-					color[1] = *(++tmp);
-				color[2] = 0;
-				number = atoi(color);
-				if (number == 99 || number == 1)
-				{
-					strcat(string, "16"); 
-					i += 2;
-				}
-				else if (number == 0) 
-				{
-					strcat(string, "1");
-					i++;
-				}
-				else
-				{
-					number %= 16;
-					_itoa(number, color, 10);
-					strcat(string, color);
-					i += strlen(color);
-				}
-				if (*(tmp+1) && *(tmp+1) == ',')
-				{
-					if (*(tmp+2) && isdigit(*(tmp+2)))
-					{
-						strcat(string, "\\highlight");
-						i += 10;
-						tmp++;
-						color[0] = *(++tmp);
-						color[1] = 0;
-						if (*(tmp+1) && isdigit(*(tmp+1)))
-							color[1] = *(++tmp);
-						color[2] = 0;
-						number = atoi(color);
-						if (number == 1)
-						{
-							strcat(string, "16");
-							i += 2;
-						}
-						else if (number == 0 || number == 99)
-							string[i++] = '1';
-						else
-						{
-							number %= 16;
-							_itoa(number, color, 10);
-							strcat(string,color);
-							i += strlen(color);
-						}
-						inbg = 1;
-					}
-				}
-				incolor=1;
-			}
-			string[i++] = ' ';
-			continue;
-		}
-		else if (*tmp == '\17') {
-			if (uline) {
-				strcat(string, "\\ulnone");
-				i += 7;
-			}
-			if (bold) {
-				strcat(string, "\\b0");
-				i += 3;
-			}
-			if (incolor && !reverse) {
-				strcat(string, "\\cf0");
-				i += 4;
-			}
-			if (inbg && !reverse)
-			{
-				strcat(string, "\\highlight0");
-				i += 11;
-			}
-			if (reverse) {
-				strcat(string, "\\cf0\\highlight0");
-				i += 15;
-			}
-			if (uline || bold || incolor || inbg || reverse)
-				string[i++] = ' ';
-			uline = bold = incolor = inbg = reverse = 0;
-			continue;
-		}
-		else if (*tmp == '\26')
-		{
-			if (reverse)
-			{
-				strcat(string, "\\cf0\\highlight0 ");
-				i += 16;
-			}
-			else
-			{
-				strcat(string, "\\cf1\\highlight16 ");
-				i += 17;
-			}
-			reverse = !reverse;
-			continue;
-		}
-
-		else if (*tmp == '\37') {
-			if (uline) {
-				strcat(string, "\\ulnone ");
-				i += 8;
-			}
-			else {
-				strcat(string, "\\ul ");
-				i += 4;
-			}
-			uline = !uline;
-			continue;
-		}
-		string[i++] = *tmp;
-	}
-	strcat(string, "}");
-	return;
-}
diff --git a/src/windows/service.c b/src/windows/service.c
@@ -1,140 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/service.c
- *   Copyright (C) 2002-2004 Dominick Meglio (codemastr)
- *   
- *   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"
-
-SERVICE_STATUS IRCDStatus; 
-SERVICE_STATUS_HANDLE IRCDStatusHandle;
-
-/* Signal to rehash */
-#define IRCD_SERVICE_CONTROL_REHASH 128
-
-MODVAR BOOL IsService = FALSE;
-
-#define WIN32_VERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5
-
-/* Places the service in the STOPPED state
- * Parameters:
- *  code - The error code (or 0)
- */
-void SetServiceStop(int code)
-{
-	IRCDStatus.dwCurrentState = SERVICE_STOPPED;
-	IRCDStatus.dwCheckPoint = 0;
-	IRCDStatus.dwWaitHint = 0;
-	IRCDStatus.dwWin32ExitCode = code;
-	IRCDStatus.dwServiceSpecificExitCode = code;
-	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
-}	
-
-/* Handles the service messages
- * Parameters:
- *  opcode - The message to process
- */
-VOID WINAPI IRCDCtrlHandler(DWORD opcode) 
-{
-	DWORD status;
-	int i;
-	Client *acptr;
-
-	/* Stopping */
-	if (opcode == SERVICE_CONTROL_STOP) 
-	{
-		IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING;
-		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
-
-/*		for (i = 0; i <= LastSlot; i++) 
-		{
-			if (!(acptr = local[i]))
-				continue;
-			if (IsUser(acptr))
-				sendnotice(acptr, "Server Terminating.");
-			else if (IsServer(acptr))
-				sendto_one(acptr, NULL, ":%s ERROR :Terminated", me.name);
-		} */
-		unload_all_modules();
-/*		for (i = LastSlot; i >= 0; i--)
-			if ((acptr = local[i]) && DBufLength(&acptr->local->sendQ) > 0)
-				(void)send_queued(acptr); */
-		SetServiceStop(0);
-	}
-	/* Rehash */
-	else if (opcode == IRCD_SERVICE_CONTROL_REHASH) 
-	{
-		request_rehash(NULL);
-	}
-
-	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
-} 
-
-/* Entry point function
- * Parameters:
- *  dwArgc   - Argument count
- *  lpszArgv - Arguments
- */
-VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) 
-{
-	DWORD error = 0;
-	char path[MAX_PATH], *folder;
-
-	IsService = TRUE;
-
-	/* Initialize the service structure */
-	IRCDStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
-	IRCDStatus.dwCurrentState = SERVICE_START_PENDING;
-	IRCDStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
-	IRCDStatus.dwWin32ExitCode = NO_ERROR;
-	IRCDStatus.dwServiceSpecificExitCode = 0;
-	IRCDStatus.dwCheckPoint = 0;
-	IRCDStatus.dwWaitHint = 0;
- 
-	GetModuleFileName(NULL,path,MAX_PATH);
-	folder = strrchr(path, '\\');
-	*folder = 0;
-	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 6"
-	 */
-	chdir("..");
-
-	/* Register the service controller */
-	IRCDStatusHandle = RegisterServiceCtrlHandler("UnrealIRCd", IRCDCtrlHandler); 
- 
-	GetOSName(OSName);
-
-	InitDebug();
-	init_winsock();
-
-	/* Initialize the IRCd */
-	if ((error = InitUnrealIRCd(dwArgc, lpszArgv)) != 1) 
-	{
-		SetServiceStop(error);
-		return;
-	}
-	
-	/* Go into the running state */
-	IRCDStatus.dwCurrentState = SERVICE_RUNNING;
-	IRCDStatus.dwCheckPoint = 0;
-	IRCDStatus.dwWaitHint = 0;  
-	SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
-
-	SocketLoop(0);
-	return;
-}
diff --git a/src/windows/toolbar.bmp b/src/windows/toolbar.bmp
Binary files differ.
diff --git a/src/windows/unreal.bmp b/src/windows/unreal.bmp
Binary files differ.
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
@@ -1,204 +0,0 @@
-; UnrealIRCd Windows Installation Script
-; Requires Inno Setup 4.1.6 or later
-
-; Uncomment the line below to package with libcurl support
-#define USE_CURL
-
-[Setup]
-AppName=UnrealIRCd 6
-AppVerName=UnrealIRCd 6.1.0
-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 6
-DefaultGroupName=UnrealIRCd 6
-AllowNoIcons=yes
-LicenseFile=src\windows\gplplusssl.rtf
-Compression=lzma
-SolidCompression=true
-MinVersion=6.1
-OutputDir=.
-SourceDir=../../
-UninstallDisplayIcon={app}\bin\UnrealIRCd.exe
-UninstallFilesDir={app}\bin\uninstaller
-DisableWelcomePage=no
-ArchitecturesInstallIn64BitMode=x64
-ArchitecturesAllowed=x64
-;These are set only on release:
-;SignedUninstaller=yes
-;SignTool=signtool
-
-; !!! 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
-Name: "installservice"; Description: "Install as a &service (not for beginners)"; GroupDescription: "Service support:"; Flags: unchecked; MinVersion: 0,4.0
-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: "TLS options:";
-Name: "fixperm"; Description: "Make UnrealIRCd folder writable by current user";
-
-[Files]
-; UnrealIRCd binaries
-Source: "UnrealIRCd.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
-Source: "UnrealIRCd.pdb"; DestDir: "{app}\bin"; Flags: ignoreversion
-Source: "unrealircdctl.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
-Source: "unrealsvc.exe";  DestDir: "{app}\bin"; Flags: ignoreversion signonce
-
-; TLS certificate generation helpers
-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\rpc\*.dll"; DestDir: "{app}\modules\rpc"; 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
-Source: "doc\conf\badwords.conf"; DestDir: "{app}\conf"; Flags: onlyifdoesntexist
-Source: "doc\conf\dccallow.conf"; DestDir: "{app}\conf"; Flags: onlyifdoesntexist
-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
-
-[Dirs]
-Name: "{app}\tmp"
-Name: "{app}\bin"
-Name: "{app}\cache"
-Name: "{app}\logs"
-Name: "{app}\conf"
-Name: "{app}\conf\tls"
-Name: "{app}\data"
-Name: "{app}\modules\third"
-
-[Code]
-var
-	uninstaller: String;
-	ErrorCode: Integer;
-
-//*********************************************************************************
-// This is where all starts.
-//*********************************************************************************
-function InitializeSetup(): Boolean;
-var
-	major: Cardinal;
-begin
-	Result := true;
-
-	if Not RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Major', major) then
-	begin
-		MsgBox('UnrealIRCd requires the Microsoft Visual C++ Redistributable for Visual Studio 2019 to be installed.' #13 +
-		       'After you click OK you will be taken to a download page from Microsoft:' #13 +
-		       '1) Scroll down to the section "Visual Studio 2015, 2017 and 2019"' #13 +
-		       '2) Click on the x64 "vc_redist.x64.exe" to download the 64 bit installer' #13 +
-		       '3) Run the installer.' #13 + #13 +
-		       'If you are already absolutely sure that you have this package installed then you can skip this step.', mbInformation, MB_OK);
-		ShellExec('open', 'https://support.microsoft.com/help/2977003/the-latest-supported-visual-c-downloads', '', '', SW_SHOWNORMAL,ewNoWait,ErrorCode);
-		MsgBox('Your browser was launched. After you have installed the Microsoft Visual C++ Redistributable for Visual Studio 2019 (vc_redist.x64.exe), click OK below to continue the UnrealIRCd installer', mbInformation, MB_OK);
-	end;
-end;
-
-procedure CurStepChanged(CurStep: TSetupStep);
-
-var
-	hWnd: Integer;
-	ResultCode: Integer;
-	ResultXP: boolean;
-	Result2003: boolean;
-	Res: Integer;
-	s: String;
-	d: String;
-begin
-if CurStep = ssPostInstall then
-	begin
-		d := ExpandConstant('{app}');
-		if IsTaskSelected('fixperm') then
-		begin
-			// This fixes the permissions in the UnrealIRCd folder by granting full access to the user
-			// running the install.
-			s := '-on "'+d+'" -ot file -actn ace -ace "n:'+GetUserNameString()+';p:full;m:set"';
-			Exec(d+'\tmp\setacl.exe', s, d, SW_HIDE, ewWaitUntilTerminated, Res);
-		end
-		else
-		begin
-			MsgBox('You have chosen to not have the installer automatically set write access. Please ensure that the user running the IRCd can write to '+d+', otherwise the IRCd will fail to load.',mbConfirmation, MB_OK);
-		end;
-		if IsTaskSelected('installservice') then
-		begin
-			// Similar to above, but this adds full access to NetworkService,
-			// otherwise it cannot copy modules, cannot write to logs, etc etc.
-			s := '-on "'+d+'" -ot file -actn ace -ace "n:NetworkService;p:full;m:set"';
-			Exec(d+'\tmp\setacl.exe', s, d, SW_HIDE, ewWaitUntilTerminated, Res);
-		end;
-	end;
-end;
-
-//*********************************************************************************
-// Checks if TLS cert file exists
-//*********************************************************************************
-
-procedure CurPageChanged(CurPage: Integer);
-begin
-	if (CurPage = wpSelectTasks) then
-	begin
-		if FileExists(ExpandConstant('{app}\conf\tls\server.cert.pem')) then
-		begin
-			WizardForm.TasksList.Checked[9]:=false;
-		end
-		else
-		begin
-			WizardForm.TasksList.Checked[9]:=true;
-		end;
-	end;
-end;
-
-[Icons]
-Name: "{group}\UnrealIRCd"; Filename: "{app}\bin\UnrealIRCd.exe"; WorkingDir: "{app}\bin"
-Name: "{group}\Uninstall UnrealIRCd"; Filename: "{uninstallexe}"; WorkingDir: "{app}\bin"
-Name: "{group}\Make Certificate"; Filename: "{app}\bin\makecert.bat"; WorkingDir: "{app}\bin"
-Name: "{group}\Documentation"; Filename: "https://www.unrealircd.org/docs/"; WorkingDir: "{app}\bin"
-Name: "{userdesktop}\UnrealIRCd"; Filename: "{app}\bin\UnrealIRCd.exe"; WorkingDir: "{app}\bin"; Tasks: desktopicon
-Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\UnrealIRCd"; Filename: "{app}\bin\UnrealIRCd.exe"; WorkingDir: "{app}\bin"; Tasks: quicklaunchicon
-
-[Run]
-Filename: "https://www.unrealircd.org/docs/"; Description: "View documentation"; Parameters: ""; Flags: postinstall skipifsilent shellexec runmaximized
-Filename: "https://www.unrealircd.org/docs/Installing_%28Windows%29"; Description: "View installation instructions"; Parameters: ""; Flags: postinstall skipifsilent shellexec runmaximized
-Filename: "{app}\bin\unrealsvc.exe"; Parameters: "install"; Flags: runminimized nowait; Tasks: installservice
-Filename: "{app}\bin\unrealsvc.exe"; Parameters: "config startup manual"; Flags: runminimized nowait; Tasks: installservice/startdemand
-Filename: "{app}\bin\unrealsvc.exe"; Parameters: "config startup auto"; Flags: runminimized nowait; Tasks: installservice/startboot
-Filename: "{app}\bin\unrealsvc.exe"; Parameters: "config crashrestart 2"; Flags: runminimized nowait; Tasks: installservice/crashrestart
-Filename: "{app}\bin\makecert.bat"; Tasks: makecert; Flags: postinstall;
-
-[UninstallRun]
-Filename: "{app}\bin\unrealsvc.exe"; Parameters: "uninstall"; Flags: runminimized; RunOnceID: "DelService"; Tasks: installservice
diff --git a/src/windows/unrealircdctl.exe.manifest b/src/windows/unrealircdctl.exe.manifest
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
-<assemblyIdentity
-    processorArchitecture="amd64"
-    name="UnrealIRCd.UnrealIRCd.6"
-    version="6.0.1.0"
-    type="win32"
-/>
-<description>UnrealIRCd - Control utility</description>
-</assembly>
diff --git a/src/windows/unrealsvc.c b/src/windows/unrealsvc.c
@@ -1,213 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/unrealsvc.c
- *   Copyright (C) 2002 Dominick Meglio (codemastr)
- *   Copyright (C) 2006-2021 Bram Matthys (Syzop)
- *   
- *   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"
-
-typedef BOOL (*UCHANGESERVICECONFIG2)(SC_HANDLE, DWORD, LPVOID);
-HMODULE hAdvapi;
-UCHANGESERVICECONFIG2 uChangeServiceConfig2;
-
-#define IRCD_SERVICE_CONTROL_REHASH 128
-void show_usage() {
-	fprintf(stderr, "unrealsvc start|stop|rehash|restart|install|uninstall|config <option> <value>");
-	fprintf(stderr, "\nValid config options:\nstartup auto|manual\n");
-	fprintf(stderr, "crashrestart delay\n");
-}
-
-char *show_error(DWORD code) {
-	static char buf[1024];
-	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, 0, buf, 1024, NULL);
-	return buf;
-}
-
-SC_HANDLE unreal_open_service_manager(void)
-{
-	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
-	if (!hSCManager)
-	{
-		printf("Failed to connect to service manager: %s", show_error(GetLastError()));
-		printf("Note that elevated administrator permissions are necessary to execute this command.\n");
-		exit(1);
-	}
-	return hSCManager;
-}
-int main(int argc, char *argv[]) {
-	char *bslash;
-
-	if (argc < 2) {
-		show_usage();
-		return -1;
-	}
-	hAdvapi = LoadLibrary("advapi32.dll");
-	uChangeServiceConfig2 = (UCHANGESERVICECONFIG2)GetProcAddress(hAdvapi, "ChangeServiceConfig2A");
-
-	if (!strcasecmp(argv[1], "install"))
-	{
-		SC_HANDLE hService, hSCManager;
-		char path[MAX_PATH+1];
-		char binpath[MAX_PATH+1];
-		hSCManager = unreal_open_service_manager();
-
-		GetModuleFileName(NULL,path,MAX_PATH);
-		if ((bslash = strrchr(path, '\\')))
-			*bslash = 0;
-		
-		strcpy(binpath,path);
-		strcat(binpath, "\\UnrealIRCd.exe");
-		hService = CreateService(hSCManager, "UnrealIRCd", "UnrealIRCd",
-				 SERVICE_CHANGE_CONFIG, SERVICE_WIN32_OWN_PROCESS,
-				 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binpath,
-				 NULL, NULL, NULL, TEXT("NT AUTHORITY\\NetworkService"), "");
-		if (hService) 
-		{
-			SERVICE_DESCRIPTION info;
-			printf("UnrealIRCd NT Service successfully installed\n");
-			info.lpDescription = "Internet Relay Chat Server. Allows users to chat with eachother via an IRC client.";
-			uChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &info);
-			CloseServiceHandle(hService);
-			printf("\n[!!!] IMPORTANT: By default the network service user cannot write to the \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 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");
-		} else {
-			printf("Failed to install UnrealIRCd NT Service - %s", show_error(GetLastError()));
-		}
-		CloseServiceHandle(hSCManager);
-		return 0;
-	}
-	else if (!strcasecmp(argv[1], "uninstall"))
-	{
-		SC_HANDLE hSCManager = unreal_open_service_manager();
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", DELETE); 
-		if (DeleteService(hService)) 
-			printf("UnrealIRCd NT Service successfully uninstalled\n");
-		else
-			printf("Failed to uninstall UnrealIRCd NT Service - %s\n", show_error(GetLastError()));
-		CloseServiceHandle(hService);
-		CloseServiceHandle(hSCManager);
-		return 0;
-	}
-	else if (!strcasecmp(argv[1], "start"))
-	{
-		SC_HANDLE hSCManager = unreal_open_service_manager();
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_START); 
-		if (StartService(hService, 0, NULL))
-			printf("UnrealIRCd NT Service successfully started");
-		else
-			printf("Failed to start UnrealIRCd NT Service - %s", show_error(GetLastError()));
-		CloseServiceHandle(hService);
-		CloseServiceHandle(hSCManager);
-		return 0;
-	}
-	else if (!strcasecmp(argv[1], "stop"))
-	{
-		SERVICE_STATUS status;
-		SC_HANDLE hSCManager = unreal_open_service_manager();
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP); 
-		ControlService(hService, SERVICE_CONTROL_STOP, &status);
-		printf("UnrealIRCd NT Service successfully stopped");
-		CloseServiceHandle(hService);
-		CloseServiceHandle(hSCManager);
-		return 0;
-	}
-	else if (!strcasecmp(argv[1], "restart"))
-	{
-		SERVICE_STATUS status;
-		SC_HANDLE hSCManager = unreal_open_service_manager();
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP|SERVICE_START); 
-		ControlService(hService, SERVICE_CONTROL_STOP, &status);
-		if (StartService(hService, 0, NULL)) 
-			printf("UnrealIRCd NT Service successfully restarted");
-		CloseServiceHandle(hService);
-		CloseServiceHandle(hSCManager);
-		return 0;
-	}
-	else if (!strcasecmp(argv[1], "rehash"))
-	{
-		SERVICE_STATUS status;
-		SC_HANDLE hSCManager = unreal_open_service_manager();
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_USER_DEFINED_CONTROL); 
-		ControlService(hService, IRCD_SERVICE_CONTROL_REHASH, &status);
-		printf("UnrealIRCd NT Service successfully rehashed");
-	}
-	else if (!strcasecmp(argv[1], "config"))
-	{
-		SERVICE_STATUS status;
-		SC_HANDLE hSCManager = unreal_open_service_manager();
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_CHANGE_CONFIG|SERVICE_START);
-		if (argc < 3) {
-			show_usage();
-			return -1;
-		}
-		if (!strcasecmp(argv[2], "startup")) {
-			if (ChangeServiceConfig(hService, SERVICE_NO_CHANGE,
-					    !strcasecmp(argv[3], "auto") ? SERVICE_AUTO_START
-						: SERVICE_DEMAND_START, SERVICE_NO_CHANGE,
-					    NULL, NULL, NULL, NULL, NULL, NULL, NULL)) 
-				printf("UnrealIRCd NT Service configuration changed");
-			else
-				printf("UnrealIRCd NT Service configuration change failed - %s", show_error(GetLastError()));	
-		}
-		else if (!strcasecmp(argv[2], "crashrestart")) {
-			SERVICE_FAILURE_ACTIONS hFailActions;
-			SC_ACTION hAction;
-			memset(&hFailActions, 0, sizeof(hFailActions));
-			if (argc >= 4) {
-				hFailActions.dwResetPeriod = 30;
-				hFailActions.cActions = 1;
-				hAction.Type = SC_ACTION_RESTART;
-				hAction.Delay = atoi(argv[3])*60000;
-				hFailActions.lpsaActions = &hAction;
-				if (uChangeServiceConfig2(hService, SERVICE_CONFIG_FAILURE_ACTIONS, 	
-						     &hFailActions))
-					printf("UnrealIRCd NT Service configuration changed");
-				else
-					printf("UnrealIRCd NT Service configuration change failed - %s", show_error(GetLastError()));	
-			}
-			else {
-				hFailActions.dwResetPeriod = 0;
-				hFailActions.cActions = 0;
-				hAction.Type = SC_ACTION_NONE;
-				hFailActions.lpsaActions = &hAction;
-				if (uChangeServiceConfig2(hService, SERVICE_CONFIG_FAILURE_ACTIONS,
-						     &hFailActions)) 
-					printf("UnrealIRCd NT Service configuration changed");
-				else
-					printf("UnrealIRCd NT Service configuration change failed - %s", show_error(GetLastError()));	
-
-				
-			}
-		}
-		else {
-			show_usage();
-			return -1;
-		}	
-	}
-	else {
-		show_usage();
-		return -1;
-	}
-}
-
diff --git a/src/windows/unrealsvc.rc b/src/windows/unrealsvc.rc
@@ -1,99 +0,0 @@
-//Microsoft Developer Studio generated resource script.
-//
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 2 resource.
-//
-#define APSTUDIO_HIDDEN_SYMBOLS
-#include "windows.h"
-#undef APSTUDIO_HIDDEN_SYMBOLS
-#include "resource.h"
-#include "winver.h"
-
-/////////////////////////////////////////////////////////////////////////////
-#undef APSTUDIO_READONLY_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-// English (U.S.) resources
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-#ifdef _WIN32
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-#pragma code_page(1252)
-#endif //_WIN32
-
-
-#ifndef _MAC
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VER_UNREAL VERSIONINFO
- FILEVERSION 1,0,0,0
- PRODUCTVERSION 1,0,0,0
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS 0x1L
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS 0x10004L
- FILETYPE 0x1L
- FILESUBTYPE 0x0L
-BEGIN
-    BLOCK "StringFileInfo"
-    BEGIN
-        BLOCK "040904b0"
-        BEGIN
-            VALUE "Comments", "\0"
-            VALUE "CompanyName", "none\0"
-            VALUE "FileDescription", "\0"
-            VALUE "FileVersion", "1.0\0"
-            VALUE "InternalName", "UnrealIRCd Service Utility\0"
-            VALUE "LegalCopyright", "\0"
-            VALUE "LegalTrademarks", "\0"
-            VALUE "OriginalFilename", "\0"
-            VALUE "PrivateBuild", "\0"
-            VALUE "ProductName", "UnrealIRCd Service Utility\0"
-            VALUE "ProductVersion", "1.0\0"
-            VALUE "SpecialBuild", "\0"
-        END
-    END
-    BLOCK "VarFileInfo"
-    BEGIN
-        VALUE "Translation", 0x409, 1200
-    END
-END
-
-#endif    // !_MAC
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Icon
-//
-
-// Icon with lowest ID value placed first to ensure application icon
-// remains consistent on all systems.
-ICO_MAIN                ICON    DISCARDABLE     "icon1.ico"
-
-
-#endif    // English (U.S.) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
-
-#ifndef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 3 resource.
-//
-
-
-/////////////////////////////////////////////////////////////////////////////
-#endif    // not APSTUDIO_INVOKED
-
diff --git a/src/windows/win.c b/src/windows/win.c
@@ -1,341 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/win.c
- *   Copyright (C) 2004 Dominick Meglio (codemastr)
- *   
- *   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 <windows.h>
-#include <tchar.h>
-#include <strsafe.h>
-
-#include "win.h"
-
-#pragma comment(lib, "User32.lib")
-
-// Newer product types than what is currently defined in
-//   Visual Studio 2005
-#ifndef PRODUCT_ULTIMATE
-#define PRODUCT_ULTIMATE                        0x00000001
-#endif
-#ifndef PRODUCT_HOME_BASIC
-#define PRODUCT_HOME_BASIC                      0x00000002
-#endif
-#ifndef PRODUCT_HOME_PREMIUM
-#define PRODUCT_HOME_PREMIUM                    0x00000003
-#endif
-#ifndef PRODUCT_ENTERPRISE
-#define PRODUCT_ENTERPRISE                      0x00000004
-#endif
-#ifndef PRODUCT_HOME_BASIC_N
-#define PRODUCT_HOME_BASIC_N                    0x00000005
-#endif
-#ifndef PRODUCT_BUSINESS
-#define PRODUCT_BUSINESS                        0x00000006
-#endif
-#ifndef PRODUCT_STANDARD_SERVER
-#define PRODUCT_STANDARD_SERVER                 0x00000007
-#endif
-#ifndef PRODUCT_DATACENTER_SERVER
-#define PRODUCT_DATACENTER_SERVER               0x00000008
-#endif
-#ifndef PRODUCT_SMALLBUSINESS_SERVER
-#define PRODUCT_SMALLBUSINESS_SERVER            0x00000009
-#endif
-#ifndef PRODUCT_ENTERPRISE_SERVER
-#define PRODUCT_ENTERPRISE_SERVER               0x0000000A
-#endif
-#ifndef PRODUCT_STARTER
-#define PRODUCT_STARTER                         0x0000000B
-#endif
-#ifndef PRODUCT_DATACENTER_SERVER_CORE
-#define PRODUCT_DATACENTER_SERVER_CORE          0x0000000C
-#endif
-#ifndef PRODUCT_STANDARD_SERVER_CORE
-#define PRODUCT_STANDARD_SERVER_CORE            0x0000000D
-#endif
-#ifndef PRODUCT_ENTERPRISE_SERVER_CORE
-#define PRODUCT_ENTERPRISE_SERVER_CORE          0x0000000E
-#endif
-#ifndef PRODUCT_ENTERPRISE_SERVER_IA64
-#define PRODUCT_ENTERPRISE_SERVER_IA64          0x0000000F
-#endif
-#ifndef PRODUCT_BUSINESS_N
-#define PRODUCT_BUSINESS_N                      0x00000010
-#endif
-#ifndef PRODUCT_WEB_SERVER
-#define PRODUCT_WEB_SERVER                      0x00000011
-#endif
-#ifndef PRODUCT_CLUSTER_SERVER
-#define PRODUCT_CLUSTER_SERVER                  0x00000012
-#endif
-#ifndef PRODUCT_HOME_SERVER
-#define PRODUCT_HOME_SERVER                     0x00000013
-#endif
-#ifndef PRODUCT_STORAGE_EXPRESS_SERVER
-#define PRODUCT_STORAGE_EXPRESS_SERVER          0x00000014
-#endif
-#ifndef PRODUCT_STORAGE_STANDARD_SERVER
-#define PRODUCT_STORAGE_STANDARD_SERVER         0x00000015
-#endif
-#ifndef PRODUCT_STORAGE_WORKGROUP_SERVER
-#define PRODUCT_STORAGE_WORKGROUP_SERVER        0x00000016
-#endif
-#ifndef PRODUCT_STORAGE_ENTERPRISE_SERVER
-#define PRODUCT_STORAGE_ENTERPRISE_SERVER       0x00000017
-#endif
-#ifndef PRODUCT_SERVER_FOR_SMALLBUSINESS
-#define PRODUCT_SERVER_FOR_SMALLBUSINESS        0x00000018
-#endif
-#ifndef PRODUCT_SMALLBUSINESS_SERVER_PREMIUM
-#define PRODUCT_SMALLBUSINESS_SERVER_PREMIUM    0x00000019
-#endif
-
-// Newer system metrics values
-#ifndef SM_SERVERR2
-#define SM_SERVERR2 89   
-#endif
-
-#ifndef VER_SUITE_WH_SERVER
-#define VER_SUITE_WH_SERVER                     0x00008000
-#endif
-
-#ifndef VER_SUITE_STORAGE_SERVER
-#define VER_SUITE_STORAGE_SERVER                0x00002000
-#endif
-
-#ifndef VER_SUITE_COMPUTE_SERVER
-#define VER_SUITE_COMPUTE_SERVER                0x00004000
-#endif
-
-typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
-typedef BOOL (WINAPI *PGPI)(DWORD, DWORD, DWORD, DWORD, PDWORD);
-
-
-
-/* Retrieves the OS name as a string
- * Parameters:
- * pszOS  - The buffer to write the OS name to (size at least OSVER_SIZE)
- */
-int GetOSName(char *pszOS)
-{
-   OSVERSIONINFOEX osvi;
-   SYSTEM_INFO si;
-   PGNSI pGNSI;
-   PGPI pGPI;
-   BOOL bOsVersionInfoEx;
-   DWORD dwType;
-
-   ZeroMemory(&si, sizeof(SYSTEM_INFO));
-   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
-
-   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
-
-   if ( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
-      return -1;
-
-   // Call GetNativeSystemInfo if supported or GetSystemInfo otherwise.
-
-   pGNSI = (PGNSI) GetProcAddress(
-      GetModuleHandle(TEXT("kernel32.dll")), 
-      "GetNativeSystemInfo");
-   if (NULL != pGNSI)
-      pGNSI(&si);
-   else GetSystemInfo(&si);
-
-   if ( VER_PLATFORM_WIN32_NT==osvi.dwPlatformId && 
-        osvi.dwMajorVersion > 4 )
-   {
-      StringCchCopy(pszOS, OSVER_SIZE, TEXT("Microsoft "));
-
-      // Test for the specific product.
-
-      if ( osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1 )
-      {
-         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 )
-             StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Vista "));
-         else StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Server 2008 " ));
-
-         pGPI = (PGPI) GetProcAddress(
-            GetModuleHandle(TEXT("kernel32.dll")), 
-            "GetProductInfo");
-
-         pGPI( 6, 0, 0, 0, &dwType);
-
-         switch( dwType )
-         {
-            case PRODUCT_ULTIMATE:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Ultimate Edition" ));
-               break;
-            case PRODUCT_HOME_PREMIUM:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Home Premium Edition" ));
-               break;
-            case PRODUCT_HOME_BASIC:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Home Basic Edition" ));
-               break;
-            case PRODUCT_ENTERPRISE:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition" ));
-               break;
-            case PRODUCT_BUSINESS:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Business Edition" ));
-               break;
-            case PRODUCT_STARTER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Starter Edition" ));
-               break;
-            case PRODUCT_CLUSTER_SERVER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Cluster Server Edition" ));
-               break;
-            case PRODUCT_DATACENTER_SERVER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Datacenter Edition" ));
-               break;
-            case PRODUCT_DATACENTER_SERVER_CORE:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Datacenter Edition (core installation)" ));
-               break;
-            case PRODUCT_ENTERPRISE_SERVER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition" ));
-               break;
-            case PRODUCT_ENTERPRISE_SERVER_CORE:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition (core installation)" ));
-               break;
-            case PRODUCT_ENTERPRISE_SERVER_IA64:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Enterprise Edition for Itanium-based Systems" ));
-               break;
-            case PRODUCT_SMALLBUSINESS_SERVER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Small Business Server" ));
-               break;
-            case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Small Business Server Premium Edition" ));
-               break;
-            case PRODUCT_STANDARD_SERVER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Standard Edition" ));
-               break;
-            case PRODUCT_STANDARD_SERVER_CORE:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Standard Edition (core installation)" ));
-               break;
-            case PRODUCT_WEB_SERVER:
-               StringCchCat(pszOS, OSVER_SIZE, TEXT("Web Server Edition" ));
-               break;
-         }
-         if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 )
-            StringCchCat(pszOS, OSVER_SIZE, TEXT( ", 64-bit" ));
-         else if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_INTEL )
-            StringCchCat(pszOS, OSVER_SIZE, TEXT(", 32-bit"));
-      }
-
-      if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2 )
-      {
-         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 &&
-                  si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
-         {
-            StringCchCat(pszOS, OSVER_SIZE, TEXT( "Windows XP Professional x64 Edition"));
-         }
-         else StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows Server 2003, "));
-
-         // Test for the server type.
-         if ( osvi.wProductType != VER_NT_WORKSTATION )
-         {
-            if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_IA64 )
-            {
-                if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
-                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Edition for Itanium-based Systems" ));
-                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 )
-                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter x64 Edition" ));
-                else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
-                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Enterprise x64 Edition" ));
-                else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Standard x64 Edition" ));
-            }
-
-            else
-            {
-                if ( osvi.wSuiteMask & VER_SUITE_COMPUTE_SERVER )
-                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Compute Cluster Edition" ));
-                else if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
-                   StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Edition" ));
-                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" ));
-                else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Standard Edition" ));
-            }
-         }
-      }
-
-      if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )
-      {
-         StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows XP "));
-         if ( osvi.wSuiteMask & VER_SUITE_PERSONAL )
-            StringCchCat(pszOS, OSVER_SIZE, TEXT( "Home Edition" ));
-         else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Professional" ));
-      }
-
-      if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
-      {
-         StringCchCat(pszOS, OSVER_SIZE, TEXT("Windows 2000 "));
-
-         if ( osvi.wProductType == VER_NT_WORKSTATION )
-         {
-            StringCchCat(pszOS, OSVER_SIZE, TEXT( "Professional" ));
-         }
-         else 
-         {
-            if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
-              StringCchCat(pszOS, OSVER_SIZE, TEXT( "Datacenter Server" ));
-            else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
-               StringCchCat(pszOS, OSVER_SIZE, TEXT( "Advanced Server" ));
-            else StringCchCat(pszOS, OSVER_SIZE, TEXT( "Server" ));
-         }
-      }
-
-       // Include service pack (if any) and build number.
-
-      if ( _tcslen(osvi.szCSDVersion) > 0 )
-      {
-          StringCchCat(pszOS, OSVER_SIZE, TEXT(" ") );
-          StringCchCat(pszOS, OSVER_SIZE, osvi.szCSDVersion);
-      }
-
-      {
-          TCHAR buf[80];
-
-          StringCchPrintf( buf, 80, TEXT(" (build %d)"), osvi.dwBuildNumber);
-          StringCchCat(pszOS, OSVER_SIZE, buf);
-      }
-
-      return 0; 
-   }
-   else
-   {  
-      return -1;
-   }
-}
diff --git a/src/windows/win.h b/src/windows/win.h
@@ -1,43 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, win32/win32.h
- *   Copyright (C) 2004 Dominick Meglio (codemastr)
- *   
- *   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 <windows.h>
-
-typedef struct {
-	int *size;
-	unsigned char **buffer;
-} StreamIO;
-
-typedef struct IRCColor {
-	struct IRCColor *next;
-	unsigned char *color;
-} IRCColor;
-
-extern UINT WM_FINDMSGSTRING;
-extern unsigned char *RTFBuf;
-extern HINSTANCE hInst;
-
-extern FARPROC lpfnOldWndProc;
-extern LRESULT RESubClassFunc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);
-extern DWORD CALLBACK SplitIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
-extern DWORD CALLBACK BufferIt(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
-extern DWORD CALLBACK RTFToIRC(int fd, unsigned char *pbBuff, long cb);
-
-#define OSVER_SIZE 256
-
diff --git a/src/windows/windebug.c b/src/windows/windebug.c
@@ -1,370 +0,0 @@
-/************************************************************************
- *   IRC - Internet Relay Chat, windows/windebug.c
- *   Copyright (C) 2002-2004 Dominick Meglio (codemastr)
- *   
- *   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 <dbghelp.h>
-
-#ifndef IRCDTOTALVERSION
-#define IRCDTOTALVERSION BASE_VERSION "-" PATCH1 PATCH2 PATCH3 PATCH4 PATCH5 PATCH6 PATCH7 PATCH8 PATCH9
-#endif
-
-extern OSVERSIONINFO VerInfo;
-extern char OSName[256];
-extern char backupbuf[8192];
-extern char *buildid;
-extern char *extraflags;
-
-/* crappy, but safe :p */
-typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
-										CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
-										CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
-										CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
-										);
-
-
-/* Runs a stack trace 
- * Parameters:
- *  e - The exception information
- * Returns:
- *  The stack trace with function and line number information
- */
-__inline char *StackTrace(EXCEPTION_POINTERS *e) 
-{
-	static char buffer[5000];
-	char curmodule[256];
-	DWORD symOptions;
-	DWORD64 dwDisp;
-	DWORD dwDisp32;
-	int frame;
-	HANDLE hProcess = GetCurrentProcess();
-	IMAGEHLP_SYMBOL64 *pSym = safe_alloc(sizeof(IMAGEHLP_SYMBOL64)+500);
-	IMAGEHLP_LINE64 pLine;
-	IMAGEHLP_MODULE64 pMod;
-	STACKFRAME64 Stack;
-	CONTEXT context;
-
-	memcpy(&context, e->ContextRecord, sizeof(CONTEXT));
-
-	/* Load the stack information */
-	memset(&Stack, 0, sizeof(Stack));
-	Stack.AddrPC.Offset = e->ContextRecord->Rip;
-	Stack.AddrPC.Mode = AddrModeFlat;
-	Stack.AddrFrame.Offset = e->ContextRecord->Rbp;
-	Stack.AddrFrame.Mode = AddrModeFlat;
-	Stack.AddrStack.Offset = e->ContextRecord->Rsp;
-	Stack.AddrStack.Mode = AddrModeFlat;
-	hProcess = GetCurrentProcess();
-
-	/* Initialize symbol retrieval system */
-	SymInitialize(hProcess, NULL, TRUE);
-	SymSetOptions(SYMOPT_LOAD_LINES|SYMOPT_UNDNAME);
-	pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
-	pSym->MaxNameLength = 500;
-
-	/* Retrieve the first module name */
-	memset(&pMod, 0, sizeof(pMod));
-	pMod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
-	SymGetModuleInfo64(hProcess, Stack.AddrPC.Offset, &pMod);
-	strcpy(curmodule, pMod.ModuleName);
-	sprintf(buffer, "\tModule: %s\n", pMod.ModuleName);
-
-	/* Walk through the stack */
-	for (frame = 0; ; frame++) 
-	{
-		char buf[500];
-		if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(),
-			&Stack, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
-			break;
-
-		memset(&pMod, 0, sizeof(pMod));
-		pMod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
-		SymGetModuleInfo64(hProcess, Stack.AddrPC.Offset, &pMod);
-		if (strcmp(curmodule, pMod.ModuleName)) 
-		{
-			strcpy(curmodule, pMod.ModuleName);
-			sprintf(buf, "\tModule: %s\n", pMod.ModuleName);
-			strcat(buffer, buf);
-		}
-
-		memset(&pLine, 0, sizeof(pLine));
-		pLine.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
-		SymGetLineFromAddr64(hProcess, Stack.AddrPC.Offset, &dwDisp32, &pLine);
-		SymGetSymFromAddr64(hProcess, Stack.AddrPC.Offset, &dwDisp, pSym);
-		sprintf(buf, "\t\t#%d %s:%d: %s\n", frame, pLine.FileName, pLine.LineNumber, 
-		        pSym->Name);
-		strcat(buffer, buf);
-	}
-	strcat(buffer, "End of Stack trace\n");
-	return buffer;
-
-}
-
-/* Retrieves the values of several registers
- * Parameters:
- *  context - The CPU context
- * Returns:
- *  The values of the registers as a string.
- */
-__inline char *GetRegisters(CONTEXT *context) 
-{
-	static char buffer[1024];
-
-	sprintf(buffer,
-		"\tRAX=%p"
-		"\tRBX=%p"
-		"\tRCX=%p"
-		"\tRDX=%p\n"
-		"\tRSI=%p"
-		"\tRDI=%p"
-		"\tRBP=%p"
-		"\tRSP=%p\n"
-		"\tR8=%p"
-		"\tR9=%p"
-		"\tR10=%p"
-		"\tR11=%p\n"
-		"\tR12=%p"
-		"\tR13=%p"
-		"\tR14=%p"
-		"\tR15=%p\n"
-		"\tRIP=%p\n",
-		(void *)context->Rax,
-		(void *)context->Rbx,
-		(void *)context->Rcx,
-		(void *)context->Rdx,
-		(void *)context->Rsi,
-		(void *)context->Rdi,
-		(void *)context->Rbp,
-		(void *)context->Rsp,
-		(void *)context->R8,
-		(void *)context->R9,
-		(void *)context->R10,
-		(void *)context->R11,
-		(void *)context->R12,
-		(void *)context->R13,
-		(void *)context->R14,
-		(void *)context->R15,
-		(void *)context->Rip);
-
-	return buffer;
-}
-
-/* Convert the exception code to a human readable string
- * Parameters:
- *  code - The exception code to convert
- * Returns:
- *  The exception code represented as a string
- */
-__inline char *GetException(DWORD code) 
-{
-	switch (code) 
-	{
-		case EXCEPTION_ACCESS_VIOLATION:
-			return "Access Violation";
-		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
-			return "Array Bounds Exceeded";
-		case EXCEPTION_BREAKPOINT:
-			return "Breakpoint";
-		case EXCEPTION_DATATYPE_MISALIGNMENT:
-			return "Datatype Misalignment";
-		case EXCEPTION_FLT_DENORMAL_OPERAND:
-			return "Floating Point Denormal Operand";
-		case EXCEPTION_FLT_DIVIDE_BY_ZERO:
-			return "Floating Point Division By Zero";
-		case EXCEPTION_FLT_INEXACT_RESULT:
-			return "Floating Point Inexact Result";
-		case EXCEPTION_FLT_INVALID_OPERATION:
-			return "Floating Point Invalid Operation";
-		case EXCEPTION_FLT_OVERFLOW:
-			return "Floating Point Overflow";
-		case EXCEPTION_FLT_STACK_CHECK:
-			return "Floating Point Stack Overflow";
-		case EXCEPTION_FLT_UNDERFLOW:
-			return "Floating Point Underflow";
-		case EXCEPTION_ILLEGAL_INSTRUCTION:
-			return "Illegal Instruction";
-		case EXCEPTION_IN_PAGE_ERROR:
-			return "In Page Error";
-		case EXCEPTION_INT_DIVIDE_BY_ZERO:
-			return "Integer Division By Zero";
-		case EXCEPTION_INT_OVERFLOW:
-			return "Integer Overflow";
-		case EXCEPTION_INVALID_DISPOSITION:
-			return "Invalid Disposition";
-		case EXCEPTION_NONCONTINUABLE_EXCEPTION:
-			return "Noncontinuable Exception";
-		case EXCEPTION_PRIV_INSTRUCTION:
-			return "Unallowed Instruction";
-		case EXCEPTION_SINGLE_STEP:
-			return "Single Step";
-		case EXCEPTION_STACK_OVERFLOW:
-			return "Stack Overflow";
-		default:
-			return "Unknown Exception";
-	}
-}
-
-void StartCrashReporter(void)
-{
-	char fname[MAX_PATH], fnamewarg[MAX_PATH+32];
-	PROCESS_INFORMATION pi;
-	STARTUPINFO si;
-	
-	memset(&pi, 0, sizeof(pi));
-	memset(&si, 0, sizeof(si));
-	
-	GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
-	
-	snprintf(fnamewarg, sizeof(fnamewarg), "\"%s\" %s", fname, "-R");
-	CreateProcess(fname, fnamewarg, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
-}
-
-void StartUnrealAgain(void)
-{
-	char fname[MAX_PATH], fnamewarg[MAX_PATH+32];
-	PROCESS_INFORMATION pi;
-	STARTUPINFO si;
-	
-	memset(&pi, 0, sizeof(pi));
-	memset(&si, 0, sizeof(si));
-	
-	GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
-	
-	snprintf(fnamewarg, sizeof(fnamewarg), "\"%s\"", fname);
-	CreateProcess(fname, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
-}
-
-/* Callback for the exception handler
- * Parameters:
- *  e - The exception information
- * Returns:
- *  EXCEPTION_EXECUTE_HANDLER to terminate the process
- * Side Effects:
- *  unrealircd.PID.core is created
- *  If not running in service mode, a message box is displayed, 
- *   else output is written to service.log
- */
-LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS *e) 
-{
-	MEMORYSTATUSEX memStats;
-	char file[512], text[1024], minidumpf[512];
-	FILE *fd;
-	time_t timet = time(NULL);
-#ifndef NOMINIDUMP
-	HANDLE hDump;
-	HMODULE hDll = NULL;
-#endif
-
-	sprintf(file, "unrealircd.%d.core", getpid());
-	fd = fopen(file, "w");
-	GlobalMemoryStatusEx(&memStats);
-	fprintf(fd, "Generated at %s\nOS: %s\n%s[%s%s%s] (%s) on %s\n"
-		    "-----------------\nMemory Information:\n"
-		    "\tPhysical: (Available:%lluMB/Total:%lluMB)\n"
-		    "\tVirtual: (Available:%lluMB/Total:%lluMB)\n"
-		    "-----------------\nException:\n\t%s\n-----------------\n"
-		    "Backup Buffer:\n\t%s\n-----------------\nRegisters:\n"
-		    "%s-----------------\nStack Trace:\n%s",
-		     asctime(gmtime(&timet)), OSName,
-			 IRCDTOTALVERSION,
-		     serveropts, extraflags ? extraflags : "", tainted ? "3" : "",
-		     buildid, me.name, memStats.ullAvailPhys/1048576, memStats.ullTotalPhys/1048576,
-		     memStats.ullAvailVirtual/1048576, memStats.ullTotalVirtual/1048576,
-		     GetException(e->ExceptionRecord->ExceptionCode), backupbuf,
-		     GetRegisters(e->ContextRecord), StackTrace(e));
-
-	sprintf(text, "UnrealIRCd has encountered a fatal error. Debugging information has been dumped to %s.", file);
-	fclose(fd);
-
-#ifndef NOMINIDUMP
-	hDll = LoadLibrary("DBGHELP.DLL");
-	if (hDll)
-	{
-		MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)GetProcAddress(hDll, "MiniDumpWriteDump");
-		if (pDump)
-		{
-			MINIDUMP_EXCEPTION_INFORMATION ExInfo;
-			sprintf(minidumpf, "unrealircd.%d.mdmp", getpid());
-			hDump = CreateFile(minidumpf, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-			if (hDump != INVALID_HANDLE_VALUE)
-			{
-				ExInfo.ThreadId = GetCurrentThreadId();
-				ExInfo.ExceptionPointers = e;
-				ExInfo.ClientPointers = 0;
-
-				if (pDump(GetCurrentProcess(), GetCurrentProcessId(), hDump, MiniDumpWithIndirectlyReferencedMemory, &ExInfo, NULL, NULL))
-				{
-					sprintf(text, "UnrealIRCd has encountered a fatal error. Debugging information has been dumped to %s and %s.", file, minidumpf);
-				}
-				CloseHandle(hDump);
-			}
-			sprintf(minidumpf, "unrealircd.%d.full.mdmp", getpid());
-			hDump = CreateFile(minidumpf, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-			if (hDump != INVALID_HANDLE_VALUE)
-			{
-				ExInfo.ThreadId = GetCurrentThreadId();
-				ExInfo.ExceptionPointers = e;
-				ExInfo.ClientPointers = 0;
-
-				pDump(GetCurrentProcess(), GetCurrentProcessId(), hDump, MiniDumpWithPrivateReadWriteMemory, &ExInfo, NULL, NULL);
-				CloseHandle(hDump);
-			}
-		}
-	}
-#endif
-	
-	if (!IsService)
-	{
-		MessageBox(NULL, text, "Fatal Error", MB_OK);
-		StartCrashReporter();
-	}
-	else 
-	{
-		FILE *fd = fopen("logs\\service.log", "a");
-
-		if (fd)
-		{
-			fprintf(fd, "UnrealIRCd has encountered a fatal error. Debugging information "
-					"has been dumped to unrealircd.%d.core, please file a bug and upload "
-					"this file to https://bugs.unrealircd.org/.", getpid());
-			fclose(fd);
-		}
-	}
-	CleanUp();
-	return EXCEPTION_EXECUTE_HANDLER;
-}
-
-void GotSigAbort(int signal)
-{
-	/* I just want to call ExceptionFilter() but it requires an argument which we don't have...
-	 * So just crash, which is rather silly but produces at least a crash report.
-	 * Feel free to improve this!
-	 */
-	char *crash = NULL;
-	*crash = 'X';
-}
-
-/* Initializes the exception handler */
-void InitDebug(void) 
-{
-	SetUnhandledExceptionFilter(&ExceptionFilter);
-	_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
-	signal(SIGABRT, GotSigAbort);
-}
-
-
diff --git a/src/windows/wingui.rc b/src/windows/wingui.rc
@@ -1,410 +0,0 @@
-// Microsoft Visual C++ generated resource script.
-//
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 2 resource.
-//
-#define APSTUDIO_HIDDEN_SYMBOLS
-#include "windows.h"
-#undef APSTUDIO_HIDDEN_SYMBOLS
-#include "resource.h"
-#include "winver.h"
-
-/////////////////////////////////////////////////////////////////////////////
-#undef APSTUDIO_READONLY_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-// English (United States) resources
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-#pragma code_page(1252)
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Dialog
-//
-
-UNREALIRCD DIALOGEX 0, 0, 345, 95
-STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_NOFAILCREATE | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
-FONT 8, "MS Sans Serif", 0, 0, 0x0
-BEGIN
-    CONTROL         130,IDC_STATIC,"Static",SS_BITMAP | SS_NOTIFY | SS_REALSIZEIMAGE,0,0,339,92
-    CONTROL         133,IDC_BAR,"Static",SS_BITMAP | SS_REALSIZEIMAGE,0,51,339,15
-END
-
-FROMVAR DIALOG 0, 0, 418, 183
-STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
-CAPTION "UnrealIRCd License"
-FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "OK",IDOK,184,162,50,14
-    CONTROL         "",IDC_TEXT,"RichEdit20A",ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP,8,7,402,149
-END
-
-HELP DIALOG 0, 0, 318, 94
-STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "Where to get help"
-FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "OK",IDOK,134,73,50,14
-    LTEXT           "Where to get help:",IDC_STATIC,14,11,60,8
-    LTEXT           "Point your IRC client to irc.unrealircd.org and join #unreal-support",IDC_STATIC,14,20,234,9
-    LTEXT           "Email us at",IDC_STATIC,14,30,37,8
-    CONTROL         "info@unrealircd.org",IDC_EMAIL,"Button",BS_OWNERDRAW,51,30,110,8
-    LTEXT           "Or visit us on the web at",IDC_STATIC,14,40,78,8
-    CONTROL         "https://www.unrealircd.org",IDC_URL,"Button",BS_OWNERDRAW,93,40,86,8
-    LTEXT           "Thank you for choosing UnrealIRCd :)",IDC_STATIC,98,55,121,9
-END
-
-FROMFILE DIALOG 0, 0, 418, 224
-STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
-CAPTION "UnrealIRCd Configuration File"
-FONT 8, "MS Sans Serif"
-BEGIN
-    CONTROL         "",IDC_TEXT,"RichEdit20A",ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP,0,17,418,194
-END
-
-STATUS DIALOGEX 0, 0, 318, 169
-STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "UnrealIRCd Status"
-FONT 8, "MS Sans Serif", 0, 0, 0x1
-BEGIN
-    CONTROL         "Tree1",IDC_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_DISABLEDRAGDROP | WS_BORDER | WS_TABSTOP,13,16,100,125
-    GROUPBOX        "Servers",IDC_STATIC,7,7,112,138
-    GROUPBOX        "Global Statistics",IDC_STATIC,122,7,189,78
-    LTEXT           "Clients:",IDC_STATIC,127,18,26,9
-    LTEXT           "",IDC_CLIENTS,170,18,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Servers:",IDC_STATIC,221,18,27,8
-    LTEXT           "",IDC_SERVERS,261,18,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Channels:",IDC_STATIC,127,34,32,8
-    LTEXT           "",IDC_CHANNELS,170,33,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Invisible:",IDC_STATIC,221,34,28,8
-    LTEXT           "",IDC_INVISO,261,33,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Operators:",IDC_STATIC,127,51,34,8
-    LTEXT           "",IDC_OPERS,170,51,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Unknown:",IDC_STATIC,221,51,34,8
-    LTEXT           "",IDC_UNKNOWN,261,51,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Max Clients:",IDC_STATIC,127,69,39,8
-    LTEXT           "",IDC_MAXCLIENTS,170,69,41,9,SS_SUNKEN,WS_EX_RIGHT
-    GROUPBOX        "Local Statistics",IDC_STATIC,122,90,189,55
-    PUSHBUTTON      "OK",IDOK,134,148,50,14
-    LTEXT           "Clients:",IDC_STATIC,127,100,24,8
-    LTEXT           "",IDC_LCLIENTS,170,100,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Servers:",IDC_STATIC,221,100,27,8
-    LTEXT           "",IDC_LSERVERS,261,100,41,9,SS_SUNKEN,WS_EX_RIGHT
-    LTEXT           "Max Clients:",IDC_STATIC,127,118,39,8
-    LTEXT           "",IDC_LMAXCLIENTS,170,118,41,9,SS_SUNKEN,WS_EX_RIGHT
-END
-
-CONFIGERROR DIALOG 0, 0, 331, 178
-STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION
-CAPTION "Configuration Error"
-FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "OK",IDOK,134,157,50,14
-    EDITTEXT        IDC_CONFIGERROR,7,7,317,142,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | WS_VSCROLL
-END
-
-GOTO DIALOG 0, 0, 124, 52
-STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "Goto"
-FONT 8, "MS Sans Serif"
-BEGIN
-    LTEXT           "Line Number:",IDC_STATIC,9,12,43,8
-    EDITTEXT        IDC_GOTO,56,9,48,14,ES_AUTOHSCROLL | ES_NUMBER
-    PUSHBUTTON      "Cancel",IDCANCEL,66,31,50,14
-    DEFPUSHBUTTON   "Go To",IDOK,8,31,50,14
-END
-
-COLOR DIALOG 0, 0, 156, 37
-STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "Color Selection"
-FONT 8, "MS Sans Serif"
-BEGIN
-    CONTROL         "",IDC_WHITE,"Button",BS_OWNERDRAW | WS_TABSTOP,4,3,13,12
-    CONTROL         "",IDC_BLACK,"Button",BS_OWNERDRAW | WS_TABSTOP,23,3,13,12
-    CONTROL         "",IDC_DARKBLUE,"Button",BS_OWNERDRAW | WS_TABSTOP,42,3,13,12
-    CONTROL         "",IDC_DARKGREEN,"Button",BS_OWNERDRAW | WS_TABSTOP,61,3,13,12
-    CONTROL         "",IDC_RED,"Button",BS_OWNERDRAW | WS_TABSTOP,80,3,13,12
-    CONTROL         "",IDC_DARKRED,"Button",BS_OWNERDRAW | WS_TABSTOP,99,3,13,12
-    CONTROL         "",IDC_PURPLE,"Button",BS_OWNERDRAW | WS_TABSTOP,118,3,13,12
-    CONTROL         "",IDC_ORANGE,"Button",BS_OWNERDRAW | WS_TABSTOP,137,3,13,12
-    CONTROL         "",IDC_YELLOW,"Button",BS_OWNERDRAW | WS_TABSTOP,4,21,13,12
-    CONTROL         "",IDC_GREEN,"Button",BS_OWNERDRAW | WS_TABSTOP,23,21,13,12
-    CONTROL         "",IDC_VDARKGREEN,"Button",BS_OWNERDRAW | WS_TABSTOP,42,21,13,12
-    CONTROL         "",IDC_LIGHTBLUE,"Button",BS_OWNERDRAW | WS_TABSTOP,61,21,13,12
-    CONTROL         "",IDC_BLUE,"Button",BS_OWNERDRAW | WS_TABSTOP,80,21,13,12
-    CONTROL         "",IDC_PINK,"Button",BS_OWNERDRAW | WS_TABSTOP,99,21,13,12
-    CONTROL         "",IDC_DARKGRAY,"Button",BS_OWNERDRAW | WS_TABSTOP,118,21,13,12
-    CONTROL         "",IDC_GRAY,"Button",BS_OWNERDRAW | WS_TABSTOP,137,21,13,12
-END
-
-TLSKEY DIALOG 0, 0, 174, 57
-STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "TLS Private Key Password"
-FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "OK",IDOK,33,35,50,14
-    PUSHBUTTON      "Cancel",IDCANCEL,89,35,50,14
-    EDITTEXT        IDC_PASS,11,12,152,14,ES_PASSWORD | ES_AUTOHSCROLL
-END
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// RT_MANIFEST
-//
-
-1                       RT_MANIFEST             "UnrealIRCd.exe.manifest"
-
-#ifdef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// TEXTINCLUDE
-//
-
-1 TEXTINCLUDE 
-BEGIN
-    "resource.h\0"
-END
-
-2 TEXTINCLUDE 
-BEGIN
-    "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
-    "#include ""windows.h""\r\n"
-    "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
-    "#include ""resource.h""\r\n"
-    "#include ""winver.h""\r\n"
-    "\0"
-END
-
-3 TEXTINCLUDE 
-BEGIN
-    "\r\n"
-    "\0"
-END
-
-#endif    // APSTUDIO_INVOKED
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// DESIGNINFO
-//
-
-#ifdef APSTUDIO_INVOKED
-GUIDELINES DESIGNINFO
-BEGIN
-    "UNREALIRCD", DIALOG
-    BEGIN
-        RIGHTMARGIN, 319
-        BOTTOMMARGIN, 94
-    END
-
-    "FROMVAR", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 411
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 176
-    END
-
-    "HELP", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 311
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 87
-    END
-
-    "FROMFILE", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 411
-        TOPMARGIN, 2
-        BOTTOMMARGIN, 189
-    END
-
-    "STATUS", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 311
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 162
-    END
-
-    "CONFIGERROR", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 324
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 171
-    END
-
-    "GOTO", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 117
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 45
-    END
-
-    "COLOR", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 149
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 30
-    END
-
-    "TLSKEY", DIALOG
-    BEGIN
-        LEFTMARGIN, 7
-        RIGHTMARGIN, 167
-        TOPMARGIN, 7
-        BOTTOMMARGIN, 50
-    END
-END
-#endif    // APSTUDIO_INVOKED
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VER_UNREAL VERSIONINFO
- FILEVERSION 1,0,0,0
- PRODUCTVERSION 1,0,0,0
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS 0x1L
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS 0x10004L
- FILETYPE 0x1L
- FILESUBTYPE 0x0L
-BEGIN
-    BLOCK "StringFileInfo"
-    BEGIN
-        BLOCK "040904b0"
-        BEGIN
-            VALUE "CompanyName", "none"
-            VALUE "FileVersion", "3.2"
-            VALUE "InternalName", "UnrealIRCd"
-            VALUE "ProductName", "UnrealIRCd"
-            VALUE "ProductVersion", "1.0"
-        END
-    END
-    BLOCK "VarFileInfo"
-    BEGIN
-        VALUE "Translation", 0x409, 1200
-    END
-END
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Icon
-//
-
-// Icon with lowest ID value placed first to ensure application icon
-// remains consistent on all systems.
-ICO_MAIN                ICON                    "icon1.ico"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Bitmap
-//
-
-BMP_LOGO                BITMAP                  "unreal.bmp"
-BMP_BAR                 BITMAP                  "bar.bmp"
-IDB_BITMAP1             BITMAP                  "toolbar.bmp"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Cursor
-//
-
-CUR_HAND                CURSOR                  "hand.CUR"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Menu
-//
-
-MENU_ABOUT MENU
-BEGIN
-    POPUP "About"
-    BEGIN
-        MENUITEM "UnrealIRCd &Team",            IDM_INFO
-        MENUITEM "Additional &Credits",         IDM_CREDITS
-        MENUITEM "&License",                    IDM_LICENSE
-        MENUITEM "&Help",                       IDM_HELP
-    END
-END
-
-MENU_CONFIG MENU
-BEGIN
-    POPUP "Config"
-    BEGIN
-        MENUITEM "ircd.&conf",                  IDM_CONF
-        MENUITEM SEPARATOR
-    END
-END
-
-MENU_REHASH MENU
-BEGIN
-    POPUP "Rehash"
-    BEGIN
-        MENUITEM "Rehash &All Files",           IDM_RHALL
-    END
-END
-
-MENU_SYSTRAY MENU
-BEGIN
-    POPUP "Systray"
-    BEGIN
-        MENUITEM "&Rehash",                     IDM_REHASH
-        MENUITEM "&Status",                     IDM_STATUS
-        MENUITEM "&Config",                     IDM_CONFIG
-        MENUITEM "&About",                      IDM_ABOUT
-        MENUITEM "S&hutdown",                   IDM_SHUTDOWN
-    END
-END
-
-MENU_CONTEXT MENU
-BEGIN
-    POPUP "Context"
-    BEGIN
-        MENUITEM "&Undo",                       IDM_UNDO
-        MENUITEM SEPARATOR
-        MENUITEM "Cu&t",                        IDM_CUT
-        MENUITEM "&Copy",                       IDM_COPY
-        MENUITEM "&Paste",                      IDM_PASTE
-        MENUITEM "&Delete",                     IDM_DELETE
-        MENUITEM SEPARATOR
-        MENUITEM "&Select All",                 IDM_SELECTALL
-    END
-END
-
-#endif    // English (United States) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
-
-#ifndef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 3 resource.
-//
-
-
-/////////////////////////////////////////////////////////////////////////////
-#endif    // not APSTUDIO_INVOKED
-
diff --git a/unrealircd.in b/unrealircd.in
@@ -1,340 +0,0 @@
-#!/bin/sh
-
-PID_FILE="@PIDFILE@"
-PID_BACKUP="@PIDFILE@.bak"
-BINDIR="@BINDIR@"
-UNREALIRCDCTL="$BINDIR/unrealircdctl"
-IRCD="$BINDIR/unrealircd"
-BUILDDIR="@BUILDDIR@"
-CONFDIR="@CONFDIR@"
-TMPDIR="@TMPDIR@"
-SCRIPTDIR="@SCRIPTDIR@"
-MODULESDIR="@MODULESDIR@"
-
-# When built with --with-asan, ASan does not dump core by default because
-# older gcc/clang might dump a 16TB core file. We explicitly enable it here.
-export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1:log_path=$TMPDIR/unrealircd_asan:detect_leaks=0"
-
-if [ ! -f $IRCD ]; then
-	echo "ERROR: Could not find the IRCd binary ($IRCD)"
-	echo "This could mean two things:"
-	echo "1) You forgot to run 'make install' after running 'make'"
-	echo "2) You answered a ./Config question incorrectly"
-	exit
-fi
-if [ ! -d "$TMPDIR" ]; then
-	mkdir "$TMPDIR"
-fi
-
-if [ "$1" = "start" ] ; then
-	if [ -r $PID_FILE ] ; then
-		if kill -CHLD `cat $PID_FILE` 1>/dev/null 2>&1; then
-			if $UNREALIRCDCTL status 1>/dev/null 2>&1; then
-				echo "UnrealIRCd is already running (PID `cat $PID_FILE`)."
-				echo "To restart UnrealIRCd, use: $0 restart"
-				exit 1
-			fi
-		fi
-	fi
-	if [ -r $PID_FILE ] ; then
-		mv -f $PID_FILE $PID_BACKUP
-	fi
-
-	# Check if ~/Unrealxxx/unrealircd.conf exists but the file
-	# ~/unrealircd/conf/unrealircd.conf does not.
-	# If so, then assume a user-build and give the user a nice hint...
-	if [ ! -f $CONFDIR/unrealircd.conf -a -f $BUILDDIR/unrealircd.conf ]; then
-		echo ""
-		echo "There is no unrealircd.conf in $CONFDIR"
-		echo "However I did find an unrealircd.conf in $BUILDDIR"
-		echo "With UnrealIRCd 4 you should no longer run the IRCd from $BUILDDIR."
-		echo "You should 'cd $SCRIPTDIR' and work from there."
-		echo "See https://www.unrealircd.org/docs/UnrealIRCd_files_and_directories"
-		exit 1
-	fi
-	if [ ! -f $CONFDIR/unrealircd.conf ]; then
-		echo ""
-		echo "The configuration file does not exist ($CONFDIR/unrealircd.conf)."
-		echo "Create one using the example configuration file, see the documentation:"
-		echo "https://www.unrealircd.org/docs/Installing_from_source#Creating_a_configuration_file"
-		exit 1
-	fi
-
-	echo "Starting UnrealIRCd"
-
-	$IRCD
-	if [ $? -ne 0 ] ; then
-		if [ -r $PID_BACKUP ] ; then
-			mv -f $PID_BACKUP $PID_FILE
-		fi
-		# Try to be helpful...
-		if ldd $IRCD 2>&1|grep -qF '=> not found'; then
-			echo "========================================================"
-			echo "UnrealIRCd failed to start due to missing libraries."
-			echo "Maybe you need to recompile UnrealIRCd? See"
-			echo "https://www.unrealircd.org/docs/FAQ#shared-library-error"
-			echo "========================================================"
-		else
-			echo "====================================================="
-			echo "UnrealIRCd failed to start. Check above for possible errors."
-			echo "If you don't understand the problem, then have a look at our:"
-			echo "* FAQ (Frequently Asked Questions): https://www.unrealircd.org/docs/FAQ"
-			echo "* Documentation: https://www.unrealircd.org/docs/"
-			echo "====================================================="
-		fi
-		exit 1
-	fi
-	# Now check if we need to create a crash report.
-	$IRCD -R
-elif [ "$1" = "stop" ] ; then
-	echo -n "Stopping UnrealIRCd"
-	if [ ! -r $PID_FILE ] ; then
-		echo
-		echo "ERROR: UnrealIRCd is not running"
-		exit 1
-	fi
-	kill -15 `cat $PID_FILE`
-	if [ "$?" != 0 ]; then
-		echo
-		echo "ERROR: UnrealIRCd is not running"
-		exit 1
-	fi
-	# Wait for UnrealIRCd to terminate, but wait 10 seconds max
-	n="0"
-	while [ "$n" -lt 10 ]
-	do
-		echo -n "."
-		if [ ! -r $PID_FILE ] ; then
-			break
-		fi
-		if ! kill -0 `cat $PID_FILE`; then
-			break
-		fi
-		n=`expr $n + 1`
-		sleep 1
-	done
-	echo
-	# In case it is still running, kill it for good.
-	if [ -r $PID_FILE ] ; then
-		kill -9 `cat $PID_FILE` 1>/dev/null 2>&1
-	fi
-elif [ "$1" = "rehash" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "status" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "module-status" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "reloadtls" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "restart" ] ; then
-	echo "Validating configuration..."
-	TMPF="$TMPDIR/configtest.txt"
-	if ! $0 configtest 1>$TMPF 2>&1; then
-		cat $TMPF
-		rm -f $TMPF
-		echo ""
-		echo "Configuration test failed. Server is NOT restarted."
-		exit 1
-	fi
-	echo "Configuration test OK."
-	$0 stop
-	$0 start
-elif [ "$1" = "croncheck" ] ; then
-	if [ -r $PID_FILE ] ; then
-		kill -CHLD `cat $PID_FILE` 1>/dev/null 2>&1
-		if [ "$?" = 0 ]; then
-			# IRCd is running, bail out silently.
-			exit 0
-		fi
-	fi
-	# PID file not found or found but stale
-	echo "UnrealIRCd is not running. Starting now..."
-	$0 start
-elif [ "$1" = "configtest" ] ; then
-	$IRCD -c
-elif [ "$1" = "module" ] ; then
-	shift
-	$IRCD -m $*
-elif [ "$1" = "mkpasswd" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "version" ] ; then
-	$IRCD -v
-elif [ "$1" = "gencloak" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "backtrace" ] ; then
-	cd $TMPDIR
-
-	# Find the corefile
-	echo "Core files available:"
-	n="0"
-	for i in `echo *core*`
-	do
-		ls -l $i
-		n=`expr $n + 1`
-	done
-
-	if [ "$n" -gt 1 ]; then
-		echo "Type the name of the core file you want to research:"
-		read corefile
-	elif [ "$i" = "*core*" -o "$n" -eq 0 ]; then
-		echo 'No core files found... Nothing to do'
-		echo ''
-		echo 'If you are sure UnrealIRCd crashed, then verify that unreal'
-		echo 'has permission to dump core (type "ulimit -c unlimited" and see'
-		echo 'if you get permission denied errors). Also verify that you did'
-		echo 'not run out of quota.'
-		echo 'If all that is ok, then it might be that UnrealIRCd did not crash but'
-		echo 'got killed by the OS (eg: cpu/mem resource limits), the syadmin,'
-		echo 'or an automated process.'
-		exit 1
-	else
-		corefile="$i"
-	fi
-
-	if [ ! -f "$corefile" ]; then
-		echo "Core file '$corefile' not found"
-	fi
-	if [ ! -s "$corefile" ]; then
-		echo 'Seems the corefile is 0 bytes'
-		echo 'This usually means you need to relax the core file resource limit'
-		echo '(type "ulimit -c unlimited"), or you might have ran out of quota.'
-		exit 1
-	fi
-
-	# This is needed for the script below and is probably also helpful for the
-	# bug report since you usually want to paste this to the development team.
-	export LANG=C
-	export LC_ALL=C
-
-	# The tmp/*.so files are often already deleted. Here we have some
-	# (ugly) scripting to recreate the tmp/*.so links to the modules *.so files...
-	echo 'info sharedlibrary'|gdb $IRCD $corefile 2>/dev/null|\
-	grep No|grep tmp/|awk '{ print $2 }'|\
-	awk -F '.' "{ system(\"[ -f $MODULESDIR/\" \$2 \"/\" \$3 \".so ] && ln -s $MODULESDIR/\" \$2 \"/\" \$3 \".so \" \$0 \" || ln -s $MODULESDIR/\" \$2 \".so \" \$0) }"
-	
-	echo ""
-	echo "=================== START HERE ======================"
-	echo "BACKTRACE:"
-
-cat >$TMPDIR/gdb.commands << __EOF__
-bt
-echo \n
-frame
-echo \n
-x/s backupbuf
-echo \n
-bt 3 full
-quit
-__EOF__
-
-	gdb -batch -x $TMPDIR/gdb.commands $IRCD $corefile
-	rm -f $TMPDIR/gdb.commands
-	echo "GCC: `gcc -v 2>&1|tail -n 1`"
-	echo "UNAME: `uname -a`"
-	echo "UNREAL: `$0 version`"
-	echo "CORE: `ls -al $corefile`"
-	echo "===================  STOP HERE ======================"
-	echo ""
-	echo "Copy the parts between the START HERE and STOP HERE marker"
-	echo "and report it on https://bugs.unrealircd.org/"
-	echo ""
-	echo 'But before you do, note the following:'
-	echo '1. We do not support modifications of any unrealircd code'
-	echo '   (except for config.h changes).'
-	echo '2. If you are using 3rd party modules we might request you'
-	echo '   to run without them and verify you still crash. This is'
-	echo '   to eleminate any loss of time due to bugs made by others'
-	echo '3. Use a reasonably recent UnrealIRCd version. We fix (crash)bugs'
-	echo '   all the time so your bug might as well be fixed already.'
-	echo ""
-	echo "Thanks!"
-elif [ "$1" = "spki" -o "$1" = "spkifp" ] ; then
-	$UNREALIRCDCTL $*
-elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
-	if [ ! -d "$BUILDDIR" ]; then
-		echo "UnrealIRCd source not found. Sorry, it is not possible to patch."
-		exit 1
-	fi
-	if [ "$2" = "" ]; then
-		echo "Argument required: ./unrealircd <hot-patch|cold-patch> <name-of-patch>"
-		exit 1
-	fi
-	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 'apt install wget' or a similar command."
-		exit 1
-	fi
-	cd "$BUILDDIR" || exit 1
-
-	# Weird way to get version, but ok.
-	UNREALVER="`./configure --version|head -n1|awk '{ print $3 }'`"
-	wget -O patch "https://www.unrealircd.org/patch?type=$1&patch=$2&version=$UNREALVER" || exit 1
-
-	# A patch file of 0 bytes means the patch is not needed
-	if [ -f patch -a ! -s patch ]; then
-		echo "This UnrealIRCd version does not require that patch"
-	fi
-
-	if patch --dry-run -p1 -R <patch 1>/dev/null 2>&1; then
-		echo "Patch already applied. Nothing to do."
-		exit 1
-	fi
-
-	if ! patch --dry-run -p1 -N <patch 1>/dev/null 2>&1; then
-		echo "Patch failed to apply (no files changed)"
-		exit 1
-	fi
-
-	if ! patch -p1 <patch; then
-		echo "Patch failed to apply"
-		exit 1
-	fi
-
-	echo "Patch applied successfully. Now recompiling..."
-	make || gmake || exit 1
-	make install || gmake install || exit 1
-
-	cd $SCRIPTDIR
-	if [ "$1" = "hot-patch" ]; then
-		echo "Patch applied successfully and installed. Rehashing your IRCd..."
-		if ./unrealircd rehash; then
-			echo "Patch installed and server rehashed correctly. All should be good now!"
-		else
-			echo "Patching the source code and recompiling succeeded,"
-			echo "however rehashing the current UnrealIRCd process FAILED"
-			echo "so it is NOT running the patched code yet."
-			echo "IMPORTANT: Check error output above!"
-			exit 1
-		fi
-	else
-		echo "Patch applied successfully. You must now restart your IRC server."
-	fi
-elif [ "$1" = "upgrade" ] ; then
-	$BINDIR/unrealircd-upgrade-script $*
-	exit
-elif [ "$1" = "genlinkblock" ] ; then
-	$IRCD -L
-else
-	if [ "$1" = "" ]; then
-		echo "This script expects a parameter. Use:"
-	else
-		echo "Unrecognized parameter '$1'. Use:"
-	fi
-	echo "unrealircd configtest    Test the configuration file"
-	echo "unrealircd start         Start the IRC Server"
-	echo "unrealircd stop          Stop (kill) the IRC Server"
-	echo "unrealircd rehash        Reload the configuration file"
-	echo "unrealircd reloadtls     Reload the SSL/TLS certificates"
-	echo "unrealircd restart       Restart the IRC Server (stop+start)"
-	echo "unrealircd status        Show current status of the IRC Server"
-	echo "unrealircd module-status Show all currently loaded modules"
-	echo "unrealircd upgrade       Upgrade UnrealIRCd to the latest version"
-	echo "unrealircd mkpasswd      Hash a password"
-	echo "unrealircd version       Display the UnrealIRCd version"
-	echo "unrealircd module        Install and uninstall 3rd party modules"
-	echo "unrealircd croncheck     For use in crontab: this checks if the server"
-	echo "                         is running. If not, the server is started."
-	echo "unrealircd genlinkblock  Generate link { } block for the other side."
-	echo "unrealircd gencloak      Display 3 random cloak keys"
-	echo "unrealircd spkifp        Display SPKI Fingerprint"
-fi