unrealircd

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

commit eec3fa048615fda07497ea59fb4c83df9282f2f0
parent ef50d10decb0f9557e53bc3dd72eaf6e666f621f
Author: acidvegas <acid.vegas@acid.vegas>
Date: Mon, 20 Apr 2020 15:12:33 -0400

updated to 5.0.4

Diffstat:
MConfig | 140++++++++++++++++++++++++++++++++++++-------------------------------------------
Mautoconf/m4/unreal.m4 | 20++++++++++++++++++++
Mconfigure | 247++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mconfigure.ac | 26++++++++++++++++----------
Mdoc/Config.header | 3+--
Mdoc/RELEASE-NOTES.md | 45+++++++++++++++++++++++++++++++++++++++++++--
Mdoc/conf/except.conf | 2++
Mdoc/conf/ircd.motd | 13+++++--------
Ddoc/conf/unrealircd.remote.conf | 186-------------------------------------------------------------------------------
Mextras/doxygen/Doxyfile | 2+-
Aextras/tests/tls/cipherscan_profiles/openssl-102-ubuntu16.txt | 27+++++++++++++++++++++++++++
Mextras/tls.cnf | 2+-
Minclude/dynconf.h | 7+++++--
Minclude/h.h | 5+++++
Minclude/modules.h | 3++-
Minclude/setup.h.in | 3+++
Minclude/windows/setup.h | 4++--
Msrc/api-event.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/api-usermode.c | 20+++++++++++++++++++-
Msrc/conf.c | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/dns.c | 2+-
Msrc/ircd.c | 7++-----
Msrc/modules/channeldb.c | 12++++++------
Msrc/modules/connect.c | 2+-
Msrc/modules/ident_lookup.c | 16++++++++++++++--
Msrc/modules/join.c | 2+-
Msrc/modules/nick.c | 4++--
Msrc/modules/oper.c | 4++--
Msrc/modules/sajoin.c | 21+++++++++++++++++++--
Msrc/modules/sapart.c | 16++++++++++++++++
Msrc/modules/server.c | 1+
Msrc/modules/svsmode.c | 7+++++++
Msrc/modules/tkl.c | 18++++++++++--------
Msrc/modules/whois.c | 8+++++++-
Msrc/modules/whox.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/random.c | 6++----
Msrc/serv.c | 40++++++++++++++++++++++++++++++++++------
Msrc/socket.c | 7+------
Msrc/tls.c | 35+++++++++++++++++++++++------------
Msrc/user.c | 26++++++++++++++++++++++++++
Msrc/version.c.SH | 2+-
Msrc/windows/UnrealIRCd.exe.manifest | 2+-
Msrc/windows/unrealinst.iss | 2+-
Munrealircd.in | 7++-----

44 files changed, 750 insertions(+), 523 deletions(-)

diff --git a/Config b/Config
@@ -255,25 +255,19 @@ fi
 
 
 #parse arguments
-NOCACHE=""
 IMPORTEDSETTINGS=""
 NOINTRO=""
 QUICK=""
-CLEAN=""
 ADVANCED=""
 while [ $# -ge 1 ] ; do
 	if [ $1 = "--help" ] ; then
 		echo "Config utility for UnrealIRCd"
 		echo "-----------------------------"
 		echo "Syntax: ./Config [options]"
-		echo "-nocache     Ignore settings saved in config.settings"
 		echo "-nointro     Skip intro (release notes, etc)"
 		echo "-quick       Skip questions, go straight to configure"
 		echo "-advanced    Include additional advanced questions"
-		echo "-clean       Clean ./configure cache"
 		exit 0
-	elif [ $1 = "-nocache" ] ; then
-		NOCACHE="1"
 	elif [ $1 = "-nointro" ] ; then
 		NOINTRO="1"
 	elif [ $1 = "-quick" -o $1 = "-q" ] ; then
@@ -285,8 +279,6 @@ while [ $# -ge 1 ] ; do
 		RUN_CONFIGURE
 		cd "$UNREALCWD"
 		exit 0
-	elif [ $1 = "-clean" -o $1 = "-C" ] ; then
-		CLEAN="1"
 	elif [ $1 = "-advanced" ] ; then
 		PREADVANCED="1"
 	fi
@@ -299,10 +291,6 @@ elif [ "$ADVANCED" = "1" ]; then
 	ADVANCED=""
 fi
 
-if [ -n "$CLEAN" ] ; then
-	rm -f config.cache
-fi
-
 if [ "`id -u`" = "0" ]; then
 	echo "ERROR: You cannot build or run UnrealIRCd as root"
 	echo ""
@@ -324,79 +312,77 @@ clear
 echo "We will now ask you a number of questions. You can just press ENTER to accept the defaults!"
 echo ""
 
-if [ -z "$NOCACHE" ] ; then
-	# This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-	UNREALRELEASES="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
-		# 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";
+# This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
+UNREALRELEASES="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
+	# 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"
+	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 [ "$IMPORTEDSETTINGS" = "none" ]; then
-			IMPORTEDSETTINGS=""
+		if [ ! -f $IMPORTEDSETTINGS/config.settings ]; then
+			echo "Directory $IMPORTEDSETTINGS does not exist or does not contain a config.settings file"
+			exit
 		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
+		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
-	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=""
+		# 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 ""
diff --git a/autoconf/m4/unreal.m4 b/autoconf/m4/unreal.m4
@@ -252,3 +252,23 @@ else
 	AC_MSG_RESULT([no])
 fi
 ])
+
+AC_DEFUN([CHECK_SSL_CTX_SET_SECURITY_LEVEL],
+[
+AC_MSG_CHECKING([for SSL_CTX_set_security_level in SSL library])
+AC_LANG_PUSH(C)
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+AC_TRY_LINK([#include <openssl/ssl.h>],
+	[SSL_CTX *ctx = NULL; SSL_CTX_set_security_level(ctx, 1);],
+	has_function=1,
+	has_function=0)
+LIBS="$SAVE_LIBS"
+AC_LANG_POP(C)
+if test $has_function = 1; then
+	AC_MSG_RESULT([yes])
+	AC_DEFINE([HAS_SSL_CTX_SET_SECURITY_LEVEL], [], [Define if ssl library has SSL_CTX_set_security_level])
+else
+	AC_MSG_RESULT([no])
+fi
+])
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.3.1.
+# Generated by GNU Autoconf 2.69 for unrealircd 5.0.4.
 #
 # 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.3.1'
-PACKAGE_STRING='unrealircd 5.0.3.1'
+PACKAGE_VERSION='5.0.4'
+PACKAGE_STRING='unrealircd 5.0.4'
 PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
 PACKAGE_URL='https://unrealircd.org/'
 
@@ -670,6 +670,8 @@ HARDEN_BINLDFLAGS
 HARDEN_BINCFLAGS
 HARDEN_LDFLAGS
 HARDEN_CFLAGS
+GMAKE
+MAKER
 OBJEXT
 EXEEXT
 ac_ct_CC
@@ -679,8 +681,6 @@ CFLAGS
 CC
 PKGCONFIG
 GUNZIP
-GMAKE
-MAKER
 INSTALL
 OPENSSLPATH
 TOUCH
@@ -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.3.1 to adapt to many kinds of systems.
+\`configure' configures unrealircd 5.0.4 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.3.1:";;
+     short | recursive ) echo "Configuration of unrealircd 5.0.4:";;
    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.3.1
+unrealircd configure 5.0.4
 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.3.1, which was
+It was created by unrealircd $as_me 5.0.4, 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="3"
+UNREAL_VERSION_MINOR="4"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -2331,7 +2331,7 @@ _ACEOF
 # The version suffix such as a beta marker or release candidate
 # marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a
 # string instead of an integer because it contains arbitrary data.
-UNREAL_VERSION_SUFFIX=".1"
+UNREAL_VERSION_SUFFIX=""
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_SUFFIX "$UNREAL_VERSION_SUFFIX"
@@ -2551,91 +2551,6 @@ $as_echo "no" >&6; }
 fi
 
 
-# Extract the first word of "gmake", so it can be a program name with args.
-set dummy gmake; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_MAKER+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$MAKER"; then
-  ac_cv_prog_MAKER="$MAKER" # Let the user override the test.
-else
-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_prog_MAKER="gmake"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  test -z "$ac_cv_prog_MAKER" && ac_cv_prog_MAKER="make"
-fi
-fi
-MAKER=$ac_cv_prog_MAKER
-if test -n "$MAKER"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAKER" >&5
-$as_echo "$MAKER" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-# Extract the first word of "gmake", so it can be a program name with args.
-set dummy gmake; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_GMAKE+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $GMAKE in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_GMAKE="$GMAKE" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_GMAKE="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  ;;
-esac
-fi
-GMAKE=$ac_cv_path_GMAKE
-if test -n "$GMAKE"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GMAKE" >&5
-$as_echo "$GMAKE" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-
-if $MAKER --version | grep -q "GNU Make"; then :
-  GNUMAKE="0"
-else
-  as_fn_error $? "It seems your system does not have gmake installed. Please install gmake." "$LINENO" 5
-fi
-
 # Extract the first word of "gunzip", so it can be a program name with args.
 set dummy gunzip; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -3687,6 +3602,90 @@ if test "$ac_cv_prog_cc_c99" = "no"; then :
   as_fn_error $? "No C99 compiler was found. Please install gcc or clang and other build tools. Eg, on Debian/Ubuntu you probably want to run the following as root: apt-get install build-essential " "$LINENO" 5
 fi
 
+# Extract the first word of "gmake", so it can be a program name with args.
+set dummy gmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_MAKER+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$MAKER"; then
+  ac_cv_prog_MAKER="$MAKER" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_MAKER="gmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_prog_MAKER" && ac_cv_prog_MAKER="make"
+fi
+fi
+MAKER=$ac_cv_prog_MAKER
+if test -n "$MAKER"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAKER" >&5
+$as_echo "$MAKER" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+# Extract the first word of "gmake", so it can be a program name with args.
+set dummy gmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_GMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $GMAKE in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_GMAKE="$GMAKE" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_GMAKE="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+GMAKE=$ac_cv_path_GMAKE
+if test -n "$GMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GMAKE" >&5
+$as_echo "$GMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+if $MAKER --version | grep -q "GNU Make"; then :
+  GNUMAKE="0"
+else
+  as_fn_error $? "It seems your system does not have make/gmake installed. If you are on Linux then install make, otherwise install gmake." "$LINENO" 5
+fi
+
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -ldescrypt" >&5
 $as_echo_n "checking for crypt in -ldescrypt... " >&6; }
@@ -6384,6 +6383,53 @@ else
 $as_echo "no" >&6; }
 fi
 
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set_security_level in SSL library" >&5
+$as_echo_n "checking for SSL_CTX_set_security_level in SSL library... " >&6; }
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <openssl/ssl.h>
+int
+main ()
+{
+SSL_CTX *ctx = NULL; SSL_CTX_set_security_level(ctx, 1);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  has_function=1
+else
+  has_function=0
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS="$SAVE_LIBS"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test $has_function = 1; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAS_SSL_CTX_SET_SECURITY_LEVEL /**/" >>confdefs.h
+
+else
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
 # Check whether --enable-dynamic-linking was given.
 if test "${enable_dynamic_linking+set}" = set; then :
   enableval=$enable_dynamic_linking; enable_dynamic_linking=$enableval
@@ -6948,6 +6994,11 @@ $as_echo "installing Argon2 library" >&6; }
 $ac_cv_prog_MAKER install PREFIX=$cur_dir/extras/argon2 || exit 1
 # We need to manually copy the libs to PRIVATELIBDIR because
 # there is no way to tell make install in libargon2 to do so.
+# BUT FIRST, delete the old library so it becomes an unlink+create
+# operation rather than overwriting the existing file which would
+# lead to a crash of the currently running IRCd.
+rm -f "$PRIVATELIBDIR/"libargon2*
+# Now copy the new library files:
 cp -av $cur_dir/extras/argon2/lib/* $PRIVATELIBDIR/
 ARGON2_CFLAGS="-I$cur_dir/extras/argon2/include"
 
@@ -8249,7 +8300,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.3.1, which was
+This file was extended by unrealircd $as_me 5.0.4, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -8312,7 +8363,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.3.1
+unrealircd config.status 5.0.4
 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.3.1], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [5.0.4], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
 AC_CONFIG_SRCDIR([src/ircd.c])
 AC_CONFIG_HEADER([include/setup.h])
 AC_CONFIG_AUX_DIR([autoconf])
@@ -34,13 +34,13 @@ UNREAL_VERSION_MAJOR=["0"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MAJOR], [$UNREAL_VERSION_MAJOR], [Major version number (e.g.: Y for X.Y.Z)])
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR=["3"]
+UNREAL_VERSION_MINOR=["4"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MINOR], [$UNREAL_VERSION_MINOR], [Minor version number (e.g.: Z for X.Y.Z)])
 
 # The version suffix such as a beta marker or release candidate
 # marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a
 # string instead of an integer because it contains arbitrary data.
-UNREAL_VERSION_SUFFIX=[".1"]
+UNREAL_VERSION_SUFFIX=[""]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_SUFFIX], ["$UNREAL_VERSION_SUFFIX"], [Version suffix such as a beta marker or release candidate marker. (e.g.: -rcX for unrealircd-3.2.9-rcX)])
 
 AC_PATH_PROG(RM,rm)
@@ -60,13 +60,6 @@ exit 1
 ])
 
 AC_PATH_PROG(INSTALL,install)
-AC_CHECK_PROG(MAKER, gmake, gmake, make)
-AC_PATH_PROG(GMAKE,gmake)
-
-AS_IF([$MAKER --version | grep -q "GNU Make"],
-        [GNUMAKE="0"],
-        [AC_MSG_ERROR([It seems your system does not have gmake installed. Please install gmake.])])
-
 AC_PATH_PROG(GUNZIP, gunzip)
 AC_PATH_PROG(PKGCONFIG, pkg-config)
 
@@ -75,6 +68,13 @@ AC_PROG_CC_C99
 AS_IF([test "$ac_cv_prog_cc_c99" = "no"],
 	[AC_MSG_ERROR([No C99 compiler was found. Please install gcc or clang and other build tools. Eg, on Debian/Ubuntu you probably want to run the following as root: apt-get install build-essential ])])
 
+dnl Check for make moved down, so the above compiler check takes precedence.
+AC_CHECK_PROG(MAKER, gmake, gmake, make)
+AC_PATH_PROG(GMAKE,gmake)
+AS_IF([$MAKER --version | grep -q "GNU Make"],
+        [GNUMAKE="0"],
+        [AC_MSG_ERROR([It seems your system does not have make/gmake installed. If you are on Linux then install make, otherwise install gmake.])])
+
 dnl Checks for libraries.
 AC_CHECK_LIB(descrypt, crypt,
 	[AC_DEFINE([HAVE_CRYPT], [], [Define if you have crypt])
@@ -497,6 +497,7 @@ AC_ARG_WITH(system-cares, [AS_HELP_STRING([--without-system-cares], [Use bundled
 CHECK_SSL
 CHECK_SSL_CTX_SET1_CURVES_LIST
 CHECK_SSL_CTX_SET_MIN_PROTO_VERSION
+CHECK_SSL_CTX_SET_SECURITY_LEVEL
 AC_ARG_ENABLE(dynamic-linking, [AS_HELP_STRING([--disable-dynamic-linking], [Make the IRCd statically link with shared objects rather than dynamically (noone knows if disabling dynamic linking actually does anything or not)])],
 	[enable_dynamic_linking=$enableval], [enable_dynamic_linking="yes"])
 AS_IF([test $enable_dynamic_linking = "yes"],
@@ -603,6 +604,11 @@ AC_MSG_RESULT(installing Argon2 library)
 $ac_cv_prog_MAKER install PREFIX=$cur_dir/extras/argon2 || exit 1
 # We need to manually copy the libs to PRIVATELIBDIR because
 # there is no way to tell make install in libargon2 to do so.
+# BUT FIRST, delete the old library so it becomes an unlink+create
+# operation rather than overwriting the existing file which would
+# lead to a crash of the currently running IRCd.
+rm -f "$PRIVATELIBDIR/"libargon2*
+# Now copy the new library files:
 cp -av $cur_dir/extras/argon2/lib/* $PRIVATELIBDIR/
 ARGON2_CFLAGS="-I$cur_dir/extras/argon2/include"
 AC_SUBST(ARGON2_CFLAGS)
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,11 +7,10 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 5.0.3.1
+                                for UnrealIRCd 5.0.4
                                     
 This program will help you to compile your IRC server, and ask you
 questions regarding the compile-time settings of it during the process. 
-regarding the setup of it, during the process.
 
 A short installation guide is available online at:
 https://www.unrealircd.org/docs/Installing_from_source
diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md
@@ -1,5 +1,46 @@
-UnrealIRCd 5.0.3.1 Release Notes
-=================================
+UnrealIRCd 5.0.4 Release Notes
+===============================
+
+This new 5.0.4 version fixes quite a number of bugs. It contains only two small feature improvements.
+
+Fixes:
+* When placing a SHUN on an online user it was not always effective.
+* Channeldb was not properly restoring all channel modes, such as +P.
+* When upgrading UnrealIRCd it could sometimes crash the currently
+  running IRC server (rare), or trigger a crash report on
+  ```./unrealircd restart``` (quite common).
+* UnrealIRCd was giving up too easily on ident lookups.
+* Crash when unloading a module with moddata.
+* Crash if an authenticated server sends wrong information (rare).
+* Removing a TEMPSHUN did not work if the user was on another server.
+* SAJOIN to 0 (part all channels) resulted in a desync when used on remote users.
+* Forced nick change from services was not showing up if the user
+  was not in any channels.
+
+Enhancements:
+* New option [set::hide-idle-time::policy](https://www.unrealircd.org/docs/Set_block#set%3A%3Ahide-idle-time)
+  by which you can change usermode +I (hide idle time in WHOIS) from
+  oper-only to settable by users. More options will follow in a future
+  release.
+* In WHOIS you can now see if a user is currently (temp)shunned.
+  This only works for locally connected users for technical reasons,
+  so use ```/WHOIS Nick Nick``` to see it for remote users.
+
+Changes:
+* The oper notices and logging with regards to server linking have changed
+  a little. They are more consistent and log more now.
+* When an IRCOp tries to oper up from an insecure connection we will now
+  mention the https://www.unrealircd.org/docs/FAQ#oper-requires-tls page.
+  This message is customizable through
+  [set::plaintext-policy::oper-message](https://www.unrealircd.org/docs/Set_block#set::plaintext-policy).
+* The French HELPOP text was updated.
+
+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.3.1
 -------------------
diff --git a/doc/conf/except.conf b/doc/conf/except.conf
@@ -33,3 +33,4 @@ except ban { mask *@2600:3c03::f03c:91ff:fe96:c1fa; } # bot.search.mibbit.com
 
 # Netsplit
 except ban { mask *@85.25.137.63; } # eagle.netsplit.de
+except ban { mask *@85.25.10.40;  } # anaconda.netsplit.de
+\ No newline at end of file
diff --git a/doc/conf/ircd.motd b/doc/conf/ircd.motd
@@ -36,12 +36,9 @@
      0,0  0║ ╚0,0     0║ 0,0       0╗   0,0  0║   ╚0,0     0╔╝
      0╚═╝  ╚════╝ ╚══════╝   ╚═╝    ╚════╝
 
+4─────────┤ 0THE WILD WILD WEST OF IRC4├─────────
 
+14• 7Do not disrupt the orderly operation of the network
+14• 7No distribution of child pornography
 
-4──────────────────┤ 0RULES 4├───────────────────
-
-14• 7Do not disrupt or compromise our security. 
-14• 7No malicious abuse of network services.
-14• 7No distribution of child pornography.
-
-4──────────────────────────────────────────────
-\ No newline at end of file
+8,4         E N T E R   T H E   V O I D          
+\ No newline at end of file
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
@@ -1,185 +0,0 @@
-admin { ""; }
-
-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 1M; options { nofakelag; } }
-class servers { pingfreq 120; maxclients  10; sendq 1M; connfreq 30;           }
-
-allow { ip *; class clients; maxperip 2; }
-
-listen { ip *; port 6667;      options { clientsonly;      } }
-listen { ip *; port 6697;      options { clientsonly; tls; } }
-listen { ip *; port REDACTED;  options { serversonly; tls; } }
-
-link irc.supernets.org {
-	incoming { mask REDACTED; }
-	outgoing {
-		bind-ip *;
-		hostname REDACTED;
-		port REDACTED;
-		options { tls; autoconnect; }
-	}
-	password "REDACTED" { spkifp; }
-	class servers;
-}
-
-log "errors.log" { flags { errors; } maxsize 10K; }
-
-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 1d;
-	reason "8,4   E N T E R   T H E   V O I D   ";
-}
-
-blacklist efnetrbl {
-	dns {
-		name rbl.efnetrbl.org;
-		type record;
-		reply { 1; 4; 5; }
-	}
-	action gzline;
-	ban-time 1d;
-	reason "8,4   E N T E R   T H E   V O I D   ";
-}
-
-blacklist torbl {
-	dns {
-		name torexit.dan.me.uk;
-		type record;
-		reply { 100; }
-	}
-	action gzline;
-	ban-time 1d;
-	reason "8,4   E N T E R   T H E   V O I D   ";
-}
-
-webirc { mask 107.161.19.53;  password "REDACTED"; } # KiwiIRC
-webirc { mask 107.161.19.109; password "REDACTED"; }
-webirc { mask 107.161.31.4;   password "REDACTED"; }
-
-webirc { mask 207.192.75.252; password "REDACTED"; } # Mibbit
-webirc { mask 64.62.228.82;   password "REDACTED"; }
-webirc { mask 78.129.202.38;  password "REDACTED"; }
-webirc { mask 109.169.29.95 ; password "REDACTED"; }
-
-set {
-	kline-address "banned@supernets.org";
-	gline-address "banned@supernets.org";
-	modes-on-connect "+ipTx";
-	modes-on-oper "+HIq";
-	snomask-on-oper "+bcFfkGsSo";
-	modes-on-join "+ns";
-	level-on-join "op";
-	restrict-channelmodes "CnLpPs";
-	restrict-commands {
-		invite          { connect-delay 3600; exempt-identified yes; }
-		list            { connect-delay 120;  exempt-identified yes; }
-		private-message { connect-delay 300;  exempt-identified yes; }
-		private-notice  { connect-delay 3600; exempt-identified yes; }
-	}
-	auto-join "#superbowl";
-	oper-auto-join "#help";
-	static-quit "EMO-QUIT";
-	static-part "EMO-PART";
-	who-limit 100;
-	nick-length 20;
-	maxchannelsperuser 10;
-	channel-command-prefix "`!@$.";
-	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 "#help";
-	cloak-method ip;
-	cloak-keys {
-		"REDACTED";
-		"REDACTED";
-		"REDACTED";
-	}
-	hiddenhost-prefix "SUPER";
-	plaintext-policy {
-		user warn;
-		oper deny;
-		server deny;
-		user-message "4WARNING: You are not using a secure connection with SSL/TLS";
-		oper-message "Network operators must connect using SSL/TLS";
-	}
-	outdated-tls-policy {
-		user warn;
-		oper deny;
-		server deny;
-		user-message "4WARNING: You are using an outdated SSL/TLS protocol or cipher";
-		oper-message "Network operators must connect using an up-to-date SSL/TLS protocol or cipher";
-	}
-	anti-flood {
-		away-flood 3:300;
-		connect-flood 3:300;
-		invite-flood 3:300;
-		join-flood 3:300;
-		knock-flood 3:300;
-		max-concurrent-conversations { users 5; new-user-every 60s; }
-		nick-flood 3:300;
-		unknown-flood-amount 2048;
-		unknown-flood-bantime 1h;
-	}
-	default-bantime 1d;
-	spamfilter {
-		ban-time 1d;
-		ban-reason "8,4   E N T E R   T H E   V O I D   ";
-		except "#anythinggoes";
-	}
-	max-targets-per-command { kick 1; part 1; privmsg 1; }
-	hide-ban-reason yes;
-	reject-message {
-		gline                "8,4   E N T E R   T H E   V O I D   ";
-		kline                "8,4   E N T E R   T H E   V O I D   ";
-		password-mismatch    "8,4   E N T E R   T H E   V O I D   ";
-		server-full          "8,4   E N T E R   T H E   V O I D   ";
-		too-many-connections "8,4   E N T E R   T H E   V O I D   ";
-		unauthorized         "8,4   E N T E R   T H E   V O I D   ";
-	}
-	antimixedutf8 {
-		score 10;
-		ban-action block;
-		ban-reason "8,4   E N T E R   T H E   V O I D   ";
-		ban-time 1h;
-	}
-	connthrottle {
-		known-users   { minimum-reputation-score 24; sasl-bypass yes;       }
-		new-users     { local-throttle 20:60;        global-throttle 30:60; }
-		disabled-when { reputation-gathering 1w;     start-delay 3m;        }
-	}
-	history {
-		channel {
-			playback-on-join        { lines 100; time 1d; }
-			max-storage-per-channel { lines 100; time 1d; }
-		}
-	}
-	manual-ban-target ip;
-}
-
-hideserver {
-	disable-map yes;
-	disable-links yes;
-	map-deny-message "Denied";
-	links-deny-message "Denied";
-}
-\ No newline at end of file
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.3
+PROJECT_NUMBER         = 5.0.4
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/extras/tests/tls/cipherscan_profiles/openssl-102-ubuntu16.txt b/extras/tests/tls/cipherscan_profiles/openssl-102-ubuntu16.txt
@@ -0,0 +1,27 @@
+Target: 127.0.0.1:5901
+
+prio  ciphersuite                    protocols              pfs                 curves
+1     ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
+2     ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
+3     ECDHE-ECDSA-AES256-SHA384      TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
+4     ECDHE-ECDSA-AES256-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-521,521bits  secp521r1,secp384r1
+5     ECDHE-ECDSA-AES128-SHA256      TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
+6     ECDHE-ECDSA-AES128-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-521,521bits  secp521r1,secp384r1
+
+Certificate: untrusted, 384 bits, ecdsa-with-SHA256 signature
+TLS ticket lifetime hint: None
+NPN protocols: None
+OCSP stapling: not supported
+Cipher ordering: server
+Curves ordering: server - fallback: no
+Server supports secure renegotiation
+Server supported compression methods: NONE
+TLS Tolerance: yes
+
+Intolerance to:
+ SSL 3.254           : absent
+ TLS 1.0             : absent
+ TLS 1.1             : absent
+ TLS 1.2             : absent
+ TLS 1.3             : absent
+ TLS 1.4             : absent
diff --git a/extras/tls.cnf b/extras/tls.cnf
@@ -18,7 +18,7 @@ stateOrProvinceName_default     = New York
 localityName                    = Locality Name (eg, city)
 
 0.organizationName              = Organization Name (eg, company)
-0.organizationName_default      = SuperNETs
+0.organizationName_default      = IRC geeks
 
 organizationalUnitName          = Organizational Unit Name (eg, section)
 organizationalUnitName_default  = IRCd  
diff --git a/include/dynconf.h b/include/dynconf.h
@@ -55,6 +55,8 @@ typedef enum AllowedChannelChars { ALLOWED_CHANNELCHARS_ANY=1, ALLOWED_CHANNELCH
 
 typedef enum BanTarget { BAN_TARGET_IP=1, BAN_TARGET_USERIP=2, BAN_TARGET_HOST=3, BAN_TARGET_USERHOST=4, BAN_TARGET_ACCOUNT=5, BAN_TARGET_CERTFP=6 } BanTarget;
 
+typedef enum HideIdleTimePolicy { HIDE_IDLE_TIME_NEVER=1, HIDE_IDLE_TIME_ALWAYS=2, HIDE_IDLE_TIME_USERMODE=3, HIDE_IDLE_TIME_OPER_USERMODE=4 } HideIdleTimePolicy;
+
 /** The set { } block configuration */
 typedef struct Configuration Configuration;
 struct Configuration {
@@ -97,9 +99,9 @@ struct Configuration {
 	char *static_part;
 	TLSOptions *tls_options;
 	Policy plaintext_policy_user;
-	char *plaintext_policy_user_message;
+	MultiLine *plaintext_policy_user_message;
 	Policy plaintext_policy_oper;
-	char *plaintext_policy_oper_message;
+	MultiLine *plaintext_policy_oper_message;
 	Policy plaintext_policy_server;
 	Policy outdated_tls_policy_user;
 	char *outdated_tls_policy_user_message;
@@ -171,6 +173,7 @@ struct Configuration {
 	int part_instead_of_quit_on_comment_change;
 	BroadcastChannelMessagesOption broadcast_channel_messages;
 	AllowedChannelChars allowed_channelchars;
+	HideIdleTimePolicy hide_idle_time;
 };
 
 extern MODVAR Configuration iConf;
diff --git a/include/h.h b/include/h.h
@@ -911,6 +911,8 @@ extern void badword_config_free(ConfigItem_badword *ca);
 extern char *badword_config_check_regex(char *s, int fastsupport, int check_broadness);
 extern AllowedChannelChars allowed_channelchars_strtoval(char *str);
 extern char *allowed_channelchars_valtostr(AllowedChannelChars v);
+extern HideIdleTimePolicy hideidletime_strtoval(char *str);
+extern char *hideidletime_valtostr(HideIdleTimePolicy v);
 extern long ClientCapabilityBit(const char *token);
 extern int is_handshake_finished(Client *client);
 extern void SetCapability(Client *acptr, const char *token);
@@ -958,6 +960,7 @@ extern char *filename_add_suffix(const char *fname, const char *suffix);
 extern int filename_has_suffix(const char *fname, const char *suffix);
 extern void addmultiline(MultiLine **l, char *line);
 extern void freemultiline(MultiLine *l);
+#define safe_free_multiline(x) do { if (x) freemultiline(x); x = NULL; } while(0)
 extern void sendnotice_multiline(Client *client, MultiLine *m);
 extern void unreal_del_quotes(char *i);
 extern char *unreal_add_quotes(char *str);
@@ -965,3 +968,5 @@ extern int unreal_add_quotes_r(char *i, char *o, size_t len);
 extern void user_account_login(MessageTag *recv_mtags, Client *client);
 extern void link_generator(void);
 extern void update_throttling_timer_settings(void);
+extern int hide_idle_time(Client *client, Client *target);
+extern void lost_server_link(Client *serv, FORMAT_STRING(const char *fmt), ...);
diff --git a/include/modules.h b/include/modules.h
@@ -650,6 +650,7 @@ struct Event {
 	vFP		event;		/**< Actual function to call */
 	void		*data;		/**< The data to pass in the function call */
 	struct timeval	last_run;	/**< Last time this event ran */
+	char		deleted;	/**< Set to 1 if this event is marked for deletion */
 	Module		*owner;		/**< To which module this event belongs */
 };
 
@@ -676,7 +677,7 @@ extern MODVAR Callback *Callbacks[MAXCALLBACKS], *RCallbacks[MAXCALLBACKS];
 extern MODVAR ClientCapability *clicaps;
 
 extern Event *EventAdd(Module *module, char *name, vFP event, void *data, long every_msec, int count);
-extern Event  *EventDel(Event *event);
+extern void   EventDel(Event *event);
 extern Event *EventMarkDel(Event *event);
 extern Event *EventFind(char *name);
 extern int EventMod(Event *event, EventInfo *mods);
diff --git a/include/setup.h.in b/include/setup.h.in
@@ -34,6 +34,9 @@
 /* Define if ssl library has SSL_CTX_set_min_proto_version */
 #undef HAS_SSL_CTX_SET_MIN_PROTO_VERSION
 
+/* Define if ssl library has SSL_CTX_set_security_level */
+#undef HAS_SSL_CTX_SET_SECURITY_LEVEL
+
 /* Define if you have crypt */
 #undef HAVE_CRYPT
 
diff --git a/include/windows/setup.h b/include/windows/setup.h
@@ -63,10 +63,10 @@
 #define UNREAL_VERSION_MAJOR 0
 
 /* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 3
+#define UNREAL_VERSION_MINOR 4
 
 /* Version suffix such as a beta marker or release candidate marker. (e.g.:
    -rcX for unrealircd-3.2.9-rcX) */
-#define UNREAL_VERSION_SUFFIX ".1"
+#define UNREAL_VERSION_SUFFIX ""
 
 #endif
diff --git a/src/api-event.c b/src/api-event.c
@@ -87,40 +87,69 @@ Event *EventAdd(Module *module, char *name, vFP event, void *data, long every_ms
 	
 }
 
-Event *EventMarkDel(Event *event)
+/** Mark the Event for deletion.
+ * The actual deletion of the event happens later on
+ * (which is of no concern to the caller).
+ */
+void EventDel(Event *e)
 {
-	event->count = -1;
-	return event;
-}
+	char buf[128];
 
-Event *EventDel(Event *event)
-{
-	Event *p, *q;
-	for (p = events; p; p = p->next)
+	/* Mark for deletion */
+	e->deleted = 1;
+
+	/* Replace the name so deleted events are clearly labeled */
+	if (e->name)
 	{
-		if (p == event)
+		snprintf(buf, sizeof(buf), "deleted:%s", e->name);
+		safe_strdup(e->name, buf);
+	}
+
+	/* Remove the event from the module, that is something we can safely do straight away */
+	if (e->owner)
+	{
+		ModuleObject *eventobjs;
+		for (eventobjs = e->owner->objects; eventobjs; eventobjs = eventobjs->next)
 		{
-			q = p->next;
-			safe_free(p->name);
-			DelListItem(p, events);
-			if (p->owner)
+			if (eventobjs->type == MOBJ_EVENT && eventobjs->object.event == e)
 			{
-				ModuleObject *eventobjs;
-				for (eventobjs = p->owner->objects; eventobjs; eventobjs = eventobjs->next)
-				{
-					if (eventobjs->type == MOBJ_EVENT && eventobjs->object.event == p)
-					{
-						DelListItem(eventobjs, p->owner->objects);
-						safe_free(eventobjs);
-						break;
-					}
-				}
+				DelListItem(eventobjs, e->owner->objects);
+				safe_free(eventobjs);
+				break;
 			}
-			safe_free(p);
-			return q;		
 		}
+		e->owner = NULL;
+	}
+}
+
+/** Remove the event for real, used only via CleanupEvents(), not for end-users. */
+static void EventDelReal(Event *e)
+{
+	if (!e->deleted)
+	{
+		ircd_log(LOG_ERROR, "EventDelReal called while e->deleted is 0. This cannot happen. Event name: %s.", e->name);
+		abort();
+	}
+	if (e->owner)
+	{
+		ircd_log(LOG_ERROR, "EventDelReal called while e->owner is non-NULL. This cannot happen. Event name: %s.", e->name);
+		abort();
+	}
+	safe_free(e->name);
+	DelListItem(e, events);
+	safe_free(e);
+}
+
+/** Remove any events that were previously marked for deletion */
+static void CleanupEvents(void)
+{
+	Event *e, *e_next;
+	for (e = events; e; e = e_next)
+	{
+		e_next = e->next;
+		if (e->deleted)
+			EventDelReal(e);
 	}
-	return NULL;
 }
 
 Event *EventFind(char *name)
@@ -171,11 +200,12 @@ int EventMod(Event *event, EventInfo *mods)
 
 void DoEvents(void)
 {
-	Event *e, *e_next;
+	Event *e;
 
-	for (e = events; e; e = e_next)
+	for (e = events; e; e = e->next)
 	{
-		e_next = e->next;
+		if (e->deleted)
+			continue;
 		if (e->count == -1)
 		{
 			EventDel(e);
@@ -195,6 +225,8 @@ void DoEvents(void)
 			}
 		}
 	}
+
+	CleanupEvents();
 }
 
 void SetupEvents(void)
diff --git a/src/api-usermode.c b/src/api-usermode.c
@@ -61,6 +61,9 @@ long SNO_OPER = 0L;
 long AllUmodes;		/* All umodes */
 long SendUmodes;	/* All umodes which are sent to other servers (global umodes) */
 
+/* Forward declarations */
+int umode_hidle_allow(Client *client, int what);
+
 void	umode_init(void)
 {
 	long val = 1;
@@ -93,7 +96,7 @@ void	umode_init(void)
 	UmodeAdd(NULL, 'd', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_DEAF);
 	UmodeAdd(NULL, 'H', UMODE_GLOBAL, 1, umode_allow_opers, &UMODE_HIDEOPER);
 	UmodeAdd(NULL, 't', UMODE_GLOBAL, 0, umode_allow_unset, &UMODE_SETHOST);
-	UmodeAdd(NULL, 'I', UMODE_GLOBAL, 1, umode_allow_opers, &UMODE_HIDLE);
+	UmodeAdd(NULL, 'I', UMODE_GLOBAL, 0, umode_hidle_allow, &UMODE_HIDLE);
 	SnomaskAdd(NULL, 'k', umode_allow_opers, &SNO_KILLS);
 	SnomaskAdd(NULL, 'c', umode_allow_opers, &SNO_CLIENT);
 	SnomaskAdd(NULL, 'f', umode_allow_opers, &SNO_FLOOD);
@@ -378,6 +381,17 @@ int umode_allow_opers(Client *client, int what)
 		return 1;
 }
 
+int umode_hidle_allow(Client *client, int what)
+{
+	if (!MyUser(client))
+		return 1;
+	if (iConf.hide_idle_time == HIDE_IDLE_TIME_OPER_USERMODE)
+		return IsOper(client) ? 1 : 0;
+	if (iConf.hide_idle_time == HIDE_IDLE_TIME_USERMODE)
+		return 1;
+	return 0; /* if set::hide-idle-time is 'never' or 'always' then +I makes no sense */
+}
+
 void unload_all_unused_umodes(void)
 {
 	long removed_umode = 0;
@@ -469,6 +483,10 @@ int i;
 		if (Usermode_Table[i].unset_on_deoper)
 			client->umodes &= ~Usermode_Table[i].mode;
 	}
+
+	/* Bit of a hack, since this is a dynamic permission umode */
+	if (iConf.hide_idle_time == HIDE_IDLE_TIME_OPER_USERMODE)
+		client->umodes &= ~UMODE_HIDLE;
 }
 
 void remove_oper_privileges(Client *client, int broadcast_mode_change)
diff --git a/src/conf.c b/src/conf.c
@@ -787,6 +787,36 @@ char *ban_target_valtostr(BanTarget v)
 	}
 }
 
+HideIdleTimePolicy hideidletime_strtoval(char *str)
+{
+	if (!strcmp(str, "never"))
+		return HIDE_IDLE_TIME_NEVER;
+	else if (!strcmp(str, "always"))
+		return HIDE_IDLE_TIME_ALWAYS;
+	else if (!strcmp(str, "usermode"))
+		return HIDE_IDLE_TIME_USERMODE;
+	else if (!strcmp(str, "oper-usermode"))
+		return HIDE_IDLE_TIME_OPER_USERMODE;
+	return 0;
+}
+
+char *hideidletime_valtostr(HideIdleTimePolicy v)
+{
+	switch(v)
+	{
+		case HIDE_IDLE_TIME_NEVER:
+			return "never";
+		case HIDE_IDLE_TIME_ALWAYS:
+			return "always";
+		case HIDE_IDLE_TIME_USERMODE:
+			return "usermode";
+		case HIDE_IDLE_TIME_OPER_USERMODE:
+			return "oper-usermode";
+		default:
+			return "INVALID";
+	}
+}
+
 ConfigFile *config_load(char *filename, char *displayname)
 {
 	struct stat sb;
@@ -1586,8 +1616,8 @@ void	free_iConf(Configuration *i)
 	free_tls_options(i->tls_options);
 	i->tls_options = NULL;
 	safe_free(i->tls_options);
-	safe_free(i->plaintext_policy_user_message);
-	safe_free(i->plaintext_policy_oper_message);
+	safe_free_multiline(i->plaintext_policy_user_message);
+	safe_free_multiline(i->plaintext_policy_oper_message);
 	safe_free(i->outdated_tls_policy_user_message);
 	safe_free(i->outdated_tls_policy_oper_message);
 	safe_free(i->restrict_usermodes);
@@ -1714,6 +1744,8 @@ void config_setdefaultsettings(Configuration *i)
 
 	i->automatic_ban_target = BAN_TARGET_IP;
 	i->manual_ban_target = BAN_TARGET_HOST;
+
+	i->hide_idle_time = HIDE_IDLE_TIME_OPER_USERMODE;
 }
 
 static void make_default_logblock(void)
@@ -1742,18 +1774,21 @@ void postconf_defaults(void)
 	{
 		/* The message depends on whether it's reject or warn.. */
 		if (iConf.plaintext_policy_user == POLICY_DENY)
-			safe_strdup(iConf.plaintext_policy_user_message, "Insecure connection. Please reconnect using SSL/TLS.");
+			addmultiline(&iConf.plaintext_policy_user_message, "Insecure connection. Please reconnect using SSL/TLS.");
 		else if (iConf.plaintext_policy_user == POLICY_WARN)
-			safe_strdup(iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using SSL/TLS.");
+			addmultiline(&iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using SSL/TLS.");
 	}
 
 	if (!iConf.plaintext_policy_oper_message)
 	{
 		/* The message depends on whether it's reject or warn.. */
 		if (iConf.plaintext_policy_oper == POLICY_DENY)
-			safe_strdup(iConf.plaintext_policy_oper_message, "You need to use a secure connection (SSL/TLS) in order to /OPER.");
+		{
+			addmultiline(&iConf.plaintext_policy_oper_message, "You need to use a secure connection (SSL/TLS) in order to /OPER.");
+			addmultiline(&iConf.plaintext_policy_oper_message, "See https://www.unrealircd.org/docs/FAQ#oper-requires-tls");
+		}
 		else if (iConf.plaintext_policy_oper == POLICY_WARN)
-			safe_strdup(iConf.plaintext_policy_oper_message, "WARNING: You /OPER'ed up from an insecure connection. Please consider using SSL/TLS.");
+			addmultiline(&iConf.plaintext_policy_oper_message, "WARNING: You /OPER'ed up from an insecure connection. Please consider using SSL/TLS.");
 	}
 
 	if (!iConf.outdated_tls_policy_user_message)
@@ -7674,9 +7709,9 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 				else if (!strcmp(cepp->ce_varname, "server"))
 					tempiConf.plaintext_policy_server = policy_strtoval(cepp->ce_vardata);
 				else if (!strcmp(cepp->ce_varname, "user-message"))
-					safe_strdup(tempiConf.plaintext_policy_user_message, cepp->ce_vardata);
+					addmultiline(&tempiConf.plaintext_policy_user_message, cepp->ce_vardata);
 				else if (!strcmp(cepp->ce_varname, "oper-message"))
-					safe_strdup(tempiConf.plaintext_policy_oper_message, cepp->ce_vardata);
+					addmultiline(&tempiConf.plaintext_policy_oper_message, cepp->ce_vardata);
 			}
 		}
 		else if (!strcmp(cep->ce_varname, "outdated-tls-policy"))
@@ -7782,12 +7817,15 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 		}
 		else if (!strcmp(cep->ce_varname, "allowed-channelchars"))
 		{
-			if (!strcmp(cep->ce_vardata, "ascii"))
-				tempiConf.allowed_channelchars = ALLOWED_CHANNELCHARS_ASCII;
-			else if (!strcmp(cep->ce_vardata, "utf8"))
-				tempiConf.allowed_channelchars = ALLOWED_CHANNELCHARS_UTF8;
-			else if (!strcmp(cep->ce_vardata, "any"))
-				tempiConf.allowed_channelchars = ALLOWED_CHANNELCHARS_ANY;
+			tempiConf.allowed_channelchars = allowed_channelchars_strtoval(cep->ce_vardata);
+		}
+		else if (!strcmp(cep->ce_varname, "hide-idle-time"))
+		{
+			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			{
+				if (!strcmp(cepp->ce_varname, "policy"))
+					tempiConf.hide_idle_time = hideidletime_strtoval(cepp->ce_vardata);
+			}
 		}
 		else
 		{
@@ -8976,15 +9014,37 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 		else if (!strcmp(cep->ce_varname, "allowed-channelchars"))
 		{
 			CheckNull(cep);
-			if (strcmp(cep->ce_vardata, "ascii") &&
-			    strcmp(cep->ce_vardata, "utf8") &&
-			    strcmp(cep->ce_vardata, "any"))
+			if (!allowed_channelchars_strtoval(cep->ce_vardata))
 			{
 				config_error("%s:%i: set::allowed-channelchars: value should be one of: 'ascii', 'utf8' or 'any'",
 				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
 				errors++;
 			}
 		}
+		else if (!strcmp(cep->ce_varname, "hide-idle-time"))
+		{
+			for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
+			{
+				CheckNull(cepp);
+				if (!strcmp(cepp->ce_varname, "policy"))
+				{
+					if (!hideidletime_strtoval(cepp->ce_vardata))
+					{
+						config_error("%s:%i: set::hide-idle-time::policy: value should be one of: 'never', 'always', 'usermode' or 'oper-usermode'",
+							     cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
+						errors++;
+					}
+				}
+				else
+				{
+					config_error_unknown(cepp->ce_fileptr->cf_filename,
+						cepp->ce_varlinenum, "set::hide-idle-time",
+						cepp->ce_varname);
+					errors++;
+					continue;
+				}
+			}
+		}
 		else
 		{
 			int used = 0;
diff --git a/src/dns.c b/src/dns.c
@@ -420,7 +420,7 @@ void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct host
 	switch ((n = connect_server(r->linkblock, r->client, he2)))
 	{
 		case 0:
-			sendto_ops_and_log("Connecting to server %s[%s].", r->linkblock->servername, ip);
+			sendto_ops_and_log("Trying to activate link with server %s[%s]...", r->linkblock->servername, ip);
 			break;
 		case -1:
 			sendto_ops_and_log("Couldn't connect to server %s[%s].", r->linkblock->servername, ip);
diff --git a/src/ircd.c b/src/ircd.c
@@ -312,7 +312,7 @@ EVENT(try_connections)
 				break;
 
 		if (!deny && connect_server(aconf, NULL, NULL) == 0)
-			sendto_realops("Connection to %s[%s] activated.",
+			sendto_ops_and_log("Trying to activate link with server %s[%s]...",
 				aconf->servername, aconf->outgoing.hostname);
 
 	}
@@ -393,7 +393,7 @@ EVENT(handshake_timeout)
 			if (client->serv && *client->serv->by)
 			{
 				/* If this is a handshake timeout to an outgoing server then notify ops & log it */
-				sendto_ops_and_log("Connection handshake timeout while connecting to server '%s' (%s)",
+				sendto_ops_and_log("Connection handshake timeout while trying to link to server '%s' (%s)",
 					client->name, client->ip?client->ip:"<unknown ip>");
 			}
 
@@ -534,9 +534,6 @@ EVENT(check_deadsockets)
 	{
 		if (!IsDead(client))
 			abort(); /* impossible */
-#ifdef DEBUGMODE
-		ircd_log(LOG_ERROR, "Closing deadsock2: %s", client->name);
-#endif
 		list_del(&client->client_node);
 		free_client(client);
 	}
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
@@ -84,6 +84,12 @@ MOD_INIT()
 
 	setcfg();
 
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_configrun);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
 	if (!channeldb_loaded)
 	{
 		/* If this is the first time that our module is loaded, then read the database. */
@@ -98,12 +104,6 @@ MOD_INIT()
 		}
 		channeldb_loaded = 1;
 	}
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_configrun);
-	return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
 	EventAdd(modinfo->handle, "channeldb_write_channeldb", write_channeldb_evt, NULL, CHANNELDB_SAVE_EVERY*1000, 0);
 	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
 	{
diff --git a/src/modules/connect.c b/src/modules/connect.c
@@ -137,7 +137,7 @@ CMD_FUNC(cmd_connect)
 	switch (retval = connect_server(aconf, client, NULL))
 	{
 	  case 0:
-		  sendnotice(client, "*** Connecting to %s[%s].",
+		  sendnotice(client, "*** Trying to activate link with server %s[%s]...",
 		      aconf->servername, aconf->outgoing.hostname);
 		  break;
 	  case -1:
diff --git a/src/modules/ident_lookup.c b/src/modules/ident_lookup.c
@@ -63,8 +63,20 @@ static EVENT(check_ident_timeout)
 
 	list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
 	{
-		if (IsIdentLookup(client) && ((TStime() - client->local->firsttime) > IDENT_CONNECT_TIMEOUT))
-			ident_lookup_failed(client);
+		if (IsIdentLookup(client))
+		{
+			if (IsIdentLookupSent(client))
+			{
+				/* set::ident::connect-timeout */
+				if ((TStime() - client->local->firsttime) > IDENT_CONNECT_TIMEOUT)
+					ident_lookup_failed(client);
+			} else
+			{
+				/* set::ident::read-timeout */
+				if ((TStime() - client->local->firsttime) > IDENT_READ_TIMEOUT)
+					ident_lookup_failed(client);
+			}
+		}
 	}
 }
 
diff --git a/src/modules/join.c b/src/modules/join.c
@@ -441,7 +441,7 @@ void _do_join(Client *client, int parc, char *parv[])
 
 				new_message(client, NULL, &mtags);
 
-				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, NULL,
+				sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 				               ":%s PART %s :%s",
 				               client->name, channel->chname, "Left all channels");
 				sendto_server(client, 0, 0, mtags, ":%s PART %s :Left all channels", client->name, channel->chname);
diff --git a/src/modules/nick.c b/src/modules/nick.c
@@ -1085,7 +1085,7 @@ int _register_user(Client *client, char *nick, char *username, char *umode, char
 			sendnumeric(client, RPL_SNOMASK, get_snomask_string_raw(user->snomask));
 
 		if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_WARN))
-			sendnotice(client, "%s", iConf.plaintext_policy_user_message);
+			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));
@@ -1300,7 +1300,7 @@ int AllowClient(Client *client, char *username)
 
 	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
 	{
-		exit_client(client, NULL, iConf.plaintext_policy_user_message);
+		exit_client(client, NULL, iConf.plaintext_policy_user_message->line);
 		return 0;
 	}
 
diff --git a/src/modules/oper.c b/src/modules/oper.c
@@ -114,7 +114,7 @@ CMD_FUNC(cmd_oper)
 	/* set::plaintext-policy::oper 'deny' */
 	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_DENY))
 	{
-		sendnotice(client, "%s", iConf.plaintext_policy_oper_message);
+		sendnotice_multiline(client, iConf.plaintext_policy_oper_message);
 		sendto_snomask_global
 		    (SNO_OPER, "Failed OPER attempt by %s (%s@%s) [not using SSL/TLS]",
 		    client->name, client->user->username, client->local->sockhost);
@@ -315,7 +315,7 @@ CMD_FUNC(cmd_oper)
 	/* set::plaintext-policy::oper 'warn' */
 	if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_WARN))
 	{
-		sendnotice(client, "%s", iConf.plaintext_policy_oper_message);
+		sendnotice_multiline(client, iConf.plaintext_policy_oper_message);
 		sendto_snomask_global
 		    (SNO_OPER, "OPER %s [%s] used an insecure (non-SSL/TLS) connection to /OPER.",
 		    client->name, name);
diff --git a/src/modules/sajoin.c b/src/modules/sajoin.c
@@ -221,10 +221,10 @@ CMD_FUNC(cmd_sajoin)
 					channel = lp->channel;
 
 					new_message(target, NULL, &mtags);
-					sendto_channel(channel, target, NULL, 0, 0, SEND_LOCAL, NULL,
+					sendto_channel(channel, target, NULL, 0, 0, SEND_LOCAL, mtags,
 					               ":%s PART %s :%s",
 					               target->name, channel->chname, "Left all channels");
-					sendto_server(client, 0, 0, mtags, ":%s PART %s :Left all channels", target->name, channel->chname);
+					sendto_server(NULL, 0, 0, mtags, ":%s PART %s :Left all channels", target->name, channel->chname);
 					if (MyConnect(target))
 						RunHook4(HOOKTYPE_LOCAL_PART, target, channel, mtags, "Left all channels");
 					free_message_tags(mtags);
@@ -275,5 +275,22 @@ CMD_FUNC(cmd_sajoin)
 				strlcat(jbuf, ",", sizeof jbuf);
 			strlcat(jbuf, name, sizeof jbuf);
 		}
+		if (did_anything)
+		{
+			if (!sjmode)
+			{
+				//sendnotice(target, "*** You were forced to join %s", jbuf);
+				sendto_umode_global(UMODE_OPER, "%s used SAJOIN to make %s join %s", client->name, target->name, jbuf);
+				ircd_log(LOG_SACMDS,"SAJOIN: %s used SAJOIN to make %s join %s",
+					client->name, target->name, jbuf);
+			}
+			else
+			{
+				//sendnotice(target, "*** You were forced to join %s with '%c'", jbuf, sjmode);
+				sendto_umode_global(UMODE_OPER, "%s used SAJOIN to make %s join %c%s", client->name, target->name, sjmode, jbuf);
+				ircd_log(LOG_SACMDS,"SAJOIN: %s used SAJOIN to make %s join %c%s",
+					client->name, target->name, sjmode, jbuf);
+			}
+		}
 	}
 }
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -159,6 +159,22 @@ CMD_FUNC(cmd_sapart)
 	parv[0] = target->name; // nick
 	parv[1] = parv[2]; // chan
 	parv[2] = comment ? commentx : NULL; // comment
+	if (comment)
+	{
+		//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)",
+			client->name, target->name, parv[1], comment);
+	}
+	else
+	{
+		//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",
+			client->name, target->name, parv[1]);
+	}
 	do_cmd(target, NULL, "PART", comment ? 3 : 2, parv);
 	/* target may be killed now due to the part reason @ spamfilter */
 }
diff --git a/src/modules/server.c b/src/modules/server.c
@@ -547,6 +547,7 @@ CMD_FUNC(cmd_server)
 		sendto_umode_global(UMODE_OPER, "Directly linked server %s provided a hopcount of %d, while 1 was expected",
 		                                servername, hop);
 		exit_client(client, NULL, "Invalid SERVER message, hop count must be 1");
+		return;
 	}
 	client->hopcount = hop;
 
diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c
@@ -407,6 +407,13 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[], 
 				if (parv[3])
 				{
 					strlcpy(target->user->svid, parv[3], sizeof(target->user->svid));
+					if(MyConnect(target))
+						/* Notify user */
+						sendnumeric(target, RPL_LOGGEDIN,
+							   BadPtr(target->name) ? "*" : target->name,
+							   BadPtr(target->user->username) ? "*" : target->user->username,
+							   BadPtr(target->user->realhost) ? "*" : target->user->realhost,
+							   target->user->svid, target->user->svid);
 					user_account_login(recv_mtags, target);
 				}
 				else
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
@@ -1028,8 +1028,8 @@ CMD_FUNC(cmd_tempshun)
 	}
 	if (!MyUser(target))
 	{
-		sendto_one(target->direction, NULL, ":%s TEMPSHUN %s :%s",
-			client->id, target->id, comment);
+		sendto_one(target, NULL, ":%s TEMPSHUN %c%s :%s",
+		           client->id, remove ? '-' : '+', target->id, comment);
 	} else {
 		char buf[1024];
 		if (!remove)
@@ -1747,7 +1747,7 @@ CMD_FUNC(cmd_eline)
 /** Helper function for cmd_spamfilter, explaining usage. */
 void spamfilter_usage(Client *client)
 {
-	sendnotice(client, "Use: /spamfilter [add|del|remove|+|-] [-simple|-regex|-posix] [type] [action] [tkltime] [tklreason] [regex]");
+	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");
 }
@@ -1755,13 +1755,12 @@ void spamfilter_usage(Client *client)
 /** Helper function for cmd_spamfilter, explaining usage has changed. */
 void spamfilter_new_usage(Client *client, char *parv[])
 {
-	sendnotice(client, "Unknown match-type '%s'. Must be one of: -regex (new fast PCRE regexes), "
-	                 "-posix (old unreal 3.2.x posix regexes) or "
+	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/-posix/-simple field!!");
+		sendnotice(client, "Using the old 3.2.x /SPAMFILTER syntax? Note the new -regex/-simple field!!");
 
 	spamfilter_usage(client);
 }
@@ -4209,8 +4208,11 @@ CMD_FUNC(cmd_tkl_del)
 	RunHook2(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))
 	{
@@ -4252,7 +4254,7 @@ CMD_FUNC(cmd_tkl_del)
  *
  * [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, posix.
+ * [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)
diff --git a/src/modules/whois.c b/src/modules/whois.c
@@ -315,6 +315,12 @@ CMD_FUNC(cmd_whois)
 			
 			RunHook2(HOOKTYPE_WHOIS, client, target);
 
+			if (IsOper(client) && MyUser(target) && IsShunned(target))
+			{
+				sendto_one(client, NULL, ":%s %d %s %s :is shunned",
+				           me.name, RPL_WHOISSPECIAL, client->name, target->name);
+			}
+
 			if (target->user->swhois && !hideoper)
 			{
 				SWhois *s;
@@ -336,7 +342,7 @@ CMD_FUNC(cmd_whois)
 			 * Umode +I hides an oper's idle time from regular users.
 			 * -Nath.
 			 */
-			if (MyConnect(target) && (IsOper(client) || !(target->umodes & UMODE_HIDLE)))
+			if (MyConnect(target) && !hide_idle_time(client, target))
 			{
 				sendnumeric(client, RPL_WHOISIDLE, name,
 				    TStime() - target->local->last, target->local->firsttime);
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -8,10 +8,18 @@
 
 #include "unrealircd.h"
 
-#define MSG_WHO 	"WHO"
+/* Module header */
+ModuleHeader MOD_HEADER
+  = {
+	"whox",
+	"5.0",
+	"command /who",
+	"UnrealIRCd Team",
+	"unrealircd-5",
+    };
 
-#define FLAGS_MARK	0x400000 /* marked client (was hybnotice) */
 
+/* Defines */
 #define FIELD_CHANNEL	0x0001
 #define FIELD_HOP	0x0002
 #define FIELD_FLAGS	0x0004
@@ -43,13 +51,14 @@
 #define WHO_ADD 1
 #define WHO_DEL 0
 
-#define SetMark(x) ((x)->flags |= FLAGS_MARK)
-#define ClearMark(x) ((x)->flags &= ~FLAGS_MARK)
-#define IsMarked(x) ((x)->flags & FLAGS_MARK)
-
 #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;
@@ -61,30 +70,46 @@ struct who_format
 	int show_ip;
 };
 
+/* 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, char *parv[], char **orig_mask, struct who_format *fmt);
-
-ModuleHeader MOD_HEADER
-  = {
-	"whox",
-	"5.0",
-	"command /who",
-	"UnrealIRCd Team",
-	"unrealircd-5",
-    };
+char *whox_md_serialize(ModData *m);
+void whox_md_unserialize(char *str, ModData *m);
+void whox_md_free(ModData *md);
 
 MOD_INIT()
 {
+	ModDataInfo mreq;
+
 	MARK_AS_OFFICIAL_MODULE(modinfo);
-	if (!CommandAdd(modinfo->handle, MSG_WHO, cmd_whox, MAXPARA, CMD_USER))
+
+	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;
 }
@@ -99,6 +124,29 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
+/** whox module data operations: serialize (rare) */
+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(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
@@ -792,9 +840,10 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
 		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) &&
-				(!(acptr->umodes & UMODE_HIDLE) || IsOper(client) ||
-				(client == acptr)) ? TStime() - acptr->local->last : 0));
+		{
+			append_format(str, sizeof str, &pos, " %d",
+				(int)((MyUser(acptr) && !hide_idle_time(client, acptr)) ? (TStime() - acptr->local->last) : 0));
+		}
 		if (HasField(fmt, FIELD_ACCOUNT))
 			append_format(str, sizeof str, &pos, " %s", (!isdigit(*acptr->user->svid)) ? acptr->user->svid : "0");
 		if (HasField(fmt, FIELD_OPLEVEL))
diff --git a/src/random.c b/src/random.c
@@ -438,18 +438,16 @@ void init_random()
 {
 	struct {
 #ifndef _WIN32
-		struct timeval nowt;	/* time */
+		struct timeval nowt;		/* time */
 		char rnd[32];			/* /dev/urandom */
 #else
-		MEMORYSTATUS mstat;		/* memory status */
 		struct _timeb nowt;		/* time */
+		MEMORYSTATUS mstat;		/* memory status */
 #endif
 	} rdat;
 
 #ifndef _WIN32
 	int fd;
-#else
-	MEMORYSTATUS mstat;
 #endif
 
 	_rs_stir();
diff --git a/src/serv.c b/src/serv.c
@@ -405,9 +405,6 @@ CMD_FUNC(cmd_error)
 
 	para = (parc > 1 && *parv[1] != '\0') ? parv[1] : "<>";
 
-	Debug((DEBUG_ERROR, "Received ERROR message from %s: %s",
-	    client->name, para));
-
 	/* Errors from untrusted sources only go to the junk snomask
 	 * (which is only for debugging issues and such).
 	 * This to prevent flooding and confusing IRCOps by
@@ -415,13 +412,15 @@ CMD_FUNC(cmd_error)
 	 */
 	if (!IsServer(client) && !client->serv)
 	{
-		sendto_snomask(SNO_JUNK, "ERROR from %s -- %s",
+		sendto_snomask(SNO_JUNK, "ERROR from server %s: %s",
 			get_client_name(client, FALSE), para);
 		return;
 	}
 
-	sendto_umode_global(UMODE_OPER, "ERROR from %s -- %s",
-	    get_client_name(client, FALSE), para);
+	sendto_umode_global(UMODE_OPER, "ERROR from server %s: %s",
+	                    get_client_name(client, FALSE), para);
+	ircd_log(LOG_ERROR, "ERROR from server %s: %s",
+	                    get_client_name(client, FALSE), para);
 }
 
 /** Save the tunefile (such as: highest seen connection count) */
@@ -1279,3 +1278,32 @@ 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 *serv, FORMAT_STRING(const char *fmt), ...)
+{
+	va_list vl;
+	static char buf[1024], buf2[512];
+
+	va_start(vl, fmt);
+	vsnprintf(buf2, sizeof(buf2), fmt, vl);
+	va_end(vl);
+
+	if (IsServer(serv))
+	{
+		/* An already established link is now lost. Broadcast this to all opers. */
+		snprintf(buf, sizeof(buf), "Lost server link to %s: %s",
+			get_client_name(serv, FALSE), buf2);
+		sendto_umode_global(UMODE_OPER, "%s", buf);
+	} else {
+		/* A link attempt failed. Only send this to local opers (can be noisy every xx seconds). */
+		snprintf(buf, sizeof(buf), "Unable to link with server %s: %s",
+			get_client_name(serv, FALSE), buf2);
+		sendto_umode(UMODE_OPER, "%s", buf);
+	}
+
+	/* Always log! */
+	ircd_log(LOG_ERROR, "%s", buf);
+}
diff --git a/src/socket.c b/src/socket.c
@@ -1086,12 +1086,7 @@ void read_packet(int fd, int revents, void *data)
 				return;
 
 			if (IsServer(client) || client->serv) /* server or outgoing connection */
-			{
-				sendto_umode_global(UMODE_OPER, "Lost connection to %s: Read error",
-					get_client_name(client, FALSE));
-				ircd_log(LOG_ERROR, "Lost connection to %s: Read error",
-					get_client_name(client, FALSE));
-			}
+				lost_server_link(client, "Read error or connection closed.");
 
 			exit_client(client, NULL, "Read error");
 			return;
diff --git a/src/tls.c b/src/tls.c
@@ -270,8 +270,25 @@ static int setup_dh_params(SSL_CTX *ctx)
 /** Disable SSL/TLS protocols as set by config */
 void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
 {
-	/* OpenSSL has two mechanisms for protocol version control:
-	 *
+	/* 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, 1);
+	}
+#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 SSL/TLS version.
@@ -937,18 +954,12 @@ static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *clien
 			         (client->serv && client->serv->conf) ? client->serv->conf->outgoing.port : -1,
 			         client->name);
 		}
-		sendto_umode(UMODE_OPER, "Lost connection to %s: %s: %s%s%s",
-			get_client_name(client, FALSE), ssl_func, ssl_errstr, additional_info, extra);
-		/* This is a connect() that fails, we don't broadcast that for non-SSL either (noisy) */
+		lost_server_link(client, "%s: %s%s%s", ssl_func, ssl_errstr, additional_info, extra);
 	} else
-	if ((IsServer(client) || (client->serv && client->serv->conf)) && (where != SAFE_SSL_WRITE))
+	if (IsServer(client) || (client->serv && client->serv->conf))
 	{
-		/* if server (either judged by IsServer() or clearly an outgoing connect),
-		 * and not writing (since otherwise deliver_it will take care of the error), THEN
-		 * send a closing link error...
-		 */
-		sendto_umode_global(UMODE_OPER, "Lost connection to %s: %s: %d (%s%s)",
-			get_client_name(client, FALSE), ssl_func, ssl_error, ssl_errstr, additional_info);
+		/* Either a trusted fully established server (incoming) or an outgoing server link (established or not) */
+		lost_server_link(client, "%s: %s%s", ssl_func, ssl_errstr, additional_info);
 	}
 
 	if (errtmp)
diff --git a/src/user.c b/src/user.c
@@ -699,3 +699,29 @@ void user_account_login(MessageTag *recv_mtags, Client *client)
 {
 	RunHook2(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;
+	}
+}
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.3.1"
+id="5.0.4"
 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.3.1"
+    version="5.0.4.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.3.1
+AppVerName=UnrealIRCd 5.0.4
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org
diff --git a/unrealircd.in b/unrealircd.in
@@ -33,11 +33,8 @@ if [ "$1" = "start" ] ; then
 	if [ ! -f @CONFDIR@/unrealircd.conf ]; then
 		echo ""
 		echo "The configuration file does not exist (@CONFDIR@/unrealircd.conf)."
-		echo "* Create one by following:"
-		echo "  https://www.unrealircd.org/docs/Installing_from_source#Creating_a_configuration_file"
-		echo "* Or if you are upgrading from version 3.2.x then read:"
-		echo "  https://www.unrealircd.org/docs/Upgrading_from_3.2.x and"
-		echo "  https://www.unrealircd.org/docs/UnrealIRCd_files_and_directories"
+		echo "Create one using the example configuration file, see the documentation:"
+		echo "https://www.unrealircd.org/docs/Installing_from_source#Creating_a_configuration_file"
 		exit 1
 	fi
 	@BINDIR@/unrealircd