unrealircd

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

commit b351238d427ef0254c712ef0e07ff8ef67a1d8f5
parent 1143363444b12b2723d85115c8dc3611d465a570
Author: acidvegas <acid.vegas@acid.vegas>
Date: Thu, 26 Nov 2020 11:02:01 -0500

Updated to 5.0.7

Diffstat:
MConfig | 2+-
MMakefile.in | 2+-
Mconfigure | 20++++++++++----------
Mconfigure.ac | 4++--
Mdoc/Config.header | 2+-
Mdoc/RELEASE-NOTES.md | 57++++++++++++++++++++++++++++++++++++++++++++++++---------
Mextras/doxygen/Doxyfile | 2+-
Minclude/modules.h | 8+++++++-
Minclude/struct.h | 1-
Minclude/windows/setup.h | 2+-
Msrc/conf.c | 19++++++++++++-------
Msrc/ircsprintf.c | 49+++++++++++++++++++++++++++++--------------------
Msrc/modules/chanmodes/censor.c | 12+++++++-----
Msrc/modules/charsys.c | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/modules/extbans/textban.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/modules/hideserver.c | 1+
Msrc/modules/mode.c | 11++++++++---
Msrc/modules/opermotd.c | 2+-
Msrc/modules/pingpong.c | 12++++++++++--
Msrc/modules/reputation.c | 30++++++++++++++++++++++--------
Msrc/modules/sapart.c | 4++--
Msrc/modules/usermodes/censor.c | 13+++++++------
Msrc/modules/websocket.c | 126++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/modules/whox.c | 38+++++++++++++++++++-------------------
Msrc/tls.c | 50++++++++++----------------------------------------
Msrc/version.c.SH | 2+-
Msrc/windows/UnrealIRCd.exe.manifest | 2+-
Msrc/windows/unrealinst.iss | 2+-

28 files changed, 414 insertions(+), 186 deletions(-)

diff --git a/Config b/Config
@@ -326,7 +326,7 @@ echo "We will now ask you a number of questions. You can just press ENTER to acc
 echo ""
 
 # This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-UNREALRELEASES="unrealircd-5.0.5.1 unrealircd-5.0.5 unrealircd-5.0.4 unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0 unrealircd-5.0.0-rc2 unrealircd-5.0.0-rc1"
+UNREALRELEASES="unrealircd-5.0.7-rc1 unrealircd-5.0.6 unrealircd-5.0.5.1 unrealircd-5.0.5 unrealircd-5.0.4 unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0 unrealircd-5.0.0-rc2 unrealircd-5.0.0-rc1"
 if [ -f "config.settings" ]; then
 	. ./config.settings
 else
diff --git a/Makefile.in b/Makefile.in
@@ -171,7 +171,7 @@ install: all
 	$(INSTALL) -m 0700 -d @CONFDIR@
 	$(INSTALL) -m 0600 doc/conf/*.conf @CONFDIR@
 	$(INSTALL) -m 0600 doc/conf/*.motd @CONFDIR@
-	$(INSTALL) -m 0600 doc/conf/modules.sources.list @CONFDIR@
+	$(INSTALL) -m 0600 doc/conf/modules.sources.list @CONFDIR@ ; \
 	$(INSTALL) -m 0700 unrealircd @SCRIPTDIR@
 	$(INSTALL) -m 0700 -d @MODULESDIR@
 	@rm -f @MODULESDIR@/*.so 1>/dev/null 2>&1
diff --git a/configure b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for unrealircd 5.0.6.
+# Generated by GNU Autoconf 2.69 for unrealircd 5.0.7.
 #
 # Report bugs to <https://bugs.unrealircd.org/>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unrealircd'
 PACKAGE_TARNAME='unrealircd'
-PACKAGE_VERSION='5.0.6'
-PACKAGE_STRING='unrealircd 5.0.6'
+PACKAGE_VERSION='5.0.7'
+PACKAGE_STRING='unrealircd 5.0.7'
 PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
 PACKAGE_URL='https://unrealircd.org/'
 
@@ -1325,7 +1325,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures unrealircd 5.0.6 to adapt to many kinds of systems.
+\`configure' configures unrealircd 5.0.7 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1391,7 +1391,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unrealircd 5.0.6:";;
+     short | recursive ) echo "Configuration of unrealircd 5.0.7:";;
    esac
   cat <<\_ACEOF
 
@@ -1544,7 +1544,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unrealircd configure 5.0.6
+unrealircd configure 5.0.7
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1913,7 +1913,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by unrealircd $as_me 5.0.6, which was
+It was created by unrealircd $as_me 5.0.7, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2321,7 +2321,7 @@ _ACEOF
 
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR="6"
+UNREAL_VERSION_MINOR="7"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -8398,7 +8398,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by unrealircd $as_me 5.0.6, which was
+This file was extended by unrealircd $as_me 5.0.7, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -8461,7 +8461,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-unrealircd config.status 5.0.6
+unrealircd config.status 5.0.7
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
@@ -7,7 +7,7 @@ dnl src/windows/unrealinst.iss
 dnl doc/Config.header
 dnl src/version.c.SH
 
-AC_INIT([unrealircd], [5.0.6], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [5.0.7], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
 AC_CONFIG_SRCDIR([src/ircd.c])
 AC_CONFIG_HEADER([include/setup.h])
 AC_CONFIG_AUX_DIR([autoconf])
@@ -34,7 +34,7 @@ UNREAL_VERSION_MAJOR=["0"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MAJOR], [$UNREAL_VERSION_MAJOR], [Major version number (e.g.: Y for X.Y.Z)])
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR=["6"]
+UNREAL_VERSION_MINOR=["7"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MINOR], [$UNREAL_VERSION_MINOR], [Minor version number (e.g.: Z for X.Y.Z)])
 
 # The version suffix such as a beta marker or release candidate
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,7 +7,7 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 5.0.6
+                                for UnrealIRCd 5.0.7
                                     
 This program will help you to compile your IRC server, and ask you
 questions regarding the compile-time settings of it during the process. 
diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md
@@ -1,6 +1,53 @@
-UnrealIRCd 5.0.6 Release Notes
+UnrealIRCd 5.0.7 Release Notes
 ===============================
 
+UnrealIRCd 5.0.7 consists mainly of fixes for the 5.x stable series,
+with some minor enhancements.
+
+Enhancements:
+* Add support for ```estonian-utf8```, ```latvian-utf8``` and
+  ```lithuanian-utf8``` in
+  [set::allowed-nickchars](https://www.unrealircd.org/docs/Nick_Character_Sets)
+* Add [message tags](https://www.unrealircd.org/docs/Message_tags)
+  to ```PONG``` to help fix timestamp issues in KiwiIRC.
+* Dutch helpop file (conf/help/help.nl.conf)
+
+Fixes:
+* When having multiple text bans (```+b ~T:censor```), these caused an empty
+  message.
+* Text bans are now no longer bypassed by voiced users (```+v```).
+* [Websockets](https://www.unrealircd.org/docs/WebSocket_support) that used
+```labeled-response``` sometimes received multiple IRC messages in one
+websocket packet.
+* The reputation score of [WEBIRC users](https://www.unrealircd.org/docs/WebIRC_block)
+  was previously the score of the WEBIRC IP rather than the end-user IP.
+* ```STATS badword``` was not working.
+* When setting a very high channel limit, it showed a weird MODE ```+l``` value.
+* The ```LINKS``` command worked, even when disabled via
+  ```hideserver::disable-links``` in the optional hideserver module.
+* In some cases ```WHO``` did not show your own entry, such as when
+  searching on account name, which was confusing.
+* Memory leak when repeatedly using ```./unrealircd reloadtls``` or
+  ```/REHASH -tls```.
+
+Module coders / Developers:
+* No changes, only some small additions to the
+[Doxygen module API docs](https://www.unrealircd.org/api/5/index.html)
+
+Reminder: UnrealIRCd 4 is End Of Life
+---------------------------------------
+
+All support for the previous series, UnrealIRCd 4.x, will stop after
+[December 31, 2020](https://www.unrealircd.org/docs/UnrealIRCd_4_EOL).
+If you haven't upgraded yet, do so soon!
+
+Upgrading from 4.x to 5.x?
+Then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5). At the
+very least, check out [Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
+
+UnrealIRCd 5.0.6
+-----------------
+
 UnrealIRCd 5.0.6 is a small maintenance release for the stable 5.x series.
 For existing 5.x users there is probably little reason to upgrade.
 
@@ -29,14 +76,6 @@ Module coders / Developers:
 * Fix double batch in message tags when using both labeled-response
   and the ```HISTORY``` command
 
-Upgrading from UnrealIRCd 4?
------------------------------
-
-Are you upgrading from UnrealIRCd 4.x to UnrealIRCd 5?
-Then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5). At the
-very least, check out [Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
-
-
 UnrealIRCd 5.0.5.1
 -------------------
 
diff --git a/extras/doxygen/Doxyfile b/extras/doxygen/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME           = "UnrealIRCd"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 5.0.6
+PROJECT_NUMBER         = 5.0.7
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/include/modules.h b/include/modules.h
@@ -353,7 +353,13 @@ typedef struct {
 
 #define EXTBANTABLESZ		32
 
-typedef enum ExtbanOptions { EXTBOPT_CHSVSMODE=0x1, EXTBOPT_ACTMODIFIER=0x2, EXTBOPT_NOSTACKCHILD=0x4, EXTBOPT_INVEX=0x8, EXTBOPT_TKL=0x10 } ExtbanOptions;
+typedef enum ExtbanOptions {
+        EXTBOPT_CHSVSMODE=0x1,		/**< SVSMODE -b/-e/-I will clear this ban */
+        EXTBOPT_ACTMODIFIER=0x2,	/**< Action modifier (not a matcher). These are extended bans like ~q/~n/~j. */
+        EXTBOPT_NOSTACKCHILD=0x4,	/**< Disallow prefixing with another extban. Eg disallow ~n:~T:censor:xyz */
+        EXTBOPT_INVEX=0x8,		/**< Available for use with +I too */
+        EXTBOPT_TKL=0x10		/**< Available for use in TKL's too (eg: /GLINE ~a:account) */
+} ExtbanOptions;
 
 typedef struct {
 	/** extbans module */
diff --git a/include/struct.h b/include/struct.h
@@ -1464,7 +1464,6 @@ typedef struct TLSOptions TLSOptions;
 struct TLSOptions {
 	char *certificate_file;
 	char *key_file;
-	char *dh_file;
 	char *trusted_ca_file;
 	unsigned int protocols;
 	char *ciphers;
diff --git a/include/windows/setup.h b/include/windows/setup.h
@@ -63,7 +63,7 @@
 #define UNREAL_VERSION_MAJOR 0
 
 /* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 6
+#define UNREAL_VERSION_MINOR 7
 
 /* Version suffix such as a beta marker or release candidate marker. (e.g.:
    -rcX for unrealircd-3.2.9-rcX) */
diff --git a/src/conf.c b/src/conf.c
@@ -1672,6 +1672,7 @@ void config_setdefaultsettings(Configuration *i)
 	i->maxdccallow = 10;
 	safe_strdup(i->channel_command_prefix, "`!.");
 	conf_channelmodes("+nt", &i->modes_on_join, 0);
+	i->conn_modes = set_usermode("+ixw");
 	i->check_target_nick_bans = 1;
 	i->maxbans = 60;
 	i->maxbanlength = 2048;
@@ -6994,7 +6995,6 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			}
 		}
 		else if (!strcmp(cepp->ce_varname, "certificate") ||
-		         !strcmp(cepp->ce_varname, "dh") ||
 		         !strcmp(cepp->ce_varname, "key") ||
 		         !strcmp(cepp->ce_varname, "trusted-ca-file"))
 		{
@@ -7011,6 +7011,17 @@ void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
 			}
 			safe_free(path);
 		}
+		else if (!strcmp(cepp->ce_varname, "dh"))
+		{
+			/* Support for this undocumented option was silently dropped in 5.0.0.
+			 * Since 5.0.7 we print a warning about it, since you never know
+			 * someone may still have it configured. -- Syzop
+			 */
+			config_warn("%s:%d: Not reading DH file '%s'. UnrealIRCd does not support old DH(E), we use modern ECDHE/EECDH. "
+			            "Just remove the 'dh' directive from your config file to get rid of this warning.",
+				cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
+				cepp->ce_vardata ? cepp->ce_vardata : "");
+		}
 		else if (!strcmp(cepp->ce_varname, "outdated-protocols"))
 		{
 			char copy[512], *p, *name;
@@ -7131,7 +7142,6 @@ void free_tls_options(TLSOptions *tlsoptions)
 
 	safe_free(tlsoptions->certificate_file);
 	safe_free(tlsoptions->key_file);
-	safe_free(tlsoptions->dh_file);
 	safe_free(tlsoptions->trusted_ca_file);
 	safe_free(tlsoptions->ciphers);
 	safe_free(tlsoptions->ciphersuites);
@@ -7152,7 +7162,6 @@ void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions)
 	{
 		safe_strdup(tlsoptions->certificate_file, tempiConf.tls_options->certificate_file);
 		safe_strdup(tlsoptions->key_file, tempiConf.tls_options->key_file);
-		safe_strdup(tlsoptions->dh_file, tempiConf.tls_options->dh_file);
 		safe_strdup(tlsoptions->trusted_ca_file, tempiConf.tls_options->trusted_ca_file);
 		tlsoptions->protocols = tempiConf.tls_options->protocols;
 		safe_strdup(tlsoptions->ciphers, tempiConf.tls_options->ciphers);
@@ -7224,10 +7233,6 @@ void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions)
 				}
 			}
 		}
-		else if (!strcmp(cepp->ce_varname, "dh"))
-		{
-			convert_to_absolute_path(&cepp->ce_vardata, CONFDIR);
-		}
 		else if (!strcmp(cepp->ce_varname, "certificate"))
 		{
 			convert_to_absolute_path(&cepp->ce_vardata, CONFDIR);
diff --git a/src/ircsprintf.c b/src/ircsprintf.c
@@ -67,23 +67,27 @@ char *ircvsnprintf(char *str, size_t size, const char *format, va_list vl)
 				int v = va_arg(vl, int);
 				int i = 0;
 				size_t len;
-				if (v<0)
-				{
-					v*=-1;
-					*str++ = '-';
-					if (str==end) break;
-				}
 				if (v==0)
 				{
 					*str++ = '0';
 					continue;
 				}
-
 				t = scratch_buffer + sizeof(scratch_buffer);
-				while (v)
+				if (v<0)
 				{
-					*--t = (v%10) + '0';
-					v/=10;
+					*str++ = '-';
+					if (str==end) break;
+					while (v)
+					{
+						*--t = '0' - (v%10);
+						v/=10;
+					}
+				} else {
+					while (v)
+					{
+						*--t = (v%10) + '0';
+						v/=10;
+					}
 				}
 
 				len = sizeof(scratch_buffer)-(t-scratch_buffer);
@@ -103,23 +107,28 @@ char *ircvsnprintf(char *str, size_t size, const char *format, va_list vl)
 					size_t len;
 
 					format += 2;
-					if (v<0)
-					{
-						v*=-1;
-						*str++ = '-';
-						if (str==end) break;
-					}
+
 					if (v==0)
 					{
 						*str++ = '0';
 						continue;
 					}
-
 					t = scratch_buffer + sizeof(scratch_buffer);
-					while (v)
+					if (v<0)
 					{
-						*--t = (v%10) + '0';
-						v/=10;
+						*str++ = '-';
+						if (str==end) break;
+						while (v)
+						{
+							*--t = '0' - (v%10);
+							v/=10;
+						}
+					} else {
+						while (v)
+						{
+							*--t = (v%10) + '0';
+							v/=10;
+						}
 					}
 
 					len = sizeof(scratch_buffer)-(t-scratch_buffer);
diff --git a/src/modules/chanmodes/censor.c b/src/modules/chanmodes/censor.c
@@ -23,7 +23,7 @@ Cmode_t EXTMODE_CENSOR = 0L;
 int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 char *censor_pre_local_part(Client *client, Channel *channel, char *text);
 char *censor_pre_local_quit(Client *client, char *text);
-
+int censor_stats_badwords_channel(Client *client, char *para);
 int censor_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int censor_config_run(ConfigFile *, ConfigEntry *, int);
 
@@ -55,7 +55,7 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, censor_can_send_to_channel);
 	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, 0, censor_pre_local_part);
 	HookAddPChar(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, 0, censor_pre_local_quit);
-	
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, censor_stats_badwords_channel);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, censor_config_run);
 	return MOD_SUCCESS;
 }
@@ -319,11 +319,13 @@ char *censor_pre_local_quit(Client *client, char *text)
 	return blocked ? NULL : text;
 }
 
-// TODO: when stats is modular, make it call this for badwords
-int stats_badwords(Client *client, char *para)
+int censor_stats_badwords_channel(Client *client, char *para)
 {
 	ConfigItem_badword *words;
 
+	if (!para || !(!strcmp(para, "b") || !strcasecmp(para, "badword")))
+		return 0;
+
 	for (words = conf_badword_channel; words; words = words->next)
 	{
 		sendtxtnumeric(client, "c %c %s%s%s %s", words->type & BADW_TYPE_REGEX ? 'R' : 'F',
@@ -331,5 +333,5 @@ int stats_badwords(Client *client, char *para)
 		           (words->type & BADW_TYPE_FAST_R) ? "*" : "",
 		           words->action == BADWORD_REPLACE ? (words->replace ? words->replace : "<censored>") : "");
 	}
-	return 0;
+	return 1;
 }
diff --git a/src/modules/charsys.c b/src/modules/charsys.c
@@ -99,6 +99,7 @@ static LangList langlist[] = {
 	{ "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 },
@@ -117,6 +118,8 @@ static LangList langlist[] = {
 	{ "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 },
@@ -257,7 +260,7 @@ int charsys_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 
 	if (type != CONFIG_SET)
 		return 0;
-	
+
 	/* We are only interrested in set::allowed-nickchars... */
 	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "allowed-nickchars"))
 		return 0;
@@ -408,7 +411,7 @@ void charsys_finish(void)
 		if (e->next)
 			strlcat(langsinuse, ",", sizeof(langsinuse));
 	}
-	
+
 	/* Free everything */
 	for (e=ilanglist; e; e=e_next)
 	{
@@ -683,7 +686,7 @@ void charsys_add_language(char *name)
 		w1251 = 1;
 	else if (!strcmp(name, "chinese") || !strcmp(name, "gbk"))
 		chinese = 1;
-	
+
 	/* INDIVIDUAL CHARSETS */
 
 	/* [LATIN1] and [LATIN-UTF8] */
@@ -840,7 +843,7 @@ void charsys_add_language(char *name)
 	if (latin1 || !strcmp(name, "swedish"))
 	{
 		/* supplied by Tank */
-		/* ao, Ao, a", A", o", O" */ 
+		/* ao, Ao, a", A", o", O" */
 		charsys_addallowed("åÅäÄöÖ");
 	}
 	if (latin_utf8 || !strcmp(name, "swedish-utf8"))
@@ -926,7 +929,7 @@ void charsys_add_language(char *name)
 		charsys_addmultibyterange(0xc5, 0xc5, 0x9e, 0x9f);
 		charsys_addmultibyterange(0xc5, 0xc5, 0xa2, 0xa3);
 	}
-	
+
 	if (latin2 || !strcmp(name, "polish"))
 	{
 		/* supplied by k4be */
@@ -1019,7 +1022,7 @@ void charsys_add_language(char *name)
 		charsys_addmultibyterange(0xd1, 0xd1, 0x80, 0x8f);
 		charsys_addmultibyterange(0xd1, 0xd1, 0x91, 0x91);
 	}
-	
+
 	if (w1251 || !strcmp(name, "belarussian-w1251"))
 	{
 		/* supplied by Bock (Samets Anton) & ss:
@@ -1043,7 +1046,7 @@ void charsys_add_language(char *name)
 		charsys_addmultibyterange(0xd1, 0xd1, 0x96, 0x96);
 		charsys_addmultibyterange(0xd1, 0xd1, 0x9e, 0x9e);
 	}
-	
+
 	if (w1251 || !strcmp(name, "ukrainian-w1251"))
 	{
 		/* supplied by Anton Samets & ss:
@@ -1067,7 +1070,7 @@ void charsys_add_language(char *name)
 		charsys_addmultibyterange(0xd2, 0xd2, 0x90, 0x91);
 	}
 
-	/* [GREEK] */	
+	/* [GREEK] */
 	if (!strcmp(name, "greek"))
 	{
 		/* supplied by GSF */
@@ -1135,6 +1138,49 @@ void charsys_add_language(char *name)
 		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);
+	}
 }
 
 /** This displays all the nick characters that are permitted */
@@ -1190,7 +1236,7 @@ char *charsys_displaychars(void)
 	}
 
 	buf[n] = '\0'; /* there's always room for a NUL */
-	
+
 	return buf;
 }
 
@@ -1204,7 +1250,7 @@ char *charsys_group(int v)
 		return "Greek script";
 	if (v & LANGAV_HEBREW_UTF8)
 		return "Hebrew script";
-	
+
 	return "Other";
 }
 
@@ -1215,7 +1261,7 @@ void charsys_dump_table(char *filter)
 	for (i = 0; langlist[i].directive; i++)
 	{
 		char *charset = langlist[i].directive;
-		
+
 		if (!match_simple(filter, charset))
 			continue; /* skip */
 
diff --git a/src/modules/extbans/textban.c b/src/modules/extbans/textban.c
@@ -81,6 +81,8 @@ ModuleHeader MOD_HEADER
 
 /* Forward declarations */
 char *extban_modeT_conv_param(char *para_in);
+int textban_check_ban(Client *client, Channel *channel, char *ban, char **msg, char **errmsg);
+int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 int extban_modeT_is_banned(Client *client, Channel *channel, char *ban, int type, char **msg, char **errmsg);
 int extban_modeT_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2);
 void parse_word(const char *s, char **word, int *type);
@@ -93,6 +95,7 @@ MOD_INIT()
 
 	memset(&req, 0, sizeof(ExtbanInfo));
 	req.flag = 'T';
+	req.options = EXTBOPT_NOSTACKCHILD; /* disallow things like ~n:~T, as we only affect text. */
 	req.conv_param = extban_modeT_conv_param;
 	req.is_banned = extban_modeT_is_banned;
 	req.is_ok = extban_modeT_is_ok;
@@ -103,6 +106,8 @@ MOD_INIT()
 		return MOD_FAILED;
 	}
 
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, textban_can_send_to_channel);
+
 	return MOD_SUCCESS;
 }
 
@@ -382,9 +387,54 @@ char *extban_modeT_conv_param(char *para_in)
 	return retbuf;
 }
 
+/** This is the regular "is banned?" routine. We can't use this as we need to be called for voiced users as well */
 int extban_modeT_is_banned(Client *client, Channel *channel, char *ban, int checktype, char **msg, char **errmsg)
 {
-	static char filtered[512]; /* temp buffer */
+	return 0;
+}
+
+/** Check for text bans (censor and block) */
+int textban_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+{
+	Ban *ban;
+
+	/* +h/+o/+a/+q users bypass textbans */
+	if (is_skochanop(client, channel))
+		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)
+	{
+		if (!strncmp(ban->banstr, "~T:", 3))
+		{
+			/* ~T ban */
+			if (textban_check_ban(client, channel, ban->banstr, msg, errmsg))
+				return HOOK_DENY;
+		} else
+		if (!strncmp(ban->banstr, "~t:", 3))
+		{
+			/* Stacked ~t:xx:~T ban (timed text ban) */
+			char *p = strchr(ban->banstr+3, ':');
+			if (p && !strncmp(p+1, "~T:", 3))
+			{
+				if (textban_check_ban(client, channel, p+1, msg, errmsg))
+					return HOOK_DENY;
+			}
+		}
+	}
+
+	return HOOK_CONTINUE;
+}
+
+
+int textban_check_ban(Client *client, Channel *channel, char *ban, char **msg, char **errmsg)
+{
+	static char retbuf[512];
+	char filtered[512]; /* temp input buffer */
 	long fl;
 	int cleaned=0;
 	char *p;
@@ -399,8 +449,8 @@ int extban_modeT_is_banned(Client *client, Channel *channel, char *ban, int chec
 	gettimeofday(&tv_alpha, NULL);
 #endif
 
-	/* We only filter on BANCHK_MSG, and we can only filter on non-NULL text of course */
-	if ((checktype != BANCHK_MSG) || (msg == NULL) || (*msg == NULL))
+	/* We can only filter on non-NULL text of course */
+	if ((msg == NULL) || (*msg == NULL))
 		return 0;
 
 	filtered[0] = '\0'; /* NOT needed, but... :P */
@@ -461,7 +511,8 @@ int extban_modeT_is_banned(Client *client, Channel *channel, char *ban, int chec
 		{
 			if (*p != ' ')
 			{
-				*msg = filtered;
+				strlcpy(retbuf, filtered, sizeof(retbuf));
+				*msg = retbuf;
 				return 0; /* allow through, but filtered */
 			}
 		}
diff --git a/src/modules/hideserver.c b/src/modules/hideserver.c
@@ -374,6 +374,7 @@ CMD_OVERRIDE_FUNC(override_links)
 			sendnotice(client, "%s", Settings.links_deny_message);
 		else
 			sendnumeric(client, RPL_ENDOFLINKS, "*");
+		return;
 	}
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
diff --git a/src/modules/mode.c b/src/modules/mode.c
@@ -986,11 +986,16 @@ process_listmode:
 		case MODE_LIMIT:
 			if (what == MODE_ADD)
 			{
+				int v;
 				REQUIRE_PARAMETER()
-				tmp = atoi(param);
-				if (channel->mode.limit == tmp)
+				v = atoi(param);
+				if (v < 0)
+					v = 1; /* setting +l with a negative number makes no sense */
+				if (v > 1000000000)
+					v = 1000000000; /* some kind of limit, 1 billion (mrah...) */
+				if (channel->mode.limit == v)
 					break;
-				channel->mode.limit = tmp;
+				channel->mode.limit = v;
 			}
 			else
 			{
diff --git a/src/modules/opermotd.c b/src/modules/opermotd.c
@@ -88,5 +88,5 @@ CMD_FUNC(cmd_opermotd)
 			   motdline->line);
 		motdline = motdline->next;
 	}
-	sendnumeric(client, RPL_ENDOFMOTD);
+	sendnumericfmt(client, RPL_ENDOFMOTD, ":End of /OPERMOTD command.");
 }
diff --git a/src/modules/pingpong.c b/src/modules/pingpong.c
@@ -93,8 +93,13 @@ CMD_FUNC(cmd_ping)
 		}
 	}
 	else
-		sendto_one(client, NULL, ":%s PONG %s :%s", me.name,
+	{
+		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);
+	}
 }
 
 /*
@@ -188,7 +193,10 @@ CMD_FUNC(cmd_pong)
 				return;
 			} else
 			{
-				sendto_one(target, NULL, ":%s PONG %s %s", client->name, origin, destination);
+				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
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
@@ -480,16 +480,12 @@ ReputationEntry *find_reputation_entry(char *ip)
 	return NULL;
 }
 
-/** 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)
+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);
@@ -498,7 +494,17 @@ int reputation_set_on_connect(Client *client)
 			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;
 }
 
@@ -507,9 +513,17 @@ 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).
 	 */
-	ReputationEntry *e = find_reputation_entry(GetIP(client));
-	sendto_server(NULL, 0, 0, NULL, ":%s REPUTATION %s %d", me.id, GetIP(client), e ? (int)e->score : 0);
+	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;
 }
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -161,7 +161,7 @@ CMD_FUNC(cmd_sapart)
 	parv[2] = comment ? commentx : NULL; // comment
 	if (comment)
 	{
-		sendnotice(target, "*** You were forced to part %s (%s)", parv[1], commentx);
+		//sendnotice(target, "*** You were forced to part %s (%s)", parv[1], commentx);
 		sendto_umode_global(UMODE_OPER, "%s used SAPART to make %s part %s (%s)",
 				    client->name, target->name, parv[1], comment);
 		ircd_log(LOG_SACMDS,"SAPART: %s used SAPART to make %s part %s (%s)",
@@ -169,7 +169,7 @@ CMD_FUNC(cmd_sapart)
 	}
 	else
 	{
-		sendnotice(target, "*** You were forced to part %s", parv[1]);
+		//sendnotice(target, "*** You were forced to part %s", parv[1]);
 		sendto_umode_global(UMODE_OPER, "%s used SAPART to make %s part %s",
 				    client->name, target->name, parv[1]);
 		ircd_log(LOG_SACMDS,"SAPART: %s used SAPART to make %s part %s",
diff --git a/src/modules/usermodes/censor.c b/src/modules/usermodes/censor.c
@@ -31,6 +31,7 @@ ConfigItem_badword *conf_badword_message = NULL;
 
 static ConfigItem_badword *copy_badword_struct(ConfigItem_badword *ca, int regex, int regflags);
 
+int censor_stats_badwords_user(Client *client, char *para);
 
 MOD_TEST()
 {
@@ -43,11 +44,9 @@ 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;
 }
@@ -254,11 +253,13 @@ int censor_can_send_to_user(Client *client, Client *target, char **text, char **
 	return HOOK_CONTINUE;
 }
 
-// TODO: when stats is modular, make it call this for badwords
-int stats_badwords(Client *client, char *para)
+int censor_stats_badwords_user(Client *client, 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',
@@ -266,5 +267,5 @@ int stats_badwords(Client *client, char *para)
 		           (words->type & BADW_TYPE_FAST_R) ? "*" : "",
 		           words->action == BADWORD_REPLACE ? (words->replace ? words->replace : "<censored>") : "");
 	}
-	return 0;
+	return 1;
 }
diff --git a/src/modules/websocket.c b/src/modules/websocket.c
@@ -8,7 +8,7 @@
 #include "unrealircd.h"
 #include <limits.h>
 
-#define WEBSOCKET_VERSION "1.0.0"
+#define WEBSOCKET_VERSION "1.1.0"
 
 ModuleHeader MOD_HEADER
   = {
@@ -23,10 +23,14 @@ ModuleHeader MOD_HEADER
  #error "In UnrealIRCd char should always be unsigned. Check your compiler"
 #endif
 
+#ifndef WEBSOCKET_SEND_BUFFER_SIZE
+ #define WEBSOCKET_SEND_BUFFER_SIZE 16384
+#endif
+
 typedef struct WebSocketUser WebSocketUser;
 struct WebSocketUser {
 	char get; /**< GET initiated */
-	char handshake_completed; /**< Handshake completed, use data frames */
+	char handshake_completed; /**< Handshake completed, use websocket frames */
 	char *handshake_key; /**< Handshake key (used during handshake) */
 	char *lefttoparse; /**< Leftover buffer to parse */
 	int lefttoparselen; /**< Length of lefttoparse buffer */
@@ -59,8 +63,8 @@ int websocket_handle_handshake(Client *client, char *readbuf, int *length);
 int websocket_complete_handshake(Client *client);
 int websocket_handle_packet_ping(Client *client, char *buf, int len);
 int websocket_handle_packet_pong(Client *client, char *buf, int len);
-int websocket_create_frame(int opcode, char **buf, int *len);
-int websocket_send_frame(Client *client, int opcode, char *buf, int len);
+int websocket_create_packet(int opcode, char **buf, int *len);
+int websocket_send_pong(Client *client, char *buf, int len);
 
 /* Global variables */
 ModDataInfo *websocket_md;
@@ -231,14 +235,14 @@ int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **m
 	if (MyConnect(to) && WSU(to) && WSU(to)->handshake_completed)
 	{
 		if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
-			websocket_create_frame(WSOP_BINARY, msg, length);
+			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);
 			*msg = safe_msg;
 			*length = *msg ? strlen(safe_msg) : 0;
-			websocket_create_frame(WSOP_TEXT, msg, length);
+			websocket_create_packet(WSOP_TEXT, msg, length);
 		}
 		return 0;
 	}
@@ -291,7 +295,7 @@ int websocket_handle_websocket(Client *client, char *readbuf2, int length2)
 }
 
 /** Incoming packet hook.
- * This processes Websocket frames, if this is a websocket connection.
+ * This processes websocket frames, if this is a websocket connection.
  * NOTE The different return values:
  * -1 means: don't touch this client anymore, it has or might have been killed!
  * 0 means: don't process this data, but you can read another packet if you want
@@ -712,7 +716,7 @@ int websocket_handle_packet_ping(Client *client, char *buf, int len)
 		dead_socket(client, "WebSocket: oversized PING request");
 		return -1;
 	}
-	websocket_send_frame(client, WSOP_PONG, buf, len);
+	websocket_send_pong(client, buf, len);
 	client->local->since++; /* lag penalty of 1 second */
 	return 0;
 }
@@ -723,28 +727,18 @@ int websocket_handle_packet_pong(Client *client, char *buf, int len)
 	return 0;
 }
 
-/** Create a frame. Used for OUTGOING data. */
-int websocket_create_frame(int opcode, char **buf, int *len)
+/** Create a simple websocket packet that is ready to be send.
+ * This is the simple version that is used ONLY for WSOP_PONG,
+ * as it does not take \r\n into account.
+ */
+int websocket_create_packet_simple(int opcode, char **buf, int *len)
 {
 	static char sendbuf[8192];
 
 	sendbuf[0] = opcode | 0x80; /* opcode & final */
 
 	if (*len > sizeof(sendbuf) - 8)
-		abort(); /* should never happen (safety) */
-
-	/* strip LF */
-	if (*len > 0)
-	{
-		if (*(*buf + *len - 1) == '\n')
-			*len = *len - 1;
-	}
-	/* strip CR */
-	if (*len > 0)
-	{
-		if (*(*buf + *len - 1) == '\r')
-			*len = *len - 1;
-	}
+		return -1; /* should never happen (safety) */
 
 	if (*len < 126)
 	{
@@ -765,13 +759,91 @@ int websocket_create_frame(int opcode, char **buf, int *len)
 	return 0;
 }
 
-/** Create and send a frame */
-int websocket_send_frame(Client *client, int opcode, char *buf, int len)
+/** Create a websocket packet that is ready to be send.
+ * This is the more complex version that takes into account
+ * stripping off \r and \n, and possibly multi line due to
+ * labeled-response. It is used for WSOP_TEXT and WSOP_BINARY.
+ * The end result is one or more websocket frames,
+ * all in a single packet *buf with size *len.
+ */
+int websocket_create_packet(int opcode, char **buf, int *len)
+{
+	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
+	char *s = *buf; /* points to start of current line */
+	char *s2; /* used for searching of end of current line */
+	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
+	int bytes_to_copy;
+	char newline;
+	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
+	int bytes_in_sendbuf = 0;
+	int bytes_single_frame;
+
+	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
+	if (*len == 0)
+		return -1;
+
+	do {
+		/* Find next \r or \n */
+		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
+		{
+			if ((*s2 == '\n') || (*s2 == '\r'))
+				break;
+		}
+
+		/* Now 's' points to start of line and 's2' points to beyond end of the line
+		 * (either at \r, \n or beyond the buffer).
+		 */
+		bytes_to_copy = s2 - s;
+
+		if (bytes_to_copy < 126)
+			bytes_single_frame = 2 + bytes_to_copy;
+		else
+			bytes_single_frame = 4 + bytes_to_copy;
+
+		if (bytes_in_sendbuf + bytes_single_frame > sizeof(sendbuf))
+		{
+			/* Overflow. This should never happen. */
+			sendto_ops("[websocket] [BUG] Overflow prevented: %d + %d > %d",
+				bytes_in_sendbuf, bytes_single_frame, (int)sizeof(sendbuf));
+			return -1;
+		}
+
+		/* Create the new frame */
+		o[0] = opcode | 0x80; /* opcode & final */
+
+		if (bytes_to_copy < 126)
+		{
+			/* Short payload */
+			o[1] = (char)bytes_to_copy;
+			memcpy(&o[2], s, bytes_to_copy);
+		} else {
+			/* Long payload */
+			o[1] = 126;
+			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
+			o[3] = (char)(bytes_to_copy & 0xFF);
+			memcpy(&o[4], s, bytes_to_copy);
+		}
+
+		/* Advance destination pointer and counter */
+		o += bytes_single_frame;
+		bytes_in_sendbuf += bytes_single_frame;
+
+		/* Advance source pointer and skip all trailing \n and \r */
+		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
+	} while(s <= lastbyte);
+
+	*buf = sendbuf;
+	*len = bytes_in_sendbuf;
+	return 0;
+}
+
+/** Create and send a WSOP_PONG frame */
+int websocket_send_pong(Client *client, char *buf, int len)
 {
 	char *b = buf;
 	int l = len;
 
-	if (websocket_create_frame(opcode, &b, &l) < 0)
+	if (websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
 		return -1;
 
 	if (DBufLength(&client->local->sendQ) > get_sendq(client))
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -497,8 +497,10 @@ static int do_match(Client *client, Client *acptr, char *mask, struct who_format
  *			- pointer to int maxmatches
  *			- format options
  * output		- NONE
- * side effects 	- lists matching invisible clients on specified channel,
+ * 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,
@@ -513,10 +515,10 @@ static void who_common_channel(Client *client, Channel *channel,
 	{
 		acptr = cm->client;
 
-		if (!IsInvisible(acptr) || IsMarked(acptr))
+		if (IsMarked(acptr))
 			continue;
 
-		if(IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
+		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
 			continue;
 
 		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
@@ -562,10 +564,12 @@ static void who_global(Client *client, char *mask, int operspy, struct who_forma
 	Client *acptr;
 	int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
 
-	/* first, list all matching INvisible clients on common channels
-	 * if this is not an operspy who
-	 */
-	if(!operspy)
+	/* 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;
 
@@ -573,26 +577,22 @@ static void who_global(Client *client, char *mask, int operspy, struct who_forma
 			who_common_channel(client, lp->channel, mask, &maxmatches, fmt);
 	}
 
-	/* second, list all matching visible clients and clear all marks
-	 * on invisible clients
-	 * if this is an operspy who, list all matching clients, no need
-	 * to clear marks
-	 */
+	/* Second, list all matching visible clients. */
 	list_for_each_entry(acptr, &client_list, client_node)
 	{
-		if(!IsUser(acptr))
+		if (!IsUser(acptr))
 			continue;
 
-		if(IsInvisible(acptr) && !operspy)
- 		{
-			ClearMark(acptr);
+		if (IsInvisible(acptr) && !operspy && (client != acptr))
 			continue;
-		}
 
-		if(IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
+		if (IsMarked(acptr))
+			continue;
+
+		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
 			continue;
 
-		if(maxmatches > 0)
+		if (maxmatches > 0)
 		{
 			if (do_match(client, acptr, mask, fmt))
  			{
diff --git a/src/tls.c b/src/tls.c
@@ -230,43 +230,6 @@ static void mylog(char *fmt, ...)
 	ircd_log(LOG_ERROR, "%s", buf);
 }
 
-/** Set DH (Diffie-Hellman) parameters.
- * We don't use this anymore, unless explicitly instructed,
- * as we use the more secure ECDHE/EECDH instead
- * (Ephemeral Elliptic-Curve Diffie-Hellman)
- */
-static int setup_dh_params(SSL_CTX *ctx)
-{
-	DH *dh;
-	BIO *bio;
-	char *dh_file = iConf.tls_options ? iConf.tls_options->dh_file : tempiConf.tls_options->dh_file;
-	/* ^^ because we can be called both before config file initalization or after */
-
-	if (dh_file == NULL)
-		return 1;
-
-	bio = BIO_new_file(dh_file, "r");
-	if (bio == NULL)
-	{
-		config_error("Failed to load DH parameters %s", dh_file);
-		config_report_ssl_error();
-		return 0;
-	}
-
-	dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
-	if (dh == NULL)
-	{
-		config_error("Failed to use DH parameters %s", dh_file);
-		config_report_ssl_error();
-		BIO_free(bio);
-		return 0;
-	}
-
-	BIO_free(bio);
-	SSL_CTX_set_tmp_dh(ctx, dh);
-	return 1;
-}
-
 /** Disable SSL/TLS protocols as set by config */
 void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
 {
@@ -382,9 +345,6 @@ SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server)
 #endif
 	SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
 
-	if (!setup_dh_params(ctx))
-		goto fail;
-
 	if (!tlsoptions->certificate_file)
 	{
 		config_error("No SSL certificate configured (set::options::ssl::certificate or in a listen block)");
@@ -577,6 +537,8 @@ void reinit_ssl(Client *client)
 		config_report_ssl_error();
 		return;
 	}
+	if (ctx_server)
+		SSL_CTX_free(ctx_server);
 	ctx_server = tmp; /* activate */
 	
 	tmp = init_ctx(iConf.tls_options, 0);
@@ -586,6 +548,8 @@ void reinit_ssl(Client *client)
 		config_report_ssl_error();
 		return;
 	}
+	if (ctx_client)
+		SSL_CTX_free(ctx_client);
 	ctx_client = tmp; /* activate */
 
 	/* listen::tls-options.... */
@@ -600,6 +564,8 @@ void reinit_ssl(Client *client)
 				config_report_ssl_error();
 				return;
 			}
+			if (listen->ssl_ctx)
+				SSL_CTX_free(listen->ssl_ctx);
 			listen->ssl_ctx = tmp; /* activate */
 		}
 	}
@@ -616,6 +582,8 @@ void reinit_ssl(Client *client)
 				config_report_ssl_error();
 				return;
 			}
+			if (sni->ssl_ctx)
+				SSL_CTX_free(sni->ssl_ctx);
 			sni->ssl_ctx = tmp; /* activate */
 		}
 	}
@@ -633,6 +601,8 @@ void reinit_ssl(Client *client)
 				config_report_ssl_error();
 				return;
 			}
+			if (link->ssl_ctx)
+				SSL_CTX_free(link->ssl_ctx);
 			link->ssl_ctx = tmp; /* activate */
 		}
 	}
diff --git a/src/version.c.SH b/src/version.c.SH
@@ -4,7 +4,7 @@ echo "Extracting src/version.c..."
 
 #id=`grep '$Id: Changes,v' ../Changes`
 #id=`echo $id |sed 's/.* Changes\,v \(.*\) .* Exp .*/\1/'`
-id="5.0.6"
+id="5.0.7"
 echo "$id"
 
 if test -r version.c
diff --git a/src/windows/UnrealIRCd.exe.manifest b/src/windows/UnrealIRCd.exe.manifest
@@ -3,7 +3,7 @@
 <assemblyIdentity
     processorArchitecture="amd64"
     name="UnrealIRCd.UnrealIRCd.5"
-    version="5.0.6.0"
+    version="5.0.7.0"
     type="win32"
 />
 <description>Internet Relay Chat Daemon</description>
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
@@ -6,7 +6,7 @@
 
 [Setup]
 AppName=UnrealIRCd 5
-AppVerName=UnrealIRCd 5.0.6
+AppVerName=UnrealIRCd 5.0.7
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org