diff --git a/Config b/Config
index 9501232..68a5906 100755
--- a/Config
+++ b/Config
@@ -95,8 +95,19 @@ fi
if [ "x$MAKE" = "x" ]; then
if [ "x$MAKEFLAGS" = "x" ]; then
if make --version 2>&1|grep -q "GNU Make"; then
- echo "Running with 4 concurrent build processes by default (make -j4)."
- export MAKE='make -j4'
+ LOWMEM=0
+ if [ -f /proc/meminfo ]; then
+ FREEKB="`cat /proc/meminfo |grep MemAvailable|awk '{ print $2 }'`"
+ if [ "$FREEKB" != "" -a "$FREEKB" -lt 768000 ]; then
+ LOWMEM=1
+ fi
+ fi
+ if [ "$LOWMEM" = 0 ]; then
+ echo "Running with 4 concurrent build processes by default (make -j4)."
+ export MAKE='make -j4'
+ else
+ echo "System detected with less than 750MB available memory, not forcing parallel build."
+ fi
fi
fi
fi
@@ -336,7 +347,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.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6 unrealircd-5.0.5.1 unrealircd-5.0.5 unrealircd-5.0.4 unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0"
+UNREALRELEASES="unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6 unrealircd-5.0.5.1 unrealircd-5.0.5 unrealircd-5.0.4 unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0"
if [ -f "config.settings" ]; then
. ./config.settings
else
@@ -639,6 +650,13 @@ if [ "$REMOTEINC" = "1" ] ; then
CURLDIR=`eval echo $cc` # modified
fi
done
+ if [ "x$CURLDIR" != "x" ]; then
+ "$CURLDIR/bin/curl-config" --libs 1>/dev/null 2>&1
+ if [ "$?" != 0 ]; then
+ echo "Curl from $CURLDIR seems unusable ($CURLDIR/bin/curl-config does not exist)"
+ CURLDIR=""
+ fi
+ fi
fi
fi
@@ -749,6 +767,13 @@ fi
rm -f config.settings
cat > config.settings << __EOF__
#
+# These are the settings saved from running './Config'.
+# Note that it is not recommended to edit config.settings by hand!
+# Chances are you misunderstand what a variable does or what the
+# supported values are. You better just re-run the ./Config script
+# and answer appropriately there, to get a correct config.settings
+# file.
+#
BASEPATH="$BASEPATH"
BINDIR="$BINDIR"
DATADIR="$DATADIR"
diff --git a/Makefile.in b/Makefile.in
index c7016a4..1857495 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -34,11 +34,11 @@ FROMDOS=/home/cmunk/bin/4dos
#
#XCFLAGS=-O -g -export-dynamic
-IRCDLIBS=@IRCDLIBS@ @PCRE2_LIBS@ @ARGON2_LIBS@ @CARES_LIBS@ @PTHREAD_LIBS@
+IRCDLIBS=@IRCDLIBS@ @PCRE2_LIBS@ @ARGON2_LIBS@ @CARES_LIBS@ @SODIUM_LIBS@ @PTHREAD_LIBS@
CRYPTOLIB=@CRYPTOLIB@
OPENSSLINCLUDES=
-XCFLAGS=@PTHREAD_CFLAGS@ @PCRE2_CFLAGS@ @ARGON2_CFLAGS@ @CARES_CFLAGS@ @CFLAGS@ @HARDEN_CFLAGS@ @CPPFLAGS@
+XCFLAGS=@PTHREAD_CFLAGS@ @PCRE2_CFLAGS@ @ARGON2_CFLAGS@ @CARES_CFLAGS@ @SODIUM_CFLAGS@ @CFLAGS@ @HARDEN_CFLAGS@ @CPPFLAGS@
#
# use the following on MIPS:
#CFLAGS= -systype bsd43 -DSYSTYPE_BSD43 -I$(INCLUDEDIR)
diff --git a/Makefile.windows b/Makefile.windows
index e89ceea..f63f1ec 100644
--- a/Makefile.windows
+++ b/Makefile.windows
@@ -24,6 +24,11 @@ MT=mt
#ARGON2_INC_DIR="C:\dev\argon2\include"
#ARGON2LIB="Argon2RefDll.lib"
+### SODIUM ###
+#SODIUM_LIB_DIR="C:\dev\unrealircd-5-libs\libsodium\......."
+#SODIUM_INC_DIR="C:\dev\unrealircd-5-libs\libsodium\......."
+#SODIUMLIB="libsodium.lib"
+
### C-ARES ####
#CARES_LIB_DIR="C:\dev\c-ares\vc\cares\dll-release"
#CARES_INC_DIR="C:\dev\c-ares"
@@ -94,6 +99,13 @@ ARGON2_INC=/I "$(ARGON2_INC_DIR)"
ARGON2_LIB=/LIBPATH:"$(ARGON2_LIB_DIR)"
!ENDIF
+!IFDEF SODIUM_INC_DIR
+SODIUM_INC=/I "$(SODIUM_INC_DIR)"
+!ENDIF
+!IFDEF SODIUM_LIB_DIR
+SODIUM_LIB=/LIBPATH:"$(SODIUM_LIB_DIR)"
+!ENDIF
+
!IFDEF USE_REMOTEINC
CURLCFLAGS=/D USE_LIBCURL
CURLOBJ=SRC/URL.OBJ
@@ -125,13 +137,13 @@ DBGLFLAG=/debug
MODDBGCFLAG=/LDd /MD /Zi
!ENDIF
-STDOPTIONS=$(PCRE2_INC) $(ARGON2_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /nologo \
+STDOPTIONS=$(PCRE2_INC) $(ARGON2_INC) $(SODIUM_INC) $(CARES_INC) $(LIBCURL_INC) $(LIBRESSL_INC) /J /I ./INCLUDE /nologo \
$(CURLCFLAGS) /D FD_SETSIZE=16384 $(SSLCFLAGS) /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE \
/D FAKELAG_CONFIGURABLE=1 \
/W3 /wd4267 /wd4101 /wd4018 /wd4244 /wd4996 /WX \
/analyze:ruleset extras\VStudioAnalyze.ruleset
STDLIBS=$(CARES_LIB) $(CARESLIB) $(PCRE2_LIB) $(PCRE2LIB) $(ARGON2_LIB) $(ARGON2LIB) \
- $(LIBRESSL_LIB) $(SSLLIB) $(LIBCURL_LIB) $(CURLLIB)
+ $(SODIUM_LIB) $(SODIUMLIB) $(LIBRESSL_LIB) $(SSLLIB) $(LIBCURL_LIB) $(CURLLIB)
CFLAGS=$(DBGCFLAG) $(STDOPTIONS) /c /Fosrc/
CFLAGSST=$(DBGCFLAGST) $(STDOPTIONS) /c /Fosrc/
LFLAGS=kernel32.lib user32.lib gdi32.lib shell32.lib ws2_32.lib advapi32.lib \
@@ -161,7 +173,7 @@ EXP_OBJ_FILES=SRC/CHANNEL.OBJ SRC/SEND.OBJ SRC/SOCKET.OBJ \
SRC/DISPATCH.OBJ SRC/API-ISUPPORT.OBJ SRC/API-COMMAND.OBJ \
SRC/API-CLICAP.OBJ SRC/API-MESSAGETAG.OBJ SRC/API-HISTORY-BACKEND.OBJ \
SRC/API-EXTBAN.OBJ SRC/API-EFUNCTIONS.OBJ SRC/CRYPT_BLOWFISH.OBJ \
- SRC/OPERCLASS.OBJ SRC/UPDCONF.OBJ SRC/CRASHREPORT.OBJ \
+ SRC/OPERCLASS.OBJ SRC/UPDCONF.OBJ SRC/CRASHREPORT.OBJ SRC/UNREALDB.OBJ \
SRC/OPENSSL_HOSTNAME_VALIDATION.OBJ \
SRC/UTF8.OBJ $(CURLOBJ)
@@ -289,9 +301,12 @@ DLL_FILES=SRC/MODULES/CLOAK.DLL \
SRC/MODULES/ECHO-MESSAGE.DLL \
SRC/MODULES/USERIP-TAG.DLL \
SRC/MODULES/USERHOST-TAG.DLL \
+ SRC/MODULES/BOT-TAG.DLL \
+ SRC/MODULES/REPLY-TAG.DLL \
SRC/MODULES/REQUIRE-MODULE.DLL \
SRC/MODULES/IDENT_LOOKUP.DLL \
SRC/MODULES/HISTORY.DLL \
+ SRC/MODULES/CHATHISTORY.DLL \
SRC/MODULES/TARGETFLOODPROT.DLL \
SRC/MODULES/TYPING-INDICATOR.DLL \
SRC/MODULES/CLIENTTAGDENY.DLL
@@ -530,6 +545,12 @@ src/operclass.obj: src/operclass.c $(INCLUDES) ./include/dbuf.h
src/updconf.obj: src/updconf.c $(INCLUDES) ./include/dbuf.h
$(CC) $(CFLAGS) src/updconf.c
+src/crashreport.obj: src/crashreport.c $(INCLUDES) ./include/dbuf.h
+ $(CC) $(CFLAGS) src/crashreport.c
+
+src/unrealdb.obj: src/unrealdb.c $(INCLUDES) ./include/dbuf.h
+ $(CC) $(CFLAGS) src/unrealdb.c
+
src/utf8.obj: src/utf8.c $(INCLUDES) ./include/dbuf.h
$(CC) $(CFLAGS) src/utf8.c
@@ -1079,6 +1100,12 @@ src/modules/userip-tag.dll: src/modules/userip-tag.c $(INCLUDES)
src/modules/userhost-tag.dll: src/modules/userhost-tag.c $(INCLUDES)
$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/userhost-tag.c $(MODLFLAGS)
+src/modules/bot-tag.dll: src/modules/bot-tag.c $(INCLUDES)
+ $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/bot-tag.c $(MODLFLAGS)
+
+src/modules/reply-tag.dll: src/modules/reply-tag.c $(INCLUDES)
+ $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/reply-tag.c $(MODLFLAGS)
+
src/modules/require-module.dll: src/modules/require-module.c $(INCLUDES)
$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/require-module.c $(MODLFLAGS)
@@ -1088,6 +1115,9 @@ src/modules/ident_lookup.dll: src/modules/ident_lookup.c $(INCLUDES)
src/modules/history.dll: src/modules/history.c $(INCLUDES)
$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/history.c $(MODLFLAGS)
+src/modules/chathistory.dll: src/modules/chathistory.c $(INCLUDES)
+ $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/chathistory.c $(MODLFLAGS)
+
src/modules/targetfloodprot.dll: src/modules/targetfloodprot.c $(INCLUDES)
$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/targetfloodprot.c $(MODLFLAGS)
diff --git a/configure b/configure
index 24c93e6..11fec73 100755
--- 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.9.
+# Generated by GNU Autoconf 2.69 for unrealircd 5.2.0.1.
#
# Report bugs to .
#
@@ -580,8 +580,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='unrealircd'
PACKAGE_TARNAME='unrealircd'
-PACKAGE_VERSION='5.0.9'
-PACKAGE_STRING='unrealircd 5.0.9'
+PACKAGE_VERSION='5.2.0.1'
+PACKAGE_STRING='unrealircd 5.2.0.1'
PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
PACKAGE_URL='https://unrealircd.org/'
@@ -641,6 +641,8 @@ build_cpu
build
CARES_LIBS
CARES_CFLAGS
+SODIUM_LIBS
+SODIUM_CFLAGS
ARGON2_LIBS
ARGON2_CFLAGS
PCRE2_LIBS
@@ -663,13 +665,13 @@ BUILDDIR
DYNAMIC_LDFLAGS
MODULEFLAGS
CRYPTOLIB
-EGREP
-GREP
-CPP
HARDEN_BINLDFLAGS
HARDEN_BINCFLAGS
HARDEN_LDFLAGS
HARDEN_CFLAGS
+EGREP
+GREP
+CPP
GMAKE
MAKER
OBJEXT
@@ -750,6 +752,7 @@ with_no_operoverride
with_operoverride_verify
with_system_pcre2
with_system_argon2
+with_system_sodium
with_system_cares
enable_ssl
enable_dynamic_linking
@@ -773,6 +776,8 @@ PCRE2_CFLAGS
PCRE2_LIBS
ARGON2_CFLAGS
ARGON2_LIBS
+SODIUM_CFLAGS
+SODIUM_LIBS
CARES_CFLAGS
CARES_LIBS'
@@ -1325,7 +1330,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.9 to adapt to many kinds of systems.
+\`configure' configures unrealircd 5.2.0.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1391,7 +1396,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of unrealircd 5.0.9:";;
+ short | recursive ) echo "Configuration of unrealircd 5.2.0.1:";;
esac
cat <<\_ACEOF
@@ -1450,6 +1455,8 @@ Optional Packages:
discovered using pkg-config
--without-system-argon2 Use bundled version instead of system argon2
library. Normally autodetected via pkg-config
+ --without-system-sodium Use bundled version instead of system sodium
+ library. Normally autodetected via pkg-config
--without-system-cares Use bundled version instead of system c-ares.
Normally autodetected via pkg-config.
@@ -1473,6 +1480,9 @@ Some influential environment variables:
ARGON2_CFLAGS
C compiler flags for ARGON2, overriding pkg-config
ARGON2_LIBS linker flags for ARGON2, overriding pkg-config
+ SODIUM_CFLAGS
+ C compiler flags for SODIUM, overriding pkg-config
+ SODIUM_LIBS linker flags for SODIUM, overriding pkg-config
CARES_CFLAGS
C compiler flags for CARES, overriding pkg-config
CARES_LIBS linker flags for CARES, overriding pkg-config
@@ -1544,7 +1554,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-unrealircd configure 5.0.9
+unrealircd configure 5.2.0.1
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1721,6 +1731,37 @@ fi
} # ac_fn_c_try_cpp
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists, giving a warning if it cannot be compiled using
@@ -1812,37 +1853,6 @@ fi
} # ac_fn_c_check_header_mongrel
-# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
-# -------------------------------------------------------
-# Tests whether HEADER exists and can be compiled using the include files in
-# INCLUDES, setting the cache variable VAR accordingly.
-ac_fn_c_check_header_compile ()
-{
- as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-$as_echo_n "checking for $2... " >&6; }
-if eval \${$3+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-$4
-#include <$2>
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- eval "$3=yes"
-else
- eval "$3=no"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-eval ac_res=\$$3
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
- eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_c_check_header_compile
-
# ac_fn_c_check_func LINENO FUNC VAR
# ----------------------------------
# Tests whether FUNC exists, setting the cache variable VAR accordingly
@@ -1913,7 +1923,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.9, which was
+It was created by unrealircd $as_me 5.2.0.1, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -2313,7 +2323,7 @@ _ACEOF
# Major version number (e.g.: Y in X.Y.Z)
-UNREAL_VERSION_MAJOR="0"
+UNREAL_VERSION_MAJOR="2"
cat >>confdefs.h <<_ACEOF
#define UNREAL_VERSION_MAJOR $UNREAL_VERSION_MAJOR
@@ -2321,7 +2331,7 @@ _ACEOF
# Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR="9"
+UNREAL_VERSION_MINOR="0"
cat >>confdefs.h <<_ACEOF
#define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -2331,7 +2341,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=""
+UNREAL_VERSION_SUFFIX=".1"
cat >>confdefs.h <<_ACEOF
#define UNREAL_VERSION_SUFFIX "$UNREAL_VERSION_SUFFIX"
@@ -3775,6 +3785,636 @@ fi
fi
+case $host_cpu in #(
+ i?86|amd64|x86_64) :
+ ac_cv_c_bigendian=no
+ ;; #(
+ *) :
+ ;;
+esac
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+ if ${ac_cv_prog_CPP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # Double quotes because CPP needs to be expanded
+ for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+ do
+ ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer to if __STDC__ is defined, since
+ # exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include
+#else
+# include
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+ break
+fi
+
+ done
+ ac_cv_prog_CPP=$CPP
+
+fi
+ CPP=$ac_cv_prog_CPP
+else
+ ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer to if __STDC__ is defined, since
+ # exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include
+#else
+# include
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$GREP"; then
+ ac_path_GREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in grep ggrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+ # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'GREP' >> "conftest.nl"
+ "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_GREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_GREP="$ac_path_GREP"
+ ac_path_GREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_GREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_GREP"; then
+ as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+ then ac_cv_path_EGREP="$GREP -E"
+ else
+ if test -z "$EGREP"; then
+ ac_path_EGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in egrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+ # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'EGREP' >> "conftest.nl"
+ "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_EGREP="$ac_path_EGREP"
+ ac_path_EGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_EGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_EGREP"; then
+ as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_EGREP=$EGREP
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+#include
+#include
+#include
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_header_stdc=yes
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "free" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+ if test "$cross_compiling" = yes; then :
+ :
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+#include
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+ (('a' <= (c) && (c) <= 'i') \
+ || ('j' <= (c) && (c) <= 'r') \
+ || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (XOR (islower (i), ISLOWER (i))
+ || toupper (i) != TOUPPER (i))
+ return 2;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+ inttypes.h stdint.h unistd.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
+$as_echo_n "checking whether byte ordering is bigendian... " >&6; }
+if ${ac_cv_c_bigendian+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_c_bigendian=unknown
+ # See if we're dealing with a universal compiler.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifndef __APPLE_CC__
+ not a universal capable compiler
+ #endif
+ typedef int dummy;
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ # Check for potential -arch flags. It is not universal unless
+ # there are at least two -arch flags with different values.
+ ac_arch=
+ ac_prev=
+ for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do
+ if test -n "$ac_prev"; then
+ case $ac_word in
+ i?86 | x86_64 | ppc | ppc64)
+ if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then
+ ac_arch=$ac_word
+ else
+ ac_cv_c_bigendian=universal
+ break
+ fi
+ ;;
+ esac
+ ac_prev=
+ elif test "x$ac_word" = "x-arch"; then
+ ac_prev=arch
+ fi
+ done
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ if test $ac_cv_c_bigendian = unknown; then
+ # See if sys/param.h defines the BYTE_ORDER macro.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+ #include
+
+int
+main ()
+{
+#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
+ && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
+ && LITTLE_ENDIAN)
+ bogus endian macros
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ # It does; now see whether it defined to BIG_ENDIAN or not.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+ #include
+
+int
+main ()
+{
+#if BYTE_ORDER != BIG_ENDIAN
+ not big endian
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_c_bigendian=yes
+else
+ ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ fi
+ if test $ac_cv_c_bigendian = unknown; then
+ # See if defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+
+int
+main ()
+{
+#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
+ bogus endian macros
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ # It does; now see whether it defined to _BIG_ENDIAN or not.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include
+
+int
+main ()
+{
+#ifndef _BIG_ENDIAN
+ not big endian
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_c_bigendian=yes
+else
+ ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ fi
+ if test $ac_cv_c_bigendian = unknown; then
+ # Compile a test program.
+ if test "$cross_compiling" = yes; then :
+ # Try to guess by grepping values from an object file.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+short int ascii_mm[] =
+ { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
+ short int ascii_ii[] =
+ { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
+ int use_ascii (int i) {
+ return ascii_mm[i] + ascii_ii[i];
+ }
+ short int ebcdic_ii[] =
+ { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+ short int ebcdic_mm[] =
+ { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+ int use_ebcdic (int i) {
+ return ebcdic_mm[i] + ebcdic_ii[i];
+ }
+ extern int foo;
+
+int
+main ()
+{
+return use_ascii (foo) == use_ebcdic (foo);
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then
+ ac_cv_c_bigendian=yes
+ fi
+ if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then
+ if test "$ac_cv_c_bigendian" = unknown; then
+ ac_cv_c_bigendian=no
+ else
+ # finding both strings is unlikely to happen, but who knows?
+ ac_cv_c_bigendian=unknown
+ fi
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_includes_default
+int
+main ()
+{
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+ u.l = 1;
+ return u.c[sizeof (long int) - 1] == 1;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ ac_cv_c_bigendian=no
+else
+ ac_cv_c_bigendian=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
+$as_echo "$ac_cv_c_bigendian" >&6; }
+ case $ac_cv_c_bigendian in #(
+ yes)
+
+$as_echo "#define NATIVE_BIG_ENDIAN 1" >>confdefs.h
+;; #(
+ no)
+
+$as_echo "#define NATIVE_LITTLE_ENDIAN 1" >>confdefs.h
+ ;; #(
+ universal)
+ as_fn_error $? "universal endianness is not supported - compile separately and use lipo(1)" "$LINENO" 5
+
+ ;; #(
+ *)
+ as_fn_error $? "unknown endianness" "$LINENO" 5 ;;
+ esac
+
+
# We want to check for compiler flag support, but there is no way to make
# clang's "argument unused" warning fatal. So we invoke the compiler through a
# wrapper script that greps for this message.
@@ -5291,403 +5931,6 @@ if test "$ac_cv_ip6" = "no"; then
as_fn_error $? "Your system does not support IPv6" "$LINENO" 5
fi
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
-$as_echo_n "checking how to run the C preprocessor... " >&6; }
-# On Suns, sometimes $CPP names a directory.
-if test -n "$CPP" && test -d "$CPP"; then
- CPP=
-fi
-if test -z "$CPP"; then
- if ${ac_cv_prog_CPP+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- # Double quotes because CPP needs to be expanded
- for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
- do
- ac_preproc_ok=false
-for ac_c_preproc_warn_flag in '' yes
-do
- # Use a header file that comes with gcc, so configuring glibc
- # with a fresh cross-compiler works.
- # Prefer to if __STDC__ is defined, since
- # exists even on freestanding compilers.
- # On the NeXT, cc -E runs the code through the compiler's parser,
- # not just through cpp. "Syntax error" is here to catch this case.
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#ifdef __STDC__
-# include
-#else
-# include
-#endif
- Syntax error
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-
-else
- # Broken: fails on valid input.
-continue
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
- # OK, works on sane cases. Now check whether nonexistent headers
- # can be detected and how.
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
- # Broken: success on invalid input.
-continue
-else
- # Passes both tests.
-ac_preproc_ok=:
-break
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-done
-# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
-rm -f conftest.i conftest.err conftest.$ac_ext
-if $ac_preproc_ok; then :
- break
-fi
-
- done
- ac_cv_prog_CPP=$CPP
-
-fi
- CPP=$ac_cv_prog_CPP
-else
- ac_cv_prog_CPP=$CPP
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
-$as_echo "$CPP" >&6; }
-ac_preproc_ok=false
-for ac_c_preproc_warn_flag in '' yes
-do
- # Use a header file that comes with gcc, so configuring glibc
- # with a fresh cross-compiler works.
- # Prefer to if __STDC__ is defined, since
- # exists even on freestanding compilers.
- # On the NeXT, cc -E runs the code through the compiler's parser,
- # not just through cpp. "Syntax error" is here to catch this case.
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#ifdef __STDC__
-# include
-#else
-# include
-#endif
- Syntax error
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-
-else
- # Broken: fails on valid input.
-continue
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
- # OK, works on sane cases. Now check whether nonexistent headers
- # can be detected and how.
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
- # Broken: success on invalid input.
-continue
-else
- # Passes both tests.
-ac_preproc_ok=:
-break
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-done
-# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
-rm -f conftest.i conftest.err conftest.$ac_ext
-if $ac_preproc_ok; then :
-
-else
- { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
-$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
-if ${ac_cv_path_GREP+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- if test -z "$GREP"; then
- ac_path_GREP_found=false
- # Loop through the user's path and test for each of PROGNAME-LIST
- as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
-do
- IFS=$as_save_IFS
- test -z "$as_dir" && as_dir=.
- for ac_prog in grep ggrep; do
- for ac_exec_ext in '' $ac_executable_extensions; do
- ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
- as_fn_executable_p "$ac_path_GREP" || continue
-# Check for GNU ac_path_GREP and select it if it is found.
- # Check for GNU $ac_path_GREP
-case `"$ac_path_GREP" --version 2>&1` in
-*GNU*)
- ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
-*)
- ac_count=0
- $as_echo_n 0123456789 >"conftest.in"
- while :
- do
- cat "conftest.in" "conftest.in" >"conftest.tmp"
- mv "conftest.tmp" "conftest.in"
- cp "conftest.in" "conftest.nl"
- $as_echo 'GREP' >> "conftest.nl"
- "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
- diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
- as_fn_arith $ac_count + 1 && ac_count=$as_val
- if test $ac_count -gt ${ac_path_GREP_max-0}; then
- # Best one so far, save it but keep looking for a better one
- ac_cv_path_GREP="$ac_path_GREP"
- ac_path_GREP_max=$ac_count
- fi
- # 10*(2^10) chars as input seems more than enough
- test $ac_count -gt 10 && break
- done
- rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
-esac
-
- $ac_path_GREP_found && break 3
- done
- done
- done
-IFS=$as_save_IFS
- if test -z "$ac_cv_path_GREP"; then
- as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
- fi
-else
- ac_cv_path_GREP=$GREP
-fi
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
-$as_echo "$ac_cv_path_GREP" >&6; }
- GREP="$ac_cv_path_GREP"
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
-$as_echo_n "checking for egrep... " >&6; }
-if ${ac_cv_path_EGREP+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
- then ac_cv_path_EGREP="$GREP -E"
- else
- if test -z "$EGREP"; then
- ac_path_EGREP_found=false
- # Loop through the user's path and test for each of PROGNAME-LIST
- as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
-do
- IFS=$as_save_IFS
- test -z "$as_dir" && as_dir=.
- for ac_prog in egrep; do
- for ac_exec_ext in '' $ac_executable_extensions; do
- ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
- as_fn_executable_p "$ac_path_EGREP" || continue
-# Check for GNU ac_path_EGREP and select it if it is found.
- # Check for GNU $ac_path_EGREP
-case `"$ac_path_EGREP" --version 2>&1` in
-*GNU*)
- ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
-*)
- ac_count=0
- $as_echo_n 0123456789 >"conftest.in"
- while :
- do
- cat "conftest.in" "conftest.in" >"conftest.tmp"
- mv "conftest.tmp" "conftest.in"
- cp "conftest.in" "conftest.nl"
- $as_echo 'EGREP' >> "conftest.nl"
- "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
- diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
- as_fn_arith $ac_count + 1 && ac_count=$as_val
- if test $ac_count -gt ${ac_path_EGREP_max-0}; then
- # Best one so far, save it but keep looking for a better one
- ac_cv_path_EGREP="$ac_path_EGREP"
- ac_path_EGREP_max=$ac_count
- fi
- # 10*(2^10) chars as input seems more than enough
- test $ac_count -gt 10 && break
- done
- rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
-esac
-
- $ac_path_EGREP_found && break 3
- done
- done
- done
-IFS=$as_save_IFS
- if test -z "$ac_cv_path_EGREP"; then
- as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
- fi
-else
- ac_cv_path_EGREP=$EGREP
-fi
-
- fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
-$as_echo "$ac_cv_path_EGREP" >&6; }
- EGREP="$ac_cv_path_EGREP"
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
-$as_echo_n "checking for ANSI C header files... " >&6; }
-if ${ac_cv_header_stdc+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include
-#include
-#include
-#include
-
-int
-main ()
-{
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- ac_cv_header_stdc=yes
-else
- ac_cv_header_stdc=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-
-if test $ac_cv_header_stdc = yes; then
- # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include
-
-_ACEOF
-if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
- $EGREP "memchr" >/dev/null 2>&1; then :
-
-else
- ac_cv_header_stdc=no
-fi
-rm -f conftest*
-
-fi
-
-if test $ac_cv_header_stdc = yes; then
- # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include
-
-_ACEOF
-if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
- $EGREP "free" >/dev/null 2>&1; then :
-
-else
- ac_cv_header_stdc=no
-fi
-rm -f conftest*
-
-fi
-
-if test $ac_cv_header_stdc = yes; then
- # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
- if test "$cross_compiling" = yes; then :
- :
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include
-#include
-#if ((' ' & 0x0FF) == 0x020)
-# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
-# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
-#else
-# define ISLOWER(c) \
- (('a' <= (c) && (c) <= 'i') \
- || ('j' <= (c) && (c) <= 'r') \
- || ('s' <= (c) && (c) <= 'z'))
-# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
-#endif
-
-#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
-int
-main ()
-{
- int i;
- for (i = 0; i < 256; i++)
- if (XOR (islower (i), ISLOWER (i))
- || toupper (i) != TOUPPER (i))
- return 2;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_run "$LINENO"; then :
-
-else
- ac_cv_header_stdc=no
-fi
-rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
- conftest.$ac_objext conftest.beam conftest.$ac_ext
-fi
-
-fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
-$as_echo "$ac_cv_header_stdc" >&6; }
-if test $ac_cv_header_stdc = yes; then
-
-$as_echo "#define STDC_HEADERS 1" >>confdefs.h
-
-fi
-
-# On IRIX 5.3, sys/types and inttypes.h are conflicting.
-for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
- inttypes.h stdint.h unistd.h
-do :
- as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
- cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
-
-fi
-
-done
-
-
ac_fn_c_check_header_mongrel "$LINENO" "sys/syslog.h" "ac_cv_header_sys_syslog_h" "$ac_includes_default"
if test "x$ac_cv_header_sys_syslog_h" = xyes; then :
@@ -6265,6 +6508,14 @@ else
fi
+# Check whether --with-system-sodium was given.
+if test "${with_system_sodium+set}" = set; then :
+ withval=$with_system_sodium;
+else
+ with_system_sodium=yes
+fi
+
+
# Check whether --with-system-cares was given.
if test "${with_system_cares+set}" = set; then :
withval=$with_system_cares;
@@ -7200,6 +7451,129 @@ cd $cur_dir
fi
+has_system_sodium="no"
+if test "x$with_system_sodium" = "xyes"; then :
+
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SODIUM" >&5
+$as_echo_n "checking for SODIUM... " >&6; }
+
+if test -n "$SODIUM_CFLAGS"; then
+ pkg_cv_SODIUM_CFLAGS="$SODIUM_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsodium >= 1.0.16\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libsodium >= 1.0.16") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_SODIUM_CFLAGS=`$PKG_CONFIG --cflags "libsodium >= 1.0.16" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$SODIUM_LIBS"; then
+ pkg_cv_SODIUM_LIBS="$SODIUM_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsodium >= 1.0.16\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "libsodium >= 1.0.16") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_SODIUM_LIBS=`$PKG_CONFIG --libs "libsodium >= 1.0.16" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi
+ if test $_pkg_short_errors_supported = yes; then
+ SODIUM_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libsodium >= 1.0.16" 2>&1`
+ else
+ SODIUM_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libsodium >= 1.0.16" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$SODIUM_PKG_ERRORS" >&5
+
+ has_system_sodium=no
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ has_system_sodium=no
+else
+ SODIUM_CFLAGS=$pkg_cv_SODIUM_CFLAGS
+ SODIUM_LIBS=$pkg_cv_SODIUM_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ has_system_sodium=yes
+if test "x$PRIVATELIBDIR" != "x"; then :
+ rm -f "$PRIVATELIBDIR/"libsodium*
+fi
+fi
+fi
+
+if test "$has_system_sodium" = "no"; then :
+
+sodium_version="1.0.18"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting sodium library" >&5
+$as_echo "extracting sodium library" >&6; }
+cur_dir=`pwd`
+cd extras
+rm -rf sodium-$sodium_version sodium
+if test "x$ac_cv_path_GUNZIP" = "x" ; then
+ tar xfz libsodium.tar.gz
+else
+ cp libsodium.tar.gz libsodium.tar.gz.bak
+ gunzip -f libsodium.tar.gz
+ cp libsodium.tar.gz.bak libsodium.tar.gz
+ tar xf libsodium.tar
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling sodium library" >&5
+$as_echo "compiling sodium library" >&6; }
+cd libsodium-$sodium_version
+save_cflags="$CFLAGS"
+CFLAGS="$orig_cflags"
+export CFLAGS
+./configure --prefix=$cur_dir/extras/sodium --libdir=$PRIVATELIBDIR --enable-shared --disable-static --enable-opt || exit 1
+CFLAGS="$save_cflags"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling sodium resolver library" >&5
+$as_echo "compiling sodium resolver library" >&6; }
+$ac_cv_prog_MAKER || exit 1
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing sodium resolver library" >&5
+$as_echo "installing sodium resolver library" >&6; }
+$ac_cv_prog_MAKER install || exit 1
+SODIUM_CFLAGS="-I$cur_dir/extras/sodium/include"
+
+SODIUM_LIBS=
+if test -n "$ac_cv_path_PKGCONFIG"; then :
+ SODIUM_LIBS="`$ac_cv_path_PKGCONFIG --libs libsodium.pc`"
+fi
+if test -z "$SODIUM_LIBS"; then :
+ SODIUM_LIBS="-L$PRIVATELIBDIR -lsodium"
+fi
+
+cd $cur_dir
+
+fi
+
has_system_cares="no"
if test "x$with_system_cares" = "xyes"; then :
@@ -7301,7 +7675,7 @@ cd c-ares-$cares_version
save_cflags="$CFLAGS"
CFLAGS="$orig_cflags"
export CFLAGS
-./configure --prefix=$cur_dir/extras/c-ares --libdir=$PRIVATELIBDIR --enable-shared || exit 1
+./configure --prefix=$cur_dir/extras/c-ares --libdir=$PRIVATELIBDIR --enable-shared --disable-tests || exit 1
CFLAGS="$save_cflags"
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling c-ares resolver library" >&5
$as_echo "compiling c-ares resolver library" >&6; }
@@ -8492,7 +8866,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.9, which was
+This file was extended by unrealircd $as_me 5.2.0.1, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -8555,7 +8929,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.9
+unrealircd config.status 5.2.0.1
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 8573023..4246558 100644
--- 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.9], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [5.2.0.1], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
AC_CONFIG_SRCDIR([src/ircd.c])
AC_CONFIG_HEADER([include/setup.h])
AC_CONFIG_AUX_DIR([autoconf])
@@ -30,17 +30,17 @@ UNREAL_VERSION_GENERATION=["5"]
AC_DEFINE_UNQUOTED([UNREAL_VERSION_GENERATION], [$UNREAL_VERSION_GENERATION], [Generation version number (e.g.: X for X.Y.Z)])
# Major version number (e.g.: Y in X.Y.Z)
-UNREAL_VERSION_MAJOR=["0"]
+UNREAL_VERSION_MAJOR=["2"]
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=["9"]
+UNREAL_VERSION_MINOR=["0"]
AC_DEFINE_UNQUOTED([UNREAL_VERSION_MINOR], [$UNREAL_VERSION_MINOR], [Minor version number (e.g.: Z for X.Y.Z)])
# The version suffix such as a beta marker or release candidate
# marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a
# string instead of an integer because it contains arbitrary data.
-UNREAL_VERSION_SUFFIX=[""]
+UNREAL_VERSION_SUFFIX=[".1"]
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)
@@ -83,6 +83,18 @@ AC_CHECK_LIB(descrypt, crypt,
[AC_DEFINE([HAVE_CRYPT], [], [Define if you have crypt])
IRCDLIBS="$IRCDLIBS-lcrypt "])])
+dnl Check for big-endian system, even though these hardly exist anymore...
+AS_CASE([$host_cpu],
+ [i?86|amd64|x86_64],
+ [ac_cv_c_bigendian=no]
+)
+AC_C_BIGENDIAN(
+ AC_DEFINE(NATIVE_BIG_ENDIAN, 1, [machine is bigendian]),
+ AC_DEFINE(NATIVE_LITTLE_ENDIAN, 1, [machine is littleendian]),
+ AC_MSG_ERROR([unknown endianness]),
+ AC_MSG_ERROR([universal endianness is not supported - compile separately and use lipo(1)])
+)
+
dnl HARDENING START
dnl This is taken from https://github.com/kmcallister/autoharden
dnl With some very small modifications (to remove C++ checking for instance)
@@ -499,6 +511,7 @@ AC_ARG_WITH(operoverride-verify, [AS_HELP_STRING([--with-operoverride-verify], [
[AC_DEFINE([OPEROVERRIDE_VERIFY], [], [Define if you want opers to have to use /invite to join +s/+p channels])])])
AC_ARG_WITH(system-pcre2, [AS_HELP_STRING([--without-system-pcre2], [Use the system pcre2 package instead of bundled, discovered using pkg-config])], [], [with_system_pcre2=yes])
AC_ARG_WITH(system-argon2, [AS_HELP_STRING([--without-system-argon2], [Use bundled version instead of system argon2 library. Normally autodetected via pkg-config])], [], [with_system_argon2=yes])
+AC_ARG_WITH(system-sodium, [AS_HELP_STRING([--without-system-sodium], [Use bundled version instead of system sodium library. Normally autodetected via pkg-config])], [], [with_system_sodium=yes])
AC_ARG_WITH(system-cares, [AS_HELP_STRING([--without-system-cares], [Use bundled version instead of system c-ares. Normally autodetected via pkg-config.])], [], [with_system_cares=yes])
CHECK_SSL
CHECK_SSL_CTX_SET1_CURVES_LIST
@@ -625,6 +638,55 @@ AC_SUBST(ARGON2_LIBS)
cd $cur_dir
])
+dnl Use system sodium when available, unless --without-system-sodium
+has_system_sodium="no"
+AS_IF([test "x$with_system_sodium" = "xyes"],[
+PKG_CHECK_MODULES([SODIUM], [libsodium >= 1.0.16],[has_system_sodium=yes
+AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libsodium*])],[has_system_sodium=no])])
+
+AS_IF([test "$has_system_sodium" = "no"],[
+dnl REMEMBER TO CHANGE WITH A NEW SODIUM RELEASE!
+sodium_version="1.0.18"
+AC_MSG_RESULT(extracting sodium library)
+cur_dir=`pwd`
+cd extras
+dnl remove old sodium directory to force a recompile...
+dnl and remove its installation prefix just to clean things up.
+rm -rf sodium-$sodium_version sodium
+if test "x$ac_cv_path_GUNZIP" = "x" ; then
+ tar xfz libsodium.tar.gz
+else
+ cp libsodium.tar.gz libsodium.tar.gz.bak
+ gunzip -f libsodium.tar.gz
+ cp libsodium.tar.gz.bak libsodium.tar.gz
+ tar xf libsodium.tar
+fi
+AC_MSG_RESULT(compiling sodium library)
+cd libsodium-$sodium_version
+save_cflags="$CFLAGS"
+CFLAGS="$orig_cflags"
+export CFLAGS
+./configure --prefix=$cur_dir/extras/sodium --libdir=$PRIVATELIBDIR --enable-shared --disable-static --enable-opt || exit 1
+CFLAGS="$save_cflags"
+AC_MSG_RESULT(compiling sodium resolver library)
+$ac_cv_prog_MAKER || exit 1
+AC_MSG_RESULT(installing sodium resolver library)
+$ac_cv_prog_MAKER install || exit 1
+SODIUM_CFLAGS="-I$cur_dir/extras/sodium/include"
+AC_SUBST(SODIUM_CFLAGS)
+SODIUM_LIBS=
+dnl See c-ares's compilation section for more info on this hack.
+dnl ensure that we're linking against the bundled version
+dnl (we only reach this code if linking against the bundled version is desired).
+AS_IF([test -n "$ac_cv_path_PKGCONFIG"],
+ [SODIUM_LIBS="`$ac_cv_path_PKGCONFIG --libs libsodium.pc`"])
+dnl For when pkg-config isn't available
+AS_IF([test -z "$SODIUM_LIBS"],
+ [SODIUM_LIBS="-L$PRIVATELIBDIR -lsodium"])
+AC_SUBST(SODIUM_LIBS)
+cd $cur_dir
+])
+
dnl Use system c-ares when available, unless --without-system-cares.
has_system_cares="no"
AS_IF([test "x$with_system_cares" = "xyes"],[
@@ -654,7 +716,7 @@ cd c-ares-$cares_version
save_cflags="$CFLAGS"
CFLAGS="$orig_cflags"
export CFLAGS
-./configure --prefix=$cur_dir/extras/c-ares --libdir=$PRIVATELIBDIR --enable-shared || exit 1
+./configure --prefix=$cur_dir/extras/c-ares --libdir=$PRIVATELIBDIR --enable-shared --disable-tests || exit 1
CFLAGS="$save_cflags"
AC_MSG_RESULT(compiling c-ares resolver library)
$ac_cv_prog_MAKER || exit 1
diff --git a/doc/Config.header b/doc/Config.header
index 37f030c..849ed60 100644
--- a/doc/Config.header
+++ b/doc/Config.header
@@ -7,7 +7,7 @@
\___/|_| |_|_| \___|\__,_|_|\___/\_| \_| \____/\__,_|
Configuration Program
- for UnrealIRCd 5.0.9
+ for UnrealIRCd 5.2.0.1
This program will help you to compile your IRC server, and ask you
questions regarding the compile-time settings of it during the process.
@@ -22,7 +22,7 @@ https://www.unrealircd.org/docs/UnrealIRCd_5_documentation
The full release notes are available in doc/RELEASE-NOTES.md
For easier viewing, check out the latest online release notes at:
-https://github.com/unrealircd/unrealircd/blob/unreal50/doc/RELEASE-NOTES.md
+https://github.com/unrealircd/unrealircd/blob/unreal52/doc/RELEASE-NOTES.md
UnrealIRCd 5 is compatible with the following services:
* anope with the "unreal4" protocol module - version 2.0.7 or higher required!
diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md
index 94106d3..5379a77 100644
--- a/doc/RELEASE-NOTES.md
+++ b/doc/RELEASE-NOTES.md
@@ -1,7 +1,176 @@
-UnrealIRCd 5.0.9 Release Notes
-===============================
+UnrealIRCd 5.2.0.1 Release Notes
+=================================
-This release comes with several nice feature enhancements. There are no major bug fixes.
+About 5.2.0.1
+--------------
+5.2.0.1 fixes an issue with spamfilter that was present in 5.2.0.
+In channels spamfilters were processed for type ```p``` instead of ```c```.
+Existing 5.2.0 users on *NIX can upgrade without restart by running
+```./unrealircd hot-patch wrongspamfilter520```
+
+UnrealIRCd 5.2.0 is out!
+-------------------------
+
+This is UnrealIRCd 5.2.0, a release with lots of new features.
+The two main new features are: an improved and more flexible anti-flood block
+and channel history which can now be stored encrypted on disk and allows
+clients to fetch hundreds/thousands of lines.
+
+Upgrading and the 5.0.x series
+-------------------------------
+UnrealIRCd 5.2.0 is the direct successor to 5.0.9/5.0.9.1.
+There will be [no further 5.0.x releases](https://www.unrealircd.org/docs/FAQ#About_the_new_5.2.x_series),
+in particular there will be no 5.0.10.
+
+Only four bugs that affect a limited number of people/networks were fixed.
+UnrealIRCd 5.2.0 is mostly a feature release.
+Admins wishing to take a conservative approach don't need to rush an
+upgrade from 5.0.x to 5.2.0, they can wait for a 5.2.1 or 5.2.2 release.
+
+If you are upgrading from 5.0.9(.1) to 5.2.0 then feel free to try the new
+```./unrealircd upgrade``` command.
+
+The only configuration change is in the set::anti-flood block (as explained
+further down under *Enhancements*). When starting UnrealIRCd will give you
+clear instructions if anything needs to be changed (and what).
+This process is really minor, the server will usually tell you to just
+delete a few old lines from the configuration file.
+
+Enhancements
+-------------
+* The set::anti-flood block has been redone so you can have different limits
+ for *unknown-users* and *known-users*.
+ * As a reminder, by default, *known-users* are users who are identified
+ to services OR are on an IP that has been connected for over 2 hours
+ in the past X days. The exact definition of "known-users" is in the
+ [security-group block](https://www.unrealircd.org/docs/Security-group_block).
+ * See [here](https://www.unrealircd.org/docs/Anti-flood_settings)
+ for more information on the layout of the new set::anti-flood block.
+ * All violations of target-flood, nick-flood, join-flood, away-flood,
+ invite-flood, knock-flood, max-concurrent-conversations are now
+ reported to opers with the snomask ```f``` (flood).
+* Add support for database encryption. The way this works
+ is that you define an encryption password in a
+ [secret { } block](https://www.unrealircd.org/docs/Secret_block).
+ Then from the various modules you can refer to this secret
+ block, from
+ [set::reputation::db-secret](https://www.unrealircd.org/docs/Set_block#set::reputation),
+ [set::tkldb::db-secret](https://www.unrealircd.org/docs/Set_block#set::tkldb)
+ and [set::channeldb::db-secret](https://www.unrealircd.org/docs/Set_block#set::channeldb).
+ This way you can encrypt the reputation, TKL and channel
+ database for increased privacy.
+* Add optional support for
+ [persistent channel history](https://www.unrealircd.org/docs/Set_block#Persistent_channel_history):
+ * This stores channel history on disk for channels that have
+ both ```+H``` and ```+P``` set.
+ * If you enable this then we ALWAYS require you to set an
+ encryption password, as we do not allow storing of
+ channel history in plain text.
+ * If you enable the option, then the history is stored in
+ ```data/history/``` in individual .db files. No channel
+ names are visible in the filenames for optimal privacy.
+ * See [Persistent channel history](https://www.unrealircd.org/docs/Set_block#Persistent_channel_history)
+ on how to enable this. By default it is off.
+* Add support for IRCv3
+ [draft/chathistory](https://ircv3.net/specs/extensions/chathistory).
+* The maximums for channel mode ```+H``` have been raised and are now
+ different for ```+r``` (registered) and ```-r``` channels. For unregistered
+ channels the limit is now 200 lines / 31 days. For registered channels
+ the limit is 5000 lines / 31 days. The old limit for both was 200 lines / 7 days.
+ These maximums can be changed in the now slightly different
+ [set::history::channel::max-storage-per-channel](https://www.unrealircd.org/docs/Set_block#set::history)
+ block.
+* Add c-ares and libsodium version output to boot screen and /VERSION.
+* WHOX now supports displaying the
+ [reputation score](https://www.unrealircd.org/docs/Reputation_score).
+ If you are an IRCOp then you can use e.g. ```WHO * %cuhsnfmdaRr```.
+* Add ability to [spamfilter](https://www.unrealircd.org/docs/Spamfilter)
+ message tags via the new ```T``` target. Right now it would be unusual
+ to use this, but some day when we have more
+ [message tags](https://www.unrealircd.org/docs/Message_tags) it
+ may come in handy.
+* Support [```+draft/reply```](https://ircv3.net/specs/client-tags/reply) IRCv3
+ client tag. Can be used by bots (and others) to indicate to what message
+ people are replying to. This module, reply-tag, is loaded by default.
+* Send [```draft/bot```](https://ircv3.net/specs/extensions/bot-mode) IRCv3
+ message tag if the user has mode ```+B``` set.
+* [Websockets](https://www.unrealircd.org/docs/WebSocket_support):
+ add support for clients to negotiate an explicit type via
+ ```Sec-WebSocket-Protocol```, instead of only the default type from
+ [listen::websocket::type](https://www.unrealircd.org/docs/WebSocket_support#2._Enable_websocket_on_the_port).
+ This is based on an IRCv3 websocket draft specification.
+ Note that UnrealIRCd refuses type text if your configuration allows
+ non-UTF8 characters in channel or nick names because it would lead
+ to security and compatibility issues.
+* [set::restrict-commands](https://www.unrealircd.org/docs/Set_block#set::restrict-commands):
+ new option *exempt-tls* which allows SSL/TLS users to bypass a restriction.
+
+Fixes
+------
+* Server squiting the wrong side. Often harmless, but when (re)connecting
+ rapidly to multiple servers with autoconnect this could cause the
+ network to fall apart.
+* Forbid using [extended server bans](https://www.unrealircd.org/docs/Extended_server_bans)
+ in ZLINE/GZLINE since they won't work there.
+* Extended server ban ```~a:accname``` was not working for shun, and only
+ partially working for kline/gline.
+* More accurate /ELINE error message.
+
+Changed
+--------
+* Channel mode ```+H``` always showed time in minutes (```m```) until now.
+ From now on it will show it in minutes (```m```), hours (```h```) or
+ days (```d```) depending on the actual value. Eg ```+H 50:7d```.
+* If you ran ```./unrealircd stop``` we used to wait only 1 second.
+ From now on we will wait up to 10 seconds max. This gives UnrealIRCd
+ plenty of time to write database files.
+* If you have zero [log blocks](https://www.unrealircd.org/docs/Log_block)
+ then we already automatically logged errors to ```ircd.log```.
+ From now on we will log everything (not only errors) to that file.
+
+Removed
+--------
+* Version check for curl and openssl as nowadays they have ABI guarantees.
+
+Module coders / Developers
+---------------------------
+* New UnrealDB API and disk format, see
+ https://www.unrealircd.org/docs/Dev:UnrealDB
+* We now use libsodium for file encryption routines as well
+ as some helpers to lock/clear passwords in memory.
+* Updated ```HOOKTYPE_LOCAL_NICKCHANGE``` and
+ ```HOOKTYPE_REMOTE_NICKCHANGE``` to include an
+ ```MessageTag *mtags``` argument in the middle.
+ You can use ```#if UNREAL_VERSION_TIME>=202115``` to detect this.
+* Updated channel mode ```conv_param``` function to
+ include a ```Channel *channel``` argument at the end.
+ You can use ```#if UNREAL_VERSION_TIME>=202120``` to detect this.
+* New: ```ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, priority);```.
+ This can be used for modules to indicate they wish to be unloaded
+ before or after others. It is used by for example the channel
+ and history modules so they can save their databases before
+ channel mode modules or other modules get unloaded.
+* New CAP [```draft/chathistory```](https://ircv3.net/specs/extensions/chathistory).
+ If a client REQ's this CAP then UnrealIRCd won't send history on-join as
+ it assumes the client will fetch it when they feel the need for it.
+* New informative CAP:
+ [```unrealircd.org/history-backend```](https://www.unrealircd.org/history-backend)
+
+Reminder: UnrealIRCd 4 is no longer supported
+----------------------------------------------
+
+UnrealIRCd 4.x is [no longer supported](https://www.unrealircd.org/docs/UnrealIRCd_4_EOL).
+Admins must [upgrade to UnrealIRCd 5](https://www.unrealircd.org/docs/Upgrading_from_4.x).
+
+UnrealIRCd 5.0.9.1
+-------------------
+The only change between 5.0.9 and 5.0.9.1 is:
+* Build improvements on *NIX (faster compiling and lower memory requirements)
+* Windows version is unchanged and still 5.0.9
+
+UnrealIRCd 5.0.9
+-----------------
+The 5.0.9 release comes with several nice feature enhancements. There are no major bug fixes.
Enhancements:
* Changes to the "Client connecting" notice on IRC (for IRCOps):
@@ -38,7 +207,7 @@ Fixes:
missing.
Changes:
-* Add doc/KEYS which contains the public key(s) used to sign UnrealIRCd releases
+* Add ```doc/KEYS``` which contains the public key(s) used to sign UnrealIRCd releases
* The options set::anti-flood::unknown-flood-* have been renamed and
integrated in a new block called
[set::anti-flood::handshake-data-flood](https://www.unrealircd.org/docs/Set_block#set::anti-flood::handshake-data-flood).
@@ -49,18 +218,6 @@ That is, when in "auto" mode, which is like for 99% of the users.
Note that the system may still limit the actual number of connections
to a lower value, epending on the value of ```ulimit -n -H```.
-Reminder: UnrealIRCd 4 is no longer supported
-----------------------------------------------
-
-UnrealIRCd 4.x is [no longer supported](https://www.unrealircd.org/docs/UnrealIRCd_4_EOL).
-Admins must upgrade to UnrealIRCd 5.
-
-Upgrading from 4.x to 5.x?
-Then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5).
-Or, at the very least, check out
-[Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
-
-
UnrealIRCd 5.0.8
-----------------
diff --git a/doc/conf/modules.conf b/doc/conf/modules.conf
index 877f4b2..0d13245 100644
--- a/doc/conf/modules.conf
+++ b/doc/conf/modules.conf
@@ -162,6 +162,8 @@ loadmodule "extbans/securitygroup"; /* +b ~G */
loadmodule "account-notify";
loadmodule "account-tag";
loadmodule "batch";
+loadmodule "bot-tag";
+loadmodule "chathistory";
loadmodule "clienttagdeny";
loadmodule "echo-message";
loadmodule "labeled-response";
@@ -169,6 +171,7 @@ loadmodule "link-security";
loadmodule "message-ids";
loadmodule "message-tags";
loadmodule "plaintext-policy";
+loadmodule "reply-tag";
loadmodule "server-time";
loadmodule "sts";
loadmodule "typing-indicator";
@@ -186,7 +189,7 @@ loadmodule "history_backend_mem";
#loadmodule "history_backend_null";
loadmodule "ident_lookup";
loadmodule "jointhrottle";
-#loadmodule "targetfloodprot";
+loadmodule "targetfloodprot";
loadmodule "tkldb";
loadmodule "tls_antidos";
loadmodule "userhost-tag";
diff --git a/doc/conf/opers.conf b/doc/conf/opers.conf
index 4e2e913..4c6ffcf 100644
--- a/doc/conf/opers.conf
+++ b/doc/conf/opers.conf
@@ -5,7 +5,7 @@ oper acidvegas {
operclass netadmin;
require-modes z;
maxlogins 1;
- vhost super.nets;
+ vhost most.dangerous.motherfuck;
swhois "1,1 1,5 1,1 ";
swhois "1,1 1,5 1,7 1,5 1,7 1,5 1,1 ";
swhois "1,1 1,5 1,7 1,5 1,7 1,5 1,7 1,5 1,1 0 1 ";
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
index dec6240..117f512 100644
--- a/doc/conf/unrealircd.remote.conf
+++ b/doc/conf/unrealircd.remote.conf
@@ -26,6 +26,10 @@ listen { ip *; port 6667; options { clientsonly; } }
listen { ip *; port 6697; options { clientsonly; tls; } }
listen { ip *; port REDACTED; options { serversonly; tls; } }
+deny channel { channel "#pumpcoin"; reason "This channel has moved to #exchange"; redirect "#exchange"; }
+deny channel { channel "#dev"; reason "This channel has moved to #superbowl"; redirect "#superbowl"; }
+deny channel { channel "#help"; reason "This channel has moved to #superbowl"; redirect "#superbowl"; }
+
link irc.supernets.org {
incoming { mask REDACTED; }
outgoing {
@@ -106,7 +110,7 @@ set {
private-notice { connect-delay 3600; exempt-identified yes; exempt-reputation-score 100; }
}
#auto-join "#superbowl";
- oper-auto-join "#help";
+ oper-auto-join "#superbowl";
static-quit "EMO-QUIT";
static-part "EMO-PART";
who-limit 100;
@@ -141,28 +145,43 @@ set {
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;
- nick-flood 3:300;
- max-concurrent-conversations {
- users 5;
- new-user-every 60s;
+ everyone {
+ connect-flood 3:300;
+ handshake-data-flood {
+ amount 4k;
+ ban-action gzline;
+ ban-time 1h;
+ }
+ target-flood {
+ channel-notice 15:5;
+ channel-privmsg 45:5;
+ channel-tagmsg 15:5;
+ private-notice 10:5;
+ private-privmsg 30:5;
+ private-tagmsg 10:5;
+ }
}
- #target-flood {
- # channel-privmsg 45:5;
- # channel-notice 15:5;
- # channel-tagmsg 15:5;
- # private-privmsg 30:5;
- # private-notice 10:5;
- # private-tagmsg 10:5;
- #}
- handshake-data-flood {
- amount 4k;
- ban-action gzline;
- ban-time 1h;
+ known-users {
+ away-flood 3:300;
+ invite-flood 3:300;
+ join-flood 3:300;
+ knock-flood 3:300;
+ nick-flood 3:300;
+ max-concurrent-conversations {
+ users 5;
+ new-user-every 60s;
+ }
+ }
+ unknown-users {
+ away-flood 3:300;
+ invite-flood 3:300;
+ join-flood 3:300;
+ knock-flood 3:300;
+ nick-flood 3:300;
+ max-concurrent-conversations {
+ users 3;
+ new-user-every 60s;
+ }
}
}
default-bantime 30d;
@@ -183,19 +202,22 @@ set {
unauthorized "8,4 E N T E R T H E V O I D ";
}
antimixedutf8 {
- score 10;
+ score 8;
ban-action block;
ban-reason "8,4 E N T E R T H E V O I D ";
}
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; }
+ known-users { minimum-reputation-score 100; sasl-bypass yes; }
+ new-users { local-throttle 20:60; global-throttle 30:60; }
+ disabled-when { reputation-gathering 1w; start-delay 3m; }
}
history {
channel {
- playback-on-join { lines 100; time 1d; }
- max-storage-per-channel { lines 100; time 1d; }
+ playback-on-join { lines 100; time 1d; }
+ max-storage-per-channel {
+ registered { lines 100; time 1d; }
+ unregistered { lines 50; time 12h; }
+ }
}
}
hide-idle-time { policy usermode; }
@@ -206,4 +228,11 @@ hideserver {
disable-links yes;
map-deny-message "Denied";
links-deny-message "Denied";
+}
+
+security-group known-users {
+ identified yes;
+ webirc no;
+ tls no;
+ reputation-score 100;
}
\ No newline at end of file
diff --git a/extras/build-tests/nix/run-tests b/extras/build-tests/nix/run-tests
index 781a4c1..a25c101 100755
--- a/extras/build-tests/nix/run-tests
+++ b/extras/build-tests/nix/run-tests
@@ -40,6 +40,14 @@ else
./run -services atheme || exit 1
fi
+# Database writing/reading tests
+## unencrypted:
+./run -services none -boot tests/db/writing/* || exit 1
+./run -services none -keepdbs -boot tests/db/reading/* || exit 1
+## encrypted:
+./run -services none -include db_crypted.conf -boot tests/db/writing/* || exit 1
+./run -services none -include db_crypted.conf -keepdbs -boot tests/db/reading/* || exit 1
+
# Do cipherscan test at the end
# Has problems on non-Linux-64-bit, so we skip there:
if [ "$FREEBSD" = 0 -a "$HOSTNAME" != "ub18-ia32" ]; then
diff --git a/extras/build-tests/windows/build.bat b/extras/build-tests/windows/build.bat
index 9bcd00c..9eee847 100644
--- a/extras/build-tests/windows/build.bat
+++ b/extras/build-tests/windows/build.bat
@@ -78,11 +78,26 @@ if %ERRORLEVEL% NEQ 0 EXIT /B 1
cd unrealircd-tests
dir
+rem All tests except db:
"C:\Program Files\Git\bin\bash.exe" ./runwin
if %ERRORLEVEL% NEQ 0 EXIT /B 1
+rem Test unencrypted db's:
+"C:\Program Files\Git\bin\bash.exe" ./runwin -boot tests/db/writing/*
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+"C:\Program Files\Git\bin\bash.exe" ./runwin -keepdbs -boot tests/db/reading/*
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+
+rem Test encrypted db's:
+"C:\Program Files\Git\bin\bash.exe" ./runwin -include db_crypted.conf -boot tests/db/writing/*
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+"C:\Program Files\Git\bin\bash.exe" ./runwin -include db_crypted.conf -keepdbs -boot tests/db/reading/*
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+
goto end
+
+
:installerfailed
type setup.log
echo INSTALLATION FAILED
diff --git a/extras/build-tests/windows/compilecmd/vs2019.bat b/extras/build-tests/windows/compilecmd/vs2019.bat
index 607e069..690194c 100644
--- a/extras/build-tests/windows/compilecmd/vs2019.bat
+++ b/extras/build-tests/windows/compilecmd/vs2019.bat
@@ -15,4 +15,7 @@ PCRE2_LIB_DIR="c:\projects\unrealircd-5-libs\pcre2\lib" ^
PCRE2LIB="pcre2-8.lib" ^
ARGON2_LIB_DIR="c:\projects\unrealircd-5-libs\argon2\vs2015\build" ^
ARGON2_INC_DIR="c:\projects\unrealircd-5-libs\argon2\include" ^
-ARGON2LIB="Argon2RefDll.lib" %*
+ARGON2LIB="Argon2RefDll.lib" ^
+SODIUM_LIB_DIR="c:\projects\unrealircd-5-libs\libsodium\bin\x64\Release\v142\dynamic" ^
+SODIUM_INC_DIR="c:\projects\unrealircd-5-libs\libsodium\src\libsodium\include" ^
+SODIUMLIB="libsodium.lib" %*
diff --git a/extras/curlinstall b/extras/curlinstall
index 9dc63e6..6715ed9 100755
--- a/extras/curlinstall
+++ b/extras/curlinstall
@@ -90,7 +90,7 @@ cd "$OUTD" || exit 1
echo "Building and installing libcurl"
CPPFLAGS="-I$ARESPATH/include" ./configure --prefix=$UNREALDIR/extras/curl --libdir=$LIBDIR --enable-shared \
- --disable-thread --enable-ares=$ARESPATH --disable-ipv6
+ --enable-ares=$ARESPATH --with-openssl
cp -R $ARESPATH/lib ares
make && make install
diff --git a/extras/doxygen/Doxyfile b/extras/doxygen/Doxyfile
index ba9b5d0..579e7ca 100644
--- 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.9
+PROJECT_NUMBER = 5.2.0.1
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
diff --git a/extras/libsodium.tar.gz b/extras/libsodium.tar.gz
new file mode 100644
index 0000000..ca816de
Binary files /dev/null and b/extras/libsodium.tar.gz differ
diff --git a/include/dynconf.h b/include/dynconf.h
index 5d60edd..dbf4c4d 100644
--- a/include/dynconf.h
+++ b/include/dynconf.h
@@ -1,6 +1,7 @@
/************************************************************************
* Unreal Internet Relay Chat Daemon, include/dynconf.h
- * Copyright (C) 1999 Carsten Munk
+ * Copyright (C) 1999-2003 Carsten Munk
+ * Copyright (C) 2003-2021 Bram Matthys
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,6 +23,15 @@
#define DYNCONF_H
+typedef struct FloodSettings FloodSettings;
+
+struct FloodSettings {
+ FloodSettings *prev, *next;
+ char *name;
+ int limit[MAXFLOODOPTIONS];
+ long period[MAXFLOODOPTIONS];
+};
+
typedef struct NetworkConfiguration NetworkConfiguration;
struct NetworkConfiguration {
unsigned x_inah:1;
@@ -118,16 +128,7 @@ struct Configuration {
int handshake_data_flood_ban_action;
struct ChMode modes_on_join;
int level_on_join;
- unsigned char away_count;
- long away_period;
- unsigned char nick_count;
- long nick_period;
- unsigned char invite_count;
- long invite_period;
- unsigned char knock_count;
- long knock_period;
- unsigned char max_concurrent_conversations_users;
- unsigned char max_concurrent_conversations_new_user_every;
+ FloodSettings *floodsettings;
int ident_connect_timeout;
int ident_read_timeout;
long default_bantime;
@@ -234,15 +235,6 @@ extern MODVAR int ipv6_disabled;
#define MODES_ON_JOIN iConf.modes_on_join.mode
#define LEVEL_ON_JOIN iConf.level_on_join
-#define AWAY_PERIOD iConf.away_period
-#define AWAY_COUNT iConf.away_count
-#define NICK_PERIOD iConf.nick_period
-#define NICK_COUNT iConf.nick_count
-#define KNOCK_PERIOD iConf.knock_period
-#define KNOCK_COUNT iConf.knock_count
-#define INVITE_PERIOD iConf.invite_period
-#define INVITE_COUNT iConf.invite_count
-
#define IDENT_CONNECT_TIMEOUT iConf.ident_connect_timeout
#define IDENT_READ_TIMEOUT iConf.ident_read_timeout
@@ -325,17 +317,8 @@ struct SetCheck {
unsigned has_restrict_channelmodes:1;
unsigned has_restrict_extendedbans:1;
unsigned has_channel_command_prefix:1;
- unsigned has_anti_flood_handshake_data_flood_amount:1;
- unsigned has_anti_flood_handshake_data_flood_ban_action:1;
- unsigned has_anti_flood_handshake_data_flood_ban_time:1;
unsigned has_modes_on_join:1;
unsigned has_level_on_join:1;
- unsigned has_anti_flood_away_count:1;
- unsigned has_anti_flood_away_period:1;
- unsigned has_anti_flood_nick_flood:1;
- unsigned has_anti_flood_connect_flood:1;
- unsigned has_anti_flood_invite_flood:1;
- unsigned has_anti_flood_knock_flood:1;
unsigned has_ident_connect_timeout:1;
unsigned has_ident_read_timeout:1;
unsigned has_default_bantime:1;
diff --git a/include/h.h b/include/h.h
index faa5216..f1aa4fc 100644
--- a/include/h.h
+++ b/include/h.h
@@ -285,6 +285,7 @@ extern char *myctime(time_t);
extern char *short_date(time_t, char *buf);
extern char *long_date(time_t);
extern void exit_client(Client *client, MessageTag *recv_mtags, char *comment);
+extern void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char *comment);
extern void initstats(), tstats(Client *, char *);
extern char *check_string(char *);
extern char *make_nick_user_host(char *, char *, char *);
@@ -312,7 +313,7 @@ extern int find_str_match_link(Link *, char *);
extern void free_str_list(Link *);
extern Link *make_link();
extern Ban *make_ban();
-extern ClientUser *make_user(Client *);
+extern User *make_user(Client *);
extern Server *make_server();
extern Client *make_client(Client *, Client *);
extern Member *find_channel_link(Member *, Channel *);
@@ -447,6 +448,7 @@ extern void Auth_FreeAuthConfig(AuthConfig *as);
extern int Auth_Check(Client *cptr, AuthConfig *as, char *para);
extern char *Auth_Hash(int type, char *para);
extern int Auth_CheckError(ConfigEntry *ce);
+extern int Auth_AutoDetectHashType(char *hash);
extern void make_cloakedhost(Client *client, char *curr, char *buf, size_t buflen);
extern int channel_canjoin(Client *client, char *name);
@@ -545,9 +547,31 @@ extern void *safe_alloc(size_t size);
*/
#define raw_strldup(str, max) our_strldup(str, max)
+extern void *safe_alloc_sensitive(size_t size);
+
+/** Free previously allocate memory pointer - this is the sensitive version which
+ * may ONLY be called on allocations returned by safe_alloc_sensitive() / safe_strdup_sensitive().
+ * This will set the memory to all zeroes before actually deallocating.
+ * It also sets the pointer to NULL, since that would otherwise be common to forget.
+ * @note If you call this function on normally allocated memory (non-sensitive) then we will crash.
+ */
+#define safe_free_sensitive(x) do { if (x) sodium_free(x); x = NULL; } while(0)
+
+/** Free previous memory (if any) and then save a duplicate of the specified string -
+ * This is the 'sensitive' version which should only be used for HIGHLY sensitive data,
+ * as it wastes about 8000 bytes even if you only duplicate a string of 32 bytes (this is by design).
+ * @param dst The current pointer and the pointer where a new copy of the string will be stored.
+ * @param str The string you want to copy
+ */
+#define safe_strdup_sensitive(dst,str) do { if (dst) sodium_free(dst); if (!(str)) dst = NULL; else dst = our_strdup_sensitive(str); } while(0)
+
+/** Safely destroy a string in memory (but do not free!) */
+#define destroy_string(str) sodium_memzero(str, strlen(str))
+
/** @} */
extern char *our_strdup(const char *str);
extern char *our_strldup(const char *str, size_t max);
+extern char *our_strdup_sensitive(const char *str);
extern long config_checkval(char *value, unsigned short flags);
extern void config_status(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2)));
@@ -701,7 +725,8 @@ extern MODVAR void (*tkl_stats)(Client *cptr, int type, char *para, int *cnt);
extern MODVAR void (*tkl_sync)(Client *client);
extern MODVAR void (*cmd_tkl)(Client *client, MessageTag *recv_mtags, int parc, char *parv[]);
extern MODVAR int (*place_host_ban)(Client *client, BanAction action, char *reason, long duration);
-extern MODVAR int (*match_spamfilter)(Client *client, char *str_in, int type, char *target, int flags, TKL **rettk);
+extern MODVAR int (*match_spamfilter)(Client *client, char *str_in, int type, char *cmd, char *target, int flags, TKL **rettk);
+extern MODVAR int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, char *cmd);
extern MODVAR int (*join_viruschan)(Client *client, TKL *tk, int type);
extern MODVAR unsigned char *(*StripColors)(unsigned char *text);
extern MODVAR const char *(*StripControlCodes)(unsigned char *text);
@@ -787,6 +812,7 @@ extern char *clean_ban_mask(char *, int, Client *);
extern int find_invex(Channel *channel, Client *client);
extern void DoMD5(char *mdout, const char *src, unsigned long n);
extern char *md5hash(char *dst, const char *src, unsigned long n);
+extern char *sha256hash(char *dst, const char *src, unsigned long n);
extern MODVAR TKL *tklines[TKLISTLEN];
extern MODVAR TKL *tklines_ip_hash[TKLIPHASHLEN1][TKLIPHASHLEN2];
extern char *cmdname_by_spamftarget(int target);
@@ -944,8 +970,12 @@ extern void free_message_tags(MessageTag *m);
extern time_t server_time_to_unix_time(const char *tbuf);
extern int history_set_limit(char *object, int max_lines, long max_t);
extern int history_add(char *object, MessageTag *mtags, char *line);
-extern int history_request(Client *acptr, char *object, HistoryFilter *filter);
+extern HistoryResult *history_request(char *object, HistoryFilter *filter);
extern int history_destroy(char *object);
+extern int can_receive_history(Client *client);
+extern void history_send_result(Client *client, HistoryResult *r);
+extern void free_history_result(HistoryResult *r);
+extern void free_history_filter(HistoryFilter *f);
extern void special_delayed_unloading(void);
extern int write_int64(FILE *fd, uint64_t t);
extern int write_int32(FILE *fd, uint32_t t);
@@ -1004,3 +1034,41 @@ extern NameValuePrioList *find_nvplist(NameValuePrioList *list, char *name);
extern void free_nvplist(NameValuePrioList *lst);
extern char *get_connect_extinfo(Client *client);
extern char *unreal_strftime(char *str);
+extern void strtolower_safe(char *dst, char *src, int size);
+extern int running_interactively(void);
+extern void skip_whitespace(char **p);
+extern void read_until(char **p, char *stopchars);
+/* src/unrealdb.c start */
+extern UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block);
+extern int unrealdb_close(UnrealDB *c);
+extern char *unrealdb_test_db(const char *filename, char *secret_block);
+extern int unrealdb_write_int64(UnrealDB *c, uint64_t t);
+extern int unrealdb_write_int32(UnrealDB *c, uint32_t t);
+extern int unrealdb_write_int16(UnrealDB *c, uint16_t t);
+extern int unrealdb_write_str(UnrealDB *c, char *x);
+extern int unrealdb_write_char(UnrealDB *c, char t);
+extern int unrealdb_read_int64(UnrealDB *c, uint64_t *t);
+extern int unrealdb_read_int32(UnrealDB *c, uint32_t *t);
+extern int unrealdb_read_int16(UnrealDB *c, uint16_t *t);
+extern int unrealdb_read_str(UnrealDB *c, char **x);
+extern int unrealdb_read_char(UnrealDB *c, char *t);
+extern char *unrealdb_test_secret(char *name);
+extern UnrealDBConfig *unrealdb_copy_config(UnrealDBConfig *src);
+extern UnrealDBConfig *unrealdb_get_config(UnrealDB *db);
+extern void unrealdb_free_config(UnrealDBConfig *c);
+extern UnrealDBError unrealdb_get_error_code(void);
+extern char *unrealdb_get_error_string(void);
+/* src/unrealdb.c end */
+/* secret { } related stuff */
+extern Secret *find_secret(char *secret_name);
+extern void free_secret_cache(SecretCache *c);
+extern void free_secret(Secret *s);
+extern Secret *secrets;
+/* end */
+extern int check_password_strength(char *pass, int min_length, int strict, char **err);
+extern int valid_secret_password(char *pass, char **err);
+extern int flood_limit_exceeded(Client *client, FloodOption opt);
+extern FloodSettings *find_floodsettings_block(const char *name);
+extern FloodSettings *get_floodsettings_for_user(Client *client, FloodOption opt);
+extern MODVAR char *floodoption_names[];
+extern void flood_limit_exceeded_log(Client *client, char *floodname);
diff --git a/include/modules.h b/include/modules.h
index 30da89e..5bd22ec 100644
--- a/include/modules.h
+++ b/include/modules.h
@@ -241,14 +241,15 @@ typedef struct {
*
* This function pointer is NULL (unused) for modes without parameters.
* @param para The input parameter.
- * @param client The client that the mode request came from (can be NULL)
+ * @param client The client that the mode request came from (can be NULL!)
+ * @param channel The channel that the mode request came from (can be NULL!)
* @returns pointer to output string (temporary storage)
* @note The 'client' field will be NULL if for example called for set::modes-on-join.
- * @note You should probably not use 'client' in most cases.
+ * @note You should probably not use 'client' or 'channel' in most cases.
* In particular you MUST NOT SEND ERRORS to the client.
* This should be done in is_ok() and not in conv_param().
*/
- char *(*conv_param)(char *para, Client *client);
+ char *(*conv_param)(char *para, Client *client, Channel *channel);
/** Free and remove parameter from list.
* This function pointer is NULL (unused) for modes without parameters.
@@ -309,7 +310,7 @@ typedef struct {
int (*is_ok)(Client *,Channel *, char mode, char *para, int, int);
void * (*put_param)(void *, char *);
char * (*get_param)(void *);
- char * (*conv_param)(char *, Client *);
+ char * (*conv_param)(char *, Client *, Channel *);
void (*free_param)(void *);
void * (*dup_struct)(void *);
int (*sjoin_check)(Channel *, void *, void *);
@@ -486,11 +487,43 @@ typedef struct {
/** @} */
+/** Filter for history: the command / type of the request */
+typedef enum HistoryFilterCommand {
+ HFC_SIMPLE=1, /**< Simple history request for lines / unixtime */
+ HFC_BEFORE=2, /**< CHATHISTORY BEFORE */
+ HFC_AFTER=3, /**< CHATHISTORY AFTER */
+ HFC_LATEST=4, /**< CHATHISTORY LATEST */
+ HFC_AROUND=5, /**< CHATHISTORY AROUND */
+ HFC_BETWEEN=6 /**< CHATHISTORY BETWEEN */
+} HistoryFilterCommand;
+
/** Filter for history get requests */
typedef struct HistoryFilter HistoryFilter;
struct HistoryFilter {
- int last_lines;
- int last_seconds;
+ HistoryFilterCommand cmd; /**< Filter command, one of HistoryFilterCommand */
+ int last_lines; /**< Used by HFC_SIMPLE */
+ int last_seconds; /**< Used by HFC_SIMPLE */
+ char *timestamp_a; /**< First parameter of HFC_* (either this or msgid_a) */
+ char *msgid_a; /**< First parameter of HFC_* (either this or timestamp_a) */
+ char *timestamp_b; /**< Second parameter of HFC_BETWEEN (either this or msgid_b) */
+ char *msgid_b; /**< Second parameter of HFC_BETWEEN (either this or timestamp_b) */
+ int limit; /**< Maximum number of lines to return */
+};
+
+/** History log lines, used by HistoryResult among others */
+typedef struct HistoryLogLine HistoryLogLine;
+struct HistoryLogLine {
+ HistoryLogLine *prev, *next;
+ time_t t;
+ MessageTag *mtags;
+ char line[1];
+};
+
+typedef struct HistoryResult HistoryResult;
+struct HistoryResult {
+ char *object; /**< Name of the history object, eg '#test' */
+ HistoryLogLine *log; /**< The resulting log lines */
+ HistoryLogLine *log_tail; /**< Last entry in the log lines */
};
/** History Backend */
@@ -500,7 +533,7 @@ struct HistoryBackend {
char *name; /**< The name of the history backend (eg: "mem") */
int (*history_set_limit)(char *object, int max_lines, long max_time); /**< Impose a limit on a history object */
int (*history_add)(char *object, MessageTag *mtags, char *line); /**< Add to history */
- int (*history_request)(Client *acptr, char *object, HistoryFilter *filter); /**< Request history */
+ HistoryResult *(*history_request)(char *object, HistoryFilter *filter); /**< Request history */
int (*history_destroy)(char *object); /**< Destroy history of this object completely */
Module *owner; /**< Module introducing this */
char unloaded; /**< Internal flag to indicate module is being unloaded */
@@ -513,7 +546,7 @@ typedef struct {
char *name;
int (*history_set_limit)(char *object, int max_lines, long max_time);
int (*history_add)(char *object, MessageTag *mtags, char *line);
- int (*history_request)(Client *acptr, char *object, HistoryFilter *filter);
+ HistoryResult *(*history_request)(char *object, HistoryFilter *filter);
int (*history_destroy)(char *object);
} HistoryBackendInfo;
@@ -614,11 +647,12 @@ typedef struct ModuleObject {
extern unsigned int ModuleGetError(Module *module);
extern const char *ModuleGetErrorStr(Module *module);
extern unsigned int ModuleGetOptions(Module *module);
-extern unsigned int ModuleSetOptions(Module *module, unsigned int options, int action);
+extern void ModuleSetOptions(Module *module, unsigned int options, int action);
struct Module
{
struct Module *prev, *next;
+ int priority;
ModuleHeader *header; /* The module's header */
#ifdef _WIN32
HMODULE dll; /* Return value of LoadLibrary */
@@ -644,6 +678,7 @@ struct Module
#define MOD_OPT_OFFICIAL 0x0002 /* Official module, do not set "tainted" */
#define MOD_OPT_PERM_RELOADABLE 0x0004 /* Module is semi-permanent: it can be re-loaded but not un-loaded */
#define MOD_OPT_GLOBAL 0x0008 /* Module is required to be loaded globally (i.e. across the entire network) */
+#define MOD_OPT_UNLOAD_PRIORITY 0x1000 /* Module wants a higher or lower unload priority */
#define MOD_Dep(name, container,module) {#name, (vFP *) &container, module}
/** Event structs */
@@ -1223,17 +1258,19 @@ int hooktype_server_quit(Client *client, MessageTag *mtags);
/** Called when a local user changes the nick name (function prototype for HOOKTYPE_LOCAL_NICKCHANGE).
* @param client The client
+ * @param mtags Message tags associated with the event
* @param newnick The new nick name
* @return The return value is ignored (use return 0)
*/
-int hooktype_local_nickchange(Client *client, char *newnick);
+int hooktype_local_nickchange(Client *client, MessageTag *mtags, char *newnick);
/** Called when a remote user changes the nick name (function prototype for HOOKTYPE_REMOTE_NICKCHANGE).
* @param client The client
+ * @param mtags Message tags associated with the event
* @param newnick The new nick name
* @return The return value is ignored (use return 0)
*/
-int hooktype_remote_nickchange(Client *client, char *newnick);
+int hooktype_remote_nickchange(Client *client, MessageTag *mtags, char *newnick);
/** Called when a user wants to join a channel, may the user join? (function prototype for HOOKTYPE_CAN_JOIN).
* @param client The client
@@ -2222,8 +2259,9 @@ enum EfunctionType {
EFUNC_TKL_SYNCH,
EFUNC_CMD_TKL,
EFUNC_PLACE_HOST_BAN,
- EFUNC_DOSPAMFILTER,
- EFUNC_DOSPAMFILTER_VIRUSCHAN,
+ EFUNC_MATCH_SPAMFILTER,
+ EFUNC_MATCH_SPAMFILTER_MTAGS,
+ EFUNC_JOIN_VIRUSCHAN,
EFUNC_FIND_TKLINE_MATCH_ZAP_EX,
EFUNC_SEND_LIST,
EFUNC_STRIPCOLORS,
@@ -2308,6 +2346,7 @@ enum EfunctionType {
#define CONFIG_REQUIRE 9
#define CONFIG_LISTEN 10
#define CONFIG_LISTEN_OPTIONS 11
+#define CONFIG_SET_HISTORY_CHANNEL 12
#define MOD_HEADER Mod_Header
#define MOD_TEST() DLLFUNC int Mod_Test(ModuleInfo *modinfo)
diff --git a/include/setup.h.in b/include/setup.h.in
index f0f20bf..a4e0830 100644
--- a/include/setup.h.in
+++ b/include/setup.h.in
@@ -142,6 +142,12 @@
/* Define the location of the modules */
#undef MODULESDIR
+/* machine is bigendian */
+#undef NATIVE_BIG_ENDIAN
+
+/* machine is littleendian */
+#undef NATIVE_LITTLE_ENDIAN
+
/* Set to the nickname history length you want */
#undef NICKNAMEHISTORYLENGTH
@@ -226,3 +232,15 @@
/* Define if you have libcurl installed to get remote includes and MOTD
support */
#undef USE_LIBCURL
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+# define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+# undef WORDS_BIGENDIAN
+# endif
+#endif
diff --git a/include/struct.h b/include/struct.h
index 21c4c00..2e1e33e 100644
--- a/include/struct.h
+++ b/include/struct.h
@@ -109,6 +109,7 @@ typedef struct ConfigItem_blacklist_module ConfigItem_blacklist_module;
typedef struct ConfigItem_help ConfigItem_help;
typedef struct ConfigItem_offchans ConfigItem_offchans;
typedef struct SecurityGroup SecurityGroup;
+typedef struct Secret Secret;
typedef struct ListStruct ListStruct;
typedef struct ListStructPrio ListStructPrio;
@@ -120,7 +121,7 @@ typedef struct Watch Watch;
typedef struct Client Client;
typedef struct LocalClient LocalClient;
typedef struct Channel Channel;
-typedef struct User ClientUser;
+typedef struct User User;
typedef struct Server Server;
typedef struct Link Link;
typedef struct Ban Ban;
@@ -211,7 +212,7 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
#define LOG_CHGCMDS 0x0100
#define LOG_OVERRIDE 0x0200
#define LOG_SPAMFILTER 0x0400
-#define LOG_DBG 0x0800 /* fixme */
+#define LOG_FLOOD 0x0800
/*
** 'offsetof' is defined in ANSI-C. The following definition
@@ -240,6 +241,14 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
*/
#define SIPHASH_KEY_LENGTH 16
+/** The length of a standard 'msgid' tag (note that special
+ * msgid tags will be longer).
+ * The 22 alphanumeric characters provide slightly more
+ * than 128 bits of randomness (62^22 > 2^128).
+ * See mtag_add_or_inherit_msgid() for more information.
+ */
+#define MSGIDLEN 22
+
/** This specifies the current client status or the client type - see @link ClientStatus @endlink in particular.
* You may think "server" or "client" are the only choices here, but there are many more
* such as states where the user is in the middle of an SSL/TLS handshake.
@@ -661,6 +670,7 @@ struct ListStructPrio {
CHECK_PRIO_LIST_ENTRY(list) \
CHECK_PRIO_LIST_ENTRY(item) \
CHECK_NULL_LIST_ITEM(item) \
+ item->priority = prio; \
add_ListItemPrio((ListStructPrio *)item, (ListStructPrio **)&list, prio); \
} while(0)
@@ -754,6 +764,7 @@ struct LoopStruct {
unsigned do_bancheck_spamf_user : 1; /* perform 'user' spamfilter bancheck */
unsigned do_bancheck_spamf_away : 1; /* perform 'away' spamfilter bancheck */
unsigned ircd_rehashing : 1;
+ unsigned ircd_terminating : 1;
unsigned tainted : 1;
Client *rehash_save_cptr, *rehash_save_client;
int rehash_save_sig;
@@ -862,6 +873,97 @@ typedef void (*CmdFunc)(Client *client, MessageTag *mtags, int parc, char *parv[
typedef void (*AliasCmdFunc)(Client *client, MessageTag *mtags, int parc, char *parv[], char *cmd);
typedef void (*OverrideCmdFunc)(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, char *parv[]);
+#include
+
+/* This is the 'chunk size', the size of encryption blocks.
+ * We choose 4K here since that is a decent amount as of 2021 and
+ * more would not benefit performance anyway.
+ * Note that you cannot change this value easily afterwards
+ * (you cannot read files with a different chunk size).
+ */
+#define UNREALDB_CRYPT_FILE_CHUNK_SIZE 4096
+
+/** The salt length. Don't change. */
+#define UNREALDB_SALT_LEN 16
+
+/** Database modes of operation (read or write)
+ * @ingroup UnrealDBFunctions
+ */
+typedef enum UnrealDBMode {
+ UNREALDB_MODE_READ = 0,
+ UNREALDB_MODE_WRITE = 1
+} UnrealDBMode;
+
+typedef enum UnrealDBCipher {
+ UNREALDB_CIPHER_XCHACHA20 = 0x0001
+} UnrealDBCipher;
+
+typedef enum UnrealDBKDF {
+ UNREALDB_KDF_ARGON2ID = 0x0001
+} UnrealDBKDF;
+
+/** Database configuration for a particular file */
+typedef struct UnrealDBConfig {
+ uint16_t kdf; /**< Key derivation function (always 0x01) */
+ uint16_t t_cost; /**< Time cost (number of rounds) */
+ uint16_t m_cost; /**< Memory cost (in number of bitshifts, eg 15 means 1<<15=32M) */
+ uint16_t p_cost; /**< Parallel cost (number of concurrent threads) */
+ uint16_t saltlen; /**< Length of the salt (normally UNREALDB_SALT_LEN) */
+ char *salt; /**< Salt */
+ uint16_t cipher; /**< Encryption cipher (always 0x01) */
+ uint16_t keylen; /**< Key length */
+ char *key; /**< The key used for encryption/decryption */
+} UnrealDBConfig;
+
+/** Error codes returned by @ref UnrealDBFunctions
+ * @ingroup UnrealDBFunctions
+ */
+typedef enum UnrealDBError {
+ UNREALDB_ERROR_SUCCESS = 0, /**< Success, not an error */
+ UNREALDB_ERROR_FILENOTFOUND = 1, /**< File does not exist */
+ UNREALDB_ERROR_CRYPTED = 2, /**< File is crypted but no password provided */
+ UNREALDB_ERROR_NOTCRYPTED = 3, /**< File is not crypted and a password was provided */
+ UNREALDB_ERROR_HEADER = 4, /**< Header is corrupt, invalid or unknown format */
+ UNREALDB_ERROR_SECRET = 5, /**< Invalid secret { } block provided - either does not exist or does not meet requirements */
+ UNREALDB_ERROR_PASSWORD = 6, /**< Invalid password provided */
+ UNREALDB_ERROR_IO = 7, /**< I/O error */
+ UNREALDB_ERROR_API = 8, /**< API call violation, eg requesting to write on a file opened for reading */
+ UNREALDB_ERROR_INTERNAL = 9, /**< Internal error, eg crypto routine returned something unexpected */
+} UnrealDBError;
+
+/** Database handle
+ * This is returned by unrealdb_open() and used by all other @ref UnrealDBFunctions
+ * @ingroup UnrealDBFunctions
+ */
+typedef struct UnrealDB {
+ FILE *fd; /**< File descriptor */
+ UnrealDBMode mode; /**< UNREALDB_MODE_READ / UNREALDB_MODE_WRITE */
+ int crypted; /**< Are we doing any encryption or just plaintext? */
+ uint64_t creationtime; /**< When this file was created/updates */
+ crypto_secretstream_xchacha20poly1305_state st; /**< Internal state for crypto engine */
+ char buf[UNREALDB_CRYPT_FILE_CHUNK_SIZE]; /**< Buffer used for reading/writing */
+ int buflen; /**< Length of current data in buffer */
+ UnrealDBError error_code; /**< Last error code. Whenever this happens we will set this, never overwrite, and block further I/O */
+ char *error_string; /**< Error string upon failure */
+ UnrealDBConfig *config; /**< Config */
+} UnrealDB;
+
+/** Used for speeding up reading/writing of DBs (so we don't have to run argon2 repeatedly) */
+typedef struct SecretCache SecretCache;
+struct SecretCache {
+ SecretCache *prev, *next;
+ UnrealDBConfig *config;
+ time_t cache_hit;
+};
+
+/** Used for storing secret { } blocks */
+struct Secret {
+ Secret *prev, *next;
+ char *name;
+ char *password;
+ SecretCache *cache;
+};
+
/* tkl:
* TKL_KILL|TKL_GLOBAL = Global K-Line (GLINE)
@@ -898,12 +1000,13 @@ typedef void (*OverrideCmdFunc)(CommandOverride *ovr, Client *client, MessageTag
#define SPAMF_USERMSG 0x0002 /* p */
#define SPAMF_USERNOTICE 0x0004 /* n */
#define SPAMF_CHANNOTICE 0x0008 /* N */
-#define SPAMF_PART 0x0010 /* P */
-#define SPAMF_QUIT 0x0020 /* q */
-#define SPAMF_DCC 0x0040 /* d */
-#define SPAMF_USER 0x0080 /* u */
-#define SPAMF_AWAY 0x0100 /* a */
-#define SPAMF_TOPIC 0x0200 /* t */
+#define SPAMF_PART 0x0010 /* P */
+#define SPAMF_QUIT 0x0020 /* q */
+#define SPAMF_DCC 0x0040 /* d */
+#define SPAMF_USER 0x0080 /* u */
+#define SPAMF_AWAY 0x0100 /* a */
+#define SPAMF_TOPIC 0x0200 /* t */
+#define SPAMF_MTAG 0x0400 /* m */
/* Other flags only for function calls: */
#define SPAMFLAG_NOWARN 0x0001
@@ -1107,6 +1210,25 @@ extern void unload_all_unused_moddata(void);
#define TLSFLAG_NOSTARTTLS 0x8
#define TLSFLAG_DISABLECLIENTCERT 0x10
+/** Flood counters for local clients */
+typedef struct FloodCounter {
+ int count;
+ long t;
+} FloodCounter;
+
+/** This is the list of different flood counters that we keep for local clients. */
+/* IMPORTANT: If you change this, update floodoption_names[] in src/user.c too !!!!!!!!!!!! */
+typedef enum FloodOption {
+ FLD_NICK = 0, /**< nick-flood */
+ FLD_JOIN = 1, /**< join-flood */
+ FLD_AWAY = 2, /**< away-flood */
+ FLD_INVITE = 3, /**< invite-flood */
+ FLD_KNOCK = 4, /**< knock-flood */
+ FLD_CONVERSATIONS = 5, /**< max-concurrent-conversations */
+} FloodOption;
+#define MAXFLOODOPTIONS 10
+
+
/** This shows the Client struct (any client), the User struct (a user), Server (a server) that are commonly accessed both in the core and by 3rd party coders.
* @defgroup CommonStructs Common structs
* @{
@@ -1119,7 +1241,7 @@ struct Client {
struct list_head lclient_node; /**< For local client list (lclient_list) */
struct list_head special_node; /**< For special lists (server || unknown || oper) */
LocalClient *local; /**< Additional information regarding locally connected clients */
- ClientUser *user; /**< Additional information, if this client is a user */
+ User *user; /**< Additional information, if this client is a user */
Server *serv; /**< Additional information, if this is a server */
ClientStatus status; /**< Client status, one of CLIENT_STATUS_* */
struct list_head client_hash; /**< For name hash table (clientTable) */
@@ -1186,6 +1308,7 @@ struct LocalClient {
struct hostent *hostp; /**< Host record for this client (used by DNS code) */
char sockhost[HOSTLEN + 1]; /**< Hostname from the socket */
u_short port; /**< Remote TCP port of client */
+ FloodCounter flood[MAXFLOODOPTIONS];
};
/** User information (persons, not servers), you use client->user to access these (see also @link Client @endlink).
@@ -1208,11 +1331,9 @@ struct User {
char *operlogin; /**< Which oper { } block was used to oper up, otherwise NULL - used by oper::maxlogins */
struct {
time_t nick_t; /**< For set::anti-flood::nick-flood: time */
- time_t away_t; /**< For set::anti-flood::away-flood: time */
time_t knock_t; /**< For set::anti-flood::knock-flood: time */
time_t invite_t; /**< For set::anti-flood::invite-flood: time */
unsigned char nick_c; /**< For set::anti-flood::nick-flood: counter */
- unsigned char away_c; /**< For set::anti-flood::away-flood: counter */
unsigned char knock_c; /**< For set::anti-flood::knock-flood: counter */
unsigned char invite_c; /**< For set::anti-flood::invite-flood: counter */
} flood; /**< Anti-flood counters */
diff --git a/include/sys.h b/include/sys.h
index f651330..dcae59d 100644
--- a/include/sys.h
+++ b/include/sys.h
@@ -224,4 +224,15 @@ extern char OSName[256];
# endif
#endif
+#ifdef NATIVE_BIG_ENDIAN
+ #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__)
+ #include
+ #define bswap_64 bswap64
+ #define bswap_32 bswap32
+ #define bswap_16 bswap16
+ #else
+ #include
+ #endif
+#endif
+
#endif /* __sys_include__ */
diff --git a/include/unrealircd.h b/include/unrealircd.h
index 3ef4440..05e77f0 100644
--- a/include/unrealircd.h
+++ b/include/unrealircd.h
@@ -26,6 +26,7 @@
#else
#include
#include
+ #include
#endif
#include
#include
@@ -35,3 +36,4 @@
#ifdef USE_LIBCURL
#include
#endif
+#include
diff --git a/include/version.h b/include/version.h
index 55badd4..281fdf9 100644
--- a/include/version.h
+++ b/include/version.h
@@ -54,7 +54,7 @@
* Can be useful if the above 3 versionids are insufficient for you (eg: you want to support CVS).
* This is updated automatically on the CVS server every Monday. so don't touch it.
*/
-#define UNREAL_VERSION_TIME 202001
+#define UNREAL_VERSION_TIME 202120
#define UnrealProtocol 5002
#define PATCH1 macro_to_str(UNREAL_VERSION_GENERATION)
diff --git a/include/windows/setup.h b/include/windows/setup.h
index a229191..f079072 100644
--- a/include/windows/setup.h
+++ b/include/windows/setup.h
@@ -60,13 +60,13 @@
#define UNREAL_VERSION_GENERATION 5
/* Major version number (e.g.: 2 for Unreal3.2*) */
-#define UNREAL_VERSION_MAJOR 0
+#define UNREAL_VERSION_MAJOR 2
/* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 9
+#define UNREAL_VERSION_MINOR 0
/* Version suffix such as a beta marker or release candidate marker. (e.g.:
-rcX for unrealircd-3.2.9-rcX) */
-#define UNREAL_VERSION_SUFFIX ""
+#define UNREAL_VERSION_SUFFIX ".1"
#endif
diff --git a/src/Makefile.in b/src/Makefile.in
index 29508bc..a50e095 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -31,7 +31,7 @@ OBJS=dns.o auth.o channel.o crule.o dbuf.o \
api-moddata.o api-extban.o api-isupport.o api-command.o \
api-clicap.o api-messagetag.o api-history-backend.o api-efunctions.o \
api-event.o \
- crypt_blowfish.o updconf.o crashreport.o modulemanager.o \
+ crypt_blowfish.o unrealdb.o updconf.o crashreport.o modulemanager.o \
utf8.o \
openssl_hostname_validation.o $(URL)
@@ -224,6 +224,9 @@ api-efunctions.o: api-efunctions.c $(INCLUDES)
crypt_blowfish.o: crypt_blowfish.c $(INCLUDES)
$(CC) $(CFLAGS) $(BINCFLAGS) -c crypt_blowfish.c
+unrealdb.o: unrealdb.c $(INCLUDES)
+ $(CC) $(CFLAGS) $(BINCFLAGS) -c unrealdb.c
+
updconf.o: updconf.c $(INCLUDES)
$(CC) $(CFLAGS) $(BINCFLAGS) -c updconf.c
diff --git a/src/aliases.c b/src/aliases.c
index 860e95b..22f299b 100644
--- a/src/aliases.c
+++ b/src/aliases.c
@@ -66,7 +66,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
{
if (SERVICES_NAME && (acptr = find_person(alias->nick, NULL)))
{
- if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, alias->nick, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, cmd, alias->nick, 0, NULL))
return;
sendto_one(acptr, NULL, ":%s PRIVMSG %s@%s :%s", client->name,
alias->nick, SERVICES_NAME, parv[1]);
@@ -78,7 +78,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
{
if (STATS_SERVER && (acptr = find_person(alias->nick, NULL)))
{
- if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, alias->nick, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, cmd, alias->nick, 0, NULL))
return;
sendto_one(acptr, NULL, ":%s PRIVMSG %s@%s :%s", client->name,
alias->nick, STATS_SERVER, parv[1]);
@@ -90,7 +90,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
{
if ((acptr = find_person(alias->nick, NULL)))
{
- if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, alias->nick, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_USERMSG, cmd, alias->nick, 0, NULL))
return;
if (MyUser(acptr))
sendto_one(acptr, NULL, ":%s!%s@%s PRIVMSG %s :%s", client->name,
@@ -112,7 +112,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
char *errmsg = NULL;
if (can_send_to_channel(client, channel, &msg, &errmsg, 0))
{
- if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_CHANMSG, channel->chname, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_CHANMSG, cmd, channel->chname, 0, NULL))
return;
new_message(client, NULL, &mtags);
sendto_channel(channel, client, client->direction,
@@ -203,7 +203,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
{
if (SERVICES_NAME && (acptr = find_person(format->nick, NULL)))
{
- if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, format->nick, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, cmd, format->nick, 0, NULL))
return;
sendto_one(acptr, NULL, ":%s PRIVMSG %s@%s :%s", client->name,
format->nick, SERVICES_NAME, output);
@@ -214,7 +214,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
{
if (STATS_SERVER && (acptr = find_person(format->nick, NULL)))
{
- if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, format->nick, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, cmd, format->nick, 0, NULL))
return;
sendto_one(acptr, NULL, ":%s PRIVMSG %s@%s :%s", client->name,
format->nick, STATS_SERVER, output);
@@ -225,7 +225,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
{
if ((acptr = find_person(format->nick, NULL)))
{
- if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, format->nick, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, output, SPAMF_USERMSG, cmd, format->nick, 0, NULL))
return;
if (MyUser(acptr))
sendto_one(acptr, NULL, ":%s!%s@%s PRIVMSG %s :%s", client->name,
@@ -247,7 +247,7 @@ void cmd_alias(Client *client, MessageTag *mtags, int parc, char *parv[], char *
char *errmsg = NULL;
if (!can_send_to_channel(client, channel, &msg, &errmsg, 0))
{
- if (alias->spamfilter && match_spamfilter(client, output, SPAMF_CHANMSG, channel->chname, 0, NULL))
+ if (alias->spamfilter && match_spamfilter(client, output, SPAMF_CHANMSG, cmd, channel->chname, 0, NULL))
return;
new_message(client, NULL, &mtags);
sendto_channel(channel, client, client->direction,
diff --git a/src/api-efunctions.c b/src/api-efunctions.c
index a739fb3..4412af1 100644
--- a/src/api-efunctions.c
+++ b/src/api-efunctions.c
@@ -67,7 +67,8 @@ void (*tkl_stats)(Client *client, int type, char *para, int *cnt);
void (*tkl_sync)(Client *client);
void (*cmd_tkl)(Client *client, MessageTag *mtags, int parc, char *parv[]);
int (*place_host_ban)(Client *client, BanAction action, char *reason, long duration);
-int (*match_spamfilter)(Client *client, char *str_in, int type, char *target, int flags, TKL **rettk);
+int (*match_spamfilter)(Client *client, char *str_in, int type, char *cmd, char *target, int flags, TKL **rettk);
+int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, char *cmd);
int (*join_viruschan)(Client *client, TKL *tk, int type);
unsigned char *(*StripColors)(unsigned char *text);
const char *(*StripControlCodes)(unsigned char *text);
@@ -306,8 +307,9 @@ void efunctions_init(void)
efunc_init_function(EFUNC_TKL_SYNCH, tkl_sync, NULL);
efunc_init_function(EFUNC_CMD_TKL, cmd_tkl, NULL);
efunc_init_function(EFUNC_PLACE_HOST_BAN, place_host_ban, NULL);
- efunc_init_function(EFUNC_DOSPAMFILTER, match_spamfilter, NULL);
- efunc_init_function(EFUNC_DOSPAMFILTER_VIRUSCHAN, join_viruschan, NULL);
+ efunc_init_function(EFUNC_MATCH_SPAMFILTER, match_spamfilter, NULL);
+ efunc_init_function(EFUNC_MATCH_SPAMFILTER_MTAGS, match_spamfilter_mtags, NULL);
+ efunc_init_function(EFUNC_JOIN_VIRUSCHAN, join_viruschan, NULL);
efunc_init_function(EFUNC_STRIPCOLORS, StripColors, NULL);
efunc_init_function(EFUNC_STRIPCONTROLCODES, StripControlCodes, NULL);
efunc_init_function(EFUNC_SPAMFILTER_BUILD_USER_STRING, spamfilter_build_user_string, NULL);
diff --git a/src/api-event.c b/src/api-event.c
index 83cc0dd..8c328d2 100644
--- a/src/api-event.c
+++ b/src/api-event.c
@@ -28,6 +28,7 @@ ID_Copyright("(C) Carsten Munk 2001");
MODVAR Event *events = NULL;
extern EVENT(unrealdns_removeoldrecords);
+extern EVENT(unrealdb_expire_secret_cache);
/** Add an event, a function that will run at regular intervals.
* @param module Module that this event belongs to
@@ -241,4 +242,5 @@ void SetupEvents(void)
EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0);
EventAdd(NULL, "try_connections", try_connections, NULL, 2000, 0);
EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0);
+ EventAdd(NULL, "unrealdb_expire_secret_cache", unrealdb_expire_secret_cache, NULL, 61000, 0);
}
diff --git a/src/api-history-backend.c b/src/api-history-backend.c
index f6eba60..2c1668d 100644
--- a/src/api-history-backend.c
+++ b/src/api-history-backend.c
@@ -166,14 +166,21 @@ int history_add(char *object, MessageTag *mtags, char *line)
return 1;
}
-int history_request(Client *client, char *object, HistoryFilter *filter)
+HistoryResult *history_request(char *object, HistoryFilter *filter)
{
- HistoryBackend *hb;
+ HistoryBackend *hb = historybackends;
+ HistoryResult *r;
+ HistoryLogLine *l;
- for (hb = historybackends; hb; hb=hb->next)
- hb->history_request(client, object, filter);
+ if (!hb)
+ return 0; /* no history backend loaded */
- return 1;
+ /* Right now we return whenever the first backend has a result. */
+ for (hb = historybackends; hb; hb = hb->next)
+ if ((r = hb->history_request(object, filter)))
+ return r;
+
+ return NULL;
}
int history_destroy(char *object)
@@ -195,3 +202,83 @@ int history_set_limit(char *object, int max_lines, long max_t)
return 1;
}
+
+/** Free a HistoryResult object that was returned from request_result() earlier */
+void free_history_result(HistoryResult *r)
+{
+ HistoryLogLine *l, *l_next;
+ for (l = r->log; l; l = l_next)
+ {
+ l_next = l->next;
+ free_message_tags(l->mtags);
+ safe_free(l);
+ }
+ safe_free(r->object);
+ safe_free(r);
+}
+
+/** Returns 1 if the client can receive channel history, 0 if not.
+ * @param client The client to check.
+ * @note It is recommend to call this function BEFORE trying to
+ * retrieve channel history via history_request(),
+ * as to not waste useless resources.
+ */
+int can_receive_history(Client *client)
+{
+ if (HasCapability(client, "server-time"))
+ return 1;
+ return 0;
+}
+
+static void history_send_result_line(Client *client, HistoryLogLine *l, char *batchid)
+{
+ if (BadPtr(batchid))
+ {
+ sendto_one(client, l->mtags, "%s", l->line);
+ } else {
+ MessageTag *m = safe_alloc(sizeof(MessageTag));
+ m->name = "batch";
+ m->value = batchid;
+ AddListItem(m, l->mtags);
+ sendto_one(client, l->mtags, "%s", l->line);
+ DelListItem(m, l->mtags);
+ safe_free(m);
+ }
+}
+
+/** Send the result of a history_request() to the client.
+ * @param client The client to send to.
+ * @param r The history result retrieved via history_request().
+ */
+void history_send_result(Client *client, HistoryResult *r)
+{
+ char batch[BATCHLEN+1];
+ HistoryLogLine *l;
+
+ if (!can_receive_history(client))
+ return;
+
+ batch[0] = '\0';
+ if (HasCapability(client, "batch"))
+ {
+ /* Start a new batch */
+ generate_batch_id(batch);
+ sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, r->object);
+ }
+
+ for (l = r->log; l; l = l->next)
+ history_send_result_line(client, l, batch);
+
+ /* End of batch */
+ if (*batch)
+ sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
+}
+
+void free_history_filter(HistoryFilter *f)
+{
+ safe_free(f->timestamp_a);
+ safe_free(f->msgid_a);
+ safe_free(f->timestamp_b);
+ safe_free(f->msgid_b);
+ safe_free(f);
+}
diff --git a/src/auth.c b/src/auth.c
index d7f3300..39403a5 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -20,7 +20,6 @@
#include "unrealircd.h"
#include "crypt_blowfish.h"
-#include
typedef struct AuthTypeList AuthTypeList;
struct AuthTypeList {
diff --git a/src/conf.c b/src/conf.c
index fe291a2..694f266 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -71,6 +71,7 @@ static int _conf_help (ConfigFile *conf, ConfigEntry *ce);
static int _conf_offchans (ConfigFile *conf, ConfigEntry *ce);
static int _conf_sni (ConfigFile *conf, ConfigEntry *ce);
static int _conf_security_group (ConfigFile *conf, ConfigEntry *ce);
+static int _conf_secret (ConfigFile *conf, ConfigEntry *ce);
/*
* Validation commands
@@ -104,6 +105,7 @@ static int _test_help (ConfigFile *conf, ConfigEntry *ce);
static int _test_offchans (ConfigFile *conf, ConfigEntry *ce);
static int _test_sni (ConfigFile *conf, ConfigEntry *ce);
static int _test_security_group (ConfigFile *conf, ConfigEntry *ce);
+static int _test_secret (ConfigFile *conf, ConfigEntry *ce);
/* This MUST be alphabetized */
static ConfigCommand _ConfigCommands[] = {
@@ -128,6 +130,7 @@ static ConfigCommand _ConfigCommands[] = {
{ "oper", _conf_oper, _test_oper },
{ "operclass", _conf_operclass, _test_operclass },
{ "require", _conf_require, _test_require },
+ { "secret", _conf_secret, _test_secret },
{ "security-group", _conf_security_group, _test_security_group },
{ "set", _conf_set, _test_set },
{ "sni", _conf_sni, _test_sni },
@@ -160,6 +163,7 @@ static NameValue _LogFlags[] = {
{ LOG_CHGCMDS, "chg-commands" },
{ LOG_CLIENT, "connects" },
{ LOG_ERROR, "errors" },
+ { LOG_FLOOD, "flood" },
{ LOG_KILL, "kills" },
{ LOG_KLINE, "kline" },
{ LOG_OPER, "oper" },
@@ -258,6 +262,7 @@ ConfigItem_blacklist_module *conf_blacklist_module = NULL;
ConfigItem_help *conf_help = NULL;
ConfigItem_offchans *conf_offchans = NULL;
SecurityGroup *securitygroups = NULL;
+Secret *secrets = NULL;
MODVAR Configuration iConf;
MODVAR Configuration tempiConf;
@@ -358,6 +363,128 @@ char *x;
return 1;
}
+/** Find an anti-flood settings block by name.
+ * @param name The name of the set::anti-flood block
+ * @returns The FloodSettings block if found, or NULL if not found.
+ */
+FloodSettings *find_floodsettings_block_ex(Configuration *conf, const char *name)
+{
+ FloodSettings *f;
+
+ for (f = conf->floodsettings; f; f = f->next)
+ if (!strcmp(f->name, name))
+ return f;
+
+ return NULL;
+}
+
+/** Find an anti-flood settings block by name.
+ * @param name The name of the set::anti-flood block
+ * @returns The FloodSettings block if found, or NULL if not found.
+ */
+FloodSettings *find_floodsettings_block(const char *name)
+{
+ return find_floodsettings_block_ex(&iConf, name);
+}
+
+/** Check if 'name' is in the array 'list'.
+ * @param name The name to check
+ * @param list The char *list[] with the list of valid names.
+ * @returns 1 if found, 0 if not
+ * @note The array in list must end in a NULL element!
+ */
+int text_in_array(const char *name, const char *list[])
+{
+ int i;
+
+ for (i=0; list[i]; i++)
+ if (!strcmp(name, list[i]))
+ return 1;
+
+ return 0; /* Not found */
+}
+
+int flood_option_is_old(const char *name)
+{
+ const char *opts[] =
+ {
+ "max-concurrent-conversations",
+ "unknown-flood-amount",
+ "unknown-flood-bantime",
+ "handshake-data-flood",
+ "away-count",
+ "away-period",
+ "away-flood",
+ "nick-flood",
+ "join-flood",
+ "invite-flood",
+ "knock-flood",
+ "connect-flood",
+ "target-flood",
+ NULL
+ };
+
+ return text_in_array(name, opts);
+}
+
+int flood_option_is_for_everyone(const char *name)
+{
+ const char *opts[] =
+ {
+ "connect-flood",
+ "handshake-data-flood",
+ "unknown-flood",
+ "target-flood",
+ NULL
+ };
+
+ return text_in_array(name, opts);
+}
+
+
+/** Parses a value like '5:60s' into a flood setting that we can store.
+ * @param str The string to parse (eg: '5:60s')
+ * @param settings The FloodSettings block to store the result in
+ * @param opt The option (eg: FLD_AWAY)
+ * @returns 1 if OK, 0 for parse error.
+ */
+int config_parse_flood_generic(const char *str, Configuration *conf, char *blockname, FloodOption opt)
+{
+ char buf[64], *p;
+ FloodSettings *settings = find_floodsettings_block_ex(conf, blockname);
+
+ /* Create a new anti-flood block if it doesn't exist */
+ if (!settings)
+ {
+ settings = safe_alloc(sizeof(FloodSettings));
+ safe_strdup(settings->name, blockname);
+ AddListItem(settings, conf->floodsettings);
+ }
+
+ if (!strcmp(str, "unlimited") || !strcmp(str, "max"))
+ {
+ settings->limit[opt] = -1;
+ settings->period[opt] = 0;
+ return 1;
+ }
+
+ /* Work on a copy so we don't destroy 'str' */
+ strlcpy(buf, str, sizeof(buf));
+
+ p = strchr(buf, ':');
+
+ /* 'blah', ':blah', '1:' */
+ if (!p || (p == buf) || (*(p+1) == '\0'))
+ return 0;
+
+ *p++ = '\0';
+
+ settings->limit[opt] = atoi(buf);
+ settings->period[opt] = config_checkval(p, CFG_TIME);
+
+ return 1;
+}
+
long config_checkval(char *orig, unsigned short flags) {
char *value = raw_strdup(orig);
char *text;
@@ -536,7 +663,7 @@ void conf_channelmodes(char *modes, struct ChMode *store, int warn)
{
if (!param)
break;
- param = Channelmode_Table[i].conv_param(param, NULL);
+ param = Channelmode_Table[i].conv_param(param, NULL, NULL);
if (!param)
break; /* invalid parameter fmt, do not set mode. */
store->extparams[i] = raw_strdup(param);
@@ -1625,17 +1752,9 @@ void config_setdefaultsettings(Configuration *i)
{
char tmp[512];
- i->handshake_data_flood_amount = 4096;
- i->handshake_data_flood_ban_action = BAN_ACT_ZLINE;
- i->handshake_data_flood_ban_time = 600;
safe_strdup(i->oper_snomask, SNO_DEFOPER);
i->ident_read_timeout = 7;
i->ident_connect_timeout = 3;
- i->nick_count = 3; i->nick_period = 60; /* NICK flood protection: max 3 per 60s */
- i->away_count = 4; i->away_period = 120; /* AWAY flood protection: max 4 per 120s */
- i->invite_count = 4; i->invite_period = 60; /* INVITE flood protection: max 4 per 60s */
- i->knock_count = 4; i->knock_period = 120; /* KNOCK protection: max 4 per 120s */
- i->throttle_count = 3; i->throttle_period = 60; /* throttle protection: max 3 per 60s */
i->ban_version_tkl_time = 86400; /* 1d */
i->spamfilter_ban_time = 86400; /* 1d */
safe_strdup(i->spamfilter_ban_reason, "Spam/advertising");
@@ -1674,6 +1793,28 @@ void config_setdefaultsettings(Configuration *i)
i->handshake_delay = -1;
i->broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO;
+ /* Flood options */
+ /* - everyone */
+ i->throttle_count = 3; i->throttle_period = 60; /* throttle protection: max 3 per 60s */
+ i->handshake_data_flood_amount = 4096;
+ i->handshake_data_flood_ban_action = BAN_ACT_ZLINE;
+ i->handshake_data_flood_ban_time = 600;
+ // (targetflood is in the targetflood module)
+ /* - known-users */
+ config_parse_flood_generic("3:60", i, "known-users", FLD_NICK); /* NICK flood protection: max 3 per 60s */
+ config_parse_flood_generic("3:90", i, "known-users", FLD_JOIN); /* JOIN flood protection: max 3 per 90s */
+ config_parse_flood_generic("4:120", i, "known-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
+ config_parse_flood_generic("4:60", i, "known-users", FLD_INVITE); /* INVITE flood protection: max 4 per 60s */
+ config_parse_flood_generic("4:120", i, "known-users", FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
+ config_parse_flood_generic("10:15", i, "known-users", FLD_CONVERSATIONS); /* 10 users, new user every 15s */
+ /* - unknown-users */
+ config_parse_flood_generic("2:60", i, "unknown-users", FLD_NICK); /* NICK flood protection: max 2 per 60s */
+ config_parse_flood_generic("2:90", i, "unknown-users", FLD_JOIN); /* JOIN flood protection: max 2 per 90s */
+ config_parse_flood_generic("4:120", i, "unknown-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
+ config_parse_flood_generic("2:60", i, "unknown-users", FLD_INVITE); /* INVITE flood protection: max 2 per 60s */
+ config_parse_flood_generic("2:120", i, "unknown-users", FLD_KNOCK); /* KNOCK protection: max 2 per 120s */
+ config_parse_flood_generic("4:15", i, "unknown-users", FLD_CONVERSATIONS); /* 4 users, new user every 15s */
+
/* SSL/TLS options */
i->tls_options = safe_alloc(sizeof(TLSOptions));
snprintf(tmp, sizeof(tmp), "%s/tls/server.cert.pem", CONFDIR);
@@ -1713,9 +1854,6 @@ void config_setdefaultsettings(Configuration *i)
i->ban_setter = SETTER_NICK;
i->ban_setter_sync = 1;
- i->max_concurrent_conversations_users = 10;
- i->max_concurrent_conversations_new_user_every = 15;
-
i->allowed_channelchars = ALLOWED_CHANNELCHARS_UTF8;
i->automatic_ban_target = BAN_TARGET_IP;
@@ -1730,11 +1868,11 @@ static void make_default_logblock(void)
{
ConfigItem_log *ca = safe_alloc(sizeof(ConfigItem_log));
- config_status("No log { } block found -- using default: errors will be logged to 'ircd.log'");
+ config_status("No log { } block found -- logging everything to 'ircd.log'");
safe_strdup(ca->file, "ircd.log");
convert_to_absolute_path(&ca->file, LOGDIR);
- ca->flags |= LOG_ERROR;
+ ca->flags |= LOG_CHGCMDS|LOG_CLIENT|LOG_ERROR|LOG_KILL|LOG_KLINE|LOG_OPER|LOG_OVERRIDE|LOG_SACMDS|LOG_SERVER|LOG_SPAMFILTER|LOG_TKL;
ca->logfd = -1;
AddListItem(ca, conf_log);
}
@@ -2705,8 +2843,15 @@ int config_run()
config_status("Running %s", cfptr->cf_filename);
for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
{
- if (!strcmp(ce->ce_varname, "set") || !strcmp(ce->ce_varname, "class"))
- continue; // already processed
+ /* These are already processed above (set, class)
+ * or via config_test() (secret).
+ */
+ if (!strcmp(ce->ce_varname, "set") ||
+ !strcmp(ce->ce_varname, "class") ||
+ !strcmp(ce->ce_varname, "secret"))
+ {
+ continue;
+ }
if ((cc = config_binary_search(ce->ce_varname))) {
if ((cc->conffunc) && (cc->conffunc(cfptr, ce) < 0))
@@ -2793,6 +2938,17 @@ int config_test()
{
if (config_verbose > 1)
config_status("Testing %s", cfptr->cf_filename);
+ /* First test and run the secret { } blocks */
+ for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
+ {
+ if (!strcmp(ce->ce_varname, "secret"))
+ {
+ int n = _test_secret(cfptr, ce);
+ errors += n;
+ if (n == 0)
+ _conf_secret(cfptr, ce);
+ }
+ }
/* First test the set { } block */
for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
{
@@ -2803,8 +2959,11 @@ int config_test()
for (ce = cfptr->cf_entries; ce; ce = ce->ce_next)
{
/* These are already processed, so skip them here.. */
- if (!strcmp(ce->ce_varname, "set"))
+ if (!strcmp(ce->ce_varname, "secret") ||
+ !strcmp(ce->ce_varname, "set"))
+ {
continue;
+ }
if ((cc = config_binary_search(ce->ce_varname))) {
if (cc->testfunc)
errors += (cc->testfunc(cfptr, ce));
@@ -2850,8 +3009,8 @@ int config_test()
if (strchr(ce->ce_varname, ':'))
{
config_error("You cannot use :: in a directive, you have to write them out. "
- "For example 'set::anti-flood::nick-flood 3:60' needs to be written as: "
- "set { anti-flood { nick-flood 3:60; } }");
+ "For example 'set::auto-join #something' needs to be written as: "
+ "set { auto-join \"#something\"; }");
config_error("See also https://www.unrealircd.org/docs/Set_block#Syntax_used_in_this_documentation");
}
}
@@ -3164,16 +3323,14 @@ char *pretty_time_val(long timeval)
buf[0] = 0;
if (timeval/86400)
- snprintf(buf, sizeof(buf), "%ld day%s ", timeval/86400, timeval/86400 != 1 ? "s" : "");
+ snprintf(buf, sizeof(buf), "%ldd", timeval/86400);
if ((timeval/3600) % 24)
- snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ld hour%s ", (timeval/3600)%24, (timeval/3600)%24 != 1 ? "s" : "");
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ldh", (timeval/3600)%24);
if ((timeval/60)%60)
- snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ld minute%s ", (timeval/60)%60, (timeval/60)%60 != 1 ? "s" : "");
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ldm", (timeval/60)%60);
if ((timeval%60))
- snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ld second%s", timeval%60, timeval%60 != 1 ? "s" : "");
- /* Strip space at the end (if any) */
- if (*buf && (buf[strlen(buf)-1] == ' '))
- buf[strlen(buf)-1] = '\0';
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%lds", timeval%60);
+
return buf;
}
@@ -7311,7 +7468,7 @@ void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions)
int _conf_set(ConfigFile *conf, ConfigEntry *ce)
{
- ConfigEntry *cep, *cepp, *ceppp;
+ ConfigEntry *cep, *cepp, *ceppp, *cep4;
Hook *h;
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
@@ -7506,85 +7663,75 @@ int _conf_set(ConfigFile *conf, ConfigEntry *ce)
else if (!strcmp(cep->ce_varname, "anti-flood")) {
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
- if (!strcmp(cepp->ce_varname, "handshake-data-flood"))
+ for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
{
- for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+ if (!strcmp(ceppp->ce_varname, "handshake-data-flood"))
{
- if (!strcmp(ceppp->ce_varname, "amount"))
- tempiConf.handshake_data_flood_amount = config_checkval(ceppp->ce_vardata, CFG_SIZE);
- else if (!strcmp(ceppp->ce_varname, "ban-time"))
- tempiConf.handshake_data_flood_ban_time = config_checkval(ceppp->ce_vardata, CFG_TIME);
- else if (!strcmp(ceppp->ce_varname, "ban-action"))
- tempiConf.handshake_data_flood_ban_action = banact_stringtoval(ceppp->ce_vardata);
- }
- }
- else if (!strcmp(cepp->ce_varname, "away-count"))
- tempiConf.away_count = atol(cepp->ce_vardata);
- else if (!strcmp(cepp->ce_varname, "away-period"))
- tempiConf.away_period = config_checkval(cepp->ce_vardata, CFG_TIME);
- else if (!strcmp(cepp->ce_varname, "away-flood"))
- {
- int cnt, period;
- config_parse_flood(cepp->ce_vardata, &cnt, &period);
- tempiConf.away_count = cnt;
- tempiConf.away_period = period;
- }
- else if (!strcmp(cepp->ce_varname, "nick-flood"))
- {
- int cnt, period;
- config_parse_flood(cepp->ce_vardata, &cnt, &period);
- tempiConf.nick_count = cnt;
- tempiConf.nick_period = period;
- }
- else if (!strcmp(cepp->ce_varname, "away-flood"))
- {
- int cnt, period;
- config_parse_flood(cepp->ce_vardata, &cnt, &period);
- tempiConf.away_count = cnt;
- tempiConf.away_period = period;
- }
- else if (!strcmp(cepp->ce_varname, "invite-flood"))
- {
- int cnt, period;
- config_parse_flood(cepp->ce_vardata, &cnt, &period);
- tempiConf.invite_count = cnt;
- tempiConf.invite_period = period;
- }
- else if (!strcmp(cepp->ce_varname, "knock-flood"))
- {
- int cnt, period;
- config_parse_flood(cepp->ce_vardata, &cnt, &period);
- tempiConf.knock_count = cnt;
- tempiConf.knock_period = period;
- }
- else if (!strcmp(cepp->ce_varname, "connect-flood"))
- {
- int cnt, period;
- config_parse_flood(cepp->ce_vardata, &cnt, &period);
- tempiConf.throttle_count = cnt;
- tempiConf.throttle_period = period;
- }
- if (!strcmp(cepp->ce_varname, "max-concurrent-conversations"))
- {
- for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
- {
- if (!strcmp(ceppp->ce_varname, "users"))
+ for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
{
- tempiConf.max_concurrent_conversations_users = atoi(ceppp->ce_vardata);
- } else
- if (!strcmp(ceppp->ce_varname, "new-user-every"))
- {
- tempiConf.max_concurrent_conversations_new_user_every = config_checkval(ceppp->ce_vardata, CFG_TIME);
+ if (!strcmp(cep4->ce_varname, "amount"))
+ tempiConf.handshake_data_flood_amount = config_checkval(cep4->ce_vardata, CFG_SIZE);
+ else if (!strcmp(cep4->ce_varname, "ban-time"))
+ tempiConf.handshake_data_flood_ban_time = config_checkval(cep4->ce_vardata, CFG_TIME);
+ else if (!strcmp(cep4->ce_varname, "ban-action"))
+ tempiConf.handshake_data_flood_ban_action = banact_stringtoval(cep4->ce_vardata);
}
}
- }
- else
- {
- for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
+ else if (!strcmp(ceppp->ce_varname, "away-flood"))
{
- int value = (*(h->func.intfunc))(conf,cepp,CONFIG_SET_ANTI_FLOOD);
- if (value == 1)
- break;
+ config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_AWAY);
+ }
+ else if (!strcmp(ceppp->ce_varname, "nick-flood"))
+ {
+ config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_NICK);
+ }
+ else if (!strcmp(ceppp->ce_varname, "join-flood"))
+ {
+ config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_JOIN);
+ }
+ else if (!strcmp(ceppp->ce_varname, "invite-flood"))
+ {
+ config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_INVITE);
+ }
+ else if (!strcmp(ceppp->ce_varname, "knock-flood"))
+ {
+ config_parse_flood_generic(ceppp->ce_vardata, &tempiConf, cepp->ce_varname, FLD_KNOCK);
+ }
+ else if (!strcmp(ceppp->ce_varname, "connect-flood"))
+ {
+ int cnt, period;
+ config_parse_flood(ceppp->ce_vardata, &cnt, &period);
+ tempiConf.throttle_count = cnt;
+ tempiConf.throttle_period = period;
+ }
+ if (!strcmp(ceppp->ce_varname, "max-concurrent-conversations"))
+ {
+ /* We use a hack here to make it fit our storage format */
+ char buf[64];
+ int users=0;
+ long every=0;
+ for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+ {
+ if (!strcmp(cep4->ce_varname, "users"))
+ {
+ users = atoi(cep4->ce_vardata);
+ } else
+ if (!strcmp(cep4->ce_varname, "new-user-every"))
+ {
+ every = config_checkval(cep4->ce_vardata, CFG_TIME);
+ }
+ }
+ snprintf(buf, sizeof(buf), "%d:%ld", users, every);
+ config_parse_flood_generic(buf, &tempiConf, cepp->ce_varname, FLD_CONVERSATIONS);
+ }
+ else
+ {
+ for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
+ {
+ int value = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD);
+ if (value == 1)
+ break;
+ }
}
}
}
@@ -7876,7 +8023,7 @@ int _conf_set(ConfigFile *conf, ConfigEntry *ce)
int _test_set(ConfigFile *conf, ConfigEntry *ce)
{
- ConfigEntry *cep, *cepp, *ceppp;
+ ConfigEntry *cep, *cepp, *ceppp, *cep4;
int tempi;
int errors = 0;
Hook *h;
@@ -8338,243 +8485,306 @@ int _test_set(ConfigFile *conf, ConfigEntry *ce)
}
else if (!strcmp(cep->ce_varname, "anti-flood"))
{
+ int anti_flood_old = 0;
+ int anti_flood_old_and_default = 0;
+
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
- if (!strcmp(cepp->ce_varname, "max-concurrent-conversations"))
+ /* Test for old options: */
+ if (flood_option_is_old(cepp->ce_varname))
{
- for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+ /* Special code if the user is using 100% of the defaults */
+ if (cepp->ce_vardata &&
+ ((!strcmp(cepp->ce_varname, "nick-flood") && !strcmp(cepp->ce_vardata, "3:60")) ||
+ (!strcmp(cepp->ce_varname, "connect-flood") && cepp->ce_vardata && !strcmp(cepp->ce_vardata, "3:60")) ||
+ (!strcmp(cepp->ce_varname, "away-flood") && cepp->ce_vardata && !strcmp(cepp->ce_vardata, "4:120"))))
+ {
+ anti_flood_old_and_default = 1;
+ } else
+ {
+ anti_flood_old = 1;
+ }
+ continue;
+ }
+
+ for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
+ {
+ int everyone = !strcmp(cepp->ce_varname, "everyone") ? 1 : 0;
+ int for_everyone = flood_option_is_for_everyone(ceppp->ce_varname);
+
+ if (everyone && !for_everyone)
+ {
+ config_error("%s:%i: %s cannot be in the set::anti-flood::everyone block. "
+ "You can put it in 'known-users' or 'unknown-users' instead.",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum,
+ ceppp->ce_varname);
+ errors++;
+ continue;
+ } else
+ if (!everyone && for_everyone)
+ {
+ config_error("%s:%i: %s must be in the set::anti-flood::everyone block, not anywhere else.",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum,
+ ceppp->ce_varname);
+ errors++;
+ continue;
+ }
+
+ /* Now comes the actual config check for each element... */
+ if (!strcmp(ceppp->ce_varname, "max-concurrent-conversations"))
+ {
+ for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+ {
+ CheckNull(cep4);
+ if (!strcmp(cep4->ce_varname, "users"))
+ {
+ int v = atoi(cep4->ce_vardata);
+ if ((v < 1) || (v > MAXCCUSERS))
+ {
+ config_error("%s:%i: set::anti-flood::max-concurrent-conversations::users: "
+ "value should be between 1 and %d",
+ cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum, MAXCCUSERS);
+ errors++;
+ }
+ } else
+ if (!strcmp(cep4->ce_varname, "new-user-every"))
+ {
+ long v = config_checkval(cep4->ce_vardata, CFG_TIME);
+ if ((v < 1) || (v > 120))
+ {
+ config_error("%s:%i: set::anti-flood::max-concurrent-conversations::new-user-every: "
+ "value should be between 1 and 120 seconds",
+ cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
+ errors++;
+ }
+ } else
+ {
+ config_error_unknownopt(cep4->ce_fileptr->cf_filename,
+ cep4->ce_varlinenum, "set::anti-flood",
+ cep4->ce_varname);
+ errors++;
+ }
+ }
+ continue; /* required here, due to checknull directly below */
+ }
+ else if (!strcmp(ceppp->ce_varname, "unknown-flood-amount") ||
+ !strcmp(ceppp->ce_varname, "unknown-flood-bantime"))
+ {
+ config_error("%s:%i: set::anti-flood::%s: this setting has been moved. "
+ "See https://www.unrealircd.org/docs/Anti-flood_settings#handshake-data-flood",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, ceppp->ce_varname);
+ errors++;
+ continue;
+ }
+ else if (!strcmp(ceppp->ce_varname, "handshake-data-flood"))
+ {
+ for (cep4 = ceppp->ce_entries; cep4; cep4 = cep4->ce_next)
+ {
+ if (!strcmp(cep4->ce_varname, "amount"))
+ {
+ long v;
+ CheckNull(cep4);
+ v = config_checkval(cep4->ce_vardata, CFG_SIZE);
+ if (v < 1024)
+ {
+ config_error("%s:%i: set::anti-flood::handshake-data-flood::amount must be at least 1024 bytes",
+ cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
+ errors++;
+ }
+ } else
+ if (!strcmp(cep4->ce_varname, "ban-action"))
+ {
+ CheckNull(cep4);
+ if (!banact_stringtoval(cep4->ce_vardata))
+ {
+ config_error("%s:%i: set::anti-flood::handshake-data-flood::ban-action has unknown action type '%s'",
+ cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum,
+ cep4->ce_vardata);
+ errors++;
+ }
+ } else
+ if (!strcmp(cep4->ce_varname, "ban-time"))
+ {
+ CheckNull(cep4);
+ } else
+ {
+ config_error_unknownopt(cep4->ce_fileptr->cf_filename,
+ cep4->ce_varlinenum, "set::anti-flood::handshake-data-flood",
+ cep4->ce_varname);
+ errors++;
+ }
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "away-count"))
+ {
+ int temp = atol(ceppp->ce_vardata);
+ CheckNull(ceppp);
+ if (temp < 1 || temp > 255)
+ {
+ config_error("%s:%i: set::anti-flood::away-count must be between 1 and 255",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "away-period"))
{
CheckNull(ceppp);
- if (!strcmp(ceppp->ce_varname, "users"))
+ int temp = config_checkval(ceppp->ce_vardata, CFG_TIME);
+ if (temp < 10)
{
- int v = atoi(ceppp->ce_vardata);
- if ((v < 1) || (v > MAXCCUSERS))
- {
- config_error("%s:%i: set::anti-flood::max-concurrent-conversations::users: "
- "value should be between 1 and %d",
- ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, MAXCCUSERS);
- errors++;
- }
- } else
- if (!strcmp(ceppp->ce_varname, "new-user-every"))
+ config_error("%s:%i: set::anti-flood::away-period must be greater than 9",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "away-flood"))
+ {
+ int cnt, period;
+ CheckNull(ceppp);
+ if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+ (cnt < 1) || (cnt > 255) || (period < 10))
{
- long v = config_checkval(ceppp->ce_vardata, CFG_TIME);
- if ((v < 1) || (v > 120))
+ config_error("%s:%i: set::anti-flood::away-flood error. Syntax is ':' (eg 5:60), "
+ "count should be 1-255, period should be greater than 9",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "nick-flood"))
+ {
+ int cnt, period;
+ CheckNull(ceppp);
+ if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+ (cnt < 1) || (cnt > 255) || (period < 5))
+ {
+ config_error("%s:%i: set::anti-flood::nick-flood error. Syntax is ':' (eg 5:60), "
+ "count should be 1-255, period should be greater than 4",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "join-flood"))
+ {
+ int cnt, period;
+ CheckNull(ceppp);
+
+ if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+ (cnt < 1) || (cnt > 255) || (period < 5))
+ {
+ config_error("%s:%i: join-flood error. Syntax is ':' (eg 5:60), "
+ "count should be 1-255, period should be greater than 4",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "invite-flood"))
+ {
+ int cnt, period;
+ CheckNull(ceppp);
+ if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+ (cnt < 1) || (cnt > 255) || (period < 5))
+ {
+ config_error("%s:%i: set::anti-flood::invite-flood error. Syntax is ':' (eg 5:60), "
+ "count should be 1-255, period should be greater than 4",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "knock-flood"))
+ {
+ int cnt, period;
+ CheckNull(ceppp);
+ if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+ (cnt < 1) || (cnt > 255) || (period < 5))
+ {
+ config_error("%s:%i: set::anti-flood::knock-flood error. Syntax is ':' (eg 5:60), "
+ "count should be 1-255, period should be greater than 4",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else if (!strcmp(ceppp->ce_varname, "connect-flood"))
+ {
+ int cnt, period;
+ CheckNull(ceppp);
+ if (strcmp(cepp->ce_varname, "everyone"))
+ {
+ config_error("%s:%i: connect-flood must be in the set::anti-flood::everyone block, not anywhere else.",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ continue;
+ }
+ if (!config_parse_flood(ceppp->ce_vardata, &cnt, &period) ||
+ (cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600))
+ {
+ config_error("%s:%i: set::anti-flood::connect-flood: Syntax is ':' (eg 5:60), "
+ "count should be 1-255, period should be 1-3600",
+ ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
+ errors++;
+ }
+ }
+ else
+ {
+ /* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */
+ int used = 0;
+ Hook *h;
+ for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
+ {
+ int value, errs = 0;
+ if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
+ && !(h->owner->options & MOD_OPT_PERM))
+ continue;
+ value = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD,&errs);
+ if (value == 2)
+ used = 1;
+ if (value == 1)
{
- config_error("%s:%i: set::anti-flood::max-concurrent-conversations::new-user-every: "
- "value should be between 1 and 120 seconds",
- ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
- errors++;
+ used = 1;
+ break;
}
- } else
+ if (value == -1)
+ {
+ used = 1;
+ errors += errs;
+ break;
+ }
+ if (value == -2)
+ {
+ used = 1;
+ errors += errs;
+ }
+ }
+ if (!used)
{
config_error_unknownopt(ceppp->ce_fileptr->cf_filename,
ceppp->ce_varlinenum, "set::anti-flood",
ceppp->ce_varname);
errors++;
}
- }
- continue; /* required here, due to checknull directly below */
- }
- else if (!strcmp(cepp->ce_varname, "unknown-flood-amount") ||
- !strcmp(cepp->ce_varname, "unknown-flood-bantime"))
- {
- config_error("%s:%i: set::anti-flood::%s: this setting has been moved. "
- "See https://www.unrealircd.org/docs/Set_block#set::anti-flood::handshake-data-flood",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname);
- errors++;
- continue;
- }
- else if (!strcmp(cepp->ce_varname, "handshake-data-flood"))
- {
- for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next)
- {
- if (!strcmp(ceppp->ce_varname, "amount"))
- {
- long v;
- CheckNull(ceppp);
- CheckDuplicate(ceppp, anti_flood_handshake_data_flood_amount, "anti-flood::handshake-data-flood::amount");
- v = config_checkval(ceppp->ce_vardata, CFG_SIZE);
- if (v < 1024)
- {
- config_error("%s:%i: set::anti-flood::handshake-data-flood::amount must be at least 1024 bytes",
- ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum);
- errors++;
- }
- } else
- if (!strcmp(ceppp->ce_varname, "ban-action"))
- {
- CheckNull(ceppp);
- CheckDuplicate(ceppp, anti_flood_handshake_data_flood_ban_action, "anti-flood::handshake-data-flood::ban-action");
- if (!banact_stringtoval(ceppp->ce_vardata))
- {
- config_error("%s:%i: set::anti-flood::handshake-data-flood::ban-action has unknown action type '%s'",
- ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum,
- ceppp->ce_vardata);
- errors++;
- }
- } else
- if (!strcmp(ceppp->ce_varname, "ban-time"))
- {
- CheckNull(ceppp);
- CheckDuplicate(ceppp, anti_flood_handshake_data_flood_ban_time, "anti-flood::handshake-data-flood::ban-time");
- } else
- {
- config_error_unknownopt(ceppp->ce_fileptr->cf_filename,
- ceppp->ce_varlinenum, "set::anti-flood::handshake-data-flood",
- ceppp->ce_varname);
- errors++;
- }
- }
- }
- else if (!strcmp(cepp->ce_varname, "away-count"))
- {
- int temp = atol(cepp->ce_vardata);
- CheckNull(cepp);
- CheckDuplicate(cepp, anti_flood_away_count, "anti-flood::away-count");
- if (temp < 1 || temp > 255)
- {
- config_error("%s:%i: set::anti-flood::away-count must be between 1 and 255",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else if (!strcmp(cepp->ce_varname, "away-period"))
- {
- CheckNull(cepp);
- int temp = config_checkval(cepp->ce_vardata, CFG_TIME);
- CheckDuplicate(cepp, anti_flood_away_period, "anti-flood::away-period");
- if (temp < 10)
- {
- config_error("%s:%i: set::anti-flood::away-period must be greater than 9",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else if (!strcmp(cepp->ce_varname, "away-flood"))
- {
- int cnt, period;
- CheckNull(cepp);
- if (settings.has_anti_flood_away_period)
- {
- config_warn("%s:%d: set::anti-flood::away-flood overrides set::anti-flood::away-period",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
continue;
}
- if (settings.has_anti_flood_away_count)
- {
- config_warn("%s:%d: set::anti-flood::away-flood overrides set::anti-flood::away-count",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- continue;
- }
- settings.has_anti_flood_away_period = 1;
- settings.has_anti_flood_away_count = 1;
- if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
- (cnt < 1) || (cnt > 255) || (period < 10))
- {
- config_error("%s:%i: set::anti-flood::away-flood error. Syntax is ':' (eg 5:60), "
- "count should be 1-255, period should be greater than 9",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else if (!strcmp(cepp->ce_varname, "nick-flood"))
- {
- int cnt, period;
- CheckNull(cepp);
- CheckDuplicate(cepp, anti_flood_nick_flood, "anti-flood::nick-flood");
- if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
- (cnt < 1) || (cnt > 255) || (period < 5))
- {
- config_error("%s:%i: set::anti-flood::nick-flood error. Syntax is ':' (eg 5:60), "
- "count should be 1-255, period should be greater than 4",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else if (!strcmp(cepp->ce_varname, "invite-flood"))
- {
- int cnt, period;
- CheckNull(cepp);
- CheckDuplicate(cepp, anti_flood_invite_flood, "anti-flood::invite-flood");
- if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
- (cnt < 1) || (cnt > 255) || (period < 5))
- {
- config_error("%s:%i: set::anti-flood::invite-flood error. Syntax is ':' (eg 5:60), "
- "count should be 1-255, period should be greater than 4",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else if (!strcmp(cepp->ce_varname, "knock-flood"))
- {
- int cnt, period;
- CheckNull(cepp);
- CheckDuplicate(cepp, anti_flood_knock_flood, "anti-flood::knock-flood");
- if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
- (cnt < 1) || (cnt > 255) || (period < 5))
- {
- config_error("%s:%i: set::anti-flood::knock-flood error. Syntax is ':' (eg 5:60), "
- "count should be 1-255, period should be greater than 4",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else if (!strcmp(cepp->ce_varname, "connect-flood"))
- {
- int cnt, period;
- CheckNull(cepp);
- CheckDuplicate(cepp, anti_flood_connect_flood, "anti-flood::connect-flood");
- if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
- (cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600))
- {
- config_error("%s:%i: set::anti-flood::connect-flood: Syntax is ':' (eg 5:60), "
- "count should be 1-255, period should be 1-3600",
- cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
- errors++;
- }
- }
- else
- {
- /* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */
- int used = 0;
- Hook *h;
- for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
- {
- int value, errs = 0;
- if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
- && !(h->owner->options & MOD_OPT_PERM))
- continue;
- value = (*(h->func.intfunc))(conf,cepp,CONFIG_SET_ANTI_FLOOD,&errs);
- if (value == 2)
- used = 1;
- if (value == 1)
- {
- used = 1;
- break;
- }
- if (value == -1)
- {
- used = 1;
- errors += errs;
- break;
- }
- if (value == -2)
- {
- used = 1;
- errors += errs;
- }
- }
- if (!used)
- {
- config_error_unknownopt(cepp->ce_fileptr->cf_filename,
- cepp->ce_varlinenum, "set::anti-flood",
- cepp->ce_varname);
- errors++;
- }
- continue;
}
}
+ /* Now the warnings: */
+ if (anti_flood_old == 1)
+ {
+ config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible. "
+ "Your custom anti-flood settings have NOT been read.",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ config_warn("See https://www.unrealircd.org/docs/Anti-flood_settings for the new block style,");
+ config_warn("OR: simply remove all the anti-flood options from the conf to get rid of this "
+ "warning and use the built-in defaults.");
+ } else
+ if (anti_flood_old_and_default == 1)
+ {
+ config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible.",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ config_warn("To fix this warning, delete the anti-flood block from your configuration file "
+ "(file %s around line %d), this will make UnrealIRCd use the built-in defaults.",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ config_warn("If you want to learn more about the new functionality you can visit "
+ "https://www.unrealircd.org/docs/Anti-flood_settings");
+ }
}
else if (!strcmp(cep->ce_varname, "options")) {
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) {
@@ -10151,6 +10361,10 @@ int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
{
CheckNull(cep);
} else
+ if (!strcmp(cep->ce_varname, "tls"))
+ {
+ CheckNull(cep);
+ } else
if (!strcmp(cep->ce_varname, "reputation-score"))
{
int v;
@@ -10185,6 +10399,8 @@ int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
s->webirc = config_checkval(cep->ce_vardata, CFG_YESNO);
else if (!strcmp(cep->ce_varname, "identified"))
s->identified = config_checkval(cep->ce_vardata, CFG_YESNO);
+ else if (!strcmp(cep->ce_varname, "tls"))
+ s->tls = config_checkval(cep->ce_vardata, CFG_YESNO);
else if (!strcmp(cep->ce_varname, "reputation-score"))
s->reputation_score = atoi(cep->ce_vardata);
else if (!strcmp(cep->ce_varname, "priority"))
@@ -10197,6 +10413,306 @@ int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
return 1;
}
+Secret *find_secret(char *secret_name)
+{
+ Secret *s;
+ for (s = secrets; s; s = s->next)
+ {
+ if (!strcasecmp(s->name, secret_name))
+ return s;
+ }
+ return NULL;
+}
+
+void free_secret_cache(SecretCache *c)
+{
+ unrealdb_free_config(c->config);
+ safe_free(c);
+}
+
+void free_secret(Secret *s)
+{
+ SecretCache *c, *c_next;
+ for (c = s->cache; c; c = c_next)
+ {
+ c_next = c->next;
+ DelListItem(c, s->cache);
+ free_secret_cache(c);
+ }
+ safe_free(s->name);
+ safe_free_sensitive(s->password);
+ safe_free(s);
+}
+
+char *_conf_secret_read_password_file(char *fname)
+{
+ char *pwd, *err;
+ int fd, n;
+
+#ifndef _WIN32
+ fd = open(fname, O_RDONLY);
+#else
+ fd = open(fname, _O_RDONLY|_O_BINARY);
+#endif
+ if (fd < 0)
+ {
+ /* This should not happen, as we tested for file exists earlier.. */
+ config_error("Could not open file '%s': %s", fname, strerror(errno));
+ return NULL;
+ }
+
+ pwd = safe_alloc_sensitive(512);
+ n = read(fd, pwd, 511);
+ if (n <= 0)
+ {
+ close(fd);
+ config_error("Could not read from file '%s': %s", fname, strerror(errno));
+ safe_free_sensitive(pwd);
+ return NULL;
+ }
+ close(fd);
+ stripcrlf(pwd);
+ sodium_stackzero(1024);
+ if (!valid_secret_password(pwd, &err))
+ {
+ config_error("Key from file '%s' does not meet password complexity requirements: %s", fname, err);
+ safe_free_sensitive(pwd);
+ return NULL;
+ }
+ return pwd;
+}
+
+char *_conf_secret_read_prompt(char *blockname)
+{
+ char *pwd, *pwd_prompt;
+ char buf[256];
+
+#ifdef _WIN32
+ /* FIXME: add windows support? should be possible in GUI no? */
+ return NULL;
+#else
+ snprintf(buf, sizeof(buf), "Enter password for secret '%s': ", blockname);
+ pwd_prompt = getpass(buf);
+ if (pwd_prompt)
+ {
+ pwd = safe_alloc_sensitive(512);
+ strlcpy(pwd, pwd_prompt, 512);
+ memset(pwd_prompt, 0, strlen(pwd_prompt)); // zero password out
+ sodium_stackzero(1024);
+ return pwd;
+ }
+ return NULL;
+#endif
+}
+
+int _test_secret(ConfigFile *conf, ConfigEntry *ce)
+{
+ int errors = 0;
+ int has_password = 0, has_password_file = 0, has_password_prompt = 0;
+ ConfigEntry *cep;
+ char *err;
+ Secret *existing;
+
+ if (!ce->ce_vardata)
+ {
+ config_error("%s:%i: secret block needs a name, eg: secret xyz {",
+ ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+ errors++;
+ return errors; /* need to return here since we dereference ce->ce_vardata later.. */
+ } else {
+ if (!security_group_valid_name(ce->ce_vardata))
+ {
+ config_error("%s:%i: secret block name '%s' contains invalid characters or is too long. "
+ "Only letters, numbers, underscore and hyphen are allowed.",
+ ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+ errors++;
+ }
+ }
+
+ existing = find_secret(ce->ce_vardata);
+
+ for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+ {
+ if (!strcmp(cep->ce_varname, "password"))
+ {
+ int n;
+ has_password = 1;
+ CheckNull(cep);
+ if (cep->ce_entries ||
+ (((n = Auth_AutoDetectHashType(cep->ce_vardata))) && ((n == AUTHTYPE_BCRYPT) || (n == AUTHTYPE_ARGON2))))
+ {
+ config_error("%s:%d: you cannot use hashed passwords here, see "
+ "https://www.unrealircd.org/docs/Secret_block#secret-plaintext",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ errors++;
+ continue;
+ }
+ if (!valid_secret_password(cep->ce_vardata, &err))
+ {
+ config_error("%s:%d: secret::password does not meet password complexity requirements: %s",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+ errors++;
+ }
+ } else
+ if (!strcmp(cep->ce_varname, "password-file"))
+ {
+ char *str;
+ has_password_file = 1;
+ CheckNull(cep);
+ convert_to_absolute_path(&cep->ce_vardata, CONFDIR);
+ if (!file_exists(cep->ce_vardata) && existing && existing->password)
+ {
+ /* Silently ignore the case where a secret block already
+ * has the password read and now the file is no longer available.
+ * This so secret::password-file can be used only to boot
+ * and then the media (eg: USB stick) can be pulled.
+ */
+ } else
+ {
+ str = _conf_secret_read_password_file(cep->ce_vardata);
+ if (!str)
+ {
+ config_error("%s:%d: secret::password-file: error reading password from file, see error from above.",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ errors++;
+ }
+ safe_free_sensitive(str);
+ }
+ } else
+ if (!strcmp(cep->ce_varname, "password-prompt"))
+ {
+#ifdef _WIN32
+ config_error("%s:%d: secret::password-prompt is not implemented in Windows at the moment, sorry!",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ config_error("Choose a different method to enter passwords or use *NIX");
+ errors++;
+ return errors;
+#endif
+ has_password_prompt = 1;
+ if (loop.ircd_booted && !find_secret(ce->ce_vardata))
+ {
+ config_error("%s:%d: you cannot add a new secret { } block that uses password-prompt and then /REHASH. "
+ "With 'password-prompt' you can only add such a password on boot.",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ config_error("Either use a different method to enter passwords or restart the IRCd on the console.");
+ errors++;
+ }
+ if (!loop.ircd_booted && !running_interactively())
+ {
+ config_error("ERROR: IRCd is not running interactively, but via a cron job or something similar.");
+ config_error("%s:%d: unable to prompt for password since IRCd is not started in a terminal",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ config_error("Either use a different method to enter passwords or start the IRCd in a terminal/SSH/..");
+ }
+ } else
+ if (!strcmp(cep->ce_varname, "password-url"))
+ {
+ config_error("%s:%d: secret::password-url is not supported yet in this UnrealIRCd version.",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+ errors++;
+ } else
+ {
+ config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+ "secret", cep->ce_varname);
+ errors++;
+ continue;
+ }
+ if (cep->ce_entries)
+ {
+ config_error("%s:%d: secret::%s does not support sub-options (%s)",
+ cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+ cep->ce_varname, cep->ce_entries->ce_varname);
+ errors++;
+ }
+ }
+
+ if (!has_password && !has_password_file && !has_password_prompt)
+ {
+ config_error("%s:%d: secret { } block must contain 1 of: password OR password-file OR password-prompt",
+ ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+ errors++;
+ }
+
+ return errors;
+}
+
+/* NOTE: contrary to all other _conf* stuff, this one actually runs during config_test,
+ * so during the early CONFIG TEST stage rather than CONFIG RUN.
+ * This so all secret { } block configuration is available already during TEST/POSTTEST
+ * stage for modules, so they can check if the password is correct or not.
+ */
+int _conf_secret(ConfigFile *conf, ConfigEntry *ce)
+{
+ ConfigEntry *cep;
+ Secret *s;
+ Secret *existing = find_secret(ce->ce_vardata);
+
+ s = safe_alloc(sizeof(Secret));
+ safe_strdup(s->name, ce->ce_vardata);
+
+ for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+ {
+ if (!strcmp(cep->ce_varname, "password"))
+ {
+ safe_strdup_sensitive(s->password, cep->ce_vardata);
+ destroy_string(cep->ce_vardata); /* destroy the original */
+ } else
+ if (!strcmp(cep->ce_varname, "password-file"))
+ {
+ if (!file_exists(cep->ce_vardata) && existing && existing->password)
+ {
+ /* Silently ignore the case where a secret block already
+ * has the password read and now the file is no longer available.
+ * This so secret::password-file can be used only to boot
+ * and then the media (eg: USB stick) can be pulled.
+ */
+ } else
+ {
+ s->password = _conf_secret_read_password_file(cep->ce_vardata);
+ }
+ } else
+ if (!strcmp(cep->ce_varname, "password-prompt"))
+ {
+ if (!loop.ircd_booted && running_interactively())
+ {
+ s->password = _conf_secret_read_prompt(ce->ce_vardata);
+ if (!s->password || !valid_secret_password(s->password, NULL))
+ {
+ config_error("Invalid password entered on console (does not meet complexity requirements)");
+ /* This cannot be the correct password, so exit */
+ exit(-1);
+ }
+ }
+ }
+ }
+
+ /* This may happen if we run twice, due to destroy_string() earlier: */
+ if (BadPtr(s->password))
+ {
+ free_secret(s);
+ return 1;
+ }
+
+ /* If there is an existing secret { } block with this name in memory
+ * and it has a different password, then free that secret block
+ */
+ if (existing)
+ {
+ if (!strcmp(s->password, existing->password))
+ {
+ free_secret(s);
+ return 1;
+ }
+ /* passwords differ, so free the old existing one,
+ * including purging the cache for it.
+ */
+ DelListItem(existing, secrets);
+ free_secret(existing);
+ }
+ AddListItem(s, secrets);
+ return 1;
+}
+
#ifdef USE_LIBCURL
static void conf_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *inc_key)
{
diff --git a/src/conf_preprocessor.c b/src/conf_preprocessor.c
index ce6446c..6551fcb 100644
--- a/src/conf_preprocessor.c
+++ b/src/conf_preprocessor.c
@@ -12,16 +12,6 @@ extern ConfigFile *conf;
NameValueList *config_defines = NULL; /**< List of @defines, only valid during configuration reading */
-void skip_whitespace(char **p)
-{
- for (; **p == ' ' || **p == '\t'; *p = *p + 1);
-}
-
-void read_until(char **p, char *stopchars)
-{
- for (; **p && !strchr(stopchars, **p); *p = *p + 1);
-}
-
static inline int ValidVarCharacter(char x)
{
if (isupper(x) || isdigit(x) || strchr("_", x))
diff --git a/src/crashreport.c b/src/crashreport.c
index 0f9d414..bd7e83c 100644
--- a/src/crashreport.c
+++ b/src/crashreport.c
@@ -4,9 +4,7 @@
*/
#include "unrealircd.h"
-#ifndef _WIN32
-#include
-#else
+#ifdef _WIN32
extern void StartUnrealAgain(void);
#endif
#include "version.h"
@@ -527,24 +525,6 @@ char *generate_crash_report(char *coredump, int *thirdpartymods)
return reportfname;
}
-int running_interactive(void)
-{
-#ifndef _WIN32
- char *s;
-
- if (!isatty(0))
- return 0;
-
- s = getenv("TERM");
- if (!s || !strcasecmp(s, "dumb") || !strcasecmp(s, "none"))
- return 0;
-
- return 1;
-#else
- return IsService ? 0 : 1;
-#endif
-}
-
#define REPORT_NEVER -1
#define REPORT_ASK 0
#define REPORT_AUTO 1
@@ -728,13 +708,41 @@ void report_crash_not_sent(char *fname)
" (if you do, please set the option 'View Status' at the end of the bug report page to 'private'!!)\n", fname);
}
+/** This checks if there are indications that 3rd party modules are
+ * loaded. This is used to provide a small warning to the user that
+ * the crash may be likely due to that.
+ */
+int check_third_party_mods_present(void)
+{
+#ifndef _WIN32
+ struct dirent *dir;
+ DIR *fd = opendir(TMPDIR);
+
+ if (!fd)
+ return 0;
+
+ /* We search for files like tmp/FC5C3116.third.somename.so */
+ while ((dir = readdir(fd)))
+ {
+ char *fname = dir->d_name;
+ if (strstr(fname, ".third.") && strstr(fname, ".so"))
+ {
+ closedir(fd);
+ return 1;
+ }
+ }
+ closedir(fd);
+#endif
+ return 0;
+}
+
void report_crash(void)
{
char *coredump, *fname;
int thirdpartymods = 0;
int crashed_secs_ago;
- if (!running_interactive() && (report_pref != REPORT_AUTO))
+ if (!running_interactively() && (report_pref != REPORT_AUTO))
exit(0); /* don't bother if we run through cron or something similar */
coredump = find_best_coredump();
@@ -750,6 +758,8 @@ void report_crash(void)
if (!fname)
return;
+ if (thirdpartymods == 0)
+ thirdpartymods = check_third_party_mods_present();
#ifndef _WIN32
printf("The IRCd has been started now (and is running), but it did crash %d seconds ago.\n", crashed_secs_ago);
printf("Crash report generated in: %s\n\n", fname);
@@ -761,10 +771,10 @@ void report_crash(void)
"by someone other than the UnrealIRCd team). If you installed new 3rd party\n"
"module(s) in the past few weeks we suggest to unload these modules and see if\n"
"the crash issue dissapears. If so, that module is probably to blame.\n"
- "If you keep crashing without 3rd party modules then please do report it to\n"
- "the UnrealIRCd team.\n"
- "The reason we ask you to do this is because more than 95%% of the crash issues\n"
- "reported nowadays are caused by 3rd party modules and not by an UnrealIRCd bug.\n"
+ "If you keep crashing without any 3rd party modules loaded then please do report\n"
+ "it to the UnrealIRCd team.\n"
+ "The reason we ask you to do this is because MORE THAN 95%% OF ALL CRASH ISSUES\n"
+ "ARE CAUSED BY 3RD PARTY MODULES and not by an UnrealIRCd bug.\n"
"\n");
}
@@ -778,7 +788,9 @@ void report_crash(void)
char answerbuf[64], *answer;
printf("Shall I send a crash report to the UnrealIRCd developers?\n");
if (!thirdpartymods)
- printf("Crash reports help us greatly with fixing bugs that affect you and others\n");
+ printf("Crash reports help us greatly with fixing bugs that affect you and others\n");
+ else
+ printf("NOTE: If the crash is caused by a 3rd party module then UnrealIRCd devs can't fix that.\n");
printf("\n");
do
@@ -805,7 +817,7 @@ void report_crash(void)
return;
}
- if (running_interactive())
+ if (running_interactively())
{
char buf[8192], *line;
diff --git a/src/hash.c b/src/hash.c
index bbadea2..e5dd203 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -465,7 +465,7 @@ Client *hash_find_nickatserver(const char *str, Client *def)
if (serv)
*serv++ = '\0';
- client = find_client(nick, NULL);
+ client = find_person(nick, NULL);
if (!client)
return NULL; /* client not found */
diff --git a/src/ircd.c b/src/ircd.c
index 6f9b5cb..ed1e0d4 100644
--- a/src/ircd.c
+++ b/src/ircd.c
@@ -19,6 +19,7 @@
*/
#include "unrealircd.h"
+#include
#ifdef __FreeBSD__
char *malloc_options = "h" MALLOC_FLAGS_EXTRA;
@@ -60,6 +61,7 @@ static void open_debugfile(), setup_signals();
extern void init_glines(void);
extern void tkl_init(void);
extern void process_clients(void);
+extern void unrealdb_test(void);
#ifndef _WIN32
MODVAR char **myargv;
@@ -80,6 +82,7 @@ void s_die()
Client *client;
if (!IsService)
{
+ loop.ircd_terminating = 1;
unload_all_modules();
list_for_each_entry(client, &lclient_list, lclient_node)
@@ -94,6 +97,7 @@ void s_die()
ControlService(hService, SERVICE_CONTROL_STOP, &status);
}
#else
+ loop.ircd_terminating = 1;
unload_all_modules();
unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE);
exit(0);
@@ -366,7 +370,7 @@ int match_tkls(Client *client)
if (loop.do_bancheck_spamf_away && IsUser(client) &&
client->user->away != NULL &&
- match_spamfilter(client, client->user->away, SPAMF_AWAY, NULL, SPAMFLAG_NOWARN, NULL))
+ match_spamfilter(client, client->user->away, SPAMF_AWAY, "AWAY", NULL, SPAMFLAG_NOWARN, NULL))
{
return 1;
}
@@ -573,84 +577,6 @@ char buf[1024];
#endif
}
-/** Ugly version checker that ensures ssl/curl runtime libraries match the
- * version we compiled for.
- */
-static void do_version_check()
-{
- const char *compiledfor, *runtime;
- int error = 0;
- char *p;
-
- /* OPENSSL:
- * Nowadays (since openssl 1.0.0) they retain binary compatibility
- * when the first two version numbers are the same: eg 1.0.0 and 1.0.2
- */
- compiledfor = OPENSSL_VERSION_TEXT;
- runtime = SSLeay_version(SSLEAY_VERSION);
- p = strchr(compiledfor, '.');
- if (p)
- {
- p = strchr(p+1, '.');
- if (p)
- {
- int versionlen = p - compiledfor + 1;
-
- if (strncasecmp(compiledfor, runtime, versionlen))
- {
- version_check_logerror("OpenSSL version mismatch: compiled for '%s', library is '%s'",
- compiledfor, runtime);
- error=1;
- }
- }
- }
-
-
-#ifdef USE_LIBCURL
- /* Perhaps someone should tell them to do this a bit more easy ;)
- * problem is runtime output is like: 'libcurl/7.11.1 c-ares/1.2.0'
- * while header output is like: '7.11.1'.
- */
- {
- char buf[128], *p;
-
- runtime = curl_version();
- compiledfor = LIBCURL_VERSION;
- if (!strncmp(runtime, "libcurl/", 8))
- {
- strlcpy(buf, runtime+8, sizeof(buf));
- p = strchr(buf, ' ');
- if (p)
- {
- *p = '\0';
- if (strcmp(compiledfor, buf))
- {
- version_check_logerror("Curl version mismatch: compiled for '%s', library is '%s'",
- compiledfor, buf);
- error = 1;
- }
- }
- }
- }
-#endif
-
- if (error)
- {
-#ifndef _WIN32
- version_check_logerror("Header<->library mismatches can make UnrealIRCd *CRASH*! "
- "Make sure you don't have multiple versions of openssl installed (eg: "
- "one in /usr and one in /usr/local). And, if you recently upgraded them, "
- "be sure to recompile UnrealIRCd.");
-#else
- version_check_logerror("Header<->library mismatches can make UnrealIRCd *CRASH*! "
- "This should never happen with official Windows builds... unless "
- "you overwrote any .dll files with newer/older ones or something.");
- win_error();
-#endif
- tainted = 1;
- }
-}
-
extern void applymeblock(void);
extern MODVAR Event *events;
@@ -927,6 +853,11 @@ int InitUnrealIRCd(int argc, char *argv[])
safe_strdup(configfile, CONFIGFILE);
init_random(); /* needs to be done very early!! */
+ if (sodium_init() < 0)
+ {
+ fprintf(stderr, "Failed to initialize sodium library -- error accessing random device?\n");
+ exit(-1);
+ }
memset(&botmotd, '\0', sizeof(MOTDFile));
memset(&rules, '\0', sizeof(MOTDFile));
@@ -1070,9 +1001,10 @@ int InitUnrealIRCd(int argc, char *argv[])
exit(0);
}
#endif
-#if 0
+#if 1
case 'S':
- charsys_dump_table(p ? p : "*");
+ //charsys_dump_table(p ? p : "*");
+ unrealdb_test();
exit(0);
#endif
#ifndef _WIN32
@@ -1165,8 +1097,6 @@ int InitUnrealIRCd(int argc, char *argv[])
}
}
- do_version_check();
-
#if !defined(_WIN32)
#ifndef _WIN32
mkdir(TMPDIR, S_IRUSR|S_IWUSR|S_IXUSR); /* Create the tmp dir, if it doesn't exist */
@@ -1215,11 +1145,13 @@ int InitUnrealIRCd(int argc, char *argv[])
fprintf(stderr, "UnrealIRCd is brought to you by Bram Matthys (Syzop), Gottem and i\n\n");
fprintf(stderr, "Using the following libraries:\n");
- fprintf(stderr, "* %s\n", pcre2_version());
fprintf(stderr, "* %s\n", SSLeay_version(SSLEAY_VERSION));
+ fprintf(stderr, "* libsodium %s\n", sodium_version_string());
#ifdef USE_LIBCURL
fprintf(stderr, "* %s\n", curl_version());
#endif
+ fprintf(stderr, "* c-ares %s\n", ares_version(NULL));
+ fprintf(stderr, "* %s\n", pcre2_version());
#endif
check_user_limit();
#ifndef _WIN32
diff --git a/src/list.c b/src/list.c
index 9ad7785..7007378 100644
--- a/src/list.c
+++ b/src/list.c
@@ -76,7 +76,7 @@ void initlists(void)
client_pool = mp_pool_new(sizeof(Client), 512 * 1024);
local_client_pool = mp_pool_new(sizeof(LocalClient), 512 * 1024);
- user_pool = mp_pool_new(sizeof(ClientUser), 512 * 1024);
+ user_pool = mp_pool_new(sizeof(User), 512 * 1024);
link_pool = mp_pool_new(sizeof(Link), 512 * 1024);
}
@@ -184,30 +184,20 @@ void free_client(Client *client)
** 'make_user' add's an User information block to a client
** if it was not previously allocated.
*/
-ClientUser *make_user(Client *client)
+User *make_user(Client *client)
{
- ClientUser *user;
+ User *user;
user = client->user;
if (!user)
{
user = mp_pool_get(user_pool);
- memset(user, 0, sizeof(ClientUser));
+ memset(user, 0, sizeof(User));
#ifdef DEBUGMODE
users.inuse++;
#endif
- user->swhois = NULL;
- user->away = NULL;
- user->flood.away_t = 0;
- user->flood.away_c = 0;
- user->joined = 0;
- user->channel = NULL;
- user->invited = NULL;
- user->server = NULL;
strlcpy(user->svid, "0", sizeof(user->svid));
- user->whowas = NULL;
- user->snomask = 0;
if (client->ip)
{
/* initially set client->user->realhost to IP */
@@ -450,6 +440,8 @@ void del_ListItem(ListStruct *item, ListStruct **list)
item->next->prev = item->prev;
if (*list == item)
*list = item->next; /* new head */
+ /* And update 'item', prev/next should point nowhere anymore */
+ item->prev = item->next = NULL;
}
/** Add item to list with a 'priority'.
diff --git a/src/mempool.c b/src/mempool.c
index f96a0b4..8d560b5 100644
--- a/src/mempool.c
+++ b/src/mempool.c
@@ -506,11 +506,6 @@ mp_pool_new(size_t item_size, size_t chunk_capacity)
pool->next = mp_allocated_pools;
mp_allocated_pools = pool;
- ircd_log(LOG_DBG, "Capacity is %lu, item size is %lu, alloc size is %lu",
- (unsigned long)pool->new_chunk_capacity,
- (unsigned long)pool->item_alloc_size,
- (unsigned long)(pool->new_chunk_capacity*pool->item_alloc_size));
-
return pool;
}
@@ -701,19 +696,12 @@ mp_pool_log_status(mp_pool_t *pool)
for (chunk = pool->empty_chunks; chunk; chunk = chunk->next)
bytes_allocated += chunk->mem_size;
- ircd_log(LOG_DBG, "%llu bytes in %d empty chunks",
- bytes_allocated, pool->n_empty_chunks);
for (chunk = pool->used_chunks; chunk; chunk = chunk->next) {
++n_used;
bu += chunk->n_allocated * pool->item_alloc_size;
ba += chunk->mem_size;
-
- ircd_log(LOG_DBG, " used chunk: %d items allocated",
- chunk->n_allocated);
}
- ircd_log(LOG_DBG, "%llu/%llu bytes in %d partially full chunks",
- bu, ba, n_used);
bytes_used += bu;
bytes_allocated += ba;
bu = ba = 0;
@@ -724,22 +712,7 @@ mp_pool_log_status(mp_pool_t *pool)
ba += chunk->mem_size;
}
- ircd_log(LOG_DBG, "%llu/%llu bytes in %d full chunks",
- bu, ba, n_full);
bytes_used += bu;
bytes_allocated += ba;
-
- ircd_log(LOG_DBG, "Total: %llu/%llu bytes allocated "
- "for cell pools are full.",
- bytes_used, bytes_allocated);
-
-#ifdef MEMPOOL_STATS
- ircd_log(LOG_DBG, "%llu cell allocations ever; "
- "%llu chunk allocations ever; "
- "%llu chunk frees ever.",
- (long long)pool->total_items_allocated,
- (long long)pool->total_chunks_allocated,
- (long long)pool->total_chunks_freed);
-#endif
}
#endif
diff --git a/src/misc.c b/src/misc.c
index 074caa2..e8a1ee5 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -81,16 +81,17 @@ typedef struct {
} SpamfilterTargetTable;
SpamfilterTargetTable spamfiltertargettable[] = {
- { SPAMF_CHANMSG, 'c', "channel", "PRIVMSG" },
- { SPAMF_USERMSG, 'p', "private", "PRIVMSG" },
+ { SPAMF_CHANMSG, 'c', "channel", "PRIVMSG" },
+ { SPAMF_USERMSG, 'p', "private", "PRIVMSG" },
{ SPAMF_USERNOTICE, 'n', "private-notice", "NOTICE" },
{ SPAMF_CHANNOTICE, 'N', "channel-notice", "NOTICE" },
- { SPAMF_PART, 'P', "part", "PART" },
- { SPAMF_QUIT, 'q', "quit", "QUIT" },
- { SPAMF_DCC, 'd', "dcc", "PRIVMSG" },
- { SPAMF_USER, 'u', "user", "NICK" },
- { SPAMF_AWAY, 'a', "away", "AWAY" },
- { SPAMF_TOPIC, 't', "topic", "TOPIC" },
+ { SPAMF_PART, 'P', "part", "PART" },
+ { SPAMF_QUIT, 'q', "quit", "QUIT" },
+ { SPAMF_DCC, 'd', "dcc", "PRIVMSG" },
+ { SPAMF_USER, 'u', "user", "NICK" },
+ { SPAMF_AWAY, 'a', "away", "AWAY" },
+ { SPAMF_TOPIC, 't', "topic", "TOPIC" },
+ { SPAMF_MTAG, 'T', "message-tag", "message-tag" },
{ 0, 0, 0, 0 }
};
@@ -583,7 +584,7 @@ static void recurse_send_quits(Client *cptr, Client *client, Client *from, Clien
recurse_send_quits(cptr, acptr, from, to, mtags, comment, splitstr);
}
- if (cptr == client && to != from)
+ if (cptr == client && to != from && !(to->direction && (to->direction == from)))
sendto_one(to, mtags, "SQUIT %s :%s", client->name, comment);
}
@@ -699,6 +700,16 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com
* @param comment The (s)quit message
*/
void exit_client(Client *client, MessageTag *recv_mtags, char *comment)
+{
+ exit_client_ex(client, client->direction, recv_mtags, comment);
+}
+
+/** Exit this IRC client, and all the dependents (users, servers) if this is a server.
+ * @param client The client to exit.
+ * @param recv_mtags Message tags to use as a base (if any).
+ * @param comment The (s)quit message
+ */
+void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char *comment)
{
long long on_for;
ConfigItem_listen *listen_conf;
@@ -812,7 +823,7 @@ void exit_client(Client *client, MessageTag *recv_mtags, char *comment)
else
ircsnprintf(splitstr, sizeof splitstr, "%s %s", client->srvptr->name, client->name);
- remove_dependents(client, client->direction, recv_mtags, comment, splitstr);
+ remove_dependents(client, origin, recv_mtags, comment, splitstr);
RunHook2(HOOKTYPE_SERVER_QUIT, client, recv_mtags);
}
@@ -1187,6 +1198,7 @@ char *our_strcasestr(char *haystack, char *needle)
* @param tag A tag used internally and for server-to-server traffic,
* not visible to end-users.
* @param priority Priority - for ordering multiple swhois entries
+ * (lower number = further up in the swhoises list in WHOIS)
* @param swhois The actual special whois title (string) you want to add to the user
* @param from Who added this entry
* @param skip Which server(-side) to skip broadcasting this entry to.
@@ -1785,6 +1797,23 @@ int read_str(FILE *fd, char **x)
return 1;
}
+/** Convert binary 'data' of size 'len' to a hexadecimal string 'str'.
+ * The caller is responsible to ensure that 'str' is sufficiently large.
+ */
+void binarytohex(void *data, size_t len, char *str)
+{
+ const char hexchars[16] = "0123456789abcdef";
+ char *datastr = (char *)data;
+ int i, n = 0;
+
+ for (i=0; i> 4) & 0xF];
+ str[n++] = hexchars[datastr[i] & 0xF];
+ }
+ str[n] = '\0';
+}
+
/** Generates an MD5 checksum.
* @param mdout[out] Buffer to store result in, the result will be 16 bytes in binary
* (not ascii printable!).
@@ -1809,31 +1838,27 @@ void DoMD5(char *mdout, const char *src, unsigned long n)
char *md5hash(char *dst, const char *src, unsigned long n)
{
char tmp[16];
- SHA256_CTX hash;
DoMD5(tmp, src, n);
- sprintf(dst, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
- tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8],
- tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14], tmp[15]);
-
+ binarytohex(tmp, sizeof(tmp), dst);
return dst;
}
-/** Convert binary 'data' of size 'len' to a hexadecimal string 'str'.
- * The caller is responsible to ensure that 'str' is sufficiently large.
+/** Generates a SHA256 checksum - ASCII printable string (0011223344..etc..).
+ * @param dst[out] Buffer to store result in, which needs to be 65 bytes minimum.
+ * @param src[in] The input data used to generate the checksum.
+ * @param n[in] Length of data.
*/
-void binarytohex(void *data, size_t len, char *str)
+char *sha256hash(char *dst, const char *src, unsigned long n)
{
- const char hexchars[16] = "0123456789abcdef";
- char *datastr = (char *)data;
- int i, n = 0;
+ SHA256_CTX hash;
+ char binaryhash[SHA256_DIGEST_LENGTH];
- for (i=0; i> 4) & 0xF];
- str[n++] = hexchars[datastr[i] & 0xF];
- }
- str[n] = '\0';
+ SHA256_Init(&hash);
+ SHA256_Update(&hash, src, n);
+ SHA256_Final(binaryhash, &hash);
+ binarytohex(binaryhash, sizeof(binaryhash), dst);
+ return dst;
}
/** Calculate the SHA256 checksum of a file */
@@ -1973,3 +1998,101 @@ char *sendtype_to_cmd(SendType sendtype)
return "TAGMSG";
return NULL;
}
+
+/** Check password strength.
+ * @param pass The password to check
+ * @param min_length The minimum length of the password
+ * @param strict Whether to require UPPER+lower+digits
+ * @returns 1 if good, 0 if not.
+ */
+int check_password_strength(char *pass, int min_length, int strict, char **err)
+{
+ char has_lowercase=0, has_uppercase=0, has_digit=0;
+ char *p;
+ static char buf[256];
+
+ if (err)
+ *err = NULL;
+
+ if (strlen(pass) < min_length)
+ {
+ if (err)
+ {
+ snprintf(buf, sizeof(buf), "Password must be at least %d characters", min_length);
+ *err = buf;
+ }
+ return 0;
+ }
+
+ for (p=pass; *p; p++)
+ {
+ if (islower(*p))
+ has_lowercase = 1;
+ else if (isupper(*p))
+ has_uppercase = 1;
+ else if (isdigit(*p))
+ has_digit = 1;
+ }
+
+ if (strict)
+ {
+ if (!has_lowercase)
+ {
+ if (err)
+ *err = "Password must contain at least 1 lowercase character";
+ return 0;
+ } else
+ if (!has_uppercase)
+ {
+ if (err)
+ *err = "Password must contain at least 1 UPPERcase character";
+ return 0;
+ } else
+ if (!has_digit)
+ {
+ if (err)
+ *err = "Password must contain at least 1 digit (number)";
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int valid_secret_password(char *pass, char **err)
+{
+ return check_password_strength(pass, 10, 1, err);
+}
+
+int running_interactively(void)
+{
+#ifndef _WIN32
+ char *s;
+
+ if (!isatty(0))
+ return 0;
+
+ s = getenv("TERM");
+ if (!s || !strcasecmp(s, "dumb") || !strcasecmp(s, "none"))
+ return 0;
+
+ return 1;
+#else
+ return IsService ? 0 : 1;
+#endif
+}
+
+/** Skip whitespace (if any) */
+void skip_whitespace(char **p)
+{
+ for (; **p == ' ' || **p == '\t'; *p = *p + 1);
+}
+
+/** Keep reading '*p' until we hit any of the 'stopchars'.
+ * Actually behaves like strstr() but then hit the end
+ * of the string (\0) i guess?
+ */
+void read_until(char **p, char *stopchars)
+{
+ for (; **p && !strchr(stopchars, **p); *p = *p + 1);
+}
diff --git a/src/modulemanager.c b/src/modulemanager.c
index 854f2a0..1165df0 100644
--- a/src/modulemanager.c
+++ b/src/modulemanager.c
@@ -6,7 +6,6 @@
#include "unrealircd.h"
#ifndef _WIN32
-#include
#define MODULEMANAGER_CONNECT_TIMEOUT 7
#define MODULEMANAGER_READ_TIMEOUT 20
diff --git a/src/modules.c b/src/modules.c
index df0b078..d7de75c 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -30,9 +30,6 @@
#else
#include
#endif
-#ifndef _WIN32
-#include
-#endif
#ifndef RTLD_NOW
#define RTLD_NOW RTLD_LAZY
#endif
@@ -444,7 +441,7 @@ char *Module_Create(char *path_)
}
}
mod->flags = MODFLAG_TESTING;
- AddListItem(mod, Modules);
+ AddListItemPrio(mod, Modules, 0);
return NULL;
}
else
@@ -1187,6 +1184,9 @@ void unload_all_modules(void)
int (*Mod_Unload)();
for (m = Modules; m; m = m->next)
{
+#ifdef DEBUGMODE
+ ircd_log(LOG_ERROR, "Unloading %s...", m->header->name);
+#endif
irc_dlsym(m->dll, "Mod_Unload", Mod_Unload);
if (Mod_Unload)
(*Mod_Unload)(&m->modinfo);
@@ -1194,15 +1194,21 @@ void unload_all_modules(void)
}
}
-unsigned int ModuleSetOptions(Module *module, unsigned int options, int action)
+void ModuleSetOptions(Module *module, unsigned int options, int action)
{
unsigned int oldopts = module->options;
- if (action)
- module->options |= options;
- else
- module->options &= ~options;
- return oldopts;
+ if (options == MOD_OPT_UNLOAD_PRIORITY)
+ {
+ DelListItem(module, Modules);
+ AddListItemPrio(module, Modules, action);
+ } else {
+ /* Simple bit flag(s) */
+ if (action)
+ module->options |= options;
+ else
+ module->options &= ~options;
+ }
}
unsigned int ModuleGetOptions(Module *module)
diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in
index 256f664..cdc560f 100644
--- a/src/modules/Makefile.in
+++ b/src/modules/Makefile.in
@@ -71,8 +71,9 @@ R_MODULES= \
account-tag.so labeled-response.so link-security.so \
message-ids.so plaintext-policy.so server-time.so sts.so \
echo-message.so userip-tag.so userhost-tag.so \
- typing-indicator.so \
- ident_lookup.so history.so \
+ bot-tag.so \
+ reply-tag.so typing-indicator.so \
+ ident_lookup.so history.so chathistory.so \
targetfloodprot.so clienttagdeny.so
MODULES=cloak.so $(R_MODULES)
@@ -612,6 +613,14 @@ userhost-tag.so: userhost-tag.c $(INCLUDES)
$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-o userhost-tag.so userhost-tag.c
+bot-tag.so: bot-tag.c $(INCLUDES)
+ $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+ -o bot-tag.so bot-tag.c
+
+reply-tag.so: reply-tag.c $(INCLUDES)
+ $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+ -o reply-tag.so reply-tag.c
+
typing-indicator.so: typing-indicator.c $(INCLUDES)
$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-o typing-indicator.so typing-indicator.c
@@ -632,6 +641,10 @@ history.so: history.c $(INCLUDES)
$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-o history.so history.c
+chathistory.so: chathistory.c $(INCLUDES)
+ $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+ -o chathistory.so chathistory.c
+
targetfloodprot.so: targetfloodprot.c $(INCLUDES)
$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
-o targetfloodprot.so targetfloodprot.c
diff --git a/src/modules/antirandom.c b/src/modules/antirandom.c
index 176351c..8522f36 100644
--- a/src/modules/antirandom.c
+++ b/src/modules/antirandom.c
@@ -858,20 +858,6 @@ static int internal_getscore(char *str)
return score;
}
-void strtolower_safe(char *dst, char *src, int size)
-{
- if (!size)
- return; /* size of 0 is unworkable */
- size--; /* for \0 */
-
- for (; *src && size; src++)
- {
- *dst++ = tolower(*src);
- size--;
- }
- *dst = '\0';
-}
-
/** Returns "spam score".
* @note a user is expected, do not call for anything else (eg: servers)
*/
diff --git a/src/modules/authprompt.c b/src/modules/authprompt.c
index 132abda..3a46d96 100644
--- a/src/modules/authprompt.c
+++ b/src/modules/authprompt.c
@@ -451,7 +451,7 @@ int authprompt_sasl_continuation(Client *client, char *buf)
sendto_one(agent, NULL, ":%s SASL %s %s C %s",
me.id, AGENT_SID(agent), client->id, SEUSER(client)->authmsg);
}
- SEUSER(client)->authmsg = NULL;
+ safe_free(SEUSER(client)->authmsg);
}
return 1; /* inhibit displaying of message */
}
diff --git a/src/modules/away.c b/src/modules/away.c
index 0f6cc18..a760c83 100644
--- a/src/modules/away.c
+++ b/src/modules/away.c
@@ -86,24 +86,16 @@ CMD_FUNC(cmd_away)
}
/* Check spamfilters */
- if (MyUser(client) && match_spamfilter(client, new_reason, SPAMF_AWAY, NULL, 0, NULL))
+ if (MyUser(client) && match_spamfilter(client, new_reason, SPAMF_AWAY, "AWAY", NULL, 0, NULL))
return;
- /* Check set::anti-flood::away-flood */
- if (MyUser(client) && AWAY_PERIOD && !ValidatePermissionsForPath("immune:away-flood",client,NULL,NULL,NULL))
+ /* Check away-flood */
+ if (MyUser(client) &&
+ !ValidatePermissionsForPath("immune:away-flood",client,NULL,NULL,NULL) &&
+ flood_limit_exceeded(client, FLD_AWAY))
{
- if ((client->user->flood.away_t + AWAY_PERIOD) <= timeofday)
- {
- client->user->flood.away_c = 0;
- client->user->flood.away_t = timeofday;
- }
- if (client->user->flood.away_c <= AWAY_COUNT)
- client->user->flood.away_c++;
- if (client->user->flood.away_c > AWAY_COUNT)
- {
- sendnumeric(client, ERR_TOOMANYAWAY);
- return;
- }
+ sendnumeric(client, ERR_TOOMANYAWAY);
+ return;
}
/* Obey set::away-length */
diff --git a/src/modules/bot-tag.c b/src/modules/bot-tag.c
new file mode 100644
index 0000000..ed566ef
--- /dev/null
+++ b/src/modules/bot-tag.c
@@ -0,0 +1,90 @@
+/*
+ * IRC - Internet Relay Chat, src/modules/bot-tag.c
+ * (C) 2021 Syzop & The UnrealIRCd Team
+ *
+ * See file AUTHORS in IRC package for additional names of
+ * the programmers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* This implements the message tag that is mentioned in
+ * https://ircv3.net/specs/extensions/bot-mode
+ * The B mode and 005 is in the modules/usermodes/bot module.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+ = {
+ "bot-tag",
+ "5.0",
+ "bot message tag",
+ "UnrealIRCd Team",
+ "unrealircd-5",
+ };
+
+int bottag_mtag_is_ok(Client *client, char *name, char *value);
+void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+
+MOD_INIT()
+{
+ MessageTagHandlerInfo mtag;
+
+ MARK_AS_OFFICIAL_MODULE(modinfo);
+
+ memset(&mtag, 0, sizeof(mtag));
+ mtag.name = "draft/bot";
+ mtag.is_ok = bottag_mtag_is_ok;
+ mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+ MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+ HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_bottag);
+
+ return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+ return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+ return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending the mtag is permitted to do so.
+ */
+int bottag_mtag_is_ok(Client *client, char *name, char *value)
+{
+ if (IsServer(client) && (value == NULL))
+ return 1; /* OK */
+
+ return 0;
+}
+
+void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+{
+ MessageTag *m;
+
+ if (IsUser(client) && has_user_mode(client, 'B'))
+ {
+ MessageTag *m = safe_alloc(sizeof(MessageTag));
+ safe_strdup(m->name, "draft/bot");
+ m->value = NULL;
+ AddListItem(m, *mtag_list);
+ }
+}
diff --git a/src/modules/chanmodes/floodprot.c b/src/modules/chanmodes/floodprot.c
index 10ef0bc..a3aa3ef 100644
--- a/src/modules/chanmodes/floodprot.c
+++ b/src/modules/chanmodes/floodprot.c
@@ -28,13 +28,13 @@ ModuleHeader MOD_HEADER
"unrealircd-5",
};
-#define FLD_CTCP 0 /* c */
-#define FLD_JOIN 1 /* j */
-#define FLD_KNOCK 2 /* k */
-#define FLD_MSG 3 /* m */
-#define FLD_NICK 4 /* n */
-#define FLD_TEXT 5 /* t */
-#define FLD_REPEAT 6 /* r */
+#define CHFLD_CTCP 0 /* c */
+#define CHFLD_JOIN 1 /* j */
+#define CHFLD_KNOCK 2 /* k */
+#define CHFLD_MSG 3 /* m */
+#define CHFLD_NICK 4 /* n */
+#define CHFLD_TEXT 5 /* t */
+#define CHFLD_REPEAT 6 /* r */
#define NUMFLD 7 /* 7 flood types */
@@ -58,13 +58,13 @@ typedef struct FloodType {
* IMPORTANT: MUST be in alphabetic order!!
*/
FloodType floodtypes[] = {
- { 'c', FLD_CTCP, "CTCPflood", 'C', "mM", 0, },
- { 'j', FLD_JOIN, "joinflood", 'i', "R", 0, },
- { 'k', FLD_KNOCK, "knockflood", 'K', "", 0, },
- { 'm', FLD_MSG, "msg/noticeflood", 'm', "M", 0, },
- { 'n', FLD_NICK, "nickflood", 'N', "", 0, },
- { 't', FLD_TEXT, "msg/noticeflood", '\0', "bd", 1, },
- { 'r', FLD_REPEAT, "repeating", '\0', "bd", 1, },
+ { 'c', CHFLD_CTCP, "CTCPflood", 'C', "mM", 0, },
+ { 'j', CHFLD_JOIN, "joinflood", 'i', "R", 0, },
+ { 'k', CHFLD_KNOCK, "knockflood", 'K', "", 0, },
+ { 'm', CHFLD_MSG, "msg/noticeflood", 'm', "M", 0, },
+ { 'n', CHFLD_NICK, "nickflood", 'N', "", 0, },
+ { 't', CHFLD_TEXT, "msg/noticeflood", '\0', "bd", 1, },
+ { 'r', CHFLD_REPEAT, "repeating", '\0', "bd", 1, },
};
#define MODEF_DEFAULT_UNSETTIME cfg.modef_default_unsettime
@@ -132,7 +132,7 @@ uint64_t gen_floodprot_msghash(char *text);
int cmodef_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what);
void *cmodef_put_param(void *r_in, char *param);
char *cmodef_get_param(void *r_in);
-char *cmodef_conv_param(char *param_in, Client *client);
+char *cmodef_conv_param(char *param_in, Client *client, Channel *channel);
void cmodef_free_param(void *r);
void *cmodef_dup_struct(void *r_in);
int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx);
@@ -142,7 +142,7 @@ int cmodef_channel_destroy(Channel *channel, int *should_destroy);
int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment);
-int floodprot_nickchange(Client *client, char *oldnick);
+int floodprot_nickchange(Client *client, MessageTag *mtags, char *oldnick);
int floodprot_chanmode_del(Channel *channel, int m);
void memberflood_free(ModData *md);
int floodprot_stats(Client *client, char *flag);
@@ -596,7 +596,7 @@ char *cmodef_get_param(void *r_in)
/** Convert parameter to something proper.
* NOTE: client may be NULL if called for e.g. set::modes-on-join
*/
-char *cmodef_conv_param(char *param_in, Client *client)
+char *cmodef_conv_param(char *param_in, Client *client, Channel *channel)
{
static char retbuf[256];
char param[256];
@@ -753,7 +753,7 @@ int floodprot_join(Client *client, Channel *channel, MessageTag *mtags, char *pa
(client->srvptr->serv->boottime && (TStime() - client->srvptr->serv->boottime >= MODEF_BOOT_DELAY)) &&
!IsULine(client))
{
- do_floodprot(channel, client, FLD_JOIN);
+ do_floodprot(channel, client, CHFLD_JOIN);
}
return 0;
}
@@ -842,7 +842,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
- if (!chp || !(chp->limit[FLD_TEXT] || chp->limit[FLD_REPEAT]))
+ if (!chp || !(chp->limit[CHFLD_TEXT] || chp->limit[CHFLD_REPEAT]))
return HOOK_CONTINUE;
if (moddata_membership(mb, mdflood).ptr == NULL)
@@ -859,7 +859,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
memberflood->firstmsg = TStime();
memberflood->nmsg = 1;
memberflood->nmsg_repeat = 1;
- if (chp->limit[FLD_REPEAT])
+ if (chp->limit[CHFLD_REPEAT])
{
memberflood->lastmsg = gen_floodprot_msghash(*msg);
memberflood->prevmsg = 0;
@@ -868,7 +868,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
}
/* Anti-repeat ('r') */
- if (chp->limit[FLD_REPEAT])
+ if (chp->limit[CHFLD_REPEAT])
{
msghash = gen_floodprot_msghash(*msg);
if (memberflood->lastmsg)
@@ -876,7 +876,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
if ((memberflood->lastmsg == msghash) || (memberflood->prevmsg == msghash))
{
memberflood->nmsg_repeat++;
- if (memberflood->nmsg_repeat > chp->limit[FLD_REPEAT])
+ if (memberflood->nmsg_repeat > chp->limit[CHFLD_REPEAT])
is_flooding_repeat = 1;
}
memberflood->prevmsg = memberflood->lastmsg;
@@ -884,11 +884,11 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
memberflood->lastmsg = msghash;
}
- if (chp->limit[FLD_TEXT])
+ if (chp->limit[CHFLD_TEXT])
{
/* increase msgs */
memberflood->nmsg++;
- if (memberflood->nmsg > chp->limit[FLD_TEXT])
+ if (memberflood->nmsg > chp->limit[CHFLD_TEXT])
is_flooding_text = 1;
}
@@ -903,11 +903,11 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
if (is_flooding_repeat)
{
snprintf(errbuf, sizeof(errbuf), "Flooding (Your last message is too similar to previous ones)");
- flood_type = FLD_REPEAT;
+ flood_type = CHFLD_REPEAT;
} else
{
- snprintf(errbuf, sizeof(errbuf), "Flooding (Limit is %i lines per %i seconds)", chp->limit[FLD_TEXT], chp->per);
- flood_type = FLD_TEXT;
+ snprintf(errbuf, sizeof(errbuf), "Flooding (Limit is %i lines per %i seconds)", chp->limit[CHFLD_TEXT], chp->per);
+ flood_type = CHFLD_TEXT;
}
if (chp->action[flood_type] == 'd')
@@ -952,10 +952,10 @@ int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int
/* HINT: don't be so stupid to reorder the items in the if's below.. you'll break things -- Syzop. */
- do_floodprot(channel, client, FLD_MSG);
+ do_floodprot(channel, client, CHFLD_MSG);
if ((text[0] == '\001') && strncmp(text+1, "ACTION ", 7))
- do_floodprot(channel, client, FLD_CTCP);
+ do_floodprot(channel, client, CHFLD_CTCP);
return 0;
}
@@ -963,11 +963,11 @@ int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int
int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment)
{
if (IsFloodLimit(channel) && !IsULine(client))
- do_floodprot(channel, client, FLD_KNOCK);
+ do_floodprot(channel, client, CHFLD_KNOCK);
return 0;
}
-int floodprot_nickchange(Client *client, char *oldnick)
+int floodprot_nickchange(Client *client, MessageTag *mtags, char *oldnick)
{
Membership *mp;
@@ -980,7 +980,7 @@ int floodprot_nickchange(Client *client, char *oldnick)
if (channel && IsFloodLimit(channel) &&
!(mp->flags & (CHFL_CHANOP|CHFL_VOICE|CHFL_CHANOWNER|CHFL_HALFOP|CHFL_CHANADMIN)))
{
- do_floodprot(channel, client, FLD_NICK);
+ do_floodprot(channel, client, CHFLD_NICK);
}
}
return 0;
@@ -1001,27 +1001,27 @@ int floodprot_chanmode_del(Channel *channel, int modechar)
switch(modechar)
{
case 'C':
- chp->counter[FLD_CTCP] = 0;
+ chp->counter[CHFLD_CTCP] = 0;
break;
case 'N':
- chp->counter[FLD_NICK] = 0;
+ chp->counter[CHFLD_NICK] = 0;
break;
case 'm':
- chp->counter[FLD_MSG] = 0;
- chp->counter[FLD_CTCP] = 0;
+ chp->counter[CHFLD_MSG] = 0;
+ chp->counter[CHFLD_CTCP] = 0;
break;
case 'K':
- chp->counter[FLD_KNOCK] = 0;
+ chp->counter[CHFLD_KNOCK] = 0;
break;
case 'i':
- chp->counter[FLD_JOIN] = 0;
+ chp->counter[CHFLD_JOIN] = 0;
break;
case 'M':
- chp->counter[FLD_MSG] = 0;
- chp->counter[FLD_CTCP] = 0;
+ chp->counter[CHFLD_MSG] = 0;
+ chp->counter[CHFLD_CTCP] = 0;
break;
case 'R':
- chp->counter[FLD_JOIN] = 0;
+ chp->counter[CHFLD_JOIN] = 0;
break;
default:
break;
diff --git a/src/modules/chanmodes/history.c b/src/modules/chanmodes/history.c
index 747dcea..124a25e 100644
--- a/src/modules/chanmodes/history.c
+++ b/src/modules/chanmodes/history.c
@@ -19,10 +19,12 @@ struct ConfigHistoryExt {
int lines; /**< number of lines */
long time; /**< seconds */
};
-struct {
+typedef struct cfgstruct cfgstruct;
+struct cfgstruct {
ConfigHistoryExt playback_on_join; /**< Maximum number of lines & time to playback on-join */
- ConfigHistoryExt max_storage_per_channel; /**< Maximum number of lines & time to record */
-} cfg;
+ ConfigHistoryExt max_storage_per_channel_registered; /**< Maximum number of lines & time to record for +r channels*/
+ ConfigHistoryExt max_storage_per_channel_unregistered; /**< Maximum number of lines & time to record for -r channels */
+};
typedef struct HistoryChanMode HistoryChanMode;
struct HistoryChanMode {
@@ -30,29 +32,38 @@ struct HistoryChanMode {
unsigned long max_time; /**< Maximum number of time (in seconds) to record */
};
+/* Global variables */
Cmode_t EXTMODE_HISTORY = 0L;
+static cfgstruct cfg;
+static cfgstruct test;
+
#define HistoryEnabled(channel) (channel->mode.extmode & EXTMODE_HISTORY)
/* Forward declarations */
-static void init_config(void);
+static void init_config(cfgstruct *cfg);
int history_config_test(ConfigFile *, ConfigEntry *, int, int *);
+int history_config_posttest(int *);
int history_config_run(ConfigFile *, ConfigEntry *, int);
int history_chanmode_change(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
static int compare_history_modes(HistoryChanMode *a, HistoryChanMode *b);
int history_chanmode_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what);
void *history_chanmode_put_param(void *r_in, char *param);
char *history_chanmode_get_param(void *r_in);
-char *history_chanmode_conv_param(char *param, Client *client);
+char *history_chanmode_conv_param(char *param, Client *client, Channel *channel);
void history_chanmode_free_param(void *r);
void *history_chanmode_dup_struct(void *r_in);
int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx);
int history_channel_destroy(Channel *channel, int *should_destroy);
int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+CMD_OVERRIDE_FUNC(override_mode);
MOD_TEST()
{
+ init_config(&test);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, history_config_test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, history_config_posttest);
+
return MOD_SUCCESS;
}
@@ -75,7 +86,7 @@ MOD_INIT()
creq.sjoin_check = history_chanmode_sjoin_check;
CmodeAdd(modinfo->handle, creq, &EXTMODE_HISTORY);
- init_config();
+ init_config(&cfg);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, history_config_run);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CHANMODE, 0, history_chanmode_change);
@@ -88,6 +99,11 @@ MOD_INIT()
MOD_LOAD()
{
+ CommandOverrideAdd(modinfo->handle, "MODE", override_mode);
+ CommandOverrideAdd(modinfo->handle, "SVSMODE", override_mode);
+ CommandOverrideAdd(modinfo->handle, "SVS2MODE", override_mode);
+ CommandOverrideAdd(modinfo->handle, "SAMODE", override_mode);
+ CommandOverrideAdd(modinfo->handle, "SJOIN", override_mode);
return MOD_SUCCESS;
}
@@ -96,14 +112,16 @@ MOD_UNLOAD()
return MOD_SUCCESS;
}
-static void init_config(void)
+static void init_config(cfgstruct *cfg)
{
/* Set default values */
- memset(&cfg, 0, sizeof(cfg));
- cfg.playback_on_join.lines = 15;
- cfg.playback_on_join.time = 86400;
- cfg.max_storage_per_channel.lines = 200;
- cfg.max_storage_per_channel.time = 86400*7;
+ memset(cfg, 0, sizeof(cfgstruct));
+ cfg->playback_on_join.lines = 15;
+ cfg->playback_on_join.time = 86400;
+ cfg->max_storage_per_channel_unregistered.lines = 200;
+ cfg->max_storage_per_channel_unregistered.time = 86400*31;
+ cfg->max_storage_per_channel_registered.lines = 5000;
+ cfg->max_storage_per_channel_registered.time = 86400*31;
}
#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
@@ -111,9 +129,9 @@ static void init_config(void)
int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
int errors = 0;
- ConfigEntry *cep, *cepp, *cep4;
- int on_join_lines=0, maximum_storage_lines=0;
- long on_join_time=0L, maximum_storage_time=0L;
+ ConfigEntry *cep, *cepp, *cep4, *cep5;
+ int on_join_lines=0, maximum_storage_lines_registered=0, maximum_storage_lines_unregistered=0;
+ long on_join_time=0L, maximum_storage_time_registered=0L, maximum_storage_time_unregistered=0L;
/* We only care about set::history */
if ((type != CONFIG_SET) || strcmp(ce->ce_varname, "history"))
@@ -134,29 +152,29 @@ int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
int v;
CheckNull(cep4);
v = atoi(cep4->ce_vardata);
- if ((v < 1) || (v > 1000000000))
+ if ((v < 0) || (v > 1000))
{
- config_error("%s:%i: set::history::channel::playback-on-join::lines must be between 1 and 1000. "
+ config_error("%s:%i: set::history::channel::playback-on-join::lines must be between 0 and 1000. "
"Recommended values are 10-50. Got: %d.",
cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum, v);
errors++;
continue;
}
- on_join_lines = v;
+ test.playback_on_join.lines = v;
} else
if (!strcmp(cep4->ce_varname, "time"))
{
long v;
CheckNull(cep4);
v = config_checkval(cep4->ce_vardata, CFG_TIME);
- if (v < 1)
+ if (v < 0)
{
- config_error("%s:%i: set::history::channel::playback-on-join::time must be a positive number.",
+ config_error("%s:%i: set::history::channel::playback-on-join::time must be zero or more.",
cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
errors++;
continue;
}
- on_join_time = v;
+ test.playback_on_join.time = v;
} else
{
config_error_unknown(cep4->ce_fileptr->cf_filename,
@@ -169,45 +187,127 @@ int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
for (cep4 = cepp->ce_entries; cep4; cep4 = cep4->ce_next)
{
- if (!strcmp(cep4->ce_varname, "lines"))
+ if (!strcmp(cep4->ce_varname, "registered"))
{
- int v;
- CheckNull(cep4);
- v = atoi(cep4->ce_vardata);
- if (v < 1)
+ for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
{
- config_error("%s:%i: set::history::channel::max-storage-per-channel::lines must be a positive number.",
- cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
- errors++;
- continue;
+ if (!strcmp(cep5->ce_varname, "lines"))
+ {
+ int v;
+ CheckNull(cep5);
+ v = atoi(cep5->ce_vardata);
+ if (v < 1)
+ {
+ config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::lines must be a positive number.",
+ cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+ errors++;
+ continue;
+ }
+ test.max_storage_per_channel_registered.lines = v;
+ } else
+ if (!strcmp(cep5->ce_varname, "time"))
+ {
+ long v;
+ CheckNull(cep5);
+ v = config_checkval(cep5->ce_vardata, CFG_TIME);
+ if (v < 1)
+ {
+ config_error("%s:%i: set::history::channel::max-storage-per-channel::registered::time must be a positive number.",
+ cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+ errors++;
+ continue;
+ }
+ test.max_storage_per_channel_registered.time = v;
+ } else
+ {
+ config_error_unknown(cep5->ce_fileptr->cf_filename,
+ cep5->ce_varlinenum, "set::history::channel::max-storage-per-channel::registered", cep5->ce_varname);
+ errors++;
+ }
}
- maximum_storage_lines = v;
} else
- if (!strcmp(cep4->ce_varname, "time"))
+ if (!strcmp(cep4->ce_varname, "unregistered"))
{
- long v;
- CheckNull(cep4);
- v = config_checkval(cep4->ce_vardata, CFG_TIME);
- if (v < 1)
+ for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
{
- config_error("%s:%i: set::history::channel::max-storage-per-channel::time must be a positive number.",
- cep4->ce_fileptr->cf_filename, cep4->ce_varlinenum);
- errors++;
- continue;
+ if (!strcmp(cep5->ce_varname, "lines"))
+ {
+ int v;
+ CheckNull(cep5);
+ v = atoi(cep5->ce_vardata);
+ if (v < 1)
+ {
+ config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::lines must be a positive number.",
+ cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+ errors++;
+ continue;
+ }
+ test.max_storage_per_channel_unregistered.lines = v;
+ } else
+ if (!strcmp(cep5->ce_varname, "time"))
+ {
+ long v;
+ CheckNull(cep5);
+ v = config_checkval(cep5->ce_vardata, CFG_TIME);
+ if (v < 1)
+ {
+ config_error("%s:%i: set::history::channel::max-storage-per-channel::unregistered::time must be a positive number.",
+ cep5->ce_fileptr->cf_filename, cep5->ce_varlinenum);
+ errors++;
+ continue;
+ }
+ test.max_storage_per_channel_unregistered.time = v;
+ } else
+ {
+ config_error_unknown(cep5->ce_fileptr->cf_filename,
+ cep5->ce_varlinenum, "set::history::channel::max-storage-per-channel::unregistered", cep5->ce_varname);
+ errors++;
+ }
}
- maximum_storage_time = v;
} else
{
- config_error_unknown(cep4->ce_fileptr->cf_filename,
- cep4->ce_varlinenum, "set::history::channel::max-storage-per-channel", cep4->ce_varname);
+ config_error_unknown(cep->ce_fileptr->cf_filename,
+ cep->ce_varlinenum, "set::history::max-storage-per-channel", cep->ce_varname);
errors++;
}
}
} else
{
- config_error_unknown(cepp->ce_fileptr->cf_filename,
- cepp->ce_varlinenum, "set::history::channel", cepp->ce_varname);
- errors++;
+ /* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */
+ int used = 0;
+ Hook *h;
+ for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
+ {
+ int value, errs = 0;
+ if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
+ && !(h->owner->options & MOD_OPT_PERM))
+ continue;
+ value = (*(h->func.intfunc))(cf, cepp, CONFIG_SET_HISTORY_CHANNEL, &errs);
+ if (value == 2)
+ used = 1;
+ if (value == 1)
+ {
+ used = 1;
+ break;
+ }
+ if (value == -1)
+ {
+ used = 1;
+ errors += errs;
+ break;
+ }
+ if (value == -2)
+ {
+ used = 1;
+ errors += errs;
+ }
+ }
+ if (!used)
+ {
+ config_error_unknown(cepp->ce_fileptr->cf_filename,
+ cepp->ce_varlinenum, "set::history::channel", cepp->ce_varname);
+ errors++;
+ }
}
}
} else {
@@ -217,23 +317,25 @@ int history_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
}
}
- if ((on_join_time && maximum_storage_time) && (on_join_time > maximum_storage_time))
- {
- config_error("set::history::channel::playback-on-join::time cannot be higher than set::history::channel::max-storage-per-channel::time. Either set the playback-on-join::time lower or the maximum::time higher.");
- errors++;
- }
- if ((on_join_lines && maximum_storage_lines) && (on_join_lines > maximum_storage_lines))
- {
- config_error("set::history::channel::playback-on-join::lines cannot be higher than set::history::channel::max-storage-per-channel::lines. Either set the playback-on-join::lines lower or the maximum::lines higher.");
- errors++;
- }
+ *errs = errors;
+ return errors ? -1 : 1;
+}
+
+int history_config_posttest(int *errs)
+{
+ int errors = 0;
+
+ /* We could check here for on join lines / on join time being bigger than max storage but..
+ * not really important.
+ */
+
*errs = errors;
return errors ? -1 : 1;
}
int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
- ConfigEntry *cep, *cepp, *cep4;
+ ConfigEntry *cep, *cepp, *cep4, *cep5;
if ((type != CONFIG_SET) || strcmp(ce->ce_varname, "history"))
return 0;
@@ -262,15 +364,44 @@ int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
for (cep4 = cepp->ce_entries; cep4; cep4 = cep4->ce_next)
{
- if (!strcmp(cep4->ce_varname, "lines"))
+ if (!strcmp(cep4->ce_varname, "registered"))
{
- cfg.max_storage_per_channel.lines = atoi(cep4->ce_vardata);
+ for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
+ {
+ if (!strcmp(cep5->ce_varname, "lines"))
+ {
+ cfg.max_storage_per_channel_registered.lines = atoi(cep5->ce_vardata);
+ } else
+ if (!strcmp(cep5->ce_varname, "time"))
+ {
+ cfg.max_storage_per_channel_registered.time = config_checkval(cep5->ce_vardata, CFG_TIME);
+ }
+ }
} else
- if (!strcmp(cep4->ce_varname, "time"))
+ if (!strcmp(cep4->ce_varname, "unregistered"))
{
- cfg.max_storage_per_channel.time = config_checkval(cep4->ce_vardata, CFG_TIME);
+ for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
+ {
+ if (!strcmp(cep5->ce_varname, "lines"))
+ {
+ cfg.max_storage_per_channel_unregistered.lines = atoi(cep5->ce_vardata);
+ } else
+ if (!strcmp(cep5->ce_varname, "time"))
+ {
+ cfg.max_storage_per_channel_unregistered.time = config_checkval(cep5->ce_vardata, CFG_TIME);
+ }
+ }
}
}
+ } else
+ {
+ Hook *h;
+ for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
+ {
+ int value = (*(h->func.intfunc))(cf, cepp, CONFIG_SET_HISTORY_CHANNEL);
+ if (value == 1)
+ break;
+ }
}
}
}
@@ -284,7 +415,7 @@ int history_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
* @param lines: The number of lines (the X in +H X:Y)
* @param t: The time value (the Y in +H X:Y)
*/
-int history_parse_chanmode(char *param, int *lines, long *t)
+int history_parse_chanmode(Channel *channel, char *param, int *lines, long *t)
{
char buf[64], *p, *q;
char contains_non_digit = 0;
@@ -327,12 +458,20 @@ int history_parse_chanmode(char *param, int *lines, long *t)
return 0;
/* Check imposed configuration limits... */
- if (*lines > cfg.max_storage_per_channel.lines)
- *lines = cfg.max_storage_per_channel.lines;
+ if (!channel || has_channel_mode(channel, 'r'))
+ {
+ if (*lines > cfg.max_storage_per_channel_registered.lines)
+ *lines = cfg.max_storage_per_channel_registered.lines;
- if (*t > cfg.max_storage_per_channel.time)
- *t = cfg.max_storage_per_channel.time;
+ if (*t > cfg.max_storage_per_channel_registered.time)
+ *t = cfg.max_storage_per_channel_registered.time;
+ } else {
+ if (*lines > cfg.max_storage_per_channel_unregistered.lines)
+ *lines = cfg.max_storage_per_channel_unregistered.lines;
+ if (*t > cfg.max_storage_per_channel_unregistered.time)
+ *t = cfg.max_storage_per_channel_unregistered.time;
+ }
return 1;
}
@@ -355,7 +494,7 @@ int history_chanmode_is_ok(Client *client, Channel *channel, char mode, char *pa
int lines = 0;
long t = 0L;
- if (!history_parse_chanmode(param, &lines, &t))
+ if (!history_parse_chanmode(channel, param, &lines, &t))
{
sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'H', "Invalid syntax for MODE +H. Use +H lines:period. The period must be in minutes (eg: 10) or a time value (eg: 1h).");
return EX_DENY;
@@ -369,19 +508,37 @@ int history_chanmode_is_ok(Client *client, Channel *channel, char mode, char *pa
return EX_DENY;
}
+static void history_chanmode_helper(char *buf, size_t bufsize, int lines, long t)
+{
+ if ((t % 86400) == 0)
+ {
+ /* Can be represented in full days, eg "1d" */
+ snprintf(buf, bufsize, "%d:%ldd", lines, t / 86400);
+ } else
+ if ((t % 3600) == 0)
+ {
+ /* Can be represented in hours, eg "8h" */
+ snprintf(buf, bufsize, "%d:%ldh", lines, t / 3600);
+ } else
+ {
+ /* Otherwise, stick to minutes */
+ snprintf(buf, bufsize, "%d:%ldm", lines, t / 60);
+ }
+}
+
/** Convert channel parameter to something proper.
* NOTE: client may be NULL if called for e.g. set::modes-playback-on-join
*/
-char *history_chanmode_conv_param(char *param, Client *client)
+char *history_chanmode_conv_param(char *param, Client *client, Channel *channel)
{
static char buf[64];
int lines = 0;
long t = 0L;
- if (!history_parse_chanmode(param, &lines, &t))
+ if (!history_parse_chanmode(channel, param, &lines, &t))
return NULL;
- snprintf(buf, sizeof(buf), "%d:%ldm", lines, t / 60);
+ history_chanmode_helper(buf, sizeof(buf), lines, t);
return buf;
}
@@ -392,7 +549,7 @@ void *history_chanmode_put_param(void *mode_in, char *param)
int lines = 0;
long t = 0L;
- if (!history_parse_chanmode(param, &lines, &t))
+ if (!history_parse_chanmode(NULL, param, &lines, &t))
return NULL;
if (!h)
@@ -416,13 +573,7 @@ char *history_chanmode_get_param(void *h_in)
if (!h_in)
return NULL;
- /* For now we convert the time to minutes for displaying purposes
- * and show it as eg 5:10m.
- * In a later release we can have a go at converting to '1h', '1d'
- * and such, but not before most people run 5.0.2+ as otherwise you
- * get desyncs in channel history retention times.
- */
- snprintf(buf, sizeof(buf), "%d:%ldm", h->max_lines, h->max_time / 60);
+ history_chanmode_helper(buf, sizeof(buf), h->max_lines, h->max_time);
return buf;
}
@@ -531,17 +682,117 @@ int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix,
int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[])
{
- if (!HistoryEnabled(channel))
+ /* Only for +H channels */
+ if (!HistoryEnabled(channel) || !cfg.playback_on_join.lines || !cfg.playback_on_join.time)
return 0;
- if (MyUser(client))
+ /* No history-on-join for clients that implement CHATHISTORY,
+ * they will pull history themselves if they need it.
+ */
+ if (HasCapability(client, "draft/chathistory") || HasCapability(client, "chathistory"))
+ return 0;
+
+ if (MyUser(client) && can_receive_history(client))
{
HistoryFilter filter;
+ HistoryResult *r;
memset(&filter, 0, sizeof(filter));
+ filter.cmd = HFC_SIMPLE;
filter.last_lines = cfg.playback_on_join.lines;
filter.last_seconds = cfg.playback_on_join.time;
- history_request(client, channel->chname, &filter);
+ r = history_request(channel->chname, &filter);
+ if (r)
+ {
+ history_send_result(client, r);
+ free_history_result(r);
+ }
}
return 0;
}
+
+/** Check if a channel went from +r to -r and adjust +H if needed.
+ * This does not only override "MODE" but also "SAMODE", "SJOIN" and more.
+ */
+CMD_OVERRIDE_FUNC(override_mode)
+{
+ Channel *channel;
+ int had_r = 0;
+
+ /* We only bother checking for this corner case if the -r
+ * comes from a server directly linked to us, this normally
+ * means: we are the server that services are linked to.
+ */
+ if ((IsServer(client) && client->local) ||
+ (IsUser(client) && client->srvptr && client->srvptr->local))
+ {
+ /* Now check if the channel is currently +r */
+ if ((parc >= 2) && !BadPtr(parv[1]) && ((channel = find_channel(parv[1], NULL))) &&
+ has_channel_mode(channel, 'r'))
+ {
+ had_r = 1;
+ }
+ }
+ CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+
+ /* If..
+ * - channel was +r
+ * - re-lookup the channel and check that it still
+ * exists (as it may have been destroyed)
+ * - and is now -r
+ * - and has +H set
+ * then...
+ */
+ if (had_r &&
+ ((channel = find_channel(parv[1], NULL))) &&
+ !has_channel_mode(channel, 'r') &&
+ HistoryEnabled(channel))
+ {
+ /* Check if limit is higher than allowed for unregistered channels */
+ HistoryChanMode *settings = (HistoryChanMode *)GETPARASTRUCT(channel, 'H');
+ int changed = 0;
+
+ if (!settings)
+ return; /* Weird */
+
+ if (settings->max_lines > cfg.max_storage_per_channel_unregistered.lines)
+ {
+ settings->max_lines = cfg.max_storage_per_channel_unregistered.lines;
+ changed = 1;
+ }
+
+ if (settings->max_time > cfg.max_storage_per_channel_unregistered.time)
+ {
+ settings->max_time = cfg.max_storage_per_channel_unregistered.time;
+ changed = 1;
+ }
+
+ if (changed)
+ {
+ MessageTag *mtags = NULL;
+ char *params = history_chanmode_get_param(settings);
+
+ if (!params)
+ return; /* Weird */
+
+ strlcpy(modebuf, "+H", sizeof(modebuf));
+ strlcpy(parabuf, params, sizeof(modebuf));
+
+ new_message(&me, NULL, &mtags);
+
+ sendto_channel(channel, &me, &me, 0, 0, SEND_LOCAL, mtags,
+ ":%s MODE %s %s %s",
+ me.name, channel->chname, modebuf, parabuf);
+ sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s %lld",
+ me.id, channel->chname, modebuf, parabuf,
+ (long long)channel->creationtime);
+
+ /* Activate this hook just like cmd_mode.c */
+ RunHook7(HOOKTYPE_REMOTE_CHANMODE, &me, channel, mtags, modebuf, parabuf, 0, 0);
+
+ free_message_tags(mtags);
+
+ *modebuf = *parabuf = '\0';
+ }
+ }
+}
diff --git a/src/modules/chanmodes/link.c b/src/modules/chanmodes/link.c
index 1f17e3f..3be4a43 100644
--- a/src/modules/chanmodes/link.c
+++ b/src/modules/chanmodes/link.c
@@ -48,7 +48,7 @@ typedef enum {
int cmodeL_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what);
void *cmodeL_put_param(void *r_in, char *param);
char *cmodeL_get_param(void *r_in);
-char *cmodeL_conv_param(char *param_in, Client *client);
+char *cmodeL_conv_param(char *param_in, Client *client, Channel *channel);
void cmodeL_free_param(void *r);
void *cmodeL_dup_struct(void *r_in);
int cmodeL_sjoin_check(Channel *channel, void *ourx, void *theirx);
@@ -171,7 +171,7 @@ char *cmodeL_get_param(void *r_in)
/** Convert parameter to something proper.
* NOTE: client may be NULL
*/
-char *cmodeL_conv_param(char *param, Client *client)
+char *cmodeL_conv_param(char *param, Client *client, Channel *channel)
{
char *p;
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
index 442ce00..7df6ddf 100644
--- a/src/modules/channeldb.c
+++ b/src/modules/channeldb.c
@@ -35,14 +35,14 @@ ModuleHeader MOD_HEADER = {
do { \
sendto_realops_and_log("[channeldb] Error writing to temporary database file " \
"'%s': %s (DATABASE NOT SAVED)", \
- fname, strerror(errno)); \
+ fname, unrealdb_get_error_string()); \
} while(0)
#define W_SAFE(x) \
do { \
if (!(x)) { \
WARN_WRITE_ERROR(tmpfname); \
- fclose(fd); \
+ unrealdb_close(db); \
return 0; \
} \
} while(0)
@@ -55,43 +55,53 @@ ModuleHeader MOD_HEADER = {
} \
} while(0)
+/* Structs */
+struct cfgstruct {
+ char *database;
+ char *db_secret;
+};
+
/* Forward declarations */
void channeldb_moddata_free(ModData *md);
-void setcfg(void);
-void freecfg(void);
-int channeldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int channeldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+void setcfg(struct cfgstruct *cfg);
+void freecfg(struct cfgstruct *cfg);
+int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int channeldb_config_posttest(int *errs);
+int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
EVENT(write_channeldb_evt);
int write_channeldb(void);
-int write_channel_entry(FILE *fd, const char *tmpfname, Channel *channel);
+int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel);
int read_channeldb(void);
static void set_channel_mode(Channel *channel, char *modes, char *parameters);
/* Global variables */
static uint32_t channeldb_version = CHANNELDB_VERSION;
-struct cfgstruct {
- char *database;
-};
static struct cfgstruct cfg;
+static struct cfgstruct test;
static long channeldb_next_event = 0;
MOD_TEST()
{
memset(&cfg, 0, sizeof(cfg));
- HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, channeldb_configtest);
+ memset(&test, 0, sizeof(test));
+ setcfg(&test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, channeldb_config_test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, channeldb_config_posttest);
return MOD_SUCCESS;
}
MOD_INIT()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
+ /* We must unload early, when all channel modes and such are still in place: */
+ ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, -99999999);
LoadPersistentLong(modinfo, channeldb_next_event);
- setcfg();
+ setcfg(&cfg);
- HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_configrun);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, channeldb_config_run);
return MOD_SUCCESS;
}
@@ -122,7 +132,10 @@ MOD_LOAD()
MOD_UNLOAD()
{
- freecfg();
+ if (loop.ircd_terminating)
+ write_channeldb();
+ freecfg(&test);
+ freecfg(&cfg);
SavePersistentLong(modinfo, channeldb_next_event);
return MOD_SUCCESS;
}
@@ -133,19 +146,20 @@ void channeldb_moddata_free(ModData *md)
md->i = 0;
}
-void setcfg(void)
+void setcfg(struct cfgstruct *cfg)
{
// Default: data/channel.db
- safe_strdup(cfg.database, "channel.db");
- convert_to_absolute_path(&cfg.database, PERMDATADIR);
+ safe_strdup(cfg->database, "channel.db");
+ convert_to_absolute_path(&cfg->database, PERMDATADIR);
}
-void freecfg(void)
+void freecfg(struct cfgstruct *cfg)
{
- safe_free(cfg.database);
+ safe_free(cfg->database);
+ safe_free(cfg->db_secret);
}
-int channeldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+int channeldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
int errors = 0;
ConfigEntry *cep;
@@ -159,16 +173,45 @@ int channeldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
- if (!cep->ce_vardata) {
+ if (!cep->ce_vardata)
+ {
config_error("%s:%i: blank set::channeldb::%s without value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
errors++;
- continue;
- }
- if (!strcmp(cep->ce_varname, "database")) {
+ } else
+ if (!strcmp(cep->ce_varname, "database"))
+ {
convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
- continue;
+ safe_strdup(test.database, cep->ce_vardata);
+ } else
+ if (!strcmp(cep->ce_varname, "db-secret"))
+ {
+ char *err;
+ if ((err = unrealdb_test_secret(cep->ce_vardata)))
+ {
+ config_error("%s:%i: set::channeldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+ errors++;
+ continue;
+ }
+ safe_strdup(test.db_secret, cep->ce_vardata);
+ } else
+ {
+ config_error("%s:%i: unknown directive set::channeldb::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+ errors++;
}
- config_error("%s:%i: unknown directive set::channeldb::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+ }
+
+ *errs = errors;
+ return errors ? -1 : 1;
+}
+
+int channeldb_config_posttest(int *errs)
+{
+ int errors = 0;
+ char *errstr;
+
+ if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+ {
+ config_error("[channeldb] %s", errstr);
errors++;
}
@@ -176,7 +219,7 @@ int channeldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
return errors ? -1 : 1;
}
-int channeldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+int channeldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
ConfigEntry *cep;
@@ -191,6 +234,8 @@ int channeldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
{
if (!strcmp(cep->ce_varname, "database"))
safe_strdup(cfg.database, cep->ce_vardata);
+ else if (!strcmp(cep->ce_varname, "db-secret"))
+ safe_strdup(cfg.db_secret, cep->ce_vardata);
}
return 1;
}
@@ -206,7 +251,7 @@ EVENT(write_channeldb_evt)
int write_channeldb(void)
{
char tmpfname[512];
- FILE *fd;
+ UnrealDB *db;
Channel *channel;
int cnt = 0;
#ifdef BENCHMARK
@@ -217,33 +262,33 @@ int write_channeldb(void)
// Write to a tempfile first, then rename it if everything succeeded
snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
- fd = fopen(tmpfname, "wb");
- if (!fd)
+ db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+ if (!db)
{
WARN_WRITE_ERROR(tmpfname);
return 0;
}
- W_SAFE(write_data(fd, &channeldb_version, sizeof(channeldb_version)));
+ W_SAFE(unrealdb_write_int32(db, channeldb_version));
/* First, count +P channels and write the count to the database */
for (channel = channels; channel; channel=channel->nextch)
if (has_channel_mode(channel, 'P'))
cnt++;
- W_SAFE(write_int64(fd, cnt));
+ W_SAFE(unrealdb_write_int64(db, cnt));
for (channel = channels; channel; channel=channel->nextch)
{
/* We only care about +P (persistent) channels */
if (has_channel_mode(channel, 'P'))
{
- if (!write_channel_entry(fd, tmpfname, channel))
+ if (!write_channel_entry(db, tmpfname, channel))
return 0;
}
}
// Everything seems to have gone well, attempt to close and rename the tempfile
- if (fclose(fd) != 0)
+ if (!unrealdb_close(db))
{
WARN_WRITE_ERROR(tmpfname);
return 0;
@@ -266,7 +311,7 @@ int write_channeldb(void)
return 1;
}
-int write_listmode(FILE *fd, const char *tmpfname, Ban *lst)
+int write_listmode(UnrealDB *db, const char *tmpfname, Ban *lst)
{
Ban *l;
int cnt = 0;
@@ -274,50 +319,50 @@ int write_listmode(FILE *fd, const char *tmpfname, Ban *lst)
/* First count and write the list count */
for (l = lst; l; l = l->next)
cnt++;
- W_SAFE(write_int32(fd, cnt));
+ W_SAFE(unrealdb_write_int32(db, cnt));
for (l = lst; l; l = l->next)
{
/* The entry, setby, seton */
- W_SAFE(write_str(fd, l->banstr));
- W_SAFE(write_str(fd, l->who));
- W_SAFE(write_int64(fd, l->when));
+ W_SAFE(unrealdb_write_str(db, l->banstr));
+ W_SAFE(unrealdb_write_str(db, l->who));
+ W_SAFE(unrealdb_write_int64(db, l->when));
}
return 1;
}
-int write_channel_entry(FILE *fd, const char *tmpfname, Channel *channel)
+int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel)
{
- W_SAFE(write_int32(fd, MAGIC_CHANNEL_START));
+ W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_START));
/* Channel name */
- W_SAFE(write_str(fd, channel->chname));
+ W_SAFE(unrealdb_write_str(db, channel->chname));
/* Channel creation time */
- W_SAFE(write_int64(fd, channel->creationtime));
+ W_SAFE(unrealdb_write_int64(db, channel->creationtime));
/* Topic (topic, setby, seton) */
- W_SAFE(write_str(fd, channel->topic));
- W_SAFE(write_str(fd, channel->topic_nick));
- W_SAFE(write_int64(fd, channel->topic_time));
+ W_SAFE(unrealdb_write_str(db, channel->topic));
+ W_SAFE(unrealdb_write_str(db, channel->topic_nick));
+ W_SAFE(unrealdb_write_int64(db, channel->topic_time));
/* Basic channel modes (eg: +sntkl key 55) */
channel_modes(&me, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel);
- W_SAFE(write_str(fd, modebuf));
- W_SAFE(write_str(fd, parabuf));
+ W_SAFE(unrealdb_write_str(db, modebuf));
+ W_SAFE(unrealdb_write_str(db, parabuf));
/* Mode lock */
- W_SAFE(write_str(fd, channel->mode_lock));
+ W_SAFE(unrealdb_write_str(db, channel->mode_lock));
/* List modes (bans, exempts, invex) */
- if (!write_listmode(fd, tmpfname, channel->banlist))
+ if (!write_listmode(db, tmpfname, channel->banlist))
return 0;
- if (!write_listmode(fd, tmpfname, channel->exlist))
+ if (!write_listmode(db, tmpfname, channel->exlist))
return 0;
- if (!write_listmode(fd, tmpfname, channel->invexlist))
+ if (!write_listmode(db, tmpfname, channel->invexlist))
return 0;
- W_SAFE(write_int32(fd, MAGIC_CHANNEL_END));
+ W_SAFE(unrealdb_write_int32(db, MAGIC_CHANNEL_END));
return 1;
}
#define R_SAFE(x) \
do { \
if (!(x)) { \
- config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, strerror(errno)); \
+ config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
if (e) \
{ \
safe_free(e->banstr); \
@@ -328,21 +373,21 @@ int write_channel_entry(FILE *fd, const char *tmpfname, Channel *channel)
} \
} while(0)
-int read_listmode(FILE *fd, Ban **lst)
+int read_listmode(UnrealDB *db, Ban **lst)
{
uint32_t total;
uint64_t when;
int i;
Ban *e = NULL;
- R_SAFE(read_data(fd, &total, sizeof(total)));
+ R_SAFE(unrealdb_read_int32(db, &total));
for (i = 0; i < total; i++)
{
e = safe_alloc(sizeof(Ban));
- R_SAFE(read_str(fd, &e->banstr));
- R_SAFE(read_str(fd, &e->who));
- R_SAFE(read_data(fd, &when, sizeof(when)));
+ R_SAFE(unrealdb_read_str(db, &e->banstr));
+ R_SAFE(unrealdb_read_str(db, &e->who));
+ R_SAFE(unrealdb_read_int64(db, &when));
e->when = when;
e->next = *lst;
*lst = e;
@@ -366,8 +411,8 @@ int read_listmode(FILE *fd, Ban **lst)
#define R_SAFE(x) \
do { \
if (!(x)) { \
- config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, strerror(errno)); \
- fclose(fd); \
+ config_warn("[channeldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+ unrealdb_close(db); \
FreeChannelEntry(); \
return 0; \
} \
@@ -375,7 +420,7 @@ int read_listmode(FILE *fd, Ban **lst)
int read_channeldb(void)
{
- FILE *fd;
+ UnrealDB *db;
uint32_t version;
int added = 0;
int i;
@@ -397,29 +442,41 @@ int read_channeldb(void)
gettimeofday(&tv_alpha, NULL);
#endif
- fd = fopen(cfg.database, "rb");
- if (!fd)
+ db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+ if (!db)
{
- if (errno == ENOENT)
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
{
/* Database does not exist. Could be first boot */
config_warn("[channeldb] No database present at '%s', will start a new one", cfg.database);
return 1;
- } else {
- config_warn("[channeldb] Unable to open the database file '%s' for reading: %s", cfg.database, strerror(errno));
+ } else
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+ {
+ /* Re-open as unencrypted */
+ db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+ if (!db)
+ {
+ /* This should actually never happen, unless some weird I/O error */
+ config_warn("[channeldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
+ return 0;
+ }
+ } else
+ {
+ config_warn("[channeldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
return 0;
}
}
- R_SAFE(read_data(fd, &version, sizeof(version)));
+ R_SAFE(unrealdb_read_int32(db, &version));
if (version > channeldb_version)
{
config_warn("[channeldb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, channeldb_version, version);
- fclose(fd);
+ unrealdb_close(db);
return 0;
}
- R_SAFE(read_data(fd, &count, sizeof(count)));
+ R_SAFE(unrealdb_read_int64(db, &count));
for (i=1; i <= count; i++)
{
@@ -434,20 +491,20 @@ int read_channeldb(void)
mode_lock = NULL;
Channel *channel;
- R_SAFE(read_data(fd, &magic, sizeof(magic)));
+ R_SAFE(unrealdb_read_int32(db, &magic));
if (magic != MAGIC_CHANNEL_START)
{
config_error("[channeldb] Corrupt database (%s) - channel magic start is 0x%x. Further reading aborted.", cfg.database, magic);
break;
}
- R_SAFE(read_str(fd, &chname));
- R_SAFE(read_data(fd, &creationtime, sizeof(creationtime)));
- R_SAFE(read_str(fd, &topic));
- R_SAFE(read_str(fd, &topic_nick));
- R_SAFE(read_data(fd, &topic_time, sizeof(topic_time)));
- R_SAFE(read_str(fd, &modes1));
- R_SAFE(read_str(fd, &modes2));
- R_SAFE(read_str(fd, &mode_lock));
+ R_SAFE(unrealdb_read_str(db, &chname));
+ R_SAFE(unrealdb_read_int64(db, &creationtime));
+ R_SAFE(unrealdb_read_str(db, &topic));
+ R_SAFE(unrealdb_read_str(db, &topic_nick));
+ R_SAFE(unrealdb_read_int64(db, &topic_time));
+ R_SAFE(unrealdb_read_str(db, &modes1));
+ R_SAFE(unrealdb_read_str(db, &modes2));
+ R_SAFE(unrealdb_read_str(db, &mode_lock));
/* If we got this far, we can create/initialize the channel with the above */
channel = get_channel(&me, chname, CREATE);
channel->creationtime = creationtime;
@@ -456,10 +513,10 @@ int read_channeldb(void)
channel->topic_time = topic_time;
safe_strdup(channel->mode_lock, mode_lock);
set_channel_mode(channel, modes1, modes2);
- R_SAFE(read_listmode(fd, &channel->banlist));
- R_SAFE(read_listmode(fd, &channel->exlist));
- R_SAFE(read_listmode(fd, &channel->invexlist));
- R_SAFE(read_data(fd, &magic, sizeof(magic)));
+ R_SAFE(read_listmode(db, &channel->banlist));
+ R_SAFE(read_listmode(db, &channel->exlist));
+ R_SAFE(read_listmode(db, &channel->invexlist));
+ R_SAFE(unrealdb_read_int32(db, &magic));
FreeChannelEntry();
added++;
if (magic != MAGIC_CHANNEL_END)
@@ -469,7 +526,7 @@ int read_channeldb(void)
}
}
- fclose(fd);
+ unrealdb_close(db);
if (added)
sendto_realops_and_log("[channeldb] Added %d persistent channels (+P)", added);
diff --git a/src/modules/chathistory.c b/src/modules/chathistory.c
new file mode 100644
index 0000000..dba2d5f
--- /dev/null
+++ b/src/modules/chathistory.c
@@ -0,0 +1,294 @@
+/* src/modules/chathistory.c - IRCv3 CHATHISTORY command.
+ * (C) Copyright 2021 Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2
+ *
+ * This implements the "CHATHISTORY" command, the CAP and 005 token.
+ * https://ircv3.net/specs/extensions/chathistory
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+ "chathistory",
+ "1.0",
+ "IRCv3 CHATHISTORY command",
+ "UnrealIRCd Team",
+ "unrealircd-5",
+};
+
+/* Forward declarations */
+CMD_FUNC(cmd_chathistory);
+
+/* Global variables */
+long CAP_CHATHISTORY = 0L;
+
+/* TODO: consider moving to config file */
+#define CHATHISTORY_LIMIT 50
+
+MOD_INIT()
+{
+ ClientCapabilityInfo c;
+
+ MARK_AS_OFFICIAL_MODULE(modinfo);
+ CommandAdd(modinfo->handle, "CHATHISTORY", cmd_chathistory, MAXPARA, CMD_USER);
+
+ memset(&c, 0, sizeof(c));
+ c.name = "draft/chathistory";
+ ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHATHISTORY);
+ return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+ ISupportSetFmt(modinfo->handle, "CHATHISTORY", "%d", CHATHISTORY_LIMIT);
+ return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+ return MOD_SUCCESS;
+}
+
+int chathistory_token(char *str, char *token, char **store)
+{
+ char *p = strchr(str, '=');
+ if (!p)
+ return 0;
+ *p = '\0'; // frag
+ if (!strcmp(str, token))
+ {
+ *p = '='; // restore
+ *store = strdup(p + 1); // can be \0
+ return 1;
+ }
+ *p = '='; // restore
+ return 0;
+}
+
+static int chathistory_targets_send_line(Client *client, HistoryResult *r, char *batchid)
+{
+ MessageTag *mtags = NULL;
+ MessageTag *m;
+ char *ts;
+
+ if (!r->log || !((m = find_mtag(r->log->mtags, "time"))) || !m->value)
+ return 0;
+ ts = m->value;
+
+ if (!BadPtr(batchid))
+ {
+ mtags = safe_alloc(sizeof(MessageTag));
+ mtags->name = strdup("batch");
+ mtags->value = strdup(batchid);
+ }
+
+ sendto_one(client, mtags, ":%s CHATHISTORY TARGETS %s %s",
+ me.name, r->object, ts);
+
+ if (mtags)
+ free_message_tags(mtags);
+
+ return 1;
+}
+
+void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
+{
+ Membership *mp;
+ HistoryResult *r;
+ char batch[BATCHLEN+1];
+ int sent = 0;
+
+ batch[0] = '\0';
+ if (HasCapability(client, "batch"))
+ {
+ /* Start a new batch */
+ generate_batch_id(batch);
+ sendto_one(client, NULL, ":%s BATCH +%s draft/chathistory-targets", me.name, batch);
+ }
+
+ filter->cmd = HFC_BEFORE;
+ if (strcmp(filter->timestamp_a, filter->timestamp_b) < 0)
+ {
+ /* Swap if needed */
+ char *swap = filter->timestamp_a;
+ filter->timestamp_a = filter->timestamp_b;
+ filter->timestamp_b = swap;
+ }
+ filter->limit = 1;
+
+ for (mp = client->user->channel; mp; mp = mp->next)
+ {
+ Channel *channel = mp->channel;
+ r = history_request(channel->chname, filter);
+ if (r->log && chathistory_targets_send_line(client, r, batch))
+ {
+ if (++sent >= limit)
+ break; /* We are done */
+ }
+ free_history_result(r);
+ r = NULL;
+ }
+
+ /* End of batch */
+ if (*batch)
+ sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
+}
+
+CMD_FUNC(cmd_chathistory)
+{
+ HistoryFilter *filter = NULL;
+ HistoryResult *r = NULL;
+ Channel *channel;
+
+ memset(&filter, 0, sizeof(filter));
+
+ /* This command is only for local users */
+ if (!MyUser(client))
+ return;
+
+ if ((parc < 5) || BadPtr(parv[4]))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS :Insufficient parameters", me.name);
+ return;
+ }
+
+ if (!HasCapability(client, "server-time"))
+ {
+ sendnotice(client, "Your IRC client does not support the 'server-time' capability");
+ sendnotice(client, "https://ircv3.net/specs/extensions/server-time");
+ sendnotice(client, "History request refused.");
+ return;
+ }
+
+ if (!strcmp(parv[1], "TARGETS"))
+ {
+ Membership *mp;
+ int limit;
+
+ filter = safe_alloc(sizeof(HistoryFilter));
+ /* Below this point, instead of 'return', use 'goto end' */
+
+ if (!chathistory_token(parv[2], "timestamp", &filter->timestamp_a))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx",
+ me.name, parv[1], parv[3]);
+ goto end;
+ }
+ if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_b))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx",
+ me.name, parv[1], parv[4]);
+ goto end;
+ }
+ limit = atoi(parv[4]);
+ chathistory_targets(client, filter, limit);
+ goto end;
+ }
+
+ channel = find_channel(parv[2], NULL);
+ if (!channel || !IsMember(client, channel) || !has_channel_mode(channel, 'H'))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_TARGET %s %s :Messages could not be retrieved",
+ me.name, parv[1], parv[2]);
+ return;
+ }
+
+ filter = safe_alloc(sizeof(HistoryFilter));
+ /* Below this point, instead of 'return', use 'goto end', which takes care of the freeing of 'filter' and 'history' */
+
+ if (!strcmp(parv[1], "BEFORE"))
+ {
+ filter->cmd = HFC_BEFORE;
+ if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+ !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+ me.name, parv[1], parv[3]);
+ goto end;
+ }
+ filter->limit = atoi(parv[4]);
+ } else
+ if (!strcmp(parv[1], "AFTER"))
+ {
+ filter->cmd = HFC_AFTER;
+ if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+ !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+ me.name, parv[1], parv[3]);
+ goto end;
+ }
+ filter->limit = atoi(parv[4]);
+ } else
+ if (!strcmp(parv[1], "LATEST"))
+ {
+ filter->cmd = HFC_LATEST;
+ if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+ !chathistory_token(parv[3], "msgid", &filter->msgid_a) &&
+ strcmp(parv[3], "*"))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx or *",
+ me.name, parv[1], parv[3]);
+ goto end;
+ }
+ filter->limit = atoi(parv[4]);
+ } else
+ if (!strcmp(parv[1], "AROUND"))
+ {
+ filter->cmd = HFC_AROUND;
+ if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+ !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+ me.name, parv[1], parv[3]);
+ goto end;
+ }
+ filter->limit = atoi(parv[4]);
+ } else
+ if (!strcmp(parv[1], "BETWEEN"))
+ {
+ filter->cmd = HFC_BETWEEN;
+ if (BadPtr(parv[5]))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s :Insufficient parameters", parv[1], me.name);
+ goto end;
+ }
+ if (!chathistory_token(parv[3], "timestamp", &filter->timestamp_a) &&
+ !chathistory_token(parv[3], "msgid", &filter->msgid_a))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+ me.name, parv[1], parv[3]);
+ goto end;
+ }
+ if (!chathistory_token(parv[4], "timestamp", &filter->timestamp_b) &&
+ !chathistory_token(parv[4], "msgid", &filter->msgid_b))
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %s :Invalid parameter, must be timestamp=xxx or msgid=xxx",
+ me.name, parv[1], parv[4]);
+ goto end;
+ }
+ filter->limit = atoi(parv[5]);
+ } else {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s :Invalid subcommand", me.name, parv[1]);
+ goto end;
+ }
+
+ if (filter->limit <= 0)
+ {
+ sendto_one(client, NULL, ":%s FAIL CHATHISTORY INVALID_PARAMS %s %d :Specified limit is =<0",
+ me.name, parv[1], filter->limit);
+ goto end;
+ }
+
+ if (filter->limit > CHATHISTORY_LIMIT)
+ filter->limit = CHATHISTORY_LIMIT;
+
+ if ((r = history_request(channel->chname, filter)))
+ history_send_result(client, r);
+
+end:
+ if (filter)
+ free_history_filter(filter);
+ if (r)
+ free_history_result(r);
+}
diff --git a/src/modules/dccdeny.c b/src/modules/dccdeny.c
index 9aaf23e..d27a9f7 100644
--- a/src/modules/dccdeny.c
+++ b/src/modules/dccdeny.c
@@ -639,7 +639,7 @@ static int can_dcc(Client *client, char *target, Client *targetcli, char *filena
return 0;
}
- if (match_spamfilter(client, filename, SPAMF_DCC, target, 0, NULL))
+ if (match_spamfilter(client, filename, SPAMF_DCC, "PRIVMSG", target, 0, NULL))
return 0;
if ((fl = dcc_isforbidden(client, filename)))
diff --git a/src/modules/history.c b/src/modules/history.c
index 31d31c1..e8311e0 100644
--- a/src/modules/history.c
+++ b/src/modules/history.c
@@ -72,6 +72,7 @@ void history_usage(Client *client)
CMD_FUNC(cmd_history)
{
HistoryFilter filter;
+ HistoryResult *r;
Channel *channel;
int lines = HISTORY_LINES_DEFAULT;
@@ -115,12 +116,18 @@ CMD_FUNC(cmd_history)
if (!HasCapability(client, "server-time"))
{
sendnotice(client, "Your IRC client does not support the 'server-time' capability");
- sendnotice(client, "https://ircv3.net/specs/extensions/server-time-3.2.html");
+ sendnotice(client, "https://ircv3.net/specs/extensions/server-time");
sendnotice(client, "History request refused.");
return;
}
memset(&filter, 0, sizeof(filter));
+ filter.cmd = HFC_SIMPLE;
filter.last_lines = lines;
- history_request(client, channel->chname, &filter);
+
+ if ((r = history_request(channel->chname, &filter)))
+ {
+ history_send_result(client, r);
+ free_history_result(r);
+ }
}
diff --git a/src/modules/history_backend_mem.c b/src/modules/history_backend_mem.c
index ac0be8d..897da65 100644
--- a/src/modules/history_backend_mem.c
+++ b/src/modules/history_backend_mem.c
@@ -1,5 +1,5 @@
/* src/modules/history_backend_mem.c - History Backend: memory
- * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team
+ * (C) Copyright 2019-2021 Bram Matthys (Syzop) and the UnrealIRCd team
* License: GPLv2
*/
#include "unrealircd.h"
@@ -34,19 +34,34 @@ ModuleHeader MOD_HEADER
* HISTORY_MAX_OFF_SECS: how many seconds may the history be 'off',
* that is: how much may we store the history longer than required.
* The other 2 macros are calculated based on that target.
+ *
+ * Update April 2021: these values are now also used for saving the
+ * history if the persistent option is enabled. Therefore changed the
+ * values to spread it even more out: from 16/128 to 60/300 so
+ * in case of persistent it will save every 5 minutes.
*/
-#define HISTORY_SPREAD 16
-#define HISTORY_MAX_OFF_SECS 128
+#if 0 //was: DEBUGMODE
+#define HISTORY_CLEAN_PER_LOOP HISTORY_BACKEND_MEM_HASH_TABLE_SIZE
+#define HISTORY_TIMER_EVERY 5
+#else
+#define HISTORY_SPREAD 60
+#define HISTORY_MAX_OFF_SECS 300
#define HISTORY_CLEAN_PER_LOOP (HISTORY_BACKEND_MEM_HASH_TABLE_SIZE/HISTORY_SPREAD)
#define HISTORY_TIMER_EVERY (HISTORY_MAX_OFF_SECS/HISTORY_SPREAD)
+#endif
-/* Definitions (structs, etc.) */
-typedef struct HistoryLogLine HistoryLogLine;
-struct HistoryLogLine {
- HistoryLogLine *prev, *next;
- time_t t;
- MessageTag *mtags;
- char line[1];
+/* Some magic numbers used in the database format */
+#define HISTORYDB_MAGIC_FILE_START 0xFEFEFEFE
+#define HISTORYDB_MAGIC_FILE_END 0xEFEFEFEF
+#define HISTORYDB_MAGIC_ENTRY_START 0xFFFFFFFF
+#define HISTORYDB_MAGIC_ENTRY_END 0xEEEEEEEE
+
+/* Definitions (structs, etc.) -- all for persistent history */
+struct cfgstruct {
+ int persist;
+ char *directory;
+ char *masterdb; /* Autogenerated for convenience, not a real config item */
+ char *db_secret;
};
typedef struct HistoryLogObject HistoryLogObject;
@@ -58,30 +73,86 @@ struct HistoryLogObject {
time_t oldest_t; /**< Oldest time in log */
int max_lines; /**< Maximum number of lines permitted */
long max_time; /**< Maximum number of seconds to retain history */
+ int dirty; /**< Dirty flag, used for disk writing */
char name[OBJECTLEN+1];
};
/* Global variables */
-static char siphashkey_history_backend_mem[SIPHASH_KEY_LENGTH];
-HistoryLogObject *history_hash_table[HISTORY_BACKEND_MEM_HASH_TABLE_SIZE];
+struct cfgstruct cfg;
+struct cfgstruct test;
+static char *siphashkey_history_backend_mem = NULL;
+HistoryLogObject **history_hash_table;
+static long already_loaded = 0;
+static char *hbm_prehash = NULL;
+static char *hbm_posthash = NULL;
/* Forward declarations */
+int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int hbm_config_posttest(int *errs);
+int hbm_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+int hbm_rehash(void);
+int hbm_rehash_complete(void);
+static void setcfg(struct cfgstruct *cfg);
+static void freecfg(struct cfgstruct *cfg);
+static void hbm_init_hashes(ModuleInfo *m);
+static void init_history_storage(ModuleInfo *modinfo);
+int hbm_modechar_del(Channel *channel, int modechar);
int hbm_history_add(char *object, MessageTag *mtags, char *line);
int hbm_history_cleanup(HistoryLogObject *h);
-int hbm_history_request(Client *client, char *object, HistoryFilter *filter);
+HistoryResult *hbm_history_request(char *object, HistoryFilter *filter);
int hbm_history_destroy(char *object);
int hbm_history_set_limit(char *object, int max_lines, long max_time);
EVENT(history_mem_clean);
+EVENT(history_mem_init);
+static int hbm_read_masterdb(void);
+static void hbm_read_dbs(void);
+static int hbm_read_db(char *fname);
+static int hbm_write_masterdb(void);
+static int hbm_write_db(HistoryLogObject *h);
+static void hbm_delete_db(HistoryLogObject *h);
+static void hbm_flush(void);
+void hbm_generic_free(ModData *m);
+void hbm_free_all_history(ModData *m);
+
+MOD_TEST()
+{
+ hbm_init_hashes(modinfo);
+ memset(&cfg, 0, sizeof(cfg));
+ memset(&test, 0, sizeof(test));
+ setcfg(&test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, hbm_config_test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, hbm_config_posttest);
+
+ return MOD_SUCCESS;
+}
MOD_INIT()
{
HistoryBackendInfo hbi;
MARK_AS_OFFICIAL_MODULE(modinfo);
- ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
+ /* We must unload early, when all channel modes and such are still in place: */
+ ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, -99999999);
- memset(&history_hash_table, 0, sizeof(history_hash_table));
- siphash_generate_key(siphashkey_history_backend_mem);
+ setcfg(&cfg);
+
+ LoadPersistentLong(modinfo, already_loaded);
+ LoadPersistentPointer(modinfo, siphashkey_history_backend_mem, hbm_generic_free);
+ LoadPersistentPointer(modinfo, history_hash_table, hbm_free_all_history);
+ if (history_hash_table == NULL)
+ history_hash_table = safe_alloc(sizeof(HistoryLogObject *) * HISTORY_BACKEND_MEM_HASH_TABLE_SIZE);
+ /* hbm_prehash & hbm_posthash already loaded in MOD_TEST through hbm_init_hashes() */
+
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, hbm_config_run);
+ HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_DEL, 0, hbm_modechar_del);
+ HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, hbm_rehash);
+ HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, hbm_rehash_complete);
+
+ if (siphashkey_history_backend_mem == NULL)
+ {
+ siphashkey_history_backend_mem = safe_alloc(SIPHASH_KEY_LENGTH);
+ siphash_generate_key(siphashkey_history_backend_mem);
+ }
memset(&hbi, 0, sizeof(hbi));
hbi.name = "mem";
@@ -97,15 +168,264 @@ MOD_INIT()
MOD_LOAD()
{
+ /* Need to save these here already (after conf reading these are set),
+ * as on next round the module reads it in TEST which happens before
+ * the saving in MOD_UNLOAD:
+ */
+ SavePersistentPointer(modinfo, hbm_prehash);
+ SavePersistentPointer(modinfo, hbm_posthash);
+
+ EventAdd(modinfo->handle, "history_mem_init", history_mem_init, NULL, 1, 1);
EventAdd(modinfo->handle, "history_mem_clean", history_mem_clean, NULL, HISTORY_TIMER_EVERY*1000, 0);
+ init_history_storage(modinfo);
return MOD_SUCCESS;
}
+/* Read the .db if 'persist' mode is enabled.
+ * Normally this would be in MOD_LOAD, but the load order always
+ * must be: channeldb first, this module second, and since we
+ * cannot influence the load order we do this silly trick
+ * with a one-time 1msec event.
+ */
+EVENT(history_mem_init)
+{
+ if (!already_loaded)
+ {
+ /* Initial boot / load of the module... */
+ already_loaded = 1;
+ if (cfg.persist)
+ hbm_read_dbs();
+ }
+}
+
MOD_UNLOAD()
{
+ if (loop.ircd_terminating)
+ hbm_flush();
+ freecfg(&test);
+ freecfg(&cfg);
+ SavePersistentPointer(modinfo, hbm_prehash);
+ SavePersistentPointer(modinfo, hbm_posthash);
+ SavePersistentPointer(modinfo, history_hash_table);
+ SavePersistentPointer(modinfo, siphashkey_history_backend_mem);
+ SavePersistentLong(modinfo, already_loaded);
return MOD_SUCCESS;
}
+/** Set cfg->masterdb based on cfg->directory, for convenience */
+static void hbm_set_masterdb_filename(struct cfgstruct *cfg)
+{
+ char buf[512];
+
+ safe_free(cfg->masterdb);
+ if (cfg->directory)
+ {
+ snprintf(buf, sizeof(buf), "%s/master.db", cfg->directory);
+ safe_strdup(cfg->masterdb, buf);
+ }
+}
+
+/** Default configuration for set::history::channel */
+static void setcfg(struct cfgstruct *cfg)
+{
+ safe_strdup(cfg->directory, "history");
+ convert_to_absolute_path(&cfg->directory, PERMDATADIR);
+ hbm_set_masterdb_filename(cfg);
+}
+
+static void freecfg(struct cfgstruct *cfg)
+{
+ safe_free(cfg->directory);
+ safe_free(cfg->db_secret);
+}
+
+static void hbm_init_hashes(ModuleInfo *modinfo)
+{
+ char buf[256];
+
+ LoadPersistentPointer(modinfo, hbm_prehash, hbm_generic_free);
+ LoadPersistentPointer(modinfo, hbm_posthash, hbm_generic_free);
+
+ if (!hbm_prehash)
+ {
+ gen_random_alnum(buf, 128);
+ safe_strdup(hbm_prehash, buf);
+ }
+
+ if (!hbm_posthash)
+ {
+ gen_random_alnum(buf, 128);
+ safe_strdup(hbm_posthash, buf);
+ }
+}
+
+/** Test the set::history::channel configuration */
+int hbm_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+ int errors = 0;
+
+ if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->ce_varname)
+ return 0;
+
+ if (!strcmp(ce->ce_varname, "persist"))
+ {
+ if (!ce->ce_vardata)
+ {
+ config_error("%s:%i: missing parameter",
+ ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+ errors++;
+ } else {
+ test.persist = config_checkval(ce->ce_vardata, CFG_YESNO);
+ }
+ } else
+ if (!strcmp(ce->ce_varname, "db-secret"))
+ {
+ char *err;
+ if ((err = unrealdb_test_secret(ce->ce_vardata)))
+ {
+ config_error("%s:%i: set::history::channel::db-secret: %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, err);
+ errors++;
+ }
+ safe_strdup(test.db_secret, ce->ce_vardata);
+ } else
+ if (!strcmp(ce->ce_varname, "directory")) // or "path" ?
+ {
+ if (!ce->ce_vardata)
+ {
+ config_error("%s:%i: missing parameter",
+ ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+ errors++;
+ } else
+ {
+ safe_strdup(test.directory, ce->ce_vardata);
+ hbm_set_masterdb_filename(&test);
+ }
+ } else
+ {
+ return 0; /* unknown option to us, let another module handle it */
+ }
+
+ *errs = errors;
+ return errors ? -1 : 1;
+}
+
+/** Post-configuration test on set::history::channel */
+int hbm_config_posttest(int *errs)
+{
+ int errors = 0;
+
+ if (test.db_secret && !test.persist)
+ {
+ config_error("set::history::channel::db-secret is set but set::history::channel::persist is disabled, this makes no sense. "
+ "Either use 'persist yes' or comment out / delete 'db-secret'.");
+ errors++;
+ } else
+ if (!test.db_secret && test.persist)
+ {
+ config_error("set::history::channel::db-secret needs to be set."); // TODO: REFER TO FAQ OR OTHER ENTRY!!!!
+ errors++;
+ } else
+ if (test.db_secret && test.persist)
+ {
+ /* Configuration is good, now check if the password is correct
+ * (if we can check at all, that is)...
+ */
+ char *errstr = NULL;
+ if (test.masterdb && ((errstr = unrealdb_test_db(test.masterdb, test.db_secret))))
+ {
+ config_error("[history] %s", errstr);
+ errors++;
+ goto hbm_config_posttest_end;
+ }
+
+ /* Ensure directory exists and is writable */
+#ifdef _WIN32
+ (void)mkdir(test.directory); /* (errors ignored) */
+#else
+ (void)mkdir(test.directory, S_IRUSR|S_IWUSR|S_IXUSR); /* (errors ignored) */
+#endif
+ if (!file_exists(test.directory))
+ {
+ config_error("[history] Directory %s does not exist and could not be created",
+ test.directory);
+ errors++;
+ } else
+ {
+ /* Only do this if directory actually exists, hence in the 'else' block */
+ if (!hbm_read_masterdb())
+ errors++;
+ }
+ }
+
+hbm_config_posttest_end:
+ freecfg(&test);
+ setcfg(&test);
+ *errs = errors;
+ return errors ? -1 : 1;
+}
+
+/** Configure ourselves based on the set::history::channel settings */
+int hbm_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+ if ((type != CONFIG_SET_HISTORY_CHANNEL) || !ce || !ce->ce_varname)
+ return 0;
+
+ if (!strcmp(ce->ce_varname, "persist"))
+ {
+ cfg.persist = config_checkval(ce->ce_vardata, CFG_YESNO);
+ } else
+ if (!strcmp(ce->ce_varname, "directory")) // or "path" ?
+ {
+ safe_strdup(cfg.directory, ce->ce_vardata);
+ convert_to_absolute_path(&cfg.directory, PERMDATADIR);
+ hbm_set_masterdb_filename(&cfg);
+ } else
+ if (!strcmp(ce->ce_varname, "db-secret"))
+ {
+ safe_strdup(cfg.db_secret, ce->ce_vardata);
+ } else
+ {
+ return 0; /* unknown option to us, let another module handle it */
+ }
+
+ return 1; /* handled by us */
+}
+
+int hbm_rehash(void)
+{
+ freecfg(&cfg);
+ setcfg(&cfg);
+ return 0;
+}
+
+int hbm_rehash_complete(void)
+{
+ return 0;
+}
+
+char *history_storage_capability_parameter(Client *client)
+{
+ static char buf[128];
+
+ if (cfg.persist)
+ strlcpy(buf, "memory,disk=encrypted", sizeof(buf));
+ else
+ strlcpy(buf, "memory", sizeof(buf));
+
+ return buf;
+}
+
+static void init_history_storage(ModuleInfo *modinfo)
+{
+ ClientCapabilityInfo cap;
+
+ memset(&cap, 0, sizeof(cap));
+ cap.name = "unrealircd.org/history-storage";
+ cap.flags = CLICAP_FLAGS_ADVERTISE_ONLY;
+ cap.parameter = history_storage_capability_parameter;
+ ClientCapabilityAdd(modinfo->handle, &cap, NULL);
+}
+
uint64_t hbm_hash(char *object)
{
return siphash_nocase(object, siphashkey_history_backend_mem) % HISTORY_BACKEND_MEM_HASH_TABLE_SIZE;
@@ -143,12 +463,38 @@ HistoryLogObject *hbm_find_or_add_object(char *object)
void hbm_delete_object_hlo(HistoryLogObject *h)
{
- int hashv = hbm_hash(h->name);
+ int hashv;
+ if (cfg.persist)
+ hbm_delete_db(h);
+
+ hashv = hbm_hash(h->name);
DelListItem(h, history_hash_table[hashv]);
safe_free(h);
}
+int hbm_modechar_del(Channel *channel, int modechar)
+{
+ HistoryLogObject *h;
+
+ if (!cfg.persist)
+ return 0;
+
+ if ((modechar == 'P') && ((h = hbm_find_object(channel->chname))))
+ {
+ /* Channel went from +P to -P and also has channel history: delete the history file */
+ hbm_delete_db(h);
+
+ h->dirty = 1;
+ /* The reason for marking the entry as 'dirty' is that someone may later
+ * set the channel +P again. If we would not set the h->dirty=1 then this
+ * would mean the history log would not get rewritten until someone speaks.
+ */
+ }
+
+ return 0;
+}
+
void hbm_duplicate_mtags(HistoryLogLine *l, MessageTag *m)
{
MessageTag *n;
@@ -207,6 +553,7 @@ void hbm_history_add_line(HistoryLogObject *h, MessageTag *mtags, char *line)
/* no tail, no head */
h->head = h->tail = l;
}
+ h->dirty = 1;
h->num_lines++;
if ((l->t < h->oldest_t) || (h->oldest_t == 0))
h->oldest_t = l->t;
@@ -233,6 +580,7 @@ void hbm_history_del_line(HistoryLogObject *h, HistoryLogLine *l)
free_message_tags(l->mtags);
safe_free(l);
+ h->dirty = 1;
h->num_lines--;
/* IMPORTANT: updating h->oldest_t takes place at the caller
@@ -263,54 +611,188 @@ int hbm_history_add(char *object, MessageTag *mtags, char *line)
return 0;
}
-int can_receive_history(Client *client)
+HistoryLogLine *duplicate_log_line(HistoryLogLine *l)
{
- if (HasCapability(client, "server-time"))
- return 1;
- return 0;
+ HistoryLogLine *n = safe_alloc(sizeof(HistoryLogLine) + strlen(l->line));
+ strcpy(n->line, l->line); /* safe, see memory allocation above ^ */
+ hbm_duplicate_mtags(n, l->mtags);
+ return n;
}
-void hbm_send_line(Client *client, HistoryLogLine *l, char *batchid)
+/** Quickly append a new line 'n' to result 'r' */
+static void hbm_result_append_line(HistoryResult *r, HistoryLogLine *n)
{
- if (can_receive_history(client))
+ if (!r->log)
{
- if (BadPtr(batchid))
+ /* First item */
+ r->log = r->log_tail = n;
+ } else
+ {
+ /* Quick append to tail */
+ r->log_tail->next = n;
+ n->prev = r->log_tail;
+ r->log_tail = n; /* we are the new tail */
+ }
+}
+
+/** Quickly prepend a new line 'n' to result 'r' */
+static void hbm_result_prepend_line(HistoryResult *r, HistoryLogLine *n)
+{
+ if (!r->log)
+ r->log_tail = n;
+ AddListItem(n, r->log);
+}
+
+/** Put lines in HistoryResult that are after a certain msgid or
+ * timestamp (excluding said msgid/timestamp).
+ * @param r The history result set that we will use
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ * which is a perfectly valid result.
+ */
+static int hbm_return_after(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+ HistoryLogLine *l, *n;
+ int written = 0;
+ int started = 0;
+ MessageTag *m;
+
+ for (l = h->head; l; l = l->next)
+ {
+ /* Not started yet? Check if this is the starting point... */
+ if (!started)
{
- sendto_one(client, l->mtags, "%s", l->line);
- } else {
- MessageTag *m = safe_alloc(sizeof(MessageTag));
- m->name = "batch";
- m->value = batchid;
- AddListItem(m, l->mtags);
- sendto_one(client, l->mtags, "%s", l->line);
- DelListItem(m, l->mtags);
- safe_free(m);
+ if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) > 0))
+ {
+ started = 1;
+ } else
+ if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+ {
+ started = 1;
+ continue;
+ }
+ }
+ if (started)
+ {
+ /* Check if we need to stop */
+ if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) >= 0))
+ {
+ break;
+ } else
+ if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
+ {
+ break;
+ }
+
+ /* Add line to the return buffer */
+ n = duplicate_log_line(l);
+ hbm_result_append_line(r, n);
+ if (++written >= filter->limit)
+ break;
}
- } else {
- /* without server-time, log playback is a bit annoying, so skip it? */
}
+
+ return written;
}
-int hbm_history_request(Client *client, char *object, HistoryFilter *filter)
+/** Put lines in HistoryResult that before after a certain msgid or
+ * timestamp (excluding said msgid/timestamp).
+ * @param r The history result set that we will use
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ * which is a perfectly valid result.
+ */
+static int hbm_return_before(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
{
- HistoryLogObject *h = hbm_find_object(object);
- HistoryLogLine *l;
- char batch[BATCHLEN+1];
- long redline; /* Imaginary timestamp. Before the red line, history is too old. */
- int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
+ HistoryLogLine *l, *n;
+ int written = 0;
+ int started = 0;
+ MessageTag *m;
- if (!h || !can_receive_history(client))
- return 0;
-
- batch[0] = '\0';
-
- if (HasCapability(client, "batch"))
+ for (l = h->tail; l; l = l->prev)
{
- /* Start a new batch */
- generate_batch_id(batch);
- sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, object);
+ /* Not started yet? Check if this is the starting point... */
+ if (!started)
+ {
+ if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) < 0))
+ {
+ started = 1;
+ } else
+ if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+ {
+ started = 1;
+ continue;
+ }
+ }
+ if (started)
+ {
+ /* Check if we need to stop */
+ if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) < 0))
+ {
+ break;
+ } else
+ if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
+ {
+ break;
+ }
+
+ /* Add line to the return buffer */
+ n = duplicate_log_line(l);
+ hbm_result_prepend_line(r, n);
+ if (++written >= filter->limit)
+ break;
+ }
}
+ return written;
+}
+
+/** Put lines in HistoryResult that are 'latest'
+ * @param r The history result set that we will use
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ * which is a perfectly valid result.
+ */
+static int hbm_return_latest(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+ HistoryLogLine *l, *n;
+ int written = 0;
+ MessageTag *m;
+
+ for (l = h->tail; l; l = l->prev)
+ {
+ if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) <= 0))
+ break; /* Stop now */
+ else
+ if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+ break; /* Stop now */
+
+ n = duplicate_log_line(l);
+ hbm_result_prepend_line(r, n);
+ if (++written >= filter->limit)
+ break;
+ }
+
+ return written;
+}
+
+/** Put lines in HistoryResult based on a 'simple' request, that is: maximum lines or time
+ * @param r The history result set that we will use
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ * which is a perfectly valid result.
+ */
+static int hbm_return_simple(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+ HistoryLogLine *l;
+ int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
+ long redline;
+ int written = 0;
+
/* Decide on red line, under this the history is too old.
* Filter can be more strict than history object (but not the other way around):
*/
@@ -336,13 +818,190 @@ int hbm_history_request(Client *client, char *object, HistoryFilter *filter)
* taken into account in hbm_history_add.
*/
if (l->t >= redline && (++cnt > lines_to_skip))
- hbm_send_line(client, l, batch);
+ {
+ /* Add to result */
+ HistoryLogLine *n = duplicate_log_line(l);
+ hbm_result_append_line(r, n);
+ written++;
+ }
}
- /* End of batch */
- if (*batch)
- sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
- return 1;
+ return written;
+}
+
+/** Put lines in HistoryResult that are 'around' a certain point.
+ * @param r The history result set that we will use
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ * which is a perfectly valid result.
+ */
+static int hbm_return_around(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+ int n = 0;
+ int orig_limit = filter->limit;
+
+ /* First request 50% above the search term */
+ if (filter->limit > 1)
+ filter->limit = filter->limit / 2;
+ n = hbm_return_before(r, h, filter);
+ /* Then the remainder (50% or more) below the search term.
+ *
+ * Ok, well, unless the original limit was 1 and we already
+ * sent 1 line, then we may not send anything anymore..
+ */
+ filter->limit = orig_limit - n;
+ if (filter->limit > 0)
+ n += hbm_return_after(r, h, filter);
+
+ return n;
+}
+
+/** Figure out the direction (forwards or backwards) for CHATHISTORY BETWEEN request
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns 0 for backward searching, 1 for forward searching, -1 for invalid / not found
+ */
+static int hbm_return_between_figure_out_direction(HistoryLogObject *h, HistoryFilter *filter)
+{
+ HistoryLogLine *l;
+ int found_a = 0;
+ int found_b = 0;
+ MessageTag *m;
+
+ /* Two timestamps? Then we can easily tell the direction. */
+ if (filter->timestamp_a && filter->timestamp_b)
+ return (strcmp(filter->timestamp_a, filter->timestamp_b) <= 0) ? 1 : 0;
+
+ for (l = h->head; l; l = l->next)
+ {
+ if (!found_a)
+ {
+ if (filter->timestamp_a && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_a) >= 0))
+ {
+ found_a = 1;
+ } else
+ if (filter->msgid_a && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_a))
+ {
+ found_a = 1;
+ }
+ if (found_a)
+ {
+ if (found_b)
+ {
+ /* B was found before A? Then the result is: backwards */
+ return 0;
+ }
+ if (filter->timestamp_b && (m = find_mtag(l->mtags, "time")) && m->value)
+ {
+ /* We can already resolve the direction now: */
+ char *timestamp_a = m->value;
+ return (strcmp(timestamp_a, filter->timestamp_b) <= 0) ? 1 : 0;
+ }
+ }
+ }
+ if (!found_b)
+ {
+ if (filter->timestamp_b && ((m = find_mtag(l->mtags, "time"))) && (strcmp(m->value, filter->timestamp_b) >= 0))
+ {
+ found_b = 1;
+ } else
+ if (filter->msgid_b && ((m = find_mtag(l->mtags, "msgid"))) && !strcmp(m->value, filter->msgid_b))
+ {
+ found_b = 1;
+ }
+ if (found_b)
+ {
+ if (found_a)
+ {
+ /* A was found before B? Then the result is: forwards */
+ return 1;
+ }
+ if (filter->timestamp_a && (m = find_mtag(l->mtags, "time")) && m->value)
+ {
+ /* We can already resolve the direction now: */
+ char *timestamp_b = m->value;
+ return (strcmp(filter->timestamp_a, timestamp_b) <= 0) ? 1 : 0;
+ }
+ }
+ }
+ }
+
+ /* Neither points were found OR
+ * one of the point is a msgid that could not be found.
+ */
+ return -1; /* Result: invalid */
+}
+
+/** Put lines in HistoryResult that are 'between' two points.
+ * @param r The history result set that we will use
+ * @param h The history log object
+ * @param filter The filter that applies
+ * @returns Number of lines written, note that this could be zero,
+ * which is a perfectly valid result.
+ */
+static int hbm_return_between(HistoryResult *r, HistoryLogObject *h, HistoryFilter *filter)
+{
+ int direction;
+
+ direction = hbm_return_between_figure_out_direction(h, filter);
+
+ if (direction == 1)
+ return hbm_return_after(r, h, filter);
+ else if (direction == 0)
+ return hbm_return_before(r, h, filter);
+ /* else direction is -1 which means not found / invalid */
+
+ return 0;
+}
+
+HistoryResult *hbm_history_request(char *object, HistoryFilter *filter)
+{
+ HistoryResult *r;
+ HistoryLogObject *h = hbm_find_object(object);
+ HistoryLogLine *l;
+ int lines_sendable = 0, lines_to_skip = 0, cnt = 0;
+ long redline;
+
+ if (!h)
+ return NULL; /* nothing found */
+
+ /* Check if we need to remove some history entries due to 'time'.
+ * No need to worry about 'count' as that is being taken care off
+ * by hbm_history_add().
+ */
+ if (h->oldest_t < TStime() - h->max_time)
+ hbm_history_cleanup(h);
+
+ r = safe_alloc(sizeof(HistoryResult));
+ safe_strdup(r->object, object);
+
+ switch(filter->cmd)
+ {
+ case HFC_BEFORE:
+ hbm_return_before(r, h, filter);
+ break;
+ case HFC_AFTER:
+ hbm_return_after(r, h, filter);
+ break;
+ case HFC_LATEST:
+ hbm_return_latest(r, h, filter);
+ break;
+ case HFC_AROUND:
+ hbm_return_around(r, h, filter);
+ break;
+ case HFC_BETWEEN:
+ hbm_return_between(r, h, filter);
+ break;
+ case HFC_SIMPLE:
+ hbm_return_simple(r, h, filter);
+ break;
+ default:
+ // unhandled
+ break;
+ }
+
+ return r;
}
/** Clean up expired entries */
@@ -425,6 +1084,367 @@ int hbm_history_set_limit(char *object, int max_lines, long max_time)
return 1;
}
+/** Read the master.db file, this is done at the INIT stage so we can still
+ * reject the configuration / boot attempt.
+ *
+ * IMPORTANT: Because we run at INIT you must use test.xyz values and not cfg.xyz!
+ */
+static int hbm_read_masterdb(void)
+{
+ UnrealDB *db;
+ uint32_t mdb_version;
+ char *prehash = NULL;
+ char *posthash = NULL;
+
+ db = unrealdb_open(test.masterdb, UNREALDB_MODE_READ, test.db_secret);
+
+ if (!db)
+ {
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+ {
+ /* Database does not exist. Could be first boot */
+ config_warn("[history] No database present at '%s', will start a new one", test.masterdb);
+ // TODO: maybe check for condition where 'master.db' does not exist but
+ // there are other .db files.
+ if (!hbm_write_masterdb())
+ return 0; /* fatal error */
+ return 1;
+ } else
+ {
+ config_warn("[history] Unable to open the database file '%s' for reading: %s", test.masterdb, unrealdb_get_error_string());
+ return 0;
+ }
+ }
+
+ /* Master db has an easy format:
+ * 64 bits: version number
+ * string: pre hash
+ * string: post hash
+ */
+ if (!unrealdb_read_int32(db, &mdb_version) ||
+ !unrealdb_read_str(db, &prehash) ||
+ !unrealdb_read_str(db, &posthash))
+ {
+ safe_free(prehash);
+ safe_free(posthash);
+ config_error("[history] Read error from database file '%s': %s",
+ test.masterdb, unrealdb_get_error_string());
+ unrealdb_close(db);
+ return 0;
+ }
+ unrealdb_close(db);
+
+ if (!prehash || !posthash)
+ {
+ config_error("[history] Read error from database file '%s': unexpected values encountered",
+ test.masterdb);
+ return 0;
+ }
+
+ /* Now, safely switch over.. */
+ safe_free(hbm_prehash);
+ safe_free(hbm_posthash);
+ hbm_prehash = prehash;
+ hbm_posthash = posthash;
+
+ return 1;
+}
+
+/** Write the master.db file. Only call this if it does not exist yet! */
+static int hbm_write_masterdb(void)
+{
+ UnrealDB *db;
+ uint32_t mdb_version;
+
+ if (!test.db_secret)
+ abort();
+
+ db = unrealdb_open(test.masterdb, UNREALDB_MODE_WRITE, test.db_secret);
+ if (!db)
+ {
+ config_error("[history] Unable to write to '%s': %s",
+ test.masterdb, unrealdb_get_error_string());
+ return 0;
+ }
+
+ if (!hbm_prehash || !hbm_posthash)
+ abort(); /* impossible */
+
+ mdb_version = 5000;
+ if (!unrealdb_write_int32(db, mdb_version) ||
+ !unrealdb_write_str(db, hbm_prehash) ||
+ !unrealdb_write_str(db, hbm_posthash))
+ {
+ config_error("[history] Unable to write to '%s': %s",
+ test.masterdb, unrealdb_get_error_string());
+ return 0;
+ }
+ unrealdb_close(db);
+ return 1;
+}
+
+/** Read all database files (except master.db, which is already loaded) */
+static void hbm_read_dbs(void)
+{
+ char buf[512];
+#ifndef _WIN32
+ struct dirent *dir;
+ DIR *fd = opendir(cfg.directory);
+
+ if (!fd)
+ return;
+
+ while ((dir = readdir(fd)))
+ {
+ char *fname = dir->d_name;
+#else
+ /* Windows */
+ WIN32_FIND_DATA hData;
+ HANDLE hFile;
+ char xbuf[512];
+
+ snprintf(xbuf, sizeof(xbuf), "%s/*.db", cfg.directory);
+
+ hFile = FindFirstFile(xbuf, &hData);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return;
+
+ do
+ {
+ char *fname = hData.cFileName;
+#endif
+
+ /* Common section for both *NIX and Windows */
+
+ snprintf(buf, sizeof(buf), "%s/%s", cfg.directory, fname);
+ if (filename_has_suffix(fname, ".db") && strcmp(fname, "master.db"))
+ {
+ if (!hbm_read_db(buf))
+ {
+ /* On error, we move the file to the 'bad' subdirectory,
+ * eg data/history/bad/xyz.db
+ */
+ char buf2[512];
+ snprintf(buf2, sizeof(buf2), "%s/bad", cfg.directory);
+#ifdef _WIN32
+ (void)mkdir(buf2); /* (errors ignored) */
+#else
+ (void)mkdir(buf2, S_IRUSR|S_IWUSR|S_IXUSR); /* (errors ignored) */
+#endif
+ snprintf(buf2, sizeof(buf2), "%s/bad/%s", cfg.directory, fname);
+ unlink(buf2);
+ (void)rename(buf, buf2);
+ }
+ }
+
+ /* End of common section */
+#ifndef _WIN32
+ }
+ closedir(fd);
+#else
+ } while (FindNextFile(hFile, &hData));
+ FindClose(hFile);
+#endif
+}
+
+#define RESET_VALUES_LOOP() do { \
+ safe_free(mtag_name); \
+ safe_free(mtag_value); \
+ safe_free(line); \
+ free_message_tags(mtags); \
+ mtags = NULL; \
+ magic = 0; \
+ line_ts = 0; \
+ } while(0)
+
+#define R_SAFE_CLEANUP() do { \
+ unrealdb_close(db); \
+ RESET_VALUES_LOOP(); \
+ safe_free(prehash); \
+ safe_free(posthash); \
+ safe_free(object); \
+ } while(0)
+#define R_SAFE(x) \
+ do { \
+ if (!(x)) { \
+ config_warn("[history] Read error from database file '%s' (possible corruption): %s", fname, unrealdb_get_error_string()); \
+ R_SAFE_CLEANUP(); \
+ return 0; \
+ } \
+ } while(0)
+
+
+/** Read a channel history db file */
+static int hbm_read_db(char *fname)
+{
+ UnrealDB *db = NULL;
+ // header
+ uint32_t magic = 0;
+ uint32_t version = 0;
+ char *prehash = NULL;
+ char *posthash = NULL;
+ char *object = NULL;
+ uint64_t max_lines = 0;
+ uint64_t max_time = 0;
+ // then, for each entry:
+ // (magic)
+ uint64_t line_ts;
+ char *mtag_name = NULL;
+ char *mtag_value = NULL;
+ MessageTag *mtags = NULL, *m;
+ char *line = NULL;
+ HistoryLogObject *h;
+
+ db = unrealdb_open(fname, UNREALDB_MODE_READ, cfg.db_secret);
+ if (!db)
+ {
+ config_warn("[history] Unable to open the database file '%s' for reading: %s", fname, unrealdb_get_error_string());
+ return 0;
+ }
+
+ R_SAFE(unrealdb_read_int32(db, &magic));
+ if (magic != HISTORYDB_MAGIC_FILE_START)
+ {
+ config_warn("[history] Database '%s' has wrong magic value, possibly corrupt (0x%lx), expected HISTORYDB_MAGIC_FILE_START.",
+ fname, (long)magic);
+ unrealdb_close(db);
+ return 0;
+ }
+
+ /* Now do a version check */
+ R_SAFE(unrealdb_read_int32(db, &version));
+ if (version < 4999)
+ {
+ config_warn("[history] Database '%s' uses an unsupported - possibly old - format (%ld).", fname, (long)version);
+ unrealdb_close(db);
+ return 0;
+ }
+ if (version > 5000)
+ {
+ config_warn("[history] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
+ fname, (unsigned long)version, (unsigned long)5000);
+ unrealdb_close(db);
+ return 0;
+ }
+
+ R_SAFE(unrealdb_read_str(db, &prehash));
+ R_SAFE(unrealdb_read_str(db, &posthash));
+
+ if (!prehash || !posthash || strcmp(prehash, hbm_prehash) || strcmp(posthash, hbm_posthash))
+ {
+ config_warn("[history] Database '%s' does not belong to our 'master.db'. Are you mixing old with new .db files perhaps? This is not supported. File ignored.",
+ fname);
+ R_SAFE_CLEANUP();
+ return 0;
+ }
+
+ R_SAFE(unrealdb_read_str(db, &object));
+ R_SAFE(unrealdb_read_int64(db, &max_lines));
+ R_SAFE(unrealdb_read_int64(db, &max_time));
+ h = hbm_find_object(object);
+ if (!h)
+ {
+ config_warn("Channel %s does not have +H set, deleting history", object);
+ R_SAFE_CLEANUP();
+ unlink(fname);
+ return 1; /* No problem */
+ }
+
+ while(1)
+ {
+ RESET_VALUES_LOOP();
+ R_SAFE(unrealdb_read_int32(db, &magic));
+ if (magic == HISTORYDB_MAGIC_FILE_END)
+ break; /* We're done, end gracefully */
+ if (magic != HISTORYDB_MAGIC_ENTRY_START)
+ {
+ config_warn("[history] Read error from database file '%s': wrong magic value in entry (0x%lx), expected HISTORYDB_MAGIC_ENTRY_START",
+ fname, (long)magic);
+ R_SAFE_CLEANUP();
+ return 0;
+ }
+
+ R_SAFE(unrealdb_read_int64(db, &line_ts));
+ while(1)
+ {
+ R_SAFE(unrealdb_read_str(db, &mtag_name));
+ R_SAFE(unrealdb_read_str(db, &mtag_value));
+ if (!mtag_name && !mtag_value)
+ break; /* We're done reading mtags for this particular line */
+ m = safe_alloc(sizeof(MessageTag));
+ safe_strdup(m->name, mtag_name);
+ safe_strdup(m->value, mtag_value);
+ AppendListItem(m, mtags);
+ safe_free(mtag_name);
+ safe_free(mtag_value);
+ }
+ R_SAFE(unrealdb_read_str(db, &line));
+ R_SAFE(unrealdb_read_int32(db, &magic));
+ if (magic != HISTORYDB_MAGIC_ENTRY_END)
+ {
+ config_warn("[history] Read error from database file '%s': wrong magic value in entry (0x%lx), expected HISTORYDB_MAGIC_ENTRY_END",
+ fname, (long)magic);
+ R_SAFE_CLEANUP();
+ return 0;
+ }
+ hbm_history_add(object, mtags, line);
+ }
+
+ /* Prevent directly rewriting the channel, now that we have just read it.
+ * This could cause things not to fire in case of corner issues like
+ * hot-loading but that should be acceptable. The alternative is that
+ * all log files are written again with identical contents for no reason,
+ * which is a waste of resources.
+ */
+ h->dirty = 0;
+
+ R_SAFE_CLEANUP();
+ return 1;
+}
+
+/** Flush all dirty logs to disk on UnrealIRCd stop */
+static void hbm_flush(void)
+{
+ int hashnum;
+ HistoryLogObject *h;
+
+ if (!cfg.persist)
+ return; /* nothing to flush anyway */
+
+ for (hashnum = 0; hashnum < HISTORY_BACKEND_MEM_HASH_TABLE_SIZE; hashnum++)
+ {
+ for (h = history_hash_table[hashnum]; h; h = h->next)
+ {
+ hbm_history_cleanup(h);
+ if (cfg.persist && h->dirty)
+ hbm_write_db(h);
+ }
+ }
+}
+
+/** Free all history.
+ * This is only called when the module is unloaded for good, so
+ * when UnrealIRCd is terminating or someone comments the module out
+ * and/or switches history backends.
+ */
+void hbm_free_all_history(ModData *m)
+{
+ int hashnum;
+ HistoryLogObject *h, *h_next;
+
+ for (hashnum = 0; hashnum < HISTORY_BACKEND_MEM_HASH_TABLE_SIZE; hashnum++)
+ {
+ for (h = history_hash_table[hashnum]; h; h = h_next)
+ {
+ h_next = h->next;
+ hbm_history_destroy(h->name);
+ }
+ }
+
+ /* And free the hash table pointer */
+ safe_free(m->ptr);
+}
+
/** Periodically clean the history.
* Instead of doing all channels in 1 go, we do a limited number
* of channels each call, hence the 'static int' and the do { } while
@@ -441,8 +1461,12 @@ EVENT(history_mem_clean)
do
{
- for (h = history_hash_table[hashnum++]; h; h = h->next)
+ for (h = history_hash_table[hashnum]; h; h = h->next)
+ {
hbm_history_cleanup(h);
+ if (cfg.persist && h->dirty)
+ hbm_write_db(h);
+ }
hashnum++;
@@ -450,3 +1474,133 @@ EVENT(history_mem_clean)
hashnum = 0;
} while(loopcnt++ < HISTORY_CLEAN_PER_LOOP);
}
+
+char *hbm_history_filename(HistoryLogObject *h)
+{
+ static char fname[512];
+ char oname[OBJECTLEN+1];
+ char hashdata[512];
+ char hash[128];
+
+ if (!hbm_prehash || !hbm_posthash)
+ abort(); /* impossible */
+
+ strtolower_safe(oname, h->name, sizeof(oname));
+ snprintf(hashdata, sizeof(hashdata), "%s %s %s", hbm_prehash, oname, hbm_posthash);
+ sha256hash(hash, hashdata, strlen(hashdata));
+
+ snprintf(fname, sizeof(fname), "%s/%s.db", cfg.directory, hash);
+ return fname;
+}
+
+#define WARN_WRITE_ERROR(fname) \
+ do { \
+ sendto_realops_and_log("[history] Error writing to temporary database file " \
+ "'%s': %s (DATABASE NOT SAVED)", \
+ fname, unrealdb_get_error_string()); \
+ } while(0)
+
+#define W_SAFE(x) \
+ do { \
+ if (!(x)) { \
+ WARN_WRITE_ERROR(tmpfname); \
+ unrealdb_close(db); \
+ return 0; \
+ } \
+ } while(0)
+
+
+// FIXME: the code below will cause massive floods on disk or I/O errors if hundreds of
+// channel logs fail to write... fun.
+static int hbm_write_db(HistoryLogObject *h)
+{
+ UnrealDB *db;
+ char *realfname;
+ char tmpfname[512];
+ HistoryLogLine *l;
+ MessageTag *m;
+ Channel *channel;
+
+ if (!cfg.db_secret)
+ abort();
+
+ channel = find_channel(h->name, NULL);
+ if (!channel || !has_channel_mode(channel, 'P'))
+ return 1; /* Don't save this channel, pretend success */
+
+ realfname = hbm_history_filename(h);
+ snprintf(tmpfname, sizeof(tmpfname), "%s.tmp", realfname);
+
+ db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+ if (!db)
+ {
+ WARN_WRITE_ERROR(tmpfname);
+ return 0;
+ }
+
+ W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_FILE_START));
+ W_SAFE(unrealdb_write_int32(db, 5000)); /* VERSION */
+ W_SAFE(unrealdb_write_str(db, hbm_prehash));
+ W_SAFE(unrealdb_write_str(db, hbm_posthash));
+ W_SAFE(unrealdb_write_str(db, h->name));
+
+ W_SAFE(unrealdb_write_int64(db, h->max_lines));
+ W_SAFE(unrealdb_write_int64(db, h->max_time));
+
+ for (l = h->head; l; l = l->next)
+ {
+ W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_ENTRY_START));
+ W_SAFE(unrealdb_write_int64(db, l->t));
+ for (m = l->mtags; m; m = m->next)
+ {
+ W_SAFE(unrealdb_write_str(db, m->name));
+ W_SAFE(unrealdb_write_str(db, m->value)); /* can be NULL */
+ }
+ W_SAFE(unrealdb_write_str(db, NULL));
+ W_SAFE(unrealdb_write_str(db, NULL));
+ W_SAFE(unrealdb_write_str(db, l->line));
+ W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_ENTRY_END));
+ }
+ W_SAFE(unrealdb_write_int32(db, HISTORYDB_MAGIC_FILE_END));
+
+ if (!unrealdb_close(db))
+ {
+ WARN_WRITE_ERROR(tmpfname);
+ return 0;
+ }
+
+#ifdef _WIN32
+ /* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+ unlink(realfname);
+#endif
+ if (rename(tmpfname, realfname) < 0)
+ {
+ sendto_realops_and_log("[history] Error renaming '%s' to '%s': %s (HISTORY NOT SAVED)",
+ tmpfname, realfname, strerror(errno));
+ return 0;
+ }
+
+ /* Now that everything was successful, clear the dirty flag */
+ h->dirty = 0;
+ return 1;
+}
+
+static void hbm_delete_db(HistoryLogObject *h)
+{
+ UnrealDB *db;
+ char *fname;
+ if (!cfg.persist || !hbm_prehash || !hbm_posthash)
+ {
+#ifdef DEBUGMODE
+ abort(); /* we should not be called, so debug this */
+#endif
+ return;
+ }
+ fname = hbm_history_filename(h);
+ unlink(fname);
+}
+
+void hbm_generic_free(ModData *m)
+{
+ safe_free(m->ptr);
+}
diff --git a/src/modules/history_backend_null.c b/src/modules/history_backend_null.c
index 3f62371..2df4513 100644
--- a/src/modules/history_backend_null.c
+++ b/src/modules/history_backend_null.c
@@ -22,7 +22,7 @@ ModuleHeader MOD_HEADER
/* Forward declarations */
int hbn_history_set_limit(char *object, int max_lines, long max_time);
int hbn_history_add(char *object, MessageTag *mtags, char *line);
-int hbn_history_request(Client *client, char *object, HistoryFilter *filter);
+HistoryResult *hbn_history_request(char *object, HistoryFilter *filter);
int hbn_history_destroy(char *object);
MOD_INIT()
@@ -30,7 +30,6 @@ MOD_INIT()
HistoryBackendInfo hbi;
MARK_AS_OFFICIAL_MODULE(modinfo);
- ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
memset(&hbi, 0, sizeof(hbi));
hbi.name = "mem";
@@ -59,9 +58,9 @@ int hbn_history_add(char *object, MessageTag *mtags, char *line)
return 1;
}
-int hbn_history_request(Client *client, char *object, HistoryFilter *filter)
+HistoryResult *hbn_history_request(char *object, HistoryFilter *filter)
{
- return 0;
+ return NULL;
}
int hbn_history_set_limit(char *object, int max_lines, long max_time)
diff --git a/src/modules/ident_lookup.c b/src/modules/ident_lookup.c
index 884af33..93ec0ed 100644
--- a/src/modules/ident_lookup.c
+++ b/src/modules/ident_lookup.c
@@ -190,16 +190,6 @@ static void ident_lookup_receive(int fd, int revents, void *userdata)
return;
}
-void skip_whitespace(char **p)
-{
- for (; **p == ' ' || **p == '\t'; *p = *p + 1);
-}
-
-void read_until(char **p, char *stopchars)
-{
- for (; **p && !strchr(stopchars, **p); *p = *p + 1);
-}
-
static char *ident_lookup_parse(Client *client, char *buf)
{
/* , : USERID : :
diff --git a/src/modules/invite.c b/src/modules/invite.c
index 93fd7e7..906f5cc 100644
--- a/src/modules/invite.c
+++ b/src/modules/invite.c
@@ -166,25 +166,16 @@ CMD_FUNC(cmd_invite)
return;
}
- if (MyConnect(client))
+ if (MyUser(client))
{
if (target_limit_exceeded(client, target, target->name))
return;
- if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL))
+ if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL) &&
+ flood_limit_exceeded(client, FLD_INVITE))
{
- if ((client->user->flood.invite_t + INVITE_PERIOD) <= timeofday)
- {
- client->user->flood.invite_c = 0;
- client->user->flood.invite_t = timeofday;
- }
- if (client->user->flood.invite_c <= INVITE_COUNT)
- client->user->flood.invite_c++;
- if (client->user->flood.invite_c > INVITE_COUNT)
- {
- sendnumeric(client, RPL_TRYAGAIN, "INVITE");
- return;
- }
+ sendnumeric(client, RPL_TRYAGAIN, "INVITE");
+ return;
}
if (!override)
diff --git a/src/modules/jointhrottle.c b/src/modules/jointhrottle.c
index eb11cf7..3247406 100644
--- a/src/modules/jointhrottle.c
+++ b/src/modules/jointhrottle.c
@@ -23,10 +23,6 @@
#include "unrealircd.h"
-/* Default settings for set::anti-flood::join-flood block: */
-#define JOINTHROTTLE_DEFAULT_COUNT 3
-#define JOINTHROTTLE_DEFAULT_TIME 90
-
ModuleHeader MOD_HEADER
= {
"jointhrottle",
@@ -40,11 +36,6 @@ ModuleInfo *ModInfo = NULL;
ModDataInfo *jointhrottle_md; /* Module Data structure which we acquire */
-struct {
- unsigned short num;
- unsigned short t;
-} cfg;
-
typedef struct JoinFlood JoinFlood;
struct JoinFlood {
@@ -55,8 +46,6 @@ struct JoinFlood {
};
/* Forward declarations */
-int jointhrottle_config_test(ConfigFile *, ConfigEntry *, int, int *);
-int jointhrottle_config_run(ConfigFile *, ConfigEntry *, int);
void jointhrottle_md_free(ModData *m);
int jointhrottle_can_join(Client *client, Channel *channel, char *key, char *parv[]);
int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
@@ -67,7 +56,6 @@ JoinFlood *jointhrottle_addentry(Client *client, Channel *channel);
MOD_TEST()
{
- HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, jointhrottle_config_test);
return MOD_SUCCESS;
}
@@ -89,12 +77,9 @@ MOD_INIT()
if (!jointhrottle_md)
abort();
- HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, jointhrottle_config_run);
HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, jointhrottle_can_join);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, jointhrottle_local_join);
- cfg.t = JOINTHROTTLE_DEFAULT_TIME;
- cfg.num = JOINTHROTTLE_DEFAULT_COUNT;
return MOD_SUCCESS;
}
@@ -109,53 +94,10 @@ MOD_UNLOAD()
return MOD_FAILED;
}
-int jointhrottle_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
-{
- int errors = 0;
- int cnt=0, period=0;
-
- if (type != CONFIG_SET_ANTI_FLOOD)
- return 0;
-
- if (strcmp(ce->ce_varname, "join-flood"))
- return 0; /* otherwise not interested */
-
- if (!ce->ce_vardata || !config_parse_flood(ce->ce_vardata, &cnt, &period) ||
- (cnt < 1) || (cnt > 255) || (period < 5))
- {
- config_error("%s:%i: set::anti-flood::join-flood. Syntax is ':' (eg 3:90), "
- "count should be 1-255, period should be greater than 4",
- ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
- errors++;
- }
-
- *errs = errors;
- return errors ? -1 : 1;
-}
-
-int jointhrottle_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
-{
- int cnt=0, period=0;
-
- if (type != CONFIG_SET_ANTI_FLOOD)
- return 0;
-
- if (strcmp(ce->ce_varname, "join-flood"))
- return 0; /* otherwise not interested */
-
- config_parse_flood(ce->ce_vardata, &cnt, &period);
-
- cfg.t = period;
- cfg.num = cnt;
-
- return 0;
-}
-
static int isjthrottled(Client *client, Channel *channel)
{
JoinFlood *e;
- int num = cfg.num;
- int t = cfg.t;
+ FloodSettings *settings = get_floodsettings_for_user(client, FLD_JOIN);
if (!MyUser(client))
return 0;
@@ -171,7 +113,8 @@ static int isjthrottled(Client *client, Channel *channel)
/* Ok... now the actual check:
* if ([timer valid] && [one more join would exceed num])
*/
- if (((TStime() - e->firstjoin) < t) && (e->numjoins == num))
+ if (((TStime() - e->firstjoin) < settings->period[FLD_JOIN]) &&
+ (e->numjoins >= settings->limit[FLD_JOIN]))
return 1; /* Throttled */
return 0;
@@ -196,7 +139,7 @@ static void jointhrottle_increase_usercounter(Client *client, Channel *channel)
e->firstjoin = TStime();
e->numjoins = 1;
} else
- if ((TStime() - e->firstjoin) < cfg.t) /* still valid? */
+ if ((TStime() - e->firstjoin) < iConf.floodsettings->period[FLD_JOIN]) /* still valid? */
{
e->numjoins++;
} else {
@@ -266,11 +209,12 @@ EVENT(jointhrottle_cleanup_structs)
{
jf_next = jf->next;
- if (jf->firstjoin + cfg.t > TStime())
+ if (jf->firstjoin + iConf.floodsettings->period[FLD_JOIN] > TStime())
continue; /* still valid entry */
#ifdef DEBUGMODE
- ircd_log(LOG_ERROR, "jointhrottle_cleanup_structs(): freeing %s/%s (%ld[%ld], %d)",
- client->name, jf->chname, jf->firstjoin, (long)(TStime() - jf->firstjoin), cfg.t);
+ ircd_log(LOG_ERROR, "jointhrottle_cleanup_structs(): freeing %s/%s (%ld[%ld], %ld)",
+ client->name, jf->chname, jf->firstjoin, (long)(TStime() - jf->firstjoin),
+ iConf.floodsettings->period[FLD_JOIN]);
#endif
if (moddata_local_client(client, jointhrottle_md).ptr == jf)
{
diff --git a/src/modules/knock.c b/src/modules/knock.c
index 3cdb485..d2197af 100644
--- a/src/modules/knock.c
+++ b/src/modules/knock.c
@@ -132,21 +132,12 @@ CMD_FUNC(cmd_knock)
if (i == HOOK_DENY)
return;
- if (MyUser(client) && !ValidatePermissionsForPath("immune:knock-flood",client,NULL,NULL,NULL))
+ if (MyUser(client) &&
+ !ValidatePermissionsForPath("immune:knock-flood",client,NULL,NULL,NULL) &&
+ flood_limit_exceeded(client, FLD_KNOCK))
{
- if ((client->user->flood.knock_t + KNOCK_PERIOD) <= timeofday)
- {
- client->user->flood.knock_c = 0;
- client->user->flood.knock_t = timeofday;
- }
- if (client->user->flood.knock_c <= KNOCK_COUNT)
- client->user->flood.knock_c++;
- if (client->user->flood.knock_c > KNOCK_COUNT)
- {
- sendnumeric(client, ERR_CANNOTKNOCK, parv[1],
- "You are KNOCK flooding");
- return;
- }
+ sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "You are KNOCK flooding");
+ return;
}
new_message(&me, NULL, &mtags);
diff --git a/src/modules/message-ids.c b/src/modules/message-ids.c
index 4b29f1f..61959f3 100644
--- a/src/modules/message-ids.c
+++ b/src/modules/message-ids.c
@@ -31,14 +31,6 @@ ModuleHeader MOD_HEADER
"unrealircd-5",
};
-/** The length of a standard 'msgid' tag (note that special
- * msgid tags will be longer).
- * The 22 alphanumeric characters provide slightly more
- * than 128 bits of randomness (62^22 > 2^128).
- * See mtag_add_or_inherit_msgid() for more information.
- */
-#define MSGIDLEN 22
-
/* Variables */
long CAP_ACCOUNT_TAG = 0L;
diff --git a/src/modules/message.c b/src/modules/message.c
index 78e0c30..db8684b 100644
--- a/src/modules/message.c
+++ b/src/modules/message.c
@@ -117,8 +117,14 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
}
// Possible FIXME: make match_spamfilter also use errmsg, or via a wrapper? or use same numeric?
- if (MyUser(client) && match_spamfilter(client, *msgtext, (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG), target->name, 0, NULL))
- return 0;
+ if (MyUser(client))
+ {
+ int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG);
+ char *cmd = sendtype_to_cmd(sendtype);
+
+ if (match_spamfilter(client, *msgtext, spamtype, cmd, target->name, 0, NULL))
+ return 0;
+ }
n = HOOK_CONTINUE;
for (h = Hooks[HOOKTYPE_CAN_SEND_TO_USER]; h; h = h->next)
@@ -379,8 +385,13 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
if ((*parv[2] == '\001') && strncmp(&parv[2][1], "ACTION ", 7))
sendflags |= SKIP_CTCP;
- if (MyUser(client) && match_spamfilter(client, text, (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG), channel->chname, 0, NULL))
- return;
+ if (MyUser(client))
+ {
+ int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG);
+
+ if (match_spamfilter(client, text, spamtype, cmd, channel->chname, 0, NULL))
+ return;
+ }
new_message(client, recv_mtags, &mtags);
diff --git a/src/modules/mode.c b/src/modules/mode.c
index 3cb9c84..f269ce0 100644
--- a/src/modules/mode.c
+++ b/src/modules/mode.c
@@ -1293,7 +1293,7 @@ int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
if (handler->is_ok(client, channel, mode, param, EXCHK_PARAM, what) == FALSE)
return paracnt; /* rejected by is_ok */
- morphed = handler->conv_param(param, client);
+ morphed = handler->conv_param(param, client, channel);
if (!morphed)
return paracnt; /* rejected by conv_param */
@@ -1303,12 +1303,12 @@ int do_extmode_char(Channel *channel, Cmode *handler, char *param, u_int what,
char *now, *requested;
char flag = handler->flag;
now = cm_getparameter(channel, flag);
- requested = handler->conv_param(param, client);
+ requested = handler->conv_param(param, client, channel);
if (now && requested && !strcmp(now, requested))
return paracnt; /* ignore... */
}
ircsnprintf(pvar[*pcount], MODEBUFLEN + 3, "+%c%s",
- handler->flag, handler->conv_param(param, client));
+ handler->flag, handler->conv_param(param, client, channel));
(*pcount)++;
param = morphed; /* set param to converted parameter. */
}
diff --git a/src/modules/nick.c b/src/modules/nick.c
index d310545..22f4340 100644
--- a/src/modules/nick.c
+++ b/src/modules/nick.c
@@ -214,11 +214,10 @@ CMD_FUNC(cmd_nick_remote)
sendto_snomask(SNO_FNICKCHANGE, "*** %s (%s@%s) has changed their nickname to %s",
client->name, client->user->username, client->user->realhost, nick);
- RunHook2(HOOKTYPE_REMOTE_NICKCHANGE, client, nick);
-
+ new_message(client, recv_mtags, &mtags);
+ RunHook3(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
client->lastnick = lastnick ? lastnick : TStime();
add_history(client, 1);
- new_message(client, recv_mtags, &mtags);
sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
client->id, nick, (long long)client->lastnick);
sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
@@ -275,19 +274,6 @@ CMD_FUNC(cmd_nick_local)
return;
}
- /* set::anti-flood::nick-flood */
- if (client->user && !ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
- {
- if ((client->user->flood.nick_c >= NICK_COUNT) &&
- (TStime() - client->user->flood.nick_t < NICK_PERIOD))
- {
- /* Throttle... */
- sendnumeric(client, ERR_NCHANGETOOFAST, nick,
- (int)(NICK_PERIOD - (TStime() - client->user->flood.nick_t)));
- return;
- }
- }
-
/* Check for collisions / in use */
if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
{
@@ -299,7 +285,7 @@ CMD_FUNC(cmd_nick_local)
{
/* Local client changing nick: check spamfilter */
spamfilter_build_user_string(spamfilter_user, nick, client);
- if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, 0, NULL))
+ if (match_spamfilter(client, spamfilter_user, SPAMF_USER, "NICK", NULL, 0, NULL))
return;
}
@@ -322,6 +308,16 @@ CMD_FUNC(cmd_nick_local)
/* fallthrough for ircops that have sufficient privileges */
}
+ /* set::anti-flood::nick-flood */
+ if (client->user &&
+ !ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL) &&
+ flood_limit_exceeded(client, FLD_NICK))
+ {
+ /* Throttle... */
+ sendnumeric(client, ERR_NCHANGETOOFAST, nick);
+ return;
+ }
+
if (!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
cptr->local->since += 3; /* Nick-flood prot. -Donwulff */
@@ -440,20 +436,13 @@ CMD_FUNC(cmd_nick_local)
}
}
- if (TStime() - client->user->flood.nick_t >= NICK_PERIOD)
- {
- client->user->flood.nick_t = TStime();
- client->user->flood.nick_c = 1;
- } else
- client->user->flood.nick_c++;
-
sendto_snomask(SNO_NICKCHANGE, "*** %s (%s@%s) has changed their nickname to %s",
client->name, client->user->username, client->user->realhost, nick);
- RunHook2(HOOKTYPE_LOCAL_NICKCHANGE, client, nick);
+ new_message(client, recv_mtags, &mtags);
+ RunHook3(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
client->lastnick = TStime();
add_history(client, 1);
- new_message(client, recv_mtags, &mtags);
sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
client->id, nick, (long long)client->lastnick);
sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
@@ -681,7 +670,12 @@ nickkill2done:
return;
if (client->user->svid[0] != '0')
+ {
user_account_login(recv_mtags, client);
+ /* no need to check for kill upon user_account_login() here
+ * since that can only happen for local users.
+ */
+ }
RunHook(HOOKTYPE_REMOTE_CONNECT, client);
@@ -721,7 +715,7 @@ int _register_user(Client *client, char *nick, char *username, char *umode, char
userbad[USERLEN * 2 + 1], *ubad = userbad, noident = 0;
int i, xx;
Hook *h;
- ClientUser *user = client->user;
+ User *user = client->user;
char *tkllayer[9] = {
me.name, /*0 server.name */
"+", /*1 +|- */
@@ -879,7 +873,7 @@ int _register_user(Client *client, char *nick, char *username, char *umode, char
find_shun(client);
spamfilter_build_user_string(spamfilter_user, client->name, client);
- if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, 0, &savetkl))
+ if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, 0, &savetkl))
{
if (savetkl && ((savetkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) ||
(savetkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN)))
diff --git a/src/modules/part.c b/src/modules/part.c
index efc8659..937087a 100644
--- a/src/modules/part.c
+++ b/src/modules/part.c
@@ -89,7 +89,7 @@ CMD_FUNC(cmd_part)
}
if (commentx)
{
- if (match_spamfilter(client, commentx, SPAMF_PART, parv[1], 0, NULL))
+ if (match_spamfilter(client, commentx, SPAMF_PART, "PART", parv[1], 0, NULL))
commentx = NULL;
if (IsDead(client))
return;
diff --git a/src/modules/quit.c b/src/modules/quit.c
index 7af7a52..8c59821 100644
--- a/src/modules/quit.c
+++ b/src/modules/quit.c
@@ -82,7 +82,7 @@ CMD_FUNC(cmd_quit)
return;
}
- if (match_spamfilter(client, comment, SPAMF_QUIT, NULL, 0, NULL))
+ if (match_spamfilter(client, comment, SPAMF_QUIT, "QUIT", NULL, 0, NULL))
{
comment = client->name;
if (IsDead(client))
diff --git a/src/modules/reply-tag.c b/src/modules/reply-tag.c
new file mode 100644
index 0000000..0364c78
--- /dev/null
+++ b/src/modules/reply-tag.c
@@ -0,0 +1,116 @@
+/*
+ * IRC - Internet Relay Chat, src/modules/reply-tag.c
+ * (C) 2021 Syzop & The UnrealIRCd Team
+ *
+ * See file AUTHORS in IRC package for additional names of
+ * the programmers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* This implements https://ircv3.net/specs/client-tags/reply */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+ = {
+ "reply-tag",
+ "5.0",
+ "+reply client tag",
+ "UnrealIRCd Team",
+ "unrealircd-5",
+ };
+
+int replytag_mtag_is_ok(Client *client, char *name, char *value);
+void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+
+MOD_INIT()
+{
+ MessageTagHandlerInfo mtag;
+
+ MARK_AS_OFFICIAL_MODULE(modinfo);
+
+#if 0
+ memset(&mtag, 0, sizeof(mtag));
+ mtag.name = "+reply";
+ mtag.is_ok = replytag_mtag_is_ok;
+ mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+ MessageTagHandlerAdd(modinfo->handle, &mtag);
+#endif
+
+ memset(&mtag, 0, sizeof(mtag));
+ mtag.name = "+draft/reply";
+ mtag.is_ok = replytag_mtag_is_ok;
+ mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+ MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+ HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_replytag);
+
+ return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+ return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+ return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending the mtag is permitted to do so.
+ */
+int replytag_mtag_is_ok(Client *client, char *name, char *value)
+{
+ char *p;
+
+ /* Require a non-empty parameter */
+ if (BadPtr(value))
+ return 0;
+
+ /* All our PRIVMSG/NOTICE msgid's are of this size: */
+ if (strlen(value) != MSGIDLEN)
+ return 0;
+
+ for (p = value; *p; p++)
+ if (!isalnum(*p))
+ return 0; /* non-alphanumeric */
+
+ return 1; /* OK */
+}
+
+void mtag_add_replytag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+{
+ MessageTag *m;
+
+ if (IsUser(client))
+ {
+#if 0
+ m = find_mtag(recv_mtags, "+reply");
+ if (m)
+ {
+ m = duplicate_mtag(m);
+ AddListItem(m, *mtag_list);
+ }
+#endif
+ m = find_mtag(recv_mtags, "+draft/reply");
+ if (m)
+ {
+ m = duplicate_mtag(m);
+ AddListItem(m, *mtag_list);
+ }
+ }
+}
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
index 50c238f..6a76881 100644
--- a/src/modules/reputation.c
+++ b/src/modules/reputation.c
@@ -19,9 +19,12 @@
#define REPUTATION_VERSION "1.2"
+/* Change to #define to benchmark. Note that this will add random
+ * reputation entries so should never be used on production servers!!!
+ */
+#undef BENCHMARK
#undef TEST
-#undef BENCHMARK
/* Benchmark results (2GHz Xeon Skylake, compiled with -O2, Linux):
* 10k random IP's with various expire times:
* - load db: 23 ms
@@ -76,12 +79,30 @@ ModuleHeader MOD_HEADER
#define Reputation(client) moddata_client(client, reputation_md).l
+#define WARN_WRITE_ERROR(fname) \
+ do { \
+ sendto_realops_and_log("[reputation] Error writing to temporary database file " \
+ "'%s': %s (DATABASE NOT SAVED)", \
+ fname, unrealdb_get_error_string()); \
+ } while(0)
+
+#define W_SAFE(x) \
+ do { \
+ if (!(x)) { \
+ WARN_WRITE_ERROR(tmpfname); \
+ unrealdb_close(db); \
+ return 0; \
+ } \
+ } while(0)
+
+
/* Definitions (structs, etc.) */
struct cfgstruct {
int expire_score[MAXEXPIRES];
long expire_time[MAXEXPIRES];
char *database;
+ char *db_secret;
};
typedef struct ReputationEntry ReputationEntry;
@@ -97,6 +118,7 @@ struct ReputationEntry {
/* Global variables */
static struct cfgstruct cfg; /**< Current configuration */
+static struct cfgstruct test; /**< Testing configuration (not active yet) */
long reputation_starttime = 0;
long reputation_writtentime = 0;
@@ -111,7 +133,8 @@ ModDataInfo *reputation_md; /* Module Data structure which we acquire */
void reputation_md_free(ModData *m);
char *reputation_md_serialize(ModData *m);
void reputation_md_unserialize(char *str, ModData *m);
-void config_setdefaults(void);
+void reputation_config_setdefaults(struct cfgstruct *cfg);
+void reputation_free_config(struct cfgstruct *cfg);
CMD_FUNC(reputation_cmd);
CMD_FUNC(reputationunperm);
int reputation_whois(Client *client, Client *target);
@@ -122,18 +145,21 @@ int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
int reputation_config_posttest(int *errs);
static uint64_t hash_reputation_entry(char *ip);
+ReputationEntry *find_reputation_entry(char *ip);
void add_reputation_entry(ReputationEntry *e);
EVENT(delete_old_records);
EVENT(add_scores);
-EVENT(save_db_evt);
-void load_db(void);
-void save_db(void);
+EVENT(reputation_save_db_evt);
+int reputation_load_db(void);
+int reputation_save_db(void);
int reputation_starttime_callback(void);
MOD_TEST()
{
memcpy(&ModInf, modinfo, modinfo->size);
memset(&cfg, 0, sizeof(cfg));
+ memset(&test, 0, sizeof(cfg));
+ reputation_config_setdefaults(&test);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, reputation_config_test);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, reputation_config_posttest);
CallbackAddEx(modinfo->handle, CALLBACKTYPE_REPUTATION_STARTTIME, reputation_starttime_callback);
@@ -146,6 +172,7 @@ MOD_INIT()
MARK_AS_OFFICIAL_MODULE(modinfo);
ModuleSetOptions(modinfo->handle, MOD_OPT_PERM, 1);
+
memset(&ReputationHashTable, 0, sizeof(ReputationHashTable));
siphash_generate_key(siphashkey_reputation);
@@ -160,7 +187,7 @@ MOD_INIT()
if (!reputation_md)
abort();
- config_setdefaults();
+ reputation_config_setdefaults(&cfg);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reputation_config_run);
HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, reputation_whois);
HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, reputation_set_on_connect);
@@ -172,43 +199,79 @@ MOD_INIT()
return MOD_SUCCESS;
}
+#ifdef BENCHMARK
+void reputation_benchmark(int entries)
+{
+ char ip[64];
+ int i;
+ ReputationEntry *e;
+
+ srand(1234); // fixed seed
+
+ for (i = 0; i < entries; i++)
+ {
+ ReputationEntry *e = safe_alloc(sizeof(ReputationEntry) + 64);
+ snprintf(e->ip, 63, "%d.%d.%d.%d", rand()%255, rand()%255, rand()%255, rand()%255);
+ e->score = rand()%255 + 1;
+ e->last_seen = TStime();
+ if (find_reputation_entry(e->ip))
+ {
+ safe_free(e);
+ continue;
+ }
+ add_reputation_entry(e);
+ }
+}
+#endif
MOD_LOAD()
{
- load_db();
+ reputation_load_db();
if (reputation_starttime == 0)
reputation_starttime = TStime();
EventAdd(ModInf.handle, "delete_old_records", delete_old_records, NULL, DELETE_OLD_EVERY*1000, 0);
EventAdd(ModInf.handle, "add_scores", add_scores, NULL, BUMP_SCORE_EVERY*1000, 0);
- EventAdd(ModInf.handle, "save_db", save_db_evt, NULL, SAVE_DB_EVERY*1000, 0);
+ EventAdd(ModInf.handle, "reputation_save_db", reputation_save_db_evt, NULL, SAVE_DB_EVERY*1000, 0);
+#ifdef BENCHMARK
+ reputation_benchmark(10000);
+#endif
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
- save_db();
+ if (loop.ircd_terminating)
+ reputation_save_db();
+ reputation_free_config(&test);
+ reputation_free_config(&cfg);
return MOD_SUCCESS;
}
-void config_setdefaults(void)
+void reputation_config_setdefaults(struct cfgstruct *cfg)
{
/* data/reputation.db */
- safe_strdup(cfg.database, "reputation.db");
- convert_to_absolute_path(&cfg.database, PERMDATADIR);
+ safe_strdup(cfg->database, "reputation.db");
+ convert_to_absolute_path(&cfg->database, PERMDATADIR);
/* EXPIRES the following entries if the IP does appear for some time: */
/* <=2 points after 1 hour */
- cfg.expire_score[0] = 2;
+ cfg->expire_score[0] = 2;
#ifndef TEST
- cfg.expire_time[0] = 3600;
+ cfg->expire_time[0] = 3600;
#else
- cfg.expire_time[0] = 36;
+ cfg->expire_time[0] = 36;
#endif
/* <=6 points after 7 days */
- cfg.expire_score[1] = 6;
- cfg.expire_time[1] = 86400*7;
+ cfg->expire_score[1] = 6;
+ cfg->expire_time[1] = 86400*7;
/* ANY result that has not been seen for 30 days */
- cfg.expire_score[2] = -1;
- cfg.expire_time[2] = 86400*30;
+ cfg->expire_score[2] = -1;
+ cfg->expire_time[2] = 86400*30;
+}
+
+void reputation_free_config(struct cfgstruct *cfg)
+{
+ safe_free(cfg->database);
+ safe_free(cfg->db_secret);
}
int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
@@ -218,11 +281,11 @@ int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
if (type != CONFIG_SET)
return 0;
-
+
/* We are only interrested in set::reputation.. */
if (!ce || strcmp(ce->ce_varname, "reputation"))
return 0;
-
+
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!cep->ce_vardata)
@@ -235,6 +298,18 @@ int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
if (!strcmp(cep->ce_varname, "database"))
{
convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
+ safe_strdup(test.database, cep->ce_vardata);
+ } else
+ if (!strcmp(cep->ce_varname, "db-secret"))
+ {
+ char *err;
+ if ((err = unrealdb_test_secret(cep->ce_vardata)))
+ {
+ config_error("%s:%i: set::channeldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+ errors++;
+ continue;
+ }
+ safe_strdup(test.db_secret, cep->ce_vardata);
} else
{
config_error("%s:%i: unknown directive set::reputation::%s",
@@ -243,7 +318,7 @@ int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
continue;
}
}
-
+
*errs = errors;
return errors ? -1 : 1;
}
@@ -254,16 +329,20 @@ int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
if (type != CONFIG_SET)
return 0;
-
+
/* We are only interrested in set::reputation.. */
if (!ce || strcmp(ce->ce_varname, "reputation"))
return 0;
-
+
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "database"))
{
safe_strdup(cfg.database, cep->ce_vardata);
+ } else
+ if (!strcmp(cep->ce_varname, "db-secret"))
+ {
+ safe_strdup(cfg.db_secret, cep->ce_vardata);
}
}
return 1;
@@ -272,13 +351,20 @@ int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
int reputation_config_posttest(int *errs)
{
int errors = 0;
+ char *errstr;
+
+ if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+ {
+ config_error("[reputation] %s", errstr);
+ errors++;
+ }
*errs = errors;
return errors ? -1 : 1;
}
/** Parse database header and set variables appropriately */
-int parse_db_header(char *buf)
+int parse_db_header_old(char *buf)
{
char *header=NULL, *version=NULL, *starttime=NULL, *writtentime=NULL;
char *p=NULL;
@@ -308,7 +394,7 @@ int parse_db_header(char *buf)
return 1;
}
-void load_db(void)
+void reputation_load_db_old(void)
{
FILE *fd;
char buf[512], *p;
@@ -324,7 +410,7 @@ void load_db(void)
config_warn("WARNING: Could not open/read database '%s': %s", cfg.database, strerror(ERRNO));
return;
}
-
+
memset(buf, 0, sizeof(buf));
if (fgets(buf, 512, fd) == NULL)
{
@@ -332,7 +418,7 @@ void load_db(void)
fclose(fd);
return;
}
-
+
/* Header contains: REPDB
* Where:
* REPDB: Literally the string "REPDB".
@@ -341,7 +427,7 @@ void load_db(void)
* in other words: when this module was first loaded, ever.
* Time that the database was last written.
*/
- if (!parse_db_header(buf))
+ if (!parse_db_header_old(buf))
{
config_error("WARNING: Cannot load database %s. Error reading header. "
"Database corrupt? Or are you downgrading from a newer "
@@ -355,7 +441,7 @@ void load_db(void)
{
char *ip = NULL, *score = NULL, *last_seen = NULL;
ReputationEntry *e;
-
+
stripcrlf(buf);
/* Format: */
ip = strtoken(&p, buf, " ");
@@ -367,12 +453,12 @@ void load_db(void)
last_seen = strtoken(&p, NULL, " ");
if (!last_seen)
continue;
-
+
e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
strcpy(e->ip, ip); /* safe, see alloc above */
e->score = atoi(score);
e->last_seen = atol(last_seen);
-
+
add_reputation_entry(e);
}
fclose(fd);
@@ -384,7 +470,130 @@ void load_db(void)
#endif
}
-void save_db(void)
+#define R_SAFE(x) \
+ do { \
+ if (!(x)) { \
+ config_warn("[reputation] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+ unrealdb_close(db); \
+ safe_free(ip); \
+ return 0; \
+ } \
+ } while(0)
+
+int reputation_load_db_new(UnrealDB *db)
+{
+ uint64_t l_db_version = 0;
+ uint64_t l_starttime = 0;
+ uint64_t l_writtentime = 0;
+ uint64_t count = 0;
+ uint64_t i;
+ char *ip = NULL;
+ uint16_t score;
+ uint64_t last_seen;
+ ReputationEntry *e;
+#ifdef BENCHMARK
+ struct timeval tv_alpha, tv_beta;
+
+ gettimeofday(&tv_alpha, NULL);
+#endif
+
+ R_SAFE(unrealdb_read_int64(db, &l_db_version)); /* reputation db version */
+ if (l_db_version > 2)
+ {
+ config_error("[reputation] Reputation DB is of a newer version (%ld) than supported by us (%ld). "
+ "Did you perhaps downgrade your UnrealIRCd?",
+ (long)l_db_version, (long)2);
+ unrealdb_close(db);
+ return 0;
+ }
+ R_SAFE(unrealdb_read_int64(db, &l_starttime)); /* starttime of data gathering */
+ R_SAFE(unrealdb_read_int64(db, &l_writtentime)); /* current time */
+ R_SAFE(unrealdb_read_int64(db, &count)); /* number of entries */
+
+ reputation_starttime = l_starttime;
+ reputation_writtentime = l_writtentime;
+
+ for (i=0; i < count; i++)
+ {
+ R_SAFE(unrealdb_read_str(db, &ip));
+ R_SAFE(unrealdb_read_int16(db, &score));
+ R_SAFE(unrealdb_read_int64(db, &last_seen));
+
+ e = safe_alloc(sizeof(ReputationEntry)+strlen(ip));
+ strcpy(e->ip, ip); /* safe, see alloc above */
+ e->score = score;
+ e->last_seen = last_seen;
+ add_reputation_entry(e);
+ safe_free(ip);
+ }
+ unrealdb_close(db);
+#ifdef BENCHMARK
+ gettimeofday(&tv_beta, NULL);
+ ircd_log(LOG_ERROR, "Reputation benchmark: LOAD DB: %lld microseconds",
+ (long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+#endif
+ return 1;
+}
+
+/** Load the reputation DB.
+ * Strategy is:
+ * 1) Check for the old header "REPDB 1", if so then call reputation_load_db_old().
+ * 2) Otherwise, open with unrealdb routine
+ * 3) If that fails due to a password provided but the file is unrealdb without password
+ * then fallback to open without a password (so users can easily upgrade to encrypted)
+ */
+int reputation_load_db(void)
+{
+ FILE *fd;
+ UnrealDB *db;
+ char buf[512];
+
+ fd = fopen(cfg.database, "r");
+ if (!fd)
+ {
+ /* Database does not exist. Could be first boot */
+ config_warn("[reputation] No database present at '%s', will start a new one", cfg.database);
+ return 1;
+ }
+
+ *buf = '\0';
+ if (fgets(buf, sizeof(buf), fd) == NULL)
+ {
+ fclose(fd);
+ config_warn("[reputation] Database at '%s' is 0 bytes", cfg.database);
+ return 1;
+ }
+ fclose(fd);
+ if (!strncmp(buf, "REPDB 1 ", 8))
+ {
+ reputation_load_db_old();
+ return 1; /* not so good to always pretend succes */
+ }
+
+ /* Otherwise, it is an unrealdb, crypted or not */
+ db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+ if (!db)
+ {
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+ {
+ /* Database does not exist. Could be first boot */
+ config_warn("[reputation] No database present at '%s', will start a new one", cfg.database);
+ return 1;
+ } else
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+ {
+ db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+ }
+ if (!db)
+ {
+ config_error("[reputation] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
+ return 0;
+ }
+ }
+ return reputation_load_db_new(db);
+}
+
+int reputation_save_db_old(void)
{
FILE *fd;
char tmpfname[512];
@@ -395,19 +604,15 @@ void save_db(void)
gettimeofday(&tv_alpha, NULL);
#endif
-
-#ifdef TEST
- sendto_realops("REPUTATION IS RUNNING IN TEST MODE. SAVING DB'S...");
-#endif
/* We write to a temporary file. Only to rename it later if everything was ok */
snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
-
+
fd = fopen(tmpfname, "w");
if (!fd)
{
config_error("ERROR: Could not open/write database '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
- return;
+ return 0;
}
if (fprintf(fd, "REPDB 1 %lld %lld\n", (long long)reputation_starttime, (long long)TStime()) < 0)
@@ -422,7 +627,7 @@ void save_db(void)
write_fail:
config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
fclose(fd);
- return;
+ return 0;
}
}
}
@@ -430,9 +635,9 @@ write_fail:
if (fclose(fd) < 0)
{
config_error("ERROR writing to '%s': %s -- DATABASE *NOT* SAVED!!!", tmpfname, strerror(ERRNO));
- return;
+ return 0;
}
-
+
/* Everything went fine. We rename our temporary file to the existing
* DB file (will overwrite), which is more or less an atomic operation.
*/
@@ -444,7 +649,7 @@ write_fail:
{
config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!",
tmpfname, cfg.database, strerror(ERRNO));
- return;
+ return 0;
}
reputation_writtentime = TStime();
@@ -455,7 +660,91 @@ write_fail:
(long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
#endif
- return;
+ return 1;
+}
+
+int reputation_save_db(void)
+{
+ UnrealDB *db;
+ char tmpfname[512];
+ int i;
+ uint64_t count;
+ ReputationEntry *e;
+#ifdef BENCHMARK
+ struct timeval tv_alpha, tv_beta;
+
+ gettimeofday(&tv_alpha, NULL);
+#endif
+
+#ifdef TEST
+ sendto_realops("REPUTATION IS RUNNING IN TEST MODE. SAVING DB'S...");
+#endif
+
+ /* Comment this out after one or more releases (means you cannot downgrade to <=5.0.9.1 anymore) */
+ if (cfg.db_secret == NULL)
+ return reputation_save_db_old();
+
+ /* We write to a temporary file. Only to rename it later if everything was ok */
+ snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+
+ db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+ if (!db)
+ {
+ WARN_WRITE_ERROR(tmpfname);
+ return 0;
+ }
+
+ /* Write header */
+ W_SAFE(unrealdb_write_int64(db, 2)); /* reputation db version */
+ W_SAFE(unrealdb_write_int64(db, reputation_starttime)); /* starttime of data gathering */
+ W_SAFE(unrealdb_write_int64(db, TStime())); /* current time */
+
+ /* Count entries */
+ count = 0;
+ for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+ for (e = ReputationHashTable[i]; e; e = e->next)
+ count++;
+ W_SAFE(unrealdb_write_int64(db, count)); /* Number of DB entries */
+
+ /* Now write the actual individual entries: */
+ for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
+ {
+ for (e = ReputationHashTable[i]; e; e = e->next)
+ {
+ W_SAFE(unrealdb_write_str(db, e->ip));
+ W_SAFE(unrealdb_write_int16(db, e->score));
+ W_SAFE(unrealdb_write_int64(db, e->last_seen));
+ }
+ }
+
+ if (!unrealdb_close(db))
+ {
+ WARN_WRITE_ERROR(tmpfname);
+ return 0;
+ }
+
+ /* Everything went fine. We rename our temporary file to the existing
+ * DB file (will overwrite), which is more or less an atomic operation.
+ */
+#ifdef _WIN32
+ /* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+ unlink(cfg.database);
+#endif
+ if (rename(tmpfname, cfg.database) < 0)
+ {
+ config_error("ERROR renaming '%s' to '%s': %s -- DATABASE *NOT* SAVED!!!",
+ tmpfname, cfg.database, strerror(ERRNO));
+ return 0;
+ }
+
+ reputation_writtentime = TStime();
+
+#ifdef BENCHMARK
+ gettimeofday(&tv_beta, NULL);
+ ircd_log(LOG_ERROR, "Reputation benchmark: SAVE DB: %lld microseconds",
+ (long long)(((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+#endif
+ return 1;
}
static uint64_t hash_reputation_entry(char *ip)
@@ -548,7 +837,7 @@ EVENT(add_scores)
*/
#define MARKER_UNREGISTERED_USER (marker)
#define MARKER_REGISTERED_USER (marker+1)
-
+
list_for_each_entry(client, &client_list, client_node)
{
if (!IsUser(client))
@@ -625,13 +914,13 @@ EVENT(delete_old_records)
gettimeofday(&tv_alpha, NULL);
#endif
-
+
for (i = 0; i < REPUTATION_HASH_TABLE_SIZE; i++)
{
for (e = ReputationHashTable[i]; e; e = e_next)
{
e_next = e->next;
-
+
if (is_reputation_expired(e))
{
#ifdef DEBUGMODE
@@ -651,9 +940,9 @@ EVENT(delete_old_records)
#endif
}
-EVENT(save_db_evt)
+EVENT(reputation_save_db_evt)
{
- save_db();
+ reputation_save_db();
}
CMD_FUNC(reputationunperm)
@@ -801,7 +1090,7 @@ CMD_FUNC(reputation_user_cmd)
sendnumeric(client, ERR_NOPRIVILEGES);
return;
}
-
+
if ((parc < 2) || BadPtr(parv[1]))
{
sendnotice(client, "Reputation module statistics:");
@@ -825,7 +1114,7 @@ CMD_FUNC(reputation_user_cmd)
sendnotice(client, "/REPUTATION 0)
{
sendto_one(client, NULL, ":%s %d %s %s :is using an IP with a reputation score of %d",
diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c
index ffa1a04..f104cb6 100644
--- a/src/modules/restrict-commands.c
+++ b/src/modules/restrict-commands.c
@@ -36,6 +36,7 @@ struct RestrictedCommand {
int exempt_identified;
int exempt_reputation_score;
int exempt_webirc;
+ int exempt_tls;
};
typedef struct {
@@ -179,9 +180,9 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
if (!strcmp(cep2->ce_varname, "connect-delay"))
{
long v = config_checkval(cep2->ce_vardata, CFG_TIME);
- if ((v < 10) || (v > 3600))
+ if ((v < 1) || (v > 3600))
{
- config_error("%s:%i: set::restrict-commands::%s::connect-delay should be in range 10-3600", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
+ config_error("%s:%i: set::restrict-commands::%s::connect-delay should be in range 1-3600", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
errors++;
}
continue;
@@ -189,10 +190,13 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
if (!strcmp(cep2->ce_varname, "exempt-identified"))
continue;
-
+
if (!strcmp(cep2->ce_varname, "exempt-webirc"))
continue;
-
+
+ if (!strcmp(cep2->ce_varname, "exempt-tls"))
+ continue;
+
if (!strcmp(cep2->ce_varname, "exempt-reputation-score"))
{
int v = atoi(cep2->ce_vardata);
@@ -279,6 +283,12 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
continue;
}
+ if (!strcmp(cep2->ce_varname, "exempt-tls"))
+ {
+ rcmd->exempt_tls = config_checkval(cep2->ce_vardata, CFG_YESNO);
+ continue;
+ }
+
if (!strcmp(cep2->ce_varname, "exempt-reputation-score"))
{
rcmd->exempt_reputation_score = atoi(cep2->ce_vardata);
@@ -299,6 +309,8 @@ int rcmd_canbypass(Client *client, RestrictedCommand *rcmd)
return 1;
if (rcmd->exempt_webirc && moddata_client_get(client, "webirc"))
return 1;
+ if (rcmd->exempt_tls && IsSecureConnect(client))
+ return 1;
if (rcmd->exempt_reputation_score > 0 && (GetReputation(client) >= rcmd->exempt_reputation_score))
return 1;
if (rcmd->connect_delay && client->local && (TStime() - client->local->firsttime >= rcmd->connect_delay))
diff --git a/src/modules/sajoin.c b/src/modules/sajoin.c
index 5de6ff4..57bd4e1 100644
--- a/src/modules/sajoin.c
+++ b/src/modules/sajoin.c
@@ -275,24 +275,5 @@ 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);
- /* Logging function added by XeRXeS */
- 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
index b981e30..d783fdd 100644
--- a/src/modules/sapart.c
+++ b/src/modules/sapart.c
@@ -159,22 +159,6 @@ 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/sasl.c b/src/modules/sasl.c
index f8e0524..cceb9fe 100644
--- a/src/modules/sasl.c
+++ b/src/modules/sasl.c
@@ -118,6 +118,8 @@ CMD_FUNC(cmd_svslogin)
strlcpy(target->user->svid, parv[3], sizeof(target->user->svid));
user_account_login(recv_mtags, target);
+ if (MyConnect(target) && IsDead(target))
+ return; /* was killed due to *LINE on ~a probably */
} else {
/* It is perfectly normal for target to be NULL as this
* happens during registration phase (pre-connect).
diff --git a/src/modules/server.c b/src/modules/server.c
index 4e7d0d4..df660f4 100644
--- a/src/modules/server.c
+++ b/src/modules/server.c
@@ -179,6 +179,7 @@ void _send_protoctl_servers(Client *client, int response)
{
char buf[512];
Client *acptr;
+ int sendit = 1;
sendto_one(client, NULL, "PROTOCTL EAUTH=%s,%d,%s%s,%s",
me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", version);
@@ -188,15 +189,24 @@ void _send_protoctl_servers(Client *client, int response)
list_for_each_entry(acptr, &global_server_list, client_node)
{
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s,", acptr->id);
+ sendit = 1;
if (strlen(buf) > sizeof(buf)-12)
- break; /* prevent overflow/cutoff if you have a network with more than 90 servers or something. */
+ {
+ if (buf[strlen(buf)-1] == ',')
+ buf[strlen(buf)-1] = '\0';
+ sendto_one(client, NULL, "%s", buf);
+ /* We use the asterisk here too for continuation lines */
+ ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=*");
+ sendit = 0;
+ }
}
/* Remove final comma (if any) */
if (buf[strlen(buf)-1] == ',')
buf[strlen(buf)-1] = '\0';
- sendto_one(client, NULL, "%s", buf);
+ if (sendit)
+ sendto_one(client, NULL, "%s", buf);
}
void _send_server_message(Client *client)
diff --git a/src/modules/setname.c b/src/modules/setname.c
index eb75a6b..85ab9c3 100644
--- a/src/modules/setname.c
+++ b/src/modules/setname.c
@@ -89,7 +89,7 @@ CMD_FUNC(cmd_setname)
/* set the new name before we check, but don't send to servers unless it is ok */
strcpy(client->info, parv[1]);
spamfilter_build_user_string(spamfilter_user, client->name, client);
- if (match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, 0, NULL))
+ if (match_spamfilter(client, spamfilter_user, SPAMF_USER, "SETNAME", NULL, 0, NULL))
{
/* Was rejected by spamfilter, restore the realname */
strcpy(client->info, tmpinfo);
diff --git a/src/modules/squit.c b/src/modules/squit.c
index 7ea2782..28325d3 100644
--- a/src/modules/squit.c
+++ b/src/modules/squit.c
@@ -144,15 +144,6 @@ CMD_FUNC(cmd_squit)
sendto_umode_global(UMODE_OPER, "Received SQUIT %s from %s (%s)",
target->name, get_client_name(client, FALSE), comment);
}
- if (IsOper(client))
- {
- /*
- * It was manually /squit'ed by a human being(we hope),
- * there is a very good chance they don't want us to
- * reconnect right away. -Cabal95
- */
- SetSQuit(target);
- }
- exit_client(target, recv_mtags, comment);
+ exit_client_ex(target, client->direction, recv_mtags, comment);
}
diff --git a/src/modules/stats.c b/src/modules/stats.c
index b01531b..fcb84e2 100644
--- a/src/modules/stats.c
+++ b/src/modules/stats.c
@@ -376,8 +376,14 @@ CMD_FUNC(cmd_stats)
else
stat->func(client, NULL);
- /* Modules can append data: */
- RunHook2(HOOKTYPE_STATS, client, flags);
+ /* Modules can append data:
+ * ('STATS S' already has special code for this that
+ * maintains certain ordering, so not included here)
+ */
+ if (stat->flag != 'S')
+ {
+ RunHook2(HOOKTYPE_STATS, client, flags);
+ }
sendnumeric(client, RPL_ENDOFSTATS, stat->flag);
@@ -772,9 +778,33 @@ int stats_officialchannels(Client *client, char *para)
#define SafePrint(x) ((x) ? (x) : "")
+/** Helper for stats_set() */
+static void stats_set_anti_flood(Client *client, FloodSettings *f)
+{
+ int i;
+
+ for (i=0; floodoption_names[i]; i++)
+ {
+ if (i == FLD_CONVERSATIONS)
+ {
+ sendtxtnumeric(client, "anti-flood::%s::%s: %d users, new user every %s",
+ f->name, floodoption_names[i],
+ (int)f->limit[i], pretty_time_val(f->period[i]));
+ }
+ else
+ {
+ sendtxtnumeric(client, "anti-flood::%s::%s: %d per %s",
+ f->name, floodoption_names[i],
+ (int)f->limit[i], pretty_time_val(f->period[i]));
+ }
+ }
+}
+
int stats_set(Client *client, char *para)
{
char *uhallow;
+ SecurityGroup *s;
+ FloodSettings *f;
if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
{
@@ -875,9 +905,17 @@ int stats_set(Client *client, char *para)
sendtxtnumeric(client, "anti-flood::handshake-data-flood::amount: %ld bytes", iConf.handshake_data_flood_amount);
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-action: %s", banact_valtostring(iConf.handshake_data_flood_ban_action));
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-time: %s", pretty_time_val(iConf.handshake_data_flood_ban_time));
- if (AWAY_PERIOD)
- sendtxtnumeric(client, "anti-flood::away-flood: %d per %s", AWAY_COUNT, pretty_time_val(AWAY_PERIOD));
- sendtxtnumeric(client, "anti-flood::nick-flood: %d per %s", NICK_COUNT, pretty_time_val(NICK_PERIOD));
+
+ /* set::anti-flood */
+ for (s = securitygroups; s; s = s->next)
+ if ((f = find_floodsettings_block(s->name)))
+ stats_set_anti_flood(client, f);
+ f = find_floodsettings_block("unknown-users");
+ stats_set_anti_flood(client, f);
+
+ //if (AWAY_PERIOD)
+ // sendtxtnumeric(client, "anti-flood::away-flood: %d per %s", AWAY_COUNT, pretty_time_val(AWAY_PERIOD));
+ //sendtxtnumeric(client, "anti-flood::nick-flood: %d per %s", NICK_COUNT, pretty_time_val(NICK_PERIOD));
sendtxtnumeric(client, "handshake-timeout: %s", pretty_time_val(iConf.handshake_timeout));
sendtxtnumeric(client, "sasl-timeout: %s", pretty_time_val(iConf.sasl_timeout));
sendtxtnumeric(client, "ident::connect-timeout: %s", pretty_time_val(IDENT_CONNECT_TIMEOUT));
@@ -895,6 +933,10 @@ int stats_set(Client *client, char *para)
sendtxtnumeric(client, "outdated-tls-policy::oper: %s", policy_valtostr(iConf.outdated_tls_policy_oper));
sendtxtnumeric(client, "outdated-tls-policy::server: %s", policy_valtostr(iConf.outdated_tls_policy_server));
RunHook2(HOOKTYPE_STATS, client, "S");
+#ifndef _WIN32
+ sendtxtnumeric(client, "This server can handle %d concurrent sockets (%d clients + %d reserve)",
+ maxclients+CLIENTS_RESERVE, maxclients, CLIENTS_RESERVE);
+#endif
return 1;
}
diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c
index 3d39974..3bbd85b 100644
--- a/src/modules/svsmode.c
+++ b/src/modules/svsmode.c
@@ -408,6 +408,8 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
{
strlcpy(target->user->svid, parv[3], sizeof(target->user->svid));
user_account_login(recv_mtags, target);
+ if (MyConnect(target) && IsDead(target))
+ return; /* was killed due to *LINE on ~a probably */
}
else
{
diff --git a/src/modules/svsnick.c b/src/modules/svsnick.c
index 63948a0..3a43e89 100644
--- a/src/modules/svsnick.c
+++ b/src/modules/svsnick.c
@@ -95,6 +95,7 @@ CMD_FUNC(cmd_svsnick)
/* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */
new_message(acptr, NULL, &mtags);
+ RunHook3(HOOKTYPE_LOCAL_NICKCHANGE, acptr, mtags, parv[2]);
sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, parv[2]);
sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, parv[2]);
sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%ld", acptr->id, parv[2], atol(parv[3]));
@@ -107,7 +108,6 @@ CMD_FUNC(cmd_svsnick)
sendto_snomask(SNO_NICKCHANGE,
"*** %s (%s@%s) has been forced to change their nickname to %s",
acptr->name, acptr->user->username, acptr->user->realhost, parv[2]);
- RunHook2(HOOKTYPE_LOCAL_NICKCHANGE, acptr, parv[2]);
strlcpy(acptr->name, parv[2], sizeof acptr->name);
add_to_client_hash_table(parv[2], acptr);
diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c
index be1e4d5..56151b1 100644
--- a/src/modules/targetfloodprot.c
+++ b/src/modules/targetfloodprot.c
@@ -234,8 +234,8 @@ int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Member
if (!MyUser(client))
return HOOK_CONTINUE;
- /* IRCOps and U-Lines override */
- if (IsULine(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
+ /* U-Lines, servers and IRCOps override */
+ if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
return HOOK_CONTINUE;
what = sendtypetowhat(sendtype);
@@ -259,6 +259,7 @@ int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Member
if (flood->cnt[what] >= channelcfg->cnt[what])
{
/* Flood detected */
+ flood_limit_exceeded_log(client, "target-flood-channel");
snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
*errmsg = errbuf;
return HOOK_DENY;
@@ -280,8 +281,8 @@ int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text
if (!MyUser(target))
return HOOK_CONTINUE;
- /* IRCOps and U-Lines override */
- if (IsULine(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
+ /* U-Lines, servers and IRCOps override */
+ if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
return HOOK_CONTINUE;
what = sendtypetowhat(sendtype);
@@ -305,6 +306,7 @@ int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text
if (flood->cnt[what] >= privatecfg->cnt[what])
{
/* Flood detected */
+ flood_limit_exceeded_log(client, "target-flood-user");
snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
*errmsg = errbuf;
return HOOK_DENY;
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
index 810954a..3bea4f1 100644
--- a/src/modules/tkl.c
+++ b/src/modules/tkl.c
@@ -82,7 +82,9 @@ void _tkl_stats(Client *client, int type, char *para, int *cnt);
void _tkl_sync(Client *client);
CMD_FUNC(_cmd_tkl);
int _place_host_ban(Client *client, BanAction action, char *reason, long duration);
-int _match_spamfilter(Client *client, char *str_in, int type, char *target, int flags, TKL **rettk);
+int _match_spamfilter(Client *client, char *str_in, int type, char *cmd, char *target, int flags, TKL **rettk);
+int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd);
+int check_mtag_spamfilters_present(void);
int _join_viruschan(Client *client, TKL *tk, int type);
void _spamfilter_build_user_string(char *buf, char *nick, Client *client);
int _match_user(char *rmask, Client *client, int options);
@@ -109,6 +111,7 @@ struct TKLTypeTable
char *log_name; /**< Used for logging and server notices */
unsigned tkltype:1; /**< Is a type available in cmd_tkl() and friends */
unsigned exceptiontype:1; /**< Is a type available for exceptions */
+ unsigned needip:1; /**< When using this exempt option, only IP addresses are permitted (processed before DNS/ident lookups etc) */
};
/** This table which defines all TKL types and TKL exception types.
@@ -117,35 +120,35 @@ struct TKLTypeTable
*
* IMPORTANT IF YOU ARE ADDING A NEW TYPE TO THIS TABLE:
* - also update eline_syntax()
- * - also check if eline_type_requires_ip() needs to be updated
* - update help.conf (HELPOP ELINE)
* - more?
*/
TKLTypeTable tkl_types[] = {
- /* */
- { "gline", 'G', TKL_KILL | TKL_GLOBAL, "G-Line", 1, 1 },
- { "kline", 'k', TKL_KILL, "K-Line", 1, 1 },
- { "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1 },
- { "zline", 'z', TKL_ZAP, "Z-Line", 1, 1 },
- { "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1 },
- { "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1 },
- { "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0 },
- { "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1 },
- { "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0 },
- { "local-spamfilter", 'e', TKL_EXCEPTION, "Local Exception", 1, 0 },
- { "local-exception", 'f', TKL_SPAMF, "Local Spamfilter", 1, 0 },
- { "blacklist", 'b', TKL_BLACKLIST, "Blacklist", 0, 1 },
- { "connect-flood", 'c', TKL_CONNECT_FLOOD, "Connect flood", 0, 1 },
- { "maxperip", 'm', TKL_MAXPERIP, "Max-per-IP", 0, 1 },
- { "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD, "Handshake data flood", 0, 1 },
- { "antirandom", 'r', TKL_ANTIRANDOM, "Antirandom", 0, 1 },
- { "antimixedutf8", '8', TKL_ANTIMIXEDUTF8, "Antimixedutf8", 0, 1 },
- { "ban-version", 'v', TKL_BAN_VERSION, "Ban Version", 0, 1 },
- { NULL, '\0', 0, NULL, 0, 0 },
+ /* */
+ { "gline", 'G', TKL_KILL | TKL_GLOBAL, "G-Line", 1, 1, 0 },
+ { "kline", 'k', TKL_KILL, "K-Line", 1, 1, 0 },
+ { "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1, 1 },
+ { "zline", 'z', TKL_ZAP, "Z-Line", 1, 1, 1 },
+ { "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1, 0 },
+ { "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1, 0 },
+ { "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0, 0 },
+ { "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1, 0 },
+ { "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0, 0 },
+ { "local-spamfilter", 'e', TKL_EXCEPTION, "Local Exception", 1, 0, 0 },
+ { "local-exception", 'f', TKL_SPAMF, "Local Spamfilter", 1, 0, 0 },
+ { "blacklist", 'b', TKL_BLACKLIST, "Blacklist", 0, 1, 1 },
+ { "connect-flood", 'c', TKL_CONNECT_FLOOD, "Connect flood", 0, 1, 1 },
+ { "maxperip", 'm', TKL_MAXPERIP, "Max-per-IP", 0, 1, 0 },
+ { "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD, "Handshake data flood", 0, 1, 1 },
+ { "antirandom", 'r', TKL_ANTIRANDOM, "Antirandom", 0, 1, 0 },
+ { "antimixedutf8", '8', TKL_ANTIMIXEDUTF8, "Antimixedutf8", 0, 1, 0 },
+ { "ban-version", 'v', TKL_BAN_VERSION, "Ban Version", 0, 1, 0 },
+ { NULL, '\0', 0, NULL, 0, 0, 0 },
};
#define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version"
int max_stats_matches = 1000;
+int mtag_spamfilters_present = 0; /**< Are any spamfilters with type SPAMF_MTAG present? */
MOD_TEST()
{
@@ -178,8 +181,9 @@ MOD_TEST()
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_SYNCH, _tkl_sync);
EfunctionAddVoid(modinfo->handle, EFUNC_CMD_TKL, _cmd_tkl);
EfunctionAdd(modinfo->handle, EFUNC_PLACE_HOST_BAN, _place_host_ban);
- EfunctionAdd(modinfo->handle, EFUNC_DOSPAMFILTER, _match_spamfilter);
- EfunctionAdd(modinfo->handle, EFUNC_DOSPAMFILTER_VIRUSCHAN, _join_viruschan);
+ EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER, _match_spamfilter);
+ EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER_MTAGS, _match_spamfilter_mtags);
+ EfunctionAdd(modinfo->handle, EFUNC_JOIN_VIRUSCHAN, _join_viruschan);
EfunctionAddVoid(modinfo->handle, EFUNC_SPAMFILTER_BUILD_USER_STRING, _spamfilter_build_user_string);
EfunctionAdd(modinfo->handle, EFUNC_MATCH_USER, _match_user);
EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH, _tkl_ip_hash);
@@ -213,6 +217,7 @@ MOD_INIT()
MOD_LOAD()
{
+ check_mtag_spamfilters_present();
EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
return MOD_SUCCESS;
}
@@ -1363,6 +1368,15 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
mask[3] = '\0';
usermask = mask; /* eg ~S: */
hostmask = mask2buf;
+
+ if (((*type == 'z') || (*type == 'Z')))
+ {
+ sendnotice(client, "ERROR: (g)zlines must be placed at *@\037IPMASK\037. "
+ "Extended server bans don't work here because (g)zlines are processed"
+ "BEFORE dns and ident lookups are done and before reading any client data. "
+ "If you want to use extended server bans then use a KLINE/GLINE instead.");
+ return;
+ }
} else {
/* Delete: allow any attempt */
strlcpy(mask2buf, mask+3, sizeof(mask2buf));
@@ -1543,14 +1557,14 @@ void eline_syntax(Client *client)
* exception to be placed on *@ip rather than
* user@host or *@host. For eg zlines.
*/
-int eline_type_requires_ip(char *bantypes)
+TKLTypeTable *eline_type_requires_ip(char *bantypes)
{
- if (strchr(bantypes, 'z') || strchr(bantypes, 'Z') ||
- strchr(bantypes, 'c') ||
- strchr(bantypes, 'b') ||
- strchr(bantypes, 'd'))
- return 1;
- return 0;
+ int i;
+
+ for (i=0; tkl_types[i].config_name; i++)
+ if (tkl_types[i].needip && strchr(bantypes, tkl_types[i].letter))
+ return &tkl_types[i];
+ return NULL;
}
/** Checks a string to see if it contains invalid ban exception types */
@@ -1590,6 +1604,7 @@ CMD_FUNC(cmd_eline)
"-", /*9 reason */
NULL
};
+ TKLTypeTable *t;
if (IsServer(client))
return;
@@ -1673,11 +1688,11 @@ CMD_FUNC(cmd_eline)
mask[3] = '\0';
usermask = mask; /* eg ~S: */
hostmask = mask2buf;
- if (eline_type_requires_ip(bantypes))
+ if ((t = eline_type_requires_ip(bantypes)))
{
- sendnotice(client, "ERROR: Ban exceptions with type z/Z/c/b do not work on extended server bans. "
- "This is because checking (g)zlines, connect-flood and blacklists is done BEFORE "
- "extended bans can be checked.");
+ sendnotice(client, "ERROR: Ban exception with type '%c' does not work on extended server bans. "
+ "This is because checking for %s takes places BEFORE "
+ "extended bans can be checked.", t->letter, t->log_name);
return;
}
} else {
@@ -1724,25 +1739,31 @@ CMD_FUNC(cmd_eline)
sendnotice(client, "[error] For technical reasons you cannot start the host with a ':', sorry");
return;
}
- if (add && eline_type_requires_ip(bantypes))
+ if (add && ((t = eline_type_requires_ip(bantypes))))
{
/* Trying to exempt a user from a (G)ZLINE,
* make sure the user isn't specifying a host then.
*/
if (strcmp(usermask, "*"))
{
- sendnotice(client, "ERROR: Ban exceptions with type z/Z/c/b need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
- "This is because checking (g)zlines, connect-flood and blacklists is done BEFORE any dns and ident lookups.");
+ sendnotice(client, "ERROR: Ban exception with type '%c' need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
+ "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
+ t->letter,
+ t->log_name);
return;
}
for (p=hostmask; *p; p++)
+ {
if (isalpha(*p) && !isxdigit(*p))
{
- sendnotice(client, "ERROR: Ban exceptions with type z/Z/c/b need to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
- "(so for example *@192.168.* is ok, but *@*.aol.com is not). "
- "This is because checking (g)zlines, connect-flood and blacklists is done BEFORE any dns and ident lookups.");
+ sendnotice(client, "ERROR: Ban exception with type '%c' needs to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
+ "(so for example *@192.168.* is OK, but *@*.aol.com is not). "
+ "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
+ t->letter,
+ t->log_name);
return;
}
+ }
}
}
else
@@ -2367,6 +2388,9 @@ TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Matc
index = tkl_hash(tkl_typetochar(type));
AddListItem(tkl, tklines[index]);
+ if (target & SPAMF_MTAG)
+ mtag_spamfilters_present = 1;
+
return tkl;
}
@@ -2637,6 +2661,7 @@ void _tkl_del_line(TKL *tkl)
/* Finally, free the entry */
free_tkl(tkl);
+ check_mtag_spamfilters_present();
}
/** Add some default ban exceptions - for localhost */
@@ -3052,7 +3077,7 @@ int _find_shun(Client *client)
if (!(tkl->type & TKL_SHUN))
continue;
- snprintf(uhost, sizeof(uhost), "%s@%s", tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
+ tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
if (match_user(uhost, client, MATCH_CHECK_REAL))
{
@@ -3114,7 +3139,7 @@ int _find_spamfilter_user(Client *client, int flags)
return 0;
spamfilter_build_user_string(spamfilter_user, client->name, client);
- return match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, flags, NULL);
+ return match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, flags, NULL);
}
/** Check a spamfilter against all local users and print a message.
@@ -4654,6 +4679,7 @@ int _join_viruschan(Client *client, TKL *tkl, int type)
/** match_spamfilter: executes the spamfilter on the input string.
* @param str The text (eg msg text, notice text, part text, quit text, etc
* @param target The spamfilter target (SPAMF_*)
+ * @param cmd The command (eg: "PRIVMSG")
* @param destination The destination as a text string (eg: "somenick", can be NULL.. eg for away)
* @param flags Any flags (SPAMFLAG_*)
* @param rettkl Pointer to an aTKLline struct, _used for special circumstances only_
@@ -4661,7 +4687,7 @@ int _join_viruschan(Client *client, TKL *tkl, int type)
* 1 if spamfilter matched and it should be blocked (or client exited), 0 if not matched.
* In case of 1, be sure to check IsDead(client)..
*/
-int _match_spamfilter(Client *client, char *str_in, int target, char *destination, int flags, TKL **rettkl)
+int _match_spamfilter(Client *client, char *str_in, int target, char *cmd, char *destination, int flags, TKL **rettkl)
{
TKL *tkl;
TKL *winner_tkl = NULL;
@@ -4676,6 +4702,9 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *destinatio
if (rettkl)
*rettkl = NULL; /* initialize to NULL */
+ if (!cmd)
+ cmd = cmdname_by_spamftarget(target);
+
if (target == SPAMF_USER)
str = str_in;
else
@@ -4755,7 +4784,7 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *destinatio
ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s!%s@%s matches filter '%s': [%s%s: '%s'] [%s]",
client->name, client->user->username, client->user->realhost,
tkl->ptr.spamfilter->match->str,
- cmdname_by_spamftarget(target), destinationbuf, str,
+ cmd, destinationbuf, str,
unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
sendto_snomask_global(SNO_SPAMF, "%s", buf);
@@ -4807,6 +4836,12 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *destinatio
me.name, client->name, destination, reason);
break;
}
+ case SPAMF_MTAG:
+ {
+ sendnumericfmt(client, ERR_CANNOTDOCOMMAND, "%s :Command blocked: %s",
+ cmd, reason);
+ break;
+ }
case SPAMF_DCC:
{
char errmsg[512];
@@ -4837,7 +4872,7 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *destinatio
if ((tkl->ptr.spamfilter->action == BAN_ACT_WARN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_WARN))
{
if ((target != SPAMF_USER) && (target != SPAMF_QUIT))
- sendnumeric(client, RPL_SPAMCMDFWD, cmdname_by_spamftarget(target), reason);
+ sendnumeric(client, RPL_SPAMCMDFWD, cmd, reason);
return 0;
} else
if ((tkl->ptr.spamfilter->action == BAN_ACT_DCCBLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_DCCBLOCK))
@@ -4875,6 +4910,60 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *destinatio
return 0; /* NOTREACHED */
}
+/** Check message-tag spamfilters.
+ * @param client The client
+ * @param mtags Message tags sent by client
+ * @param cmd Command to be executed (can be NULL)
+ * @retval Return 1 to stop processing the command (ignore it) or 0 to allow/continue as normal
+ */
+int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd)
+{
+ MessageTag *m;
+ char buf[4096];
+ char *str;
+
+ /* This is a shortcut: if there are no spamfilters present
+ * on message tags then we can return immediately.
+ * Saves a lot of CPU and it is quite likely too!
+ */
+ if (mtag_spamfilters_present == 0)
+ return 0;
+
+ for (m = mtags; m; m = m->next)
+ {
+ if (m->value)
+ {
+ snprintf(buf, sizeof(buf), "%s=%s", m->name, m->value);
+ str = buf;
+ } else {
+ str = m->name;
+ }
+ if (match_spamfilter(client, str, SPAMF_MTAG, cmd, NULL, 0, NULL))
+ return 1;
+ }
+ return 0;
+}
+
+/** Updates 'mtag_spamfilters_present' based on if any spamfilters
+ * are present with the SPAMF_MTAG target.
+ */
+int check_mtag_spamfilters_present(void)
+{
+ TKL *tkl;
+
+ for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
+ {
+ if (tkl->ptr.spamfilter->target & SPAMF_MTAG)
+ {
+ mtag_spamfilters_present = 1;
+ return 1;
+ }
+ }
+
+ mtag_spamfilters_present = 0;
+ return 0;
+}
+
/** CIDR function to compare the first 'mask' bits.
* @author Taken from atheme
* @returns 1 if equal, 0 if not.
diff --git a/src/modules/tkldb.c b/src/modules/tkldb.c
index 7cfdcd4..57a2274 100644
--- a/src/modules/tkldb.c
+++ b/src/modules/tkldb.c
@@ -61,14 +61,14 @@ ModuleHeader MOD_HEADER = {
do { \
sendto_realops_and_log("[tkldb] Error writing to temporary database file " \
"'%s': %s (DATABASE NOT SAVED)", \
- fname, strerror(errno)); \
+ fname, unrealdb_get_error_string()); \
} while(0)
#define R_SAFE(x) \
do { \
if (!(x)) { \
- config_warn("[tkldb] Read error from database file '%s' (possible corruption): %s", cfg.database, strerror(errno)); \
- fclose(fd); \
+ config_warn("[tkldb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+ unrealdb_close(db); \
FreeTKLRead(); \
return 0; \
} \
@@ -78,7 +78,7 @@ ModuleHeader MOD_HEADER = {
do { \
if (!(x)) { \
WARN_WRITE_ERROR(tmpfname); \
- fclose(fd); \
+ unrealdb_close(db); \
return 0; \
} \
} while(0)
@@ -91,41 +91,56 @@ ModuleHeader MOD_HEADER = {
} \
} while(0)
+/* Structs */
+struct cfgstruct {
+ char *database;
+ char *db_secret;
+};
+
/* Forward declarations */
void tkldb_moddata_free(ModData *md);
-void setcfg(void);
-void freecfg(void);
-int tkldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
-int tkldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
+void setcfg(struct cfgstruct *cfg);
+void freecfg(struct cfgstruct *cfg);
+int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int tkldb_config_posttest(int *errs);
+int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
EVENT(write_tkldb_evt);
int write_tkldb(void);
-int write_tkline(FILE *fd, const char *tmpfname, TKL *tkl);
+int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl);
int read_tkldb(void);
/* Globals variables */
const uint32_t tkldb_version = TKLDB_VERSION;
-struct cfgstruct {
- char *database;
-};
static struct cfgstruct cfg;
+static struct cfgstruct test;
static long tkldb_next_event = 0;
MOD_TEST()
{
memset(&cfg, 0, sizeof(cfg));
- HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkldb_configtest);
+ memset(&test, 0, sizeof(test));
+ setcfg(&test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkldb_config_test);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, tkldb_config_posttest);
return MOD_SUCCESS;
}
MOD_INIT()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
+ ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, -9999);
LoadPersistentLong(modinfo, tkldb_next_event);
- setcfg();
+ setcfg(&cfg);
+ HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkldb_config_run);
+ return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
if (!tkldb_next_event)
{
/* If this is the first time that our module is loaded, then
@@ -142,25 +157,16 @@ MOD_INIT()
}
tkldb_next_event = TStime() + TKLDB_SAVE_EVERY + TKLDB_SAVE_EVERY_DELTA;
}
- HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkldb_configrun);
- return MOD_SUCCESS;
-}
-
-MOD_LOAD()
-{
EventAdd(modinfo->handle, "tkldb_write_tkldb", write_tkldb_evt, NULL, 1000, 0);
- if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
- {
- config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
- return MOD_FAILED;
- }
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
- write_tkldb();
- freecfg();
+ if (loop.ircd_terminating)
+ write_tkldb();
+ freecfg(&test);
+ freecfg(&cfg);
SavePersistentLong(modinfo, tkldb_next_event);
return MOD_SUCCESS;
}
@@ -171,19 +177,20 @@ void tkldb_moddata_free(ModData *md)
md->i = 0;
}
-void setcfg(void)
+void setcfg(struct cfgstruct *cfg)
{
// Default: data/tkl.db
- safe_strdup(cfg.database, "tkl.db");
- convert_to_absolute_path(&cfg.database, PERMDATADIR);
+ safe_strdup(cfg->database, "tkl.db");
+ convert_to_absolute_path(&cfg->database, PERMDATADIR);
}
-void freecfg(void)
+void freecfg(struct cfgstruct *cfg)
{
- safe_free(cfg.database);
+ safe_free(cfg->database);
+ safe_free(cfg->db_secret);
}
-int tkldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+int tkldb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
int errors = 0;
ConfigEntry *cep;
@@ -197,16 +204,45 @@ int tkldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
- if (!cep->ce_vardata) {
+ if (!cep->ce_vardata)
+ {
config_error("%s:%i: blank set::tkldb::%s without value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
errors++;
- continue;
- }
- if (!strcmp(cep->ce_varname, "database")) {
+ } else
+ if (!strcmp(cep->ce_varname, "database"))
+ {
convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
- continue;
+ safe_strdup(test.database, cep->ce_vardata);
+ } else
+ if (!strcmp(cep->ce_varname, "db-secret"))
+ {
+ char *err;
+ if ((err = unrealdb_test_secret(cep->ce_vardata)))
+ {
+ config_error("%s:%i: set::tkldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
+ errors++;
+ continue;
+ }
+ safe_strdup(test.db_secret, cep->ce_vardata);
+ } else
+ {
+ config_error("%s:%i: unknown directive set::tkldb::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+ errors++;
}
- config_error("%s:%i: unknown directive set::tkldb::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+ }
+
+ *errs = errors;
+ return errors ? -1 : 1;
+}
+
+int tkldb_config_posttest(int *errs)
+{
+ int errors = 0;
+ char *errstr;
+
+ if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+ {
+ config_error("[tkldb] %s", errstr);
errors++;
}
@@ -214,7 +250,7 @@ int tkldb_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
return errors ? -1 : 1;
}
-int tkldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
+int tkldb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
ConfigEntry *cep;
@@ -229,6 +265,8 @@ int tkldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
{
if (!strcmp(cep->ce_varname, "database"))
safe_strdup(cfg.database, cep->ce_vardata);
+ else if (!strcmp(cep->ce_varname, "db-secret"))
+ safe_strdup(cfg.db_secret, cep->ce_vardata);
}
return 1;
}
@@ -244,7 +282,7 @@ EVENT(write_tkldb_evt)
int write_tkldb(void)
{
char tmpfname[512];
- FILE *fd;
+ UnrealDB *db;
uint64_t tklcount;
int index, index2;
TKL *tkl;
@@ -256,15 +294,15 @@ int write_tkldb(void)
// Write to a tempfile first, then rename it if everything succeeded
snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
- fd = fopen(tmpfname, "wb");
- if (!fd)
+ db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+ if (!db)
{
WARN_WRITE_ERROR(tmpfname);
return 0;
}
- W_SAFE(write_int32(fd, TKLDB_MAGIC));
- W_SAFE(write_data(fd, &tkldb_version, sizeof(tkldb_version)));
+ W_SAFE(unrealdb_write_int32(db, TKLDB_MAGIC));
+ W_SAFE(unrealdb_write_int32(db, tkldb_version));
// Count the *-Lines
tklcount = 0;
@@ -292,7 +330,7 @@ int write_tkldb(void)
tklcount++;
}
}
- W_SAFE(write_data(fd, &tklcount, sizeof(tklcount)));
+ W_SAFE(unrealdb_write_int64(db, tklcount));
// Now write the actual *-Lines, first the ones in the hash table
for (index = 0; index < TKLIPHASHLEN1; index++)
@@ -303,7 +341,7 @@ int write_tkldb(void)
{
if (tkl->flags & TKL_FLAG_CONFIG)
continue; /* config entry */
- if (!write_tkline(fd, tmpfname, tkl)) // write_tkline() closes the fd on errors itself
+ if (!write_tkline(db, tmpfname, tkl)) // write_tkline() closes the db on errors itself
return 0;
}
}
@@ -315,13 +353,13 @@ int write_tkldb(void)
{
if (tkl->flags & TKL_FLAG_CONFIG)
continue; /* config entry */
- if (!write_tkline(fd, tmpfname, tkl))
+ if (!write_tkline(db, tmpfname, tkl))
return 0;
}
}
// Everything seems to have gone well, attempt to close and rename the tempfile
- if (fclose(fd) != 0)
+ if (!unrealdb_close(db))
{
WARN_WRITE_ERROR(tmpfname);
return 0;
@@ -344,18 +382,18 @@ int write_tkldb(void)
}
/** Write a TKL entry */
-int write_tkline(FILE *fd, const char *tmpfname, TKL *tkl)
+int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl)
{
char tkltype;
char buf[256];
/* First, write the common attributes */
tkltype = tkl_typetochar(tkl->type);
- W_SAFE(write_data(fd, &tkltype, sizeof(tkltype))); // TKL char
+ W_SAFE(unrealdb_write_char(db, tkltype)); // TKL char
- W_SAFE(write_str(fd, tkl->set_by));
- W_SAFE(write_int64(fd, tkl->set_at));
- W_SAFE(write_int64(fd, tkl->expire_at));
+ W_SAFE(unrealdb_write_str(db, tkl->set_by));
+ W_SAFE(unrealdb_write_int64(db, tkl->set_at));
+ W_SAFE(unrealdb_write_int64(db, tkl->expire_at));
if (TKLIsServerBan(tkl))
{
@@ -365,9 +403,9 @@ int write_tkline(FILE *fd, const char *tmpfname, TKL *tkl)
snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.serverban->usermask);
usermask = buf;
}
- W_SAFE(write_str(fd, usermask));
- W_SAFE(write_str(fd, tkl->ptr.serverban->hostmask));
- W_SAFE(write_str(fd, tkl->ptr.serverban->reason));
+ W_SAFE(unrealdb_write_str(db, usermask));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->hostmask));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.serverban->reason));
} else
if (TKLIsBanException(tkl))
{
@@ -377,17 +415,17 @@ int write_tkline(FILE *fd, const char *tmpfname, TKL *tkl)
snprintf(buf, sizeof(buf), "%%%s", tkl->ptr.banexception->usermask);
usermask = buf;
}
- W_SAFE(write_str(fd, usermask));
- W_SAFE(write_str(fd, tkl->ptr.banexception->hostmask));
- W_SAFE(write_str(fd, tkl->ptr.banexception->bantypes));
- W_SAFE(write_str(fd, tkl->ptr.banexception->reason));
+ W_SAFE(unrealdb_write_str(db, usermask));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->hostmask));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->bantypes));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.banexception->reason));
} else
if (TKLIsNameBan(tkl))
{
char *hold = tkl->ptr.nameban->hold ? "H" : "*";
- W_SAFE(write_str(fd, hold));
- W_SAFE(write_str(fd, tkl->ptr.nameban->name));
- W_SAFE(write_str(fd, tkl->ptr.nameban->reason));
+ W_SAFE(unrealdb_write_str(db, hold));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->name));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.nameban->reason));
} else
if (TKLIsSpamfilter(tkl))
{
@@ -395,12 +433,12 @@ int write_tkline(FILE *fd, const char *tmpfname, TKL *tkl)
char *target = spamfilter_target_inttostring(tkl->ptr.spamfilter->target);
char action = banact_valtochar(tkl->ptr.spamfilter->action);
- W_SAFE(write_str(fd, match_type));
- W_SAFE(write_str(fd, tkl->ptr.spamfilter->match->str));
- W_SAFE(write_str(fd, target));
- W_SAFE(write_data(fd, &action, sizeof(action)));
- W_SAFE(write_str(fd, tkl->ptr.spamfilter->tkl_reason));
- W_SAFE(write_int64(fd, tkl->ptr.spamfilter->tkl_duration));
+ W_SAFE(unrealdb_write_str(db, match_type));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->match->str));
+ W_SAFE(unrealdb_write_str(db, target));
+ W_SAFE(unrealdb_write_char(db, action));
+ W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->tkl_reason));
+ W_SAFE(unrealdb_write_int64(db, tkl->ptr.spamfilter->tkl_duration));
}
return 1;
@@ -409,7 +447,7 @@ int write_tkline(FILE *fd, const char *tmpfname, TKL *tkl)
/** Read all entries from the TKL db */
int read_tkldb(void)
{
- FILE *fd;
+ UnrealDB *db;
TKL *tkl = NULL;
uint32_t magic = 0;
uint32_t version;
@@ -426,49 +464,61 @@ int read_tkldb(void)
gettimeofday(&tv_alpha, NULL);
#endif
- fd = fopen(cfg.database, "rb");
- if (!fd)
+ db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+ if (!db)
{
- if (errno == ENOENT)
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
{
/* Database does not exist. Could be first boot */
config_warn("[tkldb] No database present at '%s', will start a new one", cfg.database);
return 1;
- } else {
- config_warn("[tkldb] Unable to open database file '%s' for reading: %s", cfg.database, strerror(errno));
+ } else
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+ {
+ /* Re-open as unencrypted */
+ db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+ if (!db)
+ {
+ /* This should actually never happen, unless some weird I/O error */
+ config_warn("[tkldb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
+ return 0;
+ }
+ } else
+ {
+ config_warn("[tkldb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
return 0;
}
}
/* The database starts with a "magic value" - unless it's some old version or corrupt */
- R_SAFE(read_data(fd, &magic, sizeof(magic)));
+ R_SAFE(unrealdb_read_int32(db, &magic));
if (magic != TKLDB_MAGIC)
{
config_warn("[tkldb] Database '%s' uses an old and unsupported format OR is corrupt", cfg.database);
config_status("If you are upgrading from UnrealIRCd 4 (or 5.0.0-alpha1) then we suggest you to "
"delete the existing database. Just keep at least 1 server linked during the upgrade "
"process to preserve your global *LINES and Spamfilters.");
- fclose(fd);
+ unrealdb_close(db);
return 0;
}
/* Now do a version check */
- R_SAFE(read_data(fd, &version, sizeof(version)));
+ R_SAFE(unrealdb_read_int32(db, &version));
if (version < 4999)
{
config_warn("[tkldb] Database '%s' uses an unsupport - possibly old - format (%ld).", cfg.database, (long)version);
- fclose(fd);
+ unrealdb_close(db);
return 0;
}
if (version > tkldb_version)
{
config_warn("[tkldb] Database '%s' has version %lu while we only support %lu. Did you just downgrade UnrealIRCd? Sorry this is not suported",
cfg.database, (unsigned long)tkldb_version, (unsigned long)version);
- fclose(fd);
+ unrealdb_close(db);
return 0;
}
- R_SAFE(read_data(fd, &tklcount, sizeof(tklcount)));
+ R_SAFE(unrealdb_read_int64(db, &tklcount));
for (cnt = 0; cnt < tklcount; cnt++)
{
@@ -477,7 +527,7 @@ int read_tkldb(void)
tkl = safe_alloc(sizeof(TKL));
/* First, fetch the TKL type.. */
- R_SAFE(read_data(fd, &c, sizeof(c)));
+ R_SAFE(unrealdb_read_char(db, &c));
tkl->type = tkl_chartotype(c);
if (!tkl->type)
{
@@ -493,10 +543,10 @@ int read_tkldb(void)
}
/* Read the common types (same for all TKLs) */
- R_SAFE(read_str(fd, &tkl->set_by));
- R_SAFE(read_int64(fd, &v));
+ R_SAFE(unrealdb_read_str(db, &tkl->set_by));
+ R_SAFE(unrealdb_read_int64(db, &v));
tkl->set_at = v;
- R_SAFE(read_int64(fd, &v));
+ R_SAFE(unrealdb_read_int64(db, &v));
tkl->expire_at = v;
/* Save some CPU... if it's already expired then don't bother adding */
@@ -513,7 +563,7 @@ int read_tkldb(void)
/* Usermask - but taking into account that the
* %-prefix means a soft ban.
*/
- R_SAFE(read_str(fd, &str));
+ R_SAFE(unrealdb_read_str(db, &str));
if (*str == '%')
{
softban = 1;
@@ -524,8 +574,8 @@ int read_tkldb(void)
safe_free(str);
/* And the other 2 fields.. */
- R_SAFE(read_str(fd, &tkl->ptr.serverban->hostmask));
- R_SAFE(read_str(fd, &tkl->ptr.serverban->reason));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->hostmask));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.serverban->reason));
if (find_tkl_serverban(tkl->type, tkl->ptr.serverban->usermask,
tkl->ptr.serverban->hostmask, softban))
@@ -551,7 +601,7 @@ int read_tkldb(void)
/* Usermask - but taking into account that the
* %-prefix means a soft ban.
*/
- R_SAFE(read_str(fd, &str));
+ R_SAFE(unrealdb_read_str(db, &str));
if (*str == '%')
{
softban = 1;
@@ -562,9 +612,9 @@ int read_tkldb(void)
safe_free(str);
/* And the other 3 fields.. */
- R_SAFE(read_str(fd, &tkl->ptr.banexception->hostmask));
- R_SAFE(read_str(fd, &tkl->ptr.banexception->bantypes));
- R_SAFE(read_str(fd, &tkl->ptr.banexception->reason));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->hostmask));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->bantypes));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.banexception->reason));
if (find_tkl_banexception(tkl->type, tkl->ptr.banexception->usermask,
tkl->ptr.banexception->hostmask, softban))
@@ -587,12 +637,12 @@ int read_tkldb(void)
{
tkl->ptr.nameban = safe_alloc(sizeof(NameBan));
- R_SAFE(read_str(fd, &str));
+ R_SAFE(unrealdb_read_str(db, &str));
if (*str == 'H')
tkl->ptr.nameban->hold = 1;
safe_free(str);
- R_SAFE(read_str(fd, &tkl->ptr.nameban->name));
- R_SAFE(read_str(fd, &tkl->ptr.nameban->reason));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->name));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.nameban->reason));
if (find_tkl_nameban(tkl->type, tkl->ptr.nameban->name,
tkl->ptr.nameban->hold))
@@ -617,7 +667,7 @@ int read_tkldb(void)
tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
/* Match method */
- R_SAFE(read_str(fd, &str));
+ R_SAFE(unrealdb_read_str(db, &str));
match_method = unreal_match_method_strtoval(str);
if (!match_method)
{
@@ -627,7 +677,7 @@ int read_tkldb(void)
safe_free(str);
/* Match string (eg: regex) */
- R_SAFE(read_str(fd, &str));
+ R_SAFE(unrealdb_read_str(db, &str));
tkl->ptr.spamfilter->match = unreal_create_match(match_method, str, &err);
if (!tkl->ptr.spamfilter->match)
{
@@ -637,7 +687,7 @@ int read_tkldb(void)
safe_free(str);
/* Target (eg: cpn) */
- R_SAFE(read_str(fd, &str));
+ R_SAFE(unrealdb_read_str(db, &str));
tkl->ptr.spamfilter->target = spamfilter_gettargets(str, NULL);
if (!tkl->ptr.spamfilter->target)
{
@@ -648,7 +698,7 @@ int read_tkldb(void)
safe_free(str);
/* Action */
- R_SAFE(read_data(fd, &c, sizeof(c)));
+ R_SAFE(unrealdb_read_char(db, &c));
tkl->ptr.spamfilter->action = banact_chartoval(c);
if (!tkl->ptr.spamfilter->action)
{
@@ -657,8 +707,8 @@ int read_tkldb(void)
do_not_add = 1;
}
- R_SAFE(read_str(fd, &tkl->ptr.spamfilter->tkl_reason));
- R_SAFE(read_int64(fd, &v));
+ R_SAFE(unrealdb_read_str(db, &tkl->ptr.spamfilter->tkl_reason));
+ R_SAFE(unrealdb_read_int64(db, &v));
tkl->ptr.spamfilter->tkl_duration = v;
if (find_tkl_spamfilter(tkl->type, tkl->ptr.spamfilter->match->str,
@@ -695,10 +745,7 @@ int read_tkldb(void)
FreeTKLRead();
}
- /* If everything went fine, then reading a single byte should cause an EOF error */
- if (fread(&c, 1, 1, fd) == 1)
- config_warn("[tkldb] Database invalid. Extra data found at end of DB file.");
- fclose(fd);
+ unrealdb_close(db);
if (added_cnt)
sendto_realops_and_log("[tkldb] Re-added %d *-Lines", added_cnt);
diff --git a/src/modules/topic.c b/src/modules/topic.c
index 2aa1708..309de5d 100644
--- a/src/modules/topic.c
+++ b/src/modules/topic.c
@@ -242,7 +242,7 @@ CMD_FUNC(cmd_topic)
Hook *tmphook;
int n;
- if (match_spamfilter(client, topic, SPAMF_TOPIC, channel->chname, 0, NULL))
+ if (match_spamfilter(client, topic, SPAMF_TOPIC, "TOPIC", channel->chname, 0, NULL))
return;
for (tmphook = Hooks[HOOKTYPE_PRE_LOCAL_TOPIC]; tmphook; tmphook = tmphook->next) {
diff --git a/src/modules/websocket.c b/src/modules/websocket.c
index 646d03b..97cd7dd 100644
--- a/src/modules/websocket.c
+++ b/src/modules/websocket.c
@@ -26,6 +26,11 @@ ModuleHeader MOD_HEADER
#define WEBSOCKET_SEND_BUFFER_SIZE 16384
#endif
+typedef enum WebSocketType {
+ WEBSOCKET_TYPE_BINARY = 1,
+ WEBSOCKET_TYPE_TEXT = 2
+} WebSocketType;
+
typedef struct WebSocketUser WebSocketUser;
struct WebSocketUser {
char get; /**< GET initiated */
@@ -33,14 +38,14 @@ struct WebSocketUser {
char *handshake_key; /**< Handshake key (used during handshake) */
char *lefttoparse; /**< Leftover buffer to parse */
int lefttoparselen; /**< Length of lefttoparse buffer */
+ WebSocketType type; /**< WEBSOCKET_TYPE_BINARY or WEBSOCKET_TYPE_TEXT */
+ char *sec_websocket_protocol; /**< Only valid during parsing of the request, after that it is NULL again */
};
-#define WEBSOCKET_TYPE_BINARY 0x1
-#define WEBSOCKET_TYPE_TEXT 0x2
-
#define WSU(client) ((WebSocketUser *)moddata_client(client, websocket_md).ptr)
-#define WEBSOCKET_TYPE(client) ((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
+#define WEBSOCKET_PORT(client) ((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
+#define WEBSOCKET_TYPE(client) (WSU(client)->type)
#define WEBSOCKET_MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" /* see RFC6455 */
@@ -59,7 +64,7 @@ int websocket_packet_in(Client *client, char *readbuf, int *length);
void websocket_mdata_free(ModData *m);
int websocket_handle_packet(Client *client, char *readbuf, int length);
int websocket_handle_handshake(Client *client, char *readbuf, int *length);
-int websocket_complete_handshake(Client *client);
+int websocket_handshake_send_response(Client *client);
int websocket_handle_packet_ping(Client *client, char *buf, int len);
int websocket_handle_packet_pong(Client *client, char *buf, int len);
int websocket_create_packet(int opcode, char **buf, int *len);
@@ -67,6 +72,7 @@ int websocket_send_pong(Client *client, char *buf, int len);
/* Global variables */
ModDataInfo *websocket_md;
+static int ws_text_mode_available = 1;
MOD_TEST()
{
@@ -98,6 +104,8 @@ MOD_INIT()
MOD_LOAD()
{
+ if (non_utf8_nick_chars_in_use || (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY))
+ ws_text_mode_available = 0;
return MOD_SUCCESS;
}
@@ -302,11 +310,12 @@ int websocket_handle_websocket(Client *client, char *readbuf2, int length2)
*/
int websocket_packet_in(Client *client, char *readbuf, int *length)
{
- if ((client->local->receiveM == 0) && WEBSOCKET_TYPE(client) && !WSU(client) && (*length > 8) && !strncmp(readbuf, "GET ", 4))
+ if ((client->local->receiveM == 0) && WEBSOCKET_PORT(client) && !WSU(client) && (*length > 8) && !strncmp(readbuf, "GET ", 4))
{
/* Allocate a new WebSocketUser struct for this session */
moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
WSU(client)->get = 1;
+ WSU(client)->type = client->local->listener->websocket_options; /* the default, unless the client chooses otherwise */
}
if (!WSU(client))
@@ -452,6 +461,58 @@ int websocket_handshake_helper(char *buffer, int len, char **key, char **value,
return 0;
}
+/** Finally, validate the websocket request (handshake) and proceed or reject. */
+int websocket_handshake_valid(Client *client)
+{
+ if (!WSU(client)->handshake_key)
+ {
+ if (is_module_loaded("webredir"))
+ {
+ char *parx[2] = { NULL, NULL };
+ do_cmd(client, NULL, "GET", 1, parx);
+ }
+ dead_socket(client, "Invalid WebSocket request");
+ return 0;
+ }
+ if (WSU(client)->sec_websocket_protocol)
+ {
+ char *p = NULL, *name;
+ int negotiated = 0;
+
+ for (name = strtoken(&p, WSU(client)->sec_websocket_protocol, ",");
+ name;
+ name = strtoken(&p, NULL, ","))
+ {
+ skip_whitespace(&name);
+ if (!strcmp(name, "binary.ircv3.net"))
+ {
+ negotiated = WEBSOCKET_TYPE_BINARY;
+ break; /* First hit wins */
+ } else
+ if (!strcmp(name, "text.ircv3.net") && ws_text_mode_available)
+ {
+ negotiated = WEBSOCKET_TYPE_TEXT;
+ break; /* First hit wins */
+ }
+ }
+ if (negotiated == WEBSOCKET_TYPE_BINARY)
+ {
+ WSU(client)->type = WEBSOCKET_TYPE_BINARY;
+ safe_strdup(WSU(client)->sec_websocket_protocol, "binary.ircv3.net");
+ } else
+ if (negotiated == WEBSOCKET_TYPE_TEXT)
+ {
+ WSU(client)->type = WEBSOCKET_TYPE_TEXT;
+ safe_strdup(WSU(client)->sec_websocket_protocol, "text.ircv3.net");
+ } else
+ {
+ /* Negotiation failed, fallback to the default (don't set it here) */
+ safe_free(WSU(client)->sec_websocket_protocol);
+ }
+ }
+ return 1;
+}
+
/** Handle client GET WebSocket handshake.
* Yes, I'm going to assume that the header fits in one packet and one packet only.
*/
@@ -500,22 +561,19 @@ int websocket_handle_handshake(Client *client, char *readbuf, int *length)
return -1;
}
safe_strdup(WSU(client)->handshake_key, value);
+ } else
+ if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
+ {
+ /* Save it here, will be processed later */
+ safe_strdup(WSU(client)->sec_websocket_protocol, value);
}
}
if (end_of_request)
{
- if (!WSU(client)->handshake_key)
- {
- if (is_module_loaded("webredir"))
- {
- char *parx[2] = { NULL, NULL };
- do_cmd(client, NULL, "GET", 1, parx);
- }
- dead_socket(client, "Invalid WebSocket request");
+ if (!websocket_handshake_valid(client))
return -1;
- }
- websocket_complete_handshake(client);
+ websocket_handshake_send_response(client);
return 0;
}
@@ -528,7 +586,7 @@ int websocket_handle_handshake(Client *client, char *readbuf, int *length)
}
/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
-int websocket_complete_handshake(Client *client)
+int websocket_handshake_send_response(Client *client)
{
char buf[512], hashbuf[64];
SHA_CTX hash;
@@ -547,10 +605,21 @@ int websocket_complete_handshake(Client *client)
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: %s\r\n"
- "\r\n",
+ "Sec-WebSocket-Accept: %s\r\n",
hashbuf);
+ if (WSU(client)->sec_websocket_protocol)
+ {
+ /* using strlen() is safe here since above buffer will not
+ * cause it to be >=512 and thus we won't get into negatives.
+ */
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
+ "Sec-WebSocket-Protocol: %s\r\n",
+ WSU(client)->sec_websocket_protocol);
+ }
+
+ strlcat(buf, "\r\n", sizeof(buf));
+
/* Caution: we bypass sendQ flood checking by doing it this way.
* Risk is minimal, though, as we only permit limited text only
* once per session.
diff --git a/src/modules/whois.c b/src/modules/whois.c
index c4ec3cc..c99b7fd 100644
--- a/src/modules/whois.c
+++ b/src/modules/whois.c
@@ -103,15 +103,8 @@ CMD_FUNC(cmd_whois)
if (wilds)
continue;
- if ((target = find_client(nick, NULL)))
+ if ((target = find_person(nick, NULL)))
{
- if (IsServer(target))
- continue;
- /*
- * I'm always last :-) and target->next == NULL!!
- */
- if (IsMe(target))
- break;
/*
* 'Rules' established for sending a WHOIS reply:
* - only send replies about common or public channels
diff --git a/src/modules/whox.c b/src/modules/whox.c
index e9d4338..7cbc953 100644
--- a/src/modules/whox.c
+++ b/src/modules/whox.c
@@ -35,6 +35,7 @@ ModuleHeader MOD_HEADER
#define FIELD_OPLEVEL 0x1000 /* meaningless and stupid, but whatever */
#define FIELD_REALHOST 0x2000
#define FIELD_MODES 0x4000
+#define FIELD_REPUTATION 0x8000
#define WMATCH_NICK 0x0001
#define WMATCH_USER 0x0002
@@ -261,6 +262,7 @@ CMD_FUNC(cmd_whox)
case 'a': fmt.fields |= FIELD_ACCOUNT; break;
case 'm': fmt.fields |= FIELD_MODES; break;
case 'o': fmt.fields |= FIELD_OPLEVEL; break;
+ case 'R': fmt.fields |= FIELD_REPUTATION; break;
case ',':
s++;
fmt.querytype = s;
@@ -814,6 +816,13 @@ static void do_who(Client *client, Client *acptr, Channel *channel, struct who_f
append_format(str, sizeof str, &pos, " %s", (!isdigit(*acptr->user->svid)) ? acptr->user->svid : "0");
if (HasField(fmt, FIELD_OPLEVEL))
append_format(str, sizeof str, &pos, " %s", (channel && is_skochanop(acptr, channel)) ? "999" : "n/a");
+ if (HasField(fmt, FIELD_REPUTATION))
+ {
+ if (IsOper(client))
+ append_format(str, sizeof str, &pos, " %d", GetReputation(acptr));
+ else
+ append_format(str, sizeof str, &pos, " %s", "*");
+ }
if (HasField(fmt, FIELD_INFO))
append_format(str, sizeof str, &pos, " :%s", acptr->info);
diff --git a/src/numeric.c b/src/numeric.c
index ce4c133..1481240 100644
--- a/src/numeric.c
+++ b/src/numeric.c
@@ -478,7 +478,7 @@ static char *replies[] = {
/* 435 */ NULL, /* bahamut */
/* 436 ERR_NICKCOLLISION */ "%s :Nickname collision KILL",
/* 437 ERR_BANNICKCHANGE */ "%s :Cannot change nickname while banned on channel",
-/* 438 ERR_NCHANGETOOFAST */ "%s :Nick change too fast. Please wait %d seconds",
+/* 438 ERR_NCHANGETOOFAST */ "%s :Nick change too fast. Please try again later.",
/* 439 ERR_TARGETTOOFAST */ "%s :Message target change too fast. Please wait %ld seconds",
/* 440 ERR_SERVICESDOWN */ "%s :Services are currently down. Please try again later.",
/* 441 ERR_USERNOTINCHANNEL */ "%s %s :They aren't on that channel",
diff --git a/src/parse.c b/src/parse.c
index 717ce12..6966958 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -485,6 +485,11 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
}
}
para[++i] = NULL;
+
+ /* Check if one of the message tags are rejected by spamfilter */
+ if (MyConnect(from) && !IsServer(from) && match_spamfilter_mtags(from, mtags, cmptr ? cmptr->cmd : NULL))
+ return;
+
if (cmptr == NULL)
{
do_numeric(numeric, from, mtags, i, para);
@@ -494,6 +499,7 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
if (IsUser(cptr) && (cmptr->flags & CMD_RESETIDLE))
cptr->local->last = TStime();
+ /* Now ready to execute the command */
#ifndef DEBUGMODE
if (cmptr->flags & CMD_ALIAS)
{
diff --git a/src/scache.c b/src/scache.c
index 7525ea2..00a4601 100644
--- a/src/scache.c
+++ b/src/scache.c
@@ -7,7 +7,7 @@
#include "unrealircd.h"
/*
- * ircd used to store full servernames in ClientUser as well as in the
+ * ircd used to store full servernames in User as well as in the
* whowas info. there can be some 40k such structures alive at any
* given time, while the number of unique server names a server sees in
* its lifetime is at most a few hundred. by tokenizing server names
diff --git a/src/serv.c b/src/serv.c
index e90fd94..a58b417 100644
--- a/src/serv.c
+++ b/src/serv.c
@@ -28,6 +28,7 @@
/* s_serv.c 2.55 2/7/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
#include "unrealircd.h"
+#include
#ifndef _WIN32
/* for uname(), is POSIX so should be OK... */
#include
@@ -176,10 +177,12 @@ CMD_FUNC(cmd_version)
if (ValidatePermissionsForPath("server:info",client,NULL,NULL,NULL))
{
sendnotice(client, "%s", SSLeay_version(SSLEAY_VERSION));
- sendnotice(client, "%s", pcre2_version());
+ sendnotice(client, "libsodium %s", sodium_version_string());
#ifdef USE_LIBCURL
sendnotice(client, "%s", curl_version());
#endif
+ sendnotice(client, "c-ares %s", ares_version(NULL));
+ sendnotice(client, "%s", pcre2_version());
}
if (MyUser(client))
send_version(client,RPL_ISUPPORT);
diff --git a/src/support.c b/src/support.c
index ef7e484..6207d93 100644
--- a/src/support.c
+++ b/src/support.c
@@ -724,6 +724,34 @@ void outofmemory(size_t bytes)
exit(7);
}
+/** Allocate sensitive memory - this should only be used for HIGHLY sensitive data, since
+ * it wastes 8192+ bytes even if only asked to allocate for example 32 bytes (this is by design).
+ * @param size How many bytes to allocate
+ * @returns A pointer to the newly allocated memory.
+ * @note If out of memory then the IRCd will exit.
+ */
+void *safe_alloc_sensitive(size_t size)
+{
+ void *p;
+ if (size == 0)
+ return NULL;
+ p = sodium_malloc(((size/32)*32)+32);
+ if (!p)
+ outofmemory(size);
+ memset(p, 0, size);
+ return p;
+}
+
+/** Safely duplicate a string */
+char *our_strdup_sensitive(const char *str)
+{
+ char *ret = safe_alloc_sensitive(strlen(str)+1);
+ if (!ret)
+ outofmemory(strlen(str));
+ strcpy(ret, str); /* safe, see above */
+ return ret;
+}
+
/** Returns a unique filename in the specified directory
* using the specified suffix. The returned value will
* be of the form /.
@@ -1307,3 +1335,18 @@ char *unreal_strftime(char *str)
return str;
return buf;
}
+
+/** Convert a string to lowercase */
+void strtolower_safe(char *dst, char *src, int size)
+{
+ if (!size)
+ return; /* size of 0 is unworkable */
+ size--; /* for \0 */
+
+ for (; *src && size; src++)
+ {
+ *dst++ = tolower(*src);
+ size--;
+ }
+ *dst = '\0';
+}
diff --git a/src/unrealdb.c b/src/unrealdb.c
new file mode 100644
index 0000000..e8a7226
--- /dev/null
+++ b/src/unrealdb.c
@@ -0,0 +1,1163 @@
+/************************************************************************
+ * src/unrealdb.c
+ * Functions for dealing easily with (encrypted) database files.
+ * (C) Copyright 2021 Bram Matthys (Syzop)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+/** @file
+ * @brief Unreal database API - see @ref UnrealDBFunctions
+ */
+
+/**
+ * Read and write to database files - encrypted and unencrypted.
+ * This provides functions for dealing with (encrypted) database files.
+ * - File format: https://www.unrealircd.org/docs/Dev:UnrealDB
+ * - KDF: Argon2: https://en.wikipedia.org/wiki/Argon2
+ * - Cipher: XChaCha20 from libsodium: https://libsodium.gitbook.io/doc/advanced/stream_ciphers/xchacha20
+ * @defgroup UnrealDBFunctions Database functions
+ */
+
+/* Benchmarking results:
+ * On standard hardware as of 2021 speeds of 150-200 megabytes per second
+ * are achieved realisticly for both reading and writing encrypted
+ * database files. Of course, YMMV, depending on record sizes, CPU,
+ * and I/O speeds of the underlying hardware.
+ */
+
+/* In UnrealIRCd 5.2.0 we don't write the v1 header yet for unencrypted
+ * database files, this so users using unencrypted can easily downgrade
+ * to 5.0.9 and lower should there be any need to do so.
+ * We DO support READING encypted, unencrypted v1, and unencrypted raw (v0)
+ * in 5.2.0, though.
+ * Presumably in 2022 or so we will stop writing v0 by default and change
+ * this #undef to a #define to write v1.
+ */
+#undef UNREALDB_WRITE_V1
+
+/* If a key is specified, it must be this size */
+#define UNREALDB_KEY_LEN crypto_secretstream_xchacha20poly1305_KEYBYTES
+
+/** Default 'time cost' for Argon2id */
+#define UNREALDB_ARGON2_DEFAULT_TIME_COST 4
+/** Default 'memory cost' for Argon2id. Note that 15 means 1<<15=32M */
+#define UNREALDB_ARGON2_DEFAULT_MEMORY_COST 15
+/** Default 'parallelism cost' for Argon2id. */
+#define UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST 2
+
+#ifdef _WIN32
+/* Ignore this warning on Windows as it is a false positive */
+#pragma warning(disable : 6029)
+#endif
+
+/* Forward declarations - only used for internal (static) functions, of course */
+static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg);
+static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg);
+
+UnrealDBError unrealdb_last_error_code;
+static char *unrealdb_last_error_string = NULL;
+
+/** Set error condition on unrealdb 'c' (internal function).
+ * @param c The unrealdb file handle
+ * @param pattern The format string
+ * @param ... Any parameters to the format string
+ * @note this will also set c->failed=1 to prevent any further reading/writing.
+ */
+static void unrealdb_set_error(UnrealDB *c, UnrealDBError errcode, FORMAT_STRING(const char *pattern), ...)
+{
+ va_list vl;
+ char buf[512];
+ va_start(vl, pattern);
+ vsnprintf(buf, sizeof(buf), pattern, vl);
+ va_end(vl);
+ if (c)
+ {
+ c->error_code = errcode;
+ safe_strdup(c->error_string, buf);
+ }
+ unrealdb_last_error_code = errcode;
+ safe_strdup(unrealdb_last_error_string, buf);
+}
+
+/** Free a UnrealDB struct (internal function). */
+static void unrealdb_free(UnrealDB *c)
+{
+ unrealdb_free_config(c->config);
+ safe_free(c->error_string);
+ safe_free_sensitive(c);
+}
+
+static int unrealdb_kdf(UnrealDB *c, Secret *secr)
+{
+ if (c->config->kdf != UNREALDB_KDF_ARGON2ID)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Unknown KDF 0x%x", (int)c->config->kdf);
+ return 0;
+ }
+ /* Need to run argon2 to generate key */
+ if (argon2id_hash_raw(c->config->t_cost,
+ 1 << c->config->m_cost,
+ c->config->p_cost,
+ secr->password, strlen(secr->password),
+ c->config->salt, c->config->saltlen,
+ c->config->key, c->config->keylen) != ARGON2_OK)
+ {
+ /* out of memory or some other very unusual error */
+ unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Could not generate argon2 hash - out of memory or something weird?");
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * @addtogroup UnrealDBFunctions
+ * @{
+ */
+
+/** Get the error string for last failed unrealdb operation.
+ * @returns The error string
+ * @note Use the return value only for displaying of errors
+ * to the end-user.
+ * For programmatically checking of error conditions
+ * use unrealdb_get_error_code() instead.
+ */
+char *unrealdb_get_error_string(void)
+{
+ return unrealdb_last_error_string;
+}
+
+/** Get the error code for last failed unrealdb operation
+ * @returns An UNREAL_DB_ERROR_*
+ */
+UnrealDBError unrealdb_get_error_code(void)
+{
+ return unrealdb_last_error_code;
+}
+
+/** Open an unrealdb file.
+ * @param filename The filename to open
+ * @param mode Either UNREALDB_MODE_READ or UNREALDB_MODE_WRITE
+ * @param secret_block The name of the secret xx { } block (so NOT the actual password!!)
+ * @returns A pointer to a UnrealDB structure that can be used in subsequent calls for db read/writes,
+ * and finally unrealdb_close(). Or NULL in case of failure.
+ * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
+ * unrealdb_get_error_string() to see the actual error.
+ */
+UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block)
+{
+ UnrealDB *c = safe_alloc_sensitive(sizeof(UnrealDB));
+ char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
+ char buf[32]; /* don't change this */
+ Secret *secr=NULL;
+ SecretCache *dbcache;
+ int cached = 0;
+ char *err;
+
+ errno = 0;
+
+ if ((mode != UNREALDB_MODE_READ) && (mode != UNREALDB_MODE_WRITE))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_API, "unrealdb_open request for neither read nor write");
+ goto unrealdb_open_fail;
+ }
+
+ /* Do this check early, before we try to create any file */
+ if (secret_block != NULL)
+ {
+ secr = find_secret(secret_block);
+ if (!secr)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Secret block '%s' not found or invalid", secret_block);
+ goto unrealdb_open_fail;
+ }
+
+ if (!valid_secret_password(secr->password, &err))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_SECRET, "Password in secret block '%s' does not meet complexity requirements", secr->name);
+ goto unrealdb_open_fail;
+ }
+ }
+
+ c->mode = mode;
+ c->fd = fopen(filename, (c->mode == UNREALDB_MODE_WRITE) ? "wb" : "rb");
+ if (!c->fd)
+ {
+ if (errno == ENOENT)
+ unrealdb_set_error(c, UNREALDB_ERROR_FILENOTFOUND, "File not found: %s", strerror(errno));
+ else
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Could not open file: %s", strerror(errno));
+ goto unrealdb_open_fail;
+ }
+
+ if (secret_block == NULL)
+ {
+ if (mode == UNREALDB_MODE_READ)
+ {
+ /* READ: read header, if any, lots of fallback options here... */
+ if (fgets(buf, sizeof(buf), c->fd))
+ {
+ if (!strncmp(buf, "UnrealIRCd-DB-Crypted", 21))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_CRYPTED, "file is encrypted but no password provided");
+ goto unrealdb_open_fail;
+ } else
+ if (!strcmp(buf, "UnrealIRCd-DB-v1"))
+ {
+ /* Skip over the 32 byte header, directly to the creationtime */
+ if (fseek(c->fd, 32L, SEEK_SET) < 0)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "file header too short");
+ goto unrealdb_open_fail;
+ }
+ if (!unrealdb_read_int64(c, &c->creationtime))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (A4)");
+ goto unrealdb_open_fail;
+ }
+ /* SUCCESS = fallthrough */
+ } else
+ if (!strncmp(buf, "UnrealIRCd-DB", 13)) /* any other version than v1 = not supported by us */
+ {
+ /* We don't support this format, so refuse clearly */
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER,
+ "Unsupported version of database. Is this database perhaps created on "
+ "a new version of UnrealIRCd and are you trying to use it on an older "
+ "UnrealIRCd version? (Downgrading is not supported!)");
+ goto unrealdb_open_fail;
+ } else
+ {
+ /* Old db format, no header, seek back to beginning */
+ fseek(c->fd, 0L, SEEK_SET);
+ /* SUCCESS = fallthrough */
+ }
+ }
+ } else {
+#ifdef UNREALDB_WRITE_V1
+ /* WRITE */
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), "UnrealIRCd-DB-v1");
+ if ((fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf)) ||
+ !unrealdb_write_int64(c, TStime()))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (A1)");
+ goto unrealdb_open_fail;
+ }
+#endif
+ }
+ safe_free(unrealdb_last_error_string);
+ unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
+ return c;
+ }
+
+ c->crypted = 1;
+
+ if (c->mode == UNREALDB_MODE_WRITE)
+ {
+ /* Write the:
+ * - generic header ("UnrealIRCd-DB" + some zeroes)
+ * - the salt
+ * - the crypto header
+ */
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), "UnrealIRCd-DB-Crypted-v1");
+ if (fwrite(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (1)");
+ goto unrealdb_open_fail; /* Unable to write header nr 1 */
+ }
+
+ if (secr->cache && secr->cache->config)
+ {
+ /* Use first found cached config for this secret */
+ c->config = unrealdb_copy_config(secr->cache->config);
+ cached = 1;
+ } else {
+ /* Create a new config */
+ c->config = safe_alloc(sizeof(UnrealDBConfig));
+ c->config->kdf = UNREALDB_KDF_ARGON2ID;
+ c->config->t_cost = UNREALDB_ARGON2_DEFAULT_TIME_COST;
+ c->config->m_cost = UNREALDB_ARGON2_DEFAULT_MEMORY_COST;
+ c->config->p_cost = UNREALDB_ARGON2_DEFAULT_PARALLELISM_COST;
+ c->config->saltlen = UNREALDB_SALT_LEN;
+ c->config->salt = safe_alloc(c->config->saltlen);
+ randombytes_buf(c->config->salt, c->config->saltlen);
+ c->config->cipher = UNREALDB_CIPHER_XCHACHA20;
+ c->config->keylen = UNREALDB_KEY_LEN;
+ c->config->key = safe_alloc_sensitive(c->config->keylen);
+ }
+
+ if (c->config->kdf == 0)
+ abort();
+
+ /* Write KDF and cipher parameters */
+ if ((fwrite(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
+ (fwrite(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
+ (fwrite(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
+ (fwrite(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
+ (fwrite(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)) ||
+ (fwrite(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen) ||
+ (fwrite(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
+ (fwrite(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (2)");
+ goto unrealdb_open_fail;
+ }
+
+ if (cached)
+ {
+#ifdef DEBUGMODE
+ ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Cache hit for '%s' while writing", secr->name);
+#endif
+ } else
+ {
+#ifdef DEBUGMODE
+ ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Need to run argon2 '%s' while writing", secr->name);
+#endif
+ if (!unrealdb_kdf(c, secr))
+ {
+ /* Error already set by called function */
+ goto unrealdb_open_fail;
+ }
+ }
+
+ crypto_secretstream_xchacha20poly1305_init_push(&c->st, header, c->config->key);
+ if (fwrite(header, 1, sizeof(header), c->fd) != sizeof(header))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Unable to write header (3)");
+ goto unrealdb_open_fail; /* Unable to write crypto header */
+ }
+ if (!unrealdb_write_str(c, "UnrealIRCd-DB-Crypted-Now") ||
+ !unrealdb_write_int64(c, TStime()))
+ {
+ /* error is already set by unrealdb_write_str() */
+ goto unrealdb_open_fail; /* Unable to write crypto header */
+ }
+ if (!cached)
+ unrealdb_add_to_secret_cache(secr, c->config);
+ } else
+ {
+ char *validate = NULL;
+
+ /* Read file header */
+ if (fread(buf, 1, sizeof(buf), c->fd) != sizeof(buf))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file (file too small)");
+ goto unrealdb_open_fail; /* Header too short */
+ }
+ if (strncmp(buf, "UnrealIRCd-DB-Crypted-v1", 24))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_NOTCRYPTED, "Not a crypted file");
+ goto unrealdb_open_fail; /* Invalid header */
+ }
+ c->config = safe_alloc(sizeof(UnrealDBConfig));
+ if ((fread(&c->config->kdf, 1, sizeof(c->config->kdf), c->fd) != sizeof(c->config->kdf)) ||
+ (fread(&c->config->t_cost, 1, sizeof(c->config->t_cost), c->fd) != sizeof(c->config->t_cost)) ||
+ (fread(&c->config->m_cost, 1, sizeof(c->config->m_cost), c->fd) != sizeof(c->config->m_cost)) ||
+ (fread(&c->config->p_cost, 1, sizeof(c->config->p_cost), c->fd) != sizeof(c->config->p_cost)) ||
+ (fread(&c->config->saltlen, 1, sizeof(c->config->saltlen), c->fd) != sizeof(c->config->saltlen)))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid");
+ goto unrealdb_open_fail;
+ }
+ if (c->config->kdf != UNREALDB_KDF_ARGON2ID)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown KDF 0x%x", (int)c->config->kdf);
+ goto unrealdb_open_fail;
+ }
+ if (c->config->saltlen > 1024)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (saltlen=%d)", (int)c->config->saltlen);
+ goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
+ }
+ c->config->salt = safe_alloc(c->config->saltlen);
+ if (fread(c->config->salt, 1, c->config->saltlen, c->fd) != c->config->saltlen)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (2)");
+ goto unrealdb_open_fail; /* Header too short (read II) */
+ }
+ if ((fread(&c->config->cipher, 1, sizeof(c->config->cipher), c->fd) != sizeof(c->config->cipher)) ||
+ (fread(&c->config->keylen, 1, sizeof(c->config->keylen), c->fd) != sizeof(c->config->keylen)))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt/unknown/invalid (3)");
+ goto unrealdb_open_fail;
+ }
+ if (c->config->cipher != UNREALDB_CIPHER_XCHACHA20)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header contains unknown cipher 0x%x", (int)c->config->cipher);
+ goto unrealdb_open_fail;
+ }
+ if (c->config->keylen > 1024)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is corrupt (keylen=%d)", (int)c->config->keylen);
+ goto unrealdb_open_fail; /* Something must be wrong, this makes no sense. */
+ }
+ c->config->key = safe_alloc_sensitive(c->config->keylen);
+
+ dbcache = find_secret_cache(secr, c->config);
+ if (dbcache)
+ {
+ /* Use cached key, no need to run expensive argon2.. */
+ memcpy(c->config->key, dbcache->config->key, c->config->keylen);
+#ifdef DEBUGMODE
+ ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Cache hit for '%s' while reading", secr->name);
+#endif
+ } else {
+#ifdef DEBUGMODE
+ ircd_log(LOG_ERROR, "[UnrealDB] unrealdb_open(): Need to run argon2 for '%s' while reading", secr->name);
+#endif
+ if (!unrealdb_kdf(c, secr))
+ {
+ /* Error already set by called function */
+ goto unrealdb_open_fail;
+ }
+ }
+ /* key is now set */
+ if (fread(header, 1, sizeof(header), c->fd) != sizeof(header))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (3)");
+ goto unrealdb_open_fail; /* Header too short */
+ }
+ if (crypto_secretstream_xchacha20poly1305_init_pull(&c->st, header, c->config->key) != 0)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Crypto error - invalid password or corrupt file");
+ goto unrealdb_open_fail; /* Unusual */
+ }
+ /* Now to validate the key we read a simple string */
+ if (!unrealdb_read_str(c, &validate))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
+ goto unrealdb_open_fail; /* Incorrect key, probably */
+ }
+ if (strcmp(validate, "UnrealIRCd-DB-Crypted-Now"))
+ {
+ safe_free(validate);
+ unrealdb_set_error(c, UNREALDB_ERROR_PASSWORD, "Invalid password");
+ goto unrealdb_open_fail; /* Incorrect key, probably */
+ }
+ safe_free(validate);
+ if (!unrealdb_read_int64(c, &c->creationtime))
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_HEADER, "Header is too short (4)");
+ goto unrealdb_open_fail;
+ }
+ unrealdb_add_to_secret_cache(secr, c->config);
+ }
+ sodium_stackzero(1024);
+ safe_free(unrealdb_last_error_string);
+ unrealdb_last_error_code = UNREALDB_ERROR_SUCCESS;
+ return c;
+
+unrealdb_open_fail:
+ if (c->fd)
+ fclose(c->fd);
+ unrealdb_free(c);
+ sodium_stackzero(1024);
+ return NULL;
+}
+
+/** Close an unrealdb file.
+ * @param c The struct pointing to an unrealdb file
+ * @returns 1 if the final close was graceful and 0 if not (eg: out of disk space on final flush).
+ * In all cases the file handle is closed and 'c' is freed.
+ * @note Upon error (NULL return value) you can call unrealdb_get_error_code() and
+ * unrealdb_get_error_string() to see the actual error.
+ */
+int unrealdb_close(UnrealDB *c)
+{
+ /* If this is file was opened for writing then flush the remaining data with a TAG_FINAL
+ * (or push a block of 0 bytes with TAG_FINAL)
+ */
+ if (c->crypted && (c->mode == UNREALDB_MODE_WRITE))
+ {
+ char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
+ unsigned long long out_len = sizeof(buf_out);
+
+ crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, c->buflen, NULL, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL);
+ if (out_len > 0)
+ {
+ if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
+ {
+ /* Final write failed, error condition */
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+ fclose(c->fd);
+ unrealdb_free(c);
+ return 0;
+ }
+ }
+ }
+
+ if (fclose(c->fd) != 0)
+ {
+ /* Final close failed, error condition */
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+ unrealdb_free(c);
+ return 0;
+ }
+
+ unrealdb_free(c);
+ return 1;
+}
+
+/** Test if there is something fatally wrong with the configuration of the DB file,
+ * in which case we suggest to reject the /rehash or boot request.
+ * This tests for "wrong password" and for "trying to open an encrypted file without providing a password"
+ * which are clear configuration errors on the admin part.
+ * It does NOT test for any other conditions such as missing file, corrupted file, etc.
+ * since that usually needs different handling anyway, as they are I/O issues and don't
+ * always have a clear solution (if any is needed at all).
+ * @param filename The filename to open
+ * @param secret_block The name of the secret xx { } block (so NOT the actual password!!)
+ * @returns 1 if the password was wrong, 0 for any other error or succes.
+ */
+char *unrealdb_test_db(const char *filename, char *secret_block)
+{
+ static char buf[512];
+ UnrealDB *db = unrealdb_open(filename, UNREALDB_MODE_READ, secret_block);
+ if (!db)
+ {
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_PASSWORD)
+ {
+ snprintf(buf, sizeof(buf), "Incorrect password specified in secret block '%s' for file %s",
+ secret_block, filename);
+ return buf;
+ }
+ if (unrealdb_get_error_code() == UNREALDB_ERROR_CRYPTED)
+ {
+ snprintf(buf, sizeof(buf), "File '%s' is encrypted but no secret block provided for it",
+ filename);
+ return buf;
+ }
+ return NULL;
+ } else
+ {
+ unrealdb_close(db);
+ }
+ return NULL;
+}
+
+/** @} */
+
+/** Write to an unrealdb file.
+ * This code uses extra buffering to avoid writing small records
+ * and wasting for example a 32 bytes encryption block for a 8 byte write request.
+ * @param c Database file open for writing
+ * @param wbuf The data to be written (plaintext)
+ * @param len The length of the data to be written
+ * @note This is the internal function, api users must use one of the
+ * following functions instead:
+ * unrealdb_write_int64(), unrealdb_write_int32(), unrealdb_write_int16(),
+ * unrealdb_write_char(), unrealdb_write_str().
+ */
+static int unrealdb_write(UnrealDB *c, void *wbuf, int len)
+{
+ char buf_out[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
+ unsigned long long out_len;
+ char *buf = wbuf;
+
+ if (c->error_code)
+ return 0;
+
+ if (c->mode != UNREALDB_MODE_WRITE)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_API, "Write operation requested on a file opened for reading");
+ return 0;
+ }
+
+ if (!c->crypted)
+ {
+ if (fwrite(buf, 1, len, c->fd) != len)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+ return 0;
+ }
+ return 1;
+ }
+
+ do {
+ if (c->buflen + len < UNREALDB_CRYPT_FILE_CHUNK_SIZE)
+ {
+ /* New data fits in new buffer. Then we are done with writing.
+ * This can happen both for the first block (never write)
+ * or the remainder (tail after X writes which is less than
+ * UNREALDB_CRYPT_FILE_CHUNK_SIZE, a common case)
+ */
+ memcpy(c->buf + c->buflen, buf, len);
+ c->buflen += len;
+ break; /* Done! */
+ } else
+ {
+ /* Fill up c->buf with UNREALDB_CRYPT_FILE_CHUNK_SIZE
+ * Note that 'av_bytes' can be 0 here if c->buflen
+ * happens to be exactly UNREALDB_CRYPT_FILE_CHUNK_SIZE,
+ * that's okay.
+ */
+ int av_bytes = UNREALDB_CRYPT_FILE_CHUNK_SIZE - c->buflen;
+ if (av_bytes > 0)
+ memcpy(c->buf + c->buflen, buf, av_bytes);
+ buf += av_bytes;
+ len -= av_bytes;
+ }
+ if (crypto_secretstream_xchacha20poly1305_push(&c->st, buf_out, &out_len, c->buf, UNREALDB_CRYPT_FILE_CHUNK_SIZE, NULL, 0, 0) != 0)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_INTERNAL, "Failed to encrypt a block");
+ return 0;
+ }
+ if (fwrite(buf_out, 1, out_len, c->fd) != out_len)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Write error: %s", strerror(errno));
+ return 0;
+ }
+ /* Buffer is now flushed for sure */
+ c->buflen = 0;
+ } while(len > 0);
+
+ return 1;
+}
+
+/**
+ * @addtogroup UnrealDBFunctions
+ * @{
+ */
+
+/** Write a string to a database file.
+ * @param c UnrealDB file struct
+ * @param x String to be written
+ * @note This function can write a string up to 65534
+ * characters, which should be plenty for usage
+ * in UnrealIRCd.
+ * Note that 'x' can safely be NULL.
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_str(UnrealDB *c, char *x)
+{
+ uint16_t len;
+
+ /* First, make sure the string is not too large (would be very unusual, though) */
+ if (x)
+ {
+ int stringlen = strlen(x);
+ if (stringlen >= 0xffff)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_API,
+ "unrealdb_write_str(): string has length %d, while maximum allowed is 65534",
+ stringlen);
+ return 0;
+ }
+ len = stringlen;
+ } else {
+ len = 0xffff;
+ }
+
+ /* Write length to db as 16 bit integer */
+ if (!unrealdb_write_int16(c, len))
+ return 0;
+
+ /* Then, write the actual string (if any), without NUL terminator. */
+ if ((len > 0) && (len < 0xffff))
+ {
+ if (!unrealdb_write(c, x, len))
+ return 0;
+ }
+
+ return 1;
+}
+
+/** Write a 64 bit integer to a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_int64(UnrealDB *c, uint64_t t)
+{
+#ifdef NATIVE_BIG_ENDIAN
+ t = bswap_64(t);
+#endif
+ return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** Write a 32 bit integer to a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_int32(UnrealDB *c, uint32_t t)
+{
+#ifdef NATIVE_BIG_ENDIAN
+ t = bswap_32(t);
+#endif
+ return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** Write a 16 bit integer to a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_int16(UnrealDB *c, uint16_t t)
+{
+#ifdef NATIVE_BIG_ENDIAN
+ t = bswap_16(t);
+#endif
+ return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** Write a single 8 bit character to a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to write
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_write_char(UnrealDB *c, char t)
+{
+ return unrealdb_write(c, &t, sizeof(t));
+}
+
+/** @} */
+
+/** Read from an UnrealDB file.
+ * This code deals with buffering, block reading, etc. so the caller doesn't
+ * have to worry about that.
+ * @param c Database file open for reading
+ * @param rbuf The data to be read (will be plaintext)
+ * @param len The length of the data to be read
+ * @note This is the internal function, api users must use one of the
+ * following functions instead:
+ * unrealdb_read_int64(), unrealdb_read_int32(), unrealdb_read_int16(),
+ * unrealdb_read_char(), unrealdb_read_str().
+ */
+static int unrealdb_read(UnrealDB *c, void *rbuf, int len)
+{
+ char buf_in[UNREALDB_CRYPT_FILE_CHUNK_SIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
+ unsigned long long out_len;
+ unsigned char tag;
+ size_t rlen;
+ char *buf = rbuf;
+
+ if (c->error_code)
+ return 0;
+
+ if (c->mode != UNREALDB_MODE_READ)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_API, "Read operation requested on a file opened for writing");
+ return 0;
+ }
+
+ if (!c->crypted)
+ {
+ rlen = fread(buf, 1, len, c->fd);
+ if (rlen < len)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file (want:%d, got:%d bytes)",
+ len, (int)rlen);
+ return 0;
+ }
+ return 1;
+ }
+
+ /* First, fill 'buf' up with what we have */
+ if (c->buflen)
+ {
+ int av_bytes = MIN(c->buflen, len);
+ memcpy(buf, c->buf, av_bytes);
+ if (c->buflen - av_bytes > 0)
+ memmove(c->buf, c->buf + av_bytes, c->buflen - av_bytes);
+ c->buflen -= av_bytes;
+ len -= av_bytes;
+ if (len == 0)
+ return 1; /* Request completed entirely */
+ buf += av_bytes;
+ }
+
+ if (c->buflen != 0)
+ abort();
+
+ /* If we get here then we need to read some data */
+ do {
+ rlen = fread(buf_in, 1, sizeof(buf_in), c->fd);
+ if (rlen == 0)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file??");
+ return 0;
+ }
+ if (crypto_secretstream_xchacha20poly1305_pull(&c->st, c->buf, &out_len, &tag, buf_in, rlen, NULL, 0) != 0)
+ {
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Failed to decrypt a block - either corrupt or wrong key");
+ return 0;
+ }
+
+ /* This should be impossible as this is guaranteed not to happen by libsodium */
+ if (out_len > UNREALDB_CRYPT_FILE_CHUNK_SIZE)
+ abort();
+
+ if (len > out_len)
+ {
+ /* We eat a big block, but want more in next iteration of the loop */
+ memcpy(buf, c->buf, out_len);
+ buf += out_len;
+ len -= out_len;
+ } else {
+ /* This is the only (or last) block we need, we are satisfied */
+ memcpy(buf, c->buf, len);
+ c->buflen = out_len - len;
+ if (c->buflen > 0)
+ memmove(c->buf, c->buf+len, c->buflen);
+ return 1; /* Done */
+ }
+ } while(!feof(c->fd));
+
+ unrealdb_set_error(c, UNREALDB_ERROR_IO, "Short read - premature end of file?");
+ return 0;
+}
+
+/**
+ * @addtogroup UnrealDBFunctions
+ * @{
+ */
+
+/** Read a 64 bit integer from a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_int64(UnrealDB *c, uint64_t *t)
+{
+ if (!unrealdb_read(c, t, sizeof(uint64_t)))
+ return 0;
+#ifdef NATIVE_BIG_ENDIAN
+ *t = bswap_64(*t);
+#endif
+ return 1;
+}
+
+/** Read a 32 bit integer from a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_int32(UnrealDB *c, uint32_t *t)
+{
+ if (!unrealdb_read(c, t, sizeof(uint32_t)))
+ return 0;
+#ifdef NATIVE_BIG_ENDIAN
+ *t = bswap_32(*t);
+#endif
+ return 1;
+}
+
+/** Read a 16 bit integer from a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_int16(UnrealDB *c, uint16_t *t)
+{
+ if (!unrealdb_read(c, t, sizeof(uint16_t)))
+ return 0;
+#ifdef NATIVE_BIG_ENDIAN
+ *t = bswap_16(*t);
+#endif
+ return 1;
+}
+
+/** Read a string from a database file.
+ * @param c UnrealDB file struct
+ * @param x Pointer to string pointer
+ * @note This function will allocate memory for the data
+ * and set the string pointer to this value.
+ * If a NULL pointer was written via write_str()
+ * then read_str() may also return a NULL pointer.
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_str(UnrealDB *c, char **x)
+{
+ uint16_t len;
+ size_t size;
+
+ *x = NULL;
+
+ if (!unrealdb_read_int16(c, &len))
+ return 0;
+
+ if (len == 0xffff)
+ {
+ /* Magic value meaning NULL */
+ *x = NULL;
+ return 1;
+ }
+
+ if (len == 0)
+ {
+ /* 0 means empty string */
+ safe_strdup(*x, "");
+ return 1;
+ }
+
+ if (len > 10000)
+ return 0;
+
+ size = len;
+ *x = safe_alloc(size + 1);
+ if (!unrealdb_read(c, *x, size))
+ {
+ safe_free(*x);
+ return 0;
+ }
+ (*x)[len] = 0;
+ return 1;
+}
+
+/** Read a single 8 bit character from a database file.
+ * @param c UnrealDB file struct
+ * @param t The value to read
+ * @returns 1 on success, 0 on failure.
+ */
+int unrealdb_read_char(UnrealDB *c, char *t)
+{
+ if (!unrealdb_read(c, t, sizeof(char)))
+ return 0;
+ return 1;
+}
+
+/** @} */
+
+void fatal_error(FORMAT_STRING(const char *pattern), ...)
+{
+ va_list vl;
+ va_start(vl, pattern);
+ vfprintf(stderr, pattern, vl);
+ va_end(vl);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "Exiting with failure\n");
+ exit(-1);
+}
+
+void unrealdb_test_simple(void)
+{
+ UnrealDB *c;
+ char *key = "test";
+ int i;
+ char *str;
+
+
+ fprintf(stderr, "*** WRITE TEST ***\n");
+ c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
+ if (!c)
+ fatal_error("Could not open test db for writing: %s", strerror(errno));
+
+ if (!unrealdb_write_str(c, "Hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
+ fatal_error("Error on write 1");
+ if (!unrealdb_write_str(c, "This is a test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))
+ fatal_error("Error on write 2");
+ if (!unrealdb_close(c))
+ fatal_error("Error on close");
+ c = NULL;
+ fprintf(stderr, "Done with writing.\n\n");
+
+ fprintf(stderr, "*** READ TEST ***\n");
+ c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
+ if (!c)
+ fatal_error("Could not open test db for reading: %s", strerror(errno));
+ if (!unrealdb_read_str(c, &str))
+ fatal_error("Error on read 1: %s", c->error_string);
+ fprintf(stderr, "Got: '%s'\n", str);
+ safe_free(str);
+ if (!unrealdb_read_str(c, &str))
+ fatal_error("Error on read 2: %s", c->error_string);
+ fprintf(stderr, "Got: '%s'\n", str);
+ safe_free(str);
+ if (!unrealdb_close(c))
+ fatal_error("Error on close");
+ fprintf(stderr, "All good.\n");
+}
+
+#define UNREALDB_SPEED_TEST_BYTES 100000000
+void unrealdb_test_speed(char *key)
+{
+ UnrealDB *c;
+ int i, len;
+ char *str;
+ char buf[1024];
+ int written = 0, read = 0;
+ struct timeval tv_start, tv_end;
+
+ fprintf(stderr, "*** WRITE TEST ***\n");
+ gettimeofday(&tv_start, NULL);
+ c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_WRITE, key);
+ if (!c)
+ fatal_error("Could not open test db for writing: %s", strerror(errno));
+ do {
+
+ len = getrandom32() % 500;
+ //gen_random_alnum(buf, len);
+ for (i=0; i < len; i++)
+ buf[i] = 'a';
+ buf[i] = '\0';
+ if (!unrealdb_write_str(c, buf))
+ fatal_error("Error on writing a string of %d size", len);
+ written += len + 2; /* +2 for length */
+ } while(written < UNREALDB_SPEED_TEST_BYTES);
+ if (!unrealdb_close(c))
+ fatal_error("Error on close");
+ c = NULL;
+ gettimeofday(&tv_end, NULL);
+ fprintf(stderr, "Done with writing: %lld usecs\n\n",
+ (long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
+
+ fprintf(stderr, "*** READ TEST ***\n");
+ gettimeofday(&tv_start, NULL);
+ c = unrealdb_open("/tmp/test.db", UNREALDB_MODE_READ, key);
+ if (!c)
+ fatal_error("Could not open test db for reading: %s", strerror(errno));
+ do {
+ if (!unrealdb_read_str(c, &str))
+ fatal_error("Error on read at position %d/%d: %s", read, written, c->error_string);
+ read += strlen(str) + 2; /* same calculation as earlier */
+ safe_free(str);
+ } while(read < written);
+ if (!unrealdb_close(c))
+ fatal_error("Error on close");
+ gettimeofday(&tv_end, NULL);
+ fprintf(stderr, "Done with reading: %lld usecs\n\n",
+ (long long)(((tv_end.tv_sec - tv_start.tv_sec) * 1000000) + (tv_end.tv_usec - tv_start.tv_usec)));
+
+ fprintf(stderr, "All good.\n");
+}
+
+void unrealdb_test(void)
+{
+ //unrealdb_test_simple();
+ fprintf(stderr, "**** TESTING ENCRYPTED ****\n");
+ unrealdb_test_speed("test");
+ fprintf(stderr, "**** TESTING UNENCRYPTED ****\n");
+ unrealdb_test_speed(NULL);
+}
+
+/** TODO: document and implement
+ */
+char *unrealdb_test_secret(char *name)
+{
+ // FIXME: check if exists, if not then return an error, with a nice FAQ reference etc.
+ return NULL; /* no error */
+}
+
+UnrealDBConfig *unrealdb_copy_config(UnrealDBConfig *src)
+{
+ UnrealDBConfig *dst = safe_alloc(sizeof(UnrealDBConfig));
+
+ dst->kdf = src->kdf;
+ dst->t_cost = src->t_cost;
+ dst->m_cost = src->m_cost;
+ dst->p_cost = src->p_cost;
+ dst->saltlen = src->saltlen;
+ dst->salt = safe_alloc(dst->saltlen);
+ memcpy(dst->salt, src->salt, dst->saltlen);
+
+ dst->cipher = src->cipher;
+ dst->keylen = src->keylen;
+ if (dst->keylen)
+ {
+ dst->key = safe_alloc_sensitive(dst->keylen);
+ memcpy(dst->key, src->key, dst->keylen);
+ }
+
+ return dst;
+}
+
+UnrealDBConfig *unrealdb_get_config(UnrealDB *db)
+{
+ return unrealdb_copy_config(db->config);
+}
+
+void unrealdb_free_config(UnrealDBConfig *c)
+{
+ if (!c)
+ return;
+ safe_free(c->salt);
+ safe_free_sensitive(c->key);
+ safe_free(c);
+}
+
+static int unrealdb_config_identical(UnrealDBConfig *one, UnrealDBConfig *two)
+{
+ /* NOTE: do not compare 'key' here or all cache lookups will fail */
+ if ((one->kdf == two->kdf) &&
+ (one->t_cost == two->t_cost) &&
+ (one->m_cost == two->m_cost) &&
+ (one->p_cost == two->p_cost) &&
+ (one->saltlen == two->saltlen) &&
+ (memcmp(one->salt, two->salt, one->saltlen) == 0) &&
+ (one->cipher == two->cipher) &&
+ (one->keylen == two->keylen))
+ {
+ return 1;
+ }
+ return 0;
+}
+
+static SecretCache *find_secret_cache(Secret *secr, UnrealDBConfig *cfg)
+{
+ SecretCache *c;
+
+ for (c = secr->cache; c; c = c->next)
+ {
+ if (unrealdb_config_identical(c->config, cfg))
+ {
+ c->cache_hit = TStime();
+ return c;
+ }
+ }
+ return NULL;
+}
+
+static void unrealdb_add_to_secret_cache(Secret *secr, UnrealDBConfig *cfg)
+{
+ SecretCache *c = find_secret_cache(secr, cfg);
+
+ if (c)
+ return; /* Entry already exists in cache */
+
+ /* New entry, add! */
+ c = safe_alloc(sizeof(SecretCache));
+ c->config = unrealdb_copy_config(cfg);
+ c->cache_hit = TStime();
+ AddListItem(c, secr->cache);
+}
+
+#ifdef DEBUGMODE
+#define UNREALDB_EXPIRE_SECRET_CACHE_AFTER 1200
+#else
+#define UNREALDB_EXPIRE_SECRET_CACHE_AFTER 86400
+#endif
+
+/** Expire cached secret entries (previous Argon2 runs) */
+EVENT(unrealdb_expire_secret_cache)
+{
+ Secret *s;
+ SecretCache *c, *c_next;
+ for (s = secrets; s; s = s->next)
+ {
+ for (c = s->cache; c; c = c_next)
+ {
+ c_next = c->next;
+ if (c->cache_hit < TStime() - UNREALDB_EXPIRE_SECRET_CACHE_AFTER)
+ {
+ DelListItem(c, s->cache);
+ free_secret_cache(c);
+ }
+ }
+ }
+}
diff --git a/src/user.c b/src/user.c
index f809c25..f8bd291 100644
--- a/src/user.c
+++ b/src/user.c
@@ -142,6 +142,8 @@ int target_limit_exceeded(Client *client, void *target, const char *name)
{
u_char hash = hash_target(target);
int i;
+ int max_concurrent_conversations_users, max_concurrent_conversations_new_user_every;
+ FloodSettings *settings;
if (ValidatePermissionsForPath("immune:max-concurrent-conversations",client,NULL,NULL,NULL))
return 0;
@@ -149,7 +151,18 @@ int target_limit_exceeded(Client *client, void *target, const char *name)
if (client->local->targets[0] == hash)
return 0;
- for (i = 1; i < iConf.max_concurrent_conversations_users; i++)
+ settings = get_floodsettings_for_user(client, FLD_CONVERSATIONS);
+ max_concurrent_conversations_users = settings->limit[FLD_CONVERSATIONS];
+ max_concurrent_conversations_new_user_every = settings->period[FLD_CONVERSATIONS];
+
+ if (max_concurrent_conversations_users <= 0)
+ return 0; /* unlimited */
+
+ /* Shouldn't be needed, but better check here than access out-of-bounds memory */
+ if (max_concurrent_conversations_users > MAXCCUSERS)
+ max_concurrent_conversations_users = MAXCCUSERS;
+
+ for (i = 1; i < max_concurrent_conversations_users; i++)
{
if (client->local->targets[i] == hash)
{
@@ -166,6 +179,7 @@ int target_limit_exceeded(Client *client, void *target, const char *name)
client->local->nexttarget += 2; /* punish them some more */
client->local->since += 2; /* lag them up as well */
+ flood_limit_exceeded_log(client, "max-concurrent-conversations");
sendnumeric(client, ERR_TARGETTOOFAST, name, client->local->nexttarget - TStime());
return 1;
@@ -175,15 +189,15 @@ int target_limit_exceeded(Client *client, void *target, const char *name)
* This is so client->local->nexttarget=0 will become client->local->nexttarget=currenttime-...
*/
if (TStime() > client->local->nexttarget +
- (iConf.max_concurrent_conversations_users * iConf.max_concurrent_conversations_new_user_every))
+ (max_concurrent_conversations_users * max_concurrent_conversations_new_user_every))
{
- client->local->nexttarget = TStime() - ((iConf.max_concurrent_conversations_users-1) * iConf.max_concurrent_conversations_new_user_every);
+ client->local->nexttarget = TStime() - ((max_concurrent_conversations_users-1) * max_concurrent_conversations_new_user_every);
}
- client->local->nexttarget += iConf.max_concurrent_conversations_new_user_every;
+ client->local->nexttarget += max_concurrent_conversations_new_user_every;
/* Add the new target (first move the rest, then add us at position 0 */
- memmove(&client->local->targets[1], &client->local->targets[0], iConf.max_concurrent_conversations_users - 1);
+ memmove(&client->local->targets[1], &client->local->targets[0], max_concurrent_conversations_users - 1);
client->local->targets[0] = hash;
return 0;
@@ -698,6 +712,12 @@ void make_cloakedhost(Client *client, char *curr, char *buf, size_t buflen)
/** Called after a user is logged in (or out) of a services account */
void user_account_login(MessageTag *recv_mtags, Client *client)
{
+ if (MyConnect(client))
+ {
+ find_shun(client);
+ if (find_tkline_match(client, 0) && IsDead(client))
+ return;
+ }
RunHook2(HOOKTYPE_ACCOUNT_LOGIN, client, recv_mtags);
}
@@ -811,6 +831,10 @@ void set_security_group_defaults(void)
}
securitygroups = NULL;
+ /* Default group: webirc */
+ s = add_security_group("webirc-users", 50);
+ s->webirc = 1;
+
/* Default group: known-users */
s = add_security_group("known-users", 100);
s->identified = 1;
@@ -919,3 +943,110 @@ char *get_connect_extinfo(Client *client)
return retbuf;
}
+
+/** Log a message that flood protection kicked in for the client.
+ * This sends to the +f snomask at the moment.
+ * @param client The client to check flood for (local user)
+ * @param opt The flood option (eg FLD_AWAY)
+ */
+void flood_limit_exceeded_log(Client *client, char *floodname)
+{
+ char buf[1024];
+
+ snprintf(buf, sizeof(buf), "Flood blocked (%s) from %s!%s@%s [%s]",
+ floodname,
+ client->name,
+ client->user->username,
+ client->user->realhost,
+ GetIP(client));
+ ircd_log(LOG_FLOOD, "%s", buf);
+ sendto_snomask_global(SNO_FLOOD, "%s", buf);
+}
+
+/** Is the flood limit exceeded for an option? eg for away-flood.
+ * @param client The client to check flood for (local user)
+ * @param opt The flood option (eg FLD_AWAY)
+ * @note This increments the flood counter as well.
+ * @returns 1 if exceeded, 0 if not.
+ */
+int flood_limit_exceeded(Client *client, FloodOption opt)
+{
+ FloodSettings *f;
+
+ if (!MyUser(client))
+ return 0;
+
+ f = get_floodsettings_for_user(client, opt);
+ if (f->limit[opt] <= 0)
+ return 0; /* No limit set or unlimited */
+
+ ircd_log(LOG_ERROR, "Checking flood_limit_exceeded() for '%s', type %d with max %d:%ld...",
+ client->name, (int)opt, (int)f->limit[opt], (long)f->period[opt]);
+
+ /* Ok, let's do the flood check */
+ if ((client->local->flood[opt].t + f->period[opt]) <= timeofday)
+ {
+ /* Time exceeded, reset */
+ client->local->flood[opt].count = 0;
+ client->local->flood[opt].t = timeofday;
+ }
+ if (client->local->flood[opt].count <= f->limit[opt])
+ client->local->flood[opt].count++;
+ if (client->local->flood[opt].count > f->limit[opt])
+ {
+ flood_limit_exceeded_log(client, floodoption_names[opt]);
+ return 1; /* Flood limit hit! */
+ }
+
+ return 0;
+}
+
+/** Get the appropriate anti-flood settings block for this user.
+ * @param client The client, should be locally connected.
+ * @param opt The flood option we are interested in
+ * @returns The FloodSettings for this user, never returns NULL.
+ */
+FloodSettings *get_floodsettings_for_user(Client *client, FloodOption opt)
+{
+ SecurityGroup *s;
+ FloodSettings *f;
+
+ /* Go through all security groups by order of priority
+ * (eg: first "known-users", then "unknown-users").
+ * For each of these:
+ * - Check if a set::anti-flood::xxxx block exists for this group
+ * - Check if the limit is non-zero (eg there is any limit set)
+ * If any of these are false then we continue with next block
+ * that matches.
+ */
+
+ // XXX: alternatively, instead of this double loop,
+ // do a post-conf thing and sort iConf.floodsettings
+ // according to the security-group { } order.
+ for (s = securitygroups; s; s = s->next)
+ {
+ if (user_allowed_by_security_group(client, s) &&
+ ((f = find_floodsettings_block(s->name))) &&
+ f->limit[opt])
+ {
+ return f;
+ }
+ }
+
+ /* Return default settings block (which may have a zero limit set) */
+ f = find_floodsettings_block("unknown-users");
+ if (!f)
+ abort(); /* impossible */
+
+ return f;
+}
+
+MODVAR char *floodoption_names[] = {
+ "nick-flood",
+ "join-flood",
+ "away-flood",
+ "invite-flood",
+ "knock-flood",
+ "max-concurrent-conversations",
+ NULL
+};
diff --git a/src/version.c.SH b/src/version.c.SH
index f1fd09c..f499120 100644
--- 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.9"
+id="5.2.0.1"
echo "$id"
if test -r version.c
@@ -133,7 +133,8 @@ char *unrealcredits[] =
"Underfloor Heating Systems - www.underfloorheatingsystems.co.uk,",
"Project Free TV - newprojectfreetv.com, companyaccountscheck.com,",
"Coyote Traffic - www.coyotetraffic.com, www.emailextractor14lite.com,",
- "bet365 Bonus Code - williamspromocodes.co.uk/bet365-bonus-code-sports/",
+ "bet365 Bonus Code - williamspromocodes.co.uk/bet365-bonus-code-sports/,",
+ "Valerie Amelia Pond",
" ",
"==========================[ Supporters ]==========================",
"Our support staff: Cronus, Jobe, SpaceDog, Stealth",
diff --git a/src/windows/UnrealIRCd.exe.manifest b/src/windows/UnrealIRCd.exe.manifest
index 6018d8e..d6fb05d 100644
--- a/src/windows/UnrealIRCd.exe.manifest
+++ b/src/windows/UnrealIRCd.exe.manifest
@@ -3,7 +3,7 @@
Internet Relay Chat Daemon
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
index 1c609a4..c071af5 100644
--- a/src/windows/unrealinst.iss
+++ b/src/windows/unrealinst.iss
@@ -6,7 +6,7 @@
[Setup]
AppName=UnrealIRCd 5
-AppVerName=UnrealIRCd 5.0.9
+AppVerName=UnrealIRCd 5.2.0.1
AppPublisher=UnrealIRCd Team
AppPublisherURL=https://www.unrealircd.org
AppSupportURL=https://www.unrealircd.org
@@ -72,6 +72,7 @@ Source: "src\modules\third\*.dll"; DestDir: "{app}\modules\third"; Flags: ignore
Source: "c:\dev\unrealircd-5-libs\pcre2\bin\pcre*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "c:\dev\unrealircd-5-libs\argon2\vs2015\build\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
+Source: "c:\dev\unrealircd-5-libs\libsodium\bin\x64\Release\v142\dynamic\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "c:\dev\unrealircd-5-libs\c-ares\msvc\cares\dll-release\cares.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "c:\dev\unrealircd-5-libs\libressl\bin\openssl.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
Source: "c:\dev\unrealircd-5-libs\libressl\bin\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
diff --git a/unrealircd.in b/unrealircd.in
index 3aab393..ef03bb4 100644
--- a/unrealircd.in
+++ b/unrealircd.in
@@ -53,17 +53,34 @@ if [ "$1" = "start" ] ; then
# Now check if we need to create a crash report.
@BINDIR@/unrealircd -R
elif [ "$1" = "stop" ] ; then
- echo "Stopping UnrealIRCd"
+ echo -n "Stopping UnrealIRCd"
if [ ! -r $PID_FILE ] ; then
+ echo
echo "ERROR: UnrealIRCd is not running"
exit 1
fi
kill -15 `cat $PID_FILE`
if [ "$?" != 0 ]; then
+ echo
echo "ERROR: UnrealIRCd is not running"
exit 1
fi
- sleep 1
+ # Wait for UnrealIRCd to terminate, but wait 10 seconds max
+ n="0"
+ while [ "$n" -lt 10 ]
+ do
+ echo -n "."
+ if [ ! -r $PID_FILE ] ; then
+ break
+ fi
+ if ! kill -0 `cat $PID_FILE`; then
+ break
+ fi
+ n=`expr $n + 1`
+ sleep 1
+ done
+ echo
+ # In case it is still running, kill it for good.
if [ -r $PID_FILE ] ; then
kill -9 `cat $PID_FILE` 1>/dev/null 2>&1
fi
@@ -80,17 +97,7 @@ elif [ "$1" = "rehash" ] ; then
fi
elif [ "$1" = "restart" ] ; then
echo "Restarting UnrealIRCd"
- if [ ! -r $PID_FILE ] ; then
- echo "WARNING: UnrealIRCd was not running"
- else
- kill -15 `cat $PID_FILE`
- if [ "$?" != 0 ]; then
- echo "WARNING: UnrealIRCd was not running"
- else
- sleep 1
- kill -9 `cat $PID_FILE` 1>/dev/null 2>&1
- fi
- fi
+ $0 stop
$0 start
elif [ "$1" = "croncheck" ] ; then
if [ -r $PID_FILE ] ; then
@@ -212,7 +219,7 @@ __EOF__
echo '2. If you are using 3rd party modules we might request you'
echo ' to run without them and verify you still crash. This is'
echo ' to eleminate any loss of time due to bugs made by others'
- echo '3. Always use the latest UnrealIRCd version, we fix (crash)bugs'
+ echo '3. Use a reasonably recent UnrealIRCd version. We fix (crash)bugs'
echo ' all the time so your bug might as well be fixed already.'
echo ""
echo "Thanks!"