From 3d97a9500c0af7215aa90e78da932d13ea16e522 Mon Sep 17 00:00:00 2001 From: acidvegas Date: Sat, 19 Nov 2022 23:12:40 -0500 Subject: [PATCH] Updated to 6.0.4 --- Config | 2 +- Makefile.windows | 21 +- README.md | 15 +- configure | 70 ++- configure.ac | 8 +- doc/Config.header | 2 +- doc/RELEASE-NOTES.md | 180 +++++- doc/conf/modules.conf | 11 +- doc/conf/opers.conf | 1 + doc/conf/unrealircd.remote.conf | 106 ++-- extras/doxygen/Doxyfile | 2 +- extras/unrealircd-upgrade-script.in | 2 +- include/config.h | 2 +- include/h.h | 59 +- include/license.h | 17 +- include/modules.h | 82 ++- include/struct.h | 44 +- include/windows/setup.h | 4 +- src/Makefile.in | 2 +- src/api-efunctions.c | 13 +- src/api-extban.c | 98 +++- src/channel.c | 3 - src/conf.c | 422 ++++++++------ src/conf_preprocessor.c | 2 +- src/crashreport.c | 4 +- src/ircd.c | 4 +- src/ircd_vars.c | 2 +- src/log.c | 61 +- src/misc.c | 308 ++++++---- src/modulemanager.c | 24 +- src/modules/Makefile.in | 8 +- src/modules/antimixedutf8.c | 19 +- src/modules/antirandom.c | 40 +- src/modules/blacklist.c | 25 +- src/modules/bot-tag.c | 15 +- src/modules/certfp.c | 25 +- src/modules/chanmodes/floodprot.c | 2 +- src/modules/chanmodes/history.c | 2 +- src/modules/chanmodes/operonly.c | 9 +- src/modules/channel-context.c | 95 ++++ src/modules/channeldb.c | 26 +- src/modules/chathistory.c | 2 +- src/modules/chghost.c | 6 +- src/modules/connect.c | 4 +- src/modules/connthrottle.c | 101 ++-- src/modules/creationtime.c | 99 ++++ src/modules/extbans/account.c | 27 +- src/modules/extbans/certfp.c | 35 +- src/modules/extbans/country.c | 23 +- src/modules/extbans/realname.c | 25 +- src/modules/extbans/securitygroup.c | 23 +- src/modules/extbans/timedban.c | 2 +- src/modules/extended-monitor.c | 2 +- src/modules/geoip-tag.c | 108 ++++ src/modules/geoip_base.c | 21 +- src/modules/geoip_classic.c | 2 +- src/modules/geoip_maxmind.c | 2 +- src/modules/hideserver.c | 2 +- src/modules/history_backend_mem.c | 2 +- src/modules/history_backend_null.c | 2 +- src/modules/ident_lookup.c | 2 +- src/modules/ison.c | 3 +- src/modules/join.c | 41 +- src/modules/kill.c | 2 +- src/modules/labeled-response.c | 8 +- src/modules/message.c | 103 +--- src/modules/mode.c | 63 ++- src/modules/names.c | 3 +- src/modules/nick.c | 25 +- src/modules/oper.c | 217 ++++--- src/modules/operinfo.c | 10 +- src/modules/protoctl.c | 4 +- src/modules/reputation.c | 22 +- src/modules/restrict-commands.c | 57 +- src/modules/sapart.c | 2 + src/modules/server.c | 102 ++-- src/modules/sethost.c | 1 - src/modules/sinfo.c | 2 +- src/modules/stats.c | 34 +- src/modules/svsmode.c | 2 +- src/modules/svsnoop.c | 2 +- src/modules/svso.c | 138 +++++ src/modules/targetfloodprot.c | 2 +- src/modules/tkl.c | 284 ++++++---- src/modules/tkldb.c | 1 + src/modules/tls_antidos.c | 2 +- src/modules/tls_cipher.c | 26 +- src/modules/unreal_server_compat.c | 2 +- src/modules/vhost.c | 3 +- src/modules/watch-backend.c | 2 +- src/modules/watch.c | 3 +- src/modules/websocket.c | 2 +- src/modules/whois.c | 49 +- src/modules/whox.c | 2 +- src/operclass.c | 18 +- src/securitygroup.c | 840 ++++++++++++++++++++++++++++ src/serv.c | 8 +- src/socket.c | 15 +- src/support.c | 2 +- src/tls.c | 2 +- src/user.c | 210 +------ src/version.c.SH | 2 +- src/windows/UnrealIRCd.exe.manifest | 2 +- src/windows/gui.c | 20 +- src/windows/unrealinst.iss | 2 +- src/windows/wingui.rc | 7 +- 106 files changed, 3377 insertions(+), 1297 deletions(-) create mode 100644 src/modules/channel-context.c create mode 100644 src/modules/creationtime.c create mode 100644 src/modules/geoip-tag.c create mode 100644 src/modules/svso.c create mode 100644 src/securitygroup.c diff --git a/Config b/Config index e1ae6d8..b51a413 100755 --- a/Config +++ b/Config @@ -375,7 +375,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-6.0.2 unrealircd-6.0.1.1 unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6" +UNREALRELEASES="unrealircd-6.0.4.1 unrealircd-6.0.4 unrealircd-6.0.4-rc2 unrealircd-6.0.4-rc1 unrealircd-6.0.3 unrealircd-6.0.2 unrealircd-6.0.1.1 unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6" if [ -f "config.settings" ]; then . ./config.settings else diff --git a/Makefile.windows b/Makefile.windows index 69ec930..f0f8898 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -178,7 +178,7 @@ EXP_OBJ_FILES=src/ircd_vars.obj src/channel.obj src/send.obj src/socket.obj \ src/fdlist.obj src/dbuf.obj \ src/hash.obj src/parse.obj \ src/whowas.obj \ - src/misc.obj src/match.obj src/crule.obj \ + src/securitygroup.obj src/misc.obj src/match.obj src/crule.obj \ src/debug.obj src/support.obj src/list.obj \ src/serv.obj src/user.obj \ src/version.obj src/ircsprintf.obj \ @@ -254,6 +254,7 @@ DLL_FILES=\ src/modules/close.dll \ src/modules/connect.dll \ src/modules/connthrottle.dll \ + src/modules/creationtime.dll \ src/modules/cycle.dll \ src/modules/dccallow.dll \ src/modules/dccdeny.dll \ @@ -278,6 +279,7 @@ DLL_FILES=\ src/modules/geoip_base.dll \ src/modules/geoip_classic.dll \ src/modules/geoip_csv.dll \ + src/modules/geoip-tag.dll \ src/modules/globops.dll \ src/modules/help.dll \ src/modules/hideserver.dll \ @@ -364,6 +366,7 @@ DLL_FILES=\ src/modules/svsnline.dll \ src/modules/svsnolag.dll \ src/modules/svsnoop.dll \ + src/modules/svso.dll \ src/modules/svspart.dll \ src/modules/svssilence.dll \ src/modules/svssno.dll \ @@ -379,6 +382,7 @@ DLL_FILES=\ src/modules/trace.dll \ src/modules/tsctl.dll \ src/modules/typing-indicator.dll \ + src/modules/channel-context.dll \ src/modules/umode2.dll \ src/modules/unreal_server_compat.dll \ src/modules/unsqline.dll \ @@ -502,6 +506,9 @@ src/conf_preprocessor.obj: src/conf_preprocessor.c $(INCLUDES) src/debug.obj: src/debug.c $(INCLUDES) $(CC) $(CFLAGS) src/debug.c +src/securitygroup.obj: src/securitygroup.c $(INCLUDES) + $(CC) $(CFLAGS) src/securitygroup.c + src/misc.obj: src/misc.c $(INCLUDES) ./include/dbuf.h $(CC) $(CFLAGS) src/misc.c @@ -828,6 +835,9 @@ src/modules/connect.dll: src/modules/connect.c $(INCLUDES) src/modules/connthrottle.dll: src/modules/connthrottle.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/connthrottle.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connthrottle.pdb $(MODLFLAGS) +src/modules/creationtime.dll: src/modules/creationtime.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/creationtime.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/creationtime.pdb $(MODLFLAGS) + src/modules/cycle.dll: src/modules/cycle.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/cycle.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/cycle.pdb $(MODLFLAGS) @@ -900,6 +910,9 @@ src/modules/geoip_classic.dll: src/modules/geoip_classic.c $(INCLUDES) src/modules/geoip_csv.dll: src/modules/geoip_csv.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/geoip_csv.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip_csv.pdb $(MODLFLAGS) +src/modules/geoip-tag.dll: src/modules/geoip-tag.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/geoip-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip-tag.pdb $(MODLFLAGS) + src/modules/geoip_maxmind.dll: src/modules/geoip_maxmind.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/geoip_maxmind.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/geoip_maxmind.pdb $(MODLFLAGS) @@ -1161,6 +1174,9 @@ src/modules/svsnolag.dll: src/modules/svsnolag.c $(INCLUDES) src/modules/svsnoop.dll: src/modules/svsnoop.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/svsnoop.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svsnoop.pdb $(MODLFLAGS) +src/modules/svso.dll: src/modules/svso.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/svso.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svso.pdb $(MODLFLAGS) + src/modules/svspart.dll: src/modules/svspart.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/svspart.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svspart.pdb $(MODLFLAGS) @@ -1206,6 +1222,9 @@ src/modules/tsctl.dll: src/modules/tsctl.c $(INCLUDES) src/modules/typing-indicator.dll: src/modules/typing-indicator.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/typing-indicator.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/typing-indicator.pdb $(MODLFLAGS) +src/modules/channel-context.dll: src/modules/channel-context.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/channel-context.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/channel-context.pdb $(MODLFLAGS) + src/modules/umode2.dll: src/modules/umode2.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/umode2.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/umode2.pdb $(MODLFLAGS) diff --git a/README.md b/README.md index 86b1427..8e505c9 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,17 @@ online documentation. * UnrealIRCd 6 is the *stable* series since December 2021. All new features go in there. * UnrealIRCd 5 is the *oldstable* series. It will receive bug fixes until July 1, 2022 plus another 12 months of security fixes. +* For full details of release scheduling and EOL dates, see + [UnrealIRCd releases](https://www.unrealircd.org/docs/UnrealIRCd_releases) on the wiki ## How to get started -Please consult our excellent online documentation at https://www.unrealircd.org/docs/ -when setting up the IRCd! +### Use the wiki! +**IMPORTANT:** We recommend you follow our installation guide on the wiki instead of the +steps in this README. The wiki has more detailed information and is more easy to navigate. +* [Installing from source for *NIX](https://www.unrealircd.org/docs/Installing_from_source) +* [Installating instructions for Windows](https://www.unrealircd.org/docs/Installing_(Windows)) + +Please consult the online documentation at https://www.unrealircd.org/docs/ when setting up the IRCd! ### Step 1: Installation #### Windows @@ -36,11 +43,11 @@ Do the following steps under a separate account for running UnrealIRCd, * Now change to the directory where you installed UnrealIRCd, e.g. `cd /home/xxxx/unrealircd` ### Step 2: Configuration -Configuration files are stored in the conf/ folder by default (eg: /home/xxxx/unrealircd/conf) +Configuration files are stored in the `conf/` folder by default (eg: `/home/xxxx/unrealircd/conf`) #### Create a configuration file If you are new, then you need to create your own configuration file: -Copy conf/examples/example.conf to conf/ and call it unrealircd.conf. +Copy `conf/examples/example.conf` to `conf/` and call it `unrealircd.conf`. Then open it in an editor and carefully modify it using the documentation and FAQ as a guide (see below). ### Step 3: Booting diff --git a/configure b/configure index 7dfb3d1..28dbafc 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for unrealircd 6.0.3. +# Generated by GNU Autoconf 2.69 for unrealircd 6.0.4.2. # # Report bugs to . # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unrealircd' PACKAGE_TARNAME='unrealircd' -PACKAGE_VERSION='6.0.3' -PACKAGE_STRING='unrealircd 6.0.3' +PACKAGE_VERSION='6.0.4.2' +PACKAGE_STRING='unrealircd 6.0.4.2' PACKAGE_BUGREPORT='https://bugs.unrealircd.org/' PACKAGE_URL='https://unrealircd.org/' @@ -1347,7 +1347,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures unrealircd 6.0.3 to adapt to many kinds of systems. +\`configure' configures unrealircd 6.0.4.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1413,7 +1413,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unrealircd 6.0.3:";; + short | recursive ) echo "Configuration of unrealircd 6.0.4.2:";; esac cat <<\_ACEOF @@ -1589,7 +1589,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unrealircd configure 6.0.3 +unrealircd configure 6.0.4.2 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1958,7 +1958,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by unrealircd $as_me 6.0.3, which was +It was created by unrealircd $as_me 6.0.4.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2366,7 +2366,7 @@ _ACEOF # Minor version number (e.g.: Z in X.Y.Z) -UNREAL_VERSION_MINOR="3" +UNREAL_VERSION_MINOR="4" cat >>confdefs.h <<_ACEOF #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR @@ -2376,7 +2376,7 @@ _ACEOF # The version suffix such as a beta marker or release candidate # marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a # string instead of an integer because it contains arbitrary data. -UNREAL_VERSION_SUFFIX="" +UNREAL_VERSION_SUFFIX=".2" cat >>confdefs.h <<_ACEOF #define UNREAL_VERSION_SUFFIX "$UNREAL_VERSION_SUFFIX" @@ -5638,6 +5638,54 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $ ac_compiler_gnu=$ac_cv_c_compiler_gnu +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-overflow" >&5 +$as_echo_n "checking whether C compiler accepts -Wformat-overflow... " >&6; } +if ${ax_cv_check_cflags__Werror___Wformat_overflow+:} false; then : + $as_echo_n "(cached) " >&6 +else + + ax_check_save_flags=$CFLAGS + CFLAGS="$CFLAGS -Werror -Wformat-overflow" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ax_cv_check_cflags__Werror___Wformat_overflow=yes +else + ax_cv_check_cflags__Werror___Wformat_overflow=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CFLAGS=$ax_check_save_flags +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_overflow" >&5 +$as_echo "$ax_cv_check_cflags__Werror___Wformat_overflow" >&6; } +if test x"$ax_cv_check_cflags__Werror___Wformat_overflow" = xyes; then : + CFLAGS="$CFLAGS -Wno-format-overflow" +else + : +fi + + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' @@ -9414,7 +9462,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by unrealircd $as_me 6.0.3, which was +This file was extended by unrealircd $as_me 6.0.4.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -9477,7 +9525,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -unrealircd config.status 6.0.3 +unrealircd config.status 6.0.4.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index e3a5390..1873c89 100644 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ dnl src/windows/unrealinst.iss dnl doc/Config.header dnl src/version.c.SH -AC_INIT([unrealircd], [6.0.3], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/]) +AC_INIT([unrealircd], [6.0.4.2], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/]) AC_CONFIG_SRCDIR([src/ircd.c]) AC_CONFIG_HEADER([include/setup.h]) AC_CONFIG_AUX_DIR([autoconf]) @@ -34,13 +34,13 @@ UNREAL_VERSION_MAJOR=["0"] AC_DEFINE_UNQUOTED([UNREAL_VERSION_MAJOR], [$UNREAL_VERSION_MAJOR], [Major version number (e.g.: Y for X.Y.Z)]) # Minor version number (e.g.: Z in X.Y.Z) -UNREAL_VERSION_MINOR=["3"] +UNREAL_VERSION_MINOR=["4"] AC_DEFINE_UNQUOTED([UNREAL_VERSION_MINOR], [$UNREAL_VERSION_MINOR], [Minor version number (e.g.: Z for X.Y.Z)]) # The version suffix such as a beta marker or release candidate # marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a # string instead of an integer because it contains arbitrary data. -UNREAL_VERSION_SUFFIX=[""] +UNREAL_VERSION_SUFFIX=[".2"] 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) @@ -237,6 +237,8 @@ check_cc_flag([-Wformat-zero-length], [CFLAGS="$CFLAGS -Wno-format-zero-length"] check_cc_flag([-Wformat-truncation], [CFLAGS="$CFLAGS -Wno-format-truncation"]) +check_cc_flag([-Wformat-overflow], [CFLAGS="$CFLAGS -Wno-format-overflow"]) + dnl While it can be useful to occasionally to compile with warnings about dnl unused variables and parameters, we often 'think ahead' when coding things dnl so they may be useless now but not later. Similarly, for variables, we diff --git a/doc/Config.header b/doc/Config.header index 96c64d7..0b937c0 100644 --- a/doc/Config.header +++ b/doc/Config.header @@ -7,7 +7,7 @@ \___/|_| |_|_| \___|\__,_|_|\___/\_| \_| \____/\__,_| Configuration Program - for UnrealIRCd 6.0.3 + for UnrealIRCd 6.0.4.2 This program will help you to compile your IRC server, and ask you questions regarding the compile-time settings of it during the process. diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md index 197a2ab..77253cb 100644 --- a/doc/RELEASE-NOTES.md +++ b/doc/RELEASE-NOTES.md @@ -1,15 +1,183 @@ -UnrealIRCd 6.0.3 -================= +UnrealIRCd 6.0.4.2 +=================== +Another small update to 6.0.4.x: -A number of serious issues were discovered in UnrealIRCd 6. Among these is -an issue which will likely crash the IRCd sooner or later if you /REHASH -with any active clients connected. -We suggest everyone who is running UnrealIRCd 6 to upgrade to 6.0.3. +* Fix crash when linking. This requires a certain sequence of events: first + a server is linked in successfully, then we need to REHASH, and then a new + link attempt has to come in with the same server name (for example because + there is a network issue and the old link has not timed out yet). + If all that happens, then an UnreaIRCd 6 server may crash, but not always. +* Two IRCv3 specifications were ratified which we already supported as drafts: + * Change CAP `draft/extended-monitor` to `extended-monitor` + * Add message-tag `bot` next to existing (for now) `draft/bot` +* Update Turkish translations + +UnrealIRCd 6.0.4.1 +=================== +This is a small update to 6.0.4. It fixes the following issues that were +present in all 6.0.x versions: + +* Fix sporadic crash when linking a server (after successful authentication). + This feels like a compiler bug. It affected only some people with GCC and + only in some situations. When compiled with clang there was no problem. + Hopefully we can work around it this way. +* Make /INVITE bypass (nearly) all channel mode restrictions, as it used to + be in UnrealIRCd 5.x. Both for invites by channel ops and for OperOverride. + This also fixes a bug where an IRCOp with OperOverride could not bypass +l + (limit) and other restrictions and would have to resort back to using + MODE or SAMODE. Only +b and +i could be bypassed via INVITE OperOverride. + +UnrealIRCd 6.0.4 +----------------- +This release comes with lots of features and enhancements. In particular, +security groups and mask items now allow you to write cleaner and more +flexible configuration files. There are also JSON logging enhancements and +several bug fixes. Thanks a lot to everyone who tested the release candidates! If you are already running UnrealIRCd 6 then read below. Otherwise, jump straight to the [summary about UnrealIRCd 6](#Summary) to learn more about UnrealIRCd 6. +### Enhancements: +* Show security groups in `WHOIS` +* The [security-group block](https://www.unrealircd.org/docs/Security-group_block) + has been expanded and the same functionality is now available in + [mask items](https://www.unrealircd.org/docs/Mask_item) too: + * This means the existing options like `identified`, `webirc`, `tls` and + `reputation-score` can be used in `allow::mask` etc. + * New options (in both security-group and mask) are: + * `connect-time`: time a user is connected to IRC + * `security-group`: to check another security group + * `account`: services account name + * `country`: country code, as found by GeoIP + * `realname`: realname (gecos) of the user + * `certfp`: certificate fingerprint + * Every option also has an exclude- variant, eg. `exclude-country`. + If a user matches any `exclude-` option then it is considered not a match. + * The modules [connthrottle](https://www.unrealircd.org/docs/Connthrottle), + [restrict-commands](https://www.unrealircd.org/docs/Set_block#set::restrict-commands) + and [antirandom](https://www.unrealircd.org/docs/Set_block#set::antirandom) + now use the new `except` sub-block which is a mask item. The old syntax + (eg set::antirandom::except-webirc) is still accepted by UnrealIRCd + and converted to the appropriate new setting behind the scenes + (set::antirandom::except::webirc). + * The modules [blacklist](https://www.unrealircd.org/docs/Blacklist_block) + and [antimixedutf8](https://www.unrealircd.org/docs/Set_block#set::antimixedutf8) + now also support the `except` block (a mask item). + * Other than that the extended functionality is available in these blocks: + `allow`, `oper`, `tld`, `vhost`, `deny channel`, `allow channel`. + * Example of direct use in a ::mask item: + ``` + /* Spanish MOTD for Spanish speaking countries */ + tld { + mask { country { ES; AR; BO; CL; CO; CR; DO; EC; SV; GT; HN; MX; NI; PA; PY; PE; PR; UY; VE; } } + motd "motd.es.txt"; + rules "rules.es.txt"; + } + ``` + * Example of defining a security group and using it in a mask item later: + ``` + security-group irccloud { + mask { ip1; ip2; ip3; ip4; } + } + allow { + mask { security-group irccloud; } + class clients; + maxperip 128; + } + except ban { + mask { security-group irccloud; } + type { blacklist; connect-flood; handshake-data-flood; } + } + ``` +* Because the mask item is so powerful now, the `password` in the + [oper block](https://www.unrealircd.org/docs/Oper_block) is optional now. +* We now support oper::auto-login, which means the user will become IRCOp + automatically if they match the conditions on-connect. This can be used + in combination with + [certificate fingerprint](https://www.unrealircd.org/docs/Certificate_fingerprint) + authentication for example: + ``` + security-group Syzop { certfp "1234etc."; } + oper Syzop { + auto-login yes; + mask { security-group Syzop; } + operclass netadmin-with-override; + class opers; + } + except ban { + mask { security-group Syzop; } + type all; + } + ``` +* For [JSON logging](https://www.unrealircd.org/docs/JSON_logging) a number + of fields were added when a client is expanded: + * `geoip`: with subitem `country_code` (eg. `NL`) + * `tls`: with subitems `cipher` and `certfp` + * Under subitem `users`: + * `vhost`: if the visible host differs from the realhost then this is + set (thus for both vhost and cloaked host) + * `cloakedhost`: this is always set (except for eg. services users), even + if the user is not cloaked so you can easily search on a cloaked host. + * `idle_since`: last time the user has spoken (local clients only) + * `channels`: list of channels (array), with a maximum of 384 chars. +* The JSON logging now also strips ASCII below 32, so color- and + control codes. +* Support IRCv3 `+draft/channel-context` +* Add `example.es.conf` (Spanish example configuration file) +* The country of users is now communicated in the + [message-tag](https://www.unrealircd.org/docs/Message_tags) + `unrealircd.org/geoip` (only to IRCOps). +* Add support for linking servers via UNIX domain sockets + (`link::outgoing::file`). + +### Fixes: +* Crash in `except ban` with `~security-group:xyz` +* Crash if hideserver module was loaded but `LINKS` was not blocked. +* Crash on Windows when using the "Rehash" GUI option. +* Infinite loop if one security-group referred to another. +* Duplicate entries in the `+beI` lists of `+P` channels. +* Regular users were able to -o a service bot (that has umode +S) +* Module manager did not stop on compile error +* [`set::modes-on-join`](https://www.unrealircd.org/docs/Set_block#set::modes-on-join) + did not work with `+f` + timed bans properly, eg `[3t#b1]:10` +* Several log messages were missing some information. +* Reputation syncing across servers had a small glitch. Fix is mostly + useful for servers that were not linked to the network for days or weeks. + +### Changes: +* Clarified that UnrealIRCd is licensed as "GPLv2 or later" +* Fix use of variables in + [`set::reject-message](https://www.unrealircd.org/docs/Set_block#set::reject-message) + and in [`blacklist::reason](https://www.unrealircd.org/docs/Blacklist_block): + previously short forms of variables were (unintentionally) expanded + as well, such as `$serv` for `$server`. This is no longer supported, you need + to use the correct full variable names. + +### Developers and protocol: +* The `creationtime` is now communicated of users. Until now this + information was only known locally (the thing that was communicated + that came close was "last nick change" but that is not the same). + This is synced via (early) moddata across servers. + Module coders can use `get_connected_time()`. +* The `RPL_HOSTHIDDEN` is now sent from `userhost_changed()` so you + don't explicitly send it yourself anymore. +* The `SVSO` command is back, so services can make people IRCOp again. + See `HELPOP SVSO` or [the commit](https://github.com/unrealircd/unrealircd/commit/50e5d91c798e7d07ca0c68d9fca302a6b6610786) + for more information. +* Due to last change the `HOOKTYPE_LOCAL_OPER` parameters were changed. +* Module coders can enhance the + [JSON logging](https://www.unrealircd.org/docs/JSON_logging) + expansion items for clients and channels via new hooks like + `HOOKTYPE_JSON_EXPAND_CLIENT`. This is used by the geoip and tls modules. + +UnrealIRCd 6.0.3 +----------------- +A number of serious issues were discovered in UnrealIRCd 6. Among these is +an issue which will likely crash the IRCd sooner or later if you /REHASH +with any active clients connected. +We suggest everyone who is running UnrealIRCd 6 to upgrade to 6.0.3. + Fixes: * Crash in `WATCH` if the IRCd has been rehashed at least once. After doing a `REHASH` with active clients it will likely corrupt memory. It may take diff --git a/doc/conf/modules.conf b/doc/conf/modules.conf index 9cbe21b..10790c0 100644 --- a/doc/conf/modules.conf +++ b/doc/conf/modules.conf @@ -74,11 +74,13 @@ loadmodule "setident"; loadmodule "squit"; loadmodule "stats"; loadmodule "tkl"; +loadmodule "tline"; loadmodule "trace"; loadmodule "tsctl"; loadmodule "unsqline"; // Server-2-Server Commands +loadmodule "creationtime"; loadmodule "eos"; loadmodule "md"; loadmodule "netinfo"; @@ -97,6 +99,7 @@ loadmodule "sendsno"; loadmodule "sendumode"; loadmodule "svsjoin"; loadmodule "svskill"; +loadmodule "svslogin"; loadmodule "svslusers"; loadmodule "svsmode"; loadmodule "svsmotd"; @@ -172,12 +175,13 @@ loadmodule "extbans/quiet"; /* +b ~quiet */ loadmodule "extbans/textban"; /* +b ~text */ loadmodule "extbans/timedban"; /* +b ~time */ loadmodule "extbans/securitygroup"; /* +b ~security-group */ - +e // IRCv3 Extensions loadmodule "account-notify"; loadmodule "account-tag"; loadmodule "batch"; loadmodule "bot-tag"; +loadmodule "channel-context"; loadmodule "chathistory"; loadmodule "clienttagdeny"; loadmodule "echo-message"; @@ -200,6 +204,7 @@ loadmodule "blacklist"; loadmodule "certfp"; loadmodule "channeldb"; loadmodule "charsys"; +loadmodule "connect-flood"; loadmodule "connthrottle"; #loadmodule "geoip_base"; #loadmodule "geoip_classic"; @@ -209,6 +214,7 @@ loadmodule "history_backend_mem"; loadmodule "ident_lookup"; loadmodule "jointhrottle"; loadmodule "json-log-tag"; +loadmodule "max-unknown-connections-per-ip"; loadmodule "targetfloodprot"; loadmodule "tkldb"; loadmodule "tls_antidos"; @@ -220,4 +226,5 @@ loadmodule "restrict-commands"; loadmodule "rmtkl"; loadmodule "watch-backend"; #loadmodule "webirc"; -#loadmodule "websocket"; \ No newline at end of file +#loadmodule "webserver"; +#loadmodule "websocket"; diff --git a/doc/conf/opers.conf b/doc/conf/opers.conf index 4c6ffcf..3105954 100644 --- a/doc/conf/opers.conf +++ b/doc/conf/opers.conf @@ -1,4 +1,5 @@ oper acidvegas { + auto-login yes; mask localhost; password "REDACTED" { sslclientcertfp; } class clients; diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf index bc194e6..09f7d66 100644 --- a/doc/conf/unrealircd.remote.conf +++ b/doc/conf/unrealircd.remote.conf @@ -1,4 +1,12 @@ -admin { ""; } +@define $VOID "8,4 E N T E R T H E V O I D "; + +admin { + "4Administrator: Brandon Brown 14(aka MRCHATS) 6branbran89@supernets.org"; + " 4Moderator: Bristopher Manning 14(aka delorean) 6simpsonsfan95@supernets.org"; + " 4Sales: Branthony Bronson 14(aka pyrex) 6showercaphandgun@supernets.org"; + ""; + "Feel free to chat with us in #5000 for network help & support!"; +} alias botserv { type services; } alias bs { target botserv; type services; } @@ -60,7 +68,7 @@ blacklist dronebl { } action gzline; ban-time 30d; - reason "8,4 E N T E R T H E V O I D "; + reason "$VOID"; } blacklist efnetrbl { @@ -71,7 +79,7 @@ blacklist efnetrbl { } action gzline; ban-time 30d; - reason "8,4 E N T E R T H E V O I D "; + reason "$VOID"; } blacklist torbl { @@ -82,7 +90,7 @@ blacklist torbl { } action gzline; ban-time 30d; - reason "8,4 E N T E R T H E V O I D "; + reason "$VOID"; } set { @@ -96,13 +104,13 @@ set { restrict-usermodes "ips"; restrict-channelmodes "nLpPs"; restrict-commands { - channel-message { connect-delay 60; exempt-identified yes; exempt-reputation-score 100; } - channel-notice { connect-delay 60; exempt-identified yes; exempt-reputation-score 100; } - invite { connect-delay 300; exempt-identified yes; exempt-reputation-score 100; } - join { connect-delay 15; exempt-identified yes; exempt-reputation-score 100; } - list { connect-delay 30; exempt-identified yes; exempt-reputation-score 100; } - private-message { connect-delay 300; exempt-identified yes; exempt-reputation-score 100; } - private-notice { connect-delay 300; exempt-identified yes; exempt-reputation-score 100; } + channel-message { connect-time 60; identified yes; reputation-score 100; } + channel-notice { connect-time 60; identified yes; reputation-score 100; } + invite { connect-time 300; identified yes; reputation-score 100; } + join { connect-time 15; identified yes; reputation-score 100; } + list { connect-time 30; identified yes; reputation-score 100; } + private-message { connect-time 300; identified yes; reputation-score 100; } + private-notice { connect-time 300; identified yes; reputation-score 100; } } auto-join "#superbowl"; static-quit "EMO-QUIT"; @@ -145,14 +153,14 @@ set { ban-action gzline; ban-time 1h; } - #target-flood { - # channel-notice 15:5; - # channel-privmsg 45:5; - # channel-tagmsg 15:5; - # private-notice 10:5; - # private-privmsg 30:5; - # private-tagmsg 10:5; - #} + target-flood { + channel-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; @@ -185,29 +193,29 @@ set { modef-default-unsettime 5; spamfilter { ban-time 1d; - ban-reason "8,4 E N T E R T H E V O I D "; + ban-reason "$VOID"; except "#anythinggoes"; } max-targets-per-command { kick 1; part 1; privmsg 1; } hide-ban-reason yes; reject-message { - gline "8,4 E N T E R T H E V O I D "; - kline "8,4 E N T E R T H E V O I D "; - password-mismatch "8,4 E N T E R T H E V O I D "; - server-full "8,4 E N T E R T H E V O I D "; - too-many-connections "8,4 E N T E R T H E V O I D "; - unauthorized "8,4 E N T E R T H E V O I D "; + gline "$VOID"; + kline "$VOID"; + password-mismatch "$VOID"; + server-full "$VOID"; + too-many-connections "$VOID"; + unauthorized "$VOID"; } antimixedutf8 { score 8; ban-action block; - ban-reason "8,4 E N T E R T H E V O I D "; + ban-reason "$VOID"; } connthrottle { - 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; } - reason "8,4 E N T E R T H E V O I D "; + except { reputation-score 100; identified yes; webirc yes; } + new-users { local-throttle 20:60; global-throttle 30:60; } + disabled-when { reputation-gathering 1w; start-delay 3m; } + reason "$VOID"; } history { channel { @@ -220,32 +228,32 @@ set { } hide-idle-time { policy always; } whois-details { - account { everyone full; } - away { everyone full; } - basic { everyone full; } - bot { everyone full; } - certfp { everyone full; } - channels { everyone none; self full; oper full; } - geo { everyone none; } - idle { everyone none; } - modes { everyone none; self full; oper full; } + account { everyone full; } + away { everyone full; } + basic { everyone full; } + bot { everyone full; } + certfp { everyone full; } + channels { everyone none; self full; oper full; } + geo { everyone none; } + idle { everyone none; } + modes { everyone none; self full; oper full; } oper { everyone limited; self full; oper full; } - realhost { everyone none; self full; oper full; } - registered-nick { everyone full; } - reputation { everyone full; } + realhost { everyone none; self full; oper full; } + registered-nick { everyone full; } + reputation { everyone full; } secure { everyone limited; self full; oper full; } - server { everyone full; } - services { everyone full; } - shunned { everyone none; self none; oper full; } - swhois { everyone full; } + server { everyone full; } + services { everyone full; } + shunned { everyone none; self none; oper full; } + swhois { everyone full; } } } hideserver { disable-map yes; disable-links yes; - map-deny-message "8,4 E N T E R T H E V O I D "; - links-deny-message "8,4 E N T E R T H E V O I D "; + map-deny-message "$VOID"; + links-deny-message "$VOID"; } security-group known-users { diff --git a/extras/doxygen/Doxyfile b/extras/doxygen/Doxyfile index 469e8ca..24d123f 100644 --- a/extras/doxygen/Doxyfile +++ b/extras/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "UnrealIRCd" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 6.0.3 +PROJECT_NUMBER = 6.0.4.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/extras/unrealircd-upgrade-script.in b/extras/unrealircd-upgrade-script.in index 6c3005b..6a16321 100644 --- a/extras/unrealircd-upgrade-script.in +++ b/extras/unrealircd-upgrade-script.in @@ -119,5 +119,5 @@ fi chmod +x unrealircd-upgrade-script.stage2 ./unrealircd-upgrade-script.stage2 $* SAVERET="$?" -rm -f unrealircd-upgrade-script.stage2 unrealircd-upgrade-script.stage2 +rm -f unrealircd-upgrade-script.stage2 unrealircd-upgrade-script.stage2.asc exit $SAVERET diff --git a/include/config.h b/include/config.h index 7281cb9..9156eb7 100644 --- a/include/config.h +++ b/include/config.h @@ -123,7 +123,7 @@ * Common usage for this are: a trusted bot ran by an IRCOp, that you only * want to give "flood access" and nothing else, and other such things. */ -#define FAKELAG_CONFIGURABLE +//#undef FAKELAG_CONFIGURABLE /* The default value for class::sendq */ #define DEFAULT_SENDQ 3000000 diff --git a/include/h.h b/include/h.h index 2d11547..d22bda8 100644 --- a/include/h.h +++ b/include/h.h @@ -111,7 +111,6 @@ extern MODVAR ConfigItem_deny_version *conf_deny_version; extern MODVAR ConfigItem_alias *conf_alias; extern MODVAR ConfigItem_help *conf_help; extern MODVAR ConfigItem_offchans *conf_offchans; -extern MODVAR SecurityGroup *securitygroups; extern void completed_connection(int, int, void *); extern void clear_unknown(); extern EVENT(e_unload_module_delayed); @@ -677,6 +676,7 @@ extern void unreal_setfilemodtime(const char *filename, time_t mtime); extern void DeleteTempModules(void); extern MODVAR Extban *extbaninfo; extern Extban *findmod_by_bantype(const char *str, const char **remainder); +extern Extban *findmod_by_bantype_raw(const char *str, int ban_name_length); extern Extban *ExtbanAdd(Module *reserved, ExtbanInfo req); extern void ExtbanDel(Extban *); extern void extban_init(void); @@ -749,8 +749,9 @@ extern MODVAR const char *(*tkl_type_string)(TKL *tk); extern MODVAR const char *(*tkl_type_config_string)(TKL *tk); extern MODVAR TKL *(*tkl_add_serverban)(int type, const char *usermask, const char *hostmask, const char *reason, const char *setby, time_t expire_at, time_t set_at, int soft, int flags); -extern MODVAR TKL *(*tkl_add_banexception)(int type, const char *usermask, const char *hostmask, const char *reason, const char *set_by, - time_t expire_at, time_t set_at, int soft, const char *bantypes, int flags); +extern MODVAR TKL *(*tkl_add_banexception)(int type, const char *usermask, const char *hostmask, SecurityGroup *match, + const char *reason, const char *set_by, + time_t expire_at, time_t set_at, int soft, const char *bantypes, int flags); extern MODVAR TKL *(*tkl_add_nameban)(int type, const char *name, int hold, const char *reason, const char *setby, time_t expire_at, time_t set_at, int flags); extern MODVAR TKL *(*tkl_add_spamfilter)(int type, unsigned short target, unsigned short action, Match *match, const char *setby, @@ -779,10 +780,9 @@ extern MODVAR int (*match_spamfilter)(Client *client, const char *str_in, int ty extern MODVAR int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, const char *cmd); extern MODVAR int (*join_viruschan)(Client *client, TKL *tk, int type); extern MODVAR const char *(*StripColors)(const char *text); -extern MODVAR const char *(*StripControlCodes)(const char *text); extern MODVAR void (*spamfilter_build_user_string)(char *buf, const char *nick, Client *acptr); extern MODVAR void (*send_protoctl_servers)(Client *client, int response); -extern MODVAR int (*verify_link)(Client *client, ConfigItem_link **link_out); +extern MODVAR ConfigItem_link *(*verify_link)(Client *client); extern MODVAR void (*send_server_message)(Client *client); extern MODVAR void (*broadcast_md_client)(ModDataInfo *mdi, Client *acptr, ModData *md); extern MODVAR void (*broadcast_md_channel)(ModDataInfo *mdi, Channel *channel, ModData *md); @@ -834,6 +834,8 @@ extern MODVAR char *(*tkl_uhost)(TKL *tkl, char *buf, size_t buflen, int options extern MODVAR void (*do_unreal_log_remote_deliver)(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); extern MODVAR char *(*get_chmodes_for_user)(Client *client, const char *flags); extern MODVAR WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name); +extern MODVAR int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); +extern MODVAR int (*unreal_match_iplist)(Client *client, NameList *l); /* /Efuncs */ /* TLS functions */ @@ -859,6 +861,7 @@ extern MODVAR EVP_MD *sha1_function; extern MODVAR EVP_MD *md5_function; /* End of TLS functions */ +/* Default handlers for efunctions */ extern void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list); extern const char *mtags_to_string_default_handler(MessageTag *m, Client *client); extern void *labeled_response_save_context_default_handler(void); @@ -868,6 +871,8 @@ extern int add_silence_default_handler(Client *client, const char *mask, int sen extern int del_silence_default_handler(Client *client, const char *mask); extern int is_silenced_default_handler(Client *client, Client *acptr); extern void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); +extern int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); +/* End of default handlers for efunctions */ extern MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules; extern MODVAR int max_connection_count; @@ -925,10 +930,6 @@ extern void unreal_delete_match(Match *m); extern int unreal_match(Match *m, const char *str); extern int unreal_match_method_strtoval(const char *str); extern char *unreal_match_method_valtostr(int val); -extern void unreal_delete_masks(ConfigItem_mask *m); -extern void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce); -extern int unreal_mask_match(Client *acptr, ConfigItem_mask *m); -extern int unreal_mask_match_string(const char *name, ConfigItem_mask *m); #ifdef _WIN32 extern MODVAR BOOL IsService; #endif @@ -946,7 +947,7 @@ extern void modulemanager(int argc, char *argv[]); extern int inet_pton4(const char *src, unsigned char *dst); extern int inet_pton6(const char *src, unsigned char *dst); extern int unreal_bind(int fd, const char *ip, int port, SocketType socket_type); -extern int unreal_connect(int fd, const char *ip, int port, int ipv6); +extern int unreal_connect(int fd, const char *ip, int port, SocketType socket_type); extern int is_valid_ip(const char *str); extern int ipv6_capable(void); extern int unix_sockets_capable(void); @@ -1092,14 +1093,6 @@ extern int hide_idle_time(Client *client, Client *target); extern void lost_server_link(Client *serv, const char *tls_error_string); extern const char *sendtype_to_cmd(SendType sendtype); extern MODVAR MessageTagHandler *mtaghandlers; -extern int security_group_valid_name(const char *name); -extern int security_group_exists(const char *name); -extern SecurityGroup *add_security_group(const char *name, int order); -extern SecurityGroup *find_security_group(const char *name); -extern void free_security_group(SecurityGroup *s); -extern void set_security_group_defaults(void); -extern int user_allowed_by_security_group(Client *client, SecurityGroup *s); -extern int user_allowed_by_security_group_name(Client *client, const char *secgroupname); #define nv_find_by_name(stru, name) do_nv_find_by_name(stru, name, ARRAY_SIZEOF((stru))) extern long do_nv_find_by_name(NameValue *table, const char *cmd, int numelements); #define nv_find_by_value(stru, value) do_nv_find_by_value(stru, value, ARRAY_SIZEOF((stru))) @@ -1119,6 +1112,9 @@ extern void add_fmt_nvplist(NameValuePrioList **lst, int priority, const char *n extern void add_nvplist_numeric_fmt(NameValuePrioList **lst, int priority, const char *name, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,6,7))); extern NameValuePrioList *find_nvplist(NameValuePrioList *list, const char *name); extern void free_nvplist(NameValuePrioList *lst); +extern void unreal_add_name_values(NameValuePrioList **n, const char *name, ConfigEntry *ce); +extern const char *namevalue(NameValuePrioList *n); +extern const char *namevalue_nospaces(NameValuePrioList *n); extern const char *get_connect_extinfo(Client *client); extern char *unreal_strftime(const char *str); extern void strtolower(char *str); @@ -1132,6 +1128,30 @@ extern void read_until(char **p, char *stopchars); extern int is_ip_valid(const char *ip); extern int is_file_readable(const char *file, const char *dir); json_t *json_string_unreal(const char *s); +/* securitygroup.c start */ +extern MODVAR SecurityGroup *securitygroups; +extern void unreal_delete_masks(ConfigItem_mask *m); +extern void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce); +extern int unreal_mask_match(Client *acptr, ConfigItem_mask *m); +extern int unreal_mask_match_string(const char *name, ConfigItem_mask *m); +extern int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors); +extern int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out); +extern int test_match_block_too_broad(ConfigFile *conf, ConfigEntry *ce); +extern int security_group_valid_name(const char *name); +extern int security_group_exists(const char *name); +extern SecurityGroup *add_security_group(const char *name, int order); +extern SecurityGroup *find_security_group(const char *name); +extern void free_security_group(SecurityGroup *s); +extern void set_security_group_defaults(void); +extern int user_allowed_by_security_group(Client *client, SecurityGroup *s); +extern int user_allowed_by_security_group_name(Client *client, const char *secgroupname); +extern const char *get_security_groups(Client *client); +extern int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors); +extern int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block); +extern int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out); +extern int conf_match_block(ConfigFile *conf, ConfigEntry *ce, SecurityGroup **block); +extern int test_extended_list(Extban *extban, ConfigEntry *cep, int *errors); +/* securitygroup.c end */ /* src/unrealdb.c start */ extern UnrealDB *unrealdb_open(const char *filename, UnrealDBMode mode, char *secret_block); extern int unrealdb_close(UnrealDB *c); @@ -1249,3 +1269,6 @@ extern void add_proc_io_server(void); extern void procio_post_rehash(int failure); /* end of proc i/o */ extern int minimum_msec_since_last_run(struct timeval *tv_old, long minimum); +extern long get_connected_time(Client *client); +extern const char *StripControlCodes(const char *text); +extern const char *StripControlCodesEx(const char *text, char *output, size_t outputlen, int strip_flags); diff --git a/include/license.h b/include/license.h index 7ec743b..eda3c2b 100644 --- a/include/license.h +++ b/include/license.h @@ -27,10 +27,10 @@ char *gnulicense[] = { " \2UnrealIRCd License\2", - "This program is free software; you can redistribute it and/or modify", - "it under the terms of the GNU General Public License as published by", - "the Free Software Foundation; either version 1, or (at your option)", - "any later version.", + "This program is free software; you can redistribute it and/or", + "modify it under the terms of the GNU General Public License", + "as published by the Free Software Foundation; either version 2", + "of the License, or (at your option) any later version.", "", "This program is distributed in the hope that it will be useful,", "but WITHOUT ANY WARRANTY; without even the implied warranty of", @@ -39,10 +39,11 @@ char *gnulicense[] = { "", "You should have received a copy of the GNU General Public License", "along with this program; if not, write to the Free Software", - "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.", + "Foundation, Inc., 51 Franklin Street, Fifth Floor,", + "Boston, MA 02110-1301, USA.", "", "To see the UnrealIRCd License, please point your browser", - "at http://www.gnu.org/copyleft/gpl.html or look in the", - "file LICENSE in the UnrealIRCd dist", - 0 + "to https://www.gnu.org/licenses/old-licenses/gpl-2.0.html", + "or look at the LICENSE file in the UnrealIRCd distribution.", + NULL }; diff --git a/include/modules.h b/include/modules.h index c535144..3c34c05 100644 --- a/include/modules.h +++ b/include/modules.h @@ -450,8 +450,11 @@ struct Extban { /** extbans module */ Module *owner; - /* Set to 1 during rehash when module is unloading (which may be re-used, and then set to 0) */ + /** Set to 1 during rehash when module is unloading (which may be re-used, and then set to 0) */ char unloaded; + + /** Set to 1 when it is preregistered in MOD_TEST already */ + char preregistered; }; typedef struct { @@ -1097,8 +1100,8 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, l #define HOOKTYPE_SEE_CHANNEL_IN_WHOIS 77 /** See hooktype_join_data() */ #define HOOKTYPE_JOIN_DATA 78 -/** See hooktype_oper_invite_ban() */ -#define HOOKTYPE_OPER_INVITE_BAN 79 +/** See hooktype_invite_bypass() */ +#define HOOKTYPE_INVITE_BYPASS 79 /** See hooktype_view_topic_outside_channel() */ #define HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL 80 /** See hooktype_chan_permit_nick_change() */ @@ -1159,6 +1162,15 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, l #define HOOKTYPE_CAN_SET_TOPIC 110 /** See hooktype_ip_change() */ #define HOOKTYPE_IP_CHANGE 111 +/** See hooktype_json_expand_client() */ +#define HOOKTYPE_JSON_EXPAND_CLIENT 112 +/** See hooktype_json_expand_client() */ +#define HOOKTYPE_JSON_EXPAND_CLIENT_USER 113 +/** See hooktype_json_expand_client() */ +#define HOOKTYPE_JSON_EXPAND_CLIENT_SERVER 114 +/** See hooktype_json_expand_channel() */ +#define HOOKTYPE_JSON_EXPAND_CHANNEL 115 + /* Adding a new hook here? * 1) Add the #define HOOKTYPE_.... with a new number * 2) Add a hook prototype (see below) @@ -1683,9 +1695,10 @@ int hooktype_stats(Client *client, const char *str); * @param client The client * @param add 1 if the user becomes IRCOp, 0 if the user is no longer IRCOp * @param oper_block The name of the oper block used to oper up + * @param operclass The name of the operclass * @return The return value is ignored (use return 0) */ -int hooktype_local_oper(Client *client, int add, ConfigItem_oper *oper_block); +int hooktype_local_oper(Client *client, int add, const char *oper_block, const char *operclass); /** Called when a client sends a PASS command (function prototype for HOOKTYPE_LOCAL_PASS). * @param client The client @@ -1858,14 +1871,16 @@ int hooktype_see_channel_in_whois(Client *client, Client *target, Channel *chann */ int hooktype_join_data(Client *who, Channel *channel); -/** Should the user be able to bypass bans? (function prototype for HOOKTYPE_OPER_INVITE_BAN). +/** Should the user be able to bypass channel restrictions because they are invited? (function prototype for HOOKTYPE_INVITE_BYPASS). * @param client The client * @param channel The channel - * @note The actual meaning of this hook is more complex, you are unlikely to use it, anyway. - * @retval HOOK_DENY Deny the join if the user is also banned + * @retval HOOK_DENY Don't allow the user to bypass channel restrictions when they are invited * @retval HOOK_CONTINUE Obey the normal rules + * @note Usually you want a user to be able to bypass channel restrictions such as +l or +b when they are /INVITEd by another user + * or have invited themselves (OperOverride). But, there may be special cases where you don't want this. + * For example, this hook is used by +O to still not allow ircops to join +O channels even if they have OperOverride capability. */ -int hooktype_oper_invite_ban(Client *client, Channel *channel); +int hooktype_invite_bypass(Client *client, Channel *channel); /** Should a user be able to view the topic when not in the channel? (function prototype for HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL). * @param client The client requesting the topic @@ -2145,6 +2160,46 @@ int hooktype_realname_change(Client *client, const char *oldinfo); */ int hooktype_ip_change(Client *client, const char *oldip); +/** Called when json_expand_client() is called. + * Used for expanding information about 'client' in logging routines. + * @param client The client that should be expanded + * @param detail The amount of detail to provide (always 0 at the moment) + * @param j The JSON object + * @return The return value is ignored (use return 0) + */ +int hooktype_json_expand_client(Client *client, int detail, json_t *j); + +/** Called when json_expand_client_user() is called. + * Used for expanding information about 'client' in logging routines + * when the client is a USER. + * @param client The client that should be expanded + * @param detail The amount of detail to provide (always 0 at the moment) + * @param j The JSON object - root + * @param child The JSON object - "user" child item + * @return The return value is ignored (use return 0) + */ +int hooktype_json_expand_client_user(Client *client, int detail, json_t *j, json_t *child); + +/** Called when json_expand_client_server() is called. + * Used for expanding information about 'client' in logging routines + * when the client is a SERVER. + * @param client The client that should be expanded + * @param detail The amount of detail to provide (always 0 at the moment) + * @param j The JSON object - root + * @param child The JSON object - "server" child item + * @return The return value is ignored (use return 0) + */ +int hooktype_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child); + +/** Called when json_expand_channel() is called. + * Used for expanding information about 'channel' in logging routines. + * @param channel The channel that should be expanded + * @param detail The amount of detail to provide (always 0 at the moment) + * @param j The JSON object + * @return The return value is ignored (use return 0) + */ +int hooktype_json_expand_channel(Channel *channel, int detail, json_t *j); + /** @} */ #ifdef GCC_TYPECHECKING @@ -2223,7 +2278,7 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum ((hooktype == HOOKTYPE_JOIN_DATA) && !ValidateHook(hooktype_join_data, func)) || \ ((hooktype == HOOKTYPE_PRE_KNOCK) && !ValidateHook(hooktype_pre_knock, func)) || \ ((hooktype == HOOKTYPE_PRE_INVITE) && !ValidateHook(hooktype_pre_invite, func)) || \ - ((hooktype == HOOKTYPE_OPER_INVITE_BAN) && !ValidateHook(hooktype_oper_invite_ban, func)) || \ + ((hooktype == HOOKTYPE_INVITE_BYPASS) && !ValidateHook(hooktype_invite_bypass, func)) || \ ((hooktype == HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL) && !ValidateHook(hooktype_view_topic_outside_channel, func)) || \ ((hooktype == HOOKTYPE_CHAN_PERMIT_NICK_CHANGE) && !ValidateHook(hooktype_chan_permit_nick_change, func)) || \ ((hooktype == HOOKTYPE_IS_CHANNEL_SECURE) && !ValidateHook(hooktype_is_channel_secure, func)) || \ @@ -2259,7 +2314,11 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum ((hooktype == HOOKTYPE_POST_REMOTE_NICKCHANGE) && !ValidateHook(hooktype_post_remote_nickchange, func)) || \ ((hooktype == HOOKTYPE_USERHOST_CHANGE) && !ValidateHook(hooktype_userhost_change, func)) || \ ((hooktype == HOOKTYPE_REALNAME_CHANGE) && !ValidateHook(hooktype_realname_change, func)) || \ - ((hooktype == HOOKTYPE_IP_CHANGE) && !ValidateHook(hooktype_ip_change, func)) ) \ + ((hooktype == HOOKTYPE_IP_CHANGE) && !ValidateHook(hooktype_ip_change, func)) || \ + ((hooktype == HOOKTYPE_JSON_EXPAND_CLIENT) && !ValidateHook(hooktype_json_expand_client, func)) || \ + ((hooktype == HOOKTYPE_JSON_EXPAND_CLIENT_USER) && !ValidateHook(hooktype_json_expand_client_user, func)) || \ + ((hooktype == HOOKTYPE_JSON_EXPAND_CLIENT_SERVER) && !ValidateHook(hooktype_json_expand_client_server, func)) || \ + ((hooktype == HOOKTYPE_JSON_EXPAND_CHANNEL) && !ValidateHook(hooktype_json_expand_channel, func)) ) \ _hook_error_incompatible(); #endif /* GCC_TYPECHECKING */ @@ -2315,7 +2374,6 @@ enum EfunctionType { EFUNC_FIND_TKLINE_MATCH_ZAP_EX, EFUNC_SEND_LIST, EFUNC_STRIPCOLORS, - EFUNC_STRIPCONTROLCODES, EFUNC_SPAMFILTER_BUILD_USER_STRING, EFUNC_SEND_PROTOCTL_SERVERS, EFUNC_VERIFY_LINK, @@ -2384,6 +2442,8 @@ enum EfunctionType { EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER, EFUNC_GET_CHMODES_FOR_USER, EFUNC_WHOIS_GET_POLICY, + EFUNC_MAKE_OPER, + EFUNC_UNREAL_MATCH_IPLIST, }; /* Module flags */ diff --git a/include/struct.h b/include/struct.h index e088b77..86d99e6 100644 --- a/include/struct.h +++ b/include/struct.h @@ -559,6 +559,7 @@ typedef enum ClientStatus { #define IsIPV6(x) ((x)->local->socket_type == SOCKET_TYPE_IPV6) #define IsUnixSocket(x) ((x)->local->socket_type == SOCKET_TYPE_UNIX) #define SetIPV6(x) do { (x)->local->socket_type = SOCKET_TYPE_IPV6; } while(0) +#define SetUnixSocket(x) do { (x)->local->socket_type = SOCKET_TYPE_UNIX; } while(0) /** @} */ @@ -608,6 +609,7 @@ union ModData { int i; long l; + long long ll; char *str; void *ptr; }; @@ -762,6 +764,8 @@ struct NameList { /** Delete an entry from a NameList - AND free it */ #define del_name_list(list, str) _del_name_list(&list, str) +extern void unreal_add_names(NameList **n, ConfigEntry *ce); + /** @} */ typedef struct MultiLine MultiLine; @@ -1102,6 +1106,7 @@ struct Spamfilter { struct BanException { char *usermask; /**< User mask */ char *hostmask; /**< Host mask */ + SecurityGroup *match; /**< Security group (for config file items only) */ unsigned short subtype; /**< See TKL_SUBTYPE_* */ char *bantypes; /**< Exception types */ char *reason; /**< Reason */ @@ -1557,7 +1562,7 @@ struct ConfigFlag_allow { struct ConfigItem_allow { ConfigItem_allow *prev, *next; ConfigFlag flag; - ConfigItem_mask *mask; + SecurityGroup *match; char *server; AuthConfig *auth; int maxperip; /**< Maximum connections permitted per IP address (locally) */ @@ -1624,12 +1629,13 @@ struct ConfigItem_oper { AuthConfig *auth; char *operclass; ConfigItem_class *class; - ConfigItem_mask *mask; + SecurityGroup *match; unsigned long modes, require_modes; char *vhost; int maxlogins; int server_notice_colors; int server_notice_show_event; + int auto_login; }; /** The TLS options that are used in set::tls and otherblocks::tls-options. @@ -1679,7 +1685,7 @@ struct ConfigItem_ulines { struct ConfigItem_tld { ConfigItem_tld *prev, *next; ConfigFlag_tld flag; - ConfigItem_mask *mask; + SecurityGroup *match; char *channel; char *motd_file, *rules_file, *smotd_file; char *botmotd_file, *opermotd_file; @@ -1713,7 +1719,7 @@ struct ConfigItem_sni { struct ConfigItem_vhost { ConfigItem_vhost *prev, *next; ConfigFlag flag; - ConfigItem_mask *mask; + SecurityGroup *match; char *login, *virthost, *virtuser; SWhois *swhois; AuthConfig *auth; @@ -1725,9 +1731,10 @@ struct ConfigItem_link { /* config options: */ char *servername; /**< Name of the server ('link { }') */ struct { - ConfigItem_mask *mask; /**< incoming mask(s) to accept */ + SecurityGroup *match; /**< incoming mask(s) to accept */ } incoming; struct { + char *file; /**< UNIX domain socket to connect to */ char *bind_ip; /**< Our IP to bind to when doing the connect */ char *hostname; /**< Hostname or IP to connect to */ int port; /**< Port to connect to */ @@ -1779,14 +1786,14 @@ struct ConfigItem_deny_channel { ConfigFlag flag; char *channel, *reason, *redirect, *class; unsigned char warn; - ConfigItem_mask *mask; + SecurityGroup *match; }; struct ConfigItem_allow_channel { ConfigItem_allow_channel *prev, *next; ConfigFlag flag; char *channel, *class; - ConfigItem_mask *mask; + SecurityGroup *match; }; struct ConfigItem_allow_dcc { @@ -1859,11 +1866,28 @@ struct SecurityGroup { SecurityGroup *prev, *next; int priority; char name[SECURITYGROUPLEN+1]; + NameValuePrioList *printable_list; + int printable_list_counter; + /* Include */ int identified; int reputation_score; + long connect_time; int webirc; int tls; - ConfigItem_mask *include_mask; + NameList *ip; + ConfigItem_mask *mask; + NameList *security_group; + NameValuePrioList *extended; + /* Exclude */ + int exclude_identified; + int exclude_reputation_score; + long exclude_connect_time; + int exclude_webirc; + int exclude_tls; + NameList *exclude_ip; + ConfigItem_mask *exclude_mask; + NameList *exclude_security_group; + NameValuePrioList *exclude_extended; }; #define HM_HOST 1 @@ -2242,6 +2266,10 @@ typedef enum WhoisConfigDetails { WHOIS_CONFIG_DETAILS_FULL = 3, } WhoisConfigDetails; +/* Options for StripControlCodesEx() */ +#define UNRL_STRIP_LOW_ASCII 0x1 /**< Strip all ASCII < 32 (control codes) */ +#define UNRL_STRIP_KEEP_LF 0x2 /**< Do not strip LF (line feed, \n) */ + #endif /* __struct_include__ */ #include "dynconf.h" diff --git a/include/windows/setup.h b/include/windows/setup.h index e4242cc..4360b35 100644 --- a/include/windows/setup.h +++ b/include/windows/setup.h @@ -62,10 +62,10 @@ #define UNREAL_VERSION_MAJOR 0 /* Minor version number (e.g.: 1 for Unreal3.2.1) */ -#define UNREAL_VERSION_MINOR 3 +#define UNREAL_VERSION_MINOR 4 /* Version suffix such as a beta marker or release candidate marker. (e.g.: -rcX for unrealircd-3.2.9-rcX) */ -#define UNREAL_VERSION_SUFFIX "" +#define UNREAL_VERSION_SUFFIX ".2" #endif diff --git a/src/Makefile.in b/src/Makefile.in index 5895561..b33b44b 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -25,7 +25,7 @@ OBJS=ircd_vars.o dns.o auth.o channel.o crule.o dbuf.o \ fdlist.o hash.o ircsprintf.o list.o \ match.o modules.o parse.o mempool.o operclass.o \ conf_preprocessor.o conf.o proc_io_server.o debug.o dispatch.o \ - misc.o serv.o aliases.o socket.o \ + securitygroup.o misc.o serv.o aliases.o socket.o \ tls.o user.o scache.o send.o support.o \ version.o whowas.o random.o api-usermode.o api-channelmode.o \ api-moddata.o api-extban.o api-isupport.o api-command.o \ diff --git a/src/api-efunctions.c b/src/api-efunctions.c index d6dbfdc..05eee26 100644 --- a/src/api-efunctions.c +++ b/src/api-efunctions.c @@ -57,8 +57,9 @@ TKL *(*tkl_add_spamfilter)(int type, unsigned short target, unsigned short actio time_t expire_at, time_t set_at, time_t spamf_tkl_duration, const char *spamf_tkl_reason, int flags); -TKL *(*tkl_add_banexception)(int type, const char *usermask, const char *hostmask, const char *reason, const char *set_by, - time_t expire_at, time_t set_at, int soft, const char *bantypes, int flags); +TKL *(*tkl_add_banexception)(int type, const char *usermask, const char *hostmask, SecurityGroup *match, + const char *reason, const char *set_by, + time_t expire_at, time_t set_at, int soft, const char *bantypes, int flags); TKL *(*tkl_del_line)(TKL *tkl); void (*tkl_check_local_remove_shun)(TKL *tmp); int (*find_tkline_match)(Client *client, int skip_soft); @@ -74,10 +75,9 @@ int (*match_spamfilter)(Client *client, const char *str_in, int type, const char int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, const char *cmd); int (*join_viruschan)(Client *client, TKL *tk, int type); const char *(*StripColors)(const char *text); -const char *(*StripControlCodes)(const char *text); void (*spamfilter_build_user_string)(char *buf, const char *nick, Client *client); void (*send_protoctl_servers)(Client *client, int response); -int (*verify_link)(Client *client, ConfigItem_link **link_out); +ConfigItem_link *(*verify_link)(Client *client); void (*introduce_user)(Client *to, Client *client); void (*send_server_message)(Client *client); void (*broadcast_md_client)(ModDataInfo *mdi, Client *client, ModData *md); @@ -135,6 +135,8 @@ int (*watch_check)(Client *client, int reply, int (*watch_notify)(Client *client void (*do_unreal_log_remote_deliver)(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); char *(*get_chmodes_for_user)(Client *client, const char *flags); WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name); +int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); +int (*unreal_match_iplist)(Client *client, NameList *l); Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)()) { @@ -339,7 +341,6 @@ void efunctions_init(void) 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); efunc_init_function(EFUNC_SEND_PROTOCTL_SERVERS, send_protoctl_servers, NULL); efunc_init_function(EFUNC_VERIFY_LINK, verify_link, NULL); @@ -406,4 +407,6 @@ void efunctions_init(void) efunc_init_function(EFUNC_DO_UNREAL_LOG_REMOTE_DELIVER, do_unreal_log_remote_deliver, do_unreal_log_remote_deliver_default_handler); efunc_init_function(EFUNC_GET_CHMODES_FOR_USER, get_chmodes_for_user, NULL); efunc_init_function(EFUNC_WHOIS_GET_POLICY, whois_get_policy, NULL); + efunc_init_function(EFUNC_MAKE_OPER, make_oper, make_oper_default_handler); + efunc_init_function(EFUNC_UNREAL_MATCH_IPLIST, unreal_match_iplist, NULL); } diff --git a/src/api-extban.c b/src/api-extban.c index a750689..343c5f6 100644 --- a/src/api-extban.c +++ b/src/api-extban.c @@ -38,9 +38,27 @@ void set_isupport_extban(void) ISupportSetFmt(NULL, "EXTBAN", "~,%s", extbanstr); } -Extban *findmod_by_bantype(const char *str, const char **remainder) +Extban *findmod_by_bantype_raw(const char *str, int ban_name_length) { Extban *e; + + for (e=extbans; e; e = e->next) + { + if ((ban_name_length == 1) && (e->letter == str[0])) + return e; + if (e->name) + { + int namelen = strlen(e->name); + if ((namelen == ban_name_length) && !strncmp(e->name, str, namelen)) + return e; + } + } + + return NULL; +} + +Extban *findmod_by_bantype(const char *str, const char **remainder) +{ int ban_name_length; const char *p = strchr(str, ':'); @@ -54,20 +72,7 @@ Extban *findmod_by_bantype(const char *str, const char **remainder) *remainder = p+1; ban_name_length = p - str - 1; - - for (e=extbans; e; e = e->next) - { - if ((ban_name_length == 1) && (e->letter == str[1])) - return e; - if (e->name) - { - int namelen = strlen(e->name); - if ((namelen == ban_name_length) && !strncmp(e->name, str+1, namelen)) - return e; - } - } - - return NULL; + return findmod_by_bantype_raw(str+1, ban_name_length); } /* Check if this is a valid extended ban name */ @@ -121,6 +126,7 @@ static void extban_add_sorted(Extban *n) Extban *ExtbanAdd(Module *module, ExtbanInfo req) { Extban *e; + ModuleObject *banobj; int existing = 0; if (!req.name) @@ -167,14 +173,35 @@ Extban *ExtbanAdd(Module *module, ExtbanInfo req) { if (e->letter == req.letter) { + /* Extban already exists in our list, let's see... */ if (e->unloaded) { e->unloaded = 0; existing = 1; break; - } else { - if (module) - module->errorcode = MODERR_EXISTS; + } else + if ((module->flags == MODFLAG_TESTING) && e->preregistered) + { + /* We are in MOD_INIT (yeah confusing, isn't it?) + * and the extban already exists and it was preregistered. + * Then go ahead with really registering it. + */ + e->preregistered = 0; + existing = 1; + } else + if (module->flags == MODFLAG_NONE) + { + /* Better don't touch it, as we may still fail at this stage + * and if we would set .conv_param etc to this and the new module + * gets unloaded because of a config typo then we would be screwed + * (now we are not). + * NOTE: this does mean that if you hot-load an extban module + * then it may only be available for config stuff the 2nd rehash. + */ + return e; + } else + { + module->errorcode = MODERR_EXISTS; return NULL; } } @@ -195,14 +222,16 @@ Extban *ExtbanAdd(Module *module, ExtbanInfo req) e->is_banned_events = req.is_banned_events; e->owner = module; e->options = req.options; - if (module) - { - ModuleObject *banobj = safe_alloc(sizeof(ModuleObject)); - banobj->object.extban = e; - banobj->type = MOBJ_EXTBAN; - AddListItem(banobj, module->objects); - module->errorcode = MODERR_NOERROR; - } + + if (module->flags == MODFLAG_NONE) + e->preregistered = 1; + + banobj = safe_alloc(sizeof(ModuleObject)); + banobj->object.extban = e; + banobj->type = MOBJ_EXTBAN; + AddListItem(banobj, module->objects); + module->errorcode = MODERR_NOERROR; + set_isupport_extban(); return e; } @@ -217,10 +246,27 @@ static void unload_extban_commit(Extban *e) /* Then unload the extban */ DelListItem(e, extbans); + safe_free(e->name); safe_free(e); set_isupport_extban(); } +/** Unload all unused extended bans after a REHASH */ +void unload_all_unused_extbans(void) +{ + Extban *e, *e_next; + + for (e=extbans; e; e = e_next) + { + e_next = e->next; + if (e->letter && e->unloaded) + { + unload_extban_commit(e); + } + } + +} + void ExtbanDel(Extban *e) { /* Always free the module object */ diff --git a/src/channel.c b/src/channel.c index a465f20..385d992 100644 --- a/src/channel.c +++ b/src/channel.c @@ -40,9 +40,6 @@ long sajoinmode = 0; */ Channel *channels = NULL; -/* A buffer for rebuilding channel/nick lists with comma's */ -static char buf[BUFSIZE]; - static mp_pool_t *channel_pool = NULL; /** This describes the letters, modes and options for core channel modes. diff --git a/src/conf.c b/src/conf.c index 24c551e..a01daf1 100644 --- a/src/conf.c +++ b/src/conf.c @@ -62,7 +62,7 @@ static int _conf_alias (ConfigFile *conf, ConfigEntry *ce); static int _conf_help (ConfigFile *conf, ConfigEntry *ce); static int _conf_offchans (ConfigFile *conf, ConfigEntry *ce); static int _conf_sni (ConfigFile *conf, ConfigEntry *ce); -static int _conf_security_group (ConfigFile *conf, ConfigEntry *ce); +extern int _conf_security_group (ConfigFile *conf, ConfigEntry *ce); static int _conf_secret (ConfigFile *conf, ConfigEntry *ce); /* @@ -95,7 +95,7 @@ static int _test_alias (ConfigFile *conf, ConfigEntry *ce); static int _test_help (ConfigFile *conf, ConfigEntry *ce); static int _test_offchans (ConfigFile *conf, ConfigEntry *ce); static int _test_sni (ConfigFile *conf, ConfigEntry *ce); -static int _test_security_group (ConfigFile *conf, ConfigEntry *ce); +extern int _test_security_group (ConfigFile *conf, ConfigEntry *ce); static int _test_secret (ConfigFile *conf, ConfigEntry *ce); /* This MUST be alphabetized */ @@ -185,6 +185,7 @@ ConfigEntry *config_find_entry(ConfigEntry *ce, const char *name); extern void add_entropy_configfile(struct stat *st, const char *buf); extern void unload_all_unused_umodes(void); extern void unload_all_unused_extcmodes(void); +extern void unload_all_unused_extbans(void); extern void unload_all_unused_caps(void); extern void unload_all_unused_history_backends(void); int reloadable_perm_module_unloaded(void); @@ -232,7 +233,6 @@ ConfigResource *config_resources = NULL; ConfigItem_blacklist_module *conf_blacklist_module = NULL; ConfigItem_help *conf_help = NULL; ConfigItem_offchans *conf_offchans = NULL; -SecurityGroup *securitygroups = NULL; Secret *secrets = NULL; MODVAR Configuration iConf; @@ -577,6 +577,11 @@ long config_checkval(const char *orig, unsigned short flags) /** Free configuration setting for set::modes-on-join */ void free_conf_channelmodes(struct ChMode *store) { + int i; + + for (i=0; i < 255; i++) + safe_free(store->extparams[i]); + memset(store, 0, sizeof(struct ChMode)); } @@ -2353,7 +2358,7 @@ void config_rehash() safe_free(oper_ptr->operclass); safe_free(oper_ptr->vhost); Auth_FreeAuthConfig(oper_ptr->auth); - unreal_delete_masks(oper_ptr->mask); + free_security_group(oper_ptr->match); DelListItem(oper_ptr, conf_oper); for (s = oper_ptr->swhois; s; s = s_next) { @@ -2400,7 +2405,7 @@ void config_rehash() for (allow_ptr = conf_allow; allow_ptr; allow_ptr = (ConfigItem_allow *) next) { next = (ListStruct *)allow_ptr->next; - unreal_delete_masks(allow_ptr->mask); + free_security_group(allow_ptr->match); Auth_FreeAuthConfig(allow_ptr->auth); DelListItem(allow_ptr, conf_allow); safe_free(allow_ptr); @@ -2437,6 +2442,8 @@ void config_rehash() free_motd(&tld_ptr->opermotd); free_motd(&tld_ptr->botmotd); + free_security_group(tld_ptr->match); + DelListItem(tld_ptr, conf_tld); safe_free(tld_ptr); } @@ -2450,7 +2457,7 @@ void config_rehash() Auth_FreeAuthConfig(vhost_ptr->auth); safe_free(vhost_ptr->virthost); safe_free(vhost_ptr->virtuser); - unreal_delete_masks(vhost_ptr->mask); + free_security_group(vhost_ptr->match); for (s = vhost_ptr->swhois; s; s = s_next) { s_next = s->next; @@ -2488,7 +2495,7 @@ void config_rehash() safe_free(deny_channel_ptr->reason); safe_free(deny_channel_ptr->class); DelListItem(deny_channel_ptr, conf_deny_channel); - unreal_delete_masks(deny_channel_ptr->mask); + free_security_group(deny_channel_ptr->match); safe_free(deny_channel_ptr); } @@ -2498,7 +2505,7 @@ void config_rehash() safe_free(allow_channel_ptr->channel); safe_free(allow_channel_ptr->class); DelListItem(allow_channel_ptr, conf_allow_channel); - unreal_delete_masks(allow_channel_ptr->mask); + free_security_group(allow_channel_ptr->match); safe_free(allow_channel_ptr); } @@ -3036,7 +3043,7 @@ ConfigItem_tld *find_tld(Client *client) for (tld = conf_tld; tld; tld = tld->next) { - if (unreal_mask_match(client, tld->mask)) + if (user_allowed_by_security_group(client, tld->match)) { if ((tld->options & TLD_TLS) && !IsSecureConnect(client)) continue; @@ -3056,7 +3063,8 @@ ConfigItem_link *find_link(const char *servername, Client *client) for (link = conf_link; link; link = link->next) { - if (match_simple(link->servername, servername) && unreal_mask_match(client, link->incoming.mask)) + if (match_simple(link->servername, servername) && + user_allowed_by_security_group(client, link->incoming.match)) { return link; } @@ -3137,7 +3145,7 @@ ConfigItem_deny_channel *find_channel_allowed(Client *client, const char *name) { if (dchannel->class && strcmp(client->local->class->name, dchannel->class)) continue; - if (dchannel->mask && !unreal_mask_match(client, dchannel->mask)) + if (dchannel->match && !user_allowed_by_security_group(client, dchannel->match)) continue; break; /* MATCH deny channel { } */ } @@ -3152,7 +3160,7 @@ ConfigItem_deny_channel *find_channel_allowed(Client *client, const char *name) { if (achannel->class && strcmp(client->local->class->name, achannel->class)) continue; - if (achannel->mask && !unreal_mask_match(client, achannel->mask)) + if (achannel->match && !user_allowed_by_security_group(client, achannel->match)) continue; break; /* MATCH allow channel { } */ } @@ -3935,6 +3943,7 @@ int _conf_oper(ConfigFile *conf, ConfigEntry *ce) oper = safe_alloc(sizeof(ConfigItem_oper)); safe_strdup(oper->name, ce->value); + oper->match = safe_alloc(sizeof(SecurityGroup)); /* Inherit some defaults: */ oper->server_notice_colors = tempiConf.server_notice_colors; @@ -3990,6 +3999,10 @@ int _conf_oper(ConfigFile *conf, ConfigEntry *ce) { oper->server_notice_show_event = config_checkval(cep->value, CFG_YESNO); } + else if (!strcmp(cep->name, "auto-login")) + { + oper->auto_login = config_checkval(cep->value, CFG_YESNO); + } else if (!strcmp(cep->name, "modes")) { oper->modes = set_usermode(cep->value); @@ -4002,9 +4015,9 @@ int _conf_oper(ConfigFile *conf, ConfigEntry *ce) { oper->maxlogins = atoi(cep->value); } - else if (!strcmp(cep->name, "mask")) + else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match")) { - unreal_add_masks(&oper->mask, cep); + conf_match_block(conf, cep, &oper->match); } else if (!strcmp(cep->name, "vhost")) { @@ -4018,8 +4031,8 @@ int _conf_oper(ConfigFile *conf, ConfigEntry *ce) int _test_oper(ConfigFile *conf, ConfigEntry *ce) { char has_class = 0, has_password = 0, has_snomask = 0; - char has_modes = 0, has_require_modes = 0, has_mask = 0, has_maxlogins = 0; - char has_operclass = 0, has_vhost = 0; + char has_modes = 0, has_require_modes = 0, has_mask = 0, has_match = 0, has_broad_match = 0; + char has_maxlogins = 0, has_operclass = 0, has_vhost = 0, has_auto_login = 0; ConfigEntry *cep; int errors = 0; @@ -4125,6 +4138,10 @@ int _test_oper(ConfigFile *conf, ConfigEntry *ce) else if (!strcmp(cep->name, "server-notice-show-event")) { } + else if (!strcmp(cep->name, "auto-login")) + { + has_auto_login = config_checkval(cep->value, CFG_YESNO); + } /* oper::modes */ else if (!strcmp(cep->name, "modes")) { @@ -4187,7 +4204,22 @@ int _test_oper(ConfigFile *conf, ConfigEntry *ce) else if (!strcmp(cep->name, "mask")) { if (cep->value || cep->items) + { has_mask = 1; + test_match_block(conf, cep, &errors); + if (test_match_block_too_broad(conf, cep)) + has_broad_match = 1; + } + } + else if (!strcmp(cep->name, "match")) + { + if (cep->value || cep->items) + { + has_match = 1; + test_match_block(conf, cep, &errors); + if (test_match_block_too_broad(conf, cep)) + has_broad_match = 1; + } } else { @@ -4207,7 +4239,22 @@ int _test_oper(ConfigFile *conf, ConfigEntry *ce) else if (!strcmp(cep->name, "mask")) { if (cep->value || cep->items) + { has_mask = 1; + test_match_block(conf, cep, &errors); + if (test_match_block_too_broad(conf, cep)) + has_broad_match = 1; + } + } + else if (!strcmp(cep->name, "match")) + { + if (cep->value || cep->items) + { + has_match = 1; + test_match_block(conf, cep, &errors); + if (test_match_block_too_broad(conf, cep)) + has_broad_match = 1; + } } else if (!strcmp(cep->name, "password")) { @@ -4230,16 +4277,39 @@ int _test_oper(ConfigFile *conf, ConfigEntry *ce) } } } - if (!has_password) + + if (has_auto_login && has_broad_match) { - config_error_missing(ce->file->filename, ce->line_number, - "oper::password"); + config_error("%s:%i: your oper block for '%s' has auto-login but is completely unrestricted (mask *@*)!", + ce->file->filename, ce->line_number, ce->value); + errors++; + } else + if (!has_password && has_broad_match) + { + config_error("%s:%i: your oper block for '%s' has no password and is completely unrestricted (mask *@*)!", + ce->file->filename, ce->line_number, ce->value); errors++; } - if (!has_mask) + + /* The rest should NOT be in an 'else'... */ + if (has_password && has_auto_login) + { + config_error("%s:%i: You have auto-login enabled for your oper block '%s' but you also have a password set. " + "Remove the password if you want to use auto-login.", + ce->file->filename, ce->line_number, ce->value); + errors++; + } + if (!has_mask && !has_match) { config_error_missing(ce->file->filename, ce->line_number, - "oper::mask"); + "oper::match"); + errors++; + } + if (has_mask && has_match) + { + config_error("%s:%d: You cannot have both ::mask and ::match. " + "You should only use oper::match.", + ce->file->filename, ce->line_number); errors++; } if (!has_class) @@ -4590,8 +4660,8 @@ int _conf_tld(ConfigFile *conf, ConfigEntry *ce) for (cep = ce->items; cep; cep = cep->next) { - if (!strcmp(cep->name, "mask")) - unreal_add_masks(&ca->mask, cep); + if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask")) + conf_match_block(conf, cep, &ca->match); else if (!strcmp(cep->name, "motd")) { safe_strdup(ca->motd_file, cep->value); @@ -4640,12 +4710,12 @@ int _test_tld(ConfigFile *conf, ConfigEntry *ce) ConfigEntry *cep; int errors = 0; int fd = -1; - char has_mask = 0, has_motd = 0, has_rules = 0, has_shortmotd = 0, has_channel = 0; - char has_opermotd = 0, has_botmotd = 0, has_options = 0; + char has_mask = 0, has_match = 0, has_motd = 0, has_rules = 0, has_shortmotd = 0; + char has_channel = 0, has_opermotd = 0, has_botmotd = 0, has_options = 0; for (cep = ce->items; cep; cep = cep->next) { - if (!cep->value && strcmp(cep->name, "options")) + if (!cep->value && strcmp(cep->name, "options") && strcmp(cep->name, "mask") && strcmp(cep->name, "match")) { config_error_empty(cep->file->filename, cep->line_number, "tld", cep->name); @@ -4656,7 +4726,18 @@ int _test_tld(ConfigFile *conf, ConfigEntry *ce) if (!strcmp(cep->name, "mask")) { if (cep->value || cep->items) + { has_mask = 1; + test_match_block(conf, cep, &errors); + } + } + else if (!strcmp(cep->name, "match")) + { + if (cep->value || cep->items) + { + has_match = 1; + test_match_block(conf, cep, &errors); + } } /* tld::motd */ else if (!strcmp(cep->name, "motd")) @@ -4806,10 +4887,17 @@ int _test_tld(ConfigFile *conf, ConfigEntry *ce) continue; } } - if (!has_mask) + if (!has_mask && !has_match) { config_error_missing(ce->file->filename, ce->line_number, - "tld::mask"); + "tld::match"); + errors++; + } + if (has_mask && has_match) + { + config_error("%s:%d: You cannot have both ::mask and ::match. " + "You should only use %s::match.", + ce->file->filename, ce->line_number, ce->name); errors++; } if (!has_motd) @@ -5342,12 +5430,13 @@ int _conf_allow(ConfigFile *conf, ConfigEntry *ce) } allow = safe_alloc(sizeof(ConfigItem_allow)); allow->ipv6_clone_mask = tempiConf.default_ipv6_clone_mask; + allow->match = safe_alloc(sizeof(SecurityGroup)); for (cep = ce->items; cep; cep = cep->next) { - if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "ip") || !strcmp(cep->name, "hostname")) + if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask") || !strcmp(cep->name, "ip") || !strcmp(cep->name, "hostname")) { - unreal_add_masks(&allow->mask, cep); + conf_match_block(conf, cep, &allow->match); } else if (!strcmp(cep->name, "password")) allow->auth = AuthBlockToAuthConfig(cep); @@ -5413,7 +5502,7 @@ int _test_allow(ConfigFile *conf, ConfigEntry *ce) ConfigEntry *cep, *cepp; int errors = 0; Hook *h; - char has_ip = 0, has_hostname = 0, has_mask = 0; + char has_ip = 0, has_hostname = 0, has_mask = 0, has_match = 0; char has_maxperip = 0, has_global_maxperip = 0, has_password = 0, has_class = 0; char has_redirectserver = 0, has_redirectport = 0, has_options = 0; int hostname_possible_silliness = 0; @@ -5463,6 +5552,7 @@ int _test_allow(ConfigFile *conf, ConfigEntry *ce) for (cep = ce->items; cep; cep = cep->next) { if (strcmp(cep->name, "options") && + strcmp(cep->name, "match") && strcmp(cep->name, "mask") && config_is_blankorempty(cep, "allow")) { @@ -5494,6 +5584,12 @@ int _test_allow(ConfigFile *conf, ConfigEntry *ce) else if (!strcmp(cep->name, "mask")) { has_mask = 1; + test_match_block(conf, cep, &errors); + } + else if (!strcmp(cep->name, "match")) + { + has_match = 1; + test_match_block(conf, cep, &errors); } else if (!strcmp(cep->name, "maxperip")) { @@ -5641,27 +5737,33 @@ int _test_allow(ConfigFile *conf, ConfigEntry *ce) } } - if (has_mask && (has_ip || has_hostname)) + if ((has_mask || has_match) && (has_ip || has_hostname)) { - config_error("%s:%d: The allow block uses allow::mask, but you also have an allow::ip and allow::hostname.", + config_error("%s:%d: The allow block uses allow::match, but you also have an allow::ip and allow::hostname.", ce->file->filename, ce->line_number); - config_error("Please delete your allow::ip and allow::hostname entries and/or integrate them into allow::mask"); + config_error("Please delete your allow::ip and allow::hostname entries and/or integrate them into allow::match"); } else if (has_ip) { - config_warn("%s:%d: The allow block uses allow::mask nowadays. Rename your allow::ip item to allow::mask.", + config_warn("%s:%d: The allow block uses allow::match nowadays. Rename your allow::ip item to allow::match.", ce->file->filename, ce->line_number); config_warn("See https://www.unrealircd.org/docs/FAQ#allow-mask for more information"); } else if (has_hostname) { - config_warn("%s:%d: The allow block uses allow::mask nowadays. Rename your allow::hostname item to allow::mask.", + config_warn("%s:%d: The allow block uses allow::match nowadays. Rename your allow::hostname item to allow::match.", ce->file->filename, ce->line_number); config_warn("See https://www.unrealircd.org/docs/FAQ#allow-mask for more information"); } else - if (!has_mask) + if (has_mask && has_match) { - config_error("%s:%d: allow block needs an allow::mask", + config_error("%s:%d: You cannot have both ::mask and ::match. You should only use allow::match.", + ce->file->filename, ce->line_number); + errors++; + } else + if (!has_match && !has_mask) + { + config_error("%s:%d: allow block needs an allow::match", ce->file->filename, ce->line_number); errors++; } @@ -5702,15 +5804,15 @@ int _conf_allow_channel(ConfigFile *conf, ConfigEntry *ce) ConfigItem_allow_channel *allow = NULL; ConfigEntry *cep; char *class = NULL; - ConfigEntry *mask = NULL; + ConfigEntry *match = NULL; /* First, search for ::class, if any */ for (cep = ce->items; cep; cep = cep->next) { if (!strcmp(cep->name, "class")) class = cep->value; - else if (!strcmp(cep->name, "mask")) - mask = cep; + else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask")) + match = cep; } for (cep = ce->items; cep; cep = cep->next) @@ -5722,8 +5824,8 @@ int _conf_allow_channel(ConfigFile *conf, ConfigEntry *ce) safe_strdup(allow->channel, cep->value); if (class) safe_strdup(allow->class, class); - if (mask) - unreal_add_masks(&allow->mask, mask); + if (match) + conf_match_block(conf, match, &allow->match); AddListItem(allow, conf_allow_channel); } } @@ -5732,9 +5834,10 @@ int _conf_allow_channel(ConfigFile *conf, ConfigEntry *ce) int _test_allow_channel(ConfigFile *conf, ConfigEntry *ce) { - ConfigEntry *cep; - int errors = 0; - char has_channel = 0, has_class = 0; + ConfigEntry *cep; + int errors = 0; + char has_match = 0, has_mask = 0, has_channel = 0, has_class = 0; + for (cep = ce->items; cep; cep = cep->next) { if (config_is_blankorempty(cep, "allow channel")) @@ -5758,8 +5861,15 @@ int _test_allow_channel(ConfigFile *conf, ConfigEntry *ce) } has_class = 1; } + else if (!strcmp(cep->name, "match")) + { + has_match = 1; + test_match_block(conf, cep, &errors); + } else if (!strcmp(cep->name, "mask")) { + has_mask = 1; + test_match_block(conf, cep, &errors); } else { @@ -5768,6 +5878,13 @@ int _test_allow_channel(ConfigFile *conf, ConfigEntry *ce) errors++; } } + if (has_mask && has_match) + { + config_error("%s:%d: You cannot have both ::mask and ::match. " + "You should only use %s::match.", + ce->file->filename, ce->line_number, ce->name); + errors++; + } if (!has_channel) { config_error_missing(ce->file->filename, ce->line_number, @@ -5860,6 +5977,7 @@ int _conf_vhost(ConfigFile *conf, ConfigEntry *ce) ConfigItem_vhost *vhost; ConfigEntry *cep, *cepp; vhost = safe_alloc(sizeof(ConfigItem_vhost)); + vhost->match = safe_alloc(sizeof(SecurityGroup)); for (cep = ce->items; cep; cep = cep->next) { @@ -5880,9 +5998,9 @@ int _conf_vhost(ConfigFile *conf, ConfigEntry *ce) safe_strdup(vhost->login, cep->value); else if (!strcmp(cep->name, "password")) vhost->auth = AuthBlockToAuthConfig(cep); - else if (!strcmp(cep->name, "mask")) + else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask")) { - unreal_add_masks(&vhost->mask, cep); + conf_match_block(conf, cep, &vhost->match); } else if (!strcmp(cep->name, "swhois")) { @@ -5914,7 +6032,7 @@ int _test_vhost(ConfigFile *conf, ConfigEntry *ce) { int errors = 0; ConfigEntry *cep; - char has_vhost = 0, has_login = 0, has_password = 0, has_mask = 0; + char has_vhost = 0, has_login = 0, has_password = 0, has_mask = 0, has_match = 0; for (cep = ce->items; cep; cep = cep->next) { @@ -6007,6 +6125,12 @@ int _test_vhost(ConfigFile *conf, ConfigEntry *ce) else if (!strcmp(cep->name, "mask")) { has_mask = 1; + test_match_block(conf, cep, &errors); + } + else if (!strcmp(cep->name, "match")) + { + has_match = 1; + test_match_block(conf, cep, &errors); } else if (!strcmp(cep->name, "swhois")) { @@ -6038,10 +6162,17 @@ int _test_vhost(ConfigFile *conf, ConfigEntry *ce) "vhost::password"); errors++; } - if (!has_mask) + if (!has_mask && !has_match) { config_error_missing(ce->file->filename, ce->line_number, - "vhost::mask"); + "vhost::match"); + errors++; + } + if (has_mask && has_match) + { + config_error("%s:%d: You cannot have both ::mask and ::match. " + "You should only use %s::match.", + ce->file->filename, ce->line_number, ce->name); errors++; } return errors; @@ -6172,9 +6303,9 @@ int _conf_link(ConfigFile *conf, ConfigEntry *ce) { for (cepp = cep->items; cepp; cepp = cepp->next) { - if (!strcmp(cepp->name, "mask")) + if (!strcmp(cepp->name, "match") || !strcmp(cepp->name, "mask")) { - unreal_add_masks(&link->incoming.mask, cepp); + conf_match_block(conf, cepp, &link->incoming.match); } } } @@ -6184,6 +6315,8 @@ int _conf_link(ConfigFile *conf, ConfigEntry *ce) { if (!strcmp(cepp->name, "bind-ip")) safe_strdup(link->outgoing.bind_ip, cepp->value); + else if (!strcmp(cepp->name, "file")) + safe_strdup(link->outgoing.file, cepp->value); else if (!strcmp(cepp->name, "hostname")) safe_strdup(link->outgoing.hostname, cepp->value); else if (!strcmp(cepp->name, "port")) @@ -6273,7 +6406,7 @@ int _test_link(ConfigFile *conf, ConfigEntry *ce) ConfigEntry *cep, *cepp, *ceppp; int errors = 0; - int has_incoming = 0, has_incoming_mask = 0, has_outgoing = 0; + int has_incoming = 0, has_incoming_mask = 0, has_incoming_match = 0, has_outgoing = 0, has_outgoing_file = 0; int has_outgoing_bind_ip = 0, has_outgoing_hostname = 0, has_outgoing_port = 0; int has_outgoing_options = 0, has_hub = 0, has_leaf = 0, has_leaf_depth = 0; int has_password = 0, has_class = 0, has_options = 0; @@ -6300,11 +6433,26 @@ int _test_link(ConfigFile *conf, ConfigEntry *ce) config_detect_duplicate(&has_incoming, cep, &errors); for (cepp = cep->items; cepp; cepp = cepp->next) { + if (!strcmp(cepp->name, "match")) + { + if (cepp->value || cepp->items) + { + has_incoming_match = 1; + test_match_block(conf, cepp, &errors); + } else + if (config_is_blankorempty(cepp, "link::incoming")) + { + errors++; + continue; + } + } else if (!strcmp(cepp->name, "mask")) { if (cepp->value || cepp->items) + { has_incoming_mask = 1; - else + test_match_block(conf, cepp, &errors); + } else if (config_is_blankorempty(cepp, "link::incoming")) { errors++; @@ -6328,6 +6476,15 @@ int _test_link(ConfigFile *conf, ConfigEntry *ce) config_detect_duplicate(&has_outgoing_bind_ip, cepp, &errors); // todo: ipv4 vs ipv6 } + else if (!strcmp(cepp->name, "file")) + { + if (config_is_blankorempty(cepp, "link::outgoing")) + { + errors++; + continue; + } + config_detect_duplicate(&has_outgoing_file, cepp, &errors); + } else if (!strcmp(cepp->name, "hostname")) { if (config_is_blankorempty(cepp, "link::outgoing")) @@ -6491,24 +6648,41 @@ int _test_link(ConfigFile *conf, ConfigEntry *ce) if (has_incoming) { /* If we have an incoming sub-block then we need at least 'mask' and 'password' */ - if (!has_incoming_mask) + if (!has_incoming_mask && !has_incoming_match) { - config_error_missing(ce->file->filename, ce->line_number, "link::incoming::mask"); + config_error_missing(ce->file->filename, ce->line_number, "link::incoming::match"); + errors++; + } + if (has_incoming_mask && has_incoming_match) + { + config_error("%s:%d: You cannot have both link::incoming::mask and link::incoming::match. " + "You should only use link::incoming::match.", + ce->file->filename, ce->line_number); errors++; } } if (has_outgoing) { - /* If we have an outgoing sub-block then we need at least a hostname and port */ - if (!has_outgoing_hostname) + /* If we have an outgoing sub-block then we need at least a hostname and port or a file */ + if (!has_outgoing_file) { - config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::hostname"); - errors++; + if (!has_outgoing_hostname) + { + config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::hostname"); + errors++; + } + if (!has_outgoing_port) + { + config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::port"); + errors++; + } } - if (!has_outgoing_port) + else if (has_outgoing_file && (has_outgoing_hostname || has_outgoing_port)) { - config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::port"); + config_error("%s:%d: link block should either have a 'file' (for *NIX domain socket), " + "OR have a 'hostname' and 'port' (for IPv4/IPv6). You cannot combine both in one link block.", + ce->file->filename, ce->line_number); errors++; } } @@ -8965,6 +9139,7 @@ int _test_set(ConfigFile *conf, ConfigEntry *ce) { /* keep this in sync with _test_allow() */ int ipv6mask; + CheckNull(cep); ipv6mask = atoi(cep->value); if (ipv6mask == 0) { @@ -9818,9 +9993,9 @@ int _conf_deny_channel(ConfigFile *conf, ConfigEntry *ce) { safe_strdup(deny->class, cep->value); } - else if (!strcmp(cep->name, "mask")) + else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask")) { - unreal_add_masks(&deny->mask, cep); + conf_match_block(conf, cep, &deny->match); } } AddListItem(deny, conf_deny_channel); @@ -9894,6 +10069,7 @@ int _test_deny(ConfigFile *conf, ConfigEntry *ce) if (!strcmp(ce->value, "channel")) { char has_channel = 0, has_warn = 0, has_reason = 0, has_redirect = 0, has_class = 0; + char has_mask = 0, has_match = 0; for (cep = ce->items; cep; cep = cep->next) { if (config_is_blankorempty(cep, "deny channel")) @@ -9951,8 +10127,15 @@ int _test_deny(ConfigFile *conf, ConfigEntry *ce) } has_class = 1; } + else if (!strcmp(cep->name, "match")) + { + has_match = 1; + test_match_block(conf, cep, &errors); + } else if (!strcmp(cep->name, "mask")) { + has_mask = 1; + test_match_block(conf, cep, &errors); } else { @@ -9973,6 +10156,13 @@ int _test_deny(ConfigFile *conf, ConfigEntry *ce) "deny channel::reason"); errors++; } + if (has_mask && has_match) + { + config_error("%s:%d: You cannot have both ::mask and ::match. " + "You should only use %s %s::match.", + ce->file->filename, ce->line_number, ce->name, ce->value); + errors++; + } } else if (!strcmp(ce->value, "link")) { @@ -10177,104 +10367,6 @@ int _test_deny(ConfigFile *conf, ConfigEntry *ce) return errors; } -int _test_security_group(ConfigFile *conf, ConfigEntry *ce) -{ - int errors = 0; - ConfigEntry *cep; - - if (!ce->value) - { - config_error("%s:%i: security-group block needs a name, eg: security-group web-users {", - ce->file->filename, ce->line_number); - errors++; - } else { - if (!strcasecmp(ce->value, "unknown-users")) - { - config_error("%s:%i: The 'unknown-users' group is a special group that is the " - "inverse of 'known-users', you cannot create or adjust it in the " - "config file, as it is created automatically by UnrealIRCd.", - ce->file->filename, ce->line_number); - errors++; - return errors; - } - if (!security_group_valid_name(ce->value)) - { - config_error("%s:%i: security-group block name '%s' contains invalid characters or is too long. " - "Only letters, numbers, underscore and hyphen are allowed.", - ce->file->filename, ce->line_number, ce->value); - errors++; - } - } - - for (cep = ce->items; cep; cep = cep->next) - { - if (!strcmp(cep->name, "webirc")) - { - CheckNull(cep); - } else - if (!strcmp(cep->name, "identified")) - { - CheckNull(cep); - } else - if (!strcmp(cep->name, "tls")) - { - CheckNull(cep); - } else - if (!strcmp(cep->name, "reputation-score")) - { - int v; - CheckNull(cep); - v = atoi(cep->value); - if ((v < 1) || (v > 10000)) - { - config_error("%s:%i: security-group::reputation-score needs to be a value of 1-10000", - cep->file->filename, cep->line_number); - errors++; - } - } else - if (!strcmp(cep->name, "include-mask")) - { - } else - { - config_error_unknown(cep->file->filename, cep->line_number, - "security-group", cep->name); - errors++; - continue; - } - } - - return errors; -} - -int _conf_security_group(ConfigFile *conf, ConfigEntry *ce) -{ - ConfigEntry *cep; - SecurityGroup *s = add_security_group(ce->value, 1); - - for (cep = ce->items; cep; cep = cep->next) - { - if (!strcmp(cep->name, "webirc")) - s->webirc = config_checkval(cep->value, CFG_YESNO); - else if (!strcmp(cep->name, "identified")) - s->identified = config_checkval(cep->value, CFG_YESNO); - else if (!strcmp(cep->name, "tls")) - s->tls = config_checkval(cep->value, CFG_YESNO); - else if (!strcmp(cep->name, "reputation-score")) - s->reputation_score = atoi(cep->value); - else if (!strcmp(cep->name, "priority")) - { - s->priority = atoi(cep->value); - DelListItem(s, securitygroups); - AddListItemPrio(s, securitygroups, s->priority); - } - else if (!strcmp(cep->name, "include-mask")) - { - unreal_add_masks(&s->include_mask, cep); - } - } - return 1; -} - Secret *find_secret(const char *secret_name) { Secret *s; @@ -10687,6 +10779,7 @@ int rehash_internal(Client *client) reread_motdsandrules(); unload_all_unused_umodes(); unload_all_unused_extcmodes(); + unload_all_unused_extbans(); unload_all_unused_caps(); unload_all_unused_history_backends(); // unload_all_unused_moddata(); -- this will crash @@ -10703,8 +10796,9 @@ int rehash_internal(Client *client) void link_cleanup(ConfigItem_link *link_ptr) { safe_free(link_ptr->servername); - unreal_delete_masks(link_ptr->incoming.mask); + free_security_group(link_ptr->incoming.match); Auth_FreeAuthConfig(link_ptr->auth); + safe_free(link_ptr->outgoing.file); safe_free(link_ptr->outgoing.bind_ip); safe_free(link_ptr->outgoing.hostname); safe_free(link_ptr->hub); diff --git a/src/conf_preprocessor.c b/src/conf_preprocessor.c index 9be3338..c3cac00 100644 --- a/src/conf_preprocessor.c +++ b/src/conf_preprocessor.c @@ -1,6 +1,6 @@ /* UnrealIRCd configuration preprocessor * (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later * * Technically this isn't a 100% true preprocessor, but to the end user * it will certainly look like it, hence the name. diff --git a/src/crashreport.c b/src/crashreport.c index f3ac0f5..6ac8591 100644 --- a/src/crashreport.c +++ b/src/crashreport.c @@ -1,6 +1,6 @@ /* UnrealIRCd crash reporter code. * (C) Copyright 2015-2019 Bram Matthys ("Syzop") and the UnrealIRCd Team. - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -306,7 +306,7 @@ int crash_report_asan_log(FILE *reportfd, char *coredump) stripcrlf(buf); fprintf(reportfd, " %s\n", buf); } - n = pclose(fd); + n = fclose(fd); fprintf(reportfd, "END OF ASAN LOG\n"); if (WEXITSTATUS(n) == 127) diff --git a/src/ircd.c b/src/ircd.c index 7fcd185..17fcb87 100644 --- a/src/ircd.c +++ b/src/ircd.c @@ -57,7 +57,7 @@ EVENT(garbage_collect) if (loop.do_garbage_collect == 1) { loop.do_garbage_collect = 0; unreal_log(ULOG_INFO, "main", "GARBAGE_COLLECT_STARTED", NULL, "Cleaned up $count garbage blocks", - (ii - freelinks)); + log_data_integer("count", (ii - freelinks))); } } if (loop.do_garbage_collect == 1) @@ -83,7 +83,7 @@ int match_tkls(Client *client) { unreal_log(ULOG_INFO, "tkl", "BAN_REALNAME", client, "Banned client $client.details due to realname ban: $reason", - bconf->reason ? bconf->reason : "no reason"); + log_data_string("reason", bconf->reason ? bconf->reason : "no reason")); if (bconf->reason) { if (IsUser(client)) diff --git a/src/ircd_vars.c b/src/ircd_vars.c index 6dec950..d54b713 100644 --- a/src/ircd_vars.c +++ b/src/ircd_vars.c @@ -1,7 +1,7 @@ /************************************************************************ * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/ircd_vars.c * (c) 2021- Bram Matthys and The UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/log.c b/src/log.c index f2c674f..a2748bf 100644 --- a/src/log.c +++ b/src/log.c @@ -3,7 +3,7 @@ * (C) 2021 Bram Matthys (Syzop) and the UnrealIRCd Team * * See file AUTHORS in IRC package for additional names of - * the programmers. + * 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 @@ -43,6 +43,26 @@ int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsy void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, int expand_msg, const char *msg, va_list vl); void log_blocks_switchover(void); +/** Calculate expansion of a JSON string thanks to double escaping. + * orig => JSON => IRC + * " => \" => \\" + * \ => \\ => \\\\ + */ +int json_dump_string_length(const char *s) +{ + int len = 0; + for (; *s; s++) + { + if (*s == '\\') + len += 4; + else if (*s == '"') + len += 3; + else + len++; + } + return len; +} + /** Convert a regular string value to a JSON string. * In UnrealIRCd, this must be used instead of json_string() * as we may use non-UTF8 sequences. Also, this takes care @@ -53,13 +73,16 @@ void log_blocks_switchover(void); */ json_t *json_string_unreal(const char *s) { - static char buf[8192]; + char buf1[512], buf2[512]; char *verified_s; + const char *stripped; if (s == NULL) return json_null(); - verified_s = unrl_utf8_make_valid(s, buf, sizeof(buf), 0); + stripped = StripControlCodesEx(s, buf1, sizeof(buf1), UNRL_STRIP_LOW_ASCII|UNRL_STRIP_KEEP_LF); + verified_s = unrl_utf8_make_valid(buf1, buf2, sizeof(buf2), 0); + return json_string(verified_s); } @@ -561,6 +584,9 @@ void json_expand_client(json_t *j, const char *key, Client *client, int detail) if (client->local && client->local->creationtime) json_object_set_new(child, "connected_since", json_timestamp(client->local->creationtime)); + if (client->local && client->local->idle_since) + json_object_set_new(child, "idle_since", json_timestamp(client->local->idle_since)); + if (client->user) { char buf[512]; @@ -572,6 +598,10 @@ void json_expand_client(json_t *j, const char *key, Client *client, int detail) json_object_set_new(user, "username", json_string_unreal(client->user->username)); if (!BadPtr(client->info)) json_object_set_new(user, "realname", json_string_unreal(client->info)); + if (has_user_mode(client, 'x') && client->user->virthost && strcmp(client->user->virthost, client->user->realhost)) + json_object_set_new(user, "vhost", json_string_unreal(client->user->virthost)); + if (*client->user->cloakedhost) + json_object_set_new(user, "cloakedhost", json_string_unreal(client->user->cloakedhost)); if (client->uplink) json_object_set_new(user, "servername", json_string_unreal(client->uplink->name)); if (IsLoggedIn(client)) @@ -592,6 +622,26 @@ void json_expand_client(json_t *j, const char *key, Client *client, int detail) str = get_operclass(client); if (str) json_object_set_new(user, "operclass", json_string_unreal(str)); + if (client->user->channel) + { + Membership *m; + int cnt = 0; + int len = 0; + json_t *channels = json_array(); + json_object_set_new(user, "channels", channels); + for (m = client->user->channel; m; m = m->next) + { + len += json_dump_string_length(m->channel->name); + if (len > 384) + { + /* Truncated */ + json_array_append_new(channels, json_string_unreal("...")); + break; + } + json_array_append_new(channels, json_string_unreal(m->channel->name)); + } + } + RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_USER, client, detail, child, user); } else if (IsMe(client)) { @@ -645,7 +695,9 @@ void json_expand_client(json_t *j, const char *key, Client *client, int detail) } if (!BadPtr(client->server->features.nickchars)) json_object_set_new(features, "nick_character_sets", json_string_unreal(client->server->features.nickchars)); + RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, client, detail, child, server); } + RunHook(HOOKTYPE_JSON_EXPAND_CLIENT, client, detail, child); } void json_expand_channel(json_t *j, const char *key, Channel *channel, int detail) @@ -675,6 +727,7 @@ void json_expand_channel(json_t *j, const char *key, Channel *channel, int detai } // Possibly later: If detail is set to 1 then expand more... + RunHook(HOOKTYPE_JSON_EXPAND_CHANNEL, channel, detail, child); } const char *timestamp_iso8601_now(void) @@ -883,9 +936,11 @@ LogData *log_data_link_block(ConfigItem_link *link) safe_strdup(d->key, "link_block"); d->value.object = j = json_object(); json_object_set_new(j, "name", json_string_unreal(link->servername)); + json_object_set_new(j, "file", json_string_unreal(link->outgoing.file)); json_object_set_new(j, "hostname", json_string_unreal(link->outgoing.hostname)); json_object_set_new(j, "ip", json_string_unreal(link->connect_ip)); json_object_set_new(j, "port", json_integer(link->outgoing.port)); + json_object_set_new(j, "class", json_string_unreal(link->class->name)); if (!link->outgoing.bind_ip && iConf.link_bindip) bind_ip = iConf.link_bindip; diff --git a/src/misc.c b/src/misc.c index 2d381ef..350101b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -916,145 +916,74 @@ int is_autojoin_chan(const char *chname) return 0; } -/** Free all masks in the mask list */ -void unreal_delete_masks(ConfigItem_mask *m) -{ - ConfigItem_mask *m_next; - - for (; m; m = m_next) - { - m_next = m->next; - - safe_free(m->mask); - - safe_free(m); - } -} - -/** Internal function to add one individual mask to the list */ -static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce) -{ - ConfigItem_mask *m = safe_alloc(sizeof(ConfigItem_mask)); - - /* Since we allow both mask "xyz"; and mask { abc; def; };... */ - if (ce->value) - safe_strdup(m->mask, ce->value); - else - safe_strdup(m->mask, ce->name); - - add_ListItem((ListStruct *)m, (ListStruct **)head); -} - -/** Add mask entries from config */ -void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce) +/** Add name entries from config */ +void unreal_add_names(NameList **n, ConfigEntry *ce) { if (ce->items) { ConfigEntry *cep; for (cep = ce->items; cep; cep = cep->next) - unreal_add_mask(head, cep); + _add_name_list(n, cep->value ? cep->value : cep->name); } else + if (ce->value) { - unreal_add_mask(head, ce); + _add_name_list(n, ce->value); } } -/** Check if a client matches any of the masks in the mask list. - * The following rules apply: - * - If you have only negating entries, like '!abc' and '!def', then - * we assume an implicit * rule first, since that is clearly what - * the user wants. - * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the - * implicit * is dropped and we assume you only want to match *.com, - * with the exception of irc1*.com and irc2*.com. - * - If you only have normal entries without ! then things are - * as they always are. - * @param client The client to run the mask match against - * @param mask The mask entry from the config file - * @returns 1 on match, 0 on non-match. - */ -int unreal_mask_match(Client *client, ConfigItem_mask *mask) +/** Add name/value entries from config */ +void unreal_add_name_values(NameValuePrioList **n, const char *name, ConfigEntry *ce) { - int retval = 1; - ConfigItem_mask *m; - - if (!mask) - return 0; /* Empty mask block is no match */ - - /* First check normal matches (without ! prefix) */ - for (m = mask; m; m = m->next) + if (ce->items) { - if (m->mask[0] != '!') - { - retval = 0; /* no implicit * */ - if (match_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED)) - { - retval = 1; - break; - } - } - } - - if (retval) + ConfigEntry *cep; + for (cep = ce->items; cep; cep = cep->next) + add_nvplist(n, 0, name, cep->value ? cep->value : cep->name); + } else + if (ce->value) { - /* We matched. Check for exceptions (with ! prefix) */ - for (m = mask; m; m = m->next) - { - if ((m->mask[0] == '!') && match_user(m->mask+1, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED)) - return 0; - } + add_nvplist(n, 0, name, ce->value); } - - return retval; } -/** Check if a string matches any of the masks in the mask list. - * The following rules apply: - * - If you have only negating entries, like '!abc' and '!def', then - * we assume an implicit * rule first, since that is clearly what - * the user wants. - * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the - * implicit * is dropped and we assume you only want to match *.com, - * with the exception of irc1*.com and irc2*.com. - * - If you only have normal entries without ! then things are - * as they always are. - * @param name The name to run the mask matching on - * @param mask The mask entry from the config file - * @returns 1 on match, 0 on non-match. - */ -int unreal_mask_match_string(const char *name, ConfigItem_mask *mask) +/** Prints the name:value pair of a NameValuePrioList */ +const char *namevalue(NameValuePrioList *n) { - int retval = 1; - ConfigItem_mask *m; + static char buf[512]; - if (!mask) - return 0; /* Empty mask block is no match */ + if (!n->name) + return ""; - /* First check normal matches (without ! prefix) */ - for (m = mask; m; m = m->next) - { - if (m->mask[0] != '!') - { - retval = 0; /* no implicit * */ - if (match_simple(m->mask, name)) - { - retval = 1; - break; - } - } - } + if (!n->value) + return n->name; - if (retval) - { - /* We matched. Check for exceptions (with ! prefix) */ - for (m = mask; m; m = m->next) - { - if ((m->mask[0] == '!') && match_simple(m->mask+1, name)) - return 0; - } - } + snprintf(buf, sizeof(buf), "%s:%s", n->name, n->value); + return buf; +} - return retval; +/** Version of namevalue() but replaces spaces with underscores. + * Used in for example numeric sending routines where a field + * may not contain any spaces. + */ +const char *namevalue_nospaces(NameValuePrioList *n) +{ + static char buf[512]; + char *p; + + if (!n->name) + return ""; + + if (!n->value) + strlcpy(buf, n->name, sizeof(buf)); + + snprintf(buf, sizeof(buf), "%s:%s", n->name, n->value); + + /* Replace spaces with underscores */ + for (p=buf; *p; p++) + if (*p == ' ') + *p = '_'; + + return buf; } /** Our own strcasestr implementation because strcasestr is @@ -1461,6 +1390,11 @@ void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char { } +int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost) +{ + return 0; +} + /** my_timegm: mktime()-like function which will use GMT/UTC. * Strangely enough there is no standard function for this. * On some *NIX OS's timegm() may be available, sometimes only @@ -2539,3 +2473,141 @@ int minimum_msec_since_last_run(struct timeval *tv_old, long minimum) } return 0; } + +/** Strip color, bold, underline, and reverse codes from a string. + * @param text The input text + * @param output The buffer for the output text + * @param outputlen The length of the output buffer + * @param strip_flags Zero or (a combination of) UNRL_STRIP_LOW_ASCII / UNRL_STRIP_KEEP_LF. + * @returns The new string, which will be 'output', or in unusual cases (outputlen==0) will be NULL. + */ +const char *StripControlCodesEx(const char *text, char *output, size_t outputlen, int strip_flags) +{ + int i = 0, len = strlen(text), save_len=0; + char nc = 0, col = 0, rgb = 0; + char *o = output; + const char *save_text=NULL; + + /* Handle special cases first.. */ + + if (outputlen == 0) + return NULL; + + if (outputlen == 1) + { + *output = '\0'; + return output; + } + + /* Reserve room for the NUL byte */ + outputlen--; + + while (len > 0) + { + if ( col && ((isdigit(*text) && nc < 2) || (*text == ',' && nc < 3))) + { + nc++; + if (*text == ',') + nc = 0; + } + /* Syntax for RGB is ^DHHHHHH where H is a hex digit. + * If < 6 hex digits are specified, the code is displayed + * as text + */ + else if ((rgb && isxdigit(*text) && nc < 6) || (rgb && *text == ',' && nc < 7)) + { + nc++; + if (*text == ',') + nc = 0; + } + else + { + if (col) + col = 0; + if (rgb) + { + if (nc != 6) + { + text = save_text+1; + len = save_len-1; + rgb = 0; + continue; + } + rgb = 0; + } + switch (*text) + { + case 3: + /* color */ + col = 1; + nc = 0; + break; + case 4: + /* RGB */ + save_text = text; + save_len = len; + rgb = 1; + nc = 0; + break; + case 2: + /* bold */ + break; + case 31: + /* underline */ + break; + case 22: + /* reverse */ + break; + case 15: + /* plain */ + break; + case 29: + /* italic */ + break; + case 30: + /* strikethrough */ + break; + case 17: + /* monospace */ + break; + case 0xe2: + if (!strncmp(text+1, "\x80\x8b", 2)) + { + /* +2 means we skip 3 */ + text += 2; + len -= 2; + break; + } + /*fallthrough*/ + default: + if ((*text >= ' ') || + !(strip_flags & UNRL_STRIP_LOW_ASCII) || + ((strip_flags & UNRL_STRIP_KEEP_LF) && (*text == '\n')) + ) + { + *o++ = *text; + outputlen--; + if (outputlen == 0) + { + *o = '\0'; + return output; + } + } + break; + } + } + text++; + len--; + } + + *o = '\0'; + return output; +} + +/* strip color, bold, underline, and reverse codes from a string */ +const char *StripControlCodes(const char *text) +{ + static unsigned char new_str[4096]; + + return StripControlCodesEx(text, new_str, sizeof(new_str), 0); +} diff --git a/src/modulemanager.c b/src/modulemanager.c index 680a888..0ceac99 100644 --- a/src/modulemanager.c +++ b/src/modulemanager.c @@ -1,6 +1,6 @@ /* UnrealIRCd module manager. * (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd Team. - * License: GPLv2 + * License: GPLv2 or later * See https://www.unrealircd.org/docs/Module_manager for user documentation. */ @@ -1206,7 +1206,7 @@ int mm_compile(ManagedModule *m, char *tmpfile, int test) fprintf(stderr, "ERROR: Compile errors encountered while compiling module '%s'\n" "You are suggested to contact the author (%s) of this module:\n%s\n", m->name, m->author, m->troubleshooting); - return 1; + return 0; } /** Actually download and install the module. @@ -1222,7 +1222,7 @@ void mm_install_module(ManagedModule *m) basename = "mod.c"; tmpfile = unreal_mktemp(TMPDIR, basename); - printf("ConfigResourceing %s from %s...\n", m->name, m->source); + printf("Downloading %s from %s...\n", m->name, m->source); if (!mm_http_request(m->source, tmpfile, 1)) { fprintf(stderr, "Repository %s seems to list a module file that cannot be retrieved (%s).\n", m->repo_url, m->source); @@ -1655,21 +1655,23 @@ void mm_parse_c_file(int argc, char *args[]) void mm_self_test(void) { char name[512]; - snprintf(name, sizeof(name), "%s/src/modules/third", BUILDDIR); - if (file_exists(name)) - return; + if (!file_exists(BUILDDIR)) { fprintf(stderr, "ERROR: Directory %s does not exist.\n" "The UnrealIRCd source is required for the module manager to work!\n", BUILDDIR); + exit(-1); } else { - fprintf(stderr, "ERROR: Directory %s exists, but %s does not.\n" - "The UnrealIRCd source is required for the module manager to work.\n" - "It seems you only have a partial build directory??\n", - BUILDDIR, name); + snprintf(name, sizeof(name), "%s/src/modules/third/Makefile", BUILDDIR); + if (!file_exists(name)) + { + fprintf(stderr, "ERROR: Directory %s exists, but your UnrealIRCd is not compiled yet.\n" + "You must compile your UnrealIRCd first (run './Config', then 'make install')\n", + BUILDDIR); + exit(-1); + } } - exit(-1); } void modulemanager(int argc, char *args[]) diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index 62ee898..fce20a4 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -39,7 +39,7 @@ MODULES= \ sethost.so chghost.so chgident.so setname.so \ setident.so sdesc.so svsmode.so swhois.so\ svsmotd.so svsnline.so who_old.so whox.so mkpasswd.so \ - away.so svsnoop.so svsnick.so \ + away.so svsnoop.so svsnick.so svso.so \ chgname.so kill.so \ lag.so message.so oper.so pingpong.so \ quit.so sendumode.so sqline.so \ @@ -71,12 +71,12 @@ MODULES= \ message-tags.so batch.so \ 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 \ + echo-message.so userip-tag.so userhost-tag.so geoip-tag.so \ bot-tag.so reply-tag.so json-log-tag.so \ - typing-indicator.so \ + typing-indicator.so channel-context.so \ ident_lookup.so history.so chathistory.so \ targetfloodprot.so clienttagdeny.so watch-backend.so \ - monitor.so slog.so tls_cipher.so operinfo.so \ + monitor.so slog.so tls_cipher.so operinfo.so creationtime.so \ unreal_server_compat.so \ extended-monitor.so geoip_csv.so \ geoip_base.so extjwt.so \ diff --git a/src/modules/antimixedutf8.c b/src/modules/antimixedutf8.c index 8d7eabb..81018a4 100644 --- a/src/modules/antimixedutf8.c +++ b/src/modules/antimixedutf8.c @@ -18,6 +18,8 @@ * ban-action block; * ban-reason "Possible mixed character spam"; * ban-time 4h; // For other types + * except { + * } * }; * }; * @@ -56,6 +58,7 @@ struct { BanAction ban_action; char *ban_reason; long ban_time; + SecurityGroup *except; } cfg; static void free_config(void); @@ -196,9 +199,12 @@ CMD_OVERRIDE_FUNC(override_msg) { int score, ret; - if (!MyUser(client) || (parc < 3) || BadPtr(parv[2])) + if (!MyUser(client) || (parc < 3) || BadPtr(parv[2]) || + user_allowed_by_security_group(client, cfg.except)) { - /* Short circuit for: remote clients or insufficient parameters */ + /* Short circuit for: remote clients, insufficient parameters, + * antimixedutf8::except. + */ CallCommandOverride(ovr, client, recv_mtags, parc, parv); return; } @@ -271,6 +277,7 @@ static void init_config(void) static void free_config(void) { safe_free(cfg.ban_reason); + free_security_group(cfg.except); memset(&cfg, 0, sizeof(cfg)); /* needed! */ } @@ -319,6 +326,10 @@ int antimixedutf8_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *er if (!strcmp(cep->name, "ban-time")) { } else + if (!strcmp(cep->name, "except")) + { + test_match_block(cf, cep, &errors); + } else { config_error("%s:%i: unknown directive set::antimixedutf8::%s", cep->file->filename, cep->line_number, cep->name); @@ -357,6 +368,10 @@ int antimixedutf8_config_run(ConfigFile *cf, ConfigEntry *ce, int type) if (!strcmp(cep->name, "ban-time")) { cfg.ban_time = config_checkval(cep->value, CFG_TIME); + } else + if (!strcmp(cep->name, "except")) + { + conf_match_block(cf, cep, &cfg.except); } } return 1; diff --git a/src/modules/antirandom.c b/src/modules/antirandom.c index 9c30703..2846124 100644 --- a/src/modules/antirandom.c +++ b/src/modules/antirandom.c @@ -25,7 +25,7 @@ ModuleHeader MOD_HEADER = { "antirandom", - "1.4", + "1.5", "Detect and ban users with random names", "UnrealIRCd Team", "unrealircd-6", @@ -504,8 +504,7 @@ struct { long ban_time; int convert_to_lowercase; int show_failedconnects; - ConfigItem_mask *except_hosts; - int except_webirc; + SecurityGroup *except; } cfg; /* Forward declarations */ @@ -542,7 +541,8 @@ MOD_INIT() /* Some default values: */ cfg.convert_to_lowercase = 1; - cfg.except_webirc = 1; + cfg.except = safe_alloc(sizeof(SecurityGroup)); + cfg.except->webirc = 1; return MOD_SUCCESS; } @@ -562,7 +562,7 @@ MOD_UNLOAD() static void free_config(void) { safe_free(cfg.ban_reason); - unreal_delete_masks(cfg.except_hosts); + free_security_group(cfg.except); memset(&cfg, 0, sizeof(cfg)); /* needed! */ } @@ -580,6 +580,10 @@ int antirandom_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) for (cep = ce->items; cep; cep = cep->next) { + if (!strcmp(cep->name, "except")) + { + test_match_block(cf, cep, &errors); + } else if (!strcmp(cep->name, "except-hosts")) { } else @@ -653,14 +657,20 @@ int antirandom_config_run(ConfigFile *cf, ConfigEntry *ce, int type) for (cep = ce->items; cep; cep = cep->next) { + if (!strcmp(cep->name, "except")) + { + conf_match_block(cf, cep, &cfg.except); + } else if (!strcmp(cep->name, "except-hosts")) { + /* backwards compatible with set::antirandom::except */ for (cep2 = cep->items; cep2; cep2 = cep2->next) - unreal_add_masks(&cfg.except_hosts, cep2); + unreal_add_masks(&cfg.except->mask, cep2); } else if (!strcmp(cep->name, "except-webirc")) { - cfg.except_webirc = config_checkval(cep->value, CFG_YESNO); + /* backwards compatible with set::antirandom::except */ + cfg.except->webirc = config_checkval(cep->value, CFG_YESNO); } else if (!strcmp(cep->name, "threshold")) { @@ -863,14 +873,14 @@ int antirandom_preconnect(Client *client) if (cfg.ban_action == BAN_ACT_WARN) { unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client, - "[antirandom] would have denied access to user with score $score: $client:$client.info", + "[antirandom] would have denied access to user with score $score: $client.details:$client.user.realname", log_data_integer("score", score)); return HOOK_CONTINUE; } if (cfg.show_failedconnects) { unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client, - "[antirandom] denied access to user with score $score: $client:$client.info", + "[antirandom] denied access to user with score $score: $client.details:$client.user.realname", log_data_integer("score", score)); } place_host_ban(client, cfg.ban_action, cfg.ban_reason, cfg.ban_time); @@ -894,13 +904,8 @@ static void free_stuff(void) /** Is this user exempt from antirandom interventions? */ static int is_exempt(Client *client) { - /* WEBIRC gateway and exempt? */ - if (cfg.except_webirc) - { - const char *val = moddata_client_get(client, "webirc"); - if (val && (atoi(val)>0)) - return 1; - } + if (user_allowed_by_security_group(client, cfg.except)) + return 1; if (find_tkl_exception(TKL_ANTIRANDOM, client)) return 1; @@ -909,6 +914,5 @@ static int is_exempt(Client *client) if (IsSoftBanAction(cfg.ban_action) && IsLoggedIn(client)) return 1; - /* On except host? */ - return unreal_mask_match(client, cfg.except_hosts); + return 0; } diff --git a/src/modules/blacklist.c b/src/modules/blacklist.c index 3cbdec9..43738f2 100644 --- a/src/modules/blacklist.c +++ b/src/modules/blacklist.c @@ -67,6 +67,7 @@ struct Blacklist { int action; long ban_time; char *reason; + SecurityGroup *except; }; /* Blacklist user struct. In the c-ares DNS reply callback we need to pass @@ -236,6 +237,8 @@ void delete_blacklist_block(Blacklist *e) safe_free(e->name); safe_free(e->reason); + free_security_group(e->except); + safe_free(e); } @@ -356,6 +359,10 @@ int blacklist_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) } } } else + if (!strcmp(cep->name, "except")) + { + test_match_block(cf, cep, &errors); + } else if (!cep->value) { config_error_empty(cep->file->filename, cep->line_number, @@ -388,8 +395,8 @@ int blacklist_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) continue; } has_ban_time = 1; - } else - if (!strcmp(cep->name, "reason")) + } + else if (!strcmp(cep->name, "reason")) { if (has_reason) { @@ -524,13 +531,17 @@ int blacklist_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { d->action = banact_stringtoval(cep->value); } + else if (!strcmp(cep->name, "ban-time")) + { + d->ban_time = config_checkval(cep->value, CFG_TIME); + } else if (!strcmp(cep->name, "reason")) { safe_strdup(d->reason, cep->value); } - else if (!strcmp(cep->name, "ban-time")) + else if (!strcmp(cep->name, "except")) { - d->ban_time = config_checkval(cep->value, CFG_TIME); + conf_match_block(cf, cep, &d->except); } } @@ -574,7 +585,7 @@ int blacklist_start_check(Client *client) SetNoHandshakeDelay(client); return 0; } - + if (!BLUSER(client)) { SetBLUser(client, safe_alloc(sizeof(BLUser))); @@ -587,6 +598,10 @@ int blacklist_start_check(Client *client) if (!BLUSER(client)) break; + /* Check if user is exempt (then don't bother checking) */ + if (user_allowed_by_security_group(client, bl->except)) + continue; + /* Initiate blacklist requests */ if (bl->backend_type == BLACKLIST_BACKEND_DNS) blacklist_dns_request(client, bl); diff --git a/src/modules/bot-tag.c b/src/modules/bot-tag.c index 2a300c3..16e23b6 100644 --- a/src/modules/bot-tag.c +++ b/src/modules/bot-tag.c @@ -45,6 +45,12 @@ MOD_INIT() MARK_AS_OFFICIAL_MODULE(modinfo); + memset(&mtag, 0, sizeof(mtag)); + mtag.name = "bot"; + mtag.is_ok = bottag_mtag_is_ok; + mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED; + MessageTagHandlerAdd(modinfo->handle, &mtag); + memset(&mtag, 0, sizeof(mtag)); mtag.name = "draft/bot"; mtag.is_ok = bottag_mtag_is_ok; @@ -82,7 +88,14 @@ void mtag_add_bottag(Client *client, MessageTag *recv_mtags, MessageTag **mtag_l if (IsUser(client) && has_user_mode(client, 'B')) { - MessageTag *m = safe_alloc(sizeof(MessageTag)); + MessageTag *m; + + m = safe_alloc(sizeof(MessageTag)); + safe_strdup(m->name, "bot"); + m->value = NULL; + AddListItem(m, *mtag_list); + + m = safe_alloc(sizeof(MessageTag)); safe_strdup(m->name, "draft/bot"); m->value = NULL; AddListItem(m, *mtag_list); diff --git a/src/modules/certfp.c b/src/modules/certfp.c index c389b9e..dacd767 100644 --- a/src/modules/certfp.c +++ b/src/modules/certfp.c @@ -6,7 +6,7 @@ * * (C) Copyright 2014-2015 The UnrealIRCd team (Syzop, DBoyz, Nath and others) * - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -27,6 +27,7 @@ void certfp_unserialize(const char *str, ModData *m); int certfp_handshake(Client *client); int certfp_connect(Client *client); int certfp_whois(Client *client, Client *target, NameValuePrioList **list); +int certfp_log(Client *client, int detail, json_t *j); ModDataInfo *certfp_md; /* Module Data structure which we acquire */ @@ -51,6 +52,7 @@ MOD_INIT() HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, certfp_handshake); HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, certfp_handshake); HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, certfp_whois); + HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, certfp_log); return MOD_SUCCESS; } @@ -158,3 +160,24 @@ void certfp_unserialize(const char *str, ModData *m) { safe_strdup(m->str, str); } + +int certfp_log(Client *client, int detail, json_t *j) +{ + json_t *tls; + const char *str; + + str = moddata_client_get(client, "certfp"); + if (!str) + return 0; + + tls = json_object_get(j, "tls"); + if (!tls) + { + tls = json_object(); + json_object_set_new(j, "tls", tls); + } + + json_object_set_new(tls, "certfp", json_string_unreal(str)); + + return 0; +} diff --git a/src/modules/chanmodes/floodprot.c b/src/modules/chanmodes/floodprot.c index 148a652..272ca7a 100644 --- a/src/modules/chanmodes/floodprot.c +++ b/src/modules/chanmodes/floodprot.c @@ -110,7 +110,7 @@ struct ChannelFloodProtection { /* Global variables */ ModDataInfo *mdflood = NULL; Cmode_t EXTMODE_FLOODLIMIT = 0L; -static int timedban_available = 0; /**< Set to 1 if extbans/timedban module is loaded. */ +static int timedban_available = 1; /**< Set to 1 if extbans/timedban module is loaded. Assumed 1 during config load due to set::modes-on-join race. */ RemoveChannelModeTimer *removechannelmodetimer_list = NULL; char *floodprot_msghash_key = NULL; diff --git a/src/modules/chanmodes/history.c b/src/modules/chanmodes/history.c index 9cdc73e..dd829e0 100644 --- a/src/modules/chanmodes/history.c +++ b/src/modules/chanmodes/history.c @@ -1,7 +1,7 @@ /* * modules/chanmodes/history - Channel History * (C) Copyright 2009-2019 Bram Matthys (Syzop) and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/chanmodes/operonly.c b/src/modules/chanmodes/operonly.c index 3c4b4f7..a140a85 100644 --- a/src/modules/chanmodes/operonly.c +++ b/src/modules/chanmodes/operonly.c @@ -35,7 +35,7 @@ Cmode_t EXTCMODE_OPERONLY; int operonly_require_oper(Client *client, Channel *channel, char mode, const char *para, int checkt, int what); int operonly_can_join(Client *client, Channel *channel, const char *key, char **errmsg); int operonly_view_topic_outside_channel(Client *client, Channel *channel); -int operonly_oper_invite_ban(Client *client, Channel *channel); +int operonly_invite_bypass(Client *client, Channel *channel); MOD_TEST() { @@ -53,7 +53,7 @@ CmodeInfo req; CmodeAdd(modinfo->handle, req, &EXTCMODE_OPERONLY); HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, operonly_can_join); - HookAdd(modinfo->handle, HOOKTYPE_OPER_INVITE_BAN, 0, operonly_oper_invite_ban); + HookAdd(modinfo->handle, HOOKTYPE_INVITE_BYPASS, 0, operonly_invite_bypass); HookAdd(modinfo->handle, HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL, 0, operonly_view_topic_outside_channel); @@ -81,10 +81,9 @@ int operonly_can_join(Client *client, Channel *channel, const char *key, char ** return 0; } -int operonly_oper_invite_ban(Client *client, Channel *channel) +int operonly_invite_bypass(Client *client, Channel *channel) { - if ((channel->mode.mode & EXTCMODE_OPERONLY) && - !ValidatePermissionsForPath("channel:operonly:ban",client,NULL,NULL,NULL)) + if ((channel->mode.mode & EXTCMODE_OPERONLY) && !ValidatePermissionsForPath("channel:operonly:ban",client,NULL,NULL,NULL)) return HOOK_DENY; return HOOK_CONTINUE; diff --git a/src/modules/channel-context.c b/src/modules/channel-context.c new file mode 100644 index 0000000..cc21391 --- /dev/null +++ b/src/modules/channel-context.c @@ -0,0 +1,95 @@ +/* + * IRC - Internet Relay Chat, src/modules/channel-context.c + * (C) 2022 Valware & The UnrealIRCd Team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "unrealircd.h" + +ModuleHeader MOD_HEADER + = { + "channel-context", + "1.0", + "Channel Context (IRCv3)", + "UnrealIRCd team", + "unrealircd-6", + }; + +int chancontext_mtag_is_ok(Client *client, const char *name, const char *value); +void mtag_add_chancontext(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature); + +MOD_INIT() +{ + MessageTagHandlerInfo mtag; + + MARK_AS_OFFICIAL_MODULE(modinfo); + + memset(&mtag, 0, sizeof(mtag)); + mtag.is_ok = chancontext_mtag_is_ok; + mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED; + mtag.name = "+draft/channel-context"; + MessageTagHandlerAdd(modinfo->handle, &mtag); + + HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_chancontext); + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +int chancontext_mtag_is_ok(Client *client, const char *name, const char *value) +{ + if (BadPtr(value)) + return 0; + + /* Validate a bit further, but only for local users.. */ + if (MyUser(client)) + { + Channel *channel = find_channel(value); + if (!channel) + return 0; + if (!IsMember(client, channel)) + return 0; + } + + return 1; +} + +void mtag_add_chancontext(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature) +{ + MessageTag *m; + + if (IsUser(client)) + { + m = find_mtag(recv_mtags, "+draft/channel-context"); + if (m) + { + m = duplicate_mtag(m); + AddListItem(m, *mtag_list); + } + } +} diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c index 6330a5a..08d4725 100644 --- a/src/modules/channeldb.c +++ b/src/modules/channeldb.c @@ -1,7 +1,7 @@ /* * Stores channel settings for +P channels in a .db file * (C) Copyright 2019 Syzop, Gottem and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -359,6 +359,14 @@ int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel) return 1; } +int ban_exists(Ban *lst, Ban *e) +{ + for (; lst; lst = lst->next) + if (!mycmp(lst->banstr, e->banstr)) + return 1; + return 0; +} + #define R_SAFE(x) \ do { \ if (!(x)) { \ @@ -401,10 +409,18 @@ int read_listmode(UnrealDB *db, Ban **lst) } safe_strdup(e->banstr, str); - /* Add to list */ - e->when = when; - e->next = *lst; - *lst = e; + if (ban_exists(*lst, e)) + { + /* Free again - duplicate item */ + safe_free(e->banstr); + safe_free(e->who); + safe_free(e); + } else { + /* Add to list */ + e->when = when; + e->next = *lst; + *lst = e; + } } return 1; diff --git a/src/modules/chathistory.c b/src/modules/chathistory.c index d13c32d..9c75ff6 100644 --- a/src/modules/chathistory.c +++ b/src/modules/chathistory.c @@ -1,6 +1,6 @@ /* src/modules/chathistory.c - IRCv3 CHATHISTORY command. * (C) Copyright 2021 Bram Matthys (Syzop) and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later * * This implements the "CHATHISTORY" command, the CAP and 005 token. * https://ircv3.net/specs/extensions/chathistory diff --git a/src/modules/chghost.c b/src/modules/chghost.c index 814394b..3c3ebb0 100644 --- a/src/modules/chghost.c +++ b/src/modules/chghost.c @@ -231,6 +231,9 @@ void _userhost_changed(Client *client) if (HasCapabilityFast(client, CAP_CHGHOST)) sendto_one(client, NULL, "%s", buf); + if (MyUser(client)) + sendnumeric(client, RPL_HOSTHIDDEN, GetHost(client)); + /* A userhost change always generates the following network traffic: * server to server traffic, CAP "chghost" notifications, and * possibly PART+JOIN+MODE if force-rejoin had work to do. @@ -338,7 +341,4 @@ CMD_FUNC(cmd_chghost) safe_strdup(target->user->virthost, parv[2]); userhost_changed(target); - - if (MyUser(target)) - sendnumeric(target, RPL_HOSTHIDDEN, parv[2]); } diff --git a/src/modules/connect.c b/src/modules/connect.c index 59cff3c..19a49dc 100644 --- a/src/modules/connect.c +++ b/src/modules/connect.c @@ -106,10 +106,10 @@ CMD_FUNC(cmd_connect) return; } - if (!aconf->outgoing.hostname) + if (!aconf->outgoing.hostname && !aconf->outgoing.file) { sendnotice(client, - "*** Connect: Server %s is not configured to be an outgoing link (has a link block, but no link::outgoing::hostname)", + "*** Connect: Server %s is not configured to be an outgoing link (has a link block, but no link::outgoing::hostname or link::outgoing::file)", parv[1]); return; } diff --git a/src/modules/connthrottle.c b/src/modules/connthrottle.c index 5b35e2a..c234ef0 100644 --- a/src/modules/connthrottle.c +++ b/src/modules/connthrottle.c @@ -1,7 +1,7 @@ /* * connthrottle - Connection throttler * (C) Copyright 2004-2020 Bram Matthys (Syzop) and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later * See https://www.unrealircd.org/docs/Connthrottle */ @@ -31,10 +31,8 @@ struct cfgstruct { /* set::connthrottle::known-users: */ ThrottleSetting local; ThrottleSetting global; - /* set::connthrottle::new-users: */ - int minimum_reputation_score; - int sasl_bypass; - int webirc_bypass; + /* set::connthrottle::except: */ + SecurityGroup *except; /* set::connthrottle::disabled-when: */ long reputation_gathering; int start_delay; @@ -53,10 +51,8 @@ struct UCounter { ThrottleCounter local; /**< Local counter */ ThrottleCounter global; /**< Global counter */ int rejected_clients; /**< Number of rejected clients this minute */ - int allowed_score; /**< Number of allowed clients of type known-user */ - int allowed_sasl; /**< Number of allowed clients of type SASL */ - int allowed_webirc; /**< Number of allowed clients of type WEBIRC */ - int allowed_other; /**< Number of allowed clients of type other (new) */ + int allowed_except; /**< Number of allowed clients - on except list */ + int allowed_unknown_users; /**< Number of allowed clients - not on except list */ char disabled; /**< Module disabled by oper? */ int throttling_this_minute; /**< Did we do any throttling this minute? */ int throttling_previous_minute; /**< Did we do any throttling previous minute? */ @@ -87,9 +83,10 @@ MOD_TEST() cfg.global.count = 30; cfg.global.period = 60; cfg.start_delay = 180; /* 3 minutes */ safe_strdup(cfg.reason, "Throttled: Too many users trying to connect, please wait a while and try again"); - cfg.minimum_reputation_score = 24; - cfg.sasl_bypass = 1; - cfg.webirc_bypass = 0; + cfg.except = safe_alloc(sizeof(SecurityGroup)); + cfg.except->reputation_score = 24; + cfg.except->identified = 1; + cfg.except->webirc = 0; HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, ct_config_test); HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, ct_config_posttest); @@ -120,6 +117,7 @@ MOD_UNLOAD() { SavePersistentPointer(modinfo, ucounter); safe_free(cfg.reason); + free_security_group(cfg.except); return MOD_SUCCESS; } @@ -161,6 +159,10 @@ int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) for (cep = ce->items; cep; cep = cep->next) { + if (!strcmp(cep->name, "except")) + { + test_match_block(cf, cep, &errors); + } else if (!strcmp(cep->name, "known-users")) { for (cepp = cep->items; cepp; cepp = cepp->next) @@ -286,16 +288,20 @@ int ct_config_run(ConfigFile *cf, ConfigEntry *ce, int type) for (cep = ce->items; cep; cep = cep->next) { + if (!strcmp(cep->name, "except")) + { + conf_match_block(cf, cep, &cfg.except); + } else if (!strcmp(cep->name, "known-users")) { for (cepp = cep->items; cepp; cepp = cepp->next) { if (!strcmp(cepp->name, "minimum-reputation-score")) - cfg.minimum_reputation_score = atoi(cepp->value); + cfg.except->reputation_score = atoi(cepp->value); else if (!strcmp(cepp->name, "sasl-bypass")) - cfg.sasl_bypass = config_checkval(cepp->value, CFG_YESNO); + cfg.except->identified = config_checkval(cepp->value, CFG_YESNO); else if (!strcmp(cepp->name, "webirc-bypass")) - cfg.webirc_bypass = config_checkval(cepp->value, CFG_YESNO); + cfg.except->webirc = config_checkval(cepp->value, CFG_YESNO); } } else if (!strcmp(cep->name, "new-users")) @@ -360,23 +366,17 @@ EVENT(connthrottle_evt) unreal_log(ULOG_INFO, "connthrottle", "CONNTHROTLE_REPORT", NULL, "ConnThrottle] Stats for this server past 60 secs: " "Connections rejected: $num_rejected. " - "Accepted: $num_accepted_known_users known user(s), " - "$num_accepted_sasl SASL, " - "$num_accepted_webirc WEBIRC and " + "Accepted: $num_accepted_except except user(s) and " "$num_accepted_unknown_users new user(s).", log_data_integer("num_rejected", ucounter->rejected_clients), - log_data_integer("num_accepted_known_users", ucounter->allowed_score), - log_data_integer("num_accepted_sasl", ucounter->allowed_sasl), - log_data_integer("num_accepted_webirc", ucounter->allowed_webirc), - log_data_integer("num_accepted_unknown_users", ucounter->allowed_other)); + log_data_integer("num_accepted_except", ucounter->allowed_except), + log_data_integer("num_accepted_unknown_users", ucounter->allowed_unknown_users)); } /* Reset stats for next message */ ucounter->rejected_clients = 0; - ucounter->allowed_score = 0; - ucounter->allowed_sasl = 0; - ucounter->allowed_webirc = 0; - ucounter->allowed_other = 0; + ucounter->allowed_except = 0; + ucounter->allowed_unknown_users = 0; ucounter->throttling_previous_minute = ucounter->throttling_this_minute; ucounter->throttling_this_minute = 0; /* reset */ @@ -399,24 +399,8 @@ int ct_pre_lconnect(Client *client) if (still_reputation_gathering()) return HOOK_CONTINUE; /* still gathering reputation data */ - if (cfg.sasl_bypass && IsLoggedIn(client)) - { - /* Allowed in: user authenticated using SASL */ - return HOOK_CONTINUE; - } - - if (cfg.webirc_bypass && moddata_client_get(client, "webirc")) - { - /* Allowed in: user using WEBIRC */ - return HOOK_CONTINUE; - } - - score = GetReputation(client); - if (score >= cfg.minimum_reputation_score) - { - /* Allowed in: IP has enough reputation ("known user") */ - return HOOK_CONTINUE; - } + if (user_allowed_by_security_group(client, cfg.except)) + return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */ /* If we reach this then the user is NEW */ @@ -486,30 +470,14 @@ int ct_lconnect(Client *client) if (still_reputation_gathering()) return 0; /* still gathering reputation data */ - if (cfg.sasl_bypass && IsLoggedIn(client)) + if (user_allowed_by_security_group(client, cfg.except)) { - /* Allowed in: user authenticated using SASL */ - ucounter->allowed_sasl++; - return 0; - } - - if (cfg.webirc_bypass && moddata_client_get(client, "webirc")) - { - /* Allowed in: user using WEBIRC */ - ucounter->allowed_webirc++; - return 0; - } - - score = GetReputation(client); - if (score >= cfg.minimum_reputation_score) - { - /* Allowed in: IP has enough reputation ("known user") */ - ucounter->allowed_score++; - return 0; + ucounter->allowed_except++; + return HOOK_CONTINUE; /* allowed: user is exempt (known user or otherwise) */ } /* Allowed NEW user */ - ucounter->allowed_other++; + ucounter->allowed_unknown_users++; bump_connect_counter(1); @@ -539,9 +507,8 @@ int ct_rconnect(Client *client) } #endif - score = GetReputation(client); - if (score >= cfg.minimum_reputation_score) - return 0; /* sufficient reputation: "known-user" */ + if (user_allowed_by_security_group(client, cfg.except)) + return 0; /* user is on except list (known user or otherwise) */ bump_connect_counter(0); diff --git a/src/modules/creationtime.c b/src/modules/creationtime.c new file mode 100644 index 0000000..251e65f --- /dev/null +++ b/src/modules/creationtime.c @@ -0,0 +1,99 @@ +/* + * Store creationtime in ModData + * (C) Copyright 2022-.. Syzop and The UnrealIRCd Team + * License: GPLv2 or later + */ + +#include "unrealircd.h" + +ModuleHeader MOD_HEADER + = { + "creationtime", + "6.0", + "Store and retrieve creation time of clients", + "UnrealIRCd Team", + "unrealircd-6", + }; + +/* Forward declarations */ +void creationtime_free(ModData *m); +const char *creationtime_serialize(ModData *m); +void creationtime_unserialize(const char *str, ModData *m); +int creationtime_handshake(Client *client); +int creationtime_welcome_user(Client *client, int numeric); +int creationtime_whois(Client *client, Client *target); + +ModDataInfo *creationtime_md; /* Module Data structure which we acquire */ + +#define SetCreationTime(x,y) do { moddata_client(x, creationtime_md).ll = y; } while(0) + +MOD_INIT() +{ + ModDataInfo mreq; + + MARK_AS_OFFICIAL_MODULE(modinfo); + + memset(&mreq, 0, sizeof(mreq)); + mreq.name = "creationtime"; + mreq.free = creationtime_free; + mreq.serialize = creationtime_serialize; + mreq.unserialize = creationtime_unserialize; + mreq.sync = MODDATA_SYNC_EARLY; + mreq.type = MODDATATYPE_CLIENT; + creationtime_md = ModDataAdd(modinfo->handle, mreq); + if (!creationtime_md) + abort(); + + /* This event sets creationtime very early: on handshake in and out */ + HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, creationtime_handshake); + HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, creationtime_handshake); + + /* And this event (re)sets it because that also happens in + * welcome_user() in nick.c regarding #2174 + */ + HookAdd(modinfo->handle, HOOKTYPE_WELCOME, 0, creationtime_welcome_user); + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +int creationtime_handshake(Client *client) +{ + SetCreationTime(client, client->local->creationtime); + return 0; +} + +int creationtime_welcome_user(Client *client, int numeric) +{ + if (numeric == 0) + SetCreationTime(client, client->local->creationtime); + return 0; +} + +void creationtime_free(ModData *m) +{ + m->ll = 0; +} + +const char *creationtime_serialize(ModData *m) +{ + static char buf[64]; + + snprintf(buf, sizeof(buf), "%lld", (long long)m->ll); + return buf; +} + +void creationtime_unserialize(const char *str, ModData *m) +{ + m->ll = atoll(str); +} diff --git a/src/modules/extbans/account.c b/src/modules/extbans/account.c index d28b0e8..16a30ae 100644 --- a/src/modules/extbans/account.c +++ b/src/modules/extbans/account.c @@ -31,11 +31,10 @@ ModuleHeader MOD_HEADER const char *extban_account_conv_param(BanContext *b, Extban *extban); int extban_account_is_banned(BanContext *b); -/** Called upon module init */ -MOD_INIT() +Extban *register_account_extban(ModuleInfo *modinfo) { ExtbanInfo req; - + memset(&req, 0, sizeof(req)); req.letter = 'a'; req.name = "account"; @@ -44,14 +43,30 @@ MOD_INIT() req.is_banned = extban_account_is_banned; req.is_banned_events = BANCHK_ALL|BANCHK_TKL; req.options = EXTBOPT_INVEX|EXTBOPT_TKL; - if (!ExtbanAdd(modinfo->handle, req)) + return ExtbanAdd(modinfo->handle, req); +} + +/** Called upon module test */ +MOD_TEST() +{ + if (!register_account_extban(modinfo)) + { + config_error("could not register extended ban type"); + return MOD_FAILED; + } + return MOD_SUCCESS; +} + +/** Called upon module init */ +MOD_INIT() +{ + if (!register_account_extban(modinfo)) { config_error("could not register extended ban type"); return MOD_FAILED; } MARK_AS_OFFICIAL_MODULE(modinfo); - return MOD_SUCCESS; } @@ -95,7 +110,7 @@ int extban_account_is_banned(BanContext *b) if (!strcmp(b->banstr, "*") && IsLoggedIn(b->client)) return 1; - if (match_simple(b->banstr, b->client->user->account)) + if (b->client->user && match_simple(b->banstr, b->client->user->account)) return 1; return 0; diff --git a/src/modules/extbans/certfp.c b/src/modules/extbans/certfp.c index 8bb1102..df44b4d 100644 --- a/src/modules/extbans/certfp.c +++ b/src/modules/extbans/certfp.c @@ -32,11 +32,10 @@ int extban_certfp_is_ok(BanContext *b); const char *extban_certfp_conv_param(BanContext *b, Extban *extban); int extban_certfp_is_banned(BanContext *b); -/* Called upon module init */ -MOD_INIT() +Extban *register_certfp_extban(ModuleInfo *modinfo) { ExtbanInfo req; - + memset(&req, 0, sizeof(req)); req.letter = 'S'; req.name = "certfp"; @@ -45,14 +44,32 @@ MOD_INIT() req.is_banned = extban_certfp_is_banned; req.is_banned_events = BANCHK_ALL|BANCHK_TKL; req.options = EXTBOPT_INVEX|EXTBOPT_TKL; - if (!ExtbanAdd(modinfo->handle, req)) + return ExtbanAdd(modinfo->handle, req); +} + +/* Called upon module test */ +MOD_TEST() +{ + if (!register_certfp_extban(modinfo)) + { + config_error("could not register extended ban type"); + return MOD_FAILED; + } + + return MOD_SUCCESS; +} + +/* Called upon module init */ +MOD_INIT() +{ + if (!register_certfp_extban(modinfo)) { config_error("could not register extended ban type"); return MOD_FAILED; } MARK_AS_OFFICIAL_MODULE(modinfo); - + return MOD_SUCCESS; } @@ -82,10 +99,10 @@ int extban_certfp_is_ok(BanContext *b) if (b->is_ok_check == EXCHK_PARAM) { const char *p; - + if (strlen(b->banstr) != CERT_FP_LEN) return extban_certfp_usage(b->client); - + for (p = b->banstr; *p; p++) if (!isxdigit(*p)) return extban_certfp_usage(b->client); @@ -100,9 +117,9 @@ const char *extban_certfp_conv_param(BanContext *b, Extban *extban) { static char retbuf[EVP_MAX_MD_SIZE * 2 + 1]; char *p; - + strlcpy(retbuf, b->banstr, sizeof(retbuf)); - + for (p = retbuf; *p; p++) { *p = tolower(*p); diff --git a/src/modules/extbans/country.c b/src/modules/extbans/country.c index 012bfe9..520a3c4 100644 --- a/src/modules/extbans/country.c +++ b/src/modules/extbans/country.c @@ -32,8 +32,7 @@ int extban_country_is_ok(BanContext *b); const char *extban_country_conv_param(BanContext *b, Extban *extban); int extban_country_is_banned(BanContext *b); -/* Called upon module init */ -MOD_INIT() +Extban *register_country_extban(ModuleInfo *modinfo) { ExtbanInfo req; @@ -45,7 +44,25 @@ MOD_INIT() req.is_banned = extban_country_is_banned; req.is_banned_events = BANCHK_ALL|BANCHK_TKL; req.options = EXTBOPT_INVEX|EXTBOPT_TKL; - if (!ExtbanAdd(modinfo->handle, req)) + return ExtbanAdd(modinfo->handle, req); +} + +/* Called upon module test */ +MOD_TEST() +{ + if (!register_country_extban(modinfo)) + { + config_error("could not register extended ban type"); + return MOD_FAILED; + } + + return MOD_SUCCESS; +} + +/* Called upon module init */ +MOD_INIT() +{ + if (!register_country_extban(modinfo)) { config_error("could not register extended ban type"); return MOD_FAILED; diff --git a/src/modules/extbans/realname.c b/src/modules/extbans/realname.c index f2e94c9..7121015 100644 --- a/src/modules/extbans/realname.c +++ b/src/modules/extbans/realname.c @@ -31,11 +31,10 @@ ModuleHeader MOD_HEADER const char *extban_realname_conv_param(BanContext *b, Extban *extban); int extban_realname_is_banned(BanContext *b); -/** Called upon module init */ -MOD_INIT() +Extban *register_realname_extban(ModuleInfo *modinfo) { ExtbanInfo req; - + memset(&req, 0, sizeof(req)); req.letter = 'r'; req.name = "realname"; @@ -44,14 +43,30 @@ MOD_INIT() req.is_banned = extban_realname_is_banned; req.is_banned_events = BANCHK_ALL|BANCHK_TKL; req.options = EXTBOPT_INVEX|EXTBOPT_TKL; - if (!ExtbanAdd(modinfo->handle, req)) + return ExtbanAdd(modinfo->handle, req); +} + +/** Called upon module test */ +MOD_TEST() +{ + if (!register_realname_extban(modinfo)) + { + config_error("could not register extended ban type"); + return MOD_FAILED; + } + return MOD_SUCCESS; +} + +/** Called upon module init */ +MOD_INIT() +{ + if (!register_realname_extban(modinfo)) { config_error("could not register extended ban type"); return MOD_FAILED; } MARK_AS_OFFICIAL_MODULE(modinfo); - return MOD_SUCCESS; } diff --git a/src/modules/extbans/securitygroup.c b/src/modules/extbans/securitygroup.c index ac412f2..e0fa33a 100644 --- a/src/modules/extbans/securitygroup.c +++ b/src/modules/extbans/securitygroup.c @@ -32,8 +32,7 @@ const char *extban_securitygroup_conv_param(BanContext *b, Extban *extban); int extban_securitygroup_is_ok(BanContext *b); int extban_securitygroup_is_banned(BanContext *b); -/** Called upon module init */ -MOD_INIT() +Extban *register_securitygroup_extban(ModuleInfo *modinfo) { ExtbanInfo req; @@ -45,7 +44,25 @@ MOD_INIT() req.is_banned = extban_securitygroup_is_banned; req.is_banned_events = BANCHK_ALL|BANCHK_TKL; req.options = EXTBOPT_INVEX|EXTBOPT_TKL; - if (!ExtbanAdd(modinfo->handle, req)) + return ExtbanAdd(modinfo->handle, req); +} + +/** Called upon module test */ +MOD_TEST() +{ + if (!register_securitygroup_extban(modinfo)) + { + config_error("could not register extended ban type ~G"); + return MOD_FAILED; + } + + return MOD_SUCCESS; +} + +/** Called upon module init */ +MOD_INIT() +{ + if (!register_securitygroup_extban(modinfo)) { config_error("could not register extended ban type ~G"); return MOD_FAILED; diff --git a/src/modules/extbans/timedban.c b/src/modules/extbans/timedban.c index af20c52..a041037 100644 --- a/src/modules/extbans/timedban.c +++ b/src/modules/extbans/timedban.c @@ -1,7 +1,7 @@ /* * timedban - Timed bans that are automatically unset. * (C) Copyright 2009-2017 Bram Matthys (Syzop) and the UnrealIRCd team. - * License: GPLv2 + * License: GPLv2 or later * * This module adds an extended ban ~t:time:mask * Where 'time' is the time in minutes after which the ban will be removed. diff --git a/src/modules/extended-monitor.c b/src/modules/extended-monitor.c index b4aed6f..02f7c6c 100644 --- a/src/modules/extended-monitor.c +++ b/src/modules/extended-monitor.c @@ -49,7 +49,7 @@ MOD_INIT() ModDataInfo mreq; memset(&cap, 0, sizeof(cap)); - cap.name = "draft/extended-monitor"; + cap.name = "extended-monitor"; c = ClientCapabilityAdd(modinfo->handle, &cap, &CAP_EXTENDED_MONITOR); if (!c) { diff --git a/src/modules/geoip-tag.c b/src/modules/geoip-tag.c new file mode 100644 index 0000000..fdcb4d8 --- /dev/null +++ b/src/modules/geoip-tag.c @@ -0,0 +1,108 @@ +/* + * IRC - Internet Relay Chat, src/modules/geoip-tag.c + * (C) 2022 westor, Syzop and The UnrealIRCd Team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "unrealircd.h" + +ModuleHeader MOD_HEADER + = { + "geoip-tag", + "6.0", + "geoip message tag", + "UnrealIRCd Team", + "unrealircd-6", + }; + +/* Forward declarations */ +int geoip_mtag_is_ok(Client *client, const char *name, const char *value); +int geoip_mtag_should_send_to_client(Client *target); +void mtag_add_geoip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature); + +MOD_INIT() +{ + MessageTagHandlerInfo mtag; + + MARK_AS_OFFICIAL_MODULE(modinfo); + + memset(&mtag, 0, sizeof(mtag)); + mtag.name = "unrealircd.org/geoip"; + mtag.is_ok = geoip_mtag_is_ok; + mtag.should_send_to_client = geoip_mtag_should_send_to_client; + mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED; + MessageTagHandlerAdd(modinfo->handle, &mtag); + + HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_geoip); + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +/** This function verifies if the client sending + * 'geoip-tag' is permitted to do so and uses a permitted + * syntax. + * We simply allow geoip-tag ONLY from servers and with any syntax. + */ +int geoip_mtag_is_ok(Client *client, const char *name, const char *value) +{ + if (IsServer(client)) + return 1; + + return 0; +} + +void mtag_add_geoip(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature) +{ + MessageTag *m; + + GeoIPResult *geoip; + + if (IsUser(client) && ((geoip = geoip_client(client)))) + { + MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/geoip"); + if (m) + { + m = duplicate_mtag(m); + } else { + m = safe_alloc(sizeof(MessageTag)); + safe_strdup(m->name, "unrealircd.org/geoip"); + safe_strdup(m->value, geoip->country_code); + } + AddListItem(m, *mtag_list); + } +} + +/** Outgoing filter for this message tag */ +int geoip_mtag_should_send_to_client(Client *target) +{ + if (IsServer(target) || IsOper(target)) + return 1; + + return 0; +} diff --git a/src/modules/geoip_base.c b/src/modules/geoip_base.c index 1cf2ea1..545cfba 100644 --- a/src/modules/geoip_base.c +++ b/src/modules/geoip_base.c @@ -2,7 +2,7 @@ * GEOIP Base module, needed for all geoip functions * as this stores the geo information in ModData. * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -28,6 +28,7 @@ int geoip_base_handshake(Client *client); int geoip_base_ip_change(Client *client, const char *oldip); int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list); int geoip_connect_extinfo(Client *client, NameValuePrioList **list); +int geoip_log(Client *client, int detail, json_t *j); int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type); EVENT(geoip_base_set_existing_users_evt); @@ -125,6 +126,7 @@ MOD_INIT() HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 1, geoip_connect_extinfo); /* (prio: near-first) */ HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0,geoip_base_handshake); /* in case the IP changed in registration phase (WEBIRC, HTTP Forwarded) */ HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois); + HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, geoip_log); CommandAdd(modinfo->handle, "GEOIP", cmd_geoip, MAXPARA, CMD_USER); @@ -206,7 +208,7 @@ void geoip_base_unserialize(const char *str, ModData *m) char *country_code = NULL; GeoIPResult *res; - if (m->ptr == NULL) + if (m->ptr) { free_geoip_result((GeoIPResult *)m->ptr); m->ptr = NULL; @@ -254,6 +256,21 @@ int geoip_connect_extinfo(Client *client, NameValuePrioList **list) return 0; } +int geoip_log(Client *client, int detail, json_t *j) +{ + GeoIPResult *geo = GEOIPDATA(client); + json_t *geoip; + + if (!geo) + return 0; + + geoip = json_object(); + json_object_set_new(j, "geoip", geoip); + json_object_set_new(geoip, "country_code", json_string_unreal(geo->country_code)); + + return 0; +} + int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list) { GeoIPResult *geo; diff --git a/src/modules/geoip_classic.c b/src/modules/geoip_classic.c index c7299fc..7476669 100644 --- a/src/modules/geoip_classic.c +++ b/src/modules/geoip_classic.c @@ -1,6 +1,6 @@ /* GEOIP Classic module * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/geoip_maxmind.c b/src/modules/geoip_maxmind.c index 06581f3..61e9e32 100644 --- a/src/modules/geoip_maxmind.c +++ b/src/modules/geoip_maxmind.c @@ -1,6 +1,6 @@ /* GEOIP maxmind module * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/hideserver.c b/src/modules/hideserver.c index 4ed3ab9..46acc56 100644 --- a/src/modules/hideserver.c +++ b/src/modules/hideserver.c @@ -459,7 +459,7 @@ CMD_OVERRIDE_FUNC(override_links) sendnumeric(client, RPL_LINKS, acptr->name, me.name, 1, (acptr->info[0] ? acptr->info : "(Unknown Location)")); else - sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink->name, + sendnumeric(client, RPL_LINKS, acptr->name, acptr->uplink ? acptr->uplink->name : me.name, acptr->hopcount, (acptr->info[0] ? acptr->info : "(Unknown Location)")); } diff --git a/src/modules/history_backend_mem.c b/src/modules/history_backend_mem.c index 9fd5117..a288c56 100644 --- a/src/modules/history_backend_mem.c +++ b/src/modules/history_backend_mem.c @@ -1,6 +1,6 @@ /* src/modules/history_backend_mem.c - History Backend: memory * (C) Copyright 2019-2021 Bram Matthys (Syzop) and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/history_backend_null.c b/src/modules/history_backend_null.c index 7136ce2..e5fd553 100644 --- a/src/modules/history_backend_null.c +++ b/src/modules/history_backend_null.c @@ -1,6 +1,6 @@ /* src/modules/history_backend_null.c - History Backend: null / none * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/ident_lookup.c b/src/modules/ident_lookup.c index c729bcc..9fbe9c1 100644 --- a/src/modules/ident_lookup.c +++ b/src/modules/ident_lookup.c @@ -1,6 +1,6 @@ /* src/modules/ident_lookup.c - Ident lookups (RFC1413) * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/ison.c b/src/modules/ison.c index 928810c..7a2832c 100644 --- a/src/modules/ison.c +++ b/src/modules/ison.c @@ -62,10 +62,9 @@ MOD_UNLOAD() * ISON :nicklist */ -static char buf[BUFSIZE]; - CMD_FUNC(cmd_ison) { + char buf[BUFSIZE]; char request[BUFSIZE]; char namebuf[USERLEN + HOSTLEN + 4]; Client *acptr; diff --git a/src/modules/join.c b/src/modules/join.c index 37610b8..0428b5b 100644 --- a/src/modules/join.c +++ b/src/modules/join.c @@ -94,37 +94,32 @@ MOD_UNLOAD() */ int _can_join(Client *client, Channel *channel, const char *key, char **errmsg) { - Link *lp; - Ban *banned; Hook *h; - int i=0, j=0; + + /* An /INVITE lets you bypass all restrictions */ + if (is_invited(client, channel)) + { + int j = 0; + for (h = Hooks[HOOKTYPE_INVITE_BYPASS]; h; h = h->next) + { + j = (*(h->func.intfunc))(client,channel); + if (j != 0) + break; + } + /* Bypass is OK, unless a HOOKTYPE_INVITE_BYPASS hook returns HOOK_DENY */ + if (j != HOOK_DENY) + return 0; + } for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next) { - i = (*(h->func.intfunc))(client,channel,key, errmsg); + int i = (*(h->func.intfunc))(client,channel,key, errmsg); if (i != 0) return i; } - for (h = Hooks[HOOKTYPE_OPER_INVITE_BAN]; h; h = h->next) - { - j = (*(h->func.intfunc))(client,channel); - if (j != 0) - break; - } - /* See if we can evade this ban */ - banned = is_banned(client, channel, BANCHK_JOIN, NULL, NULL); - if (banned && j == HOOK_DENY) - { - *errmsg = STR_ERR_BANNEDFROMCHAN; - return ERR_BANNEDFROMCHAN; - } - - if (is_invited(client, channel)) - return 0; /* allowed to walk through all the other modes */ - - if (banned) + if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL)) { *errmsg = STR_ERR_BANNEDFROMCHAN; return ERR_BANNEDFROMCHAN; @@ -262,6 +257,7 @@ void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, con MessageTag *mtags_mode = NULL; Cmode *cm; char modebuf[BUFSIZE], parabuf[BUFSIZE]; + int should_destroy = 0; channel->mode.mode = MODES_ON_JOIN; @@ -281,6 +277,7 @@ void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, con sendto_server(NULL, 0, 0, mtags_mode, ":%s MODE %s %s %s %lld", me.id, channel->name, modebuf, parabuf, (long long)channel->creationtime); sendto_one(client, mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf); + RunHook(HOOKTYPE_LOCAL_CHANMODE, &me, channel, mtags_mode, modebuf, parabuf, 0, 0, &should_destroy); free_message_tags(mtags_mode); } diff --git a/src/modules/kill.c b/src/modules/kill.c index 2a62a75..0e82c18 100644 --- a/src/modules/kill.c +++ b/src/modules/kill.c @@ -21,7 +21,6 @@ #include "unrealircd.h" CMD_FUNC(cmd_kill); -static char buf[BUFSIZE], buf2[BUFSIZE]; ModuleHeader MOD_HEADER = { @@ -58,6 +57,7 @@ CMD_FUNC(cmd_kill) { char targetlist[BUFSIZE]; char reason[BUFSIZE]; + char buf2[BUFSIZE]; char *str; char *nick, *save = NULL; Client *target; diff --git a/src/modules/labeled-response.c b/src/modules/labeled-response.c index d1a0f4f..ae49ef9 100644 --- a/src/modules/labeled-response.c +++ b/src/modules/labeled-response.c @@ -197,11 +197,15 @@ int lr_post_command(Client *from, MessageTag *mtags, const char *buf) int more_tags = currentcmd.firstbuf[0] == '@'; currentcmd.client = NULL; /* prevent lr_packet from interfering */ snprintf(packet, sizeof(packet)-3, - "@label=%s%s%s\r\n", + "@label=%s%s%s", currentcmd.label, more_tags ? ";" : " ", more_tags ? currentcmd.firstbuf+1 : currentcmd.firstbuf); - sendbufto_one(from, packet, 0); + /* Format the IRC message correctly here, so we can take the + * quick path through sendbufto_one(). + */ + strlcat(packet, "\r\n", sizeof(packet)); + sendbufto_one(from, packet, strlen(packet)); goto done; } diff --git a/src/modules/message.c b/src/modules/message.c index 62a1dbe..43290fa 100644 --- a/src/modules/message.c +++ b/src/modules/message.c @@ -22,7 +22,6 @@ /* Forward declarations */ const char *_StripColors(const char *text); -const char *_StripControlCodes(const char *text); int ban_version(Client *client, const char *text); CMD_FUNC(cmd_private); CMD_FUNC(cmd_notice); @@ -47,7 +46,6 @@ MOD_TEST() { MARK_AS_OFFICIAL_MODULE(modinfo); EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCOLORS, _StripColors); - EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCONTROLCODES, _StripControlCodes); EfunctionAdd(modinfo->handle, EFUNC_CAN_SEND_TO_CHANNEL, _can_send_to_channel); return MOD_SUCCESS; } @@ -526,8 +524,10 @@ CMD_FUNC(cmd_tagmsg) /* Taken from xchat by Peter Zelezny * changed very slightly by codemastr * RGB color stripping support added -- codemastr + * + * NOTE: if you change/update/enhance StripColors() then consider changing + * the StripControlCodes() function as well (in misc.c) !! */ - const char *_StripColors(const char *text) { int i = 0, len = strlen(text), save_len=0; @@ -595,103 +595,6 @@ const char *_StripColors(const char *text) return new_str; } -/* strip color, bold, underline, and reverse codes from a string */ -const char *_StripControlCodes(const char *text) -{ - int i = 0, len = strlen(text), save_len=0; - char nc = 0, col = 0, rgb = 0; - const char *save_text=NULL; - static unsigned char new_str[4096]; - while (len > 0) - { - if ( col && ((isdigit(*text) && nc < 2) || (*text == ',' && nc < 3))) - { - nc++; - if (*text == ',') - nc = 0; - } - /* Syntax for RGB is ^DHHHHHH where H is a hex digit. - * If < 6 hex digits are specified, the code is displayed - * as text - */ - else if ((rgb && isxdigit(*text) && nc < 6) || (rgb && *text == ',' && nc < 7)) - { - nc++; - if (*text == ',') - nc = 0; - } - else - { - if (col) - col = 0; - if (rgb) - { - if (nc != 6) - { - text = save_text+1; - len = save_len-1; - rgb = 0; - continue; - } - rgb = 0; - } - switch (*text) - { - case 3: - /* color */ - col = 1; - nc = 0; - break; - case 4: - /* RGB */ - save_text = text; - save_len = len; - rgb = 1; - nc = 0; - break; - case 2: - /* bold */ - break; - case 31: - /* underline */ - break; - case 22: - /* reverse */ - break; - case 15: - /* plain */ - break; - case 29: - /* italic */ - break; - case 30: - /* strikethrough */ - break; - case 17: - /* monospace */ - break; - case 0xe2: - if (!strncmp(text+1, "\x80\x8b", 2)) - { - /* +2 means we skip 3 */ - text += 2; - len -= 2; - break; - } - /*fallthrough*/ - default: - new_str[i] = *text; - i++; - break; - } - } - text++; - len--; - } - new_str[i] = 0; - return new_str; -} - /** Check ban version { } blocks, returns 1 if banned and 0 if not. */ int ban_version(Client *client, const char *text) { diff --git a/src/modules/mode.c b/src/modules/mode.c index 90fe1d9..f1ec5df 100644 --- a/src/modules/mode.c +++ b/src/modules/mode.c @@ -472,8 +472,8 @@ MultiLineMode *make_mode_str(Client *client, Channel *channel, Cmode_t oldem, in if (curr == MAXMULTILINEMODES) { /* Should be impossible.. */ - unreal_log(ULOG_ERROR, "mode", "MODE_MULTINE_EXCEEDED", client, - "A mode string caused an avalanche effect of more than $max_multiline modes " + unreal_log(ULOG_ERROR, "mode", "MODE_MULTILINE_EXCEEDED", client, + "A mode string caused an avalanche effect of more than $max_multiline_modes modes " "in channel $channel. Caused by client $client. Expect a desync.", log_data_integer("max_multiline_modes", MAXMULTILINEMODES), log_data_channel("channel", channel)); @@ -807,6 +807,7 @@ void do_mode_char_member_mode_new(Channel *channel, Cmode *handler, const char * "[BUG] Client $target.details on channel $channel: " "found via find_membership_link() but NOT found via find_member_link(). " "This should never happen! Please report on https://bugs.unrealircd.org/", + log_data_client("target", target), log_data_channel("channel", channel)); return; } @@ -816,6 +817,58 @@ void do_mode_char_member_mode_new(Channel *channel, Cmode *handler, const char * if ((what == MODE_DEL) && !strchr(member->member_modes, modechar)) return; /* already unset */ + /* HOOKTYPE_MODE_DEOP code */ + if (what == MODE_DEL) + { + int ret = EX_ALLOW; + const char *badmode = NULL; + Hook *h; + const char *my_access; + Membership *my_membership; + + /* Set "my_access" to access flags of the requestor */ + if (IsUser(client) && (my_membership = find_membership_link(client->user->channel, channel))) + my_access = my_membership->member_modes; /* client */ + else + my_access = ""; /* server */ + + for (h = Hooks[HOOKTYPE_MODE_DEOP]; h; h = h->next) + { + int n = (*(h->func.intfunc))(client, target, channel, what, modechar, my_access, member->member_modes, &badmode); + if (n == EX_DENY) + { + ret = n; + } else + if (n == EX_ALWAYS_DENY) + { + ret = n; + break; + } + } + + if (ret == EX_ALWAYS_DENY) + { + if (MyUser(client) && badmode) + sendto_one(client, NULL, "%s", badmode); /* send error message, if any */ + + if (MyUser(client)) + return; /* stop processing this mode */ + } + + /* This probably should work but is completely untested (the operoverride stuff, I mean): */ + if (ret == EX_DENY) + { + if (!op_can_override("channel:override:mode:del",client,channel,handler)) + { + if (badmode) + sendto_one(client, NULL, "%s", badmode); /* send error message, if any */ + return; /* stop processing this mode */ + } else { + opermode = 1; + } + } + } + if (what == MODE_ADD) { if (strchr(member->member_modes, modechar)) @@ -1306,8 +1359,6 @@ CMD_FUNC(_cmd_umode) /* Notify */ userhost_changed(client); - if (MyUser(client)) - sendnumeric(client, RPL_HOSTHIDDEN, client->user->virthost); } /* -x */ @@ -1321,8 +1372,6 @@ CMD_FUNC(_cmd_umode) /* Notify */ userhost_changed(client); - if (MyUser(client)) - sendnumeric(client, RPL_HOSTHIDDEN, client->user->realhost); } /* * If I understand what this code is doing correctly... @@ -1337,7 +1386,7 @@ CMD_FUNC(_cmd_umode) { list_del(&client->special_node); if (MyUser(client)) - RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL); + RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL); remove_oper_privileges(client, 0); } diff --git a/src/modules/names.c b/src/modules/names.c index e61bce0..9e735e0 100644 --- a/src/modules/names.c +++ b/src/modules/names.c @@ -68,8 +68,6 @@ MOD_UNLOAD() * 12 Feb 2000 - geesh, time for a rewrite -lucas ************************************************************************/ -static char buf[BUFSIZE]; - /* ** cmd_names ** parv[1] = channel @@ -88,6 +86,7 @@ CMD_FUNC(cmd_names) int idx, flag = 1, spos; const char *para = parv[1], *s; char nuhBuffer[NICKLEN+USERLEN+HOSTLEN+3]; + char buf[BUFSIZE]; if (parc < 2 || !MyConnect(client)) { diff --git a/src/modules/nick.c b/src/modules/nick.c index 377b375..7104c07 100644 --- a/src/modules/nick.c +++ b/src/modules/nick.c @@ -46,7 +46,6 @@ ModuleHeader MOD_HEADER #define ASSUME_NICK_IN_FLIGHT /* Variables */ -static char buf[BUFSIZE]; static char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* Forward declarations */ @@ -489,6 +488,7 @@ CMD_FUNC(cmd_uid) Client *acptr, *serv = NULL; Client *acptrs; char nick[NICKLEN + 1]; + char buf[BUFSIZE]; long lastnick = 0; int differ = 1; const char *hostname, *username, *sstamp, *umodes, *virthost, *ip_raw, *realname; @@ -758,10 +758,21 @@ CMD_FUNC(cmd_nick) } } +/** Welcome the user on IRC. + * Send the RPL_WELCOME, LUSERS, MOTD, auto join channels, everything... + */ void welcome_user(Client *client, TKL *viruschan_tkl) { int i; ConfigItem_tld *tlds; + char buf[BUFSIZE]; + + /* Make creation time the real 'online since' time, excluding registration time. + * Otherwise things like set::anti-spam-quit-messagetime 10s could mean + * 1 second in practice (#2174). + */ + client->local->creationtime = TStime(); + client->local->idle_since = TStime(); RunHook(HOOKTYPE_WELCOME, client, 0); sendnumeric(client, RPL_WELCOME, NETWORK_NAME, client->name, client->user->username, client->user->realhost); @@ -826,10 +837,11 @@ void welcome_user(Client *client, TKL *viruschan_tkl) sendto_serv_butone_nickcmd(client->direction, NULL, client, (*buf == '\0' ? "+" : buf)); broadcast_moddata_client(client); - RunHook(HOOKTYPE_LOCAL_CONNECT, client); + if (buf[0] != '\0' && buf[1] != '\0') sendto_one(client, NULL, ":%s MODE %s :%s", client->name, client->name, buf); + if (client->user->snomask) sendnumeric(client, RPL_SNOMASK, client->user->snomask); @@ -839,12 +851,7 @@ void welcome_user(Client *client, TKL *viruschan_tkl) if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_WARN) && outdated_tls_client(client)) sendnotice(client, "%s", outdated_tls_client_build_string(iConf.outdated_tls_policy_user_message, client)); - /* Make creation time the real 'online since' time, excluding registration time. - * Otherwise things like set::anti-spam-quit-messagetime 10s could mean - * 1 second in practice (#2174). - */ - client->local->creationtime = TStime(); - client->local->idle_since = TStime(); + RunHook(HOOKTYPE_LOCAL_CONNECT, client); /* Give the user a fresh start as far as fake-lag is concerned. * Otherwise the user could be lagged up already due to all the CAP stuff. @@ -1261,7 +1268,7 @@ int AllowClient(Client *client) if (aconf->flags.tls && !IsSecure(client)) continue; - if (!unreal_mask_match(client, aconf->mask)) + if (!user_allowed_by_security_group(client, aconf->match)) continue; /* Check authentication */ diff --git a/src/modules/oper.c b/src/modules/oper.c index c2a6541..395f58a 100644 --- a/src/modules/oper.c +++ b/src/modules/oper.c @@ -20,10 +20,6 @@ #include "unrealircd.h" -CMD_FUNC(cmd_oper); - - -/* Place includes here */ #define MSG_OPER "OPER" /* OPER */ ModuleHeader MOD_HEADER @@ -35,27 +31,37 @@ ModuleHeader MOD_HEADER "unrealircd-6", }; -/* This is called on module init, before Server Ready */ -MOD_INIT() +/* Forward declarations */ +CMD_FUNC(cmd_oper); +int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost); +int oper_connect(Client *client); + +MOD_TEST() { - CommandAdd(modinfo->handle, MSG_OPER, cmd_oper, MAXPARA, CMD_USER); MARK_AS_OFFICIAL_MODULE(modinfo); + EfunctionAdd(modinfo->handle, EFUNC_MAKE_OPER, _make_oper); + return MOD_SUCCESS; +} + +MOD_INIT() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + CommandAdd(modinfo->handle, MSG_OPER, cmd_oper, MAXPARA, CMD_USER); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, oper_connect); return MOD_SUCCESS; } -/* Is first run when server is 100% ready */ MOD_LOAD() { return MOD_SUCCESS; } -/* Called when module is unloaded */ MOD_UNLOAD() { return MOD_SUCCESS; } -void set_oper_host(Client *client, char *host) +void set_oper_host(Client *client, const char *host) { char uhost[HOSTLEN + USERLEN + 1]; char *p; @@ -70,8 +76,96 @@ void set_oper_host(Client *client, char *host) client->id, client->user->username); host = p; } - iNAH_host(client, host); - SetHidden(client); + safe_strdup(client->user->virthost, host); + if (MyConnect(client)) + sendto_server(NULL, 0, 0, NULL, ":%s SETHOST :%s", client->id, client->user->virthost); + client->umodes |= UMODE_SETHOST|UMODE_HIDE; +} + +int _make_oper(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost) +{ + long old_umodes = client->umodes & ALL_UMODES; + + userhost_save_current(client); + + /* Put in the right class (if any) */ + if (clientclass) + { + if (client->local->class) + client->local->class->clients--; + client->local->class = clientclass; + client->local->class->clients++; + } + + /* set oper user modes */ + client->umodes |= UMODE_OPER; + if (modes) + client->umodes |= modes; /* oper::modes */ + else + client->umodes |= OPER_MODES; /* set::modes-on-oper */ + + /* oper::vhost */ + if (vhost) + { + set_oper_host(client, vhost); + } else + if (IsHidden(client) && !client->user->virthost) + { + /* +x has just been set by modes-on-oper and no vhost. cloak the oper! */ + safe_strdup(client->user->virthost, client->user->cloakedhost); + } + + userhost_changed(client); + + unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client, + "$client.details is now an IRC Operator [oper-block: $oper_block] [operclass: $operclass]", + log_data_string("oper_block", operblock_name), + log_data_string("operclass", operclass)); + + /* set oper snomasks */ + if (snomask) + set_snomask(client, snomask); /* oper::snomask */ + else + set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */ + + send_umode_out(client, 1, old_umodes); + if (client->user->snomask) + sendnumeric(client, RPL_SNOMASK, client->user->snomask); + + list_add(&client->special_node, &oper_list); + + RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock_name, operclass); + + sendnumeric(client, RPL_YOUREOPER); + + /* Update statistics */ + if (IsInvisible(client) && !(old_umodes & UMODE_INVISIBLE)) + irccounts.invisible++; + if (IsOper(client) && !IsHideOper(client)) + irccounts.operators++; + + if (SHOWOPERMOTD == 1) + { + const char *args[1] = { NULL }; + do_cmd(client, NULL, "OPERMOTD", 1, args); + } + + if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0")) + { + char *chans = strdup(OPER_AUTO_JOIN_CHANS); + const char *args[3] = { + client->name, + chans, + NULL + }; + do_cmd(client, NULL, "JOIN", 3, args); + safe_free(chans); + /* Theoretically the oper may be killed on join. Would be fun, though */ + if (IsDead(client)) + return 0; + } + + return 1; } /* @@ -83,7 +177,6 @@ CMD_FUNC(cmd_oper) { ConfigItem_oper *operblock; const char *operblock_name, *password; - long old_umodes = client->umodes & ALL_UMODES; if (!MyUser(client)) return; @@ -152,7 +245,7 @@ CMD_FUNC(cmd_oper) * more seriously, they are logged as errors instead of warnings. */ - if (!unreal_mask_match(client, operblock->mask)) + if (!user_allowed_by_security_group(client, operblock->match)) { sendnumeric(client, ERR_NOOPERHOST); unreal_log(ULOG_ERROR, "oper", "OPER_FAILED", client, @@ -164,7 +257,7 @@ CMD_FUNC(cmd_oper) return; } - if (!Auth_Check(client, operblock->auth, password)) + if (operblock->auth && !Auth_Check(client, operblock->auth, password)) { sendnumeric(client, ERR_PASSWDMISMATCH); if (FAILOPER_WARN) @@ -227,12 +320,6 @@ CMD_FUNC(cmd_oper) /* Store which oper block was used to become IRCOp (for maxlogins and whois) */ safe_strdup(client->user->operlogin, operblock->name); - /* Put in the right class */ - if (client->local->class) - client->local->class->clients--; - client->local->class = operblock->class; - client->local->class->clients++; - /* oper::swhois */ if (operblock->swhois) { @@ -241,67 +328,7 @@ CMD_FUNC(cmd_oper) swhois_add(client, "oper", -100, s->line, &me, NULL); } - /* set oper user modes */ - client->umodes |= UMODE_OPER; - if (operblock->modes) - client->umodes |= operblock->modes; /* oper::modes */ - else - client->umodes |= OPER_MODES; /* set::modes-on-oper */ - - /* oper::vhost */ - if (operblock->vhost) - { - set_oper_host(client, operblock->vhost); - } else - if (IsHidden(client) && !client->user->virthost) - { - /* +x has just been set by modes-on-oper and no vhost. cloak the oper! */ - safe_strdup(client->user->virthost, client->user->cloakedhost); - } - - unreal_log(ULOG_INFO, "oper", "OPER_SUCCESS", client, - "$client.details is now an IRC Operator [oper-block: $oper_block]", - log_data_string("oper_block", parv[1])); - - /* set oper snomasks */ - if (operblock->snomask) - set_snomask(client, operblock->snomask); /* oper::snomask */ - else - set_snomask(client, OPER_SNOMASK); /* set::snomask-on-oper */ - - send_umode_out(client, 1, old_umodes); - if (client->user->snomask) - sendnumeric(client, RPL_SNOMASK, client->user->snomask); - - list_add(&client->special_node, &oper_list); - - RunHook(HOOKTYPE_LOCAL_OPER, client, 1, operblock); - - sendnumeric(client, RPL_YOUREOPER); - - /* Update statistics */ - if (IsInvisible(client) && !(old_umodes & UMODE_INVISIBLE)) - irccounts.invisible++; - if (IsOper(client) && !IsHideOper(client)) - irccounts.operators++; - - if (SHOWOPERMOTD == 1) - do_cmd(client, NULL, "OPERMOTD", parc, parv); - - if (!BadPtr(OPER_AUTO_JOIN_CHANS) && strcmp(OPER_AUTO_JOIN_CHANS, "0")) - { - char *chans = strdup(OPER_AUTO_JOIN_CHANS); - const char *args[3] = { - client->name, - chans, - NULL - }; - do_cmd(client, NULL, "JOIN", 3, args); - safe_free(chans); - /* Theoretically the oper may be killed on join. Would be fun, though */ - if (IsDead(client)) - return; - } + make_oper(client, operblock->name, operblock->operclass, operblock->class, operblock->modes, operblock->snomask, operblock->vhost); /* set::plaintext-policy::oper 'warn' */ if (!IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_oper == POLICY_WARN)) @@ -323,3 +350,29 @@ CMD_FUNC(cmd_oper) log_data_string("warn_type", "OUTDATED_TLS_PROTOCOL_OR_CIPHER")); } } + +int oper_connect(Client *client) +{ + ConfigItem_oper *e; + + if (IsOper(client)) + return 0; + + for (e = conf_oper; e; e = e->next) + { + if (e->auto_login && user_allowed_by_security_group(client, e->match)) + { + /* Ideally we would check all the criteria that cmd_oper does. + * I'm taking a shortcut for now that is not ideal... + */ + const char *parx[3]; + parx[0] = NULL; + parx[1] = e->name; + parx[2] = NULL; + do_cmd(client, NULL, "OPER", 3, parx); + return 0; + } + } + + return 0; +} diff --git a/src/modules/operinfo.c b/src/modules/operinfo.c index 7571c35..0b01676 100644 --- a/src/modules/operinfo.c +++ b/src/modules/operinfo.c @@ -1,7 +1,7 @@ /* * Store oper login in ModData, used by WHOIS and for auditting purposes. * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -16,7 +16,7 @@ ModuleHeader MOD_HEADER }; /* Forward declarations */ -int operinfo_local_oper(Client *client, int up, ConfigItem_oper *oper_block); +int operinfo_local_oper(Client *client, int up, const char *oper_block, const char *operclass); void operinfo_free(ModData *m); const char *operinfo_serialize(ModData *m); void operinfo_unserialize(const char *str, ModData *m); @@ -68,12 +68,12 @@ MOD_UNLOAD() return MOD_SUCCESS; } -int operinfo_local_oper(Client *client, int up, ConfigItem_oper *oper_block) +int operinfo_local_oper(Client *client, int up, const char *oper_block, const char *operclass) { if (up) { - moddata_client_set(client, "operlogin", oper_block->name); - moddata_client_set(client, "operclass", oper_block->operclass); + moddata_client_set(client, "operlogin", oper_block); + moddata_client_set(client, "operclass", operclass); } else { moddata_client_set(client, "operlogin", NULL); moddata_client_set(client, "operclass", NULL); diff --git a/src/modules/protoctl.c b/src/modules/protoctl.c index a1c7b31..2131200 100644 --- a/src/modules/protoctl.c +++ b/src/modules/protoctl.c @@ -206,7 +206,7 @@ CMD_FUNC(cmd_protoctl) if ((aclient = hash_find_id(sid, NULL)) != NULL) { unreal_log(ULOG_ERROR, "link", "LINK_DENIED_SID_COLLISION", client, - "Server link $client rejected. Server with SID $sid already exist via uplink $exiting_client.server.uplink.", + "Server link $client rejected. Server with SID $sid already exist via uplink $existing_client.server.uplink.", log_data_string("sid", sid), log_data_client("existing_client", aclient)); exit_client(client, NULL, "SID collision"); @@ -266,7 +266,7 @@ CMD_FUNC(cmd_protoctl) */ strlcpy(client->name, servername, sizeof(client->name)); - if (!verify_link(client, &aconf)) + if (!(aconf = verify_link(client))) return; /* note: software, protocol and flags may be NULL */ diff --git a/src/modules/reputation.c b/src/modules/reputation.c index 7757eec..ca9a580 100644 --- a/src/modules/reputation.c +++ b/src/modules/reputation.c @@ -1,7 +1,7 @@ /* * reputation - Provides a scoring system for "known users". * (C) Copyright 2015-2019 Bram Matthys (Syzop) and the UnrealIRCd team. - * License: GPLv2 + * License: GPLv2 or later * * How this works is simple: * Every 5 minutes the IP address of all the connected users receive @@ -918,6 +918,24 @@ static inline int is_reputation_expired(ReputationEntry *e) return 0; } +/** If the reputation changed (due to server syncing) then update the + * individual users reputation score as well. + */ +void reputation_changed_update_users(ReputationEntry *e) +{ + Client *client; + + list_for_each_entry(client, &client_list, client_node) + { + if (client->ip && !strcmp(e->ip, client->ip)) + { + /* With some (possibly unneeded) care to only go forward */ + if (Reputation(client) < e->score) + Reputation(client) = e->score; + } + } +} + EVENT(delete_old_records) { int i; @@ -1279,6 +1297,7 @@ CMD_FUNC(reputation_server_cmd) log_data_integer("score", e->score)); #endif e->score = score; + reputation_changed_update_users(e); } /* If we don't have any entry for this IP, add it now. */ @@ -1296,6 +1315,7 @@ CMD_FUNC(reputation_server_cmd) e->score = score; e->last_seen = TStime(); add_reputation_entry(e); + reputation_changed_update_users(e); } /* Propagate to the non-client direction (score may be updated) */ diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c index 1884bfb..28b6e75 100644 --- a/src/modules/restrict-commands.c +++ b/src/modules/restrict-commands.c @@ -32,11 +32,7 @@ struct RestrictedCommand { RestrictedCommand *prev, *next; char *cmd; char *conftag; - long connect_delay; - int exempt_identified; - int exempt_reputation_score; - int exempt_webirc; - int exempt_tls; + SecurityGroup *except; }; typedef struct { @@ -103,6 +99,7 @@ MOD_UNLOAD() next = rcmd->next; safe_free(rcmd->conftag); safe_free(rcmd->cmd); + free_security_group(rcmd->except); DelListItem(rcmd, RestrictedCommandList); safe_free(rcmd); } @@ -170,6 +167,12 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) continue; } + if (!strcmp(cep2->name, "except")) + { + test_match_block(cf, cep2, &errors); + continue; + } + if (!cep2->value) { config_error("%s:%i: blank set::restrict-commands::%s:%s without value", cep2->file->filename, cep2->line_number, cep->name, cep2->name); @@ -249,7 +252,13 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type) config_warn("[restrict-commands] Command '%s' does not exist. Did you mistype? Or is the module providing it not loaded?", cmd); continue; } - + if (find_restrictions_bycmd(cmd)) + { + config_warn("[restrict-commands] Multiple set::restrict-commands items for command '%s'. " + "Only one config block will be effective.", + cmd); + continue; + } if (!CommandOverrideAdd(ModInf.handle, cmd, 0, rcmd_override)) { config_warn("[restrict-commands] Failed to add override for '%s' (NO RESTRICTIONS APPLY)", cmd); @@ -260,38 +269,46 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type) rcmd = safe_alloc(sizeof(RestrictedCommand)); safe_strdup(rcmd->cmd, cmd); safe_strdup(rcmd->conftag, conftag); + rcmd->except = safe_alloc(sizeof(SecurityGroup)); + for (cep2 = cep->items; cep2; cep2 = cep2->next) { + if (!strcmp(cep2->name, "except")) + { + conf_match_block(cf, cep2, &rcmd->except); + continue; + } + if (!cep2->value) continue; if (!strcmp(cep2->name, "connect-delay")) { - rcmd->connect_delay = config_checkval(cep2->value, CFG_TIME); + rcmd->except->connect_time = config_checkval(cep2->value, CFG_TIME); continue; } if (!strcmp(cep2->name, "exempt-identified")) { - rcmd->exempt_identified = config_checkval(cep2->value, CFG_YESNO); + rcmd->except->identified = config_checkval(cep2->value, CFG_YESNO); continue; } if (!strcmp(cep2->name, "exempt-webirc")) { - rcmd->exempt_webirc = config_checkval(cep2->value, CFG_YESNO); + rcmd->except->webirc = config_checkval(cep2->value, CFG_YESNO); continue; } if (!strcmp(cep2->name, "exempt-tls")) { - rcmd->exempt_tls = config_checkval(cep2->value, CFG_YESNO); + rcmd->except->tls = config_checkval(cep2->value, CFG_YESNO); continue; } if (!strcmp(cep2->name, "exempt-reputation-score")) { - rcmd->exempt_reputation_score = atoi(cep2->value); + rcmd->except->reputation_score = atoi(cep2->value); continue; } } @@ -305,15 +322,7 @@ int rcmd_canbypass(Client *client, RestrictedCommand *rcmd) { if (!client || !rcmd) return 1; - if (rcmd->exempt_identified && IsLoggedIn(client)) - 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->creationtime >= rcmd->connect_delay)) + if (user_allowed_by_security_group(client, rcmd->except)) return 1; return 0; } @@ -351,11 +360,11 @@ int rcmd_block_message(Client *client, const char *text, SendType sendtype, cons if (rcmd && !rcmd_canbypass(client, rcmd)) { int notice = (sendtype == SEND_TYPE_NOTICE ? 1 : 0); // temporary hack FIXME !!! - if (rcmd->connect_delay) + if (rcmd->except->connect_time) { ircsnprintf(errbuf, sizeof(errbuf), "You cannot send %ss to %ss until you've been connected for %ld seconds or more", - (notice ? "notice" : "message"), display, rcmd->connect_delay); + (notice ? "notice" : "message"), display, rcmd->except->connect_time); } else { ircsnprintf(errbuf, sizeof(errbuf), "Sending of %ss to %ss been disabled by the network administrators", @@ -382,11 +391,11 @@ CMD_OVERRIDE_FUNC(rcmd_override) rcmd = find_restrictions_bycmd(ovr->command->cmd); if (rcmd && !rcmd_canbypass(client, rcmd)) { - if (rcmd->connect_delay) + if (rcmd->except->connect_time) { sendnumericfmt(client, ERR_UNKNOWNCOMMAND, "%s :You must be connected for at least %ld seconds before you can use this command", - ovr->command->cmd, rcmd->connect_delay); + ovr->command->cmd, rcmd->except->connect_time); } else { sendnumericfmt(client, ERR_UNKNOWNCOMMAND, "%s :This command is disabled by the network administrator", diff --git a/src/modules/sapart.c b/src/modules/sapart.c index 936a65d..4de8e47 100644 --- a/src/modules/sapart.c +++ b/src/modules/sapart.c @@ -167,6 +167,7 @@ CMD_FUNC(cmd_sapart) log_sapart(client, target, request, comment); +/*** if (comment) { snprintf(commentx, sizeof(commentx), "SAPart: %s", comment); @@ -174,6 +175,7 @@ CMD_FUNC(cmd_sapart) } else { sendnotice(target, "*** You were forced to part %s", request); } +***/ parv[0] = target->name; // nick parv[1] = request; // chan diff --git a/src/modules/server.c b/src/modules/server.c index a673aee..888b861 100644 --- a/src/modules/server.c +++ b/src/modules/server.c @@ -45,7 +45,7 @@ EVENT(server_handshake_timeout); void send_channel_modes_sjoin3(Client *to, Channel *channel); CMD_FUNC(cmd_server); CMD_FUNC(cmd_sid); -int _verify_link(Client *client, ConfigItem_link **link_out); +ConfigItem_link *_verify_link(Client *client); void _send_protoctl_servers(Client *client, int response); void _send_server_message(Client *client); void _introduce_user(Client *to, Client *acptr); @@ -59,7 +59,6 @@ void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp); static int connect_server_helper(ConfigItem_link *, Client *); /* Global variables */ -static char buf[BUFSIZE]; static cfgstruct cfg; static char *last_autoconnect_server = NULL; @@ -77,7 +76,7 @@ MOD_TEST() MARK_AS_OFFICIAL_MODULE(modinfo); EfunctionAddVoid(modinfo->handle, EFUNC_SEND_PROTOCTL_SERVERS, _send_protoctl_servers); EfunctionAddVoid(modinfo->handle, EFUNC_SEND_SERVER_MESSAGE, _send_server_message); - EfunctionAdd(modinfo->handle, EFUNC_VERIFY_LINK, _verify_link); + EfunctionAddPVoid(modinfo->handle, EFUNC_VERIFY_LINK, TO_PVOIDFUNC(_verify_link)); EfunctionAddVoid(modinfo->handle, EFUNC_INTRODUCE_USER, _introduce_user); EfunctionAdd(modinfo->handle, EFUNC_CHECK_DENY_VERSION, _check_deny_version); EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_SINFO, _broadcast_sinfo); @@ -254,8 +253,13 @@ int server_needs_linking(ConfigItem_link *aconf) Client *client; ConfigItem_class *class; - /* We're only interested in autoconnect blocks that are valid. Also, we ignore temporary link blocks. */ - if (!(aconf->outgoing.options & CONNECT_AUTO) || !aconf->outgoing.hostname || (aconf->flag.temporary == 1)) + /* We're only interested in autoconnect blocks that also have + * a valid link::outgoing configuration. We also ignore + * temporary link blocks (not that they should exist...). + */ + if (!(aconf->outgoing.options & CONNECT_AUTO) || + (!aconf->outgoing.hostname && !aconf->outgoing.file) || + (aconf->flag.temporary == 1)) return 0; class = aconf->class; @@ -635,12 +639,10 @@ void _send_server_message(Client *client) /** Verify server link. * This does authentication and authorization checks. - * @param cptr The client directly connected to us (cptr). - * @param client The client which (originally) issued the server command (client). - * @param link_out Pointer-to-pointer-to-link block. Will be set when auth OK. Caller may pass NULL if he doesn't care. - * @returns This function returns 1 on successful authentication, 0 otherwise - in which case the client has been killed. + * @param client The client which issued the command + * @returns On successfull authentication, the link block is returned. On failure NULL is returned (client has been killed!). */ -int _verify_link(Client *client, ConfigItem_link **link_out) +ConfigItem_link *_verify_link(Client *client) { ConfigItem_link *link, *orig_link; Client *acptr = NULL, *ocptr = NULL; @@ -652,15 +654,12 @@ int _verify_link(Client *client, ConfigItem_link **link_out) if (client->local->hostp && client->local->hostp->h_name) set_sockhost(client, client->local->hostp->h_name); - if (link_out) - *link_out = NULL; - if (!client->local->passwd) { unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_PASSWORD", client, "Link with server $client.details denied: No password provided. Protocol error."); exit_client(client, NULL, "Missing password"); - return 0; + return NULL; } if (client->server && client->server->conf) @@ -679,7 +678,7 @@ int _verify_link(Client *client, ConfigItem_link **link_out) log_data_link_block(client->server->conf)); exit_client_fmt(client, NULL, "Servername (%s) does not match name in my link block (%s)", client->name, client->server->conf->servername); - return 0; + return NULL; } link = client->server->conf; goto skip_host_check; @@ -695,16 +694,16 @@ int _verify_link(Client *client, ConfigItem_link **link_out) unreal_log(ULOG_ERROR, "link", "LINK_DENIED_UNKNOWN_SERVER", client, "Link with server $client.details denied: No link block named '$client'"); exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG); - return 0; + return NULL; } - if (!link->incoming.mask) + if (!link->incoming.match) { unreal_log(ULOG_ERROR, "link", "LINK_DENIED_NO_INCOMING", client, - "Link with server $client.details denied: Link block exists, but there is no link::incoming::mask set.", + "Link with server $client.details denied: Link block exists, but there is no link::incoming::match set.", log_data_link_block(link)); exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG); - return 0; + return NULL; } orig_link = link; @@ -716,7 +715,7 @@ int _verify_link(Client *client, ConfigItem_link **link_out) "Link with server $client.details denied: Server is in link block but link::incoming::mask didn't match", log_data_link_block(orig_link)); exit_client(client, NULL, LINK_DEFAULT_ERROR_MSG); - return 0; + return NULL; } skip_host_check: @@ -767,7 +766,7 @@ skip_host_check: log_data_link_block(link)); } exit_client(client, NULL, "Link denied (Authentication failed)"); - return 0; + return NULL; } /* Verify the TLS certificate (if requested) */ @@ -782,7 +781,7 @@ skip_host_check: log_data_string("certificate_failure_msg", "not using TLS"), log_data_link_block(link)); exit_client(client, NULL, "Link denied (Not using TLS)"); - return 0; + return NULL; } if (!verify_certificate(client->local->ssl, link->servername, &errstr)) { @@ -791,7 +790,7 @@ skip_host_check: log_data_string("certificate_failure_msg", errstr), log_data_link_block(link)); exit_client(client, NULL, "Link denied (Certificate verification failed)"); - return 0; + return NULL; } } @@ -803,7 +802,7 @@ skip_host_check: log_data_string("ban_reason", bconf->reason), log_data_link_block(link)); exit_client_fmt(client, NULL, "Banned server: %s", bconf->reason); - return 0; + return NULL; } if (link->class->clients + 1 > link->class->maxclients) @@ -813,7 +812,7 @@ skip_host_check: "class '$link_block.class' is full", log_data_link_block(link)); exit_client(client, NULL, "Full class"); - return 0; + return NULL; } if (!IsLocalhost(client) && (iConf.plaintext_policy_server == POLICY_DENY) && !IsSecure(client)) { @@ -823,7 +822,7 @@ skip_host_check: "See https://www.unrealircd.org/docs/FAQ#server-requires-tls", log_data_link_block(link)); exit_client(client, NULL, "Servers need to use TLS (set::plaintext-policy::server is 'deny')"); - return 0; + return NULL; } if (IsSecure(client) && (iConf.outdated_tls_policy_server == POLICY_DENY) && outdated_tls_client(client)) { @@ -834,7 +833,7 @@ skip_host_check: log_data_link_block(link), log_data_string("tls_cipher", tls_get_cipher(client))); exit_client(client, NULL, "Server using outdates TLS protocol or cipher (set::outdated-tls-policy::server is 'deny')"); - return 0; + return NULL; } /* This one is at the end, because it causes us to delink another server, * so we want to be (reasonably) sure that this one will succeed before @@ -850,7 +849,7 @@ skip_host_check: log_data_string("me_name", me.name), log_data_link_block(link)); exit_client(client, NULL, "Server Exists (server trying to link with same name as myself)"); - return 0; + return NULL; } else { unreal_log(ULOG_ERROR, "link", "LINK_DROPPED_REINTRODUCED", client, "Link with server $client.details causes older link " @@ -861,9 +860,7 @@ skip_host_check: } } - if (link_out) - *link_out = link; - return 1; + return link; } /** Server command. Only for locally connected servers!!. @@ -935,7 +932,7 @@ CMD_FUNC(cmd_server) */ strlcpy(client->name, servername, sizeof(client->name)); - if (!verify_link(client, &aconf)) + if (!(aconf = verify_link(client))) return; /* Rejected */ /* From this point the server is authenticated, so we can be more verbose @@ -1200,6 +1197,7 @@ CMD_FUNC(cmd_sid) unreal_log(ULOG_ERROR, "link", "REMOTE_LINK_DENIED_SERVER_BAN", client, "Denied remote server $servername which was introduced by $client: " "Server is banned ($ban_reason)", + log_data_string("servername", servername), log_data_string("ban_reason", bconf->reason)); /* Before UnrealIRCd 6 this would SQUIT the server who introduced * this server. That seems a bit of an overreaction, so we now @@ -1330,6 +1328,8 @@ CMD_FUNC(cmd_sid) void _introduce_user(Client *to, Client *acptr) { + char buf[512]; + build_umode_string(acptr, 0, SEND_UMODES, buf); sendto_one_nickcmd(to, NULL, acptr, buf); @@ -1807,13 +1807,13 @@ void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp) { Client *client; - if (!aconf->outgoing.hostname) + if (!aconf->outgoing.hostname && !aconf->outgoing.file) { /* Actually the caller should make sure that this doesn't happen, * so this error may never be triggered: */ unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NO_OUTGOING", NULL, - "Connect to $link_block failed: link block is for incoming only (no link::outgoing::hostname set)", + "Connect to $link_block failed: link block is for incoming only (no link::outgoing::hostname or link::outgoing::file set)", log_data_link_block(aconf)); return; } @@ -1827,7 +1827,7 @@ void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp) * If we dont know the IP# for this host and itis a hostname and * not a ip# string, then try and find the appropriate host record. */ - if (!aconf->connect_ip) + if (!aconf->connect_ip && !aconf->outgoing.file) { if (is_valid_ip(aconf->outgoing.hostname)) { @@ -1861,7 +1861,7 @@ void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp) * Copy these in so we have something for error detection. */ strlcpy(client->name, aconf->servername, sizeof(client->name)); - strlcpy(client->local->sockhost, aconf->outgoing.hostname, HOSTLEN + 1); + strlcpy(client->local->sockhost, aconf->outgoing.hostname ? aconf->outgoing.hostname : aconf->outgoing.file, HOSTLEN + 1); if (!connect_server_helper(aconf, client)) { @@ -1884,7 +1884,7 @@ void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp) SetOutgoing(client); irccounts.unknown++; list_add(&client->lclient_node, &unknown_list); - set_sockhost(client, aconf->outgoing.hostname); + set_sockhost(client, aconf->outgoing.hostname ? aconf->outgoing.hostname : "127.0.0.1"); add_client_to_list(client); if (aconf->outgoing.options & CONNECT_TLS) @@ -1896,7 +1896,9 @@ void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp) fd_setselect(client->local->fd, FD_SELECT_WRITE, completed_connection, client); unreal_log(ULOG_INFO, "link", "LINK_CONNECTING", client, - "Trying to activate link with server $client ($link_block.ip:$link_block.port)...", + aconf->outgoing.file + ? "Trying to activate link with server $client ($link_block.file)..." + : "Trying to activate link with server $client ($link_block.ip:$link_block.port)...", log_data_link_block(aconf)); } @@ -1911,21 +1913,23 @@ static int connect_server_helper(ConfigItem_link *aconf, Client *client) char *bindip; char buf[BUFSIZE]; - if (!aconf->connect_ip) + if (!aconf->connect_ip && !aconf->outgoing.file) { unreal_log(ULOG_ERROR, "link", "LINK_ERROR_NOIP", client, - "Connect to $client failed: no IP address to connect to", + "Connect to $client failed: no IP address or file to connect to", log_data_link_block(aconf)); return 0; /* handled upstream or shouldn't happen */ } - - if (strchr(aconf->connect_ip, ':')) + + if (aconf->outgoing.file) + SetUnixSocket(client); + else if (strchr(aconf->connect_ip, ':')) SetIPV6(client); - safe_strdup(client->ip, aconf->connect_ip); + safe_strdup(client->ip, aconf->connect_ip ? aconf->connect_ip : "127.0.0.1"); snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(client, TRUE)); - client->local->fd = fd_socket(IsIPV6(client) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf); + client->local->fd = fd_socket(IsUnixSocket(client) ? AF_UNIX : (IsIPV6(client) ? AF_INET6 : AF_INET), SOCK_STREAM, 0, buf); if (client->local->fd < 0) { if (ERRNO == P_EMFILE) @@ -1949,7 +1953,7 @@ static int connect_server_helper(ConfigItem_link *aconf, Client *client) return 0; } - set_sockhost(client, aconf->outgoing.hostname); + set_sockhost(client, aconf->outgoing.hostname ? aconf->outgoing.hostname : "127.0.0.1"); if (!aconf->outgoing.bind_ip && iConf.link_bindip) bindip = iConf.link_bindip; @@ -1971,10 +1975,14 @@ static int connect_server_helper(ConfigItem_link *aconf, Client *client) set_sock_opts(client->local->fd, client, IsIPV6(client)); - if (!unreal_connect(client->local->fd, client->ip, aconf->outgoing.port, IsIPV6(client))) + if (!unreal_connect(client->local->fd, + aconf->outgoing.file ? aconf->outgoing.file : client->ip, + aconf->outgoing.port, client->local->socket_type)) { unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client, - "Connect to $client ($link_block.ip:$link_block.port) failed: $socket_error", + aconf->outgoing.file + ? "Connect to $client ($link_block.file) failed: $socket_error" + : "Connect to $client ($link_block.ip:$link_block.port) failed: $socket_error", log_data_socket_error(client->local->fd), log_data_link_block(aconf)); return 0; diff --git a/src/modules/sethost.c b/src/modules/sethost.c index 510123f..fdffaf0 100644 --- a/src/modules/sethost.c +++ b/src/modules/sethost.c @@ -143,7 +143,6 @@ CMD_FUNC(cmd_sethost) if (MyConnect(client)) { sendto_one(client, NULL, ":%s MODE %s :+xt", client->name, client->name); - sendnumeric(client, RPL_HOSTHIDDEN, vhost); sendnotice(client, "Your nick!user@host-mask is now (%s!%s@%s) - To disable it type /mode %s -x", client->name, client->user->username, vhost, diff --git a/src/modules/sinfo.c b/src/modules/sinfo.c index d297001..5af22f0 100644 --- a/src/modules/sinfo.c +++ b/src/modules/sinfo.c @@ -1,7 +1,7 @@ /* * cmd_sinfo - Server information * (C) Copyright 2019 Bram Matthys (Syzop) and the UnrealIRCd team. - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/stats.c b/src/modules/stats.c index a7b5678..03f8910 100644 --- a/src/modules/stats.c +++ b/src/modules/stats.c @@ -487,14 +487,14 @@ int stats_except(Client *client, const char *para) int stats_allow(Client *client, const char *para) { ConfigItem_allow *allows; - ConfigItem_mask *m; + NameValuePrioList *m; for (allows = conf_allow; allows; allows = allows->next) { - for (m = allows->mask; m; m = m->next) + for (m = allows->match->printable_list; m; m = m->next) { sendnumeric(client, RPL_STATSILINE, - m->mask, "-", + namevalue_nospaces(m), "-", allows->maxperip, allows->global_maxperip, allows->class->name, @@ -521,14 +521,14 @@ int stats_command(Client *client, const char *para) int stats_oper(Client *client, const char *para) { ConfigItem_oper *o; - ConfigItem_mask *m; + NameValuePrioList *m; for (o = conf_oper; o; o = o->next) { - for (m = o->mask; m; m = m->next) + for (m = o->match->printable_list; m; m = m->next) { sendnumeric(client, RPL_STATSOLINE, - 'O', m->mask, o->name, + 'O', namevalue_nospaces(m), o->name, o->operclass ? o->operclass: "", o->class->name ? o->class->name : ""); } @@ -668,15 +668,19 @@ int stats_uline(Client *client, const char *para) } int stats_vhost(Client *client, const char *para) { - ConfigItem_mask *m; ConfigItem_vhost *vhosts; + NameValuePrioList *m; for (vhosts = conf_vhost; vhosts; vhosts = vhosts->next) { - for (m = vhosts->mask; m; m = m->next) + for (m = vhosts->match->printable_list; m; m = m->next) { - sendtxtnumeric(client, "vhost %s%s%s %s %s", vhosts->virtuser ? vhosts->virtuser : "", vhosts->virtuser ? "@" : "", - vhosts->virthost, vhosts->login, m->mask); + sendtxtnumeric(client, "vhost %s%s%s %s %s", + vhosts->virtuser ? vhosts->virtuser : "", + vhosts->virtuser ? "@" : "", + vhosts->virthost, + vhosts->login, + namevalue_nospaces(m)); } } return 0; @@ -935,12 +939,16 @@ int stats_set(Client *client, const char *para) int stats_tld(Client *client, const char *para) { ConfigItem_tld *tld; - ConfigItem_mask *m; + NameValuePrioList *m; for (tld = conf_tld; tld; tld = tld->next) { - for (m = tld->mask; m; m = m->next) - sendnumeric(client, RPL_STATSTLINE, m->mask, tld->motd_file, tld->rules_file ? tld->rules_file : "none"); + for (m = tld->match->printable_list; m; m = m->next) + { + sendnumeric(client, RPL_STATSTLINE, namevalue_nospaces(m), + tld->motd_file, + tld->rules_file ? tld->rules_file : "none"); + } } return 0; diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c index f534847..de30483 100644 --- a/src/modules/svsmode.c +++ b/src/modules/svsmode.c @@ -395,7 +395,7 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, const char *pa * so remove all oper-only modes and snomasks. */ if (MyUser(client)) - RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL); + RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL); remove_oper_privileges(target, 0); } goto setmodex; diff --git a/src/modules/svsnoop.c b/src/modules/svsnoop.c index c86d022..25de4d5 100644 --- a/src/modules/svsnoop.c +++ b/src/modules/svsnoop.c @@ -82,7 +82,7 @@ CMD_FUNC(cmd_svsnoop) if (!list_empty(&acptr->special_node)) list_del(&acptr->special_node); - RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL); + RunHook(HOOKTYPE_LOCAL_OPER, client, 0, NULL, NULL); remove_oper_privileges(acptr, 1); } } diff --git a/src/modules/svso.c b/src/modules/svso.c new file mode 100644 index 0000000..2eebb8b --- /dev/null +++ b/src/modules/svso.c @@ -0,0 +1,138 @@ +/* src/modules/svso.c - Grant IRCOp rights (for Services) + * (C) Copyright 2022 Bram Matthys (Syzop) and the UnrealIRCd team + * License: GPLv2 or later + */ +#include "unrealircd.h" + +ModuleHeader MOD_HEADER += { + "svso", + "6.0.0", + "Grant oper privileges via SVSO services command", + "UnrealIRCd Team", + "unrealircd-6", +}; + +/* Forward declarations */ +CMD_FUNC(cmd_svso); + +MOD_INIT() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + + CommandAdd(modinfo->handle, "SVSO", cmd_svso, MAXPARA, CMD_USER|CMD_SERVER); + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +/* Syntax: SVSO + * All these parameters need to be set, you cannot leave any of them out, + * HOWEVER some can be set to "-" to skip setting them, this is true for: + * , , , + * + * In UnrealIRCd the will be prefixed by "services:" if not already + * present. It is up to you to include or omit it. + */ +CMD_FUNC(cmd_svso) +{ + Client *acptr; + char oper_account[64]; + const char *operclass; + const char *clientclass; + ConfigItem_class *clientclass_c; + const char *modes; + long modes_i = 0; + const char *snomask; + const char *vhost; + + if (!IsULine(client)) + return; + + if ((parc < 8) || BadPtr(parv[7])) + { + sendnumeric(client, ERR_NEEDMOREPARAMS, "SVSO"); + return; + } + + operclass = parv[3]; + clientclass = parv[4]; + modes = parv[5]; + snomask = parv[6]; + vhost = parv[7]; + + acptr = find_user(parv[1], NULL); + if (!acptr) + { + sendnumeric(client, ERR_NOSUCHNICK, parv[1]); + return; + } + + if (!MyUser(acptr)) + { + /* Forward it to the correct server, and we are done... */ + sendto_one(acptr, recv_mtags, ":%s SVSO %s %s %s %s %s %s %s", + client->name, acptr->id, parv[2], parv[3], parv[4], parv[5], parv[6], parv[7]); + return; + } + + /* CAVEAT ALERT ! + * Don't mix up 'client' and 'acptr' below... + * 'client' is the server or services pseudouser that requests the change + * 'acptr' is the person that will be made OPER + */ + + /* If we get here, we validate the request and then make the user oper. */ + if (!find_operclass(operclass)) + { + sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Operclass does not exist"); + return; + } + + /* Set any items to NULL if they are skipped (on request) */ + if (!strcmp(clientclass, "-")) + clientclass = NULL; + if (!strcmp(modes, "-")) + modes = NULL; + if (!strcmp(snomask, "-")) + snomask = NULL; + if (!strcmp(vhost, "-")) + vhost = NULL; + + /* First, maybe the user is oper already? Then de-oper them.. */ + if (IsOper(acptr)) + { + int was_hidden_oper = IsHideOper(acptr) ? 1 : 0; + + list_del(&acptr->special_node); + RunHook(HOOKTYPE_LOCAL_OPER, acptr, 0, NULL, NULL); + remove_oper_privileges(acptr, 1); + + if (!was_hidden_oper) + irccounts.operators--; + VERIFY_OPERCOUNT(acptr, "svso"); + + } + + /* Prefix the oper block name with "services:" if it hasn't already */ + if (!strncmp(parv[2], "services:", 9)) + strlcpy(oper_account, parv[2], sizeof(oper_account)); + else + snprintf(oper_account, sizeof(oper_account), "services:%s", parv[2]); + + /* These needs to be looked up... */ + clientclass_c = find_class(clientclass); /* NULL is fine! */ + if (modes) + modes_i = set_usermode(modes); + + if (!make_oper(acptr, oper_account, operclass, clientclass_c, modes_i, snomask, vhost)) + sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper"); +} diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c index 5d7d6ac..c76f0c7 100644 --- a/src/modules/targetfloodprot.c +++ b/src/modules/targetfloodprot.c @@ -1,6 +1,6 @@ /* Target flood protection * (C)Copyright 2020 Bram Matthys and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/tkl.c b/src/modules/tkl.c index 7bc96b9..b0beea0 100644 --- a/src/modules/tkl.c +++ b/src/modules/tkl.c @@ -60,8 +60,9 @@ char *_tkl_type_config_string(TKL *tk); char *tkl_banexception_configname_to_chars(char *name); TKL *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, char *set_by, time_t expire_at, time_t set_at, int soft, int flags); -TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, char *reason, char *set_by, - time_t expire_at, time_t set_at, int soft, char *bantypes, int flags); +TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match, + char *reason, char *set_by, + time_t expire_at, time_t set_at, int soft, char *bantypes, int flags); TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by, time_t expire_at, time_t set_at, int flags); TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by, @@ -91,6 +92,7 @@ int check_mtag_spamfilters_present(void); int _join_viruschan(Client *client, TKL *tk, int type); void _spamfilter_build_user_string(char *buf, char *nick, Client *client); int _match_user(const char *rmask, Client *client, int options); +int _unreal_match_iplist(Client *client, NameList *l); int _match_user_extended_server_ban(const char *banstr, Client *client); void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, const char **tkl_username, const char **tkl_hostname); int _tkl_ip_hash(char *ip); @@ -204,6 +206,7 @@ MOD_TEST() EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_DEL, _sendnotice_tkl_del); EfunctionAdd(modinfo->handle, EFUNC_FIND_TKL_EXCEPTION, _find_tkl_exception); EfunctionAddString(modinfo->handle, EFUNC_TKL_UHOST, _tkl_uhost); + EfunctionAdd(modinfo->handle, EFUNC_UNREAL_MATCH_IPLIST, _unreal_match_iplist); return MOD_SUCCESS; } @@ -678,7 +681,7 @@ int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int { ConfigEntry *cep, *cepp; int errors = 0; - int has_mask = 0; + char has_mask = 0, has_match = 0; /* We are only interested in except { } blocks */ if (configtype != CONFIG_EXCEPT) @@ -705,32 +708,18 @@ int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int { if (!strcmp(cep->name, "mask")) { - if (cep->items) + if (cep->value || cep->items) { - /* mask { *@1.1.1.1; *@2.2.2.2; *@3.3.3.3; }; */ - for (cepp = cep->items; cepp; cepp = cepp->next) - { - if (!cepp->name) - { - config_error_empty(cepp->file->filename, - cepp->line_number, "except ban", "mask"); - errors++; - continue; - } - has_mask = 1; - } - } else - if (cep->value) - { - /* mask *@1.1.1.1; */ - if (!cep->value) - { - config_error_empty(cep->file->filename, - cep->line_number, "except ban", "mask"); - errors++; - continue; - } has_mask = 1; + test_match_block(cf, cep, &errors); + } + } else + if (!strcmp(cep->name, "match")) + { + if (cep->value || cep->items) + { + has_match = 1; + test_match_block(cf, cep, &errors); } } else if (!strcmp(cep->name, "type")) @@ -766,10 +755,17 @@ int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int } } - if (!has_mask) + if (!has_mask && !has_match) { config_error_missing(ce->file->filename, ce->line_number, - "except ban::mask"); + "except ban::match"); + errors++; + } + if (has_mask && has_match) + { + config_error("%s:%d: You cannot have both ::mask and ::match. " + "You should only use except::match.", + ce->file->filename, ce->line_number); errors++; } @@ -777,60 +773,10 @@ int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int return errors ? -1 : 1; } -void config_create_tkl_except(char *mask, char *bantypes) -{ - char *usermask = NULL; - char *hostmask = NULL; - int soft = 0; - char buf[256]; - char mask1buf[512]; - char mask2buf[512]; - char *p; - - if (*mask == '%') - { - soft = 1; - mask++; - } - strlcpy(buf, mask, sizeof(buf)); - if (is_extended_server_ban(buf)) - { - char *err = NULL; - if (!parse_extended_server_ban(buf, NULL, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf))) - { - config_warn("Could not add extended server ban '%s': %s", buf, err); - return; - } - usermask = mask1buf; - hostmask = mask2buf; - } else - { - p = strchr(buf, '@'); - if (!p) - { - usermask = "*"; - hostmask = buf; - } else { - *p++ = '\0'; - usermask = buf; - hostmask = p; - } - } - - if ((*usermask == ':') || (*hostmask == ':')) - { - config_error("Cannot add illegal ban '%s': for a given user@host - neither " - "user nor host may start with a : character (semicolon)", mask); - return; - } - - tkl_add_banexception(TKL_EXCEPTION, usermask, hostmask, "Added in configuration file", - "-config-", 0, TStime(), soft, bantypes, TKL_FLAG_CONFIG); -} - int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype) { ConfigEntry *cep, *cepp; + SecurityGroup *match = NULL; char bantypes[64]; /* We are only interested in except { } blocks */ @@ -867,6 +813,10 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype) char *str = tkl_banexception_configname_to_chars(cep->value); strlcat(bantypes, str, sizeof(bantypes)); } + } else + if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask")) + { + conf_match_block(cf, cep, &match); } } @@ -885,24 +835,8 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype) abort(); /* someone can't code */ } - /* Now walk through all mask entries */ - for (cep = ce->items; cep; cep = cep->next) - { - if (!strcmp(cep->name, "mask")) - { - if (cep->items) - { - /* mask { *@1.1.1.1; *@2.2.2.2; *@3.3.3.3; }; */ - for (cepp = cep->items; cepp; cepp = cepp->next) - config_create_tkl_except(cepp->name, bantypes); - } else - if (cep->value) - { - /* mask *@1.1.1.1; */ - config_create_tkl_except(cep->value, bantypes); - } - } - } + tkl_add_banexception(TKL_EXCEPTION, "-", "-", match, "Added in configuration file", + "-config-", 0, TStime(), 0, bantypes, TKL_FLAG_CONFIG); return 1; } @@ -2564,6 +2498,9 @@ TKL *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, * @param type TKL_EXCEPTION or TKLEXCEPT|TKL_GLOBAL. * @param usermask The user mask * @param hostmask The host mask + * @param match A securitygroup used for matching (can be NULL, + * if not NULL then this field is used as-is and not copied + * so caller should not free!) * @param reason The reason for the ban * @param set_by Who (or what) set the ban * @param expire_at When will the ban expire (0 for permanent) @@ -2577,15 +2514,15 @@ TKL *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, * Be sure not to call this function for spamfilters, * qlines or exempts, which have their own function! */ -TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, char *reason, char *set_by, - time_t expire_at, time_t set_at, int soft, char *bantypes, int flags) +TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match, + char *reason, char *set_by, + time_t expire_at, time_t set_at, int soft, char *bantypes, int flags) { TKL *tkl; int index, index2; if (!TKLIsBanExceptionType(type)) abort(); - tkl = safe_alloc(sizeof(TKL)); /* First the common fields */ tkl->type = type; @@ -2597,6 +2534,7 @@ TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, char *reaso tkl->ptr.banexception = safe_alloc(sizeof(BanException)); safe_strdup(tkl->ptr.banexception->usermask, usermask); safe_strdup(tkl->ptr.banexception->hostmask, hostmask); + tkl->ptr.banexception->match = match; if (soft) tkl->ptr.banexception->subtype = TKL_SUBTYPE_SOFT; safe_strdup(tkl->ptr.banexception->bantypes, bantypes); @@ -2702,6 +2640,8 @@ void _free_tkl(TKL *tkl) { safe_free(tkl->ptr.banexception->usermask); safe_free(tkl->ptr.banexception->hostmask); + if (tkl->ptr.banexception->match) + free_security_group(tkl->ptr.banexception->match); safe_free(tkl->ptr.banexception->bantypes); safe_free(tkl->ptr.banexception->reason); safe_free(tkl->ptr.banexception); @@ -2770,7 +2710,7 @@ static void add_default_exempts(void) * Currently the list is: gline, kline, gzline, zline, shun, blacklist, * connect-flood, handshake-data-flood. */ - tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.0/8", "localhost is always exempt", + tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.0/8", NULL, "localhost is always exempt", "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG); } @@ -2962,6 +2902,10 @@ static int find_tkl_exception_matcher(Client *client, int ban_type, TKL *except_ if (!tkl_banexception_matches_type(except_tkl, ban_type)) return 0; + /* For config file except ban { } we use security groups instead of simple user/host */ + if (except_tkl->ptr.banexception->match) + return user_allowed_by_security_group(client, except_tkl->ptr.banexception->match); + tkl_uhost(except_tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX); if (match_user(uhost, client, MATCH_CHECK_REAL)) @@ -3565,12 +3509,26 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl } else if (TKLIsBanException(tkl)) { - char uhostbuf[BUFSIZE]; - char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0); - sendnumeric(client, RPL_STATSEXCEPTTKL, uhost, - tkl->ptr.banexception->bantypes, - (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0, - (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason); + if (tkl->ptr.banexception->match) + { + /* Config-added: uses security groups */ + NameValuePrioList *m; + for (m = tkl->ptr.banexception->match->printable_list; m; m = m->next) + { + sendnumeric(client, RPL_STATSEXCEPTTKL, namevalue_nospaces(m), + tkl->ptr.banexception->bantypes, + (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0, + (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason); + } + } else { + /* IRC-added: uses simple user/host mask */ + char uhostbuf[BUFSIZE]; + char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0); + sendnumeric(client, RPL_STATSEXCEPTTKL, uhost, + tkl->ptr.banexception->bantypes, + (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0, + (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason); + } } else { /* That's weird, unknown TKL type */ @@ -3706,7 +3664,7 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl) } else { unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_SYNC_SEND_ENTRY", NULL, - "[BUG] tkl_sync_send_entry() called for '%s' but unknown type: $tkl.type_string ($tkl_type_int)", + "[BUG] tkl_sync_send_entry() called, but unknown type: $tkl.type_string ($tkl_type_int)", log_data_tkl("tkl", tkl), log_data_integer("tkl_type_int", typ)); abort(); @@ -4072,7 +4030,7 @@ CMD_FUNC(cmd_tkl_add) { tkl_entry_exists = 1; } else { - tkl = tkl_add_banexception(type, usermask, hostmask, reason, + tkl = tkl_add_banexception(type, usermask, hostmask, NULL, reason, set_by, expire_at, set_at, softban, bantypes, 0); } } else @@ -4123,7 +4081,7 @@ CMD_FUNC(cmd_tkl_add) { unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client, "Invalid TKL entry from $client: " - "Spamfilter '$spamfilter_string' has unkown match-type '$spamfilter_type'", + "Spamfilter '$spamfilter_string' has unknown match-type '$spamfilter_type'", log_data_string("spamfilter_string", match_string), log_data_string("spamfilter_type", parv[10])); return; @@ -4133,7 +4091,7 @@ CMD_FUNC(cmd_tkl_add) { unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client, "Invalid TKL entry from $client: " - "Spamfilter '$spamfilter_string' has unkown targets '$spamfilter_targets'", + "Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'", log_data_string("spamfilter_string", match_string), log_data_string("spamfilter_targets", parv[3])); return; @@ -4143,7 +4101,7 @@ CMD_FUNC(cmd_tkl_add) { unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client, "Invalid TKL entry from $client: " - "Spamfilter '$spamfilter_string' has unkown action '$spamfilter_action'", + "Spamfilter '$spamfilter_string' has unknown action '$spamfilter_action'", log_data_string("spamfilter_string", match_string), log_data_string("spamfilter_action", parv[4])); return; @@ -4312,7 +4270,7 @@ CMD_FUNC(cmd_tkl_del) { unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client, "Invalid TKL deletion request from $client: " - "Spamfilter '$spamfilter_string' has unkown targets '$spamfilter_targets'", + "Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'", log_data_string("spamfilter_string", match_string), log_data_string("spamfilter_targets", parv[3])); return; @@ -4322,7 +4280,7 @@ CMD_FUNC(cmd_tkl_del) { unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client, "Invalid TKL deletion request from $client: " - "Spamfilter '$spamfilter_string' has unkown action '$spamfilter_action'", + "Spamfilter '$spamfilter_string' has unknown action '$spamfilter_action'", log_data_string("spamfilter_string", match_string), log_data_string("spamfilter_action", parv[4])); return; @@ -5147,6 +5105,102 @@ int _match_user(const char *rmask, Client *client, int options) return 0; /* NOMATCH: nothing of the above matched */ } +/** Returns 1 if the user is allowed by any of the security groups in the named list. + * This is only used by security-group::security-group and + * security-group::exclude-security-group. + * @param client Client to check + * @param l The NameList + * @returns 1 if any of the security groups match, 0 if none of them matched. + */ +int _unreal_match_iplist(Client *client, NameList *l) +{ + char client_ipv6 = 0; + char clientip[IPSZ], maskip[IPSZ]; + + if (!client->ip) + return 0; /* unusual, maybe services? */ + + if (strchr(client->ip, ':')) + { + client_ipv6 = 1; + if (!inet_pton(AF_INET6, client->ip, clientip)) + return 0; /* unusual failure */ + } else { + if (!inet_pton(AF_INET, client->ip, clientip)) + return 0; /* unusual failure */ + } + + for (; l; l = l->next) + { + char mask[512], *p; + int cidr = -1; /* CIDR length, -1 for no CIDR */ + + strlcpy(mask, l->name, sizeof(mask)); + p = strchr(mask, '/'); + if (p) + { + *p++ = '\0'; + cidr = atoi(p); + if (cidr <= 0) + return 0; /* NOMATCH: invalid CIDR */ + } + + /* Three possible types: wildcard, ipv6, ipv4 */ + + if (strchr(mask, '*') || strchr(mask, '?')) + { + /* Wildcards */ + if (match_simple(mask, client->ip)) + return 1; /* MATCH by wildcard IP */ + } + else if (strchr(mask, ':')) + { + /* IPv6 */ + if (!client_ipv6) + continue; /* NOMATCH: client is IPv4 */ + if (!inet_pton(AF_INET6, mask, maskip)) + continue; /* NOMATCH: invalid IPv6 IP in mask */ + if (cidr < 0) + { + /* Try to match by exact IP */ + if (comp_with_mask(clientip, maskip, 128)) + return 1; /* MATCH by exact IP */ + } else + if (cidr > 128) + { + continue; /* NOMATCH: invalid CIDR */ + } else + if (comp_with_mask(clientip, maskip, cidr)) + { + return 1; /* MATCH by CIDR */ + } + } else + { + /* IPv4 */ + if (client_ipv6) + continue; /* NOMATCH: client is IPv6 */ + if (!inet_pton(AF_INET, mask, maskip)) + continue; /* NOMATCH: invalid IPv6 IP in mask */ + if (cidr < 0) + { + /* Try to match by exact IP */ + if (comp_with_mask(clientip, maskip, 32)) + return 1; /* MATCH: by exact IP */ + } else + if (cidr > 32) + { + continue; /* NOMATCH: invalid CIDR */ + } else + if (comp_with_mask(clientip, maskip, cidr)) + { + return 1; /* MATCH by CIDR */ + } + } + } + return 0; +} + + int _match_user_extended_server_ban(const char *banstr, Client *client) { const char *nextbanstr; diff --git a/src/modules/tkldb.c b/src/modules/tkldb.c index fdc05a5..bfbefdc 100644 --- a/src/modules/tkldb.c +++ b/src/modules/tkldb.c @@ -625,6 +625,7 @@ int read_tkldb(void) { tkl_add_banexception(tkl->type, tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask, + NULL, tkl->ptr.banexception->reason, tkl->set_by, tkl->expire_at, tkl->set_at, softban, diff --git a/src/modules/tls_antidos.c b/src/modules/tls_antidos.c index b3f6d91..65d70ab 100644 --- a/src/modules/tls_antidos.c +++ b/src/modules/tls_antidos.c @@ -5,7 +5,7 @@ * * (C) Copyright 2015- Bram Matthys and the UnrealIRCd team. * - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/modules/tls_cipher.c b/src/modules/tls_cipher.c index a7e84be..7d8d29f 100644 --- a/src/modules/tls_cipher.c +++ b/src/modules/tls_cipher.c @@ -1,7 +1,7 @@ /* * Store TLS cipher in ModData * (C) Copyright 2021-.. Syzop and The UnrealIRCd Team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -22,6 +22,7 @@ void tls_cipher_unserialize(const char *str, ModData *m); int tls_cipher_handshake(Client *client); int tls_cipher_connect(Client *client); int tls_cipher_whois(Client *client, Client *target); +int log_tls_cipher(Client *client, int detail, json_t *j); ModDataInfo *tls_cipher_md; /* Module Data structure which we acquire */ @@ -45,6 +46,8 @@ ModDataInfo mreq; HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, tls_cipher_handshake); HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, tls_cipher_handshake); + HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, log_tls_cipher); + return MOD_SUCCESS; } @@ -89,3 +92,24 @@ void tls_cipher_unserialize(const char *str, ModData *m) { safe_strdup(m->str, str); } + +int log_tls_cipher(Client *client, int detail, json_t *j) +{ + json_t *tls; + const char *str; + + str = moddata_client_get(client, "tls_cipher"); + if (!str) + return 0; + + tls = json_object_get(j, "tls"); + if (!tls) + { + tls = json_object(); + json_object_set_new(j, "tls", tls); + } + + json_object_set_new(tls, "cipher", json_string_unreal(str)); + + return 0; +} diff --git a/src/modules/unreal_server_compat.c b/src/modules/unreal_server_compat.c index b768761..70d5f04 100644 --- a/src/modules/unreal_server_compat.c +++ b/src/modules/unreal_server_compat.c @@ -1,7 +1,7 @@ /* * unreal_server_compat - Compatibility with pre-U6 servers * (C) Copyright 2016-2021 Bram Matthys (Syzop) - * License: GPLv2 + * License: GPLv2 or later * * Currently the only purpose of this module is to rewrite MODE * and SJOIN lines to older servers so any bans/exempts/invex diff --git a/src/modules/vhost.c b/src/modules/vhost.c index 965ae28..66d159d 100644 --- a/src/modules/vhost.c +++ b/src/modules/vhost.c @@ -89,7 +89,7 @@ CMD_FUNC(cmd_vhost) return; } - if (!unreal_mask_match(client, vhost->mask)) + if (!user_allowed_by_security_group(client, vhost->match)) { unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client, "Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]", @@ -157,7 +157,6 @@ CMD_FUNC(cmd_vhost) for (s = vhost->swhois; s; s = s->next) swhois_add(client, "vhost", -100, s->line, &me, NULL); } - sendnumeric(client, RPL_HOSTHIDDEN, vhost->virthost); sendnotice(client, "*** Your vhost is now %s%s%s", vhost->virtuser ? vhost->virtuser : "", vhost->virtuser ? "@" : "", diff --git a/src/modules/watch-backend.c b/src/modules/watch-backend.c index 708ab09..594c343 100644 --- a/src/modules/watch-backend.c +++ b/src/modules/watch-backend.c @@ -85,7 +85,7 @@ MOD_INIT() } LoadPersistentPointer(modinfo, watchTable, watch_generic_free); if (watchTable == NULL) - watchTable = safe_alloc(sizeof(Watch) * WATCH_HASH_TABLE_SIZE); + watchTable = safe_alloc(sizeof(Watch *) * WATCH_HASH_TABLE_SIZE); memset(&mreq, 0 , sizeof(mreq)); mreq.type = MODDATATYPE_LOCAL_CLIENT; diff --git a/src/modules/watch.c b/src/modules/watch.c index 9839b15..e0cf39e 100644 --- a/src/modules/watch.c +++ b/src/modules/watch.c @@ -119,8 +119,6 @@ static void show_watch_removed(Client *client, char *name) } } -static char buf[BUFSIZE]; - #define WATCHES(client) (moddata_local_client(client, watchCounterMD).i) #define WATCH(client) (moddata_local_client(client, watchListMD).ptr) @@ -130,6 +128,7 @@ static char buf[BUFSIZE]; CMD_FUNC(cmd_watch) { char request[BUFSIZE]; + char buf[BUFSIZE]; Client *target; char *s, *user; char *p = NULL, *def = "l"; diff --git a/src/modules/websocket.c b/src/modules/websocket.c index 111b0e4..fd9148e 100644 --- a/src/modules/websocket.c +++ b/src/modules/websocket.c @@ -1,7 +1,7 @@ /* * websocket - WebSocket support (RFC6455) * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later * This module was sponsored by Aberrant Software Inc. */ diff --git a/src/modules/whois.c b/src/modules/whois.c index 125fd17..f2b89a0 100644 --- a/src/modules/whois.c +++ b/src/modules/whois.c @@ -52,7 +52,6 @@ struct WhoisConfig { }; /* Global variables */ -static char buf[BUFSIZE]; WhoisConfig *whoisconfig = NULL; /* Forward declarations */ @@ -147,6 +146,8 @@ static void whois_config_setdefaults(void) whois_config_add("reputation", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); + whois_config_add("security-groups", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); + whois_config_add("geo", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("certfp", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); @@ -294,6 +295,7 @@ CMD_FUNC(cmd_whois) char *p = NULL; int len, mlen; char querybuf[BUFSIZE]; + char buf[BUFSIZE]; int ntargets = 0; int maxtargets = max_targets_for_command("WHOIS"); @@ -561,6 +563,51 @@ CMD_FUNC(cmd_whois) } } + /* The following code deals with security-groups */ + policy = whois_get_policy(client, target, "security-groups"); + if ((policy > WHOIS_CONFIG_DETAILS_NONE) && !IsULine(target)) + { + SecurityGroup *s; + int security_groups_whois_lines = 0; + + mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name) + strlen("is in security-groups: "); + + if (user_allowed_by_security_group_name(target, "known-users")) + strlcpy(buf, "known-users,", sizeof(buf)); + else + strlcpy(buf, "unknown-users,", sizeof(buf)); + len = strlen(buf); + + for (s = securitygroups; s; s = s->next) + { + if (len + strlen(s->name) > (size_t)BUFSIZE - 4 - mlen) + { + buf[len-1] = '\0'; + add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups", + target, RPL_WHOISSPECIAL, + "%s :is in security-groups: %s", target->name, buf); + security_groups_whois_lines++; + *buf = '\0'; + len = 0; + } + if (strcmp(s->name, "known-users") && user_allowed_by_security_group(target, s)) + { + strcpy(buf + len, s->name); + len += strlen(buf+len); + strcpy(buf + len, ","); + len++; + } + } + + if (*buf) + { + buf[len-1] = '\0'; + add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups", + client, RPL_WHOISSPECIAL, + "%s :is in security-groups: %s", target->name, buf); + security_groups_whois_lines++; + } + } if (MyUser(target) && IsShunned(target) && (whois_get_policy(client, target, "shunned") > WHOIS_CONFIG_DETAILS_NONE)) { add_nvplist_numeric(&list, -20000, "shunned", client, RPL_WHOISSPECIAL, diff --git a/src/modules/whox.c b/src/modules/whox.c index 67ab533..596a278 100644 --- a/src/modules/whox.c +++ b/src/modules/whox.c @@ -3,7 +3,7 @@ * was originally made for tircd and modified to work with u4. * - 2018 i * - 2019 Bram Matthys (Syzop) - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" diff --git a/src/operclass.c b/src/operclass.c index 0eaaa1e..9551eb0 100644 --- a/src/operclass.c +++ b/src/operclass.c @@ -1,6 +1,6 @@ /** Oper classes code. * (C) Copyright 2015-present tmcarthur and the UnrealIRCd team - * License: GPLv2 + * License: GPLv2 or later */ #include "unrealircd.h" @@ -282,6 +282,7 @@ OperPermission ValidatePermissionsForPathEx(OperClassACL *acl, OperClassACLPath OperPermission ValidatePermissionsForPath(const char *path, Client *client, Client *victim, Channel *channel, const void *extra) { ConfigItem_oper *ce_oper; + const char *operclass; ConfigItem_operclass *ce_operClass; OperClass *oc = NULL; OperClassACLPath *operPath; @@ -299,15 +300,18 @@ OperPermission ValidatePermissionsForPath(const char *path, Client *client, Clie ce_oper = find_oper(client->user->operlogin); if (!ce_oper) { - return OPER_DENY; - } - - ce_operClass = find_operclass(ce_oper->operclass); - if (!ce_operClass) + operclass = moddata_client_get(client, "operclass"); + if (!operclass) + return OPER_DENY; + } else { - return OPER_DENY; + operclass = ce_oper->operclass; } + ce_operClass = find_operclass(operclass); + if (!ce_operClass) + return OPER_DENY; + oc = ce_operClass->classStruct; operPath = OperClass_parsePath(path); while (oc && operPath) diff --git a/src/securitygroup.c b/src/securitygroup.c new file mode 100644 index 0000000..6986761 --- /dev/null +++ b/src/securitygroup.c @@ -0,0 +1,840 @@ +/* + * Mask & security-group routines. + * (C) Copyright 2015-.. Syzop and the UnrealIRCd team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "unrealircd.h" + +/* Global variables */ +SecurityGroup *securitygroups = NULL; + +/** Free all masks in the mask list */ +void unreal_delete_masks(ConfigItem_mask *m) +{ + ConfigItem_mask *m_next; + + for (; m; m = m_next) + { + m_next = m->next; + + safe_free(m->mask); + + safe_free(m); + } +} + +/** Internal function to add one individual mask to the list */ +static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce) +{ + ConfigItem_mask *m = safe_alloc(sizeof(ConfigItem_mask)); + + /* Since we allow both mask "xyz"; and mask { abc; def; };... */ + if (ce->value) + safe_strdup(m->mask, ce->value); + else + safe_strdup(m->mask, ce->name); + + add_ListItem((ListStruct *)m, (ListStruct **)head); +} + +/** Add mask entries from config */ +void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce) +{ + if (ce->items) + { + ConfigEntry *cep; + for (cep = ce->items; cep; cep = cep->next) + unreal_add_mask(head, cep); + } else + { + unreal_add_mask(head, ce); + } +} + +/** Check if a client matches any of the masks in the mask list. + * The following rules apply: + * - If you have only negating entries, like '!abc' and '!def', then + * we assume an implicit * rule first, since that is clearly what + * the user wants. + * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the + * implicit * is dropped and we assume you only want to match *.com, + * with the exception of irc1*.com and irc2*.com. + * - If you only have normal entries without ! then things are + * as they always are. + * @param client The client to run the mask match against + * @param mask The mask entry from the config file + * @returns 1 on match, 0 on non-match. + */ +int unreal_mask_match(Client *client, ConfigItem_mask *mask) +{ + int retval = 1; + ConfigItem_mask *m; + + if (!mask) + return 0; /* Empty mask block is no match */ + + /* First check normal matches (without ! prefix) */ + for (m = mask; m; m = m->next) + { + if (m->mask[0] != '!') + { + retval = 0; /* no implicit * */ + if (match_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED)) + { + retval = 1; + break; + } + } + } + + if (retval) + { + /* We matched. Check for exceptions (with ! prefix) */ + for (m = mask; m; m = m->next) + { + if ((m->mask[0] == '!') && match_user(m->mask+1, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED)) + return 0; + } + } + + return retval; +} + +/** Check if a string matches any of the masks in the mask list. + * The following rules apply: + * - If you have only negating entries, like '!abc' and '!def', then + * we assume an implicit * rule first, since that is clearly what + * the user wants. + * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the + * implicit * is dropped and we assume you only want to match *.com, + * with the exception of irc1*.com and irc2*.com. + * - If you only have normal entries without ! then things are + * as they always are. + * @param name The name to run the mask matching on + * @param mask The mask entry from the config file + * @returns 1 on match, 0 on non-match. + */ +int unreal_mask_match_string(const char *name, ConfigItem_mask *mask) +{ + int retval = 1; + ConfigItem_mask *m; + + if (!mask) + return 0; /* Empty mask block is no match */ + + /* First check normal matches (without ! prefix) */ + for (m = mask; m; m = m->next) + { + if (m->mask[0] != '!') + { + retval = 0; /* no implicit * */ + if (match_simple(m->mask, name)) + { + retval = 1; + break; + } + } + } + + if (retval) + { + /* We matched. Check for exceptions (with ! prefix) */ + for (m = mask; m; m = m->next) + { + if ((m->mask[0] == '!') && match_simple(m->mask+1, name)) + return 0; + } + } + + return retval; +} + +#define CheckNullX(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); *errors = *errors + 1; return 0; } +int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors) +{ + ConfigEntry *cepp; + + if (!strcmp(cep->name, "webirc") || !strcmp(cep->name, "exclude-webirc")) + { + CheckNullX(cep); + } else + if (!strcmp(cep->name, "identified") || !strcmp(cep->name, "exclude-identified")) + { + CheckNullX(cep); + } else + if (!strcmp(cep->name, "tls") || !strcmp(cep->name, "exclude-tls")) + { + CheckNullX(cep); + } else + if (!strcmp(cep->name, "reputation-score") || !strcmp(cep->name, "exclude-reputation-score")) + { + const char *str = cep->value; + int v; + CheckNullX(cep); + if (*str == '<') + str++; + v = atoi(str); + if ((v < 1) || (v > 10000)) + { + config_error("%s:%i: %s needs to be a value of 1-10000", + cep->file->filename, cep->line_number, cep->name); + *errors = *errors + 1; + } + } else + if (!strcmp(cep->name, "connect-time") || !strcmp(cep->name, "exclude-connect-time")) + { + const char *str = cep->value; + long v; + CheckNullX(cep); + if (*str == '<') + str++; + v = config_checkval(str, CFG_TIME); + if (v < 1) + { + config_error("%s:%i: %s needs to be a time value (and more than 0 seconds)", + cep->file->filename, cep->line_number, cep->name); + *errors = *errors + 1; + } + } else + if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "exclude-mask")) + { + for (cepp = cep->items; cepp; cepp = cepp->next) + { + if (!strcmp(cepp->name, "mask")) + continue; + if (cepp->items || cepp->value) + { + config_error("%s:%i: security-group::mask should contain hostmasks only. " + "Perhaps you meant to use it in security-group { %s ... } directly?", + cepp->file->filename, cepp->line_number, + cepp->name); + *errors = *errors + 1; + } + } + } else + if (!strcmp(cep->name, "ip")) + { + } else + if (!strcmp(cep->name, "security-group") || !strcmp(cep->name, "exclude-security-group")) + { + } else + { + /* Let's see if an extended server ban exists for this item... */ + Extban *extban; + if (!strncmp(cep->name, "exclude-", 8)) + extban = findmod_by_bantype_raw(cep->name+8, strlen(cep->name+8)); + else + extban = findmod_by_bantype_raw(cep->name, strlen(cep->name)); + if (extban && (extban->options & EXTBOPT_TKL) && (extban->is_banned_events & BANCHK_TKL)) + { + test_extended_list(extban, cep, errors); + return 1; /* Yup, handled */ + } + return 0; /* Unhandled: unknown item for us */ + } + return 1; /* Handled, but there could be errors */ +} + +int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out) +{ + int errors = 0; + ConfigEntry *cep; + + /* (If there is only a ce->value, trust that it is OK) */ + + /* Test ce->items... */ + for (cep = ce->items; cep; cep = cep->next) + { + /* Only complain about things with values, + * as valueless things like "10.0.0.0/8" are treated as a mask. + */ + if (!test_match_item(conf, cep, &errors) && cep->value) + { + config_error_unknown(cep->file->filename, cep->line_number, + ce->name, cep->name); + errors++; + continue; + } + } + + *errors_out = *errors_out + errors; + return errors ? 0 : 1; +} + +#define tmbbw_is_wildcard(x) (!strcmp(x, "*") || !strcmp(x, "*@*")) +int test_match_block_too_broad(ConfigFile *conf, ConfigEntry *ce) +{ + ConfigEntry *cep, *cepp; + + // match *; + if (ce->value && tmbbw_is_wildcard(ce->value)) + return 1; + + for (cep = ce->items; cep; cep = cep->next) + { + // match { *; } + if (!cep->value && tmbbw_is_wildcard(cep->name)) + return 1; + if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "ip")) + { + // match { mask *; } + if (cep->value && tmbbw_is_wildcard(cep->value)) + return 1; + // match { mask { *; } } + for (cepp = cep->items; cepp; cepp = cepp->next) + if (tmbbw_is_wildcard(cepp->name)) + return 1; + } + } + + return 0; +} + +int _test_security_group(ConfigFile *conf, ConfigEntry *ce) +{ + int errors = 0; + ConfigEntry *cep; + + /* First, check the name of the security group */ + if (!ce->value) + { + config_error("%s:%i: security-group block needs a name, eg: security-group web-users {", + ce->file->filename, ce->line_number); + errors++; + } else { + if (!strcasecmp(ce->value, "unknown-users")) + { + config_error("%s:%i: The 'unknown-users' group is a special group that is the " + "inverse of 'known-users', you cannot create or adjust it in the " + "config file, as it is created automatically by UnrealIRCd.", + ce->file->filename, ce->line_number); + errors++; + return errors; + } + if (!security_group_valid_name(ce->value)) + { + config_error("%s:%i: security-group block name '%s' contains invalid characters or is too long. " + "Only letters, numbers, underscore and hyphen are allowed.", + ce->file->filename, ce->line_number, ce->value); + errors++; + } + } + + for (cep = ce->items; cep; cep = cep->next) + { + if (!test_match_item(conf, cep, &errors)) + { + config_error_unknown(cep->file->filename, cep->line_number, + "security-group", cep->name); + errors++; + continue; + } + } + + return errors; +} + +int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block) +{ + int errors = 0; /* unused */ + SecurityGroup *s = *block; + + /* The following code is there so we don't create a security group + * unless there is actually a valid config item for it encountered. + * This so the security group '*s' can stay NULL if there are zero + * items, so we don't waste any CPU if it is unused. + */ + if (*block == NULL) + { + /* Yeah we call a TEST routine from a CONFIG RUN routine ;). */ + if (!test_match_item(conf, cep, &errors)) + return 0; /* not for us */ + /* If we are still here then we must create the security group */ + *block = s = safe_alloc(sizeof(SecurityGroup)); + } + + if (!strcmp(cep->name, "webirc")) + s->webirc = config_checkval(cep->value, CFG_YESNO); + else if (!strcmp(cep->name, "identified")) + s->identified = config_checkval(cep->value, CFG_YESNO); + else if (!strcmp(cep->name, "tls")) + s->tls = config_checkval(cep->value, CFG_YESNO); + else if (!strcmp(cep->name, "reputation-score")) + { + if (*cep->value == '<') + s->reputation_score = 0 - atoi(cep->value+1); + else + s->reputation_score = atoi(cep->value); + } + else if (!strcmp(cep->name, "connect-time")) + { + if (*cep->value == '<') + s->connect_time = 0 - config_checkval(cep->value+1, CFG_TIME); + else + s->connect_time = config_checkval(cep->value, CFG_TIME); + } + else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask")) + { + unreal_add_masks(&s->mask, cep); + } + else if (!strcmp(cep->name, "ip")) + { + unreal_add_names(&s->ip, cep); + } + else if (!strcmp(cep->name, "security-group")) + { + unreal_add_names(&s->security_group, cep); + } + else if (!strcmp(cep->name, "exclude-webirc")) + s->exclude_webirc = config_checkval(cep->value, CFG_YESNO); + else if (!strcmp(cep->name, "exclude-identified")) + s->exclude_identified = config_checkval(cep->value, CFG_YESNO); + else if (!strcmp(cep->name, "exclude-tls")) + s->exclude_tls = config_checkval(cep->value, CFG_YESNO); + else if (!strcmp(cep->name, "exclude-reputation-score")) + { + if (*cep->value == '<') + s->exclude_reputation_score = 0 - atoi(cep->value+1); + else + s->exclude_reputation_score = atoi(cep->value); + } + else if (!strcmp(cep->name, "exclude-mask")) + { + unreal_add_masks(&s->exclude_mask, cep); + } + else if (!strcmp(cep->name, "exclude-security-group")) + { + unreal_add_names(&s->security_group, cep); + } + else + { + /* Let's see if an extended server ban exists for this item... this needs to be LAST! */ + Extban *extban; + const char *name = cep->name; + + if (!strncmp(cep->name, "exclude-", 8)) + { + /* Extended (exclusive) ? */ + name = cep->name + 8; + if (findmod_by_bantype_raw(name, strlen(name))) + unreal_add_name_values(&s->exclude_extended, name, cep); + else + return 0; /* Unhandled */ + } else { + /* Extended (inclusive) */ + if (findmod_by_bantype_raw(name, strlen(name))) + unreal_add_name_values(&s->extended, name, cep); + else + return 0; /* Unhandled */ + } + } + + add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value); + + return 1; /* Handled by us (guaranteed earlier) */ +} + +int conf_match_block(ConfigFile *conf, ConfigEntry *ce, SecurityGroup **block) +{ + ConfigEntry *cep; + SecurityGroup *s = *block; + + if (*block == NULL) + *block = s = safe_alloc(sizeof(SecurityGroup)); + + /* Check for simple form: match *; / mask *; */ + if (ce->value) + { + unreal_add_masks(&s->mask, ce); + add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", ce->value); + } + + /* Check for long form: match { .... } / mask { .... } */ + for (cep = ce->items; cep; cep = cep->next) + { + if (!conf_match_item(conf, cep, &s) && !cep->value && !cep->items) + { + /* Valueless? Then it must be a mask like 10.0.0.0/8 */ + unreal_add_masks(&s->mask, cep); + add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", cep->name); + } + } + return 1; +} + +int _conf_security_group(ConfigFile *conf, ConfigEntry *ce) +{ + ConfigEntry *cep; + SecurityGroup *s = add_security_group(ce->value, 1); + + for (cep = ce->items; cep; cep = cep->next) + { + if (!strcmp(cep->name, "priority")) + { + s->priority = atoi(cep->value); + DelListItem(s, securitygroups); + AddListItemPrio(s, securitygroups, s->priority); + } else + conf_match_item(conf, cep, &s); + } + return 1; +} + +/** Check if the name of the security-group contains only valid characters. + * @param name The name of the group + * @returns 1 if name is valid, 0 if not (eg: illegal characters) + */ +int security_group_valid_name(const char *name) +{ + const char *p; + + if (strlen(name) > SECURITYGROUPLEN) + return 0; /* Too long */ + + for (p = name; *p; p++) + { + if (!isalnum(*p) && !strchr("_-", *p)) + return 0; /* Character not allowed */ + } + return 1; +} + +/** Find a security-group. + * @param name The name of the security group + * @returns A SecurityGroup struct, or NULL if not found. + */ +SecurityGroup *find_security_group(const char *name) +{ + SecurityGroup *s; + for (s = securitygroups; s; s = s->next) + if (!strcasecmp(name, s->name)) + return s; + return NULL; +} + +/** Checks if a security-group exists. + * This function takes the 'unknown-users' magic group into account as well. + * @param name The name of the security group + * @returns 1 if it exists, 0 if not + */ +int security_group_exists(const char *name) +{ + if (!strcmp(name, "unknown-users") || find_security_group(name)) + return 1; + return 0; +} + +/** Add a new security-group and add it to the list, but search for existing one first. + * @param name The name of the security group + * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list) + */ +SecurityGroup *add_security_group(const char *name, int priority) +{ + SecurityGroup *s = find_security_group(name); + + /* Existing? */ + if (s) + return s; + + /* Otherwise, create a new entry */ + s = safe_alloc(sizeof(SecurityGroup)); + strlcpy(s->name, name, sizeof(s->name)); + s->priority = priority; + AddListItemPrio(s, securitygroups, priority); + return s; +} + +/** Free a SecurityGroup struct */ +void free_security_group(SecurityGroup *s) +{ + if (s == NULL) + return; + unreal_delete_masks(s->mask); + unreal_delete_masks(s->exclude_mask); + free_entire_name_list(s->security_group); + free_entire_name_list(s->exclude_security_group); + free_entire_name_list(s->ip); + free_entire_name_list(s->exclude_ip); + free_nvplist(s->extended); + free_nvplist(s->exclude_extended); + free_nvplist(s->printable_list); + safe_free(s); +} + +/** Initialize the default security-group blocks */ +void set_security_group_defaults(void) +{ + SecurityGroup *s, *s_next; + + /* First free all security groups */ + for (s = securitygroups; s; s = s_next) + { + s_next = s->next; + free_security_group(s); + } + securitygroups = NULL; + + /* Default group: webirc */ + s = add_security_group("webirc-users", 50); + s->webirc = 1; + + /* Default group: known-users */ + s = add_security_group("known-users", 100); + s->identified = 1; + s->reputation_score = 25; + s->webirc = 0; + + /* Default group: tls-and-known-users */ + s = add_security_group("tls-and-known-users", 200); + s->identified = 1; + s->reputation_score = 25; + s->webirc = 0; + s->tls = 1; + + /* Default group: tls-users */ + s = add_security_group("tls-users", 300); + s->tls = 1; +} + +int user_matches_extended_list(Client *client, NameValuePrioList *e) +{ + Extban *extban; + BanContext b; + + for (; e; e = e->next) + { + extban = findmod_by_bantype_raw(e->name, strlen(e->name)); + if (!extban || + !(extban->options & EXTBOPT_TKL) || + !(extban->is_banned_events & BANCHK_TKL)) + { + continue; /* extban not found or of incorrect type */ + } + + memset(&b, 0, sizeof(BanContext)); + b.client = client; + b.banstr = e->value; + b.ban_check_types = BANCHK_TKL; + if (extban->is_banned(&b)) + return 1; + } + + return 0; +} + +int test_extended_list(Extban *extban, ConfigEntry *cep, int *errors) +{ + BanContext b; + + if (cep->value) + { + memset(&b, 0, sizeof(BanContext)); + b.banstr = cep->value; + b.ban_check_types = BANCHK_TKL; + b.what = MODE_ADD; + if (!extban->conv_param(&b, extban)) + { + config_error("%s:%i: %s has an invalid value", + cep->file->filename, cep->line_number, cep->name); + *errors = *errors + 1; + return 0; + } + } + + for (cep = cep->items; cep; cep = cep->next) + { + memset(&b, 0, sizeof(BanContext)); + b.banstr = cep->name; + b.ban_check_types = BANCHK_TKL; + b.what = MODE_ADD; + if (!extban->conv_param(&b, extban)) + { + config_error("%s:%i: %s has an invalid value", + cep->file->filename, cep->line_number, cep->name); + *errors = *errors + 1; + return 0; + } + } + + return 1; +} + +/** Returns 1 if the user is allowed by any of the security groups in the named list. + * This is only used by security-group::security-group and + * security-group::exclude-security-group. + * @param client Client to check + * @param l The NameList + * @returns 1 if any of the security groups match, 0 if none of them matched. + */ +int user_allowed_by_security_group_list(Client *client, NameList *l) +{ + for (; l; l = l->next) + if (user_allowed_by_security_group_name(client, l->name)) + return 1; + return 0; +} + +/** Returns 1 if the user is OK as far as the security-group is concerned. + * @param client The client to check + * @param s The security-group to check against + * @retval 1 if user is allowed by security-group, 0 if not. + */ +int user_allowed_by_security_group(Client *client, SecurityGroup *s) +{ + static int recursion_security_group = 0; + + /* Allow NULL securitygroup, makes it easier in the code elsewhere */ + if (!s) + return 0; + + if (recursion_security_group > 8) + { + unreal_log(ULOG_WARNING, "main", "SECURITY_GROUP_LOOP_DETECTED", client, + "Loop detected while processing security-group '$security_group' -- " + "are you perhaps referencing a security-group from a security-group?", + log_data_string("security_group", s->name)); + return 0; + } + recursion_security_group++; + + /* DO NOT USE 'return' IN CODE BELOW!!!!!!!!! + * - use 'goto user_not_allowed' to reject + * - use 'goto user_allowed' to accept + */ + + /* Process EXCLUSION criteria first... */ + if (s->exclude_identified && IsLoggedIn(client)) + goto user_not_allowed; + if (s->exclude_webirc && moddata_client_get(client, "webirc")) + goto user_not_allowed; + if ((s->exclude_reputation_score > 0) && (GetReputation(client) >= s->exclude_reputation_score)) + goto user_not_allowed; + if ((s->exclude_reputation_score < 0) && (GetReputation(client) < 0 - s->exclude_reputation_score)) + goto user_not_allowed; + if (s->exclude_connect_time != 0) + { + long connect_time = get_connected_time(client); + if ((s->exclude_connect_time > 0) && (connect_time >= s->exclude_connect_time)) + goto user_not_allowed; + if ((s->exclude_connect_time < 0) && (connect_time < 0 - s->exclude_connect_time)) + goto user_not_allowed; + } + if (s->exclude_tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client)))) + goto user_not_allowed; + if (s->exclude_mask && unreal_mask_match(client, s->exclude_mask)) + goto user_not_allowed; + if (s->exclude_ip && unreal_match_iplist(client, s->exclude_ip)) + goto user_not_allowed; + if (s->exclude_security_group && user_allowed_by_security_group_list(client, s->exclude_security_group)) + goto user_not_allowed; + if (s->exclude_extended && user_matches_extended_list(client, s->exclude_extended)) + goto user_not_allowed; + + /* Then process INCLUSION criteria... */ + if (s->identified && IsLoggedIn(client)) + goto user_allowed; + if (s->webirc && moddata_client_get(client, "webirc")) + goto user_allowed; + if ((s->reputation_score > 0) && (GetReputation(client) >= s->reputation_score)) + goto user_allowed; + if ((s->reputation_score < 0) && (GetReputation(client) < 0 - s->reputation_score)) + goto user_allowed; + if (s->connect_time != 0) + { + long connect_time = get_connected_time(client); + if ((s->connect_time > 0) && (connect_time >= s->connect_time)) + goto user_allowed; + if ((s->connect_time < 0) && (connect_time < 0 - s->connect_time)) + goto user_allowed; + } + if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client)))) + goto user_allowed; + if (s->mask && unreal_mask_match(client, s->mask)) + goto user_allowed; + if (s->ip && unreal_match_iplist(client, s->ip)) + goto user_allowed; + if (s->security_group && user_allowed_by_security_group_list(client, s->security_group)) + goto user_allowed; + if (s->extended && user_matches_extended_list(client, s->extended)) + goto user_allowed; + +user_not_allowed: + recursion_security_group--; + return 0; + +user_allowed: + recursion_security_group--; + return 1; +} + +/** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version. + * @param client The client to check + * @param secgroupname The name of the security-group to check against + * @retval 1 if user is allowed by security-group, 0 if not. + */ +int user_allowed_by_security_group_name(Client *client, const char *secgroupname) +{ + SecurityGroup *s; + + /* Handle the magical 'unknown-users' case. */ + if (!strcmp(secgroupname, "unknown-users")) + { + /* This is simply the inverse of 'known-users' */ + s = find_security_group("known-users"); + if (!s) + return 0; /* that's weird!? pretty impossible. */ + return !user_allowed_by_security_group(client, s); + } + + /* Find the group and evaluate it */ + s = find_security_group(secgroupname); + if (!s) + return 0; /* security group not found: no match */ + return user_allowed_by_security_group(client, s); +} + +/** Get comma separated list of matching security groups for 'client'. + * This is usually only used for displaying purposes. + * @returns string like "unknown-users,tls-users" from a static buffer. + */ +const char *get_security_groups(Client *client) +{ + SecurityGroup *s; + static char buf[512]; + + *buf = '\0'; + + /* We put known-users or unknown-users at the beginning. + * The latter is special and doesn't actually exist + * in the linked list, hence the special code here, + * and again later in the for loop to skip it. + */ + if (user_allowed_by_security_group_name(client, "known-users")) + strlcat(buf, "known-users,", sizeof(buf)); + else + strlcat(buf, "unknown-users,", sizeof(buf)); + + for (s = securitygroups; s; s = s->next) + { + if (strcmp(s->name, "known-users") && + user_allowed_by_security_group(client, s)) + { + strlcat(buf, s->name, sizeof(buf)); + strlcat(buf, ",", sizeof(buf)); + } + } + + if (*buf) + buf[strlen(buf)-1] = '\0'; + return buf; +} diff --git a/src/serv.c b/src/serv.c index 8f8bdd9..fbfa5c2 100644 --- a/src/serv.c +++ b/src/serv.c @@ -1208,7 +1208,9 @@ void lost_server_link(Client *client, const char *tls_error_string) if (client->server->conf) { unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client, - "Unable to link with server $client [$link_block.ip:$link_block.port]: $tls_error_string", + client->server->conf->outgoing.file + ? "Unable to link with server $client [$link_block.file]: $tls_error_string" + : "Unable to link with server $client [$link_block.ip:$link_block.port]: $tls_error_string", log_data_string("tls_error_string", tls_error_string), log_data_link_block(client->server->conf)); } else { @@ -1221,7 +1223,9 @@ void lost_server_link(Client *client, const char *tls_error_string) if (client->server->conf) { unreal_log(ULOG_ERROR, "link", "LINK_ERROR_CONNECT", client, - "Unable to link with server $client [$link_block.ip:$link_block.port]: $socket_error", + client->server->conf->outgoing.file + ? "Unable to link with server $client [$link_block.file]: $socket_error" + : "Unable to link with server $client [$link_block.ip:$link_block.port]: $socket_error", log_data_socket_error(client->local->fd), log_data_link_block(client->server->conf)); } else { diff --git a/src/socket.c b/src/socket.c index 0191214..456f841 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1300,11 +1300,11 @@ int deliver_it(Client *client, char *str, int len, int *want_read) } /** Initiate an outgoing connection, the actual connect() call. */ -int unreal_connect(int fd, const char *ip, int port, int ipv6) +int unreal_connect(int fd, const char *ip, int port, SocketType socket_type) { int n; - if (ipv6) + if (socket_type == SOCKET_TYPE_IPV6) { struct sockaddr_in6 server; memset(&server, 0, sizeof(server)); @@ -1312,13 +1312,22 @@ int unreal_connect(int fd, const char *ip, int port, int ipv6) inet_pton(AF_INET6, ip, &server.sin6_addr); server.sin6_port = htons(port); n = connect(fd, (struct sockaddr *)&server, sizeof(server)); - } else { + } + else if (socket_type == SOCKET_TYPE_IPV4) + { struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; inet_pton(AF_INET, ip, &server.sin_addr); server.sin_port = htons(port); n = connect(fd, (struct sockaddr *)&server, sizeof(server)); + } else + { + struct sockaddr_un server; + memset(&server, 0, sizeof(server)); + server.sun_family = AF_UNIX; + strlcpy(server.sun_path, ip, sizeof(server.sun_path)); + n = connect(fd, (struct sockaddr *)&server, sizeof(server)); } #ifndef _WIN32 diff --git a/src/support.c b/src/support.c index cf88e94..931c531 100644 --- a/src/support.c +++ b/src/support.c @@ -1269,7 +1269,7 @@ void buildvarstring(const char *inbuf, char *outbuf, size_t len, const char *nam /* find variable name in list */ found = 0; for (cnt = 0; name[cnt]; cnt++) - if (!strncasecmp(name[cnt], i, p - i)) + if (!strncasecmp(name[cnt], i, strlen(name[cnt]))) { /* Found */ found = 1; diff --git a/src/tls.c b/src/tls.c index b7b2429..c127354 100644 --- a/src/tls.c +++ b/src/tls.c @@ -354,7 +354,7 @@ SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server) if (SSL_CTX_set_ciphersuites(ctx, tlsoptions->ciphersuites) == 0) { unreal_log(ULOG_ERROR, "config", "TLS_INVALID_CIPHERSUITES_LIST", NULL, - "Failed to set TLS ciphersuites list '$tls_ciphers_list'\n$tls_error.all", + "Failed to set TLS ciphersuites list '$tls_ciphersuites_list'\n$tls_error.all", log_data_string("tls_ciphersuites_list", tlsoptions->ciphersuites), log_data_tls_error()); goto fail; diff --git a/src/user.c b/src/user.c index 1f9cbb9..2492d47 100644 --- a/src/user.c +++ b/src/user.c @@ -30,7 +30,6 @@ #include "unrealircd.h" MODVAR int dontspread = 0; -static char buf[BUFSIZE]; /** Inhibit labeled/response reply. This means it will result in an empty ACK * because we cannot handle the command via labeled-reponse. Rare, but @@ -69,11 +68,9 @@ void iNAH_host(Client *client, const char *host) safe_strdup(client->user->virthost, host); if (MyConnect(client)) sendto_server(NULL, 0, 0, NULL, ":%s SETHOST :%s", client->id, client->user->virthost); - client->umodes |= UMODE_SETHOST; + client->umodes |= UMODE_SETHOST|UMODE_HIDE; userhost_changed(client); - - sendnumeric(client, RPL_HOSTHIDDEN, client->user->virthost); } /** Convert a user mode string to a bitmask - only used by config. @@ -420,6 +417,7 @@ void build_umode_string(Client *client, long old, long sendmask, char *umode_buf void send_umode_out(Client *client, int show_to_user, long old) { Client *acptr; + char buf[512]; build_umode_string(client, old, SEND_UMODES, buf); @@ -739,194 +737,26 @@ int hide_idle_time(Client *client, Client *target) } } -/** Check if the name of the security-group contains only valid characters. - * @param name The name of the group - * @returns 1 if name is valid, 0 if not (eg: illegal characters) - */ -int security_group_valid_name(const char *name) -{ - const char *p; - - if (strlen(name) > SECURITYGROUPLEN) - return 0; /* Too long */ - - for (p = name; *p; p++) - { - if (!isalnum(*p) && !strchr("_-", *p)) - return 0; /* Character not allowed */ - } - return 1; -} - -/** Find a security-group. - * @param name The name of the security group - * @returns A SecurityGroup struct, or NULL if not found. - */ -SecurityGroup *find_security_group(const char *name) -{ - SecurityGroup *s; - for (s = securitygroups; s; s = s->next) - if (!strcasecmp(name, s->name)) - return s; - return NULL; -} - -/** Checks if a security-group exists. - * This function takes the 'unknown-users' magic group into account as well. - * @param name The name of the security group - * @returns 1 if it exists, 0 if not - */ -int security_group_exists(const char *name) -{ - if (!strcmp(name, "unknown-users") || find_security_group(name)) - return 1; - return 0; -} - -/** Add a new security-group and add it to the list, but search for existing one first. - * @param name The name of the security group - * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list) - */ -SecurityGroup *add_security_group(const char *name, int priority) -{ - SecurityGroup *s = find_security_group(name); - - /* Existing? */ - if (s) - return s; - - /* Otherwise, create a new entry */ - s = safe_alloc(sizeof(SecurityGroup)); - strlcpy(s->name, name, sizeof(s->name)); - s->priority = priority; - AddListItemPrio(s, securitygroups, priority); - return s; -} - -/** Free a SecurityGroup struct */ -void free_security_group(SecurityGroup *s) -{ - unreal_delete_masks(s->include_mask); - safe_free(s); -} - -/** Initialize the default security-group blocks */ -void set_security_group_defaults(void) -{ - SecurityGroup *s, *s_next; - - /* First free all security groups */ - for (s = securitygroups; s; s = s_next) - { - s_next = s->next; - free_security_group(s); - } - securitygroups = NULL; - - /* Default group: webirc */ - s = add_security_group("webirc-users", 50); - s->webirc = 1; - - /* Default group: known-users */ - s = add_security_group("known-users", 100); - s->identified = 1; - s->reputation_score = 25; - s->webirc = 0; - - /* Default group: tls-and-known-users */ - s = add_security_group("tls-and-known-users", 200); - s->identified = 1; - s->reputation_score = 25; - s->webirc = 0; - s->tls = 1; - - /* Default group: tls-users */ - s = add_security_group("tls-users", 300); - s->tls = 1; -} - -/** Returns 1 if the user is OK as far as the security-group is concerned. +/** Get how long a client is connected to IRC. * @param client The client to check - * @param s The security-group to check against - * @retval 1 if user is allowed by security-group, 0 if not. + * @returns how long the client is connected to IRC (number of seconds) */ -int user_allowed_by_security_group(Client *client, SecurityGroup *s) +long get_connected_time(Client *client) { - if (s->identified && IsLoggedIn(client)) - return 1; - if (s->webirc && moddata_client_get(client, "webirc")) - return 1; - if (s->reputation_score && (GetReputation(client) >= s->reputation_score)) - return 1; - if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client)))) - return 1; - if (s->include_mask && unreal_mask_match(client, s->include_mask)) - return 1; + const char *str; + long connect_time = 0; + + /* Shortcut for local clients */ + if (client->local) + return TStime() - client->local->creationtime; + + /* Otherwise, hopefully available through this... */ + str = moddata_client_get(client, "creationtime"); + if (!BadPtr(str) && (*str != '0')) + return TStime() - atoll(str); return 0; } -/** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version. - * @param client The client to check - * @param secgroupname The name of the security-group to check against - * @retval 1 if user is allowed by security-group, 0 if not. - */ -int user_allowed_by_security_group_name(Client *client, const char *secgroupname) -{ - SecurityGroup *s; - - /* Handle the magical 'unknown-users' case. */ - if (!strcmp(secgroupname, "unknown-users")) - { - /* This is simply the inverse of 'known-users' */ - s = find_security_group("known-users"); - if (!s) - return 0; /* that's weird!? pretty impossible. */ - return !user_allowed_by_security_group(client, s); - } - - /* Find the group and evaluate it */ - s = find_security_group(secgroupname); - if (!s) - return 0; /* security group not found: no match */ - return user_allowed_by_security_group(client, s); -} - -/** Get comma separated list of matching security groups for 'client'. - * This is usually only used for displaying purposes. - * @returns string like "unknown-users,tls-users" from a static buffer. - */ -const char *get_security_groups(Client *client) -{ - SecurityGroup *s; - static char buf[512]; - - *buf = '\0'; - - /* We put known-users or unknown-users at the beginning. - * The latter is special and doesn't actually exist - * in the linked list, hence the special code here, - * and again later in the for loop to skip it. - */ - if (user_allowed_by_security_group_name(client, "known-users")) - strlcat(buf, "known-users,", sizeof(buf)); - else - strlcat(buf, "unknown-users,", sizeof(buf)); - - for (s = securitygroups; s; s = s->next) - { - if (strcmp(s->name, "known-users") && - user_allowed_by_security_group(client, s)) - { - strlcat(buf, s->name, sizeof(buf)); - strlcat(buf, ",", sizeof(buf)); - } - } - - if (*buf) - buf[strlen(buf)-1] = '\0'; - return buf; -} - /** Return extended information about user for the "Client connecting" line. * @returns A string such as "[secure] [reputation: 5]", never returns NULL. */ @@ -966,6 +796,10 @@ const char *get_connect_extinfo(Client *client) secgroups = get_security_groups(client); if (secgroups) add_nvplist(&list, 100, "security-groups", secgroups); + + /* tkl shunned */ + if (IsShunned(client)) + add_nvplist(&list, 110, "shunned", NULL); *retbuf = '\0'; for (e = list; e; e = e->next) @@ -977,8 +811,8 @@ const char *get_connect_extinfo(Client *client) strlcat(retbuf, tmp, sizeof(retbuf)); } /* Cut off last space (unless empty string) */ - if (*buf) - buf[strlen(buf)-1] = '\0'; + if (*retbuf) + retbuf[strlen(retbuf)-1] = '\0'; /* Free the list, as it was only used to build retbuf */ free_nvplist(list); diff --git a/src/version.c.SH b/src/version.c.SH index ce6bda7..117873a 100644 --- a/src/version.c.SH +++ b/src/version.c.SH @@ -7,7 +7,7 @@ echo "Extracting src/version.c..." if [ -d ../.git ]; then SUFFIX="-$(git rev-parse --short HEAD)" fi -id="6.0.3$SUFFIX" +id="6.0.4.2$SUFFIX" echo "$id" if test -r version.c diff --git a/src/windows/UnrealIRCd.exe.manifest b/src/windows/UnrealIRCd.exe.manifest index d1f0ea9..1888419 100644 --- a/src/windows/UnrealIRCd.exe.manifest +++ b/src/windows/UnrealIRCd.exe.manifest @@ -3,7 +3,7 @@ Internet Relay Chat Daemon diff --git a/src/windows/gui.c b/src/windows/gui.c index 4af5932..d7b6d5d 100644 --- a/src/windows/gui.c +++ b/src/windows/gui.c @@ -536,25 +536,7 @@ LRESULT CALLBACK MainDLG(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) return AskCloseUnreal(hDlg); case IDM_RHALL: MessageBox(NULL, "Rehashing all files", "Rehashing", MB_OK); - request_rehash(NULL); - break; - case IDM_RHCONF: - MessageBox(NULL, "Rehashing the Config file", "Rehashing", MB_OK); - request_rehash(NULL); - break; - case IDM_RHMOTD: - { - MessageBox(NULL, "Rehashing all MOTD and Rules files", "Rehashing", MB_OK); - rehash_motdrules(); - break; - } - case IDM_RHOMOTD: - MessageBox(NULL, "Rehashing the OperMOTD", "Rehashing", MB_OK); - read_motd(conf_files->opermotd_file, &opermotd); - break; - case IDM_RHBMOTD: - MessageBox(NULL, "Rehashing the BotMOTD", "Rehashing", MB_OK); - read_motd(conf_files->botmotd_file, &botmotd); + dorehash = 1; break; case IDM_LICENSE: DialogBox(hInst, "FromVar", hDlg, (DLGPROC)LicenseDLG); diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss index 30d45d3..bc77e9c 100755 --- a/src/windows/unrealinst.iss +++ b/src/windows/unrealinst.iss @@ -6,7 +6,7 @@ [Setup] AppName=UnrealIRCd 6 -AppVerName=UnrealIRCd 6.0.3 +AppVerName=UnrealIRCd 6.0.4.2 AppPublisher=UnrealIRCd Team AppPublisherURL=https://www.unrealircd.org AppSupportURL=https://www.unrealircd.org diff --git a/src/windows/wingui.rc b/src/windows/wingui.rc index c3023be..d190e72 100644 --- a/src/windows/wingui.rc +++ b/src/windows/wingui.rc @@ -362,12 +362,7 @@ MENU_REHASH MENU BEGIN POPUP "Rehash" BEGIN - MENUITEM "&All Files", IDM_RHALL - MENUITEM SEPARATOR - MENUITEM "&Config file", IDM_RHCONF - MENUITEM "&All MOTD and Rules", IDM_RHMOTD - MENUITEM "&OperMOTD", IDM_RHOMOTD - MENUITEM "&BotMOTD", IDM_RHBMOTD + MENUITEM "Rehash &All Files", IDM_RHALL END END