4
mirror of git://git.acid.vegas/unrealircd.git synced 2024-12-28 09:16:38 +00:00

Updated to 5.2.0.1

This commit is contained in:
Dionysus 2021-06-19 11:52:51 -04:00
parent 72c7ba196c
commit fc973f2667
Signed by: acidvegas
GPG Key ID: EF4B922DB85DC9DE
95 changed files with 7213 additions and 1880 deletions

27
Config
View File

@ -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
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"

View File

@ -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)

View File

@ -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)

1262
configure vendored

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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!

View File

@ -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
-----------------

View File

@ -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";

View File

@ -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 ";

View File

@ -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,8 +145,24 @@ set {
oper-message "Network operators must connect using an up-to-date SSL/TLS protocol or cipher";
}
anti-flood {
away-flood 3:300;
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;
}
}
known-users {
away-flood 3:300;
invite-flood 3:300;
join-flood 3:300;
knock-flood 3:300;
@ -151,18 +171,17 @@ set {
users 5;
new-user-every 60s;
}
#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;
}
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; }
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; }
max-storage-per-channel {
registered { lines 100; time 1d; }
unregistered { lines 50; time 12h; }
}
}
}
hide-idle-time { policy usermode; }
@ -207,3 +229,10 @@ hideserver {
map-deny-message "Denied";
links-deny-message "Denied";
}
security-group known-users {
identified yes;
webirc no;
tls no;
reputation-score 100;
}

View File

@ -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

View File

@ -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

View File

@ -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" %*

View File

@ -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

View File

@ -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

BIN
extras/libsodium.tar.gz Normal file

Binary file not shown.

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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 <sodium.h>
/* 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)
@ -904,6 +1006,7 @@ typedef void (*OverrideCmdFunc)(CommandOverride *ovr, Client *client, MessageTag
#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 */

View File

@ -224,4 +224,15 @@ extern char OSName[256];
# endif
#endif
#ifdef NATIVE_BIG_ENDIAN
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__)
#include <sys/endian.h>
#define bswap_64 bswap64
#define bswap_32 bswap32
#define bswap_16 bswap16
#else
#include <byteswap.h>
#endif
#endif
#endif /* __sys_include__ */

View File

@ -26,6 +26,7 @@
#else
#include <sys/resource.h>
#include <utime.h>
#include <dirent.h>
#endif
#include <fcntl.h>
#include <signal.h>
@ -35,3 +36,4 @@
#ifdef USE_LIBCURL
#include <curl/curl.h>
#endif
#include <argon2.h>

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
if (!hb)
return 0; /* no history backend loaded */
/* Right now we return whenever the first backend has a result. */
for (hb = historybackends; hb; hb = hb->next)
hb->history_request(client, object, filter);
if ((r = hb->history_request(object, filter)))
return r;
return 1;
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);
}

View File

@ -20,7 +20,6 @@
#include "unrealircd.h"
#include "crypt_blowfish.h"
#include <argon2.h>
typedef struct AuthTypeList AuthTypeList;
struct AuthTypeList {

File diff suppressed because it is too large Load Diff

View File

@ -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))

View File

@ -4,9 +4,7 @@
*/
#include "unrealircd.h"
#ifndef _WIN32
#include <dirent.h>
#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");
}
@ -779,6 +789,8 @@ void report_crash(void)
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");
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;

View File

@ -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 */

View File

@ -19,6 +19,7 @@
*/
#include "unrealircd.h"
#include <ares.h>
#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

View File

@ -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'.

View File

@ -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

View File

@ -91,6 +91,7 @@ SpamfilterTargetTable spamfiltertargettable[] = {
{ 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<len; i++)
{
str[n++] = hexchars[(datastr[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<len; i++)
{
str[n++] = hexchars[(datastr[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);
}

View File

@ -6,7 +6,6 @@
#include "unrealircd.h"
#ifndef _WIN32
#include <dirent.h>
#define MODULEMANAGER_CONNECT_TIMEOUT 7
#define MODULEMANAGER_READ_TIMEOUT 20

View File

@ -30,9 +30,6 @@
#else
#include <dlfcn.h>
#endif
#ifndef _WIN32
#include <dirent.h>
#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 (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;
return oldopts;
}
}
unsigned int ModuleGetOptions(Module *module)

View File

@ -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

View File

@ -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)
*/

View File

@ -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 */
}

View File

@ -86,25 +86,17 @@ 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))
{
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)
/* Check away-flood */
if (MyUser(client) &&
!ValidatePermissionsForPath("immune:away-flood",client,NULL,NULL,NULL) &&
flood_limit_exceeded(client, FLD_AWAY))
{
sendnumeric(client, ERR_TOOMANYAWAY);
return;
}
}
/* Obey set::away-length */
if (strlen(new_reason) > iConf.away_length)

90
src/modules/bot-tag.c Normal file
View File

@ -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);
}
}

View File

@ -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;

View File

@ -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,47 +187,129 @@ 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"))
{
for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
{
if (!strcmp(cep5->ce_varname, "lines"))
{
int v;
CheckNull(cep4);
v = atoi(cep4->ce_vardata);
CheckNull(cep5);
v = atoi(cep5->ce_vardata);
if (v < 1)
{
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);
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;
}
maximum_storage_lines = v;
test.max_storage_per_channel_registered.lines = v;
} else
if (!strcmp(cep4->ce_varname, "time"))
if (!strcmp(cep5->ce_varname, "time"))
{
long v;
CheckNull(cep4);
v = config_checkval(cep4->ce_vardata, CFG_TIME);
CheckNull(cep5);
v = config_checkval(cep5->ce_vardata, CFG_TIME);
if (v < 1)
{
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);
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;
}
maximum_storage_time = v;
test.max_storage_per_channel_registered.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(cep5->ce_fileptr->cf_filename,
cep5->ce_varlinenum, "set::history::channel::max-storage-per-channel::registered", cep5->ce_varname);
errors++;
}
}
} else
if (!strcmp(cep4->ce_varname, "unregistered"))
{
for (cep5 = cep4->ce_entries; cep5; cep5 = cep5->ce_next)
{
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++;
}
}
} else
{
config_error_unknown(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "set::history::max-storage-per-channel", cep->ce_varname);
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))(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 {
config_error_unknown(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "set::history", cep->ce_varname);
@ -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++;
*errs = errors;
return errors ? -1 : 1;
}
if ((on_join_lines && maximum_storage_lines) && (on_join_lines > maximum_storage_lines))
int history_config_posttest(int *errs)
{
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++;
}
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(cep4->ce_varname, "time"))
if (!strcmp(cep5->ce_varname, "time"))
{
cfg.max_storage_per_channel.time = config_checkval(cep4->ce_vardata, CFG_TIME);
cfg.max_storage_per_channel_registered.time = config_checkval(cep5->ce_vardata, CFG_TIME);
}
}
} else
if (!strcmp(cep4->ce_varname, "unregistered"))
{
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';
}
}
}

View File

@ -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;

View File

@ -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,24 +173,53 @@ 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++;
} else
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;
}
if (!strcmp(cep->ce_varname, "database")) {
convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
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++;
}
}
*errs = errors;
return errors ? -1 : 1;
}
int channeldb_config_posttest(int *errs)
{
int errors = 0;
char *errstr;
if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
{
config_error("[channeldb] %s", errstr);
errors++;
}
*errs = errors;
return errors ? -1 : 1;
}
int channeldb_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);

294
src/modules/chathistory.c Normal file
View File

@ -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);
}

View File

@ -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)))

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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)
{
/* <port> , <port> : USERID : <OSTYPE>: <username>

View File

@ -166,26 +166,17 @@ 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 ((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)
if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL) &&
flood_limit_exceeded(client, FLD_INVITE))
{
sendnumeric(client, RPL_TRYAGAIN, "INVITE");
return;
}
}
if (!override)
{

View File

@ -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 '<count>:<period>' (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)
{

View File

@ -132,22 +132,13 @@ 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");
sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "You are KNOCK flooding");
return;
}
}
new_message(&me, NULL, &mtags);

View File

@ -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;

View File

@ -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))
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))
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);

View File

@ -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. */
}

View File

@ -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)))

View File

@ -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;

View File

@ -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))

116
src/modules/reply-tag.c Normal file
View File

@ -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);
}
}
}

View File

@ -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)
@ -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",
@ -264,6 +339,10 @@ int reputation_config_run(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;
@ -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;
@ -341,7 +427,7 @@ void load_db(void)
* in other words: when this module was first loaded, ever.
* <writtentime> 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 "
@ -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];
@ -396,10 +605,6 @@ 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());
@ -407,7 +612,7 @@ void save_db(void)
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,7 +635,7 @@ 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
@ -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)
@ -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)
@ -993,7 +1282,7 @@ CMD_FUNC(reputation_cmd)
{
if (MyUser(client))
reputation_user_cmd(client, recv_mtags, parc, parv);
else if (IsServer(client))
else if (IsServer(client) || IsMe(client))
reputation_server_cmd(client, recv_mtags, parc, parv);
}

View File

@ -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;
@ -193,6 +194,9 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
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))

View File

@ -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);
}
}
}
}

View File

@ -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 */
}

View File

@ -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).

View File

@ -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,14 +189,23 @@ 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';
if (sendit)
sendto_one(client, NULL, "%s", buf);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -376,8 +376,14 @@ CMD_FUNC(cmd_stats)
else
stat->func(client, NULL);
/* Modules can append data: */
/* 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;
}

View File

@ -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
{

View File

@ -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);

View File

@ -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;

View File

@ -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[] = {
/* <config name> <letter> <TKL_xxx type> <logging name> <tkl option?> <exempt option?> */
{ "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 },
/* <config name> <letter> <TKL_xxx type> <logging name> <tkl option?> <exempt option?> <need ip address?> */
{ "gline", 'G', TKL_KILL | TKL_GLOBAL, "G-Line", 1, 1, 0 },
{ "kline", 'k', TKL_KILL, "K-Line", 1, 1, 0 },
{ "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1, 1 },
{ "zline", 'z', TKL_ZAP, "Z-Line", 1, 1, 1 },
{ "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1, 0 },
{ "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1, 0 },
{ "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0, 0 },
{ "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1, 0 },
{ "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0, 0 },
{ "local-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,27 +1739,33 @@ 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
{
/* It's seemingly a nick .. let's see if we can find the user */
@ -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.

View File

@ -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()
{
if (loop.ircd_terminating)
write_tkldb();
freecfg();
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,24 +204,53 @@ 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++;
} else
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::tkldb::db-secret: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err);
errors++;
continue;
}
if (!strcmp(cep->ce_varname, "database")) {
convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR);
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++;
}
}
*errs = errors;
return errors ? -1 : 1;
}
int tkldb_config_posttest(int *errs)
{
int errors = 0;
char *errstr;
if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
{
config_error("[tkldb] %s", errstr);
errors++;
}
*errs = errors;
return errors ? -1 : 1;
}
int tkldb_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);

View File

@ -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) {

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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",

View File

@ -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)
{

View File

@ -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

View File

@ -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 <ares.h>
#ifndef _WIN32
/* for uname(), is POSIX so should be OK... */
#include <sys/utsname.h>
@ -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);

View File

@ -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 <dir>/<random-hex>.<suffix>
@ -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';
}

1163
src/unrealdb.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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
};

View File

@ -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",

View File

@ -3,7 +3,7 @@
<assemblyIdentity
processorArchitecture="amd64"
name="UnrealIRCd.UnrealIRCd.5"
version="5.0.9.0"
version="5.2.0.1"
type="win32"
/>
<description>Internet Relay Chat Daemon</description>

View File

@ -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

View File

@ -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
# 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!"